From 61ac791de59e6e0f6df69b7e8c2d2b8c247368c3 Mon Sep 17 00:00:00 2001 From: jazz405 Date: Sun, 30 Nov 2025 15:42:54 -0500 Subject: [PATCH 01/20] initial warlock cleanup and moves --- sim/warlock/affliction/darksoul_misery.go | 47 --- sim/warlock/affliction/haunt.go | 98 ------ sim/warlock/affliction/hotfixes.go | 36 --- sim/warlock/affliction/malefic_effect.go | 76 ----- sim/warlock/affliction/malefic_grasp.go | 74 ----- sim/warlock/affliction/potent_afflictions.go | 20 -- sim/warlock/affliction/soul_swap.go | 177 ----------- sim/warlock/affliction/soulburn.go | 65 ---- sim/warlock/{affliction => }/agony.go | 15 +- sim/warlock/demonology/carrion_swarm.go | 47 --- sim/warlock/demonology/chaos_wave.go | 53 ---- sim/warlock/demonology/corruption.go | 16 - sim/warlock/demonology/darksoul_knowledge.go | 48 --- sim/warlock/demonology/drain_life.go | 17 - sim/warlock/demonology/felflame.go | 14 - sim/warlock/demonology/hand_of_guldan.go | 107 ------- sim/warlock/demonology/hotfixes.go | 47 --- sim/warlock/demonology/immolation_aura.go | 66 ---- sim/warlock/demonology/metamorphosis.go | 87 ------ sim/warlock/demonology/molten_core.go | 70 ----- sim/warlock/demonology/talents.go | 85 ----- sim/warlock/demonology/touch_of_chaos.go | 68 ---- sim/warlock/demonology/void_ray.go | 40 --- sim/warlock/demonology/wild_imp.go | 248 --------------- sim/warlock/destruction/backdraft.go | 66 ---- sim/warlock/destruction/chaos_bolt.go | 78 ----- sim/warlock/destruction/chaotic_energy.go | 26 -- .../destruction/dark_soul_instability.go | 44 --- sim/warlock/destruction/emberstorm.go | 37 --- sim/warlock/destruction/felflame.go | 11 - sim/warlock/destruction/fire_and_brimstone.go | 48 --- .../fire_and_brimstone_conflagrate.go | 69 ---- .../fire_and_brimstone_immolate.go | 108 ------- .../fire_and_brimstone_incinerate.go | 74 ----- sim/warlock/destruction/havoc.go | 104 ------ sim/warlock/{demonology => }/doom.go | 19 +- sim/warlock/{affliction => }/drain_soul.go | 21 +- sim/warlock/felflame.go | 43 --- sim/warlock/{destruction => }/immolate.go | 0 sim/warlock/{destruction => }/incinerate.go | 19 +- sim/warlock/items.go | 295 +++--------------- sim/warlock/{affliction => }/nightfall.go | 0 sim/warlock/{destruction => }/rain_of_fire.go | 0 .../{affliction => }/seed_of_corruption.go | 0 sim/warlock/{demonology => }/shadowbolt.go | 0 sim/warlock/{demonology => }/soulfire.go | 0 46 files changed, 83 insertions(+), 2600 deletions(-) delete mode 100644 sim/warlock/affliction/darksoul_misery.go delete mode 100644 sim/warlock/affliction/haunt.go delete mode 100644 sim/warlock/affliction/hotfixes.go delete mode 100644 sim/warlock/affliction/malefic_effect.go delete mode 100644 sim/warlock/affliction/malefic_grasp.go delete mode 100644 sim/warlock/affliction/potent_afflictions.go delete mode 100644 sim/warlock/affliction/soul_swap.go delete mode 100644 sim/warlock/affliction/soulburn.go rename sim/warlock/{affliction => }/agony.go (80%) delete mode 100644 sim/warlock/demonology/carrion_swarm.go delete mode 100644 sim/warlock/demonology/chaos_wave.go delete mode 100644 sim/warlock/demonology/corruption.go delete mode 100644 sim/warlock/demonology/darksoul_knowledge.go delete mode 100644 sim/warlock/demonology/drain_life.go delete mode 100644 sim/warlock/demonology/felflame.go delete mode 100644 sim/warlock/demonology/hand_of_guldan.go delete mode 100644 sim/warlock/demonology/hotfixes.go delete mode 100644 sim/warlock/demonology/immolation_aura.go delete mode 100644 sim/warlock/demonology/metamorphosis.go delete mode 100644 sim/warlock/demonology/molten_core.go delete mode 100644 sim/warlock/demonology/talents.go delete mode 100644 sim/warlock/demonology/touch_of_chaos.go delete mode 100644 sim/warlock/demonology/void_ray.go delete mode 100644 sim/warlock/demonology/wild_imp.go delete mode 100644 sim/warlock/destruction/backdraft.go delete mode 100644 sim/warlock/destruction/chaos_bolt.go delete mode 100644 sim/warlock/destruction/chaotic_energy.go delete mode 100644 sim/warlock/destruction/dark_soul_instability.go delete mode 100644 sim/warlock/destruction/emberstorm.go delete mode 100644 sim/warlock/destruction/felflame.go delete mode 100644 sim/warlock/destruction/fire_and_brimstone.go delete mode 100644 sim/warlock/destruction/fire_and_brimstone_conflagrate.go delete mode 100644 sim/warlock/destruction/fire_and_brimstone_immolate.go delete mode 100644 sim/warlock/destruction/fire_and_brimstone_incinerate.go delete mode 100644 sim/warlock/destruction/havoc.go rename sim/warlock/{demonology => }/doom.go (73%) rename sim/warlock/{affliction => }/drain_soul.go (76%) delete mode 100644 sim/warlock/felflame.go rename sim/warlock/{destruction => }/immolate.go (100%) rename sim/warlock/{destruction => }/incinerate.go (70%) rename sim/warlock/{affliction => }/nightfall.go (100%) rename sim/warlock/{destruction => }/rain_of_fire.go (100%) rename sim/warlock/{affliction => }/seed_of_corruption.go (100%) rename sim/warlock/{demonology => }/shadowbolt.go (100%) rename sim/warlock/{demonology => }/soulfire.go (100%) diff --git a/sim/warlock/affliction/darksoul_misery.go b/sim/warlock/affliction/darksoul_misery.go deleted file mode 100644 index 5a65fd1f44..0000000000 --- a/sim/warlock/affliction/darksoul_misery.go +++ /dev/null @@ -1,47 +0,0 @@ -package affliction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -func (affliction *AfflictionWarlock) registerDarkSoulMisery() { - buff := affliction.RegisterAura(core.Aura{ - Label: "Dark Soul: Misery", - ActionID: core.ActionID{SpellID: 113860}, - Duration: time.Second * 20, - }). - AttachMultiplyCastSpeed(1.3). - AttachMultiplyAttackSpeed(1.3) - - spell := affliction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 113860}, - DamageMultiplier: 1, - ProcMask: core.ProcMaskEmpty, - SpellSchool: core.SpellSchoolShadow, - ClassSpellMask: warlock.WarlockSpellDarkSoulMisery, - Cast: core.CastConfig{ - DefaultCast: core.Cast{NonEmpty: true}, - CD: core.Cooldown{ - Timer: affliction.NewTimer(), - Duration: time.Minute * 2, - }, - }, - ManaCost: core.ManaCostOptions{ - BaseCostPercent: 5, - }, - RechargeTime: time.Minute * 2, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - buff.Activate(sim) - }, - RelatedSelfBuff: buff, - }) - - affliction.AddMajorCooldown(core.MajorCooldown{ - Spell: spell, - Priority: core.CooldownPriorityDefault, - Type: core.CooldownTypeDPS, - }) -} diff --git a/sim/warlock/affliction/haunt.go b/sim/warlock/affliction/haunt.go deleted file mode 100644 index b6965b0afa..0000000000 --- a/sim/warlock/affliction/haunt.go +++ /dev/null @@ -1,98 +0,0 @@ -package affliction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -// Damage Done By Caster setup -const ( - DDBC_Haunt int = iota - DDBC_Total -) - -const HauntSpellID = 48181 - -const hauntScale = 2.625 -const hauntCoeff = 2.625 - -func (affliction *AfflictionWarlock) registerHaunt() { - actionID := core.ActionID{SpellID: HauntSpellID} - - affliction.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellHaunt, - MissileSpeed: 20, - - ManaCost: core.ManaCostOptions{BaseCostPercent: 12}, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - CastTime: 1500 * time.Millisecond, - }, - }, - - DamageMultiplier: 1, - CritMultiplier: affliction.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: hauntCoeff, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return affliction.SoulShards.CanSpend(1) - }, - - // Despite not being a DoT, Haunt maintains a hidden 2s tick - // timer with a Pandemic effect that grants additional time to - // debuff refreshes. In order to enable the pandemic refresh, we - // will register the Haunt debuff as a non-warlock DoT. - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Haunt-" + affliction.Label, - ActionID: actionID, - - OnGain: func(aura *core.Aura, sim *core.Simulation) { - core.EnableDamageDoneByCaster(DDBC_Haunt, DDBC_Total, affliction.AttackTables[aura.Unit.UnitIndex], hauntDamageDoneByCasterHandler) - }, - - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - core.DisableDamageDoneByCaster(DDBC_Haunt, affliction.AttackTables[aura.Unit.UnitIndex]) - }, - }, - - NumberOfTicks: 4, - TickLength: 2 * time.Second, - AffectedByCastSpeed: false, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := affliction.CalcScalingSpellDmg(hauntScale) - result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - affliction.SoulShards.Spend(sim, 1, spell.ActionID) - spell.WaitTravelTime(sim, func(sim *core.Simulation) { - spell.DealDamage(sim, result) - if result.Landed() { - spell.Dot(target).Apply(sim) - } - }) - }, - }) -} - -func hauntDamageDoneByCasterHandler(sim *core.Simulation, spell *core.Spell, attackTable *core.AttackTable) float64 { - if spell.Matches(warlock.WarlockSpellSeedOfCorruption | - warlock.WarlockSpellCorruption | - warlock.WarlockSpellDrainLife | - warlock.WarlockSpellDrainSoul | - warlock.WarlockSpellMaleficGrasp | - warlock.WarlockSpellAgony | - warlock.WarlockSpellUnstableAffliction) { - return 1.35 - } - - return 1 -} diff --git a/sim/warlock/affliction/hotfixes.go b/sim/warlock/affliction/hotfixes.go deleted file mode 100644 index bb238711c7..0000000000 --- a/sim/warlock/affliction/hotfixes.go +++ /dev/null @@ -1,36 +0,0 @@ -package affliction - -import ( - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -func (affliction *AfflictionWarlock) registerHotfixes() { - - // 2025-07-31 - Agony’s damage over time increased by 5%. - affliction.AddStaticMod(core.SpellModConfig{ - ClassMask: warlock.WarlockSpellAgony, - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.05, - }) - - // 2025-09-22 - Corruption’s damage over time decreased from 33% to 20%. - affliction.AddStaticMod(core.SpellModConfig{ - ClassMask: warlock.WarlockSpellCorruption, - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.20, - }) - - // 2025-07-31 - Malefic Damage increased by 50% - affliction.AddStaticMod(core.SpellModConfig{ - ClassMask: warlock.WarlockSpellMaleficGrasp | warlock.WarlockSpellDrainSoul, - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.5, - }) - - // 2025-07-31 - The damage your Malefic Grasp causes your other DoTs to deal increased to 50% (was 30%). - affliction.MaleficGraspMaleficEffectMultiplier += 0.2 - // 2025-07-31 - The damage your Drain Soul causes your other DoTs to deal increased to 100% (was 60%). - affliction.DrainSoulMaleficEffectMultiplier += 0.4 - -} diff --git a/sim/warlock/affliction/malefic_effect.go b/sim/warlock/affliction/malefic_effect.go deleted file mode 100644 index 240b9ccca1..0000000000 --- a/sim/warlock/affliction/malefic_effect.go +++ /dev/null @@ -1,76 +0,0 @@ -package affliction - -import ( - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -func (affliction *AfflictionWarlock) registerMaleficEffect() { - var procDot *core.Dot - buildSpell := func(id int32) *core.Spell { - return affliction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: id}.WithTag(1), - Flags: core.SpellFlagPassiveSpell | core.SpellFlagNoOnCastComplete | core.SpellFlagNoSpellMods | core.SpellFlagIgnoreAttackerModifiers, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskSpellDamage, - ClassSpellMask: warlock.WarlockSpellMaleficGrasp, - - ThreatMultiplier: 1, - DamageMultiplier: 1, - CritMultiplier: affliction.DefaultCritMultiplier(), - BonusSpellPower: 0, // used to transmit base damage - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcDamage(sim, target, spell.BonusSpellPower, procDot.OutcomeTickMagicCritNoHitCounter) - spell.DealPeriodicDamage(sim, result) - - // Adjust metrics just for Malefic Effects as it is a edgecase and needs to be handled manually - if result.DidCrit() { - spell.SpellMetrics[result.Target.UnitIndex].CritTicks++ - } else { - spell.SpellMetrics[result.Target.UnitIndex].Ticks++ - } - }, - }) - } - - corruptionProc := buildSpell(172) - agonyProc := buildSpell(980) - uaProc := buildSpell(30108) - - procTable := map[*core.Spell]**core.Spell{ - corruptionProc: &affliction.Corruption, - agonyProc: &affliction.Agony, - uaProc: &affliction.UnstableAffliction, - } - - // used to iterate over the map in constant order - procKeys := []*core.Spell{corruptionProc, agonyProc, uaProc} - affliction.ProcMaleficEffect = func(target *core.Unit, coeff float64, sim *core.Simulation) { - - // I don't like it but if sac is specced the damage replication effect specifically is increased by 20% - // Nothing we can do really properly with SpellMod's here nicely - if affliction.Talents.GrimoireOfSacrifice { - coeff *= 1.2 - } - - if affliction.T15_4pc.IsActive() { - coeff *= 1.05 - } - - if affliction.T16_2pc_buff != nil && affliction.T16_2pc_buff.IsActive() { - coeff *= 1.2 - } - - for _, proc := range procKeys { - source := procTable[proc] - dot := (*source).Dot(target) - if !dot.IsActive() { - continue - } - - proc.BonusSpellPower = calculateDoTBaseTickDamage(dot, target) * coeff - procDot = dot - proc.Cast(sim, target) - } - } -} diff --git a/sim/warlock/affliction/malefic_grasp.go b/sim/warlock/affliction/malefic_grasp.go deleted file mode 100644 index 8162febda1..0000000000 --- a/sim/warlock/affliction/malefic_grasp.go +++ /dev/null @@ -1,74 +0,0 @@ -package affliction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -const maleficGraspScale = 0.132 -const maleficGraspCoeff = 0.132 - -func (affliction *AfflictionWarlock) registerMaleficGrasp() { - affliction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 103103}, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL | core.SpellFlagChanneled, - ClassSpellMask: warlock.WarlockSpellMaleficGrasp, - - ManaCost: core.ManaCostOptions{BaseCostPercent: 1.5}, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - DamageMultiplierAdditive: 1, - CritMultiplier: affliction.DefaultCritMultiplier(), - ThreatMultiplier: 1, - - Dot: core.DotConfig{ - Aura: core.Aura{Label: "MaleficGrasp"}, - NumberOfTicks: 4, - TickLength: 1 * time.Second, - AffectedByCastSpeed: true, - HasteReducesDuration: true, - BonusCoefficient: maleficGraspCoeff, - - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, _ bool) { - dot.Snapshot(target, affliction.CalcScalingSpellDmg(maleficGraspScale)) - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - result := dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) - if !result.Landed() { - return - } - - affliction.ProcMaleficEffect(target, affliction.MaleficGraspMaleficEffectMultiplier, sim) - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHitNoHitCounter) - if result.Landed() { - spell.Dot(target).Apply(sim) - } - spell.DealOutcome(sim, result) - }, - - ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, useSnapshot bool) *core.SpellResult { - dot := spell.Dot(target) - if useSnapshot { - result := dot.CalcSnapshotDamage(sim, target, dot.OutcomeExpectedSnapshotCrit) - result.Damage /= dot.TickPeriod().Seconds() - return result - } else { - result := spell.CalcPeriodicDamage(sim, target, affliction.CalcScalingSpellDmg(maleficGraspScale), spell.OutcomeExpectedMagicCrit) - result.Damage /= dot.CalcTickPeriod().Round(time.Millisecond).Seconds() - return result - } - }, - }) -} diff --git a/sim/warlock/affliction/potent_afflictions.go b/sim/warlock/affliction/potent_afflictions.go deleted file mode 100644 index aefc75ff77..0000000000 --- a/sim/warlock/affliction/potent_afflictions.go +++ /dev/null @@ -1,20 +0,0 @@ -package affliction - -import ( - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -func (affliction *AfflictionWarlock) registerPotentAffliction() { - dmgMod := affliction.AddDynamicMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: affliction.getMasteryBonus() / 100, - ClassMask: warlock.WarlockSpellAgony | warlock.WarlockSpellUnstableAffliction | warlock.WarlockSpellCorruption, - }) - - affliction.AddOnMasteryStatChanged(func(_ *core.Simulation, _, _ float64) { - dmgMod.UpdateFloatValue(affliction.getMasteryBonus() / 100) - }) - - dmgMod.Activate() -} diff --git a/sim/warlock/affliction/soul_swap.go b/sim/warlock/affliction/soul_swap.go deleted file mode 100644 index 5a5e244bb1..0000000000 --- a/sim/warlock/affliction/soul_swap.go +++ /dev/null @@ -1,177 +0,0 @@ -package affliction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" -) - -func (affliction *AfflictionWarlock) registerSoulSwap() { - var debuffState map[int32]core.DotState - dotRefs := []**core.Spell{&affliction.Corruption, &affliction.Agony, &affliction.Seed, &affliction.UnstableAffliction} - - inhaleBuff := core.BlockPrepull(affliction.RegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 86211}, - Label: "Soul Swap", - Duration: time.Second * 3, - })) - - // Exhale - affliction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 86213}, - Flags: core.SpellFlagAPL, - ProcMask: core.ProcMaskEmpty, - SpellSchool: core.SpellSchoolShadow, - - ThreatMultiplier: 1, - CritMultiplier: affliction.DefaultCritMultiplier(), - DamageMultiplier: 1, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return inhaleBuff.IsActive() && target != affliction.LastInhaleTarget && !affliction.SoulBurnAura.IsActive() - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - // restore states - for _, spellRef := range dotRefs { - dot := (*spellRef).Dot(target) - state, ok := debuffState[dot.ActionID.SpellID] - if !ok { - // not stored, was not active - continue - } - - (*spellRef).Proc(sim, target) - dot.RestoreState(state, sim) - } - - inhaleBuff.Deactivate(sim) - }, - }) - - // used to not allocate a result for every check - expectedDamage := &core.SpellResult{} - - // we dont use seed in the expected calculations as it's not applied by exhale - expectedDotRefs := []**core.Spell{&affliction.Corruption, &affliction.Agony, &affliction.UnstableAffliction} - - // Inhale - affliction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 86121}.WithTag(1), - Flags: core.SpellFlagAPL, - ProcMask: core.ProcMaskEmpty, - SpellSchool: core.SpellSchoolShadow, - - ThreatMultiplier: 1, - CritMultiplier: affliction.DefaultCritMultiplier(), - DamageMultiplier: 1, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return anyDoTActive(dotRefs, target) && !inhaleBuff.IsActive() && !affliction.SoulBurnAura.IsActive() - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - affliction.LastInhaleTarget = target - debuffState = map[int32]core.DotState{} - - // store states - for _, spellRef := range dotRefs { - dot := (*spellRef).Dot(target) - if dot.IsActive() { - debuffState[dot.ActionID.SpellID] = dot.SaveState(sim) - } - } - - inhaleBuff.Activate(sim) - }, - - ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, useSnapshot bool) *core.SpellResult { - expectedDamage.Damage = 0 - if useSnapshot { - for _, spellRef := range expectedDotRefs { - dot := (*spellRef).Dot(target) - expectedDamage.Damage += dot.Spell.ExpectedTickDamageFromCurrentSnapshot(sim, target) - } - - return expectedDamage - } - - for _, spellRef := range expectedDotRefs { - dot := (*spellRef).Dot(target) - expectedDamage.Damage += dot.Spell.ExpectedTickDamage(sim, target) - } - - return expectedDamage - }, - }) - - // Soulswap: Soulburn - affliction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 86121}.WithTag(2), - Flags: core.SpellFlagAPL, - ProcMask: core.ProcMaskEmpty, - SpellSchool: core.SpellSchoolShadow, - - ThreatMultiplier: 1, - CritMultiplier: affliction.DefaultCritMultiplier(), - DamageMultiplier: 1, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return affliction.SoulBurnAura.IsActive() - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - affliction.Agony.Proc(sim, target) - affliction.Corruption.Proc(sim, target) - affliction.UnstableAffliction.Proc(sim, target) - affliction.SoulBurnAura.Deactivate(sim) - }, - - ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, useSnapshot bool) *core.SpellResult { - expectedDamage.Damage = 0 - if useSnapshot { - for _, spellRef := range expectedDotRefs { - dot := (*spellRef).Dot(target) - expectedDamage.Damage += dot.Spell.ExpectedTickDamageFromCurrentSnapshot(sim, target) - } - - return expectedDamage - } - - for _, spellRef := range expectedDotRefs { - dot := (*spellRef).Dot(target) - expectedDamage.Damage += dot.Spell.ExpectedTickDamage(sim, target) - } - - return expectedDamage - }, - }) -} - -func anyDoTActive(dots []**core.Spell, target *core.Unit) bool { - for _, spellRef := range dots { - if (*spellRef).Dot(target).IsActive() { - return true - } - } - - return false -} diff --git a/sim/warlock/affliction/soulburn.go b/sim/warlock/affliction/soulburn.go deleted file mode 100644 index 5ff9c7a708..0000000000 --- a/sim/warlock/affliction/soulburn.go +++ /dev/null @@ -1,65 +0,0 @@ -package affliction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -func (affliction *AfflictionWarlock) registerSoulburn() { - - castTimeMod := affliction.AddDynamicMod(core.SpellModConfig{ - Kind: core.SpellMod_CastTime_Pct, - ClassMask: warlock.WarlockSpellSummonImp | warlock.WarlockSpellSummonSuccubus | warlock.WarlockSpellSummonFelhunter | warlock.WarlockSpellSoulFire, - FloatValue: -1.0, - }) - - drainLifeCastMod := affliction.AddDynamicMod(core.SpellModConfig{ - Kind: core.SpellMod_CastTime_Pct, - ClassMask: warlock.WarlockSpellDrainLife, - FloatValue: -0.5, - }) - - affliction.SoulBurnAura = affliction.RegisterAura(core.Aura{ - Label: "Soulburn", - ActionID: core.ActionID{SpellID: 74434}, - Duration: core.NeverExpires, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - castTimeMod.Activate() - drainLifeCastMod.Activate() - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - castTimeMod.Deactivate() - drainLifeCastMod.Deactivate() - }, - }) - - affliction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 74434}, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellSoulBurn, - - Cast: core.CastConfig{ - CD: core.Cooldown{ - Timer: affliction.NewTimer(), - Duration: time.Second, - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - affliction.SoulBurnAura.Activate(sim) - - // if we cast this >= 20 sec pre pull we will regenerate the shard - if sim.CurrentTime > -20*time.Second { - affliction.SoulShards.Spend(sim, 1, spell.ActionID) - } - }, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return affliction.SoulShards.CanSpend(1) - }, - }) -} diff --git a/sim/warlock/affliction/agony.go b/sim/warlock/agony.go similarity index 80% rename from sim/warlock/affliction/agony.go rename to sim/warlock/agony.go index 1e4e4b22d5..e9936d9b4f 100644 --- a/sim/warlock/affliction/agony.go +++ b/sim/warlock/agony.go @@ -1,17 +1,16 @@ -package affliction +package warlock import ( "time" "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" ) const agonyScale = 0.0255 const agonyCoeff = 0.0255 -func (affliction *AfflictionWarlock) registerAgony() { - affliction.Agony = affliction.RegisterSpell(core.SpellConfig{ +func (warlock *Warlock) registerAgony() { + warlock.Agony = warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 980}, Flags: core.SpellFlagAPL, ProcMask: core.ProcMaskSpellDamage, @@ -21,7 +20,7 @@ func (affliction *AfflictionWarlock) registerAgony() { ThreatMultiplier: 1, DamageMultiplier: 1, BonusCoefficient: agonyCoeff, - CritMultiplier: affliction.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultCritMultiplier(), Cast: core.CastConfig{ DefaultCast: core.Cast{ @@ -44,7 +43,7 @@ func (affliction *AfflictionWarlock) registerAgony() { AffectedByCastSpeed: true, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, affliction.CalcScalingSpellDmg(agonyScale)) + dot.Snapshot(target, warlock.CalcScalingSpellDmg(agonyScale)) }, BonusCoefficient: agonyCoeff, @@ -65,7 +64,7 @@ func (affliction *AfflictionWarlock) registerAgony() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { if spell.CalcAndDealOutcome(sim, target, spell.OutcomeMagicHit).Landed() { - affliction.ApplyDotWithPandemic(spell.Dot(target), sim) + warlock.ApplyDotWithPandemic(spell.Dot(target), sim) spell.Dot(target).AddStack(sim) } }, @@ -80,7 +79,7 @@ func (affliction *AfflictionWarlock) registerAgony() { result.Damage /= dot.TickPeriod().Seconds() return result } else { - result := spell.CalcPeriodicDamage(sim, target, affliction.CalcScalingSpellDmg(agonyScale), spell.OutcomeExpectedMagicCrit) + result := spell.CalcPeriodicDamage(sim, target, warlock.CalcScalingSpellDmg(agonyScale), spell.OutcomeExpectedMagicCrit) result.Damage *= 10 result.Damage /= dot.CalcTickPeriod().Round(time.Millisecond).Seconds() return result diff --git a/sim/warlock/demonology/carrion_swarm.go b/sim/warlock/demonology/carrion_swarm.go deleted file mode 100644 index 12ab0ebdec..0000000000 --- a/sim/warlock/demonology/carrion_swarm.go +++ /dev/null @@ -1,47 +0,0 @@ -package demonology - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -const carrionSwarmScale = 0.5 -const carrionSwarmVariance = 0.1 -const carrionSwarmCoeff = 0.5 - -func (demonology *DemonologyWarlock) registerCarrionSwarm() { - demonology.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 103967}, - Flags: core.SpellFlagAoE | core.SpellFlagAPL, - ProcMask: core.ProcMaskSpellDamage, - SpellSchool: core.SpellSchoolShadow, - ClassSpellMask: warlock.WarlockSpellCarrionSwarm, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCDMin: time.Millisecond * 500, - GCD: core.GCDMin, - }, - CD: core.Cooldown{ - Timer: demonology.NewTimer(), - Duration: time.Second * 12, - }, - }, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: carrionSwarmCoeff, - CritMultiplier: demonology.DefaultCritMultiplier(), - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return demonology.IsInMeta() && demonology.CanSpendDemonicFury(50) - }, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - demonology.SpendDemonicFury(sim, 50, spell.ActionID) - spell.CalcAndDealAoeDamageWithVariance(sim, spell.OutcomeMagicHitAndCrit, func(sim *core.Simulation, _ *core.Spell) float64 { - return demonology.CalcAndRollDamageRange(sim, carrionSwarmScale, carrionSwarmVariance) - }) - }, - }) -} diff --git a/sim/warlock/demonology/chaos_wave.go b/sim/warlock/demonology/chaos_wave.go deleted file mode 100644 index 4433cf6be8..0000000000 --- a/sim/warlock/demonology/chaos_wave.go +++ /dev/null @@ -1,53 +0,0 @@ -package demonology - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -const chaosWaveScale = 1 -const chaosWaveCoeff = 1.167 - -func (demonology *DemonologyWarlock) registerChaosWave() { - demonology.ChaosWave = demonology.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 124916}, - SpellSchool: core.SpellSchoolChaos, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAoE | core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellChaosWave, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - Charges: 2, - RechargeTime: time.Second * 15, - - DamageMultiplier: 1, - CritMultiplier: demonology.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: chaosWaveCoeff, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return demonology.IsInMeta() && demonology.CanSpendDemonicFury(80) - }, - - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { - // keep stacks in sync as they're shared - demonology.HandOfGuldan.ConsumeCharge(sim) - demonology.SpendDemonicFury(sim, 80, spell.ActionID) - pa := sim.GetConsumedPendingActionFromPool() - pa.NextActionAt = sim.CurrentTime + time.Millisecond*1300 // Fixed delay of 1.3 seconds - pa.Priority = core.ActionPriorityAuto - - pa.OnAction = func(sim *core.Simulation) { - spell.CalcAndDealAoeDamage(sim, demonology.CalcScalingSpellDmg(chaosWaveScale), spell.OutcomeMagicHitAndCrit) - } - - sim.AddPendingAction(pa) - }, - }) -} diff --git a/sim/warlock/demonology/corruption.go b/sim/warlock/demonology/corruption.go deleted file mode 100644 index c498ae4c9b..0000000000 --- a/sim/warlock/demonology/corruption.go +++ /dev/null @@ -1,16 +0,0 @@ -package demonology - -import "github.com/wowsims/tbc/sim/core" - -func (demonology *DemonologyWarlock) registerCorruption() { - corruption := demonology.RegisterCorruption(nil, func(resultList core.SpellResultSlice, spell *core.Spell, sim *core.Simulation) { - if resultList[0].Landed() { - demonology.GainDemonicFury(sim, 4, spell.ActionID) - } - }) - - // replaced by doom in meta - corruption.ExtraCastCondition = func(sim *core.Simulation, target *core.Unit) bool { - return !demonology.IsInMeta() - } -} diff --git a/sim/warlock/demonology/darksoul_knowledge.go b/sim/warlock/demonology/darksoul_knowledge.go deleted file mode 100644 index e7134d8bee..0000000000 --- a/sim/warlock/demonology/darksoul_knowledge.go +++ /dev/null @@ -1,48 +0,0 @@ -package demonology - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/core/stats" - "github.com/wowsims/tbc/sim/warlock" -) - -func (demonology *DemonologyWarlock) registerDarksoulKnowledge() { - buff := demonology.NewTemporaryStatsAura( - "Dark Soul: Knowledge", - core.ActionID{SpellID: 113861}, - stats.Stats{stats.MasteryRating: 30 * core.MasteryRatingPerMasteryPoint}, - time.Second*20, - ) - - spell := demonology.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 113861}, - DamageMultiplier: 1, - ProcMask: core.ProcMaskEmpty, - SpellSchool: core.SpellSchoolShadow, - ClassSpellMask: warlock.WarlockSpellDarkSoulKnowledge, - Cast: core.CastConfig{ - DefaultCast: core.Cast{NonEmpty: true}, - CD: core.Cooldown{ - Timer: demonology.NewTimer(), - Duration: time.Minute * 2, - }, - }, - ManaCost: core.ManaCostOptions{ - BaseCostPercent: 5, - }, - RechargeTime: time.Minute * 2, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - buff.Activate(sim) - }, - RelatedSelfBuff: buff.Aura, - }) - - demonology.AddMajorCooldown(core.MajorCooldown{ - Spell: spell, - BuffAura: buff, - Priority: core.CooldownPriorityDefault, - Type: core.CooldownTypeDPS, - }) -} diff --git a/sim/warlock/demonology/drain_life.go b/sim/warlock/demonology/drain_life.go deleted file mode 100644 index abde9d638b..0000000000 --- a/sim/warlock/demonology/drain_life.go +++ /dev/null @@ -1,17 +0,0 @@ -package demonology - -import "github.com/wowsims/tbc/sim/core" - -func (demo *DemonologyWarlock) registerDrainLife() { - demo.RegisterDrainLife(func(_ core.SpellResultSlice, spell *core.Spell, sim *core.Simulation) { - if demo.IsInMeta() { - if demo.CanSpendDemonicFury(30) { - demo.SpendDemonicFury(sim, 30, spell.ActionID) - } else { - demo.ChanneledDot.Deactivate(sim) - } - } else { - demo.GainDemonicFury(sim, 10, spell.ActionID) - } - }) -} diff --git a/sim/warlock/demonology/felflame.go b/sim/warlock/demonology/felflame.go deleted file mode 100644 index f70c067673..0000000000 --- a/sim/warlock/demonology/felflame.go +++ /dev/null @@ -1,14 +0,0 @@ -package demonology - -import "github.com/wowsims/tbc/sim/core" - -func (demo *DemonologyWarlock) registerFelFlame() { - felFlame := demo.RegisterFelflame(func(_ core.SpellResultSlice, spell *core.Spell, sim *core.Simulation) { - demo.GainDemonicFury(sim, 15, spell.ActionID) - }) - - // Is replaced within meta, can not use it when active - felFlame.ExtraCastCondition = func(sim *core.Simulation, target *core.Unit) bool { - return !demo.Metamorphosis.RelatedSelfBuff.IsActive() - } -} diff --git a/sim/warlock/demonology/hand_of_guldan.go b/sim/warlock/demonology/hand_of_guldan.go deleted file mode 100644 index f4ca4431de..0000000000 --- a/sim/warlock/demonology/hand_of_guldan.go +++ /dev/null @@ -1,107 +0,0 @@ -package demonology - -import ( - "math" - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -const shadowFlameScale = 0.137 -const shadowFlameCoeff = 0.137 -const hogScale = 0.575 -const hogCoeff = 0.575 - -func (demonology *DemonologyWarlock) registerHandOfGuldan() { - shadowFlame := demonology.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 47960}, - SpellSchool: core.SpellSchoolFire | core.SpellSchoolShadow, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagNoOnCastComplete, - ClassSpellMask: warlock.WarlockSpellShadowflameDot, - - ThreatMultiplier: 1, - DamageMultiplier: 1, - CritMultiplier: demonology.DefaultCritMultiplier(), - BonusCoefficient: shadowFlameCoeff, - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Shadowflame", - MaxStacks: 2, - }, - NumberOfTicks: 6, - TickLength: time.Second, - AffectedByCastSpeed: true, - BonusCoefficient: shadowFlameCoeff, - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, 0) - stacks := math.Min(float64(dot.Aura.GetStacks())+1, 2) - dot.SnapshotBaseDamage = demonology.CalcScalingSpellDmg(shadowFlameScale) + stacks*dot.BonusCoefficient*dot.Spell.BonusDamage() - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) - demonology.GainDemonicFury(sim, 2, dot.Spell.ActionID) - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - spell.Dot(target).Apply(sim) - spell.Dot(target).Aura.AddStack(sim) - }, - }) - - demonology.HandOfGuldan = demonology.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 105174}, - SpellSchool: core.SpellSchoolFire | core.SpellSchoolShadow, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAoE | core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellHandOfGuldan, - - ManaCost: core.ManaCostOptions{BaseCostPercent: 5}, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - Charges: 2, - RechargeTime: time.Second * 15, - - DamageMultiplier: 1, - CritMultiplier: demonology.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: hogCoeff, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return !demonology.IsInMeta() - }, - - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { - // keep stacks in sync as they're shared - demonology.ChaosWave.ConsumeCharge(sim) - demonology.HandOfGuldanImpactTime = sim.CurrentTime + time.Millisecond*1300 - pa := sim.GetConsumedPendingActionFromPool() - pa.NextActionAt = demonology.HandOfGuldanImpactTime // Fixed delay of 1.3 seconds - pa.Priority = core.ActionPriorityAuto - - pa.OnAction = func(sim *core.Simulation) { - results := spell.CalcAoeDamage( - sim, - demonology.CalcScalingSpellDmg(hogScale), - spell.OutcomeMagicHitAndCrit, - ) - - for _, result := range results { - if result.Landed() { - shadowFlame.Cast(sim, result.Target) - } - } - - spell.DealBatchedAoeDamage(sim) - } - - sim.AddPendingAction(pa) - }, - }) -} diff --git a/sim/warlock/demonology/hotfixes.go b/sim/warlock/demonology/hotfixes.go deleted file mode 100644 index 39f7ab2d6e..0000000000 --- a/sim/warlock/demonology/hotfixes.go +++ /dev/null @@ -1,47 +0,0 @@ -package demonology - -import ( - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -func (demonology *DemonologyWarlock) registerHotfixes() { - - // 2025-07-31 - Chaos Wave damage increased by 70%. - demonology.AddStaticMod(core.SpellModConfig{ - ClassMask: warlock.WarlockSpellChaosWave, - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.7, - }) - - // 2025-07-31 - Hellfire damage increased by 25%. - // 2025-07-31 - Immolation Aura damage increased by 25%. - demonology.AddStaticMod(core.SpellModConfig{ - ClassMask: warlock.WarlockSpellHellfire | warlock.WarlockSpellImmolationAura, - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.25, - }) - - // 2025-09-31 - Doom’s damage over time increased from 33% to 50%. - demonology.AddStaticMod(core.SpellModConfig{ - ClassMask: warlock.WarlockSpellDoom, - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.50, - }) - - // 2025-09-31 - Soul Fire damage increased by 20%. - demonology.AddStaticMod(core.SpellModConfig{ - ClassMask: warlock.WarlockSpellSoulFire, - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.20, - }) - - // 2025-09-31 - Wild Imp Damage increased from 43% to 60%. - for _, imp := range demonology.WildImps { - imp.AddStaticMod(core.SpellModConfig{ - ClassMask: warlock.WarlockSpellImpFireBolt, - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.60, - }) - } -} diff --git a/sim/warlock/demonology/immolation_aura.go b/sim/warlock/demonology/immolation_aura.go deleted file mode 100644 index d526fd2c9f..0000000000 --- a/sim/warlock/demonology/immolation_aura.go +++ /dev/null @@ -1,66 +0,0 @@ -package demonology - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -const immolationAuraScale = 0.17499999702 -const immolationAuraCoeff = 0.17499999702 - -func (demonology *DemonologyWarlock) registerImmolationAura() { - var baseDamage = demonology.CalcScalingSpellDmg(immolationAuraScale) - - immolationAura := demonology.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 104025}, - SpellSchool: core.SpellSchoolFire, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAoE | core.SpellFlagAPL | core.SpellFlagNoMetrics, - ClassSpellMask: warlock.WarlockSpellImmolationAura, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: time.Second, - }, - }, - - DamageMultiplierAdditive: 1, - CritMultiplier: demonology.DefaultCritMultiplier(), - ThreatMultiplier: 1, - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Immolation Aura (DoT)", - }, - - TickLength: time.Second, - NumberOfTicks: 8, - AffectedByCastSpeed: true, - HasteReducesDuration: true, - BonusCoefficient: immolationAuraCoeff, - IsAOE: true, - - OnTick: func(sim *core.Simulation, _ *core.Unit, dot *core.Dot) { - if !demonology.CanSpendDemonicFury(25) { - dot.Deactivate(sim) - return - } - - demonology.SpendDemonicFury(sim, 25, dot.Spell.ActionID) - dot.Spell.CalcAndDealPeriodicAoeDamage(sim, baseDamage, dot.OutcomeTick) - }, - }, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return demonology.IsInMeta() && demonology.CanSpendDemonicFury(25) - }, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - spell.AOEDot().Apply(sim) - }, - }) - - demonology.Metamorphosis.RelatedSelfBuff.ApplyOnExpire(func(aura *core.Aura, sim *core.Simulation) { - immolationAura.AOEDot().Deactivate(sim) - }) -} diff --git a/sim/warlock/demonology/metamorphosis.go b/sim/warlock/demonology/metamorphosis.go deleted file mode 100644 index c7da86b146..0000000000 --- a/sim/warlock/demonology/metamorphosis.go +++ /dev/null @@ -1,87 +0,0 @@ -package demonology - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -func (demo *DemonologyWarlock) registerMetamorphosis() { - metaActionId := core.ActionID{SpellID: 103958} - var queueMetaCost func(sim *core.Simulation) - var drainLifeManaCost core.ResourceCostImpl - - metaAura := demo.RegisterAura(core.Aura{ - Label: "Metamorphosis", - ActionID: metaActionId, - Duration: core.NeverExpires, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - queueMetaCost(sim) - - // update cast cost - drainLifeManaCost = demo.DrainLife.Cost - demo.DrainLife.Cost.ResourceCostImpl = NewDemonicFuryCost(0) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - demo.DrainLife.Cost.ResourceCostImpl = drainLifeManaCost - }, - }).AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_GlobalCooldown_Flat, - TimeValue: -time.Millisecond * 500, - ClassMask: warlock.WarlockSpellSummonDoomguard | warlock.WarlockSpellSummonInfernal | warlock.WarlockSpellCarrionSwarm | warlock.WarlockSpellLifeTap, - }) - - queueMetaCost = func(sim *core.Simulation) { - pa := core.PendingAction{ - NextActionAt: sim.CurrentTime + time.Second, - Priority: core.ActionPriorityAuto, - OnAction: func(sim *core.Simulation) { - if !metaAura.IsActive() { - return - } - - demo.SpendUpToDemonicFury(sim, 6, metaActionId) - if demo.DemonicFury.Value() < 50 { - metaAura.Deactivate(sim) - return - } - - queueMetaCost(sim) - }, - } - - sim.AddPendingAction(&pa) - } - - demo.Metamorphosis = demo.RegisterSpell(core.SpellConfig{ - ActionID: metaActionId, - Flags: core.SpellFlagAPL | core.SpellFlagNoOnCastComplete, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskEmpty, - - ThreatMultiplier: 1, - DamageMultiplier: 1, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - NonEmpty: true, - }, - - CD: core.Cooldown{ - Timer: demo.NewTimer(), - Duration: time.Second * 10, - }, - }, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return !metaAura.IsActive() && demo.DemonicFury.Value() >= 50 - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - metaAura.Activate(sim) - }, - - RelatedSelfBuff: metaAura, - }) -} diff --git a/sim/warlock/demonology/molten_core.go b/sim/warlock/demonology/molten_core.go deleted file mode 100644 index 24560b4531..0000000000 --- a/sim/warlock/demonology/molten_core.go +++ /dev/null @@ -1,70 +0,0 @@ -package demonology - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -func (demonology *DemonologyWarlock) registerMoltenCore() { - demonology.MoltenCore = core.BlockPrepull(demonology.RegisterAura(core.Aura{ - Label: "Demonic Core", - ActionID: core.ActionID{SpellID: 122355}, - Duration: time.Second * 30, - MaxStacks: 10, - })).AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_CastTime_Pct, - FloatValue: -0.5, - ClassMask: warlock.WarlockSpellSoulFire, - }).AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_PowerCost_Pct, - FloatValue: -0.5, - ClassMask: warlock.WarlockSpellSoulFire, - }) - - // When Shadow Flame or Wild Imp deals damage 8% chance to proc - // When Chaos Wave -> 100% Proc Chance - apply := func(unit *core.Unit) { - unit.MakeProcTriggerAura(core.ProcTrigger{ - Name: "Demonic Core Tracker", - Outcome: core.OutcomeLanded, - ClassSpellMask: warlock.WarlockSpellImpFireBolt | warlock.WarlockSpellShadowflameDot | warlock.WarlockSpellChaosWave | warlock.WarlockSpellShadowBolt | warlock.WarlockSpellSoulFire | warlock.WarlockSpellTouchOfChaos, - Callback: core.CallbackOnPeriodicDamageDealt | core.CallbackOnSpellHitDealt | core.CallbackOnCastComplete, - TriggerImmediately: true, - - Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.Matches(warlock.WarlockSpellSoulFire) && result == nil && demonology.MoltenCore.IsActive() { - demonology.MoltenCore.RemoveStack(sim) - } - - if spell.Matches(warlock.WarlockSpellShadowflameDot) && sim.Proc(0.08, "Demonic Core Proc") { - demonology.MoltenCore.Activate(sim) - demonology.MoltenCore.AddStack(sim) - } - - // proc fire bolt on cast - if result == nil && spell.Matches(warlock.WarlockSpellImpFireBolt) && sim.Proc(0.08, "Demonic Core Proc") { - demonology.MoltenCore.Activate(sim) - demonology.MoltenCore.AddStack(sim) - } - - if spell.Matches(warlock.WarlockSpellChaosWave) && result != nil && result.Landed() { - demonology.MoltenCore.Activate(sim) - demonology.MoltenCore.AddStack(sim) - } - - // Decimation Passive effect, proc on cast - if sim.IsExecutePhase25() && spell.Matches(warlock.WarlockSpellShadowBolt|warlock.WarlockSpellSoulFire) && result == nil { - demonology.MoltenCore.Activate(sim) - demonology.MoltenCore.AddStack(sim) - } - }, - }) - } - - apply(&demonology.Unit) - for _, pet := range demonology.WildImps { - apply(&pet.Unit) - } -} diff --git a/sim/warlock/demonology/talents.go b/sim/warlock/demonology/talents.go deleted file mode 100644 index d4973d8a59..0000000000 --- a/sim/warlock/demonology/talents.go +++ /dev/null @@ -1,85 +0,0 @@ -package demonology - -import ( - "fmt" - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -func (demonlogy *DemonologyWarlock) registerGrimoireOfSupremacy() { - if !demonlogy.Talents.GrimoireOfSupremacy { - return - } - - // Pimp my demo pet - felGuard := demonlogy.Felguard - demonlogy.Felguard.PseudoStats.DamageDealtMultiplier *= 1.2 - felGuard.Name = "Wrathguard" - felGuard.Label = fmt.Sprintf("%s - %s", demonlogy.Label, "Wrathguard") - - // Now dualwield with 1.5x less base damage - weaponConfig := warlock.ScaledAutoAttackConfig(2) - weaponConfig.MainHand.BaseDamageMax /= 1.5 - weaponConfig.MainHand.BaseDamageMin /= 1.5 - weaponConfig.OffHand = weaponConfig.MainHand - - felGuard.EnableAutoAttacks(felGuard, *weaponConfig) - felGuard.ChangeStatInheritance(demonlogy.SimplePetStatInheritanceWithScale(2 + 1.0/3.0)) - felGuard.PseudoStats.DisableDWMissPenalty = true - - mortalCleave := felGuard.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 115625}, - SpellSchool: core.SpellSchoolPhysical, - ProcMask: core.ProcMaskMeleeMHSpecial, - Flags: core.SpellFlagMeleeMetrics | core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellFelGuardLegionStrike, - - EnergyCost: core.EnergyCostOptions{ - Cost: 60, - }, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: time.Second * 1, - }, - }, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - CritMultiplier: 2, - - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { - baseDmg := spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) * 1.95 - baseDmg /= float64(sim.Environment.ActiveTargetCount()) - spell.CalcAndDealAoeDamage(sim, baseDmg, spell.OutcomeMeleeWeaponSpecialHitAndCrit) - // Pets are not affected by Fury gain modifiers - demonlogy.DemonicFury.Gain(sim, 12, core.ActionID{SpellID: 30213}) - }, - }) - - felGuard.AutoCastAbilities = []*core.Spell{mortalCleave} -} - -func (demonology *DemonologyWarlock) registerGrimoireOfService() { - if !demonology.Talents.GrimoireOfService { - return - } - - felGuard := demonology.registerFelguardWithName("Grimoire: Felguard", false, true, true) - felGuard.MinEnergy = 90 - - demonology.BuildAndRegisterSummonSpell(111898, felGuard) -} - -func (demonology *DemonologyWarlock) registerGrimoireOfSacrifice() { - if !demonology.Talents.GrimoireOfSacrifice { - return - } - - // rest handle din talents.go of warlock - for _, pet := range demonology.WildImps { - pet.Fireball.DamageMultiplier *= 1.25 - } -} diff --git a/sim/warlock/demonology/touch_of_chaos.go b/sim/warlock/demonology/touch_of_chaos.go deleted file mode 100644 index c3b6955342..0000000000 --- a/sim/warlock/demonology/touch_of_chaos.go +++ /dev/null @@ -1,68 +0,0 @@ -package demonology - -import ( - "math" - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -const tocScale = 0.767 -const tocVariance = 0.1 -const tocCoeff = 0.767 - -func (demonology *DemonologyWarlock) registerTouchOfChaos() { - demonology.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 103964}, - SpellSchool: core.SpellSchoolChaos, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellTouchOfChaos, - MissileSpeed: 120, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: time.Second, - }, - }, - - DamageMultiplierAdditive: 1, - CritMultiplier: demonology.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: tocCoeff, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return demonology.IsInMeta() && demonology.CanSpendDemonicFury(40) - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcDamage(sim, target, demonology.CalcAndRollDamageRange(sim, tocScale, tocVariance), spell.OutcomeMagicHitAndCrit) - demonology.SpendDemonicFury(sim, 40, spell.ActionID) - spell.WaitTravelTime(sim, func(sim *core.Simulation) { - spell.DealDamage(sim, result) - - corruption := demonology.Corruption.Dot(target) - if corruption.IsActive() { - corruption.TakeSnapshot(sim, false) - - // most sane way I can think off to keep tick count but update haste tick rate and roll over properly - // duration is actually extended on refresh for lower haste - state := corruption.SaveState(sim) - corruption.ApplyRollover(sim) - state.ExtraTicks = 0 - state.TickPeriod = corruption.TickPeriod() - state.RemainingDuration = state.TickPeriod*time.Duration(state.TicksRemaining) + state.NextTickIn - corruption.RestoreState(state, sim) - - // add up to the max duration or up to 5 seconds - maxLength := math.Min(float64(corruption.BaseDuration()+corruption.BaseDuration()/2), float64(corruption.RemainingDuration(sim)+time.Second*5)) - - for idx := 0; float64(corruption.RemainingDuration(sim)+corruption.TickPeriod()) < maxLength; idx++ { - corruption.AddTick() - } - } - }) - }, - }) -} diff --git a/sim/warlock/demonology/void_ray.go b/sim/warlock/demonology/void_ray.go deleted file mode 100644 index 0bd601931f..0000000000 --- a/sim/warlock/demonology/void_ray.go +++ /dev/null @@ -1,40 +0,0 @@ -package demonology - -import ( - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -const voidRayScale = 0.525 -const voidRayVariance = 0.1 -const voidRayCoeff = 0.234 - -func (demonology *DemonologyWarlock) registerVoidRay() { - demonology.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 115422}, - SpellSchool: core.SpellSchoolShadowFlame, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAoE | core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellVoidray, - MissileSpeed: 38, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - DamageMultiplier: 1.0, - CritMultiplier: demonology.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: voidRayCoeff, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return demonology.IsInMeta() && demonology.CanSpendDemonicFury(80) - }, - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { - demonology.SpendDemonicFury(sim, 80, spell.ActionID) - spell.CalcAndDealAoeDamageWithVariance(sim, spell.OutcomeMagicHitAndCrit, func(sim *core.Simulation, _ *core.Spell) float64 { - return demonology.CalcAndRollDamageRange(sim, voidRayScale, voidRayVariance) - }) - }, - }) -} diff --git a/sim/warlock/demonology/wild_imp.go b/sim/warlock/demonology/wild_imp.go deleted file mode 100644 index 7dcb7c1311..0000000000 --- a/sim/warlock/demonology/wild_imp.go +++ /dev/null @@ -1,248 +0,0 @@ -package demonology - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/core/stats" - "github.com/wowsims/tbc/sim/warlock" -) - -// wild imps will cast 10 casts then despawn -// they fight like any other guardian imp -// we can potentially spawn a lot of imps due to Doom being able to proc them so.. fingers crossed >.< - -type WildImpPet struct { - core.Pet - - Fireball *core.Spell -} - -// registers the wild imp spell and handlers -// count The number of imps that shoudl be registered. It will be the upper limit the sim can spawn simultaniously -func (demonology *DemonologyWarlock) registerWildImp(count int) { - demonology.WildImps = make([]*WildImpPet, count) - for idx := 0; idx < count; idx++ { - demonology.WildImps[idx] = demonology.buildWildImp(count) - demonology.AddPet(demonology.WildImps[idx]) - } - - // register passiv - demonology.registerWildImpPassive() -} - -func (demonology *DemonologyWarlock) buildWildImp(counter int) *WildImpPet { - wildImpStatInheritance := func() core.PetStatInheritance { - return func(ownerStats stats.Stats) stats.Stats { - defaultInheritance := demonology.SimplePetStatInheritanceWithScale(0)(ownerStats) - defaultInheritance[stats.HasteRating] = 0 - return defaultInheritance - } - } - - pet := &WildImpPet{ - Pet: core.NewPet(core.PetConfig{ - Name: "Wild Imp", - Owner: &demonology.Character, - BaseStats: stats.Stats{stats.Health: 48312.8, stats.Armor: 19680}, - NonHitExpStatInheritance: wildImpStatInheritance(), - EnabledOnStart: false, - IsGuardian: true, - HasDynamicMeleeSpeedInheritance: false, - HasDynamicCastSpeedInheritance: false, - HasResourceRegenInheritance: false, - }), - } - - // set pet class for proper scaling values - pet.Class = pet.Owner.Class - pet.EnableEnergyBar(core.EnergyBarOptions{ - MaxEnergy: 10, - HasNoRegen: true, - }) - - oldEnable := pet.OnPetEnable - pet.OnPetEnable = func(sim *core.Simulation) { - if oldEnable != nil { - oldEnable(sim) - } - - pet.MultiplyCastSpeed(sim, pet.Owner.PseudoStats.CastSpeedMultiplier) - } - - oldDisable := pet.OnPetDisable - pet.OnPetDisable = func(sim *core.Simulation) { - if oldDisable != nil { - oldDisable(sim) - } - - pet.MultiplyCastSpeed(sim, 1/pet.PseudoStats.CastSpeedMultiplier) - } - - pet.registerFireboltSpell() - return pet -} - -func (pet *WildImpPet) GetPet() *core.Pet { - return &pet.Pet -} - -func (pet *WildImpPet) Reset(sim *core.Simulation) { -} - -func (pet *WildImpPet) OnEncounterStart(sim *core.Simulation) { -} - -func (pet *WildImpPet) ExecuteCustomRotation(sim *core.Simulation) { - spell := pet.Fireball - if spell.CanCast(sim, pet.CurrentTarget) { - spell.Cast(sim, pet.CurrentTarget) - pet.WaitUntil(sim, sim.CurrentTime+time.Millisecond*100) - return - } - - if pet.CurrentEnergy() == 0 { - if sim.Log != nil { - pet.Log(sim, "Wild Imp despawned.") - } - - pa := sim.GetConsumedPendingActionFromPool() - pa.NextActionAt = sim.CurrentTime - pa.Priority = core.ActionPriorityAuto - - pa.OnAction = func(sim *core.Simulation) { - pet.Disable(sim) - } - - sim.AddPendingAction(pa) - - return - } - - var offset = time.Duration(0) - if pet.Hardcast.Expires > sim.CurrentTime { - offset = pet.Hardcast.Expires - sim.CurrentTime - } - - pet.WaitUntil(sim, sim.CurrentTime+offset+time.Millisecond*100) -} - -// Hotfixes already included -const felFireBoltScale = 0.242 -const felFireBoltVariance = 0.05 -const felFireBoltCoeff = 0.242 - -func (pet *WildImpPet) registerFireboltSpell() { - pet.Fireball = pet.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 104318}, - SpellSchool: core.SpellSchoolFire, - ProcMask: core.ProcMaskSpellDamage, - ClassSpellMask: warlock.WarlockSpellImpFireBolt, - MissileSpeed: 16, - - EnergyCost: core.EnergyCostOptions{ - Cost: 1, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: time.Second * 1, - CastTime: time.Second * 2, - }, - }, - - DamageMultiplier: 1, - CritMultiplier: 2, - ThreatMultiplier: 1, - BonusCoefficient: felFireBoltCoeff, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - pet.Owner.Unit.GetSecondaryResourceBar().Gain(sim, 5, spell.ActionID) - result := spell.CalcDamage(sim, target, pet.CalcAndRollDamageRange(sim, felFireBoltScale, felFireBoltVariance), spell.OutcomeMagicHitAndCrit) - spell.WaitTravelTime(sim, func(sim *core.Simulation) { - spell.DealDamage(sim, result) - }) - }, - }) -} - -func (warlock *DemonologyWarlock) SpawnImp(sim *core.Simulation) { - for _, pet := range warlock.WildImps { - if pet.IsActive() { - continue - } - - pet.Enable(sim, pet) - return - } - - panic("TOO MANY IMPS!") -} - -func (demonology *DemonologyWarlock) registerWildImpPassive() { - var trigger *core.Aura - trigger = demonology.MakeProcTriggerAura(core.ProcTrigger{ - MetricsActionID: core.ActionID{SpellID: 114925}, - Name: "Demonic Calling", - Callback: core.CallbackOnCastComplete, - ClassSpellMask: warlock.WarlockSpellShadowBolt | warlock.WarlockSpellSoulFire | warlock.WarlockSpellTouchOfChaos, - Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - demonology.SpawnImp(sim) - trigger.Deactivate(sim) - }, - }) - - getCD := func() time.Duration { - return time.Duration(20/demonology.TotalSpellHasteMultiplier()) * time.Second - } - - var triggerAction *core.PendingAction - var controllerImpSpawn func(sim *core.Simulation) - controllerImpSpawn = func(sim *core.Simulation) { - if demonology.ImpSwarm == nil || demonology.ImpSwarm.CD.IsReady(sim) { - trigger.Activate(sim) - } - - triggerAction = sim.GetConsumedPendingActionFromPool() - triggerAction.NextActionAt = sim.CurrentTime + getCD() - triggerAction.Priority = core.ActionPriorityAuto - triggerAction.OnAction = controllerImpSpawn - sim.AddPendingAction(triggerAction) - } - - core.MakePermanent(demonology.RegisterAura(core.Aura{ - Label: "Wild Imp - Controller", - OnGain: func(aura *core.Aura, sim *core.Simulation) { - cd := time.Duration(sim.Roll(float64(time.Second), float64(getCD()))) - - // initially do random timer to simulate real world scenario more appropiate - triggerAction = sim.GetConsumedPendingActionFromPool() - triggerAction.NextActionAt = sim.CurrentTime + cd - triggerAction.Priority = core.ActionPriorityAuto - triggerAction.OnAction = controllerImpSpawn - sim.AddPendingAction(triggerAction) - }, - })).ApplyOnEncounterStart(func(aura *core.Aura, sim *core.Simulation) { - // If you pre-cast and activate Demonic Calling it is activated - // at the start of the fight with a 1-2.5s delay - if !trigger.IsActive() { - cd := time.Duration(sim.Roll(float64(time.Second), float64(time.Millisecond*2500))) - triggerAction = sim.GetConsumedPendingActionFromPool() - triggerAction.NextActionAt = sim.CurrentTime + cd - triggerAction.Priority = core.ActionPriorityAuto - triggerAction.OnAction = func(sim *core.Simulation) { - trigger.Activate(sim) - } - sim.AddPendingAction(triggerAction) - } - }) - - demonology.MakeProcTriggerAura(core.ProcTrigger{ - Name: "Wild Imp - Doom Monitor", - ClassSpellMask: warlock.WarlockSpellDoom, - Outcome: core.OutcomeCrit, - Callback: core.CallbackOnPeriodicDamageDealt, - Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - demonology.SpawnImp(sim) - }, - }) -} diff --git a/sim/warlock/destruction/backdraft.go b/sim/warlock/destruction/backdraft.go deleted file mode 100644 index 51533e03b0..0000000000 --- a/sim/warlock/destruction/backdraft.go +++ /dev/null @@ -1,66 +0,0 @@ -package destruction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -func (destruction *DestructionWarlock) registerBackdraft() { - buff := core.BlockPrepull(destruction.RegisterAura(core.Aura{ - Label: "Backdraft", - ActionID: core.ActionID{SpellID: 117828}, - Duration: time.Second * 15, - MaxStacks: 6, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - if spell.Matches(warlock.WarlockSpellChaosBolt) && aura.GetStacks() >= 3 { - aura.SetStacks(sim, aura.GetStacks()-3) - return - } - - if spell.Matches(warlock.WarlockSpellIncinerate | warlock.WarlockSpellFaBIncinerate) { - aura.RemoveStack(sim) - } - }, - })).AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_PowerCost_Pct, - FloatValue: -0.3, - ClassMask: warlock.WarlockSpellIncinerate | warlock.WarlockSpellFaBIncinerate, - }).AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_CastTime_Pct, - FloatValue: -0.3, - ClassMask: warlock.WarlockSpellIncinerate | warlock.WarlockSpellFaBIncinerate, - }) - - // chaos bolt requries 3 charges - mod := destruction.AddDynamicMod(core.SpellModConfig{ - Kind: core.SpellMod_CastTime_Pct, - FloatValue: -0.3, - ClassMask: warlock.WarlockSpellChaosBolt, - }) - - buff.OnStacksChange = func(aura *core.Aura, sim *core.Simulation, oldStacks, newStacks int32) { - if newStacks >= 3 { - mod.Activate() - } else { - mod.Deactivate() - } - } - - buff.ApplyOnExpire(func(aura *core.Aura, sim *core.Simulation) { - mod.Deactivate() - }) - - destruction.MakeProcTriggerAura(core.ProcTrigger{ - Name: "Backdraft - Trigger", - ClassSpellMask: warlock.WarlockSpellConflagrate | warlock.WarlockSpellFaBConflagrate, - Callback: core.CallbackOnCastComplete, - Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - buff.Activate(sim) - - // always grants 3 stacks - buff.SetStacks(sim, buff.GetStacks()+3) - }, - }) -} diff --git a/sim/warlock/destruction/chaos_bolt.go b/sim/warlock/destruction/chaos_bolt.go deleted file mode 100644 index d1117938fd..0000000000 --- a/sim/warlock/destruction/chaos_bolt.go +++ /dev/null @@ -1,78 +0,0 @@ -package destruction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/core/stats" - "github.com/wowsims/tbc/sim/warlock" -) - -var chaosBoltVariance = 0.2 -var chaosBoltScale = 2.5875 -var chaosBoltCoeff = 2.5875 -var chaosBoltDotCoeff = 0.1294 -var chaosBoltDotScale = 0.1294 - -func (destro *DestructionWarlock) registerChaosBolt() { - destro.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 116858}, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellChaosBolt, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - CastTime: 3000 * time.Millisecond, - }, - }, - - DamageMultiplierAdditive: 1, - CritMultiplier: destro.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: chaosBoltCoeff, - BonusCritPercent: 100, - MissileSpeed: 16, - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Chaosbolt (DoT)", - }, - NumberOfTicks: 3, - TickLength: time.Second, - BonusCoefficient: chaosBoltDotCoeff, - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, destro.CalcScalingSpellDmg(chaosBoltDotScale)) - dot.SnapshotAttackerMultiplier *= (1 + destro.GetStat(stats.SpellCritPercent)/100) - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTickMagicCrit) - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := destro.CalcAndRollDamageRange(sim, chaosBoltScale, chaosBoltVariance) - spell.DamageMultiplier *= (1 + destro.GetStat(stats.SpellCritPercent)/100) - result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - spell.DamageMultiplier /= (1 + destro.GetStat(stats.SpellCritPercent)/100) - - // check again we can actually spend as Dark Soul might have run out before the cast finishes - if spell.Flags.Matches(SpellFlagDestructionHavoc) { - //Havoc Spell doesn't spend resources as it was a duplicate - } else if result.Landed() && destro.BurningEmbers.CanSpend(core.TernaryFloat64(destro.T15_2pc.IsActive(), 8, 10)) { - destro.BurningEmbers.Spend(sim, core.TernaryFloat64(destro.T15_2pc.IsActive(), 8, 10), spell.ActionID) - } else { - return - } - - spell.WaitTravelTime(sim, func(s *core.Simulation) { - spell.DealDamage(sim, result) - }) - }, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return destro.BurningEmbers.CanSpend(core.TernaryFloat64(destro.T15_2pc.IsActive(), 8, 10)) - }, - }) -} diff --git a/sim/warlock/destruction/chaotic_energy.go b/sim/warlock/destruction/chaotic_energy.go deleted file mode 100644 index 138cafb603..0000000000 --- a/sim/warlock/destruction/chaotic_energy.go +++ /dev/null @@ -1,26 +0,0 @@ -package destruction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/core/stats" - "github.com/wowsims/tbc/sim/warlock" -) - -func (destruction DestructionWarlock) ApplyChaoticEnergy() { - core.MakePermanent(destruction.RegisterAura(core.Aura{ - Label: "Chaotic Energy", - }).AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_GlobalCooldown_Flat, - TimeValue: -time.Millisecond * 500, - ClassMask: warlock.WarlockSpellAll, - }).AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_PowerCost_Pct, - FloatValue: 3, - ClassMask: warlock.WarlockSpellsChaoticEnergyDestro, - })) - - destruction.MultiplyStat(stats.MP5, 7.25) - destruction.HasteEffectsRegen() -} diff --git a/sim/warlock/destruction/dark_soul_instability.go b/sim/warlock/destruction/dark_soul_instability.go deleted file mode 100644 index 38d997cade..0000000000 --- a/sim/warlock/destruction/dark_soul_instability.go +++ /dev/null @@ -1,44 +0,0 @@ -package destruction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/core/stats" - "github.com/wowsims/tbc/sim/warlock" -) - -func (destruction *DestructionWarlock) registerDarkSoulInstability() { - buff := destruction.NewTemporaryStatsAura( - "Dark Soul: Instability", - core.ActionID{SpellID: 113858}, - stats.Stats{stats.CritRating: 30 * core.CritRatingPerCritPercent}, - time.Second*20, - ) - - spell := destruction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 113858}, - DamageMultiplier: 1, - ProcMask: core.ProcMaskEmpty, - SpellSchool: core.SpellSchoolShadow, - ClassSpellMask: warlock.WarlockSpellDarkSoulInsanity, - Cast: core.CastConfig{ - DefaultCast: core.Cast{NonEmpty: true}, - CD: core.Cooldown{ - Timer: destruction.NewTimer(), - Duration: time.Minute * 2, - }, - }, - RechargeTime: time.Minute * 2, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - buff.Activate(sim) - }, - RelatedSelfBuff: buff.Aura, - }) - destruction.AddMajorCooldown(core.MajorCooldown{ - Spell: spell, - BuffAura: buff, - Priority: core.CooldownPriorityDefault, - Type: core.CooldownTypeDPS, - }) -} diff --git a/sim/warlock/destruction/emberstorm.go b/sim/warlock/destruction/emberstorm.go deleted file mode 100644 index f8f1826f90..0000000000 --- a/sim/warlock/destruction/emberstorm.go +++ /dev/null @@ -1,37 +0,0 @@ -package destruction - -import ( - "github.com/wowsims/tbc/sim/core" -) - -func (destruction *DestructionWarlock) ApplyMastery() { - - spenderMod := destruction.AddDynamicMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: destruction.getSpenderMasteryBonus(), - ClassMask: SpellMaskCinderSpender, - }) - - generatorMod := destruction.AddDynamicMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: destruction.getGeneratorMasteryBonus(), - ClassMask: SpellMaskCinderGenerator, - }) - - destruction.AddOnMasteryStatChanged(func(sim *core.Simulation, oldMasteryRating, newMasteryRating float64) { - generatorMod.UpdateFloatValue(destruction.getGeneratorMasteryBonus()) - spenderMod.UpdateFloatValue(destruction.getSpenderMasteryBonus()) - }) - - core.MakePermanent(destruction.RegisterAura(core.Aura{ - Label: "Mastery: Emberstorm", - OnGain: func(aura *core.Aura, sim *core.Simulation) { - generatorMod.Activate() - spenderMod.Activate() - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - generatorMod.Deactivate() - spenderMod.Deactivate() - }, - })) -} diff --git a/sim/warlock/destruction/felflame.go b/sim/warlock/destruction/felflame.go deleted file mode 100644 index c35faaaf4f..0000000000 --- a/sim/warlock/destruction/felflame.go +++ /dev/null @@ -1,11 +0,0 @@ -package destruction - -import ( - "github.com/wowsims/tbc/sim/core" -) - -func (destruction DestructionWarlock) registerFelflame() { - destruction.RegisterFelflame(func(resultList core.SpellResultSlice, spell *core.Spell, sim *core.Simulation) { - destruction.BurningEmbers.Gain(sim, core.TernaryFloat64(resultList[0].DidCrit(), 2, 1), spell.ActionID) - }) -} diff --git a/sim/warlock/destruction/fire_and_brimstone.go b/sim/warlock/destruction/fire_and_brimstone.go deleted file mode 100644 index eafb21e36d..0000000000 --- a/sim/warlock/destruction/fire_and_brimstone.go +++ /dev/null @@ -1,48 +0,0 @@ -package destruction - -import ( - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -func (destruction *DestructionWarlock) registerFireAndBrimstone() { - destruction.FABAura = destruction.RegisterAura(core.Aura{ - Label: "Fire and Brimstone", - ActionID: core.ActionID{SpellID: 108683}, - Duration: core.NeverExpires, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - if !destruction.BurningEmbers.CanSpend(10) && aura.IsActive() { - aura.Deactivate(sim) - } - }, - }) - - destruction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 108683}, - SpellSchool: core.SpellSchoolFire, - DamageMultiplier: 1, - ThreatMultiplier: 1, - ProcMask: core.ProcMaskEmpty, - Cast: core.CastConfig{ - DefaultCast: core.Cast{NonEmpty: true}, - }, - - ClassSpellMask: warlock.WarlockSpellFireAndBrimstone, - Flags: core.SpellFlagAPL, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - destruction.FABAura.Activate(sim) - }, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return destruction.BurningEmbers.CanSpend(10) - }, - }) - - destruction.registerFireAndBrimstoneConflagrate() - destruction.registerFireAndBrimstoneImmolate() - destruction.registerFireAndBrimstoneIncinerate() -} - -func (destruction *DestructionWarlock) getFABReduction() float64 { - return 0.35 * (1 + destruction.getSpenderMasteryBonus()) -} diff --git a/sim/warlock/destruction/fire_and_brimstone_conflagrate.go b/sim/warlock/destruction/fire_and_brimstone_conflagrate.go deleted file mode 100644 index 757bfbce83..0000000000 --- a/sim/warlock/destruction/fire_and_brimstone_conflagrate.go +++ /dev/null @@ -1,69 +0,0 @@ -package destruction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -func (destruction *DestructionWarlock) registerFireAndBrimstoneConflagrate() { - destruction.FABConflagrate = destruction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 108685}, - SpellSchool: core.SpellSchoolFire, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAoE | core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellFaBConflagrate, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - Charges: 2, - RechargeTime: time.Second * 12, - - DamageMultiplier: 1, - CritMultiplier: destruction.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: conflagrateCoeff, - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return destruction.BurningEmbers.CanSpend(10) - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - if !destruction.FABAura.IsActive() { - destruction.FABAura.Activate(sim) - } - - // reduce damage for this spell based on mastery - reduction := destruction.getFABReduction() - spell.DamageMultiplier *= reduction - - // keep charges in sync - destruction.Conflagrate.ConsumeCharge(sim) - for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { - result := spell.CalcAndDealDamage( - sim, - aoeTarget, - destruction.CalcAndRollDamageRange(sim, conflagrateScale, conflagrateVariance), - spell.OutcomeMagicHitAndCrit) - - var emberGain int32 = 1 - - // ember lottery - if sim.Proc(0.15, "Ember Lottery") { - emberGain *= 2 - } - - if result.DidCrit() { - emberGain += 1 - } - - destruction.BurningEmbers.Gain(sim, float64(emberGain), spell.ActionID) - } - spell.DamageMultiplier /= reduction - destruction.BurningEmbers.Spend(sim, 10, spell.ActionID) - }, - }) -} diff --git a/sim/warlock/destruction/fire_and_brimstone_immolate.go b/sim/warlock/destruction/fire_and_brimstone_immolate.go deleted file mode 100644 index 1d6de26926..0000000000 --- a/sim/warlock/destruction/fire_and_brimstone_immolate.go +++ /dev/null @@ -1,108 +0,0 @@ -package destruction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -func (destruction *DestructionWarlock) registerFireAndBrimstoneImmolate() { - fabImmolate := destruction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 108686}, - SpellSchool: core.SpellSchoolFire, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellImmolate, - - ManaCost: core.ManaCostOptions{BaseCostPercent: 3}, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - CastTime: 1500 * time.Millisecond, - }, - ModifyCast: func(sim *core.Simulation, _ *core.Spell, _ *core.Cast) { - if !destruction.FABAura.IsActive() { - destruction.FABAura.Activate(sim) - } - }, - }, - - DamageMultiplier: 1, - CritMultiplier: destruction.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: immolateCoeff, - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return destruction.BurningEmbers.CanSpend(10) - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - reduction := destruction.getFABReduction() - spell.DamageMultiplier *= reduction - spell.RelatedDotSpell.DamageMultiplier *= reduction - - destruction.BurningEmbers.Spend(sim, 10, spell.ActionID) - results := spell.CalcAoeDamage(sim, destruction.CalcScalingSpellDmg(immolateScale), spell.OutcomeMagicHitAndCrit) - for _, result := range results { - if result.Landed() { - spell.RelatedDotSpell.Cast(sim, result.Target) - } - - if result.DidCrit() { - destruction.BurningEmbers.Gain(sim, 1, spell.ActionID) - } - } - spell.DealBatchedAoeDamage(sim) - - spell.DamageMultiplier /= reduction - spell.RelatedDotSpell.DamageMultiplier /= reduction - }, - }) - - fabImmolate.RelatedDotSpell = destruction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 108686}.WithTag(1), - SpellSchool: core.SpellSchoolFire, - ProcMask: core.ProcMaskSpellDamage, - ClassSpellMask: warlock.WarlockSpellImmolateDot, - Flags: core.SpellFlagPassiveSpell, - - DamageMultiplier: 1, - CritMultiplier: destruction.DefaultCritMultiplier(), - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "FAB - Immolate (DoT)", - OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.ClassSpellMask == warlock.WarlockSpellImmolate && spell != fabImmolate { - if fabImmolate.RelatedDotSpell.Dot(result.Target).IsActive() { - fabImmolate.RelatedDotSpell.Dot(result.Target).Deactivate(sim) - } - } - }, - }, - NumberOfTicks: 5, - TickLength: 3 * time.Second, - AffectedByCastSpeed: true, - BonusCoefficient: immolateCoeff, - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, destruction.CalcScalingSpellDmg(immolateScale)) - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - result := dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) - if result.DidCrit() { - destruction.BurningEmbers.Gain(sim, 1, dot.Spell.ActionID) - } - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - // both immolate versions are mutually exlusive and the og one will always be stronger - // baf does not overwrite default dot - if destruction.Immolate.Dot(target).IsActive() { - return - } - - destruction.ApplyDotWithPandemic(spell.Dot(target), sim) - }, - }) -} diff --git a/sim/warlock/destruction/fire_and_brimstone_incinerate.go b/sim/warlock/destruction/fire_and_brimstone_incinerate.go deleted file mode 100644 index 8d07c4d35a..0000000000 --- a/sim/warlock/destruction/fire_and_brimstone_incinerate.go +++ /dev/null @@ -1,74 +0,0 @@ -package destruction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -var bafIncinerateScale = 1.568 -var bafIncinerateCoeff = 1.568 - -func (destruction *DestructionWarlock) registerFireAndBrimstoneIncinerate() { - destruction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 114654}, - SpellSchool: core.SpellSchoolFire, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAoE | core.SpellFlagAPL, - MissileSpeed: 24, - ClassSpellMask: warlock.WarlockSpellFaBIncinerate, - - ManaCost: core.ManaCostOptions{BaseCostPercent: 5}, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - CastTime: 2000 * time.Millisecond, - }, - }, - - DamageMultiplierAdditive: 1, - CritMultiplier: destruction.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: bafIncinerateCoeff, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return destruction.BurningEmbers.CanSpend(10) - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - if !destruction.FABAura.IsActive() { - destruction.FABAura.Activate(sim) - } - - reduction := destruction.getFABReduction() - spell.DamageMultiplier *= reduction - destruction.BurningEmbers.Spend(sim, 10, spell.ActionID) - for _, enemy := range sim.Encounter.ActiveTargetUnits { - baseDamage := destruction.CalcAndRollDamageRange(sim, bafIncinerateScale, incinerateVariance) - result := spell.CalcDamage(sim, enemy, baseDamage, spell.OutcomeMagicHitAndCrit) - var emberGain int32 = 1 - if destruction.T15_4pc.IsActive() && sim.Proc(0.08, "T15 4p") { - emberGain += 1 - } - - // ember lottery - if sim.Proc(0.15, "Ember Lottery") { - emberGain *= 2 - } - - if result.DidCrit() { - emberGain += 1 - } - - destruction.BurningEmbers.Gain(sim, float64(emberGain), spell.ActionID) - - spell.WaitTravelTime(sim, func(sim *core.Simulation) { - spell.DealDamage(sim, result) - }) - } - - spell.DamageMultiplier /= reduction - }, - }) -} diff --git a/sim/warlock/destruction/havoc.go b/sim/warlock/destruction/havoc.go deleted file mode 100644 index 23ec71ba17..0000000000 --- a/sim/warlock/destruction/havoc.go +++ /dev/null @@ -1,104 +0,0 @@ -package destruction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -func (destruction *DestructionWarlock) spellMatches(aura *core.Aura, sim *core.Simulation, spell *core.Spell, target *core.Unit) { - if !destruction.HavocAuras.Get(target).IsActive() { //If the target of the calling spell does NOT have the HavocDebuff - //How many stacks are meant to be removed - var stacks int32 - if spell.Matches(warlock.WarlockSpellFelFlame | warlock.WarlockSpellImmolate | warlock.WarlockSpellIncinerate | - warlock.WarlockSpellShadowBurn | warlock.WarlockSpellConflagrate) { - stacks = 1 - } else if spell.Matches(warlock.WarlockSpellChaosBolt) { - stacks = 3 - } else { - return - } - - for _, havocAuras := range destruction.HavocAuras.ToMap() { - for _, havocAura := range havocAuras { - if havocAura != nil { - if havocAura.IsActive() { - aura.RemoveStacks(sim, stacks) - //AddHavocFlag - spell.Flags |= SpellFlagDestructionHavoc - spell.Proc(sim, havocAura.Unit) - //RemoveHavocFlag - spell.Flags &^= SpellFlagDestructionHavoc - } - } - } - } - - } -} - -func (destruction *DestructionWarlock) registerHavoc() { - havocDebuffAura := core.Aura{ - Label: "Havoc", - ActionID: core.ActionID{SpellID: 80240}, - Duration: time.Second * 15, - } - - destruction.HavocAuras = destruction.NewEnemyAuraArray(func(target *core.Unit) *core.Aura { - return target.RegisterAura(havocDebuffAura) - }) - - var havocCharges int32 = 3 - var cooldown = 25 - - actionID := core.ActionID{SpellID: 80240} - destruction.HavocChargesAura = destruction.RegisterAura(core.Aura{ - Label: "Havoc Charges Aura", - ActionID: actionID, - Duration: time.Second * 15, - MaxStacks: havocCharges, - - OnGain: func(aura *core.Aura, sim *core.Simulation) { - destruction.HavocChargesAura.AddStacks(sim, havocCharges) - }, - - OnApplyEffects: func(aura *core.Aura, sim *core.Simulation, target *core.Unit, spell *core.Spell) { - destruction.spellMatches(aura, sim, spell, target) - - if aura.GetStacks() == 0 { - aura.Deactivate(sim) - destruction.HavocAuras.DeactivateAll(sim) - } - }, - - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - aura.Deactivate(sim) - }, - }) - - destruction.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellHavoc, - - ManaCost: core.ManaCostOptions{BaseCostPercent: 4}, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCDMin: time.Millisecond * 500, - GCD: core.GCDMin, - }, - CD: core.Cooldown{ - Timer: destruction.NewTimer(), - Duration: time.Duration(cooldown) * time.Second, - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - destruction.HavocChargesAura.Activate(sim) - destruction.HavocAuras.Get(target).Activate(sim) - }, - }) -} diff --git a/sim/warlock/demonology/doom.go b/sim/warlock/doom.go similarity index 73% rename from sim/warlock/demonology/doom.go rename to sim/warlock/doom.go index 2466a9a429..100a5fd0c5 100644 --- a/sim/warlock/demonology/doom.go +++ b/sim/warlock/doom.go @@ -1,17 +1,16 @@ -package demonology +package warlock import ( "time" "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" ) const doomScale = 0.9375 const doomCoeff = 0.9375 -func (demonology *DemonologyWarlock) registerDoom() { - demonology.RegisterSpell(core.SpellConfig{ +func (warlock *Warlock) registerDoom() { + warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 603}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, @@ -21,7 +20,7 @@ func (demonology *DemonologyWarlock) registerDoom() { Cast: core.CastConfig{DefaultCast: core.Cast{GCD: core.GCDDefault}}, DamageMultiplierAdditive: 1, - CritMultiplier: demonology.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultCritMultiplier(), ThreatMultiplier: 1, Dot: core.DotConfig{ @@ -34,7 +33,7 @@ func (demonology *DemonologyWarlock) registerDoom() { BonusCoefficient: doomCoeff, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, demonology.CalcScalingSpellDmg(doomScale)) + dot.Snapshot(target, warlock.CalcScalingSpellDmg(doomScale)) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) @@ -42,14 +41,14 @@ func (demonology *DemonologyWarlock) registerDoom() { }, ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return demonology.IsInMeta() && demonology.CanSpendDemonicFury(60) + return warlock.IsInMeta() && warlock.CanSpendDemonicFury(60) }, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHitNoHitCounter) if result.Landed() { - demonology.SpendDemonicFury(sim, 60, spell.ActionID) - demonology.ApplyDotWithPandemic(spell.Dot(target), sim) + warlock.SpendDemonicFury(sim, 60, spell.ActionID) + warlock.ApplyDotWithPandemic(spell.Dot(target), sim) } spell.DealOutcome(sim, result) }, @@ -61,7 +60,7 @@ func (demonology *DemonologyWarlock) registerDoom() { result.Damage /= dot.TickPeriod().Seconds() return result } else { - result := spell.CalcPeriodicDamage(sim, target, demonology.CalcScalingSpellDmg(doomScale), spell.OutcomeExpectedMagicCrit) + result := spell.CalcPeriodicDamage(sim, target, warlock.CalcScalingSpellDmg(doomScale), spell.OutcomeExpectedMagicCrit) result.Damage /= dot.CalcTickPeriod().Round(time.Millisecond).Seconds() return result } diff --git a/sim/warlock/affliction/drain_soul.go b/sim/warlock/drain_soul.go similarity index 76% rename from sim/warlock/affliction/drain_soul.go rename to sim/warlock/drain_soul.go index 9d17978f6b..4ec4bc5e88 100644 --- a/sim/warlock/affliction/drain_soul.go +++ b/sim/warlock/drain_soul.go @@ -1,17 +1,16 @@ -package affliction +package warlock import ( "time" "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" ) const drainSoulScale = 0.257 const drainSoulCoeff = 0.257 -func (affliction *AfflictionWarlock) registerDrainSoul() { - affliction.RegisterSpell(core.SpellConfig{ +func (warlock *Warlock) registerDrainSoul() { + warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 1120}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, @@ -26,7 +25,7 @@ func (affliction *AfflictionWarlock) registerDrainSoul() { }, DamageMultiplierAdditive: 1, - CritMultiplier: affliction.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultCritMultiplier(), ThreatMultiplier: 1, Dot: core.DotConfig{ @@ -38,21 +37,21 @@ func (affliction *AfflictionWarlock) registerDrainSoul() { BonusCoefficient: drainSoulCoeff, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, _ bool) { - dot.Snapshot(target, affliction.CalcScalingSpellDmg(drainSoulScale)) + dot.Snapshot(target, warlock.CalcScalingSpellDmg(drainSoulScale)) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { result := dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) // Every 2nd tick grants 1 soul shard if dot.TickCount()%2 == 0 { - affliction.SoulShards.Gain(sim, 1, dot.Spell.ActionID) + warlock.SoulShards.Gain(sim, 1, dot.Spell.ActionID) } if !result.Landed() || !sim.IsExecutePhase20() { return } - affliction.ProcMaleficEffect(target, affliction.DrainSoulMaleficEffectMultiplier, sim) + warlock.ProcMaleficEffect(target, warlock.DrainSoulMaleficEffectMultiplier, sim) }, }, @@ -71,20 +70,20 @@ func (affliction *AfflictionWarlock) registerDrainSoul() { result.Damage /= dot.TickPeriod().Seconds() return result } else { - result := spell.CalcPeriodicDamage(sim, target, affliction.CalcScalingSpellDmg(drainSoulScale), spell.OutcomeExpectedMagicCrit) + result := spell.CalcPeriodicDamage(sim, target, warlock.CalcScalingSpellDmg(drainSoulScale), spell.OutcomeExpectedMagicCrit) result.Damage /= dot.CalcTickPeriod().Round(time.Millisecond).Seconds() return result } }, }) - dmgMode := affliction.AddDynamicMod(core.SpellModConfig{ + dmgMode := warlock.AddDynamicMod(core.SpellModConfig{ Kind: core.SpellMod_DamageDone_Pct, FloatValue: 1, ClassMask: warlock.WarlockSpellDrainSoul, }) - affliction.RegisterResetEffect(func(s *core.Simulation) { + warlock.RegisterResetEffect(func(s *core.Simulation) { dmgMode.Deactivate() s.RegisterExecutePhaseCallback(func(sim *core.Simulation, isExecute int32) { if isExecute > 20 { diff --git a/sim/warlock/felflame.go b/sim/warlock/felflame.go deleted file mode 100644 index 6fd82fc188..0000000000 --- a/sim/warlock/felflame.go +++ /dev/null @@ -1,43 +0,0 @@ -package warlock - -import "github.com/wowsims/tbc/sim/core" - -const felFlameVariance = 0.1 -const felFlameScale = 0.85 -const felFlameCoeff = 0.85 - -func (warlock *Warlock) RegisterFelflame(callback WarlockSpellCastedCallback) *core.Spell { - resultSlice := make(core.SpellResultSlice, 1) - - return warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 77799}, - SpellSchool: core.SpellSchoolFire | core.SpellSchoolShadow, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL, - ClassSpellMask: WarlockSpellFelFlame, - MissileSpeed: 38, - ManaCost: core.ManaCostOptions{BaseCostPercent: 6}, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - DamageMultiplier: 1.0, - CritMultiplier: warlock.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: felFlameCoeff, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := warlock.CalcAndRollDamageRange(sim, felFlameScale, felFlameVariance) - result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - resultSlice[0] = result - - if callback != nil { - callback(resultSlice, spell, sim) - } - - spell.WaitTravelTime(sim, func(s *core.Simulation) { - spell.DealDamage(sim, result) - }) - }, - }) -} diff --git a/sim/warlock/destruction/immolate.go b/sim/warlock/immolate.go similarity index 100% rename from sim/warlock/destruction/immolate.go rename to sim/warlock/immolate.go diff --git a/sim/warlock/destruction/incinerate.go b/sim/warlock/incinerate.go similarity index 70% rename from sim/warlock/destruction/incinerate.go rename to sim/warlock/incinerate.go index acc0b9f663..345fefb382 100644 --- a/sim/warlock/destruction/incinerate.go +++ b/sim/warlock/incinerate.go @@ -1,18 +1,17 @@ -package destruction +package warlock import ( "time" "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" ) const incinerateVariance = 0.1 const incinerateScale = 1.54 * 1.15 // Hotfix const incinerateCoeff = 1.54 * 1.15 -func (destro *DestructionWarlock) registerIncinerate() { - destro.RegisterSpell(core.SpellConfig{ +func (warlock *Warlock) registerIncinerate() { + warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 29722}, SpellSchool: core.SpellSchoolFire, ProcMask: core.ProcMaskSpellDamage, @@ -29,19 +28,19 @@ func (destro *DestructionWarlock) registerIncinerate() { }, DamageMultiplierAdditive: 1, - CritMultiplier: destro.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultCritMultiplier(), ThreatMultiplier: 1, BonusCoefficient: incinerateCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - if destro.FABAura.IsActive() { - destro.FABAura.Deactivate(sim) + if warlock.FABAura.IsActive() { + warlock.FABAura.Deactivate(sim) } - baseDamage := destro.CalcAndRollDamageRange(sim, incinerateScale, incinerateVariance) + baseDamage := warlock.CalcAndRollDamageRange(sim, incinerateScale, incinerateVariance) result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) var emberGain int32 = 1 - if destro.T15_4pc.IsActive() && sim.Proc(0.08, "T15 4p") { + if warlock.T15_4pc.IsActive() && sim.Proc(0.08, "T15 4p") { emberGain += 1 } @@ -54,7 +53,7 @@ func (destro *DestructionWarlock) registerIncinerate() { emberGain += 1 } - destro.BurningEmbers.Gain(sim, float64(emberGain), spell.ActionID) + warlock.BurningEmbers.Gain(sim, float64(emberGain), spell.ActionID) spell.WaitTravelTime(sim, func(sim *core.Simulation) { spell.DealDamage(sim, result) }) diff --git a/sim/warlock/items.go b/sim/warlock/items.go index 17e26f9376..cd1331e9b6 100644 --- a/sim/warlock/items.go +++ b/sim/warlock/items.go @@ -1,284 +1,85 @@ package warlock import ( - "time" - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/core/proto" - "github.com/wowsims/tbc/sim/core/stats" ) -// T11 -var ItemSetMaleficRaiment = core.NewItemSet(core.ItemSet{ - Name: "Shadowflame Regalia", +// Dungeon Set 3 +var ItemSetOblivionRaiment = core.NewItemSet(core.ItemSet{ + ID: 644, + Name: "Oblivion Raiment", Bonuses: map[int32]core.ApplySetBonus{ 2: func(agent core.Agent, setBonusAura *core.Aura) { - setBonusAura.AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_CastTime_Pct, - ClassMask: WarlockSpellChaosBolt | WarlockSpellHandOfGuldan | WarlockSpellHaunt, - FloatValue: -0.1, - }) + // Grants your pet 45 mana per 5 sec. + // Pet Mana Regen - 37375 }, 4: func(agent core.Agent, setBonusAura *core.Aura) { - warlock := agent.(WarlockAgent).GetWarlock() - - dmgMod := warlock.AddDynamicMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Flat, - ClassMask: WarlockSpellFelFlame, - FloatValue: 3.0, - }) - - aura := warlock.RegisterAura(core.Aura{ - Label: "Fel Spark", - ActionID: core.ActionID{SpellID: 89937}, - Duration: 15 * time.Second, - MaxStacks: 2, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - dmgMod.Activate() - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - dmgMod.Deactivate() - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.Matches(WarlockSpellFelFlame) && result.Landed() { - aura.RemoveStack(sim) - } - }, - }) - - setBonusAura.AttachProcTrigger(core.ProcTrigger{ - Name: "Item - Warlock T11 4P Bonus", - ActionID: core.ActionID{SpellID: 89935}, - ClassSpellMask: WarlockSpellImmolateDot | WarlockSpellUnstableAffliction, - Callback: core.CallbackOnPeriodicDamageDealt, - ProcChance: 0.02, - Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - aura.Activate(sim) - aura.SetStacks(sim, 2) - }, - }) + // Your Seed of Corruption deals 180 additional damage when it detonates. + // Improved Seed of Corruption - 37376 }, }, }) -// T14 -var ItemSetShaSkinRegalia = core.NewItemSet(core.ItemSet{ - Name: "Sha-Skin Regalia", - DisabledInChallengeMode: true, +// T4 +var ItemSetVoidheartRaiment = core.NewItemSet(core.ItemSet{ + ID: 645, + Name: "Voidheart Raiment", Bonuses: map[int32]core.ApplySetBonus{ 2: func(agent core.Agent, setBonusAura *core.Aura) { - setBonusAura.AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.1, - ClassMask: WarlockSpellCorruption, - }).AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.05, - ClassMask: WarlockSpellIncinerate | WarlockSpellFaBIncinerate, - }).AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.02, - ClassMask: WarlockSpellShadowBolt | WarlockSpellDemonicSlash | WarlockSpellTouchOfChaos, - }) + // Your shadow damage spells have a chance to grant you 135 bonus shadow damage for 15 sec. + // Shadowflame - 37377 + // Your fire damage spells have a chance to grant you 135 bonus fire damage for 15 sec. + // Hellfire - 39437 }, 4: func(agent core.Agent, setBonusAura *core.Aura) { - buff := agent.GetCharacter().RegisterAura(core.Aura{ - Label: "Sha-Skin Regalia - 4P Buff", - ActionID: core.ActionID{SpellID: 148463}, - Duration: time.Second * 20, - }).AttachMultiplicativePseudoStatBuff(&agent.GetCharacter().PseudoStats.DamageDealtMultiplier, 1.10) - - agent.GetCharacter().OnSpellRegistered(func(spell *core.Spell) { - if spell.Matches(WarlockDarkSoulSpell) { - spell.RelatedSelfBuff.ApplyOnGain(func(aura *core.Aura, sim *core.Simulation) { - buff.Activate(sim) - }) - } - }) + // Increases the duration of your Corruption and Immolate abilities by 3 sec. + // Improved Corruption and Immolate - 37380 }, }, }) -// T15 -var ItemSetRegaliaOfTheThousandfeldHells = core.NewItemSet(core.ItemSet{ - Name: "Regalia of the Thousandfold Hells", - DisabledInChallengeMode: true, +// T5 +var ItemSetCorruptorRaiment = core.NewItemSet(core.ItemSet{ + ID: 646, + Name: "Corruptor Raiment", Bonuses: map[int32]core.ApplySetBonus{ 2: func(agent core.Agent, setBonusAura *core.Aura) { - warlock := agent.(WarlockAgent).GetWarlock() - warlock.T15_2pc = agent.GetCharacter().RegisterAura(core.Aura{ - Label: "Regalia of the Thousandfold Hells - 2P Buff", - Duration: time.Second * 20, - }).AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_DotNumberOfTicks_Flat, - IntValue: 2, - ClassMask: WarlockSpellHaunt, - }) - - agent.GetCharacter().OnSpellRegistered(func(spell *core.Spell) { - if spell.Matches(WarlockDarkSoulSpell) { - spell.RelatedSelfBuff.ApplyOnGain(func(aura *core.Aura, sim *core.Simulation) { - warlock.T15_2pc.Activate(sim) - }) - } - }) + // Causes your pet to be healed for 15% of the damage you deal. + // Pet Healing - 37381 }, 4: func(agent core.Agent, setBonusAura *core.Aura) { - setBonusAura.AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.05, - ClassMask: WarlockSpellMaleficGrasp | WarlockSpellDrainSoul, - }) - - warlock := agent.(WarlockAgent).GetWarlock() - warlock.T15_4pc = setBonusAura + // Your Shadowbolt spell hits increase the damage of Corruption by 10% and your Incinerate spell hits increase the damage of Immolate by 10%. + // Improved Corruption and Immolate - 37384 }, }, }) -// T16 -var ItemSetRegaliaOfTheHornedNightmare = core.NewItemSet(core.ItemSet{ - Name: "Regalia of the Horned Nightmare", - DisabledInChallengeMode: true, +// T6 +var ItemSetMaleficRaiment = core.NewItemSet(core.ItemSet{ + ID: 670, + Name: "Malefic Raiment", Bonuses: map[int32]core.ApplySetBonus{ 2: func(agent core.Agent, setBonusAura *core.Aura) { - warlock := agent.(WarlockAgent).GetWarlock() - var buff *core.Aura - switch warlock.Spec { - case proto.Spec_SpecAfflictionWarlock: - buff = warlock.RegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 145082}, - Label: "Regalia of the Horned Nightmare - Affli - 2pc", - Duration: time.Second * 10, - }).AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.15, - ClassMask: WarlockSpellDrainSoul | WarlockSpellMaleficGrasp, - }) - - warlock.T16_2pc_buff = buff - setBonusAura.OnSpellHitDealt = func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.Matches(WarlockSpellUnstableAffliction) && result.DidCrit() && sim.Proc(0.5, "T16 - 2pc") { - buff.Activate(sim) - return - } - } - case proto.Spec_SpecDemonologyWarlock: - // TODO: Research if all pets or just the primary pet is affected - buffAction := core.ActionID{SpellID: 145075} - applyBuffAura := func(unit *core.Unit) { - unit.RegisterAura(core.Aura{ - ActionID: buffAction, - Label: "Regalia of the Horned Nightmare - Demo - 2pc", - Duration: time.Second * 10, - }).AttachMultiplicativePseudoStatBuff(&unit.PseudoStats.DamageDealtMultiplier, 1.2) - } - - applyBuffAura(&warlock.Unit) - for _, pet := range warlock.Pets { - if pet.IsGuardian() { - continue - } - - applyBuffAura(&pet.Unit) - } - - setBonusAura.OnSpellHitDealt = func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.Matches(WarlockSpellSoulFire) && sim.Proc(0.2, "T16 - 2pc") { - warlock.GetAuraByID(buffAction).Activate(sim) - for _, pet := range warlock.Pets { - if pet.IsGuardian() { - continue - } - - if !pet.IsActive() { - continue - } - - pet.GetAuraByID(buffAction).Activate(sim) - } - } - } - case proto.Spec_SpecDestructionWarlock: - buff = warlock.RegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 145075}, - Label: "Regalia of the Horned Nightmare - Destro - 2pc", - Duration: time.Second * 10, - }).AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_BonusCrit_Percent, - FloatValue: 0.1, - ClassMask: WarlockSpellImmolate | WarlockSpellImmolateDot | WarlockSpellIncinerate | WarlockSpellFaBIncinerate, - }) - - setBonusAura.OnSpellHitDealt = func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.Matches(WarlockSpellConflagrate|WarlockSpellFaBConflagrate) && result.DidCrit() && sim.Proc(0.2, "T16 - 2pc") { - buff.Activate(sim) - return - } - } - default: - return - } + // Each time one of your Corruption or Immolate spells deals periodic damage, you heal 70 health. + // Dot Heals - 38394 }, 4: func(agent core.Agent, setBonusAura *core.Aura) { - warlock := agent.(WarlockAgent).GetWarlock() - switch agent.GetCharacter().Spec { - case proto.Spec_SpecAfflictionWarlock: - warlock.OnSpellRegistered(func(spell *core.Spell) { - if !spell.Matches(WarlockSpellHaunt) { - return - } - - for _, target := range warlock.Env.Encounter.AllTargets { - dot := spell.Dot(&target.Unit) - if dot == nil { - break - } - dot.ApplyOnExpire(func(_ *core.Aura, sim *core.Simulation) { - if sim.Proc(0.1, "T16 4p") { - warlock.GetSecondaryResourceBar().Gain(sim, 1, spell.ActionID) - } - }) - } - - }) - case proto.Spec_SpecDemonologyWarlock: - setBonusAura.AttachProcTrigger(core.ProcTrigger{ - Callback: core.CallbackOnCastComplete, - ClassSpellMask: WarlockSpellShadowBolt | WarlockSpellTouchOfChaos, - ProcChance: 0.08, - Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - // TODO: NOT IMPLEMENTED - Need to verify how this interacts with existing Shadow Flame DoT - }, - }) - case proto.Spec_SpecDestructionWarlock: - buff := warlock.RegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 145164}, - Label: "Regalia of the Horned Nightmare - Demo - 4pc", - Duration: time.Second * 5, - Icd: &core.Cooldown{ - Timer: warlock.NewTimer(), - Duration: time.Second * 10, - }, - }).AttachStatBuff(stats.CritRating, core.CritRatingPerCritPercent*15) - - warlock.GetSecondaryResourceBar().RegisterOnGain(func( - sim *core.Simulation, - _, realGain float64, - actionID core.ActionID, - ) { - if realGain == 0 || buff.Icd.IsReady(sim) { - return - } - - old := warlock.GetSecondaryResourceBar().Value() - realGain - if int(old/10) == int(warlock.GetSecondaryResourceBar().Value()/10) { - return - } - - buff.Activate(sim) - }) - } + // Increases damage done by shadowbolt and incinerate by 6%. + // Improved Shadow Bolt and Incinerate - 38393 }, }, }) + +func init() { + core.NewItemEffect(19337, func(agent core.Agent) { + // The Black Book + }) + + core.NewItemEffect(30449, func(agent core.Agent) { + // Void Star Talisman + }) + + core.NewItemEffect(32493, func(agent core.Agent) { + // Ashtongue Talisman of Shadows + }) +} diff --git a/sim/warlock/affliction/nightfall.go b/sim/warlock/nightfall.go similarity index 100% rename from sim/warlock/affliction/nightfall.go rename to sim/warlock/nightfall.go diff --git a/sim/warlock/destruction/rain_of_fire.go b/sim/warlock/rain_of_fire.go similarity index 100% rename from sim/warlock/destruction/rain_of_fire.go rename to sim/warlock/rain_of_fire.go diff --git a/sim/warlock/affliction/seed_of_corruption.go b/sim/warlock/seed_of_corruption.go similarity index 100% rename from sim/warlock/affliction/seed_of_corruption.go rename to sim/warlock/seed_of_corruption.go diff --git a/sim/warlock/demonology/shadowbolt.go b/sim/warlock/shadowbolt.go similarity index 100% rename from sim/warlock/demonology/shadowbolt.go rename to sim/warlock/shadowbolt.go diff --git a/sim/warlock/demonology/soulfire.go b/sim/warlock/soulfire.go similarity index 100% rename from sim/warlock/demonology/soulfire.go rename to sim/warlock/soulfire.go From 45adbf129f90a438f3d5c65ad091216cab6a9e55 Mon Sep 17 00:00:00 2001 From: jazz405 Date: Sun, 30 Nov 2025 21:56:10 -0500 Subject: [PATCH 02/20] more updates, specific spells --- .gitignore | 1 + sim/warlock/affliction/affliction.go | 131 ------------ sim/warlock/affliction/affliction_test.go | 17 -- sim/warlock/affliction/apl_values.go | 202 ------------------ sim/warlock/agony.go | 11 +- sim/warlock/conflagrate.go | 39 ++++ sim/warlock/corruption.go | 39 ++-- sim/warlock/curse_of_elements.go | 4 +- sim/warlock/demonology/apl_values.go | 36 ---- sim/warlock/demonology/demonology.go | 153 ------------- sim/warlock/demonology/demonology_test.go | 15 -- sim/warlock/demonology/hellfire.go | 31 --- sim/warlock/demonology/master_demonologist.go | 94 -------- sim/warlock/destruction/conflagrate.go | 60 ------ sim/warlock/destruction/destruction.go | 105 --------- sim/warlock/destruction/destruction_test.go | 15 -- sim/warlock/destruction/shadowburn.go | 57 ----- sim/warlock/doom.go | 31 +-- sim/warlock/drain_life.go | 24 +-- sim/warlock/drain_soul.go | 96 --------- sim/warlock/{demonology => }/fel_guard.go | 26 +-- sim/warlock/hellfire.go | 61 ------ sim/warlock/immolate.go | 60 ++---- sim/warlock/incinerate.go | 27 +-- sim/warlock/lifetap.go | 18 +- sim/warlock/nightfall.go | 31 --- sim/warlock/rain_of_fire.go | 68 ------ sim/warlock/seed_of_corruption.go | 37 ++-- sim/warlock/shadowbolt.go | 22 +- sim/warlock/shadowburn.go | 34 +++ sim/warlock/soulfire.go | 100 +++------ .../{affliction => }/unstable_affliction.go | 28 +-- sim/warlock/warlock.go | 185 ++++++---------- 33 files changed, 284 insertions(+), 1574 deletions(-) delete mode 100644 sim/warlock/affliction/affliction.go delete mode 100644 sim/warlock/affliction/affliction_test.go delete mode 100644 sim/warlock/affliction/apl_values.go create mode 100644 sim/warlock/conflagrate.go delete mode 100644 sim/warlock/demonology/apl_values.go delete mode 100644 sim/warlock/demonology/demonology.go delete mode 100644 sim/warlock/demonology/demonology_test.go delete mode 100644 sim/warlock/demonology/hellfire.go delete mode 100644 sim/warlock/demonology/master_demonologist.go delete mode 100644 sim/warlock/destruction/conflagrate.go delete mode 100644 sim/warlock/destruction/destruction.go delete mode 100644 sim/warlock/destruction/destruction_test.go delete mode 100644 sim/warlock/destruction/shadowburn.go delete mode 100644 sim/warlock/drain_soul.go rename sim/warlock/{demonology => }/fel_guard.go (81%) delete mode 100644 sim/warlock/hellfire.go delete mode 100644 sim/warlock/nightfall.go delete mode 100644 sim/warlock/rain_of_fire.go create mode 100644 sim/warlock/shadowburn.go rename sim/warlock/{affliction => }/unstable_affliction.go (60%) diff --git a/.gitignore b/.gitignore index 80937fb89e..f3c323e5f9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ ui/*/*/index.html # IDE folders .idea .history +.vscode # binaries dist diff --git a/sim/warlock/affliction/affliction.go b/sim/warlock/affliction/affliction.go deleted file mode 100644 index 6b7ebfca01..0000000000 --- a/sim/warlock/affliction/affliction.go +++ /dev/null @@ -1,131 +0,0 @@ -package affliction - -import ( - "math" - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/core/proto" - "github.com/wowsims/tbc/sim/warlock" -) - -func RegisterAfflictionWarlock() { - core.RegisterAgentFactory( - proto.Player_AfflictionWarlock{}, - proto.Spec_SpecAfflictionWarlock, - func(character *core.Character, options *proto.Player) core.Agent { - return NewAfflictionWarlock(character, options) - }, - func(player *proto.Player, spec interface{}) { - playerSpec, ok := spec.(*proto.Player_AfflictionWarlock) - if !ok { - panic("Invalid spec value for Affliction Warlock!") - } - player.Spec = playerSpec - }, - ) -} - -func NewAfflictionWarlock(character *core.Character, options *proto.Player) *AfflictionWarlock { - affOptions := options.GetAfflictionWarlock().Options - - affliction := &AfflictionWarlock{ - Warlock: warlock.NewWarlock(character, options, affOptions.ClassOptions), - ExhaleWindow: time.Duration(affOptions.ExhaleWindow * int32(time.Millisecond)), - } - - affliction.MaleficGraspMaleficEffectMultiplier = 0.3 - affliction.DrainSoulMaleficEffectMultiplier = 0.6 - - return affliction -} - -type AfflictionWarlock struct { - *warlock.Warlock - - SoulShards core.SecondaryResourceBar - Agony *core.Spell - UnstableAffliction *core.Spell - - SoulBurnAura *core.Aura - - LastCorruptionTarget *core.Unit // Tracks the last target we've applied corruption to - LastInhaleTarget *core.Unit - - DrainSoulMaleficEffectMultiplier float64 - MaleficGraspMaleficEffectMultiplier float64 - ProcMaleficEffect func(target *core.Unit, coeff float64, sim *core.Simulation) - - ExhaleWindow time.Duration -} - -func (affliction AfflictionWarlock) getMasteryBonus() float64 { - return (8 + affliction.GetMasteryPoints()) * 3.1 -} - -func (affliction *AfflictionWarlock) GetWarlock() *warlock.Warlock { - return affliction.Warlock -} - -const MaxSoulShards = 4.0 - -func (affliction *AfflictionWarlock) Initialize() { - affliction.Warlock.Initialize() - - affliction.SoulShards = affliction.RegisterNewDefaultSecondaryResourceBar(core.SecondaryResourceConfig{ - Type: proto.SecondaryResourceType_SecondaryResourceTypeSoulShards, - Max: MaxSoulShards, - Default: MaxSoulShards, - }) - - affliction.registerPotentAffliction() - affliction.registerHaunt() - affliction.RegisterCorruption(func(resultList core.SpellResultSlice, spell *core.Spell, sim *core.Simulation) { - if resultList[0].Landed() { - affliction.LastCorruptionTarget = resultList[0].Target - } - }, nil) - - affliction.registerAgony() - affliction.registerNightfall() - affliction.registerUnstableAffliction() - affliction.registerMaleficEffect() - affliction.registerMaleficGrasp() - affliction.registerDrainSoul() - affliction.registerDarkSoulMisery() - affliction.registerSoulburn() - affliction.registerSeed() - affliction.registerSoulSwap() - - affliction.registerHotfixes() -} - -func (affliction *AfflictionWarlock) ApplyTalents() { - affliction.Warlock.ApplyTalents() -} - -func (affliction *AfflictionWarlock) Reset(sim *core.Simulation) { - affliction.Warlock.Reset(sim) - - affliction.LastCorruptionTarget = nil -} - -func (affliction *AfflictionWarlock) OnEncounterStart(sim *core.Simulation) { - defaultShards := MaxSoulShards - if affliction.SoulBurnAura.IsActive() { - defaultShards -= 1 - } - - haunt := affliction.GetSpell(core.ActionID{SpellID: HauntSpellID}) - count := float64(affliction.SpellsInFlight[haunt]) - defaultShards -= count - - affliction.SoulShards.ResetBarTo(sim, defaultShards) - affliction.Warlock.OnEncounterStart(sim) -} - -func calculateDoTBaseTickDamage(dot *core.Dot, target *core.Unit) float64 { - stacks := math.Max(float64(dot.Aura.GetStacks()), 1) - attackTable := dot.Spell.Unit.AttackTables[target.UnitIndex] - return dot.SnapshotBaseDamage * dot.Spell.AttackerDamageMultiplier(attackTable, true) * stacks -} diff --git a/sim/warlock/affliction/affliction_test.go b/sim/warlock/affliction/affliction_test.go deleted file mode 100644 index c054829309..0000000000 --- a/sim/warlock/affliction/affliction_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package affliction - -import ( - "testing" - - _ "unsafe" - - "github.com/wowsims/tbc/sim/common" -) - -func init() { - RegisterAfflictionWarlock() - common.RegisterAllEffects() -} - -func TestAffliction(t *testing.T) { -} diff --git a/sim/warlock/affliction/apl_values.go b/sim/warlock/affliction/apl_values.go deleted file mode 100644 index 02f80156eb..0000000000 --- a/sim/warlock/affliction/apl_values.go +++ /dev/null @@ -1,202 +0,0 @@ -package affliction - -import ( - "fmt" - "math" - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/core/proto" -) - -func (warlock *AfflictionWarlock) NewAPLValue(rot *core.APLRotation, config *proto.APLValue) core.APLValue { - switch config.Value.(type) { - case *proto.APLValue_WarlockHauntInFlight: - spellInFlight := proto.APLValueSpellInFlight{ - SpellId: core.Spell{ActionID: core.ActionID{SpellID: 48181}}.ToProto(), - } - return rot.NewValueSpellInFlight(&spellInFlight, nil) - case *proto.APLValue_AfflictionCurrentSnapshot: - return warlock.newAfflictionCurrentSnapshot(rot, config.GetAfflictionCurrentSnapshot(), config.Uuid) - case *proto.APLValue_AfflictionExhaleWindow: - return warlock.newValueExhaleWindow(config.GetAfflictionExhaleWindow(), config.Uuid) - default: - return warlock.Warlock.NewAPLValue(rot, config) - } -} - -func (warlock *AfflictionWarlock) NewAPLAction(rot *core.APLRotation, config *proto.APLAction) core.APLActionImpl { - switch config.Action.(type) { - case *proto.APLAction_WarlockNextExhaleTarget: - return warlock.newActionNextExhaleTarget(config.GetWarlockNextExhaleTarget()) - default: - return nil - } -} - -type APLActionNextExhaleTarget struct { - warlock *AfflictionWarlock - lastExecutedAt time.Duration -} - -// Execute implements core.APLActionImpl. -func (action *APLActionNextExhaleTarget) Execute(sim *core.Simulation) { - action.lastExecutedAt = sim.CurrentTime - if action.warlock.CurrentTarget != action.warlock.LastInhaleTarget { - return - } - - nextTarget := core.NewUnitReference(&proto.UnitReference{Type: proto.UnitReference_NextTarget}, &action.warlock.Unit).Get() - if nextTarget == nil { - return - } - - if sim.Log != nil { - action.warlock.Log(sim, "Changing target to %s", nextTarget.Label) - } - - action.warlock.CurrentTarget = nextTarget -} - -func (action *APLActionNextExhaleTarget) Finalize(*core.APLRotation) {} -func (action *APLActionNextExhaleTarget) GetAPLValues() []core.APLValue { return nil } -func (action *APLActionNextExhaleTarget) GetInnerActions() []*core.APLAction { return nil } -func (action *APLActionNextExhaleTarget) GetNextAction(sim *core.Simulation) *core.APLAction { - return nil -} -func (action *APLActionNextExhaleTarget) PostFinalize(*core.APLRotation) {} -func (action *APLActionNextExhaleTarget) ReResolveVariableRefs(*core.APLRotation, map[string]*proto.APLValue) { -} - -func (action *APLActionNextExhaleTarget) IsReady(sim *core.Simulation) bool { - // Prevent infinite loops by only allowing this action to be performed once at each timestamp. - return action.lastExecutedAt != sim.CurrentTime -} - -// Reset implements core.APLActionImpl. -func (action *APLActionNextExhaleTarget) Reset(sim *core.Simulation) { - action.lastExecutedAt = core.NeverExpires -} - -// String implements core.APLActionImpl. -func (action *APLActionNextExhaleTarget) String() string { - return "Changing to Next Exhale Target" -} - -func (warlock *AfflictionWarlock) newActionNextExhaleTarget(_ *proto.APLActionWarlockNextExhaleTarget) core.APLActionImpl { - return &APLActionNextExhaleTarget{ - warlock: warlock, - lastExecutedAt: core.NeverExpires, - } -} - -// modified snapshot tracker, designed to be affliction specific. -// checks the snapshotted magnitude of existing dots relative to baseline. -// ignores crit and haste factors, since malefic effect ignores these -type APLValueAfflictionCurrentSnapshot struct { - core.DefaultAPLValueImpl - warlock *AfflictionWarlock - spell *core.Spell - sbssDotRefs []**core.Spell - targetRef core.UnitReference - baseValue float64 - baseValueDummyAura *core.Aura // Used to get the base value at encounter start -} - -func (warlock *AfflictionWarlock) newAfflictionCurrentSnapshot(rot *core.APLRotation, config *proto.APLValueAfflictionCurrentSnapshot, _ *proto.UUID) *APLValueAfflictionCurrentSnapshot { - spell := rot.GetAPLSpell(config.SpellId) - if spell == nil { - return nil - } - - targetRef := rot.GetTargetUnit(config.TargetUnit) - - baseValueDummyAura := core.MakePermanent(warlock.GetOrRegisterAura(core.Aura{ - Label: "Dummy Aura - APL Current Snapshot Base Value", - Duration: core.NeverExpires, - })) - - return &APLValueAfflictionCurrentSnapshot{ - warlock: warlock, - spell: spell, - targetRef: targetRef, - baseValueDummyAura: baseValueDummyAura, - } -} - -func (value *APLValueAfflictionCurrentSnapshot) Finalize(rot *core.APLRotation) { - value.sbssDotRefs = []**core.Spell{&value.warlock.Agony, &value.warlock.Corruption, &value.warlock.UnstableAffliction} - if value.baseValueDummyAura != nil { - value.baseValueDummyAura.ApplyOnInit(func(aura *core.Aura, sim *core.Simulation) { - // Soulburn: Soul Swap - if value.spell.ActionID.SpellID == 86121 && value.spell.ActionID.Tag == 1 { - total := 0.0 - target := value.targetRef.Get() - - for _, spellRef := range value.sbssDotRefs { - spell := (*spellRef) - total += (spell.ExpectedTickDamage(sim, target) * spell.Dot(target).CalcTickPeriod().Seconds()) / (1 + (spell.SpellCritChance(target) * (spell.CritDamageMultiplier() - 1))) - } - value.baseValue = total - - } else { - target := value.targetRef.Get() - value.baseValue = value.spell.ExpectedTickDamage(sim, target) * value.spell.Dot(target).CalcTickPeriod().Seconds() - value.baseValue /= (1 + (value.spell.SpellCritChance(target) * (value.spell.CritDamageMultiplier() - 1))) - } - }) - } -} - -func (value *APLValueAfflictionCurrentSnapshot) Type() proto.APLValueType { - return proto.APLValueType_ValueTypeFloat -} - -func (value *APLValueAfflictionCurrentSnapshot) String() string { - return fmt.Sprintf("Current Snapshot on %s", value.spell.ActionID) -} - -func (value *APLValueAfflictionCurrentSnapshot) GetFloat(sim *core.Simulation) float64 { - target := value.targetRef.Get() - snapshotDamage := 0.0 - //Soulburn: Soul Swap - if value.spell.ActionID.SpellID == 86121 && value.spell.ActionID.Tag == 1 { - target := value.targetRef.Get() - - for _, spellRef := range value.sbssDotRefs { - dot := (*spellRef).Dot(target) - - snapshotDamage += (dot.Spell.ExpectedTickDamageFromCurrentSnapshot(sim, target) * dot.TickPeriod().Seconds()) / (1 + (dot.SnapshotCritChance * (dot.Spell.CritDamageMultiplier() - 1))) - } - } else { - dot := value.spell.Dot(target) - snapshotDamage = (value.spell.ExpectedTickDamageFromCurrentSnapshot(sim, target) * dot.TickPeriod().Seconds()) / (1 + (dot.SnapshotCritChance * (dot.Spell.CritDamageMultiplier() - 1))) - } - - if snapshotDamage == 0 { - return -1 - } - - // Rounding this to effectively 3 decimal places as a percentage to avoid floating point errors - return math.Round((snapshotDamage/value.baseValue)*100000)/100000 - 1 -} - -type APLValueExhaleWindow struct { - core.DefaultAPLValueImpl - warlock *AfflictionWarlock -} - -func (warlock *AfflictionWarlock) newValueExhaleWindow(_ *proto.APLValueAfflictionExhaleWindow, _ *proto.UUID) core.APLValue { - return &APLValueExhaleWindow{ - warlock: warlock, - } -} -func (value *APLValueExhaleWindow) Type() proto.APLValueType { - return proto.APLValueType_ValueTypeDuration -} -func (value *APLValueExhaleWindow) GetDuration(sim *core.Simulation) time.Duration { - return time.Duration(value.warlock.ExhaleWindow) -} -func (value *APLValueExhaleWindow) String() string { - return "Exhale Window()" -} diff --git a/sim/warlock/agony.go b/sim/warlock/agony.go index e9936d9b4f..e7b3e92a84 100644 --- a/sim/warlock/agony.go +++ b/sim/warlock/agony.go @@ -10,12 +10,12 @@ const agonyScale = 0.0255 const agonyCoeff = 0.0255 func (warlock *Warlock) registerAgony() { - warlock.Agony = warlock.RegisterSpell(core.SpellConfig{ + warlock.CurseOfAgony = warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 980}, Flags: core.SpellFlagAPL, ProcMask: core.ProcMaskSpellDamage, SpellSchool: core.SpellSchoolShadow, - ClassSpellMask: warlock.WarlockSpellAgony, + ClassSpellMask: WarlockSpellCurseOfAgony, ThreatMultiplier: 1, DamageMultiplier: 1, @@ -62,13 +62,6 @@ func (warlock *Warlock) registerAgony() { }, }, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - if spell.CalcAndDealOutcome(sim, target, spell.OutcomeMagicHit).Landed() { - warlock.ApplyDotWithPandemic(spell.Dot(target), sim) - spell.Dot(target).AddStack(sim) - } - }, - ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, useSnapshot bool) *core.SpellResult { dot := spell.Dot(target) diff --git a/sim/warlock/conflagrate.go b/sim/warlock/conflagrate.go new file mode 100644 index 0000000000..b372cc1712 --- /dev/null +++ b/sim/warlock/conflagrate.go @@ -0,0 +1,39 @@ +package warlock + +import ( + "time" + + "github.com/wowsims/tbc/sim/core" +) + +const conflagrateCoeff = 0.429 + +func (warlock *Warlock) registerConflagrate() { + warlock.Conflagrate = warlock.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: 30912}, + SpellSchool: core.SpellSchoolFire, + ProcMask: core.ProcMaskSpellDamage, + Flags: core.SpellFlagAPL, + ClassSpellMask: WarlockSpellConflagrate, + + ManaCost: core.ManaCostOptions{FlatCost: 305}, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + CD: core.Cooldown{ + Duration: time.Second * 10, + }, + }, + DamageMultiplier: 1.0, + CritMultiplier: warlock.DefaultCritMultiplier(), + ThreatMultiplier: 1, + BonusCoefficient: conflagrateCoeff, + RechargeTime: time.Second * 10, + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + //tie this to landed/hit + target.GetAura("Immolate (DoT)").Deactivate(sim) + + }, + }) +} diff --git a/sim/warlock/corruption.go b/sim/warlock/corruption.go index 97d38cd34b..04e8c11705 100644 --- a/sim/warlock/corruption.go +++ b/sim/warlock/corruption.go @@ -6,34 +6,37 @@ import ( "github.com/wowsims/tbc/sim/core" ) -const corruptionScale = 0.165 -const corruptionCoeff = 0.165 +const corruptionScale = 0.156 +const corruptionCoeff = 0.156 func (warlock *Warlock) RegisterCorruption(onApplyCallback WarlockSpellCastedCallback, onTickCallback WarlockSpellCastedCallback) *core.Spell { resultSlice := make(core.SpellResultSlice, 1) warlock.Corruption = warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 172}, + ActionID: core.ActionID{SpellID: 27216}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagAPL, ClassSpellMask: WarlockSpellCorruption, - ManaCost: core.ManaCostOptions{BaseCostPercent: 1.25}, - Cast: core.CastConfig{DefaultCast: core.Cast{GCD: core.GCDDefault}}, - - DamageMultiplierAdditive: 1, - CritMultiplier: warlock.DefaultCritMultiplier(), - ThreatMultiplier: 1, + ManaCost: core.ManaCostOptions{FlatCost: 370}, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + CastTime: time.Millisecond*2000 - (time.Millisecond * 400 * time.Duration(warlock.Talents.ImprovedCorruption)), + }, + }, Dot: core.DotConfig{ Aura: core.Aura{ - Label: "Corruption", + Label: "Corruption", + Tag: "Affliction", + ActionID: core.ActionID{SpellID: 27216}, }, - NumberOfTicks: 9, - TickLength: 2 * time.Second, - AffectedByCastSpeed: true, - BonusCoefficient: corruptionCoeff, + NumberOfTicks: 6, + TickLength: 3 * time.Second, + AffectedByCastSpeed: false, + BonusCoefficient: corruptionCoeff + ((0.12 * float64(warlock.Talents.EmpoweredCorruption)) / 6), OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { dot.Snapshot(target, warlock.CalcScalingSpellDmg(corruptionScale)) @@ -41,10 +44,6 @@ func (warlock *Warlock) RegisterCorruption(onApplyCallback WarlockSpellCastedCal OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { resultSlice[0] = dot.CalcSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) - if warlock.SiphonLife != nil { - warlock.SiphonLife.Cast(sim, &warlock.Unit) - } - if onTickCallback != nil { onTickCallback(resultSlice, dot.Spell, sim) } @@ -55,10 +54,6 @@ func (warlock *Warlock) RegisterCorruption(onApplyCallback WarlockSpellCastedCal ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHitNoHitCounter) - dot := spell.Dot(target) - if result.Landed() { - warlock.ApplyDotWithPandemic(dot, sim) - } if onApplyCallback != nil { resultSlice[0] = result onApplyCallback(resultSlice, spell, sim) diff --git a/sim/warlock/curse_of_elements.go b/sim/warlock/curse_of_elements.go index 0721f6e9f6..17a32e2ded 100644 --- a/sim/warlock/curse_of_elements.go +++ b/sim/warlock/curse_of_elements.go @@ -10,14 +10,14 @@ func (warlock *Warlock) registerCurseOfElements() { warlock.CurseOfElementsAuras = warlock.NewEnemyAuraArray(core.CurseOfElementsAura) warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 1490}, + ActionID: core.ActionID{SpellID: 27228}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskEmpty, Flags: core.SpellFlagAPL, ClassSpellMask: WarlockSpellCurseOfElements, ManaCost: core.ManaCostOptions{ - BaseCostPercent: 4, + FlatCost: 260, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ diff --git a/sim/warlock/demonology/apl_values.go b/sim/warlock/demonology/apl_values.go deleted file mode 100644 index 11dc2e07c5..0000000000 --- a/sim/warlock/demonology/apl_values.go +++ /dev/null @@ -1,36 +0,0 @@ -package demonology - -import ( - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/core/proto" -) - -func (warlock *DemonologyWarlock) NewAPLValue(rot *core.APLRotation, config *proto.APLValue) core.APLValue { - switch config.Value.(type) { - case *proto.APLValue_WarlockHandOfGuldanInFlight: - return warlock.newValueWarlockHandOfGuldanInFlight(rot, config.GetWarlockHandOfGuldanInFlight()) - default: - return warlock.Warlock.NewAPLValue(rot, config) - } -} - -type APLValueWarlockHandOfGuldanInFlight struct { - core.DefaultAPLValueImpl - warlock *DemonologyWarlock -} - -func (warlock *DemonologyWarlock) newValueWarlockHandOfGuldanInFlight(rot *core.APLRotation, config *proto.APLValueWarlockHandOfGuldanInFlight) core.APLValue { - return &APLValueWarlockHandOfGuldanInFlight{ - warlock: warlock, - } -} -func (value *APLValueWarlockHandOfGuldanInFlight) Type() proto.APLValueType { - return proto.APLValueType_ValueTypeBool -} -func (value *APLValueWarlockHandOfGuldanInFlight) GetBool(sim *core.Simulation) bool { - warlock := value.warlock - return warlock.HandOfGuldanImpactTime > 0 && sim.CurrentTime < warlock.HandOfGuldanImpactTime -} -func (value *APLValueWarlockHandOfGuldanInFlight) String() string { - return "Warlock Hand of Guldan in Flight()" -} diff --git a/sim/warlock/demonology/demonology.go b/sim/warlock/demonology/demonology.go deleted file mode 100644 index 80d9b98021..0000000000 --- a/sim/warlock/demonology/demonology.go +++ /dev/null @@ -1,153 +0,0 @@ -package demonology - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/core/proto" - "github.com/wowsims/tbc/sim/warlock" -) - -func RegisterDemonologyWarlock() { - core.RegisterAgentFactory( - proto.Player_DemonologyWarlock{}, - proto.Spec_SpecDemonologyWarlock, - func(character *core.Character, options *proto.Player) core.Agent { - return NewDemonologyWarlock(character, options) - }, - func(player *proto.Player, spec interface{}) { - playerSpec, ok := spec.(*proto.Player_DemonologyWarlock) - if !ok { - panic("Invalid spec value for Demonology Warlock!") - } - player.Spec = playerSpec - }, - ) -} - -func NewDemonologyWarlock(character *core.Character, options *proto.Player) *DemonologyWarlock { - demoOptions := options.GetDemonologyWarlock().Options - - demonology := &DemonologyWarlock{ - Warlock: warlock.NewWarlock(character, options, demoOptions.ClassOptions), - } - - demonology.Felguard = demonology.registerFelguard() - demonology.registerWildImp(15) - demonology.registerGrimoireOfService() - return demonology -} - -type DemonologyWarlock struct { - *warlock.Warlock - - DemonicFury core.SecondaryResourceBar - Metamorphosis *core.Spell - HandOfGuldan *core.Spell - ChaosWave *core.Spell - - MoltenCore *core.Aura - - Felguard *warlock.WarlockPet - WildImps []*WildImpPet - HandOfGuldanImpactTime time.Duration - ImpSwarm *core.Spell -} - -func (demonology *DemonologyWarlock) GetWarlock() *warlock.Warlock { - return demonology.Warlock -} - -const DefaultDemonicFury = 200 - -func (demonology *DemonologyWarlock) Initialize() { - demonology.Warlock.Initialize() - - demonology.DemonicFury = demonology.RegisterNewDefaultSecondaryResourceBar(core.SecondaryResourceConfig{ - Type: proto.SecondaryResourceType_SecondaryResourceTypeDemonicFury, - Max: 1000, // Multiplied by 10 to avoid having to refactor to float - Default: DefaultDemonicFury, - }) - - demonology.registerMetamorphosis() - demonology.registerMasterDemonologist() - demonology.registerShadowBolt() - demonology.registerFelFlame() - demonology.registerCorruption() - demonology.registerDrainLife() - demonology.registerHandOfGuldan() - demonology.registerHellfire() - demonology.registerSoulfire() - demonology.registerMoltenCore() - demonology.registerCarrionSwarm() - demonology.registerChaosWave() - demonology.registerDoom() - demonology.registerImmolationAura() - demonology.registerTouchOfChaos() - demonology.registerVoidRay() - demonology.registerDarksoulKnowledge() - - demonology.registerHotfixes() -} - -func (demonology *DemonologyWarlock) ApplyTalents() { - demonology.Warlock.ApplyTalents() - - // Demo specific versions - demonology.registerGrimoireOfSupremacy() - demonology.registerGrimoireOfSacrifice() -} - -func (demonology *DemonologyWarlock) Reset(sim *core.Simulation) { - demonology.Warlock.Reset(sim) - - demonology.HandOfGuldanImpactTime = 0 -} - -func (demonology *DemonologyWarlock) OnEncounterStart(sim *core.Simulation) { - demonology.DemonicFury.ResetBarTo(sim, DefaultDemonicFury) - demonology.Warlock.OnEncounterStart(sim) -} - -func NewDemonicFuryCost(cost int) *warlock.SecondaryResourceCost { - return &warlock.SecondaryResourceCost{ - SecondaryCost: cost, - Name: "Demonic Fury", - } -} - -func (demo *DemonologyWarlock) IsInMeta() bool { - return demo.Metamorphosis.RelatedSelfBuff.IsActive() -} - -func (demo *DemonologyWarlock) CanSpendDemonicFury(amount float64) bool { - if demo.T15_2pc.IsActive() { - amount *= 0.7 - } - - return demo.DemonicFury.CanSpend(amount) -} - -func (demo *DemonologyWarlock) SpendUpToDemonicFury(sim *core.Simulation, limit float64, actionID core.ActionID) { - if demo.T15_2pc.IsActive() { - limit *= 0.7 - } - - demo.DemonicFury.SpendUpTo(sim, limit, actionID) -} - -func (demo *DemonologyWarlock) SpendDemonicFury(sim *core.Simulation, amount float64, actionID core.ActionID) { - if demo.T15_2pc.IsActive() { - amount *= 0.7 - } - - demo.DemonicFury.Spend(sim, amount, actionID) -} - -func (demo *DemonologyWarlock) GainDemonicFury(sim *core.Simulation, amount float64, actionID core.ActionID) { - if demo.T15_4pc.IsActive() { - amount *= 1.1 - } - - demo.DemonicFury.Gain(sim, amount, actionID) -} diff --git a/sim/warlock/demonology/demonology_test.go b/sim/warlock/demonology/demonology_test.go deleted file mode 100644 index 6c0e02c14d..0000000000 --- a/sim/warlock/demonology/demonology_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package demonology - -import ( - "testing" - - "github.com/wowsims/tbc/sim/common" -) - -func init() { - RegisterDemonologyWarlock() - common.RegisterAllEffects() -} - -func TestDemonology(t *testing.T) { -} diff --git a/sim/warlock/demonology/hellfire.go b/sim/warlock/demonology/hellfire.go deleted file mode 100644 index 87e34419db..0000000000 --- a/sim/warlock/demonology/hellfire.go +++ /dev/null @@ -1,31 +0,0 @@ -package demonology - -import ( - "github.com/wowsims/tbc/sim/core" -) - -func (demonology *DemonologyWarlock) registerHellfire() { - hellfire := demonology.RegisterHellfire(func(resultList core.SpellResultSlice, spell *core.Spell, sim *core.Simulation) { - if demonology.IsInMeta() { - return - } - - // 10 for primary, 3 for every other target - fury := 10 + ((len(resultList))-1)*3 - demonology.GainDemonicFury(sim, float64(fury), spell.ActionID) - }) - - oldExtra := hellfire.ExtraCastCondition - hellfire.ExtraCastCondition = func(sim *core.Simulation, target *core.Unit) bool { - if oldExtra != nil && !oldExtra(sim, target) { - return false - } - - return !demonology.IsInMeta() - } - - demonology.Metamorphosis.RelatedSelfBuff.ApplyOnGain(func(aura *core.Aura, sim *core.Simulation) { - demonology.Hellfire.SelfHot().Deactivate(sim) - }) - -} diff --git a/sim/warlock/demonology/master_demonologist.go b/sim/warlock/demonology/master_demonologist.go deleted file mode 100644 index b85a4a28dd..0000000000 --- a/sim/warlock/demonology/master_demonologist.go +++ /dev/null @@ -1,94 +0,0 @@ -package demonology - -import ( - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -// Caster Form + Pet Damage = 1% per Masterypoint -func (demo *DemonologyWarlock) getNormalMasteryBonus() float64 { - return demo.getNormalMasteryBonusFrom(demo.GetMasteryPoints()) -} - -func (demo *DemonologyWarlock) getNormalMasteryBonusFrom(points float64) float64 { - return (points + 8) / 100 -} - -// Meta Damage = 3% per Mastery Point -func (demo *DemonologyWarlock) getMetaMasteryBonus() float64 { - return demo.getMetaMasteryBonusFrom(demo.GetMasteryPoints()) -} - -func (demo *DemonologyWarlock) getMetaMasteryBonusFrom(points float64) float64 { - return (points + 8.0) * 3 / 100 -} - -func (demo *DemonologyWarlock) registerMasterDemonologist() { - corruptionMod := demo.AddDynamicMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: -1 + 1/demo.getMetaMasteryBonus(), - ClassMask: warlock.WarlockSpellCorruption, - }) - - corruptionModCaster := demo.AddDynamicMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: demo.getNormalMasteryBonus(), - ClassMask: warlock.WarlockSpellCorruption, - }) - - demo.Metamorphosis.RelatedSelfBuff.ApplyOnGain(func(aura *core.Aura, sim *core.Simulation) { - corruptionMod.UpdateFloatValue(-1 + 1/(1+demo.getMetaMasteryBonus())) - corruptionModCaster.UpdateFloatValue(demo.getNormalMasteryBonus()) - corruptionMod.Activate() - corruptionModCaster.Activate() - demo.PseudoStats.DamageDealtMultiplier /= 1 + demo.getNormalMasteryBonus() - demo.PseudoStats.DamageDealtMultiplier *= 1 + demo.getMetaMasteryBonus() - - }).ApplyOnExpire(func(aura *core.Aura, sim *core.Simulation) { - demo.PseudoStats.DamageDealtMultiplier /= 1 + demo.getMetaMasteryBonus() - demo.PseudoStats.DamageDealtMultiplier *= 1 + demo.getNormalMasteryBonus() - corruptionMod.Deactivate() - corruptionModCaster.Deactivate() - }) - - demo.AddOnMasteryStatChanged(func(sim *core.Simulation, oldMasteryRating, newMasteryRating float64) { - if demo.Metamorphosis.RelatedSelfBuff.IsActive() { - demo.PseudoStats.DamageDealtMultiplier /= 1 + demo.getMetaMasteryBonusFrom(core.MasteryRatingToMasteryPoints(oldMasteryRating)) - demo.PseudoStats.DamageDealtMultiplier *= 1 + demo.getMetaMasteryBonus() - corruptionMod.UpdateFloatValue(-1 + 1/(1+demo.getMetaMasteryBonus())) - corruptionModCaster.UpdateFloatValue(demo.getNormalMasteryBonus()) - } else { - demo.PseudoStats.DamageDealtMultiplier /= 1 + demo.getNormalMasteryBonusFrom(core.MasteryRatingToMasteryPoints(oldMasteryRating)) - demo.PseudoStats.DamageDealtMultiplier *= 1 + demo.getNormalMasteryBonus() - } - - for _, pet := range demo.Pets { - if pet.IsActive() { - pet.PseudoStats.DamageDealtMultiplier /= 1 + demo.getNormalMasteryBonusFrom(core.MasteryRatingToMasteryPoints(oldMasteryRating)) - pet.PseudoStats.DamageDealtMultiplier *= 1 + demo.getNormalMasteryBonus() - } - } - }) - - demo.PseudoStats.DamageDealtMultiplier *= 1 + demo.getNormalMasteryBonus() - - for _, pet := range demo.Pets { - oldEnable := pet.OnPetEnable - pet.OnPetEnable = func(sim *core.Simulation) { - if oldEnable != nil { - oldEnable(sim) - } - - pet.PseudoStats.DamageDealtMultiplier *= 1 + demo.getNormalMasteryBonus() - } - - oldDisable := pet.OnPetDisable - pet.OnPetDisable = func(sim *core.Simulation) { - if oldDisable != nil { - oldDisable(sim) - } - - pet.PseudoStats.DamageDealtMultiplier /= 1 + demo.getNormalMasteryBonus() - } - } -} diff --git a/sim/warlock/destruction/conflagrate.go b/sim/warlock/destruction/conflagrate.go deleted file mode 100644 index 186e677650..0000000000 --- a/sim/warlock/destruction/conflagrate.go +++ /dev/null @@ -1,60 +0,0 @@ -package destruction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -const conflagrateScale = 1.725 -const conflagrateVariance = 0.1 -const conflagrateCoeff = 1.725 - -func (destruction *DestructionWarlock) registerConflagrate() { - destruction.Conflagrate = destruction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 17962}, - SpellSchool: core.SpellSchoolFire, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellConflagrate, - - ManaCost: core.ManaCostOptions{BaseCostPercent: 1}, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - DamageMultiplier: 1.0, - CritMultiplier: destruction.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: conflagrateCoeff, - Charges: 2, - RechargeTime: time.Second * 12, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - if destruction.FABAura.IsActive() { - destruction.FABAura.Deactivate(sim) - } - - // keep charges in sync ONLY if not a Havoc Duplicate - if !spell.Flags.Matches(SpellFlagDestructionHavoc) { - destruction.FABConflagrate.ConsumeCharge(sim) - } - - baseDamage := destruction.CalcAndRollDamageRange(sim, conflagrateScale, conflagrateVariance) - result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - var emberGain int32 = 1 - - // ember lottery - if sim.Proc(0.15, "Ember Lottery") { - emberGain *= 2 - } - - if result.DidCrit() { - emberGain += 1 - } - - destruction.BurningEmbers.Gain(sim, float64(emberGain), spell.ActionID) - }, - }) -} diff --git a/sim/warlock/destruction/destruction.go b/sim/warlock/destruction/destruction.go deleted file mode 100644 index 522702ac32..0000000000 --- a/sim/warlock/destruction/destruction.go +++ /dev/null @@ -1,105 +0,0 @@ -package destruction - -import ( - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/core/proto" - "github.com/wowsims/tbc/sim/warlock" -) - -func RegisterDestructionWarlock() { - core.RegisterAgentFactory( - proto.Player_DestructionWarlock{}, - proto.Spec_SpecDestructionWarlock, - func(character *core.Character, options *proto.Player) core.Agent { - return NewDestructionWarlock(character, options) - }, - func(player *proto.Player, spec interface{}) { - playerSpec, ok := spec.(*proto.Player_DestructionWarlock) - if !ok { - panic("Invalid spec value for Destruction Warlock!") - } - player.Spec = playerSpec - }, - ) -} - -const SpellFlagDestructionHavoc = core.SpellFlagAgentReserved1 - -const DefaultBurningEmbers = 10 - -func NewDestructionWarlock(character *core.Character, options *proto.Player) *DestructionWarlock { - destroOptions := options.GetDestructionWarlock().Options - destruction := &DestructionWarlock{ - Warlock: warlock.NewWarlock(character, options, destroOptions.ClassOptions), - } - - destruction.BurningEmbers = destruction.RegisterNewDefaultSecondaryResourceBar(core.SecondaryResourceConfig{ - Type: proto.SecondaryResourceType_SecondaryResourceTypeBurningEmbers, - Max: 40, - Default: DefaultBurningEmbers, - }) - - return destruction -} - -type DestructionWarlock struct { - *warlock.Warlock - - Conflagrate *core.Spell - BurningEmbers core.SecondaryResourceBar - FABAura *core.Aura - FABImmolate *core.Spell - FABConflagrate *core.Spell - Havoc *core.Spell - HavocChargesAura *core.Aura - HavocAuras core.AuraArray -} - -func (destruction DestructionWarlock) getGeneratorMasteryBonus() float64 { - return 0.09 + 0.01*destruction.GetMasteryPoints() -} - -func (destruction DestructionWarlock) getSpenderMasteryBonus() float64 { - return 0.24 + 0.03*destruction.GetMasteryPoints() -} - -func (destruction *DestructionWarlock) GetWarlock() *warlock.Warlock { - return destruction.Warlock -} - -func (destruction *DestructionWarlock) Initialize() { - destruction.Warlock.Initialize() - - destruction.registerDarkSoulInstability() - destruction.ApplyChaoticEnergy() - destruction.ApplyMastery() - destruction.registerIncinerate() - destruction.registerConflagrate() - destruction.registerImmolate() - destruction.registerBackdraft() - destruction.registerFelflame() - destruction.registerChaosBolt() - destruction.registerShadowBurnSpell() - destruction.registerRainOfFire() - destruction.registerFireAndBrimstone() - destruction.registerHavoc() - destruction.RegisterDrainLife(nil) // no extra callback needed -} - -func (destruction *DestructionWarlock) ApplyTalents() { - destruction.Warlock.ApplyTalents() -} - -func (destruction *DestructionWarlock) Reset(sim *core.Simulation) { - destruction.Warlock.Reset(sim) -} - -func (destruction *DestructionWarlock) OnEncounterStart(sim *core.Simulation) { - destruction.BurningEmbers.ResetBarTo(sim, DefaultBurningEmbers) - destruction.Warlock.OnEncounterStart(sim) -} - -var SpellMaskCinderSpender = warlock.WarlockSpellChaosBolt | warlock.WarlockSpellEmberTap | warlock.WarlockSpellShadowBurn -var SpellMaskCinderGenerator = warlock.WarlockSpellImmolate | warlock.WarlockSpellImmolateDot | - warlock.WarlockSpellIncinerate | warlock.WarlockSpellFelFlame | warlock.WarlockSpellConflagrate | - warlock.WarlockSpellFaBIncinerate | warlock.WarlockSpellFaBConflagrate diff --git a/sim/warlock/destruction/destruction_test.go b/sim/warlock/destruction/destruction_test.go deleted file mode 100644 index d9dee78ab9..0000000000 --- a/sim/warlock/destruction/destruction_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package destruction - -import ( - "testing" - - "github.com/wowsims/tbc/sim/common" -) - -func init() { - RegisterDestructionWarlock() - common.RegisterAllEffects() -} - -func TestDestruction(t *testing.T) { -} diff --git a/sim/warlock/destruction/shadowburn.go b/sim/warlock/destruction/shadowburn.go deleted file mode 100644 index 0a88915f11..0000000000 --- a/sim/warlock/destruction/shadowburn.go +++ /dev/null @@ -1,57 +0,0 @@ -package destruction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -var shadowBurnScale = 3.5 -var shadowBurnVariance = 0.2 -var shadowBurnCoeff = 3.5 - -func (destruction *DestructionWarlock) registerShadowBurnSpell() { - manaMetric := destruction.NewManaMetrics(core.ActionID{SpellID: 17877}) - destruction.Shadowburn = destruction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 17877}, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellShadowBurn, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return sim.IsExecutePhase20() && destruction.BurningEmbers.CanSpend(core.TernaryFloat64(destruction.T15_2pc.IsActive(), 8, 10)) - }, - - DamageMultiplierAdditive: 1, - CritMultiplier: destruction.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: shadowBurnCoeff, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := destruction.CalcAndRollDamageRange(sim, shadowBurnScale, shadowBurnVariance) - result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - - if spell.Flags.Matches(SpellFlagDestructionHavoc) { - //Havoc Spell doesn't spend resources as it was a duplicate - } else if result.Landed() { - destruction.BurningEmbers.Spend(sim, core.TernaryFloat64(destruction.T15_2pc.IsActive(), 8, 10), spell.ActionID) - } - - pa := sim.GetConsumedPendingActionFromPool() - pa.NextActionAt = sim.CurrentTime + time.Second*5 - pa.Priority = core.ActionPriorityAuto - - pa.OnAction = func(sim *core.Simulation) { - destruction.AddMana(sim, destruction.MaxMana()*0.15, manaMetric) - } - - sim.AddPendingAction(pa) - }, - }) -} diff --git a/sim/warlock/doom.go b/sim/warlock/doom.go index 100a5fd0c5..4eba990b38 100644 --- a/sim/warlock/doom.go +++ b/sim/warlock/doom.go @@ -6,18 +6,22 @@ import ( "github.com/wowsims/tbc/sim/core" ) -const doomScale = 0.9375 -const doomCoeff = 0.9375 +const doomCoeff = 2 -func (warlock *Warlock) registerDoom() { +func (warlock *Warlock) registerCurseOfDoom() { warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 603}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellDoom, + ClassSpellMask: WarlockSpellCurseOfDoom, - Cast: core.CastConfig{DefaultCast: core.Cast{GCD: core.GCDDefault}}, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + CD: core.Cooldown{Duration: time.Minute * 1}, + }, DamageMultiplierAdditive: 1, CritMultiplier: warlock.DefaultCritMultiplier(), @@ -33,26 +37,13 @@ func (warlock *Warlock) registerDoom() { BonusCoefficient: doomCoeff, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, warlock.CalcScalingSpellDmg(doomScale)) + dot.Snapshot(target, warlock.CalcScalingSpellDmg(doomCoeff)) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) }, }, - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return warlock.IsInMeta() && warlock.CanSpendDemonicFury(60) - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHitNoHitCounter) - if result.Landed() { - warlock.SpendDemonicFury(sim, 60, spell.ActionID) - warlock.ApplyDotWithPandemic(spell.Dot(target), sim) - } - spell.DealOutcome(sim, result) - }, - ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, useSnapshot bool) *core.SpellResult { dot := spell.Dot(target) if useSnapshot { @@ -60,7 +51,7 @@ func (warlock *Warlock) registerDoom() { result.Damage /= dot.TickPeriod().Seconds() return result } else { - result := spell.CalcPeriodicDamage(sim, target, warlock.CalcScalingSpellDmg(doomScale), spell.OutcomeExpectedMagicCrit) + result := spell.CalcPeriodicDamage(sim, target, warlock.CalcScalingSpellDmg(doomCoeff), spell.OutcomeExpectedMagicCrit) result.Damage /= dot.CalcTickPeriod().Round(time.Millisecond).Seconds() return result } diff --git a/sim/warlock/drain_life.go b/sim/warlock/drain_life.go index 7f67813f8c..14a0543b2a 100644 --- a/sim/warlock/drain_life.go +++ b/sim/warlock/drain_life.go @@ -6,22 +6,20 @@ import ( "github.com/wowsims/tbc/sim/core" ) -const drainLifeScale = 0.334 -const drainLifeCoeff = 0.334 +const drainLifeCoeff = 0.143 func (warlock *Warlock) RegisterDrainLife(callback WarlockSpellCastedCallback) { - manaMetric := warlock.NewManaMetrics(core.ActionID{SpellID: 689}) - healthMetric := warlock.NewHealthMetrics(core.ActionID{SpellID: 689}) + healthMetric := warlock.NewHealthMetrics(core.ActionID{SpellID: 27220}) resultSlice := make(core.SpellResultSlice, 1) warlock.DrainLife = warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 689}, + ActionID: core.ActionID{SpellID: 27220}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagChanneled | core.SpellFlagAPL, ClassSpellMask: WarlockSpellDrainLife, - ManaCost: core.ManaCostOptions{BaseCostPercent: 1}, + ManaCost: core.ManaCostOptions{FlatCost: 425}, Cast: core.CastConfig{DefaultCast: core.Cast{GCD: core.GCDDefault}}, DamageMultiplierAdditive: 1, @@ -30,20 +28,16 @@ func (warlock *Warlock) RegisterDrainLife(callback WarlockSpellCastedCallback) { BonusCoefficient: drainLifeCoeff, Dot: core.DotConfig{ - Aura: core.Aura{Label: "Drain Life"}, - NumberOfTicks: 6, - TickLength: 1 * time.Second, - AffectedByCastSpeed: true, - HasteReducesDuration: true, - BonusCoefficient: drainLifeCoeff, + Aura: core.Aura{Label: "Drain Life"}, + NumberOfTicks: 5, + TickLength: 1 * time.Second, + BonusCoefficient: drainLifeCoeff, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, _ bool) { - dot.Snapshot(target, warlock.CalcScalingSpellDmg(drainLifeScale)) + dot.Snapshot(target, warlock.CalcScalingSpellDmg(drainLifeCoeff)) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { resultSlice[0] = dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) - // Spend mana per tick - warlock.SpendMana(sim, dot.Spell.Cost.GetCurrentCost(), manaMetric) warlock.GainHealth(sim, warlock.MaxHealth()*0.02, healthMetric) if callback != nil { diff --git a/sim/warlock/drain_soul.go b/sim/warlock/drain_soul.go deleted file mode 100644 index 4ec4bc5e88..0000000000 --- a/sim/warlock/drain_soul.go +++ /dev/null @@ -1,96 +0,0 @@ -package warlock - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" -) - -const drainSoulScale = 0.257 -const drainSoulCoeff = 0.257 - -func (warlock *Warlock) registerDrainSoul() { - warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 1120}, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL | core.SpellFlagChanneled, - ClassSpellMask: warlock.WarlockSpellDrainSoul, - - ManaCost: core.ManaCostOptions{BaseCostPercent: 1.5}, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - DamageMultiplierAdditive: 1, - CritMultiplier: warlock.DefaultCritMultiplier(), - ThreatMultiplier: 1, - - Dot: core.DotConfig{ - Aura: core.Aura{Label: "DrainSoul"}, - NumberOfTicks: 6, - TickLength: 2 * time.Second, - AffectedByCastSpeed: true, - HasteReducesDuration: true, - BonusCoefficient: drainSoulCoeff, - - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, _ bool) { - dot.Snapshot(target, warlock.CalcScalingSpellDmg(drainSoulScale)) - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - result := dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) - - // Every 2nd tick grants 1 soul shard - if dot.TickCount()%2 == 0 { - warlock.SoulShards.Gain(sim, 1, dot.Spell.ActionID) - } - - if !result.Landed() || !sim.IsExecutePhase20() { - return - } - - warlock.ProcMaleficEffect(target, warlock.DrainSoulMaleficEffectMultiplier, sim) - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHitNoHitCounter) - if result.Landed() { - spell.Dot(target).Apply(sim) - } - spell.DealOutcome(sim, result) - }, - - ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, useSnapshot bool) *core.SpellResult { - dot := spell.Dot(target) - if useSnapshot { - result := dot.CalcSnapshotDamage(sim, target, dot.OutcomeExpectedSnapshotCrit) - result.Damage /= dot.TickPeriod().Seconds() - return result - } else { - result := spell.CalcPeriodicDamage(sim, target, warlock.CalcScalingSpellDmg(drainSoulScale), spell.OutcomeExpectedMagicCrit) - result.Damage /= dot.CalcTickPeriod().Round(time.Millisecond).Seconds() - return result - } - }, - }) - - dmgMode := warlock.AddDynamicMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 1, - ClassMask: warlock.WarlockSpellDrainSoul, - }) - - warlock.RegisterResetEffect(func(s *core.Simulation) { - dmgMode.Deactivate() - s.RegisterExecutePhaseCallback(func(sim *core.Simulation, isExecute int32) { - if isExecute > 20 { - return - } - - dmgMode.Activate() - }) - }) -} diff --git a/sim/warlock/demonology/fel_guard.go b/sim/warlock/fel_guard.go similarity index 81% rename from sim/warlock/demonology/fel_guard.go rename to sim/warlock/fel_guard.go index 4502adc55d..3e2e0c0895 100644 --- a/sim/warlock/demonology/fel_guard.go +++ b/sim/warlock/fel_guard.go @@ -1,4 +1,4 @@ -package demonology +package warlock import ( "math" @@ -10,20 +10,20 @@ import ( "github.com/wowsims/tbc/sim/warlock" ) -func (demo *DemonologyWarlock) registerFelguard() *warlock.WarlockPet { +func (warlock *Warlock) registerFelguard() *warlock.WarlockPet { name := proto.WarlockOptions_Summon_name[int32(proto.WarlockOptions_Felguard)] - enabledOnStart := proto.WarlockOptions_Felguard == demo.Options.Summon - return demo.registerFelguardWithName(name, enabledOnStart, false, false) + enabledOnStart := proto.WarlockOptions_Felguard == warlock.Options.Summon + return warlock.registerFelguardWithName(name, enabledOnStart, false, false) } -func (demo *DemonologyWarlock) registerFelguardWithName(name string, enabledOnStart bool, autoCastFelstorm bool, isGuardian bool) *warlock.WarlockPet { - pet := demo.RegisterPet(proto.WarlockOptions_Felguard, 2, 3.5, name, enabledOnStart, isGuardian) - felStorm := registerFelstorm(pet, demo, autoCastFelstorm) - legionStrike := registerLegionStrikeSpell(pet, demo) +func (warlock *Warlock) registerFelguardWithName(name string, enabledOnStart bool, autoCastFelstorm bool, isGuardian bool) *warlock.WarlockPet { + pet := warlock.RegisterPet(proto.WarlockOptions_Felguard, 2, 3.5, name, enabledOnStart, isGuardian) + felStorm := registerFelstorm(pet, warlock, autoCastFelstorm) + legionStrike := registerLegionStrikeSpell(pet, warlock) pet.MinEnergy = 120 if !isGuardian { - demo.RegisterSpell(core.SpellConfig{ + warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 89751}, SpellSchool: core.SpellSchoolPhysical, ProcMask: core.ProcMaskEmpty, @@ -31,7 +31,7 @@ func (demo *DemonologyWarlock) registerFelguardWithName(name string, enabledOnSt Cast: core.CastConfig{ CD: core.Cooldown{ - Timer: demo.NewTimer(), + Timer: warlock.NewTimer(), Duration: time.Second * 45, }, }, @@ -66,7 +66,7 @@ func (demo *DemonologyWarlock) registerFelguardWithName(name string, enabledOnSt var legionStrikePetAction = core.ActionID{SpellID: 30213} -func registerLegionStrikeSpell(pet *warlock.WarlockPet, demo *DemonologyWarlock) *core.Spell { +func registerLegionStrikeSpell(pet *warlock.WarlockPet, warlock *Warlock) *core.Spell { legionStrike := pet.RegisterSpell(core.SpellConfig{ ActionID: legionStrikePetAction, SpellSchool: core.SpellSchoolPhysical, @@ -98,7 +98,7 @@ func registerLegionStrikeSpell(pet *warlock.WarlockPet, demo *DemonologyWarlock) baseDmg /= float64(sim.Environment.ActiveTargetCount()) spell.CalcAndDealAoeDamage(sim, baseDmg, spell.OutcomeMeleeWeaponSpecialHitAndCrit) // Pets are not affected by Fury gain modifiers - demo.DemonicFury.Gain(sim, 12, core.ActionID{SpellID: 30213}) + warlock.warlocknicFury.Gain(sim, 12, core.ActionID{SpellID: 30213}) }, }) @@ -107,7 +107,7 @@ func registerLegionStrikeSpell(pet *warlock.WarlockPet, demo *DemonologyWarlock) return legionStrike } -func registerFelstorm(pet *warlock.WarlockPet, _ *DemonologyWarlock, autoCast bool) *core.Spell { +func registerFelstorm(pet *warlock.WarlockPet, _ *Warlock, autoCast bool) *core.Spell { felStorm := pet.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 89751}, SpellSchool: core.SpellSchoolPhysical, diff --git a/sim/warlock/hellfire.go b/sim/warlock/hellfire.go deleted file mode 100644 index e39e5c49ea..0000000000 --- a/sim/warlock/hellfire.go +++ /dev/null @@ -1,61 +0,0 @@ -package warlock - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" -) - -const hellFireScale = 0.20999999344 -const hellFireCoeff = 0.20999999344 - -func (warlock *Warlock) RegisterHellfire(callback WarlockSpellCastedCallback) *core.Spell { - baseDamage := warlock.CalcScalingSpellDmg(hellFireScale) - - hellfireActionID := core.ActionID{SpellID: 1949} - manaMetric := warlock.NewManaMetrics(hellfireActionID) - warlock.Hellfire = warlock.RegisterSpell(core.SpellConfig{ - ActionID: hellfireActionID, - SpellSchool: core.SpellSchoolFire, - Flags: core.SpellFlagAoE | core.SpellFlagChanneled | core.SpellFlagAPL, - ProcMask: core.ProcMaskSpellDamage, - ClassSpellMask: WarlockSpellHellfire, - ThreatMultiplier: 1, - DamageMultiplier: 1, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - ManaCost: core.ManaCostOptions{BaseCostPercent: 2}, - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Hellfire", - }, - - IsAOE: true, - TickLength: time.Second, - NumberOfTicks: 14, - HasteReducesDuration: true, - AffectedByCastSpeed: true, - BonusCoefficient: hellFireCoeff, - - OnTick: func(sim *core.Simulation, _ *core.Unit, dot *core.Dot) { - results := dot.Spell.CalcAndDealPeriodicAoeDamage(sim, baseDamage, dot.Spell.OutcomeMagicHit) - warlock.SpendMana(sim, warlock.MaxMana()*0.02, manaMetric) - - if callback != nil { - callback(results, dot.Spell, sim) - } - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - spell.AOEDot().Apply(sim) - }, - }) - - return warlock.Hellfire -} diff --git a/sim/warlock/immolate.go b/sim/warlock/immolate.go index 9ac7dd56e7..12eb2c1406 100644 --- a/sim/warlock/immolate.go +++ b/sim/warlock/immolate.go @@ -1,4 +1,4 @@ -package destruction +package warlock import ( "time" @@ -7,8 +7,7 @@ import ( "github.com/wowsims/tbc/sim/warlock" ) -const immolateScale = 0.47 * 1.3 // Hotfix -const immolateCoeff = 0.47 * 1.3 +const immolateCoeff = 0.13 // Damage Done By Caster setup const ( @@ -16,14 +15,14 @@ const ( DDBC_Total ) -func (destruction *DestructionWarlock) registerImmolate() { - actionID := core.ActionID{SpellID: 348} - destruction.Immolate = destruction.RegisterSpell(core.SpellConfig{ +func (warlock *Warlock) registerImmolate() { + actionID := core.ActionID{SpellID: 27215} + warlock.Immolate = warlock.RegisterSpell(core.SpellConfig{ ActionID: actionID, SpellSchool: core.SpellSchoolFire, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellImmolate, + ClassSpellMask: WarlockSpellImmolate, ManaCost: core.ManaCostOptions{BaseCostPercent: 3}, Cast: core.CastConfig{ @@ -34,69 +33,46 @@ func (destruction *DestructionWarlock) registerImmolate() { }, DamageMultiplier: 1, - CritMultiplier: destruction.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultCritMultiplier(), ThreatMultiplier: 1, BonusCoefficient: immolateCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - if destruction.FABAura.IsActive() { - destruction.FABAura.Deactivate(sim) - } - - result := spell.CalcDamage(sim, target, destruction.CalcScalingSpellDmg(immolateScale), spell.OutcomeMagicHitAndCrit) + result := spell.CalcDamage(sim, target, warlock.CalcScalingSpellDmg(immolateCoeff), spell.OutcomeMagicHitAndCrit) if result.Landed() { spell.RelatedDotSpell.Cast(sim, target) } - if result.DidCrit() { - destruction.BurningEmbers.Gain(sim, 1, spell.ActionID) - } - spell.DealDamage(sim, result) }, }) - destruction.Immolate.RelatedDotSpell = destruction.RegisterSpell(core.SpellConfig{ + warlock.Immolate.RelatedDotSpell = warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 348}.WithTag(1), SpellSchool: core.SpellSchoolFire, ProcMask: core.ProcMaskSpellDamage, - ClassSpellMask: warlock.WarlockSpellImmolateDot, + ClassSpellMask: WarlockSpellImmolateDot, Flags: core.SpellFlagPassiveSpell, DamageMultiplier: 1, - CritMultiplier: destruction.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultCritMultiplier(), Dot: core.DotConfig{ Aura: core.Aura{ Label: "Immolate (DoT)", OnGain: func(aura *core.Aura, sim *core.Simulation) { - core.EnableDamageDoneByCaster(DDBC_Immolate, DDBC_Total, destruction.AttackTables[aura.Unit.UnitIndex], immolateDamageDoneByCasterHandler) + core.EnableDamageDoneByCaster(DDBC_Immolate, DDBC_Total, warlock.AttackTables[aura.Unit.UnitIndex], immolateDamageDoneByCasterHandler) }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { - core.DisableDamageDoneByCaster(DDBC_Immolate, destruction.AttackTables[aura.Unit.UnitIndex]) + core.DisableDamageDoneByCaster(DDBC_Immolate, warlock.AttackTables[aura.Unit.UnitIndex]) }, }, - NumberOfTicks: 5, - TickLength: 3 * time.Second, - AffectedByCastSpeed: true, - BonusCoefficient: immolateCoeff, + NumberOfTicks: 5, + TickLength: 3 * time.Second, + BonusCoefficient: immolateCoeff, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, destruction.CalcScalingSpellDmg(immolateScale)) + dot.Snapshot(target, warlock.CalcScalingSpellDmg(immolateCoeff)) }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - result := dot.CalcSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) - if result.DidCrit() { - destruction.BurningEmbers.Gain(sim, 1, dot.Spell.ActionID) - } - if destruction.SiphonLife != nil { - destruction.SiphonLife.Cast(sim, &destruction.Unit) - } - dot.Spell.DealPeriodicDamage(sim, result) - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - destruction.ApplyDotWithPandemic(spell.Dot(target), sim) }, ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, useSnapshot bool) *core.SpellResult { @@ -106,7 +82,7 @@ func (destruction *DestructionWarlock) registerImmolate() { result.Damage /= dot.TickPeriod().Seconds() return result } else { - result := spell.CalcPeriodicDamage(sim, target, destruction.CalcScalingSpellDmg(immolateCoeff), spell.OutcomeExpectedMagicCrit) + result := spell.CalcPeriodicDamage(sim, target, warlock.CalcScalingSpellDmg(immolateCoeff), spell.OutcomeExpectedMagicCrit) result.Damage /= dot.CalcTickPeriod().Round(time.Millisecond).Seconds() return result } diff --git a/sim/warlock/incinerate.go b/sim/warlock/incinerate.go index 345fefb382..ece5234396 100644 --- a/sim/warlock/incinerate.go +++ b/sim/warlock/incinerate.go @@ -7,23 +7,22 @@ import ( ) const incinerateVariance = 0.1 -const incinerateScale = 1.54 * 1.15 // Hotfix const incinerateCoeff = 1.54 * 1.15 func (warlock *Warlock) registerIncinerate() { warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 29722}, + ActionID: core.ActionID{SpellID: 32231}, SpellSchool: core.SpellSchoolFire, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagAPL, MissileSpeed: 24, - ClassSpellMask: warlock.WarlockSpellIncinerate, + ClassSpellMask: WarlockSpellIncinerate, ManaCost: core.ManaCostOptions{BaseCostPercent: 5}, Cast: core.CastConfig{ DefaultCast: core.Cast{ GCD: core.GCDDefault, - CastTime: 2000 * time.Millisecond, + CastTime: 2500 * time.Millisecond, }, }, @@ -33,27 +32,9 @@ func (warlock *Warlock) registerIncinerate() { BonusCoefficient: incinerateCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - if warlock.FABAura.IsActive() { - warlock.FABAura.Deactivate(sim) - } - - baseDamage := warlock.CalcAndRollDamageRange(sim, incinerateScale, incinerateVariance) + baseDamage := warlock.CalcAndRollDamageRange(sim, incinerateCoeff, incinerateVariance) result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - var emberGain int32 = 1 - if warlock.T15_4pc.IsActive() && sim.Proc(0.08, "T15 4p") { - emberGain += 1 - } - - // ember lottery - if sim.Proc(0.15, "Ember Lottery") { - emberGain *= 2 - } - - if result.DidCrit() { - emberGain += 1 - } - warlock.BurningEmbers.Gain(sim, float64(emberGain), spell.ActionID) spell.WaitTravelTime(sim, func(sim *core.Simulation) { spell.DealDamage(sim, result) }) diff --git a/sim/warlock/lifetap.go b/sim/warlock/lifetap.go index aee112c7a5..8fdb4b0c4d 100644 --- a/sim/warlock/lifetap.go +++ b/sim/warlock/lifetap.go @@ -2,12 +2,18 @@ package warlock import ( "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/core/stats" ) func (warlock *Warlock) registerLifeTap() { - actionID := core.ActionID{SpellID: 1454} + actionID := core.ActionID{SpellID: 27222} manaMetrics := warlock.NewManaMetrics(actionID) + baseRestore := 582.0 * (1.0 + 0.1*float64(warlock.Talents.ImprovedLifeTap)) + + petRestore := 0.3333 * float64(warlock.Talents.ManaFeed) + var petManaMetrics []*core.ResourceMetrics + if warlock.Talents.ManaFeed > 0 { + petManaMetrics = append(petManaMetrics, warlock.ActivePet.NewManaMetrics(actionID)) + } warlock.RegisterSpell(core.SpellConfig{ ActionID: actionID, @@ -25,8 +31,14 @@ func (warlock *Warlock) registerLifeTap() { ThreatMultiplier: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - restore := 0.15 * warlock.GetStat(stats.Health) + // Life tap adds 0.8*sp to mana restore + restore := baseRestore + warlock.RemoveHealth(sim, 582) warlock.AddMana(sim, restore, manaMetrics) + + if warlock.Talents.ManaFeed > 0 { + warlock.ActivePet.AddMana(sim, restore*petRestore, petManaMetrics[0]) + } }, }) } diff --git a/sim/warlock/nightfall.go b/sim/warlock/nightfall.go deleted file mode 100644 index 8e683644ff..0000000000 --- a/sim/warlock/nightfall.go +++ /dev/null @@ -1,31 +0,0 @@ -package affliction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -func (affliction *AfflictionWarlock) registerNightfall() { - buff := affliction.RegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 17941}, - Label: "Shadow Trance", - Duration: time.Second * 6, - }) - - affliction.MakeProcTriggerAura(core.ProcTrigger{ - Name: "Nightfall", - ClassSpellMask: warlock.WarlockSpellCorruption, - Callback: core.CallbackOnPeriodicDamageDealt, - Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - dot := spell.Dot(result.Target) - if dot == nil || result.Target != affliction.LastCorruptionTarget || !sim.Proc(0.1, "Nightfall Proc") { - return - } - - affliction.SoulShards.Gain(sim, 1, buff.ActionID) - buff.Activate(sim) - }, - }) -} diff --git a/sim/warlock/rain_of_fire.go b/sim/warlock/rain_of_fire.go deleted file mode 100644 index 282ab6157b..0000000000 --- a/sim/warlock/rain_of_fire.go +++ /dev/null @@ -1,68 +0,0 @@ -package destruction - -import ( - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" -) - -// Rain of Fire does not need to be channeled anymore for destruction warlocks -// RoF applies a hidden dot to the target which exhibits all the usual dot mechanics -// This also causes rof to not stack on the same target -// -// Measured proc rate for rof on 2 targets is 70 procs on 550 ticks ~12.5% = 1/8th - -var rofScale = 0.15 -var rofCoeff = 0.15 - -func (destruction DestructionWarlock) registerRainOfFire() { - baseDamage := destruction.CalcScalingSpellDmg(rofScale) - destruction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 104232}, - SpellSchool: core.SpellSchoolFire, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAoE | core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellRainOfFire, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - ManaCost: core.ManaCostOptions{ - BaseCostPercent: 6.25, - }, - - DamageMultiplierAdditive: 1, - CritMultiplier: destruction.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: rofCoeff, - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Rain of Fire (DoT)", - ActionID: core.ActionID{SpellID: 104232}.WithTag(1), - }, - - TickLength: time.Second, - NumberOfTicks: 8, - HasteReducesDuration: true, - AffectedByCastSpeed: true, - IsAOE: true, - BonusCoefficient: rofCoeff, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { - result := dot.Spell.CalcAndDealPeriodicDamage(sim, aoeTarget, baseDamage, dot.OutcomeTickMagicCrit) - if result.Landed() && sim.Proc(0.125, "RoF - Ember Proc") { - destruction.BurningEmbers.Gain(sim, 2, dot.ActionID) - } - } - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - spell.AOEDot().Apply(sim) - }, - }) -} diff --git a/sim/warlock/seed_of_corruption.go b/sim/warlock/seed_of_corruption.go index a030076147..c4a60b80a5 100644 --- a/sim/warlock/seed_of_corruption.go +++ b/sim/warlock/seed_of_corruption.go @@ -1,10 +1,9 @@ -package affliction +package warlock import ( "time" "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" ) const seedTickScale = 0.21 @@ -13,33 +12,33 @@ const seedExploScale = 0.91 const seedExploCoeff = 0.91 const seedExploVariance = 0.15 -func (affliction *AfflictionWarlock) registerSeed() { +func (warlock *Warlock) registerSeed() { actionID := core.ActionID{SpellID: 27243} type seedOptions struct { damageTaken float64 isSoulBurn bool } - seedPropertyTracker := make([]seedOptions, len(affliction.Env.AllUnits)) + seedPropertyTracker := make([]seedOptions, len(warlock.Env.AllUnits)) - seedExplosion := affliction.RegisterSpell(core.SpellConfig{ + seedExplosion := warlock.RegisterSpell(core.SpellConfig{ ActionID: actionID.WithTag(1), // actually 27285 SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagAoE | core.SpellFlagPassiveSpell, - ClassSpellMask: warlock.WarlockSpellSeedOfCorruptionExposion, + ClassSpellMask: WarlockSpellSeedOfCorruptionExposion, DamageMultiplierAdditive: 1, - CritMultiplier: affliction.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultCritMultiplier(), ThreatMultiplier: 1, BonusCoefficient: seedExploCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDmg := affliction.CalcAndRollDamageRange(sim, seedExploScale, seedExploVariance) + baseDmg := warlock.CalcAndRollDamageRange(sim, seedExploScale, seedExploVariance) isSoulBurn := seedPropertyTracker[target.UnitIndex].isSoulBurn for _, aoeTarget := range sim.Encounter.ActiveTargetUnits { result := spell.CalcAndDealDamage(sim, aoeTarget, baseDmg, spell.OutcomeMagicHitAndCrit) if isSoulBurn && result.Landed() { - affliction.Corruption.Proc(sim, aoeTarget) + warlock.Corruption.Proc(sim, aoeTarget) } } }, @@ -48,18 +47,18 @@ func (affliction *AfflictionWarlock) registerSeed() { trySeedPop := func(sim *core.Simulation, target *core.Unit, dmg float64, seed *core.Dot) { seedPropertyTracker[target.UnitIndex].damageTaken += dmg if seedPropertyTracker[target.UnitIndex].damageTaken >= float64(seed.HastedTickCount())*seed.SnapshotBaseDamage*seed.SnapshotAttackerMultiplier { - affliction.Seed.Dot(target).Deactivate(sim) + warlock.Seed.Dot(target).Deactivate(sim) seedExplosion.Cast(sim, target) } } - affliction.Seed = affliction.RegisterSpell(core.SpellConfig{ + warlock.Seed = warlock.RegisterSpell(core.SpellConfig{ ActionID: actionID, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagAPL, MissileSpeed: 28, - ClassSpellMask: warlock.WarlockSpellSeedOfCorruption, + ClassSpellMask: WarlockSpellSeedOfCorruption, ManaCost: core.ManaCostOptions{BaseCostPercent: 6}, Cast: core.CastConfig{ @@ -69,7 +68,7 @@ func (affliction *AfflictionWarlock) registerSeed() { }, }, - CritMultiplier: affliction.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultCritMultiplier(), DamageMultiplierAdditive: 1, ThreatMultiplier: 1, @@ -81,16 +80,16 @@ func (affliction *AfflictionWarlock) registerSeed() { return } - trySeedPop(sim, result.Target, result.Damage, affliction.Seed.Dot(result.Target)) + trySeedPop(sim, result.Target, result.Damage, warlock.Seed.Dot(result.Target)) }, OnPeriodicDamageTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - trySeedPop(sim, result.Target, result.Damage, affliction.Seed.Dot(result.Target)) + trySeedPop(sim, result.Target, result.Damage, warlock.Seed.Dot(result.Target)) }, OnGain: func(aura *core.Aura, sim *core.Simulation) { seedPropertyTracker[aura.Unit.UnitIndex].damageTaken = 0 - if affliction.SoulBurnAura.IsActive() { + if warlock.SoulBurnAura.IsActive() { seedPropertyTracker[aura.Unit.UnitIndex].isSoulBurn = true - affliction.SoulBurnAura.Deactivate(sim) + warlock.SoulBurnAura.Deactivate(sim) } }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { @@ -103,7 +102,7 @@ func (affliction *AfflictionWarlock) registerSeed() { BonusCoefficient: seedTickCoeff, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, affliction.CalcScalingSpellDmg(seedTickScale)) + dot.Snapshot(target, warlock.CalcScalingSpellDmg(seedTickScale)) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { result := dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) @@ -115,7 +114,7 @@ func (affliction *AfflictionWarlock) registerSeed() { result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHit) spell.WaitTravelTime(sim, func(sim *core.Simulation) { if result.Landed() { - if affliction.Options.DetonateSeed { + if warlock.Options.DetonateSeed { seedExplosion.Cast(sim, target) } else { spell.Dot(target).Apply(sim) diff --git a/sim/warlock/shadowbolt.go b/sim/warlock/shadowbolt.go index 21391e9b67..f04f2cc95f 100644 --- a/sim/warlock/shadowbolt.go +++ b/sim/warlock/shadowbolt.go @@ -1,22 +1,21 @@ -package demonology +package warlock import ( "time" "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" ) const shadowBoltScale = 1.38 const shadowBoltCoeff = 1.38 -func (demonology *DemonologyWarlock) registerShadowBolt() { - demonology.RegisterSpell(core.SpellConfig{ +func (warlock *Warlock) registerShadowBolt() { + warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 686}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellShadowBolt, + ClassSpellMask: WarlockSpellShadowBolt, MissileSpeed: 20, ManaCost: core.ManaCostOptions{BaseCostPercent: 5.5}, @@ -28,23 +27,14 @@ func (demonology *DemonologyWarlock) registerShadowBolt() { }, DamageMultiplierAdditive: 1, - CritMultiplier: demonology.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultCritMultiplier(), ThreatMultiplier: 1, - BonusCoefficient: shadowBoltCoeff, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return !demonology.IsInMeta() - }, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcDamage(sim, target, demonology.CalcScalingSpellDmg(shadowBoltScale), spell.OutcomeMagicHitAndCrit) + result := spell.CalcDamage(sim, target, warlock.CalcScalingSpellDmg(shadowBoltScale), spell.OutcomeMagicHitAndCrit) spell.WaitTravelTime(sim, func(sim *core.Simulation) { spell.DealDamage(sim, result) }) - - if result.Landed() { - demonology.GainDemonicFury(sim, 25, core.ActionID{SpellID: 686}) - } }, }) } diff --git a/sim/warlock/shadowburn.go b/sim/warlock/shadowburn.go new file mode 100644 index 0000000000..9161dec579 --- /dev/null +++ b/sim/warlock/shadowburn.go @@ -0,0 +1,34 @@ +package warlock + +import ( + "time" + + "github.com/wowsims/tbc/sim/core" +) + +var shadowBurnCoeff = 0.429 + +func (warlock *Warlock) registerShadowBurnSpell() { + + warlock.Shadowburn = warlock.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: 30546}, + SpellSchool: core.SpellSchoolShadow, + ProcMask: core.ProcMaskSpellDamage, + Flags: core.SpellFlagAPL, + ClassSpellMask: WarlockSpellShadowBurn, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + CD: core.Cooldown{ + Duration: time.Second * 15, + }, + }, + + DamageMultiplier: 1, + CritMultiplier: warlock.DefaultCritMultiplier(), + ThreatMultiplier: 1, + BonusCoefficient: shadowBurnCoeff, + }) +} diff --git a/sim/warlock/soulfire.go b/sim/warlock/soulfire.go index 814c8ffb20..2e6f25feed 100644 --- a/sim/warlock/soulfire.go +++ b/sim/warlock/soulfire.go @@ -1,91 +1,39 @@ -package demonology +package warlock import ( "time" "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/core/stats" - "github.com/wowsims/tbc/sim/warlock" ) -const soulfireScale = 0.854 -const soulfireCoeff = 0.854 +const soulfireCoeff = 1.15 const soulfireVariance = 0.2 -func (demonology *DemonologyWarlock) registerSoulfire() { - getSoulFireConfig := func(config *core.SpellConfig, extraApplyEffect core.ApplySpellResults) core.SpellConfig { - return core.SpellConfig{ - ActionID: config.ActionID, - SpellSchool: core.SpellSchoolFire, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellSoulFire, - MissileSpeed: 24, - - ManaCost: config.ManaCost, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - CastTime: 4 * time.Second, - }, +func (warlock *Warlock) registerSoulfire() { + warlock.Soulfire = warlock.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: 30545}, + SpellSchool: core.SpellSchoolFire, + ProcMask: core.ProcMaskSpellDamage, + Flags: core.SpellFlagAPL, + ClassSpellMask: WarlockSpellSoulFire, + MissileSpeed: 21, + + ManaCost: core.ManaCostOptions{FlatCost: 250}, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + CastTime: time.Millisecond*6000 - (time.Millisecond * 400 * time.Duration(warlock.Talents.Bane)), }, - - DamageMultiplierAdditive: 1, - CritMultiplier: demonology.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: soulfireCoeff, - BonusCritPercent: 100, - - ExtraCastCondition: config.ExtraCastCondition, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := demonology.CalcAndRollDamageRange(sim, soulfireScale, soulfireVariance) - - // Damage is increased by crit chance - spell.DamageMultiplier *= (1 + demonology.GetStat(stats.SpellCritPercent)/100) - result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - spell.DamageMultiplier /= (1 + demonology.GetStat(stats.SpellCritPercent)/100) - - if extraApplyEffect != nil { - extraApplyEffect(sim, target, spell) - } - - spell.WaitTravelTime(sim, func(sim *core.Simulation) { - spell.DealDamage(sim, result) - }) + CD: core.Cooldown{ + Duration: 1 * time.Minute, }, - } - } - - getSoulFireCost := func() float64 { - baseCost := 160.0 - if demonology.MoltenCore.IsActive() { - baseCost /= 2 - } - return baseCost - } - - demonology.RegisterSpell(getSoulFireConfig(&core.SpellConfig{ - ActionID: core.ActionID{SpellID: 6353}, - ManaCost: core.ManaCostOptions{ - BaseCostPercent: 15, - PercentModifier: 1, - }, - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return !demonology.IsInMeta() }, - }, func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - demonology.GainDemonicFury(sim, 30, spell.ActionID) - })) - demonology.RegisterSpell(getSoulFireConfig(&core.SpellConfig{ - ActionID: core.ActionID{SpellID: 104027}, - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return demonology.IsInMeta() && demonology.CanSpendDemonicFury(getSoulFireCost()) - }, - }, func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - demonology.SpendDemonicFury(sim, getSoulFireCost(), spell.ActionID) - })) + DamageMultiplier: 1, + CritMultiplier: warlock.DefaultCritMultiplier(), + ThreatMultiplier: 1, + BonusCoefficient: soulfireCoeff, + }) } diff --git a/sim/warlock/affliction/unstable_affliction.go b/sim/warlock/unstable_affliction.go similarity index 60% rename from sim/warlock/affliction/unstable_affliction.go rename to sim/warlock/unstable_affliction.go index a41ce6f2ae..03581c2fac 100644 --- a/sim/warlock/affliction/unstable_affliction.go +++ b/sim/warlock/unstable_affliction.go @@ -1,22 +1,20 @@ -package affliction +package warlock import ( "time" "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" ) -const uaCoeff = 0.29 -const uaScale = 0.29 +const uaCoeff = 0.2 -func (affliction *AfflictionWarlock) registerUnstableAffliction() { - affliction.UnstableAffliction = affliction.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 30108}, +func (warlock *Warlock) registerUnstableAffliction() { + warlock.UnstableAffliction = warlock.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: 30405}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagAPL, - ClassSpellMask: warlock.WarlockSpellUnstableAffliction, + ClassSpellMask: WarlockSpellUnstableAffliction, ManaCost: core.ManaCostOptions{BaseCostPercent: 1.5}, Cast: core.CastConfig{ @@ -27,7 +25,7 @@ func (affliction *AfflictionWarlock) registerUnstableAffliction() { }, DamageMultiplierAdditive: 1, - CritMultiplier: affliction.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultCritMultiplier(), ThreatMultiplier: 1, Dot: core.DotConfig{ @@ -38,21 +36,13 @@ func (affliction *AfflictionWarlock) registerUnstableAffliction() { BonusCoefficient: uaCoeff, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, _ bool) { - dot.Snapshot(target, affliction.CalcScalingSpellDmg(uaScale)) + dot.Snapshot(target, warlock.CalcScalingSpellDmg(uaCoeff)) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) }, }, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHitNoHitCounter) - if result.Landed() { - affliction.ApplyDotWithPandemic(spell.Dot(target), sim) - } - spell.DealOutcome(sim, result) - }, - ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, useSnapshot bool) *core.SpellResult { dot := spell.Dot(target) if useSnapshot { @@ -60,7 +50,7 @@ func (affliction *AfflictionWarlock) registerUnstableAffliction() { result.Damage /= dot.TickPeriod().Seconds() return result } else { - result := spell.CalcPeriodicDamage(sim, target, affliction.CalcScalingSpellDmg(uaScale), spell.OutcomeExpectedMagicCrit) + result := spell.CalcPeriodicDamage(sim, target, warlock.CalcScalingSpellDmg(uaCoeff), spell.OutcomeExpectedMagicCrit) result.Damage /= dot.CalcTickPeriod().Round(time.Millisecond).Seconds() return result } diff --git a/sim/warlock/warlock.go b/sim/warlock/warlock.go index deb2382133..ea82d87e33 100644 --- a/sim/warlock/warlock.go +++ b/sim/warlock/warlock.go @@ -1,10 +1,6 @@ package warlock import ( - "fmt" - "math" - "time" - "github.com/wowsims/tbc/sim/core" "github.com/wowsims/tbc/sim/core/proto" "github.com/wowsims/tbc/sim/core/stats" @@ -15,20 +11,43 @@ type Warlock struct { Talents *proto.WarlockTalents Options *proto.WarlockOptions - Corruption *core.Spell + // Base Spells + Corruption *core.Spell + DrainLife *core.Spell + Hellfire *core.Spell + Immolate *core.Spell + Incinerate *core.Spell + SearingPain *core.Spell + Seeds []*core.Spell + ShadowBolt *core.Spell + Soulfire *core.Spell + + LifeTap *core.Spell + + // Curses + CurseOfAgony *core.Spell + CurseOfDoom *core.Spell + CurseOfElements *core.Spell CurseOfElementsAuras core.AuraArray - Immolate *core.Spell - Metamorphosis *core.Spell - Seed *core.Spell - ShadowEmbraceAuras core.AuraArray - Shadowburn *core.Spell - Hellfire *core.Spell - DrainLife *core.Spell - SiphonLife *core.Spell - - ActivePet *WarlockPet - Felhunter *WarlockPet - // Felguard *WarlockPet + CurseOfRecklessness *core.Spell + CurseOfTongues *core.Spell + + // Talent Tree Spells + AmplifyCurse *core.Spell + Conflagrate *core.Spell + Shadowburn *core.Spell + SiphonLife *core.Spell + UnstableAffliction *core.Spell + + // Auras + AmplifyCurseAura *core.Aura + NightfallProcAura *core.Aura + ImpShadowboltAura *core.Aura + + // Pets + ActivePet *WarlockPet + Felhunter *WarlockPet + Felguard *WarlockPet Imp *WarlockPet Succubus *WarlockPet Voidwalker *WarlockPet @@ -37,11 +56,6 @@ type Warlock struct { Infernal *InfernalPet serviceTimer *core.Timer - - // Item sets - T15_2pc *core.Aura - T15_4pc *core.Aura - T16_2pc_buff *core.Aura } func (warlock *Warlock) GetCharacter() *core.Character { @@ -86,6 +100,10 @@ func (warlock *Warlock) AddRaidBuffs(raidBuffs *proto.RaidBuffs) { } +func (warlock *Warlock) AddPartyBuffs(partyBuffs *proto.PartyBuffs) { + +} + func (warlock *Warlock) Reset(sim *core.Simulation) { } @@ -120,27 +138,21 @@ type WarlockAgent interface { const ( WarlockSpellFlagNone int64 = 0 WarlockSpellConflagrate int64 = 1 << iota - WarlockSpellFaBConflagrate WarlockSpellShadowBolt - WarlockSpellChaosBolt WarlockSpellImmolate WarlockSpellImmolateDot WarlockSpellIncinerate - WarlockSpellFaBIncinerate WarlockSpellSoulFire WarlockSpellShadowBurn WarlockSpellLifeTap WarlockSpellCorruption - WarlockSpellHaunt WarlockSpellUnstableAffliction + WarlockSpellCurseOfAgony WarlockSpellCurseOfElements WarlockSpellAgony - WarlockSpellDrainSoul WarlockSpellDrainLife - WarlockSpellMetamorphosis WarlockSpellSeedOfCorruption WarlockSpellSeedOfCorruptionExposion - WarlockSpellHandOfGuldan WarlockSpellHellfire WarlockSpellImmolationAura WarlockSpellSearingPain @@ -157,116 +169,43 @@ const ( WarlockSpellSuccubusLashOfPain WarlockSpellVoidwalkerTorment WarlockSpellSummonInfernal - WarlockSpellDemonSoul - WarlockSpellShadowflame - WarlockSpellShadowflameDot - WarlockSpellSoulBurn - WarlockSpellFelFlame - WarlockSpellBurningEmbers - WarlockSpellEmberTap WarlockSpellRainOfFire - WarlockSpellFireAndBrimstone - WarlockSpellDarkSoulInsanity - WarlockSpellDarkSoulKnowledge - WarlockSpellDarkSoulMisery - WarlockSpellMaleficGrasp - WarlockSpellDemonicSlash - WarlockSpellTouchOfChaos - WarlockSpellChaosWave - WarlockSpellCarrionSwarm - WarlockSpellDoom - WarlockSpellVoidray + WarlockSpellCurseOfDoom + WarlockSpellCurseOfRecklessness + WarlockSpellCurseOfWeakness + WarlockSpellCurseOfTongues WarlockSpellSiphonLife - WarlockSpellHavoc WarlockSpellAll int64 = 1< Date: Tue, 2 Dec 2025 18:35:02 -0500 Subject: [PATCH 03/20] first pass on talents --- sim/warlock/{fel_guard.go => _fel_guard.go} | 0 .../{seed_of_corruption.go => _seed.go} | 0 sim/warlock/_talents.go | 291 ------------- sim/warlock/corruption.go | 3 +- sim/warlock/immolate.go | 27 +- sim/warlock/lifetap.go | 26 +- sim/warlock/siphon_life.go | 56 +++ sim/warlock/talents.go | 390 ++++++++++++++++++ sim/warlock/warlock.go | 2 + 9 files changed, 470 insertions(+), 325 deletions(-) rename sim/warlock/{fel_guard.go => _fel_guard.go} (100%) rename sim/warlock/{seed_of_corruption.go => _seed.go} (100%) delete mode 100644 sim/warlock/_talents.go create mode 100644 sim/warlock/siphon_life.go create mode 100644 sim/warlock/talents.go diff --git a/sim/warlock/fel_guard.go b/sim/warlock/_fel_guard.go similarity index 100% rename from sim/warlock/fel_guard.go rename to sim/warlock/_fel_guard.go diff --git a/sim/warlock/seed_of_corruption.go b/sim/warlock/_seed.go similarity index 100% rename from sim/warlock/seed_of_corruption.go rename to sim/warlock/_seed.go diff --git a/sim/warlock/_talents.go b/sim/warlock/_talents.go deleted file mode 100644 index ce9dd166de..0000000000 --- a/sim/warlock/_talents.go +++ /dev/null @@ -1,291 +0,0 @@ -package warlock - -import ( - "fmt" - "time" - - "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/core/proto" -) - -func (warlock *Warlock) registerHarvestLife() { - if !warlock.Talents.HarvestLife { - return - } - - warlock.AddStaticMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.5, - ClassMask: WarlockSpellDrainLife, - }) -} - -func (warlock *Warlock) registerArchimondesDarkness() { - if !warlock.Talents.ArchimondesDarkness { - return - } - - warlock.AddStaticMod(core.SpellModConfig{ - Kind: core.SpellMod_ModCharges_Flat, - IntValue: 2, - ClassMask: WarlockDarkSoulSpell, - }) - - warlock.AddStaticMod(core.SpellModConfig{ - Kind: core.SpellMod_Cooldown_Flat, - TimeValue: -time.Second * 100, - ClassMask: WarlockDarkSoulSpell, - }) -} - -func (warlock *Warlock) registerKilJaedensCunning() { - if !warlock.Talents.KiljaedensCunning { - return - } - - warlock.AddStaticMod(core.SpellModConfig{ - Kind: core.SpellMod_AllowCastWhileMoving, - ClassMask: WarlockSpellIncinerate | WarlockSpellShadowBolt | WarlockSpellMaleficGrasp, - }) -} - -func (warlock *Warlock) registerMannarothsFury() { - if !warlock.Talents.MannorothsFury { - return - } - - buff := warlock.RegisterAura(core.Aura{ - Label: "Mannaroth's Fury", - ActionID: core.ActionID{SpellID: 108508}, - Duration: time.Second * 10, - }).AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - ClassMask: WarlockSpellRainOfFire | WarlockSpellSeedOfCorruptionExposion | WarlockSpellSeedOfCorruption | WarlockSpellHellfire | WarlockSpellImmolationAura, - FloatValue: 1, - }) - - warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 108508}, - Flags: core.SpellFlagAPL, - ProcMask: core.ProcMaskEmpty, - SpellSchool: core.SpellSchoolShadow, - - ThreatMultiplier: 1, - DamageMultiplier: 1, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - NonEmpty: true, - }, - - CD: core.Cooldown{ - Timer: warlock.NewTimer(), - Duration: time.Minute, - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - buff.Activate(sim) - }, - }) -} - -func (warlock *Warlock) registerGrimoireOfSupremacy() { - if !warlock.Talents.GrimoireOfSupremacy { - return - } - - // It's honestly just a repaint and a 20% damage mod slapped on top.. - fireBolt := warlock.Imp.GetSpell(petActionFireBolt) - - // It's now Felbolt! - fireBolt.ActionID = core.ActionID{SpellID: 115746} - fireBolt.DamageMultiplier *= 1.2 - updateName(&warlock.Imp.Pet, "Fel Imp") - - // Spell stays the same - warlock.Voidwalker.PseudoStats.DamageDealtMultiplier *= 1.2 - updateName(&warlock.Voidwalker.Pet, "Voidlord") - - // Now Tongue Lash - shadowBite := warlock.Felhunter.GetSpell(petActionShadowBite) - shadowBite.ActionID = core.ActionID{SpellID: 115778} - warlock.Felhunter.PseudoStats.DamageDealtMultiplier *= 1.2 - updateName(&warlock.Felhunter.Pet, "Observer") - - // Succubus get's larger makeover - // Now dualwield with 1.5x less base damage - weaponConfig := ScaledAutoAttackConfig(3) - weaponConfig.MainHand.BaseDamageMax /= 1.5 - weaponConfig.MainHand.BaseDamageMin /= 1.5 - weaponConfig.OffHand = weaponConfig.MainHand - - warlock.Succubus.EnableAutoAttacks(warlock.Succubus, *weaponConfig) - warlock.Succubus.ChangeStatInheritance(warlock.SimplePetStatInheritanceWithScale(1 + 1.0/9.0)) - lashOfPain := warlock.Succubus.GetSpell(petActionLashOfPain) - lashOfPain.ActionID = core.ActionID{SpellID: 115748} - //Shivarra will auto-cast at 100 energy - warlock.Succubus.MinEnergy = 100 - warlock.Succubus.PseudoStats.DamageDealtMultiplier *= 1.2 - warlock.Succubus.PseudoStats.DisableDWMissPenalty = true - updateName(&warlock.Succubus.Pet, "Shivarra") - - updateName(&warlock.Infernal.Pet, "Abyssal") - warlock.Infernal.PseudoStats.DamageDealtMultiplier *= 1.2 - - updateName(&warlock.Doomguard.Pet, "Terrorguard") - warlock.Doomguard.PseudoStats.DamageDealtMultiplier *= 1.2 - - warlock.AddStaticMod(core.SpellModConfig{ - Kind: core.SpellMod_PowerCost_Pct, - FloatValue: -0.5, - ClassMask: WarlockSpellSummonInfernal | WarlockSpellSummonDoomguard, - }) -} - -func updateName(pet *core.Pet, name string) { - pet.Name = name - pet.Label = fmt.Sprintf("%s - %s", pet.Owner.Label, name) -} - -func (warlock *Warlock) registerGrimoireOfService() { - if !warlock.Talents.GrimoireOfService { - return - } - - // build all pets as they're additional summons - imp := warlock.registerImpWithName("Grimoire: Imp", false, true) - felHunter := warlock.registerFelHunterWithName("Grimoire: Felhunter", false, true) - voidWalker := warlock.registerVoidWalkerWithName("Grimoire: Voidwalker", false, true) - succubus := warlock.registerSuccubusWithName("Grimoire: Succubus", false, true) - - warlock.serviceTimer = warlock.NewTimer() - - warlock.BuildAndRegisterSummonSpell(111859, imp) - warlock.BuildAndRegisterSummonSpell(111895, voidWalker) - warlock.BuildAndRegisterSummonSpell(111896, succubus) - warlock.BuildAndRegisterSummonSpell(111897, felHunter) -} - -func (warlock *Warlock) BuildAndRegisterSummonSpell(id int32, pet *WarlockPet) { - for _, spell := range pet.AutoCastAbilities { - spell.Flags &= ^core.SpellFlagAPL - } - - warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: id}, - SpellSchool: core.SpellSchoolShadow, - Flags: core.SpellFlagAPL, - ProcMask: core.ProcMaskEmpty, - - ThreatMultiplier: 1, - DamageMultiplier: 1, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - CD: core.Cooldown{ - Timer: warlock.serviceTimer, - Duration: time.Minute * 2, - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - pet.EnableWithTimeout(sim, pet, time.Second*20) - }, - }) -} - -func (warlock *Warlock) registerGrimoireOfSacrifice() { - if !warlock.Talents.GrimoireOfSacrifice { - return - } - - buff := warlock.RegisterAura(core.Aura{ - Label: "Grimioire of Sacrifice", - ActionID: core.ActionID{SpellID: 108503}, - Duration: time.Hour, - 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.Matches(WarlockSpellChaosBolt) || !result.Landed() { - return - } - - warlock.ApplyDotWithPandemic(spell.Dot(result.Target), sim) - }, - }) - - switch warlock.Spec { - case proto.Spec_SpecDemonologyWarlock: - buff.AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.25, - ClassMask: WarlockSpellShadowBolt | WarlockSpellSoulBurn | WarlockSpellHandOfGuldan | WarlockSpellChaosWave | WarlockSpellTouchOfChaos | WarlockSpellDemonicSlash | WarlockSpellVoidray | WarlockSpellSoulFire, - }) - case proto.Spec_SpecAfflictionWarlock: - buff.AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.2, - ClassMask: WarlockSpellDrainSoul | WarlockSpellMaleficGrasp | WarlockSpellHaunt | WarlockSpellFelFlame, - }) - case proto.Spec_SpecDestructionWarlock: - buff.AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.15, - ClassMask: WarlockSpellConflagrate | WarlockSpellShadowBurn | WarlockSpellFelFlame | WarlockSpellIncinerate | WarlockSpellDrainLife, - }) - } - - applyPetHook := func(pet *WarlockPet) { - oldEnable := pet.OnPetEnable - pet.OnPetEnable = func(sim *core.Simulation) { - if oldEnable != nil { - oldEnable(sim) - } - - if buff.IsActive() { - buff.Deactivate(sim) - } - } - } - - warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 108503}, - SpellSchool: core.SpellSchoolFire, - Flags: core.SpellFlagAPL, - ProcMask: core.ProcMaskEmpty, - - ThreatMultiplier: 1, - DamageMultiplier: 1, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - NonEmpty: true, - }, - - CD: core.Cooldown{ - Timer: warlock.NewTimer(), - Duration: time.Second * 30, - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - if warlock.ActivePet != nil { - warlock.ActivePet.Disable(sim) - } - - buff.Activate(sim) - }, - }) - - for _, pet := range warlock.Pets { - pet.DisableOnStart() - } - - applyPetHook(warlock.Imp) - applyPetHook(warlock.Succubus) - applyPetHook(warlock.Felhunter) - applyPetHook(warlock.Voidwalker) -} diff --git a/sim/warlock/corruption.go b/sim/warlock/corruption.go index 04e8c11705..9715c03e33 100644 --- a/sim/warlock/corruption.go +++ b/sim/warlock/corruption.go @@ -23,7 +23,7 @@ func (warlock *Warlock) RegisterCorruption(onApplyCallback WarlockSpellCastedCal Cast: core.CastConfig{ DefaultCast: core.Cast{ GCD: core.GCDDefault, - CastTime: time.Millisecond*2000 - (time.Millisecond * 400 * time.Duration(warlock.Talents.ImprovedCorruption)), + CastTime: time.Millisecond * 2000, }, }, @@ -36,7 +36,6 @@ func (warlock *Warlock) RegisterCorruption(onApplyCallback WarlockSpellCastedCal NumberOfTicks: 6, TickLength: 3 * time.Second, AffectedByCastSpeed: false, - BonusCoefficient: corruptionCoeff + ((0.12 * float64(warlock.Talents.EmpoweredCorruption)) / 6), OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { dot.Snapshot(target, warlock.CalcScalingSpellDmg(corruptionScale)) diff --git a/sim/warlock/immolate.go b/sim/warlock/immolate.go index 12eb2c1406..b115298aec 100644 --- a/sim/warlock/immolate.go +++ b/sim/warlock/immolate.go @@ -4,17 +4,10 @@ import ( "time" "github.com/wowsims/tbc/sim/core" - "github.com/wowsims/tbc/sim/warlock" ) const immolateCoeff = 0.13 -// Damage Done By Caster setup -const ( - DDBC_Immolate int = iota - DDBC_Total -) - func (warlock *Warlock) registerImmolate() { actionID := core.ActionID{SpellID: 27215} warlock.Immolate = warlock.RegisterSpell(core.SpellConfig{ @@ -60,12 +53,12 @@ func (warlock *Warlock) registerImmolate() { Dot: core.DotConfig{ Aura: core.Aura{ Label: "Immolate (DoT)", - OnGain: func(aura *core.Aura, sim *core.Simulation) { - core.EnableDamageDoneByCaster(DDBC_Immolate, DDBC_Total, warlock.AttackTables[aura.Unit.UnitIndex], immolateDamageDoneByCasterHandler) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - core.DisableDamageDoneByCaster(DDBC_Immolate, warlock.AttackTables[aura.Unit.UnitIndex]) - }, + // OnGain: func(aura *core.Aura, sim *core.Simulation) { + // core.EnableDamageDoneByCaster(DDBC_Immolate, DDBC_Total, warlock.AttackTables[aura.Unit.UnitIndex], immolateDamageDoneByCasterHandler) + // }, + // OnExpire: func(aura *core.Aura, sim *core.Simulation) { + // core.DisableDamageDoneByCaster(DDBC_Immolate, warlock.AttackTables[aura.Unit.UnitIndex]) + // }, }, NumberOfTicks: 5, TickLength: 3 * time.Second, @@ -89,11 +82,3 @@ func (warlock *Warlock) registerImmolate() { }, }) } - -func immolateDamageDoneByCasterHandler(sim *core.Simulation, spell *core.Spell, attackTable *core.AttackTable) float64 { - if spell.Matches(warlock.WarlockSpellRainOfFire) { - return 1.5 - } - - return 1 -} diff --git a/sim/warlock/lifetap.go b/sim/warlock/lifetap.go index 8fdb4b0c4d..041c6dc369 100644 --- a/sim/warlock/lifetap.go +++ b/sim/warlock/lifetap.go @@ -1,19 +1,23 @@ package warlock import ( + "math" + "github.com/wowsims/tbc/sim/core" + "github.com/wowsims/tbc/sim/core/stats" ) func (warlock *Warlock) registerLifeTap() { actionID := core.ActionID{SpellID: 27222} manaMetrics := warlock.NewManaMetrics(actionID) - baseRestore := 582.0 * (1.0 + 0.1*float64(warlock.Talents.ImprovedLifeTap)) + healthCost := 582.0 + baseRestore := healthCost * (1.0 + 0.1*float64(warlock.Talents.ImprovedLifeTap)) - petRestore := 0.3333 * float64(warlock.Talents.ManaFeed) - var petManaMetrics []*core.ResourceMetrics - if warlock.Talents.ManaFeed > 0 { - petManaMetrics = append(petManaMetrics, warlock.ActivePet.NewManaMetrics(actionID)) - } + // petRestore := 0.3333 * float64(warlock.Talents.ManaFeed) + // var petManaMetrics []*core.ResourceMetrics + // if warlock.Talents.ManaFeed > 0 { + // petManaMetrics = append(petManaMetrics, warlock.ActivePet.NewManaMetrics(actionID)) + // } warlock.RegisterSpell(core.SpellConfig{ ActionID: actionID, @@ -32,13 +36,13 @@ func (warlock *Warlock) registerLifeTap() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { // Life tap adds 0.8*sp to mana restore - restore := baseRestore - warlock.RemoveHealth(sim, 582) + restore := baseRestore + (math.Max(warlock.GetStat(stats.SpellPower), warlock.GetStat(stats.ShadowPower)) * 0.8) + warlock.RemoveHealth(sim, healthCost) warlock.AddMana(sim, restore, manaMetrics) - if warlock.Talents.ManaFeed > 0 { - warlock.ActivePet.AddMana(sim, restore*petRestore, petManaMetrics[0]) - } + // if warlock.Talents.ManaFeed > 0 { + // warlock.ActivePet.AddMana(sim, restore*petRestore, petManaMetrics[0]) + // } }, }) } diff --git a/sim/warlock/siphon_life.go b/sim/warlock/siphon_life.go new file mode 100644 index 0000000000..57966661c0 --- /dev/null +++ b/sim/warlock/siphon_life.go @@ -0,0 +1,56 @@ +package warlock + +import ( + "time" + + "github.com/wowsims/tbc/sim/core" +) + +const siphonLifeCoeff = 0.1 + +func (warlock *Warlock) registerSiphonLifeSpell(onApplyCallback WarlockSpellCastedCallback, onTickCallback WarlockSpellCastedCallback) { + actionID := core.ActionID{SpellID: 30911} + baseCost := 410.0 + resultSlice := make(core.SpellResultSlice, 1) + healthMetrics := warlock.NewHealthMetrics(actionID) + + warlock.SiphonLife = warlock.RegisterSpell(core.SpellConfig{ + ActionID: actionID, + SpellSchool: core.SpellSchoolShadow, + ClassSpellMask: WarlockSpellSiphonLife, + BaseCost: baseCost, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + Cost: baseCost, + GCD: core.GCDDefault, + }, + }, + Dot: core.DotConfig{ + Aura: core.Aura{ + Label: "Corruption", + Tag: "Affliction", + ActionID: core.ActionID{SpellID: 27216}, + }, + NumberOfTicks: 6, + TickLength: 3 * time.Second, + AffectedByCastSpeed: false, + + OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { + dot.Snapshot(target, warlock.CalcScalingSpellDmg(corruptionScale)) + }, + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + resultSlice[0] = dot.CalcSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) + + if onTickCallback != nil { + onTickCallback(resultSlice, dot.Spell, sim) + } + + dot.Spell.DealPeriodicDamage(sim, resultSlice[0]) + + healthToRegain := resultSlice[0].Damage * (1 * warlock.PseudoStats.BonusHealingTaken) + warlock.GainHealth(sim, healthToRegain, healthMetrics) + dot.Spell.ApplyAOEThreat(healthToRegain * 0.5) + }, + }, + }) +} diff --git a/sim/warlock/talents.go b/sim/warlock/talents.go new file mode 100644 index 0000000000..63d82a23c5 --- /dev/null +++ b/sim/warlock/talents.go @@ -0,0 +1,390 @@ +package warlock + +import ( + "time" + + "github.com/wowsims/tbc/sim/core" + "github.com/wowsims/tbc/sim/core/stats" +) + +/* +Affliction +Skipping the following (for now) +- Soul Siphon +- Improved Life Tap -> included in lifetap.go +- Empowered Corruption -> included in corruption.go +- Siphon Life -> implemented in siphon_life.go +- Fel Concentration +- Grim Reach +- Curse of Weakness +- Curse of Exhaustion +- Dark Pact +- Improved Howl of Terror +*/ +func (warlock *Warlock) applySuppression() { + if warlock.Talents.Suppression == 0 { + return + } + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_BonusHit_Percent, + FloatValue: 0.02 * float64(warlock.Talents.Suppression), + ClassMask: WarlockAfflictionSpells, + }) +} + +func (warlock *Warlock) applyImprovedCorruption() { + if warlock.Talents.Suppression == 0 { + return + } + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_CastTime_Flat, + TimeValue: time.Millisecond * 400 * time.Duration(warlock.Talents.ImprovedCorruption), + ClassMask: WarlockAfflictionSpells, + }) +} + +func (warlock *Warlock) registerAmplifyCurse() { + if warlock.Talents.AmplifyCurse == false { + return + } + + actionID := core.ActionID{SpellID: 18288} + warlock.AmplifyCurseAura = warlock.RegisterAura(core.Aura{ + Label: "Amplify Curse", + ActionID: actionID, + Duration: time.Second * 30, + }) + + warlock.AmplifyCurse = warlock.RegisterSpell(core.SpellConfig{ + ActionID: actionID, + Flags: core.SpellFlagAPL, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: time.Second, + }, + CD: core.Cooldown{ + Timer: warlock.NewTimer(), + Duration: time.Minute * 3, + }, + }, + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { + + }, + RelatedSelfBuff: warlock.AmplifyCurseAura, + }) + + warlock.AddMajorCooldown(core.MajorCooldown{ + Spell: warlock.AmplifyCurse, + Type: core.CooldownTypeDPS, + }) +} + +func (warlock *Warlock) applyImprovedCurseOfAgony() { + if warlock.Talents.ImprovedCurseOfAgony == 0 { + return + } + + //This is a flat X% dot dmg buff, technically incorrect, fix later + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_DotDamageDone_Pct, + FloatValue: 1 * (0.05 * float64(warlock.Talents.ImprovedCurseOfAgony)), + ClassMask: WarlockSpellCurseOfAgony, + }) +} + +func (warlock *Warlock) applyNighfall() { + if warlock.Talents.Nightfall == 0 { + return + } + + warlock.NightfallProcAura = warlock.RegisterAura(core.Aura{ + Label: "Nightfall Shadow Trance", + ActionID: core.ActionID{SpellID: 17941}, + Duration: time.Second * 10, + OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { + // Check for an instant cast shadowbolt to disable aura + if spell != warlock.ShadowBolt || spell.CurCast.CastTime != 0 { + return + } + aura.Deactivate(sim) + }, + }) + + warlock.RegisterAura(core.Aura{ + Label: "Nightfall", + 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, spellEffect *core.SpellEffect) { + if spell != warlock.Corruption && spell != warlock.DrainLife { + return + } + if sim.RandomFloat("nightfall") > 0.04 { + return + } + warlock.NightfallProcAura.Activate(sim) + }, + }) + +} + +func (warlock *Warlock) applyEmpoweredCorruption() { + if warlock.Talents.ImprovedCorruption == 0 { + return + } + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_BonusCoeffecient_Flat, + FloatValue: ((0.12 * float64(warlock.Talents.EmpoweredCorruption)) / 6), + ClassMask: WarlockSpellCorruption, + }) +} + +func (warlock *Warlock) applyShadowEmbrace() { + if warlock.Talents.ShadowEmbrace == 0 { + return + } + + var debuffAuras []*core.Aura + for _, target := range warlock.Env.Encounter.Targets { + debuffAuras = append(debuffAuras, core.ShadowEmbraceAura(&target.Unit, warlock.Talents.ShadowEmbrace)) + } + + warlock.RegisterAura(core.Aura{ + Label: "Shadow Embrace Talent", + Duration: core.NeverExpires, + OnReset: func(aura *core.Aura, sim *core.Simulation) { + aura.Activate(sim) + }, + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, spellEffect *core.SpellEffect) { + if !spellEffect.Landed() { + return + } + + if spell == warlock.Corruption || spell == warlock.SiphonLife || spell == warlock.CurseOfAgony || spell.SameAction(warlock.Seeds[0].ActionID) { + debuffAuras[spellEffect.Target.Index].Activate(sim) + } + }, + }) +} + +func (warlock *Warlock) applyShadowMastery() { + if warlock.Talents.ShadowMastery == 0 { + return + } + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_DamageDone_Pct, + FloatValue: 0.02 * float64(warlock.Talents.ShadowMastery), + ClassMask: WarlockShadowDamage, + }) +} + +func (warlock *Warlock) applyContagion() { + if warlock.Talents.Contagion == 0 { + return + } + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_DamageDone_Pct, + FloatValue: 0.01 * float64(warlock.Talents.Contagion), + ClassMask: WarlockContagionSpells, + }) +} + +func (warlock *Warlock) applyUnstableAffliction() { + if warlock.Talents.UnstableAffliction { + warlock.registerUnstableAffliction() + } +} + +/* +Demonology +Skipping so many for now +*/ +func (warlock *Warlock) applyDemonicEmbrace() { + if warlock.Talents.DemonicEmbrace == 0 { + return + } + + warlock.AddStatDependency(stats.Stamina, stats.Stamina, (0.03)*float64(warlock.Talents.DemonicEmbrace)) + warlock.AddStatDependency(stats.Spirit, stats.Spirit, (0.03)*float64(warlock.Talents.DemonicEmbrace)) +} + +// TODO - Add pet part +func (warlock *Warlock) applyFelIntellect() { + if warlock.Talents.FelIntellect == 0 { + return + } + + warlock.AddStatDependency(stats.Intellect, stats.Mana, 15*(0.01)*float64(warlock.Talents.FelIntellect)) + +} + +func (warlock *Warlock) applyFelStamina() { + if warlock.Talents.FelStamina == 0 { + return + } + + warlock.AddStatDependency(stats.Health, stats.Health, 1+0.01*float64(warlock.Talents.FelStamina)) +} + +// Placeholder for Unholy Power +//func (warlock *Warlock) applyUnholyPower() {} + +//Placeholder for DSac +//func (warlock *Warlock) applyDemonicSacrifice(){} + +//Placeholder for MasterDemonologist +//func (warlock *Warlock) applyMasterDemonologist(){} + +//Placeholder for Demonic Knowledge +//func (warlock *Warlock) applyDemonicKnowledge(){} + +func (warlock *Warlock) applySoulLink() { + if !warlock.Talents.SoulLink { + return + } + + // Add if/while pet is alive + warlock.PseudoStats.DamageTakenMultiplier *= 0.80 + warlock.PseudoStats.DamageDealtMultiplier *= 1.05 +} + +func (warlock *Warlock) applyDemonicTactics() { + if warlock.Talents.DemonicTactics == 0 { + return + } + + warlock.AddStat(stats.SpellCritPercent, 0.01*float64(warlock.Talents.DemonicTactics)) +} + +/* +Destruction +Skip for now: + - Improved shadowbolt - included in shadowbolt.go + - ImprovedImmolate - include in immolate.go +*/ + +func (warlock *Warlock) applyCataclysm() { + if warlock.Talents.Cataclysm == 0 { + return + } + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_PowerCost_Pct, + FloatValue: 1.0 - 0.01*float64(warlock.Talents.Cataclysm), + ClassMask: WarlockDestructionSpells, + }) +} + +func (warlock *Warlock) applyBane() { + if warlock.Talents.Cataclysm == 0 { + return + } + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_CastTime_Flat, + TimeValue: -(time.Millisecond * 100) * time.Duration(warlock.Talents.Bane), + ClassMask: WarlockSpellShadowBolt | WarlockSpellImmolate, + }) + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_CastTime_Flat, + TimeValue: -(time.Millisecond * 400) * time.Duration(warlock.Talents.Bane), + ClassMask: WarlockSpellSoulFire, + }) +} + +//TODO - implement the pet talents +// func (warlock *Warlock) applyImprovedFirebolt(){} +// func (warlock *Warlock) applyImprovedLashOfPain(){} + +func (warlock *Warlock) applyDestructiveReach() { + if warlock.Talents.DestructiveReach == 0 { + return + } + + warlock.PseudoStats.ThreatMultiplier *= 1.0 - (0.5 * float64(warlock.Talents.DestructiveReach)) +} + +func (warlock *Warlock) applyImprovedSearingPain() { + if warlock.Talents.ImprovedSearingPain == 0 { + return + } + var critBonus = 0 + switch warlock.Talents.ImprovedSearingPain { + case 1: + critBonus = 4 + case 2: + critBonus = 7 + case 10: + critBonus = 10 + } + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_BonusCrit_Percent, + FloatValue: float64(critBonus), + ClassMask: WarlockSpellSearingPain, + }) +} + +func (warlock *Warlock) applyRuin() { + if !warlock.Talents.Ruin { + return + } + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_CritMultiplier_Flat, + FloatValue: 1.0, + ClassMask: WarlockDestructionSpells, + }) +} + +func (warlock *Warlock) applyEmberstorm() { + if warlock.Talents.Emberstorm == 0 { + return + } + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_DamageDone_Pct, + FloatValue: 0.01 * float64(warlock.Talents.Emberstorm), + ClassMask: WarlockFireDamage, + }) + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_CastTime_Pct, + FloatValue: 0.02 * float64(warlock.Talents.Emberstorm), + ClassMask: WarlockSpellImmolate, + }) +} + +func (warlock *Warlock) applyBacklash() { + if warlock.Talents.Backlash == 0 { + return + } + + warlock.AddStat(stats.SpellCritPercent, float64(warlock.Talents.Backlash)) +} + +func (warlock *Warlock) applySoulLeech() { + if warlock.Talents.SoulLeech == 0 { + return + } + +} + +func (warlock *Warlock) applyShadowAndFlame() { + if warlock.Talents.ShadowAndFlame == 0 { + return + } + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_BonusCoeffecient_Flat, + FloatValue: 0.04 * float64(warlock.Talents.ShadowAndFlame), + ClassMask: WarlockSpellShadowBolt | WarlockSpellIncinerate, + }) +} diff --git a/sim/warlock/warlock.go b/sim/warlock/warlock.go index f94c44cf90..98e1702aaf 100644 --- a/sim/warlock/warlock.go +++ b/sim/warlock/warlock.go @@ -200,6 +200,8 @@ const ( WarlockAllSummons = WarlockSummonSpells | WarlockSpellSummonInfernal | WarlockSpellSummonDoomguard + WarlockContagionSpells = WarlockSpellCurseOfAgony | WarlockSpellCorruption | WarlockSpellSeedOfCorruption + WarlockCurses = WarlockSpellCurseOfAgony | WarlockSpellCurseOfDoom | WarlockSpellCurseOfElements | WarlockSpellCurseOfRecklessness | WarlockSpellCurseOfTongues | WarlockSpellCurseOfWeakness From fe00129999da790cb38e8ac1dc986b07ad0871f6 Mon Sep 17 00:00:00 2001 From: jazz405 Date: Tue, 2 Dec 2025 21:35:05 -0500 Subject: [PATCH 04/20] site stood up with minimal issues --- sim/warlock/talents.go | 55 +++++++++++++++++++++++++++++++++--------- sim/warlock/warlock.go | 24 ++++++++++++------ 2 files changed, 60 insertions(+), 19 deletions(-) diff --git a/sim/warlock/talents.go b/sim/warlock/talents.go index 63d82a23c5..28239c6b40 100644 --- a/sim/warlock/talents.go +++ b/sim/warlock/talents.go @@ -7,6 +7,40 @@ import ( "github.com/wowsims/tbc/sim/core/stats" ) +func (warlock *Warlock) applyAfflictionTalents() { + warlock.applySuppression() + warlock.applyImprovedCorruption() + warlock.registerAmplifyCurse() + warlock.applyImprovedCurseOfAgony() + warlock.applyNightfall() + warlock.applyEmpoweredCorruption() + warlock.applyShadowEmbrace() + warlock.applyShadowMastery() + warlock.applyContagion() + warlock.applyUnstableAffliction() +} + +func (warlock *Warlock) applyDemonologyTalents() { + warlock.applyDemonicEmbrace() + warlock.applyFelIntellect() + warlock.applyFelStamina() + warlock.applySoulLink() + warlock.applyDemonicTactics() + +} + +func (warlock *Warlock) applyDestructionTalents() { + warlock.applyCataclysm() + warlock.applyBane() + warlock.applyDestructiveReach() + warlock.applyImprovedSearingPain() + warlock.applyRuin() + warlock.applyEmberstorm() + warlock.applyBacklash() + warlock.applySoulLeech() + warlock.applyShadowAndFlame() +} + /* Affliction Skipping the following (for now) @@ -94,7 +128,7 @@ func (warlock *Warlock) applyImprovedCurseOfAgony() { }) } -func (warlock *Warlock) applyNighfall() { +func (warlock *Warlock) applyNightfall() { if warlock.Talents.Nightfall == 0 { return } @@ -118,7 +152,7 @@ func (warlock *Warlock) applyNighfall() { OnReset: func(aura *core.Aura, sim *core.Simulation) { aura.Activate(sim) }, - OnPeriodicDamageDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, spellEffect *core.SpellEffect) { + OnPeriodicDamageDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, spellEffect *core.SpellResult) { if spell != warlock.Corruption && spell != warlock.DrainLife { return } @@ -148,24 +182,19 @@ func (warlock *Warlock) applyShadowEmbrace() { return } - var debuffAuras []*core.Aura - for _, target := range warlock.Env.Encounter.Targets { - debuffAuras = append(debuffAuras, core.ShadowEmbraceAura(&target.Unit, warlock.Talents.ShadowEmbrace)) - } - warlock.RegisterAura(core.Aura{ Label: "Shadow Embrace Talent", Duration: core.NeverExpires, OnReset: func(aura *core.Aura, sim *core.Simulation) { aura.Activate(sim) }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, spellEffect *core.SpellEffect) { + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, spellEffect *core.SpellResult) { if !spellEffect.Landed() { return } if spell == warlock.Corruption || spell == warlock.SiphonLife || spell == warlock.CurseOfAgony || spell.SameAction(warlock.Seeds[0].ActionID) { - debuffAuras[spellEffect.Target.Index].Activate(sim) + core.ShadowEmbraceAura(spellEffect.Target, warlock.Talents.ShadowEmbrace, spell.Dot(spellEffect.Target).Duration).Activate(sim) } }, }) @@ -210,8 +239,10 @@ func (warlock *Warlock) applyDemonicEmbrace() { return } - warlock.AddStatDependency(stats.Stamina, stats.Stamina, (0.03)*float64(warlock.Talents.DemonicEmbrace)) - warlock.AddStatDependency(stats.Spirit, stats.Spirit, (0.03)*float64(warlock.Talents.DemonicEmbrace)) + warlock.MultiplyStat(stats.Stamina, 1.0+(0.03)*float64(warlock.Talents.DemonicEmbrace)) + warlock.MultiplyStat(stats.Spirit, 1.0+(0.03)*float64(warlock.Talents.DemonicEmbrace)) + // warlock.AddStatDependency(stats.Stamina, stats.Stamina, (0.03)*float64(warlock.Talents.DemonicEmbrace)) + // warlock.AddStatDependency(stats.Spirit, stats.Spirit, (0.03)*float64(warlock.Talents.DemonicEmbrace)) } // TODO - Add pet part @@ -229,7 +260,7 @@ func (warlock *Warlock) applyFelStamina() { return } - warlock.AddStatDependency(stats.Health, stats.Health, 1+0.01*float64(warlock.Talents.FelStamina)) + warlock.MultiplyStat(stats.Health, 1.0+0.01*float64(warlock.Talents.FelStamina)) } // Placeholder for Unholy Power diff --git a/sim/warlock/warlock.go b/sim/warlock/warlock.go index 98e1702aaf..7574663f9e 100644 --- a/sim/warlock/warlock.go +++ b/sim/warlock/warlock.go @@ -69,16 +69,26 @@ func (warlock *Warlock) GetWarlock() *Warlock { } func RegisterWarlock() { - + core.RegisterAgentFactory( + proto.Player_Warlock{}, + proto.Spec_SpecWarlock, + func(character *core.Character, options *proto.Player) core.Agent { + return NewWarlock(character, options, options.GetWarlock().Options.ClassOptions) + }, + func(player *proto.Player, spec interface{}) { + playerSpec, ok := spec.(*proto.Player_Warlock) + if !ok { + panic("Invalid spec value for Warlock!") + } + player.Spec = playerSpec + }, + ) } func (warlock *Warlock) ApplyTalents() { - // warlock.registerHarvestLife() - // warlock.registerArchimondesDarkness() - // warlock.registerKilJaedensCunning() - // warlock.registerMannarothsFury() - // warlock.registerGrimoireOfSupremacy() - // warlock.registerGrimoireOfSacrifice() + warlock.applyAfflictionTalents() + warlock.applyDemonologyTalents() + warlock.applyDestructionTalents() } func (warlock *Warlock) Initialize() { From f8d6fdde51a95dfb622a4e23a363d8f6412f3e4f Mon Sep 17 00:00:00 2001 From: jazz405 Date: Wed, 3 Dec 2025 18:15:12 -0500 Subject: [PATCH 05/20] spell updates, can see in ui --- sim/warlock/agony.go | 2 +- sim/warlock/conflagrate.go | 2 +- sim/warlock/corruption.go | 33 +++++++++++++++--------------- sim/warlock/curse_of_elements.go | 2 +- sim/warlock/drain_life.go | 12 +++++------ sim/warlock/immolate.go | 2 +- sim/warlock/incinerate.go | 2 +- sim/warlock/lifetap.go | 2 +- sim/warlock/{_seed.go => seed.go} | 4 ---- sim/warlock/shadowbolt.go | 3 +-- sim/warlock/shadowburn.go | 4 ++-- sim/warlock/siphon_life.go | 14 ++++++------- sim/warlock/soulfire.go | 2 +- sim/warlock/talents.go | 2 +- sim/warlock/unstable_affliction.go | 2 +- sim/warlock/warlock.go | 19 +++++++++++++++-- 16 files changed, 58 insertions(+), 49 deletions(-) rename sim/warlock/{_seed.go => seed.go} (96%) diff --git a/sim/warlock/agony.go b/sim/warlock/agony.go index e7b3e92a84..238fc40209 100644 --- a/sim/warlock/agony.go +++ b/sim/warlock/agony.go @@ -9,7 +9,7 @@ import ( const agonyScale = 0.0255 const agonyCoeff = 0.0255 -func (warlock *Warlock) registerAgony() { +func (warlock *Warlock) registerCurseOfAgony() { warlock.CurseOfAgony = warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 980}, Flags: core.SpellFlagAPL, diff --git a/sim/warlock/conflagrate.go b/sim/warlock/conflagrate.go index b372cc1712..0f647be53d 100644 --- a/sim/warlock/conflagrate.go +++ b/sim/warlock/conflagrate.go @@ -10,7 +10,7 @@ const conflagrateCoeff = 0.429 func (warlock *Warlock) registerConflagrate() { warlock.Conflagrate = warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 30912}, + ActionID: core.ActionID{SpellID: 17962}, SpellSchool: core.SpellSchoolFire, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagAPL, diff --git a/sim/warlock/corruption.go b/sim/warlock/corruption.go index 9715c03e33..beced5ded1 100644 --- a/sim/warlock/corruption.go +++ b/sim/warlock/corruption.go @@ -6,14 +6,13 @@ import ( "github.com/wowsims/tbc/sim/core" ) -const corruptionScale = 0.156 const corruptionCoeff = 0.156 -func (warlock *Warlock) RegisterCorruption(onApplyCallback WarlockSpellCastedCallback, onTickCallback WarlockSpellCastedCallback) *core.Spell { +func (warlock *Warlock) registerCorruption() *core.Spell { resultSlice := make(core.SpellResultSlice, 1) warlock.Corruption = warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 27216}, + ActionID: core.ActionID{SpellID: 172}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagAPL, @@ -31,34 +30,34 @@ func (warlock *Warlock) RegisterCorruption(onApplyCallback WarlockSpellCastedCal Aura: core.Aura{ Label: "Corruption", Tag: "Affliction", - ActionID: core.ActionID{SpellID: 27216}, + ActionID: core.ActionID{SpellID: 172}, }, NumberOfTicks: 6, TickLength: 3 * time.Second, AffectedByCastSpeed: false, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, warlock.CalcScalingSpellDmg(corruptionScale)) + dot.Snapshot(target, warlock.CalcScalingSpellDmg(corruptionCoeff)) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { resultSlice[0] = dot.CalcSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) - if onTickCallback != nil { - onTickCallback(resultSlice, dot.Spell, sim) - } + // if onTickCallback != nil { + // onTickCallback(resultSlice, dot.Spell, sim) + // // } dot.Spell.DealPeriodicDamage(sim, resultSlice[0]) }, }, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHitNoHitCounter) - if onApplyCallback != nil { - resultSlice[0] = result - onApplyCallback(resultSlice, spell, sim) - } - spell.DealOutcome(sim, result) - }, + // ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + // result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHitNoHitCounter) + // if onApplyCallback != nil { + // resultSlice[0] = result + // onApplyCallback(resultSlice, spell, sim) + // } + // spell.DealOutcome(sim, result) + // }, ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, useSnapshot bool) *core.SpellResult { dot := spell.Dot(target) if useSnapshot { @@ -66,7 +65,7 @@ func (warlock *Warlock) RegisterCorruption(onApplyCallback WarlockSpellCastedCal result.Damage /= dot.TickPeriod().Seconds() return result } else { - result := spell.CalcPeriodicDamage(sim, target, warlock.CalcScalingSpellDmg(corruptionScale), spell.OutcomeExpectedMagicCrit) + result := spell.CalcPeriodicDamage(sim, target, warlock.CalcScalingSpellDmg(corruptionCoeff), spell.OutcomeExpectedMagicCrit) result.Damage /= dot.CalcTickPeriod().Round(time.Millisecond).Seconds() return result } diff --git a/sim/warlock/curse_of_elements.go b/sim/warlock/curse_of_elements.go index 17a32e2ded..e4ee72c16d 100644 --- a/sim/warlock/curse_of_elements.go +++ b/sim/warlock/curse_of_elements.go @@ -10,7 +10,7 @@ func (warlock *Warlock) registerCurseOfElements() { warlock.CurseOfElementsAuras = warlock.NewEnemyAuraArray(core.CurseOfElementsAura) warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 27228}, + ActionID: core.ActionID{SpellID: 1490}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskEmpty, Flags: core.SpellFlagAPL, diff --git a/sim/warlock/drain_life.go b/sim/warlock/drain_life.go index 14a0543b2a..31c758c405 100644 --- a/sim/warlock/drain_life.go +++ b/sim/warlock/drain_life.go @@ -8,12 +8,12 @@ import ( const drainLifeCoeff = 0.143 -func (warlock *Warlock) RegisterDrainLife(callback WarlockSpellCastedCallback) { - healthMetric := warlock.NewHealthMetrics(core.ActionID{SpellID: 27220}) +func (warlock *Warlock) registerDrainLife() { + healthMetric := warlock.NewHealthMetrics(core.ActionID{SpellID: 689}) resultSlice := make(core.SpellResultSlice, 1) warlock.DrainLife = warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 27220}, + ActionID: core.ActionID{SpellID: 689}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagChanneled | core.SpellFlagAPL, @@ -40,9 +40,9 @@ func (warlock *Warlock) RegisterDrainLife(callback WarlockSpellCastedCallback) { warlock.GainHealth(sim, warlock.MaxHealth()*0.02, healthMetric) - if callback != nil { - callback(resultSlice, dot.Spell, sim) - } + // if callback != nil { + // callback(resultSlice, dot.Spell, sim) + // } }, }, diff --git a/sim/warlock/immolate.go b/sim/warlock/immolate.go index b115298aec..94049b13bd 100644 --- a/sim/warlock/immolate.go +++ b/sim/warlock/immolate.go @@ -9,7 +9,7 @@ import ( const immolateCoeff = 0.13 func (warlock *Warlock) registerImmolate() { - actionID := core.ActionID{SpellID: 27215} + actionID := core.ActionID{SpellID: 348} warlock.Immolate = warlock.RegisterSpell(core.SpellConfig{ ActionID: actionID, SpellSchool: core.SpellSchoolFire, diff --git a/sim/warlock/incinerate.go b/sim/warlock/incinerate.go index ece5234396..90f8a9a5b2 100644 --- a/sim/warlock/incinerate.go +++ b/sim/warlock/incinerate.go @@ -11,7 +11,7 @@ const incinerateCoeff = 1.54 * 1.15 func (warlock *Warlock) registerIncinerate() { warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 32231}, + ActionID: core.ActionID{SpellID: 29722}, SpellSchool: core.SpellSchoolFire, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagAPL, diff --git a/sim/warlock/lifetap.go b/sim/warlock/lifetap.go index 041c6dc369..1791d962c2 100644 --- a/sim/warlock/lifetap.go +++ b/sim/warlock/lifetap.go @@ -8,7 +8,7 @@ import ( ) func (warlock *Warlock) registerLifeTap() { - actionID := core.ActionID{SpellID: 27222} + actionID := core.ActionID{SpellID: 1454} manaMetrics := warlock.NewManaMetrics(actionID) healthCost := 582.0 baseRestore := healthCost * (1.0 + 0.1*float64(warlock.Talents.ImprovedLifeTap)) diff --git a/sim/warlock/_seed.go b/sim/warlock/seed.go similarity index 96% rename from sim/warlock/_seed.go rename to sim/warlock/seed.go index c4a60b80a5..42b792521d 100644 --- a/sim/warlock/_seed.go +++ b/sim/warlock/seed.go @@ -87,10 +87,6 @@ func (warlock *Warlock) registerSeed() { }, OnGain: func(aura *core.Aura, sim *core.Simulation) { seedPropertyTracker[aura.Unit.UnitIndex].damageTaken = 0 - if warlock.SoulBurnAura.IsActive() { - seedPropertyTracker[aura.Unit.UnitIndex].isSoulBurn = true - warlock.SoulBurnAura.Deactivate(sim) - } }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { seedPropertyTracker[aura.Unit.UnitIndex].damageTaken = 0 diff --git a/sim/warlock/shadowbolt.go b/sim/warlock/shadowbolt.go index f04f2cc95f..7f6b877367 100644 --- a/sim/warlock/shadowbolt.go +++ b/sim/warlock/shadowbolt.go @@ -6,7 +6,6 @@ import ( "github.com/wowsims/tbc/sim/core" ) -const shadowBoltScale = 1.38 const shadowBoltCoeff = 1.38 func (warlock *Warlock) registerShadowBolt() { @@ -31,7 +30,7 @@ func (warlock *Warlock) registerShadowBolt() { ThreatMultiplier: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcDamage(sim, target, warlock.CalcScalingSpellDmg(shadowBoltScale), spell.OutcomeMagicHitAndCrit) + result := spell.CalcDamage(sim, target, warlock.CalcScalingSpellDmg(shadowBoltCoeff), spell.OutcomeMagicHitAndCrit) spell.WaitTravelTime(sim, func(sim *core.Simulation) { spell.DealDamage(sim, result) }) diff --git a/sim/warlock/shadowburn.go b/sim/warlock/shadowburn.go index 9161dec579..cfe2d6bee7 100644 --- a/sim/warlock/shadowburn.go +++ b/sim/warlock/shadowburn.go @@ -8,10 +8,10 @@ import ( var shadowBurnCoeff = 0.429 -func (warlock *Warlock) registerShadowBurnSpell() { +func (warlock *Warlock) registerShadowBurn() { warlock.Shadowburn = warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 30546}, + ActionID: core.ActionID{SpellID: 17877}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagAPL, diff --git a/sim/warlock/siphon_life.go b/sim/warlock/siphon_life.go index 57966661c0..16e92ace7e 100644 --- a/sim/warlock/siphon_life.go +++ b/sim/warlock/siphon_life.go @@ -8,8 +8,8 @@ import ( const siphonLifeCoeff = 0.1 -func (warlock *Warlock) registerSiphonLifeSpell(onApplyCallback WarlockSpellCastedCallback, onTickCallback WarlockSpellCastedCallback) { - actionID := core.ActionID{SpellID: 30911} +func (warlock *Warlock) registerSiphonLifeSpell() { + actionID := core.ActionID{SpellID: 6353} baseCost := 410.0 resultSlice := make(core.SpellResultSlice, 1) healthMetrics := warlock.NewHealthMetrics(actionID) @@ -29,21 +29,21 @@ func (warlock *Warlock) registerSiphonLifeSpell(onApplyCallback WarlockSpellCast Aura: core.Aura{ Label: "Corruption", Tag: "Affliction", - ActionID: core.ActionID{SpellID: 27216}, + ActionID: core.ActionID{SpellID: 6353}, }, NumberOfTicks: 6, TickLength: 3 * time.Second, AffectedByCastSpeed: false, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, warlock.CalcScalingSpellDmg(corruptionScale)) + dot.Snapshot(target, warlock.CalcScalingSpellDmg(siphonLifeCoeff)) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { resultSlice[0] = dot.CalcSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) - if onTickCallback != nil { - onTickCallback(resultSlice, dot.Spell, sim) - } + // if onTickCallback != nil { + // onTickCallback(resultSlice, dot.Spell, sim) + // } dot.Spell.DealPeriodicDamage(sim, resultSlice[0]) diff --git a/sim/warlock/soulfire.go b/sim/warlock/soulfire.go index 2e6f25feed..dcec959414 100644 --- a/sim/warlock/soulfire.go +++ b/sim/warlock/soulfire.go @@ -11,7 +11,7 @@ const soulfireVariance = 0.2 func (warlock *Warlock) registerSoulfire() { warlock.Soulfire = warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 30545}, + ActionID: core.ActionID{SpellID: 6353}, SpellSchool: core.SpellSchoolFire, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagAPL, diff --git a/sim/warlock/talents.go b/sim/warlock/talents.go index 28239c6b40..b6e9193819 100644 --- a/sim/warlock/talents.go +++ b/sim/warlock/talents.go @@ -193,7 +193,7 @@ func (warlock *Warlock) applyShadowEmbrace() { return } - if spell == warlock.Corruption || spell == warlock.SiphonLife || spell == warlock.CurseOfAgony || spell.SameAction(warlock.Seeds[0].ActionID) { + if spell == warlock.Corruption || spell == warlock.SiphonLife || spell == warlock.CurseOfAgony || spell.SameAction(warlock.Seed.ActionID) { core.ShadowEmbraceAura(spellEffect.Target, warlock.Talents.ShadowEmbrace, spell.Dot(spellEffect.Target).Duration).Activate(sim) } }, diff --git a/sim/warlock/unstable_affliction.go b/sim/warlock/unstable_affliction.go index 03581c2fac..8dd8459af0 100644 --- a/sim/warlock/unstable_affliction.go +++ b/sim/warlock/unstable_affliction.go @@ -10,7 +10,7 @@ const uaCoeff = 0.2 func (warlock *Warlock) registerUnstableAffliction() { warlock.UnstableAffliction = warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 30405}, + ActionID: core.ActionID{SpellID: 30108}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, Flags: core.SpellFlagAPL, diff --git a/sim/warlock/warlock.go b/sim/warlock/warlock.go index 7574663f9e..6144ca852e 100644 --- a/sim/warlock/warlock.go +++ b/sim/warlock/warlock.go @@ -20,7 +20,7 @@ type Warlock struct { Immolate *core.Spell Incinerate *core.Spell SearingPain *core.Spell - Seeds []*core.Spell + Seed *core.Spell ShadowBolt *core.Spell Soulfire *core.Spell @@ -93,11 +93,26 @@ func (warlock *Warlock) ApplyTalents() { func (warlock *Warlock) Initialize() { + // Curses warlock.registerCurseOfElements() + warlock.registerCurseOfDoom() + warlock.registerCurseOfAgony() + + warlock.registerCorruption() + warlock.registerSeed() + warlock.registerConflagrate() + warlock.registerDrainLife() + warlock.registerImmolate() + warlock.registerIncinerate() + warlock.registerLifeTap() + warlock.registerShadowBolt() + warlock.registerShadowBurn() + warlock.registerSiphonLifeSpell() + warlock.registerSoulfire() + // doomguardInfernalTimer := warlock.NewTimer() // warlock.registerSummonDoomguard(doomguardInfernalTimer) // warlock.registerSummonInfernal(doomguardInfernalTimer) - warlock.registerLifeTap() // Fel Armor 10% Stamina core.MakePermanent( From e45febc681fcb37ab261ad997d8a35b08847a747 Mon Sep 17 00:00:00 2001 From: jazz405 Date: Fri, 5 Dec 2025 19:51:59 -0500 Subject: [PATCH 06/20] some ui updates --- proto/warlock.proto | 8 ++++++++ sim/core/debuffs.go | 14 +++++++++----- ui/warlock/dps/inputs.ts | 27 +++++++++++++++++++++++++-- ui/warlock/dps/presets.ts | 4 ++-- ui/warlock/dps/sim.ts | 16 ++++++++++++++-- 5 files changed, 58 insertions(+), 11 deletions(-) diff --git a/proto/warlock.proto b/proto/warlock.proto index 13a7f20277..1732ab88f1 100644 --- a/proto/warlock.proto +++ b/proto/warlock.proto @@ -87,9 +87,17 @@ message WarlockOptions { Felguard = 5; } + enum Armor { + NoArmor = 0; + FelArmor = 1; + DemonArmor = 2; + } + Summon summon = 1; bool detonate_seed = 2; bool use_item_swap_bonus_stats = 3; + bool sacrifice_summon = 5; + Armor armor = 4; } message Warlock { diff --git a/sim/core/debuffs.go b/sim/core/debuffs.go index cb28d7fe58..7e1c72e189 100644 --- a/sim/core/debuffs.go +++ b/sim/core/debuffs.go @@ -77,22 +77,26 @@ const WeakenedBlowsDuration = time.Second * 30 // –10% Physical damage dealt func WeakenedBlowsAura(target *Unit) *Aura { - return physDamageDealtAura(target, "Weakened Blows", 115798, WeakenedBlowsDuration) + return physDamageDealtAura(target, "Weakened Blows", 115798, WeakenedBlowsDuration, 10) } func DemoralizingScreech(target *Unit) *Aura { - return physDamageDealtAura(target, "Demoralizing Screech", 24423, time.Second*10) + return physDamageDealtAura(target, "Demoralizing Screech", 24423, time.Second*10, 10) } func DemoralizingRoar(target *Unit) *Aura { - return physDamageDealtAura(target, "Demoralizing Roar", 50256, time.Second*15) + return physDamageDealtAura(target, "Demoralizing Roar", 50256, time.Second*15, 10) } -func physDamageDealtAura(target *Unit, label string, spellID int32, duration time.Duration) *Aura { +func ShadowEmbraceAura(target *Unit, level int32, duration time.Duration) *Aura { + return physDamageDealtAura(target, "Shadow Embrace", 32385, duration, level) +} + +func physDamageDealtAura(target *Unit, label string, spellID int32, duration time.Duration, level int32) *Aura { aura := target.GetOrRegisterAura(Aura{ Label: label, ActionID: ActionID{SpellID: spellID}, Duration: duration, }) - PhysDamageReductionEffect(aura, 0.1) + PhysDamageReductionEffect(aura, 0.1*float64(level)) return aura } diff --git a/ui/warlock/dps/inputs.ts b/ui/warlock/dps/inputs.ts index 2baf19654c..5faf67d8df 100644 --- a/ui/warlock/dps/inputs.ts +++ b/ui/warlock/dps/inputs.ts @@ -1,10 +1,11 @@ +import { Input } from '../../core/components/input'; import * as InputHelpers from '../../core/components/input_helpers.js'; import { Player } from '../../core/player.js'; import { Spec } from '../../core/proto/common.js'; -import { WarlockOptions_Summon as Summon } from '../../core/proto/warlock.js'; +import { WarlockOptions_Summon as Summon, WarlockOptions_Armor as Armor, Warlock } from '../../core/proto/warlock.js'; import { ActionId } from '../../core/proto_utils/action_id.js'; import { WarlockSpecs } from '../../core/proto_utils/utils.js'; -import i18n from '../../i18n/config.js'; +import { EventID } from '../../core/typed_event'; // 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. @@ -22,3 +23,25 @@ export const PetInput = () => changeEmitter: (player: Player) => player.changeEmitter, }); +export const ArmorInput = () => + InputHelpers.makeClassOptionsEnumIconInput({ + fieldName: 'armor', + values: [ + { value: Armor.NoArmor, tooltip: 'No Armor'}, + { actionId: ActionId.fromSpellId(28176), value: Armor.FelArmor}, + { actionId: ActionId.fromSpellId(706), value: Armor.DemonArmor} + ] + }) + +export const DemonicSacrificeInput = () => + InputHelpers.makeClassOptionsBooleanIconInput({ + fieldName: 'sacrificeSummon', + id: ActionId.fromSpellId(18788), + getValue: (player: Player) => player.getClassOptions().sacrificeSummon && player.getTalents().demonicSacrifice && player.getClassOptions().summon != Summon.NoSummon, + setValue: (eventID: EventID, player: Player, newValue: boolean) => { + const newOptions = player.getClassOptions(); + newOptions.sacrificeSummon = newValue; + player.setClassOptions(eventID, newOptions); + }, + changeEmitter: (player: Player) => player.specOptionsChangeEmitter, + }) diff --git a/ui/warlock/dps/presets.ts b/ui/warlock/dps/presets.ts index ea0b7a1d75..295f71ff84 100644 --- a/ui/warlock/dps/presets.ts +++ b/ui/warlock/dps/presets.ts @@ -32,9 +32,9 @@ export const P1_EP_PRESET = PresetUtils.makePresetEpWeights( // https://wowhead.com/wotlk/talent-calc and copy the numbers in the url. export const Talents = { - name: 'A', + name: 'Destruction', data: SavedTalents.create({ - talentsString: '', + talentsString: '-20501301332001-50500051220051053105', }), }; diff --git a/ui/warlock/dps/sim.ts b/ui/warlock/dps/sim.ts index 674a7bf13c..7071044b77 100644 --- a/ui/warlock/dps/sim.ts +++ b/ui/warlock/dps/sim.ts @@ -16,6 +16,7 @@ const modifyDisplayStats = (player: Player) => { TypedEvent.freezeAllAndDo(() => { const currentStats = player.getCurrentStats().finalStats?.stats; + console.log("currentStatrs", currentStats) if (currentStats === undefined) { return {}; } @@ -45,10 +46,17 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarlock, { Stat.StatMana, Stat.StatStamina, Stat.StatIntellect, + Stat.StatSpirit, Stat.StatSpellPower, + Stat.StatShadowPower, + Stat.StatFirePower, Stat.StatMP5, ], - [PseudoStat.PseudoStatSpellHitPercent, PseudoStat.PseudoStatSpellCritPercent, PseudoStat.PseudoStatSpellHastePercent], + [ + PseudoStat.PseudoStatSpellHitPercent, + PseudoStat.PseudoStatSpellCritPercent, + PseudoStat.PseudoStatSpellHastePercent + ], ), gemStats: DEFAULT_CASTER_GEM_STATS, @@ -85,7 +93,11 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarlock, { }, // IconInputs to include in the 'Player' section on the settings tab. - playerIconInputs: [WarlockInputs.PetInput()], + playerIconInputs: [ + WarlockInputs.PetInput(), + WarlockInputs.ArmorInput(), + WarlockInputs.DemonicSacrificeInput() + ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [BuffDebuffInputs.AttackSpeedBuff], From 0fc3034ce300094c663d6400bdc17dccbaccc61c Mon Sep 17 00:00:00 2001 From: jazz405 Date: Fri, 5 Dec 2025 19:52:28 -0500 Subject: [PATCH 07/20] updates --- sim/warlock/seed.go | 4 ++-- sim/warlock/warlock.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sim/warlock/seed.go b/sim/warlock/seed.go index 42b792521d..e027309e23 100644 --- a/sim/warlock/seed.go +++ b/sim/warlock/seed.go @@ -24,8 +24,8 @@ func (warlock *Warlock) registerSeed() { ActionID: actionID.WithTag(1), // actually 27285 SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAoE | core.SpellFlagPassiveSpell, - ClassSpellMask: WarlockSpellSeedOfCorruptionExposion, + Flags: core.SpellFlagPassiveSpell, + ClassSpellMask: WarlockSpellSeedOfCorruptionExplosion, DamageMultiplierAdditive: 1, CritMultiplier: warlock.DefaultCritMultiplier(), diff --git a/sim/warlock/warlock.go b/sim/warlock/warlock.go index 6144ca852e..5a7de95107 100644 --- a/sim/warlock/warlock.go +++ b/sim/warlock/warlock.go @@ -183,7 +183,7 @@ const ( WarlockSpellAgony WarlockSpellDrainLife WarlockSpellSeedOfCorruption - WarlockSpellSeedOfCorruptionExposion + WarlockSpellSeedOfCorruptionExplosion WarlockSpellHellfire WarlockSpellImmolationAura WarlockSpellSearingPain @@ -209,7 +209,7 @@ const ( WarlockSpellAll int64 = 1< Date: Sun, 7 Dec 2025 09:56:22 -0500 Subject: [PATCH 08/20] some spell and casting fixes --- assets/database/db.json | 8 +- proto/common.proto | 6 + sim/core/apl.go | 1 + sim/core/apl_actions_casting.go | 6 + sim/core/cast.go | 6 +- sim/core/character.go | 2 + sim/core/environment.go | 2 + sim/core/gcd.go | 2 +- sim/core/mana.go | 43 ++-- sim/core/pet.go | 146 +----------- sim/core/sim.go | 2 + sim/core/spell.go | 11 +- sim/core/spell_queueing.go | 3 +- sim/core/stats/stats.go | 1 + sim/core/unit.go | 19 +- sim/druid/treants.go | 10 +- sim/shaman/shaman.go | 2 +- sim/warlock/_apl_values.go | 17 +- sim/warlock/agony.go | 1 - sim/warlock/conflagrate.go | 2 +- sim/warlock/doom.go | 2 +- sim/warlock/immolate.go | 3 +- sim/warlock/incinerate.go | 2 +- sim/warlock/lifetap.go | 16 +- sim/warlock/{_pets.go => pets.go} | 263 ++++++++++----------- sim/warlock/seed.go | 3 +- sim/warlock/shadowbolt.go | 2 +- sim/warlock/shadowburn.go | 2 +- sim/warlock/soulfire.go | 2 +- sim/warlock/unstable_affliction.go | 1 - sim/warlock/warlock.go | 37 +-- ui/core/components/character_stats.tsx | 9 + ui/core/components/inputs/buffs_debuffs.ts | 10 - ui/core/components/inputs/consumables.ts | 20 +- ui/core/components/raid_sim_action.tsx | 2 +- ui/core/individual_sim_ui.tsx | 1 + ui/core/launched_sims.tsx | 2 +- ui/core/proto_utils/database.ts | 2 +- ui/core/proto_utils/stats.ts | 6 + ui/core/worker_pool.ts | 3 + ui/core/wowhead.ts | 2 +- ui/rogue/dps/sim.ts | 1 - ui/scss/sims/index.scss | 19 +- ui/warlock/dps/presets.ts | 17 +- ui/worker/sim_worker.ts | 2 +- 45 files changed, 300 insertions(+), 419 deletions(-) rename sim/warlock/{_pets.go => pets.go} (58%) diff --git a/assets/database/db.json b/assets/database/db.json index d232d1b573..e238b765b8 100644 --- a/assets/database/db.json +++ b/assets/database/db.json @@ -2182,9 +2182,9 @@ {"id":21866,"name":"Arcanoweave Bracers","icon":"inv_bracer_19","type":6,"armorType":1,"phase":1,"quality":3,"setName":"Arcanoweave Vestments","setId":558,"expansion":2,"sources":[{"crafted":{"profession":11,"spellId":26782}},{"crafted":{"profession":11,"spellId":26782}}],"scalingOptions":{"0":{"randPropPoints":35,"stats":{"2":31,"35":67},"ilvl":112}}}, {"id":21867,"name":"Arcanoweave Boots","icon":"inv_boots_07","type":10,"armorType":1,"phase":1,"quality":3,"setName":"Arcanoweave Vestments","setId":558,"expansion":2,"sources":[{"crafted":{"profession":11,"spellId":26783}},{"crafted":{"profession":11,"spellId":26783}}],"scalingOptions":{"0":{"randPropPoints":47,"stats":{"2":39,"35":106},"ilvl":114}}}, {"id":21868,"name":"Arcanoweave Robe","icon":"inv_chest_cloth_01","type":5,"armorType":1,"phase":1,"quality":3,"setName":"Arcanoweave Vestments","setId":558,"expansion":2,"sources":[{"crafted":{"profession":11,"spellId":26784}},{"crafted":{"profession":11,"spellId":26784}}],"scalingOptions":{"0":{"randPropPoints":63,"stats":{"2":45,"35":156},"ilvl":115}}}, -{"id":21869,"name":"Frozen Shadoweave Shoulders","icon":"inv_shoulder_25","type":3,"armorType":1,"gemSockets":[4,3],"socketBonus":[0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"phase":1,"quality":4,"setName":"Shadow's Embrace","setId":553,"expansion":2,"sources":[{"crafted":{"profession":11,"spellId":26756}},{"crafted":{"profession":11,"spellId":26756}}],"scalingOptions":{"0":{"randPropPoints":52,"stats":{"2":21,"3":15,"6":100,"35":133},"ilvl":105}}}, -{"id":21870,"name":"Frozen Shadoweave Boots","icon":"inv_boots_cloth_03","type":10,"armorType":1,"gemSockets":[4,3],"socketBonus":[0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"phase":1,"quality":4,"setName":"Shadow's Embrace","setId":553,"expansion":2,"sources":[{"crafted":{"profession":11,"spellId":26757}},{"crafted":{"profession":11,"spellId":26757}}],"scalingOptions":{"0":{"randPropPoints":52,"stats":{"2":15,"3":9,"6":114,"35":122},"ilvl":105}}}, -{"id":21871,"name":"Frozen Shadoweave Robe","icon":"inv_chest_cloth_08","type":5,"armorType":1,"gemSockets":[4,3],"socketBonus":[0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"phase":1,"quality":4,"setName":"Shadow's Embrace","setId":553,"expansion":2,"sources":[{"crafted":{"profession":11,"spellId":26758}},{"crafted":{"profession":11,"spellId":26758}}],"scalingOptions":{"0":{"randPropPoints":70,"stats":{"2":30,"3":20,"6":144,"35":178},"ilvl":105}}}, +{"id":21869,"name":"Frozen Shadoweave Shoulders","icon":"inv_shoulder_25","type":3,"armorType":1,"gemSockets":[4,3],"socketBonus":[0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"phase":1,"quality":4,"setName":"Shadow's Embrace","setId":553,"expansion":2,"sources":[{"crafted":{"profession":11,"spellId":26756}},{"crafted":{"profession":11,"spellId":26756}}],"scalingOptions":{"0":{"randPropPoints":52,"stats":{"2":21,"3":15,"9":57,"12":57,"35":133},"ilvl":105}}}, +{"id":21870,"name":"Frozen Shadoweave Boots","icon":"inv_boots_cloth_03","type":10,"armorType":1,"gemSockets":[4,3],"socketBonus":[0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"phase":1,"quality":4,"setName":"Shadow's Embrace","setId":553,"expansion":2,"sources":[{"crafted":{"profession":11,"spellId":26757}},{"crafted":{"profession":11,"spellId":26757}}],"scalingOptions":{"0":{"randPropPoints":52,"stats":{"2":15,"3":9,"9":57,"12":57,"35":122},"ilvl":105}}}, +{"id":21871,"name":"Frozen Shadoweave Robe","icon":"inv_chest_cloth_08","type":5,"armorType":1,"gemSockets":[4,3],"socketBonus":[0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"phase":1,"quality":4,"setName":"Shadow's Embrace","setId":553,"expansion":2,"sources":[{"crafted":{"profession":11,"spellId":26758}},{"crafted":{"profession":11,"spellId":26758}}],"scalingOptions":{"0":{"randPropPoints":70,"stats":{"2":30,"3":20,"9":72,"12":72,"35":178},"ilvl":105}}}, {"id":21873,"name":"Primal Mooncloth Belt","icon":"inv_belt_31","type":8,"armorType":1,"gemSockets":[4,3],"socketBonus":[0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"phase":1,"quality":4,"setName":"Primal Mooncloth","setId":554,"expansion":2,"sources":[{"crafted":{"profession":11,"spellId":26760}},{"crafted":{"profession":11,"spellId":26760}}],"scalingOptions":{"0":{"randPropPoints":55,"stats":{"3":12,"5":81,"6":27,"17":11,"35":109},"ilvl":115}}}, {"id":21874,"name":"Primal Mooncloth Shoulders","icon":"inv_shoulder_02","type":3,"armorType":1,"phase":1,"quality":4,"setName":"Primal Mooncloth","setId":554,"expansion":2,"sources":[{"crafted":{"profession":11,"spellId":26761}},{"crafted":{"profession":11,"spellId":26761}}],"scalingOptions":{"0":{"randPropPoints":52,"stats":{"3":16,"5":92,"6":31,"17":15,"35":133},"ilvl":105}}}, {"id":21875,"name":"Primal Mooncloth Robe","icon":"inv_chest_cloth_04","type":5,"armorType":1,"gemSockets":[4,3],"socketBonus":[0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"phase":1,"quality":4,"setName":"Primal Mooncloth","setId":554,"expansion":2,"sources":[{"crafted":{"profession":11,"spellId":26762}},{"crafted":{"profession":11,"spellId":26762}}],"scalingOptions":{"0":{"randPropPoints":75,"stats":{"3":20,"5":121,"6":41,"17":20,"35":202},"ilvl":120}}}, @@ -12104,4 +12104,4 @@ "spellEffects":[ {"id":696085,"spellId":17528,"type":30,"minEffectSize":449,"effectSpread":301,"resourceType":3} ] -} \ No newline at end of file +} diff --git a/proto/common.proto b/proto/common.proto index 40f5d6a78c..f8f238f853 100644 --- a/proto/common.proto +++ b/proto/common.proto @@ -449,6 +449,7 @@ enum StrengthOfEarthType { EnhancingAndCyclone = 4; } + // Buffs that affect the entire raid. // Reindexed to The Burning Crusade raid buffs. // Next index: 36 @@ -575,6 +576,8 @@ message ConsumesSpec { int32 food_id = 6; int32 explosive_id = 7; int32 conjured_id = 9; + int32 imbue_id = 10; + int32 pet_food_id = 11; } enum MobType { @@ -715,11 +718,14 @@ enum ConsumableType { ConsumableTypeExplosive = 5; ConsumableTypeBattleElixir = 6; ConsumableTypeGuardianElixir = 7; + ConsumableTypeImbue = 8; + ConsumableTypePetFood = 9; } + // Extra enum for describing which items are eligible for an enchant, when // ItemType alone is not enough. enum EnchantType { diff --git a/sim/core/apl.go b/sim/core/apl.go index a4a0d484eb..7c460e1a77 100644 --- a/sim/core/apl.go +++ b/sim/core/apl.go @@ -161,6 +161,7 @@ func (unit *Unit) newAPLRotation(config *proto.APLRotation) *APLRotation { action := rotation.newAPLAction(aplItem.Action) if action != nil { rotation.priorityList = append(rotation.priorityList, action) + rotation.priorityListIdxMap = append(rotation.priorityListIdxMap, i) } } diff --git a/sim/core/apl_actions_casting.go b/sim/core/apl_actions_casting.go index 538845460a..9d2456ac7b 100644 --- a/sim/core/apl_actions_casting.go +++ b/sim/core/apl_actions_casting.go @@ -15,13 +15,16 @@ type APLActionCastSpell struct { func (rot *APLRotation) newActionCastSpell(config *proto.APLActionCastSpell) APLActionImpl { spell := rot.GetAPLSpell(config.SpellId) + if spell == nil { return nil } + target := rot.GetTargetUnit(config.Target) if target.Get() == nil { return nil } + return &APLActionCastSpell{ spell: spell, target: target, @@ -85,9 +88,12 @@ func (rot *APLRotation) newActionChannelSpell(config *proto.APLActionChannelSpel } spell := rot.GetAPLSpell(config.SpellId) + if spell == nil { + println("GETAPLSPELL GAVE ME A NIL ONE") return nil } + println("GET APL SPELL: ", spell.ActionID.SpellID) if !spell.Flags.Matches(SpellFlagChanneled) { return nil } diff --git a/sim/core/cast.go b/sim/core/cast.go index 27666accda..eae19bd796 100644 --- a/sim/core/cast.go +++ b/sim/core/cast.go @@ -155,9 +155,9 @@ func (spell *Spell) makeCastFunc(config CastConfig) CastSuccessFunc { return spell.castFailureHelper(sim, "casting/channeling %v for %s, curTime = %s", hc.ActionID, hc.Expires-sim.CurrentTime, sim.CurrentTime) } - if !spell.CanCastDuringChannel(sim) { - return spell.castFailureHelper(sim, "cannot interrupt in-progress channel of %v with a cast of %v", spell.Unit.ChanneledDot.ActionID, spell.ActionID) - } + // if !spell.CanCastDuringChannel(sim) { + // return spell.castFailureHelper(sim, "cannot interrupt in-progress channel of %v with a cast of %v", spell.Unit.ChanneledDot.ActionID, spell.ActionID) + // } if effectiveTime := spell.CurCast.EffectiveTime(); effectiveTime != 0 { diff --git a/sim/core/character.go b/sim/core/character.go index 064c0dbec6..e26bd0e888 100644 --- a/sim/core/character.go +++ b/sim/core/character.go @@ -259,6 +259,7 @@ func (character *Character) addUniversalStatDependencies() { character.Unit.addUniversalStatDependencies() character.AddStat(stats.Health, 20-14*20) character.AddStatDependency(stats.Stamina, stats.Health, 14) + } // Returns a partially-filled PlayerStats proto for use in the CharacterStats api call. @@ -447,6 +448,7 @@ func (character *Character) FillPlayerStats(playerStats *proto.PlayerStats) { character.applyBuildPhaseAuras(CharacterBuildPhaseAll) playerStats.FinalStats = &proto.UnitStats{ + // for i, stat Stats: character.GetStats().ToProtoArray(), PseudoStats: character.GetPseudoStatsProto(), ApiVersion: GetCurrentProtoVersion(), diff --git a/sim/core/environment.go b/sim/core/environment.go index fd01847495..6e96d08ec6 100644 --- a/sim/core/environment.go +++ b/sim/core/environment.go @@ -209,6 +209,8 @@ func (env *Environment) finalize(raidProto *proto.Raid, _ *proto.Encounter, raid for partyIdx, party := range env.Raid.Parties { for _, player := range party.Players { character := player.GetCharacter() + //fillplayerstats check that later + println("fillplayerstats in environment.go") character.FillPlayerStats(raidStats.Parties[partyIdx].Players[character.PartyIndex]) } } diff --git a/sim/core/gcd.go b/sim/core/gcd.go index 0ec5cf35fb..c91ac23ab2 100644 --- a/sim/core/gcd.go +++ b/sim/core/gcd.go @@ -74,7 +74,7 @@ func (unit *Unit) ReactToEvent(sim *Simulation, randomizeReactionTime bool) { newEvaluationTime := sim.CurrentTime + unit.ReactionTime if randomizeReactionTime { - newEvaluationTime = sim.CurrentTime + DurationFromSeconds(sim.RandomFloat("Reaction Time") * 2 * unit.ReactionTime.Seconds()) + newEvaluationTime = sim.CurrentTime + DurationFromSeconds(sim.RandomFloat("Reaction Time")*2*unit.ReactionTime.Seconds()) } if unit.NextRotationActionAt() > newEvaluationTime { diff --git a/sim/core/mana.go b/sim/core/mana.go index a3dc7830df..ea4f0c079b 100644 --- a/sim/core/mana.go +++ b/sim/core/mana.go @@ -37,24 +37,23 @@ type manaBar struct { // as well as enable the mana gain action to regenerate mana. // It will then enable mana gain metrics for reporting. func (character *Character) EnableManaBar() { - character.EnableManaBarWithModifier(1.0) + character.EnableManaBarWithModifier() character.Unit.SetCurrentPowerBar(ManaBar) } -func (character *Character) EnableManaBarWithModifier(modifier float64) { - - // Starting with cataclysm you get mp5 equal 5% of your base mana - character.AddStat(stats.MP5, character.baseStats[stats.Mana]*0.05) +func (character *Character) EnableManaBarWithModifier() { if character.Unit.Type == PlayerUnit { + + // Patch 2.4 changed mp5 formula -> SpiritIntellectRegen = 5 * 0.00932715221261 * sqrt(Intellect) * Spirit + mp5 := 5 * 0.00932715221261 * math.Sqrt(character.stats[stats.Intellect]) * character.stats[stats.Spirit] + character.AddStat(stats.MP5, mp5) + // Pets might have different scaling so let them handle their scaling character.AddStatDependency(stats.Intellect, stats.SpellCritPercent, CritPerIntMaxLevel[character.Class]) - // Starting with cataclysm 1 intellect now provides 1 spell power - character.AddStatDependency(stats.Intellect, stats.SpellPower, 1.0) + character.AddStatDependency(stats.Intellect, stats.Mana, 15) - // first 10 int should not count so remove them - character.AddStat(stats.SpellPower, -10) } // Not a real spell, just holds metrics from mana gain threat. @@ -62,12 +61,10 @@ func (character *Character) EnableManaBarWithModifier(modifier float64) { ActionID: ActionID{OtherID: proto.OtherAction_OtherActionManaGain}, }) - character.manaCombatMetrics = character.NewManaMetrics(ActionID{OtherID: proto.OtherAction_OtherActionManaRegen, Tag: 1}) - character.manaNotCombatMetrics = character.NewManaMetrics(ActionID{OtherID: proto.OtherAction_OtherActionManaRegen, Tag: 2}) - character.BaseMana = character.GetBaseStats()[stats.Mana] character.Unit.manaBar.unit = &character.Unit - character.Unit.manaBar.manaRegenMultiplier = 1.0 + character.manaCombatMetrics = character.NewManaMetrics(ActionID{OtherID: proto.OtherAction_OtherActionManaRegen, Tag: 1}) + character.manaNotCombatMetrics = character.NewManaMetrics(ActionID{OtherID: proto.OtherAction_OtherActionManaRegen, Tag: 2}) } func (unit *Unit) HasManaBar() bool { @@ -308,14 +305,19 @@ func (mb *manaBar) reset() { func (mb *manaBar) IsOOM() bool { return mb.waitingForMana != 0 } -func (mb *manaBar) StartOOMEvent(sim *Simulation, requiredMana float64) { +func (mb *manaBar) StartOOMEvent(sim *Simulation, requiredMana float64, isPet bool) { mb.waitingForManaStartTime = sim.CurrentTime mb.waitingForMana = requiredMana - mb.unit.Metrics.MarkOOM(sim) + if !isPet { + mb.unit.Metrics.MarkOOM(sim) + } + } -func (mb *manaBar) EndOOMEvent(sim *Simulation) { +func (mb *manaBar) EndOOMEvent(sim *Simulation, isPet bool) { eventDuration := sim.CurrentTime - mb.waitingForManaStartTime - mb.unit.Metrics.AddOOMTime(sim, eventDuration) + if !isPet { + mb.unit.Metrics.AddOOMTime(sim, eventDuration) + } mb.waitingForManaStartTime = 0 mb.waitingForMana = 0 } @@ -347,18 +349,19 @@ func newManaCost(spell *Spell, options ManaCostOptions) *SpellCost { func (mc *ManaCost) MeetsRequirement(sim *Simulation, spell *Spell) bool { spell.CurCast.Cost = spell.Cost.GetCurrentCost() meetsRequirement := spell.Unit.CurrentMana() >= spell.CurCast.Cost - + isPet := spell.Unit.Type == PetUnit if spell.CurCast.Cost > 0 { if meetsRequirement { if spell.Unit.IsOOM() { - spell.Unit.EndOOMEvent(sim) + spell.Unit.EndOOMEvent(sim, isPet) } } else { if spell.Unit.IsOOM() { // Continuation of OOM event. spell.Unit.waitingForMana = min(spell.Unit.waitingForMana, spell.CurCast.Cost) } else { - spell.Unit.StartOOMEvent(sim, spell.CurCast.Cost) + + spell.Unit.StartOOMEvent(sim, spell.CurCast.Cost, isPet) } } } diff --git a/sim/core/pet.go b/sim/core/pet.go index 50c099c477..cbf1a62885 100644 --- a/sim/core/pet.go +++ b/sim/core/pet.go @@ -2,7 +2,6 @@ package core import ( "fmt" - "math" "slices" "time" @@ -30,13 +29,10 @@ type PetConfig struct { BaseStats stats.Stats // Hit and Expertise are always inherited by combining the owners physical hit and expertise, then halving it // For casters this will automatically give spell hit cap at 7.5% physical hit and exp - NonHitExpStatInheritance PetStatInheritance - EnabledOnStart bool - IsGuardian bool - HasDynamicMeleeSpeedInheritance bool - HasDynamicCastSpeedInheritance bool - HasResourceRegenInheritance bool - StartsAtOwnerDistance bool + NonHitExpStatInheritance PetStatInheritance + EnabledOnStart bool + IsGuardian bool + StartsAtOwnerDistance bool } // Pet is an extension of Character, for any entity created by a player that can @@ -59,30 +55,11 @@ type Pet struct { pendingStatInheritance stats.Stats statInheritanceAction *PendingAction - // In MoP pets inherit their owners melee speed and cast speed - // rather than having auras such as Heroism being applied to them. - dynamicMeleeSpeedInheritance PetSpeedInheritance - inheritedMeleeSpeedMultiplier float64 - dynamicCastSpeedInheritance PetSpeedInheritance - inheritedCastSpeedMultiplier float64 - - // If true the pet will automatically inherit the owner's melee speed - hasDynamicMeleeSpeedInheritance bool - // If true the pet will automatically inherit the owner's cast speed - hasDynamicCastSpeedInheritance bool - // If true the pet will automatically inherit the owner's regen speed multiplier - hasResourceRegenInheritance bool - isReset bool // Some pets expire after a certain duration. This is the pending action that disables // the pet on expiration. timeoutAction *PendingAction - - // Examples: - // DK Raise Dead is doing its whole RP thing by climbing out of the ground before attacking. - // Monk clones Rush towards targets before attacking. - startAttackDelay time.Duration } func NewPet(config PetConfig) Pet { @@ -108,15 +85,10 @@ func NewPet(config PetConfig) Pet { PartyIndex: config.Owner.PartyIndex, baseStats: config.BaseStats, }, - Owner: config.Owner, - statInheritance: makeStatInheritanceFunc(config.NonHitExpStatInheritance), - hasDynamicMeleeSpeedInheritance: config.HasDynamicMeleeSpeedInheritance, - inheritedMeleeSpeedMultiplier: 1, - hasDynamicCastSpeedInheritance: config.HasDynamicCastSpeedInheritance, - inheritedCastSpeedMultiplier: 1, - hasResourceRegenInheritance: config.HasResourceRegenInheritance, - enabledOnStart: config.EnabledOnStart, - isGuardian: config.IsGuardian, + Owner: config.Owner, + statInheritance: makeStatInheritanceFunc(config.NonHitExpStatInheritance), + enabledOnStart: config.EnabledOnStart, + isGuardian: config.IsGuardian, } pet.GCD = pet.NewTimer() @@ -133,9 +105,7 @@ func NewPet(config PetConfig) Pet { } func (pet *Pet) Initialize() { - if pet.hasResourceRegenInheritance { - pet.enableResourceRegenInheritance() - } + } func makeStatInheritanceFunc(nonHitExpStatInheritance PetStatInheritance) PetStatInheritance { @@ -261,21 +231,9 @@ func (pet *Pet) Enable(sim *Simulation, petAgent PetAgent) { pet.OnPetEnable(sim) } - if pet.hasDynamicMeleeSpeedInheritance { - pet.enableDynamicMeleeSpeed(sim) - } - - if pet.hasDynamicCastSpeedInheritance { - pet.enableDynamicCastSpeed(sim) - } - - pet.SetGCDTimer(sim, max(0, sim.CurrentTime+pet.startAttackDelay, sim.CurrentTime)) + pet.SetGCDTimer(sim, max(0, sim.CurrentTime, sim.CurrentTime)) pet.AutoAttacks.EnableAutoSwing(sim) - if pet.startAttackDelay > 0 { - pet.AutoAttacks.StopMeleeUntil(sim, max(SpellBatchWindow, sim.CurrentTime+pet.startAttackDelay)-pet.AutoAttacks.MainhandSwingSpeed()) - } - if sim.Log != nil { pet.Log(sim, "Pet stats: %s", pet.GetStats().FlatString()) pet.Log(sim, "Pet inherited stats: %s", pet.ApplyStatDependencies(pet.inheritedStats).FlatString()) @@ -288,26 +246,15 @@ func (pet *Pet) Enable(sim *Simulation, petAgent PetAgent) { // make sure to reset it to refresh focus pet.focusBar.reset(sim) pet.focusBar.enable(sim, sim.CurrentTime) - if pet.hasResourceRegenInheritance { - pet.focusBar.focusRegenMultiplier *= pet.Owner.PseudoStats.AttackSpeedMultiplier - } } if pet.HasEnergyBar() { // make sure to reset it to refresh energy pet.energyBar.reset(sim) pet.energyBar.enable(sim, sim.CurrentTime) - if pet.hasResourceRegenInheritance { - pet.energyBar.energyRegenMultiplier *= pet.Owner.PseudoStats.AttackSpeedMultiplier - } } } -func (pet *Pet) EnableWithStartAttackDelay(sim *Simulation, petAgent PetAgent, startAttackDelay time.Duration) { - pet.startAttackDelay = startAttackDelay - pet.Enable(sim, petAgent) -} - // Helper for enabling a pet that will expire after a certain duration. func (pet *Pet) EnableWithTimeout(sim *Simulation, petAgent PetAgent, petDuration time.Duration) { pet.Enable(sim, petAgent) @@ -325,77 +272,6 @@ func (pet *Pet) SetTimeoutAction(sim *Simulation, duration time.Duration) { sim.AddPendingAction(pet.timeoutAction) } -func (pet *Pet) SetStartAttackDelay(startAttackDelay time.Duration) { - pet.startAttackDelay = startAttackDelay -} - -func (pet *Pet) enableDynamicMeleeSpeed(sim *Simulation) { - if slices.Contains(pet.Owner.DynamicMeleeSpeedPets, pet) { - panic("Pet already present in dynamic melee speed pet list!") - } - - if math.Abs(pet.inheritedMeleeSpeedMultiplier-1) > 1e-14 { - panic(fmt.Sprintf("Pet melee speed multiplier was not reset properly! Current inherited value = %.17f", pet.inheritedMeleeSpeedMultiplier)) - } - - pet.dynamicMeleeSpeedInheritance = func(sim *Simulation, ownerSpeedMultiplier float64) { - pet.inheritedMeleeSpeedMultiplier *= ownerSpeedMultiplier - pet.MultiplyMeleeSpeed(sim, ownerSpeedMultiplier) - } - - pet.dynamicMeleeSpeedInheritance(sim, pet.Owner.PseudoStats.MeleeSpeedMultiplier) - pet.dynamicMeleeSpeedInheritance(sim, pet.Owner.PseudoStats.AttackSpeedMultiplier) - pet.Owner.DynamicMeleeSpeedPets = append(pet.Owner.DynamicMeleeSpeedPets, pet) -} - -func (pet *Pet) resetDynamicMeleeSpeed(sim *Simulation) { - if pet.dynamicMeleeSpeedInheritance == nil { - return - } - - if idx := slices.Index(pet.Owner.DynamicMeleeSpeedPets, pet); idx != -1 { - pet.Owner.DynamicMeleeSpeedPets = removeBySwappingToBack(pet.Owner.DynamicMeleeSpeedPets, idx) - } else { - panic("Pet not present in dynamic melee speed pet list!") - } - - pet.dynamicMeleeSpeedInheritance(sim, 1/pet.inheritedMeleeSpeedMultiplier) - pet.dynamicMeleeSpeedInheritance = nil -} - -func (pet *Pet) enableDynamicCastSpeed(sim *Simulation) { - if slices.Contains(pet.Owner.DynamicCastSpeedPets, pet) { - panic("Pet already present in dynamic cast speed pet list!") - } - - if math.Abs(pet.inheritedCastSpeedMultiplier-1) > 1e-14 { - panic(fmt.Sprintf("Pet cast speed multiplier was not reset properly! Current inherited value = %.17f", pet.inheritedCastSpeedMultiplier)) - } - - pet.dynamicCastSpeedInheritance = func(sim *Simulation, ownerSpeedMultiplier float64) { - pet.inheritedCastSpeedMultiplier *= ownerSpeedMultiplier - pet.MultiplyCastSpeed(sim, ownerSpeedMultiplier) - } - - pet.dynamicCastSpeedInheritance(sim, pet.Owner.PseudoStats.CastSpeedMultiplier) - pet.Owner.DynamicCastSpeedPets = append(pet.Owner.DynamicCastSpeedPets, pet) -} - -func (pet *Pet) resetDynamicCastSpeed(sim *Simulation) { - if pet.dynamicCastSpeedInheritance == nil { - return - } - - if idx := slices.Index(pet.Owner.DynamicCastSpeedPets, pet); idx != -1 { - pet.Owner.DynamicCastSpeedPets = removeBySwappingToBack(pet.Owner.DynamicCastSpeedPets, idx) - } else { - panic("Pet not present in dynamic cast speed pet list!") - } - - pet.dynamicCastSpeedInheritance(sim, 1/pet.inheritedCastSpeedMultiplier) - pet.dynamicCastSpeedInheritance = nil -} - func (pet *Pet) enableResourceRegenInheritance() { if !slices.Contains(pet.Owner.RegenInheritancePets, pet) { pet.Owner.RegenInheritancePets = append(pet.Owner.RegenInheritancePets, pet) @@ -411,8 +287,6 @@ func (pet *Pet) Disable(sim *Simulation) { } pet.resetDynamicStats(sim) - pet.resetDynamicMeleeSpeed(sim) - pet.resetDynamicCastSpeed(sim) pet.CancelGCDTimer(sim) pet.focusBar.disable(sim) pet.energyBar.disable(sim) diff --git a/sim/core/sim.go b/sim/core/sim.go index 472ead2f68..4fc9ddb783 100644 --- a/sim/core/sim.go +++ b/sim/core/sim.go @@ -128,6 +128,8 @@ func runSim(rsr *proto.RaidSimRequest, progress chan *proto.ProgressMetrics, ski } errStr += "\nStack Trace:\n" + string(debug.Stack()) + + println(errStr) result = &proto.RaidSimResult{ Error: &proto.ErrorOutcome{Message: errStr}, } diff --git a/sim/core/spell.go b/sim/core/spell.go index e7338b7731..a05e634afc 100644 --- a/sim/core/spell.go +++ b/sim/core/spell.go @@ -593,7 +593,7 @@ func (spell *Spell) CanCast(sim *Simulation, target *Unit) bool { } // While casting or channeling, no other action is possible - if (spell.Unit.Hardcast.Expires > sim.CurrentTime) || !spell.CanCastDuringChannel(sim) { + if spell.Unit.Hardcast.Expires > sim.CurrentTime { //if sim.Log != nil { // sim.Log("Cant cast because already casting/channeling") //} @@ -626,7 +626,9 @@ func (spell *Spell) CanCast(sim *Simulation, target *Unit) bool { // Example: Metamorphosis drains 4 Demonic Fury every second. // This means at the end of the cast you could end up not meeting the casting requirements. func (spell *Spell) CanCompleteCast(sim *Simulation, target *Unit, logCastFailure bool) bool { + if !spell.Unit.IsEnabled() { + println("Unit is not Enabled") if logCastFailure { return spell.castFailureHelper(sim, "unit is disabled") } @@ -659,9 +661,9 @@ func (spell *Spell) CanCompleteCast(sim *Simulation, target *Unit, logCastFailur if spell.Cost != nil { if !spell.Cost.MeetsRequirement(sim, spell) { - //if sim.Log != nil { - // sim.Log("Cant cast because of resource cost") - //} + if sim.Log != nil { + sim.Log("Cant cast because of resource cost") + } if logCastFailure { return spell.castFailureHelper(sim, spell.Cost.CostFailureReason(sim, spell)) } @@ -683,6 +685,7 @@ func (spell *Spell) Cast(sim *Simulation, target *Unit) bool { if target == nil { target = spell.Unit.CurrentTarget } + return spell.castFn(sim, target) } diff --git a/sim/core/spell_queueing.go b/sim/core/spell_queueing.go index 7f17195976..98f12ce169 100644 --- a/sim/core/spell_queueing.go +++ b/sim/core/spell_queueing.go @@ -89,7 +89,7 @@ func (spell *Spell) CanQueue(sim *Simulation, target *Unit) bool { } // Apply SQW leniency to any pending hardcasts - if (spell.Unit.Hardcast.Expires > sim.CurrentTime+MaxSpellQueueWindow) || !spell.CanCastDuringChannel(sim) { + if spell.Unit.Hardcast.Expires > sim.CurrentTime+MaxSpellQueueWindow { return false } @@ -125,6 +125,7 @@ func (spell *Spell) CastOrQueue(sim *Simulation, target *Unit) { // Schedule the cast to go off without delay spell.Unit.QueueSpell(sim, spell, target, queueTime) } else { + println("I can not cast ", spell.ActionID.SpellID) // Fallback to make sure there is always log output spell.Cast(sim, target) } diff --git a/sim/core/stats/stats.go b/sim/core/stats/stats.go index 331d63ad0c..1dd96ea54c 100644 --- a/sim/core/stats/stats.go +++ b/sim/core/stats/stats.go @@ -499,6 +499,7 @@ type PseudoStats struct { HealingTakenMultiplier float64 // All healing sources including self-healing ExternalHealingTakenMultiplier float64 // Modulates the output of the individual tank sim healing model MovementSpeedMultiplier float64 // Multiplier for movement speed, default to 1. Player base movement 7 yards/s. All effects affecting movements are multipliers. + SelfHealingMultiplier float64 // Healing from spells and abilities, only-self } func NewPseudoStats() PseudoStats { diff --git a/sim/core/unit.go b/sim/core/unit.go index c81f9b696e..0537cb5623 100644 --- a/sim/core/unit.go +++ b/sim/core/unit.go @@ -126,10 +126,8 @@ type Unit struct { // Pets owned by this Unit. PetAgents []PetAgent - DynamicStatsPets []*Pet - DynamicMeleeSpeedPets []*Pet - DynamicCastSpeedPets []*Pet - RegenInheritancePets []*Pet + DynamicStatsPets []*Pet + RegenInheritancePets []*Pet // AutoAttacks is the manager for auto attack swings. // Must be enabled to use, with "EnableAutoAttacks()". @@ -495,10 +493,6 @@ func (unit *Unit) updateCastSpeed() { func (unit *Unit) MultiplyCastSpeed(sim *Simulation, amount float64) { unit.PseudoStats.CastSpeedMultiplier *= amount - unit.Env.TriggerDelayedPetInheritance(sim, unit.DynamicCastSpeedPets, func(sim *Simulation, pet *Pet) { - pet.dynamicCastSpeedInheritance(sim, amount) - }) - unit.updateCastSpeed() } @@ -561,10 +555,6 @@ func (unit *Unit) MultiplyMeleeSpeed(sim *Simulation, amount float64) { unit.PseudoStats.MeleeSpeedMultiplier *= amount unit.updateMeleeAttackSpeed() - unit.Env.TriggerDelayedPetInheritance(sim, unit.DynamicMeleeSpeedPets, func(sim *Simulation, pet *Pet) { - pet.dynamicMeleeSpeedInheritance(sim, amount) - }) - unit.AutoAttacks.UpdateSwingTimers(sim) } @@ -625,10 +615,6 @@ func (unit *Unit) MultiplyAttackSpeed(sim *Simulation, amount float64) { unit.updateAttackSpeed() unit.updateMeleeAndRangedHaste() - unit.Env.TriggerDelayedPetInheritance(sim, unit.DynamicMeleeSpeedPets, func(sim *Simulation, pet *Pet) { - pet.dynamicMeleeSpeedInheritance(sim, amount) - }) - unit.AutoAttacks.UpdateSwingTimers(sim) } @@ -762,7 +748,6 @@ func (unit *Unit) reset(sim *Simulation, _ Agent) { } unit.DynamicStatsPets = unit.DynamicStatsPets[:0] - unit.DynamicMeleeSpeedPets = unit.DynamicMeleeSpeedPets[:0] clear(unit.SpellsInFlight) if unit.Type != PetUnit { diff --git a/sim/druid/treants.go b/sim/druid/treants.go index 8f6a922690..b678acc5d0 100644 --- a/sim/druid/treants.go +++ b/sim/druid/treants.go @@ -46,11 +46,11 @@ type TreantConfig struct { func (druid *Druid) NewDefaultTreant(config TreantConfig) *DefaultTreantImpl { treant := &DefaultTreantImpl{ Pet: core.NewPet(core.PetConfig{ - Name: "Treant", - Owner: &druid.Character, - NonHitExpStatInheritance: config.NonHitExpStatInheritance, - HasDynamicMeleeSpeedInheritance: true, - HasDynamicCastSpeedInheritance: true, + Name: "Treant", + Owner: &druid.Character, + NonHitExpStatInheritance: config.NonHitExpStatInheritance, + // HasDynamicMeleeSpeedInheritance: true, + // HasDynamicCastSpeedInheritance: true, }), } diff --git a/sim/shaman/shaman.go b/sim/shaman/shaman.go index 92d2d90282..c6bb9f987d 100644 --- a/sim/shaman/shaman.go +++ b/sim/shaman/shaman.go @@ -56,7 +56,7 @@ func NewShaman(character *core.Character, talents string, selfBuffs SelfBuffs, t // Add Shaman stat dependencies shaman.AddStatDependency(stats.BonusArmor, stats.Armor, 1) shaman.AddStatDependency(stats.Agility, stats.PhysicalCritPercent, core.CritPerAgiMaxLevel[shaman.Class]) - shaman.EnableManaBarWithModifier(1.0) + shaman.EnableManaBarWithModifier() shaman.AddStatDependency(stats.Agility, stats.AttackPower, 2.0) shaman.AddStat(stats.AttackPower, -20) diff --git a/sim/warlock/_apl_values.go b/sim/warlock/_apl_values.go index 668d2687fe..f22df36285 100644 --- a/sim/warlock/_apl_values.go +++ b/sim/warlock/_apl_values.go @@ -43,19 +43,12 @@ func (value *APLValueWarlockShouldRecastDrainSoul) GetBool(sim *core.Simulation) warlock.UnstableAffliction.CastTime() curseRefresh := max( - warlock.BaneOfAgony.CurDot().RemainingDuration(sim), - warlock.BaneOfDoom.CurDot().RemainingDuration(sim), + warlock.CurseOfAgony.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.BaneOfAgony.CastTime() - - hauntRefresh := 1000 * time.Second - if warlock.HauntDebuffAuras != nil { - hauntRefresh = warlock.HauntDebuffAuras.Get(warlock.CurrentTarget).RemainingDuration(sim) - - warlock.Haunt.CastTime() - - warlock.Haunt.TravelTime() - } + // warlock.CurseOfTonguesAuras.Get(warlock.CurrentTarget).RemainingDuration(sim), + // warlock.CurseOfWeaknessAuras.Get(warlock.CurrentTarget).RemainingDuration(sim), + ) - warlock.CurseOfAgony.CastTime() timeUntilRefresh := min(uaRefresh, curseRefresh) diff --git a/sim/warlock/agony.go b/sim/warlock/agony.go index 238fc40209..173dcdce06 100644 --- a/sim/warlock/agony.go +++ b/sim/warlock/agony.go @@ -20,7 +20,6 @@ func (warlock *Warlock) registerCurseOfAgony() { ThreatMultiplier: 1, DamageMultiplier: 1, BonusCoefficient: agonyCoeff, - CritMultiplier: warlock.DefaultCritMultiplier(), Cast: core.CastConfig{ DefaultCast: core.Cast{ diff --git a/sim/warlock/conflagrate.go b/sim/warlock/conflagrate.go index 0f647be53d..435288897c 100644 --- a/sim/warlock/conflagrate.go +++ b/sim/warlock/conflagrate.go @@ -26,7 +26,7 @@ func (warlock *Warlock) registerConflagrate() { }, }, DamageMultiplier: 1.0, - CritMultiplier: warlock.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultSpellCritMultiplier(), ThreatMultiplier: 1, BonusCoefficient: conflagrateCoeff, RechargeTime: time.Second * 10, diff --git a/sim/warlock/doom.go b/sim/warlock/doom.go index 4eba990b38..2fd8ece110 100644 --- a/sim/warlock/doom.go +++ b/sim/warlock/doom.go @@ -24,7 +24,7 @@ func (warlock *Warlock) registerCurseOfDoom() { }, DamageMultiplierAdditive: 1, - CritMultiplier: warlock.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultSpellCritMultiplier(), ThreatMultiplier: 1, Dot: core.DotConfig{ diff --git a/sim/warlock/immolate.go b/sim/warlock/immolate.go index 94049b13bd..360db0b9d8 100644 --- a/sim/warlock/immolate.go +++ b/sim/warlock/immolate.go @@ -26,7 +26,7 @@ func (warlock *Warlock) registerImmolate() { }, DamageMultiplier: 1, - CritMultiplier: warlock.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultSpellCritMultiplier(), ThreatMultiplier: 1, BonusCoefficient: immolateCoeff, @@ -48,7 +48,6 @@ func (warlock *Warlock) registerImmolate() { Flags: core.SpellFlagPassiveSpell, DamageMultiplier: 1, - CritMultiplier: warlock.DefaultCritMultiplier(), Dot: core.DotConfig{ Aura: core.Aura{ diff --git a/sim/warlock/incinerate.go b/sim/warlock/incinerate.go index 90f8a9a5b2..1f3ce693c5 100644 --- a/sim/warlock/incinerate.go +++ b/sim/warlock/incinerate.go @@ -27,7 +27,7 @@ func (warlock *Warlock) registerIncinerate() { }, DamageMultiplierAdditive: 1, - CritMultiplier: warlock.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultSpellCritMultiplier(), ThreatMultiplier: 1, BonusCoefficient: incinerateCoeff, diff --git a/sim/warlock/lifetap.go b/sim/warlock/lifetap.go index 1791d962c2..2dbf3245e1 100644 --- a/sim/warlock/lifetap.go +++ b/sim/warlock/lifetap.go @@ -13,11 +13,11 @@ func (warlock *Warlock) registerLifeTap() { healthCost := 582.0 baseRestore := healthCost * (1.0 + 0.1*float64(warlock.Talents.ImprovedLifeTap)) - // petRestore := 0.3333 * float64(warlock.Talents.ManaFeed) - // var petManaMetrics []*core.ResourceMetrics - // if warlock.Talents.ManaFeed > 0 { - // petManaMetrics = append(petManaMetrics, warlock.ActivePet.NewManaMetrics(actionID)) - // } + petRestore := 0.3333 * float64(warlock.Talents.ManaFeed) + var petManaMetrics []*core.ResourceMetrics + if warlock.Talents.ManaFeed > 0 { + petManaMetrics = append(petManaMetrics, warlock.ActivePet.NewManaMetrics(actionID)) + } warlock.RegisterSpell(core.SpellConfig{ ActionID: actionID, @@ -40,9 +40,9 @@ func (warlock *Warlock) registerLifeTap() { warlock.RemoveHealth(sim, healthCost) warlock.AddMana(sim, restore, manaMetrics) - // if warlock.Talents.ManaFeed > 0 { - // warlock.ActivePet.AddMana(sim, restore*petRestore, petManaMetrics[0]) - // } + if warlock.Talents.ManaFeed > 0 { + warlock.ActivePet.AddMana(sim, restore*petRestore, petManaMetrics[0]) + } }, }) } diff --git a/sim/warlock/_pets.go b/sim/warlock/pets.go similarity index 58% rename from sim/warlock/_pets.go rename to sim/warlock/pets.go index 3f8466050c..6ed7f10fc9 100644 --- a/sim/warlock/_pets.go +++ b/sim/warlock/pets.go @@ -1,7 +1,6 @@ package warlock import ( - "math" "time" "github.com/wowsims/tbc/sim/core" @@ -13,52 +12,117 @@ type WarlockPet struct { core.Pet AutoCastAbilities []*core.Spell - MinEnergy float64 // The minimum amount of energy needed to the AI casts a spell + MinMana float64 // The minimum amount of energy needed to the AI casts a spell + ManaIntRatio float64 } var petBaseStats = map[proto.WarlockOptions_Summon]*stats.Stats{ + // stam 101+171 + // int 327+124 + //spirit 263 + //attack power 135+289 + //Damage 2.0s, 136-173 (77.1dps) + //mana 2988 proto.WarlockOptions_Imp: { - stats.Health: 48312.8, - stats.Armor: 19680, + stats.Mana: 10000, + stats.Stamina: 101, + stats.Strength: 153, //fix these later + stats.Agility: 108, //fix these later + stats.Intellect: 327, + stats.Spirit: 263, + stats.AttackPower: 135, + stats.MP5: 123, }, + //str 153 + //agi 108 + // stam 280+117 + // int 133+124 + //spirit 122 + //ap 286+289 + //dmg 115-144 + //armor proto.WarlockOptions_Voidwalker: { - stats.Health: 120900.8, - stats.Armor: 19680, + stats.Stamina: 280, + stats.Strength: 153, + stats.Agility: 108, + stats.Intellect: 133, + stats.Spirit: 122, + stats.AttackPower: 286, + stats.MP5: 48, }, + // str 154 + // agi 108 + //stam 280+117 + //int 133+124 + //spirit 122 + //ap 286+289 + //DMG 173-216 97.2 (2.0) + // mana 3862 proto.WarlockOptions_Succubus: { - stats.Health: 84606.8, - stats.Armor: 12568, - }, - proto.WarlockOptions_Felhunter: { - stats.Health: 84606.8, - stats.Armor: 19680, + stats.Mana: 10000, + stats.Stamina: 280, + stats.Strength: 154, + stats.Agility: 108, + stats.Intellect: 133, + stats.Spirit: 122, + stats.AttackPower: 286, + stats.MP5: 48, }, + // str 153 + //agi 108 + //stam 280+117 + //int 133+124 + //spirit 122 + //ap286+289 + //dmg 132-164 + //mana 3862 + proto.WarlockOptions_Felhunter: {}, + // str 153 + //agi 108 + //stam 280+117 + //int 133+124 + //spiri 122 + //ap286+289 + //dmg 216+269 + //mana 3862 proto.WarlockOptions_Felguard: { - stats.Health: 84606.8, - stats.Armor: 12568, + stats.Stamina: 280, + stats.Strength: 153, + stats.Agility: 108, + stats.Intellect: 133, + stats.Spirit: 122, + stats.AttackPower: 286, + stats.MP5: 48, }, } -func (warlock *Warlock) SimplePetStatInheritanceWithScale(apScale float64) core.PetStatInheritance { +func (warlock *Warlock) SimplePetStatInheritanceWithScale() core.PetStatInheritance { return func(ownerStats stats.Stats) stats.Stats { - return stats.Stats{ - stats.Stamina: ownerStats[stats.Stamina] * 1.0 / 3.0, - stats.SpellPower: ownerStats[stats.SpellPower], // All pets inherit spell 1:1 - stats.HasteRating: ownerStats[stats.HasteRating], - stats.PhysicalCritPercent: ownerStats[stats.SpellCritPercent], // All pets seem to use spell crit for Physical abilities - stats.SpellCritPercent: ownerStats[stats.SpellCritPercent], + const resistScale = 0.4 + const baseStatScale = 0.3 - stats.AttackPower: ownerStats[stats.SpellPower] * apScale, + return stats.Stats{ + stats.Stamina: ownerStats[stats.Stamina] * 0.3, + stats.Intellect: ownerStats[stats.Intellect] * 0.3, + stats.Armor: ownerStats[stats.Armor] * 0.35, + stats.SpellPenetration: ownerStats[stats.SpellPenetration], // not 100% on this one + stats.SpellPower: max(ownerStats[stats.ShadowPower], ownerStats[stats.FirePower]) * 0.15, + stats.AttackPower: max(ownerStats[stats.ShadowPower], ownerStats[stats.FirePower]) * 0.57, + stats.ArcaneResistance: ownerStats[stats.ArcaneResistance] * resistScale, + stats.FireResistance: ownerStats[stats.FireResistance] * resistScale, + stats.FrostResistance: ownerStats[stats.FrostResistance] * resistScale, + stats.NatureResistance: ownerStats[stats.NatureResistance] * resistScale, + stats.ShadowResistance: ownerStats[stats.ShadowResistance] * resistScale, } } } -func ScaledAutoAttackConfig(swingSpeed float64) *core.AutoAttackOptions { +func AutoAttackConfig(min float64, max float64) *core.AutoAttackOptions { return &core.AutoAttackOptions{ MainHand: core.Weapon{ - BaseDamageMin: math.Floor(core.ClassBaseScaling[proto.Class_ClassWarlock]), - BaseDamageMax: math.Ceil(core.ClassBaseScaling[proto.Class_ClassWarlock]), - SwingSpeed: swingSpeed, + BaseDamageMin: float64(min), + BaseDamageMax: float64(max), + SwingSpeed: 2.0, CritMultiplier: 2, }, AutoSwingMelee: true, @@ -75,20 +139,16 @@ func (warlock *Warlock) makePet( ) *WarlockPet { pet := &WarlockPet{ Pet: core.NewPet(core.PetConfig{ - Name: name, - Owner: &warlock.Character, - BaseStats: baseStats, - NonHitExpStatInheritance: statInheritance, - EnabledOnStart: enabledOnStart, - IsGuardian: isGuardian, - HasDynamicMeleeSpeedInheritance: true, - HasDynamicCastSpeedInheritance: true, - HasResourceRegenInheritance: true, + Name: name, + Owner: &warlock.Character, + BaseStats: baseStats, + NonHitExpStatInheritance: statInheritance, + EnabledOnStart: enabledOnStart, + IsGuardian: isGuardian, }), } // set pet class for proper scaling values - pet.Class = pet.Owner.Class if enabledOnStart { warlock.RegisterResetEffect(func(sim *core.Simulation) { warlock.ActivePet = pet @@ -106,12 +166,7 @@ func (warlock *Warlock) setPetOptions(petAgent core.PetAgent, aaOptions *core.Au pet.EnableAutoAttacks(petAgent, *aaOptions) } - pet.EnableEnergyBar(core.EnergyBarOptions{ - MaxEnergy: 200, - UnitClass: proto.Class_ClassWarlock, - HasHasteRatingScaling: true, - }) - + pet.EnableManaBar() warlock.AddPet(petAgent) } @@ -131,7 +186,7 @@ func (warlock *Warlock) registerImp() *WarlockPet { func (warlock *Warlock) registerImpWithName(name string, enabledOnStart bool, isGuardian bool) *WarlockPet { pet := warlock.RegisterPet(proto.WarlockOptions_Imp, 0, 0, name, enabledOnStart, isGuardian) pet.registerFireboltSpell() - pet.MinEnergy = 120 + pet.MinMana = 145 return pet } @@ -143,8 +198,8 @@ func (warlock *Warlock) registerFelHunter() *WarlockPet { func (warlock *Warlock) registerFelHunterWithName(name string, enabledOnStart bool, isGuardian bool) *WarlockPet { pet := warlock.RegisterPet(proto.WarlockOptions_Felhunter, 2, 3.5, name, enabledOnStart, isGuardian) - pet.registerShadowBiteSpell() - pet.MinEnergy = 100 + //add felhunter ability + pet.MinMana = 130 return pet } @@ -157,7 +212,7 @@ func (warlock *Warlock) registerVoidWalker() *WarlockPet { func (warlock *Warlock) registerVoidWalkerWithName(name string, enabledOnStart bool, isGuardian bool) *WarlockPet { pet := warlock.RegisterPet(proto.WarlockOptions_Voidwalker, 2, 3.5, name, enabledOnStart, isGuardian) pet.registerTormentSpell() - pet.MinEnergy = 120 + pet.MinMana = 120 return pet } @@ -168,16 +223,16 @@ func (warlock *Warlock) registerSuccubus() *WarlockPet { } func (warlock *Warlock) registerSuccubusWithName(name string, enabledOnStart bool, isGuardian bool) *WarlockPet { - pet := warlock.RegisterPet(proto.WarlockOptions_Succubus, 3, 1.667, name, enabledOnStart, isGuardian) + pet := warlock.RegisterPet(proto.WarlockOptions_Succubus, 173, 216, name, enabledOnStart, isGuardian) pet.registerLashOfPainSpell() - pet.MinEnergy = 160 + pet.MinMana = 190 return pet } func (warlock *Warlock) RegisterPet( t proto.WarlockOptions_Summon, - swingSpeed float64, - apScale float64, + min float64, + max float64, name string, enabledOnStart bool, isGuardian bool, @@ -188,11 +243,11 @@ func (warlock *Warlock) RegisterPet( } var attackOptions *core.AutoAttackOptions = nil - if swingSpeed > 0 { - attackOptions = ScaledAutoAttackConfig(swingSpeed) + if t > 1 { + attackOptions = AutoAttackConfig(min, max) } - inheritance := warlock.SimplePetStatInheritanceWithScale(apScale) + inheritance := warlock.SimplePetStatInheritanceWithScale() return warlock.makePet(name, enabledOnStart, *baseStats, attackOptions, inheritance, isGuardian) } @@ -210,56 +265,21 @@ func (pet *WarlockPet) ExecuteCustomRotation(sim *core.Simulation) { waitUntil := time.Duration(1<<63 - 1) for _, spell := range pet.AutoCastAbilities { - if spell.CanCast(sim, pet.CurrentTarget) && pet.CurrentEnergy() > pet.MinEnergy { + if spell.CanCast(sim, pet.CurrentTarget) && pet.CurrentMana() > pet.MinMana { spell.Cast(sim, pet.CurrentTarget) return } // calculate energy required - cost := max(pet.MinEnergy, spell.Cost.GetCurrentCost()) - timeTillEnergy := max(0, (cost-pet.CurrentEnergy())/pet.EnergyRegenPerSecond()) - waitUntil = min(waitUntil, time.Duration(float64(time.Second)*timeTillEnergy)) + cost := max(pet.MinMana, spell.Cost.GetCurrentCost()) + timeTillMana := max(0, (cost-pet.CurrentMana())/pet.ManaRegenPerSecondWhileCombat()) + waitUntil = min(waitUntil, time.Duration(float64(time.Second)*timeTillMana)) } // for now average the delay out to 100 ms so we don't need to roll random every time pet.WaitUntil(sim, sim.CurrentTime+waitUntil+time.Millisecond*100) } -var petActionShadowBite = core.ActionID{SpellID: 54049} - -func (pet *WarlockPet) registerShadowBiteSpell() { - pet.AutoCastAbilities = append(pet.AutoCastAbilities, pet.RegisterSpell(core.SpellConfig{ - ActionID: petActionShadowBite, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskSpellDamage, - ClassSpellMask: WarlockSpellFelHunterShadowBite, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - EnergyCost: core.EnergyCostOptions{ - Cost: 60, - }, - - DamageMultiplier: 1, - CritMultiplier: 2, - ThreatMultiplier: 1, - BonusCoefficient: 0.38, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcAndDealDamage(sim, target, pet.CalcScalingSpellDmg(0.38), spell.OutcomeMagicHitAndCrit) - if result.Landed() { - switch pet.Owner.Spec { - case proto.Spec_SpecDemonologyWarlock: - pet.Owner.Unit.GetSecondaryResourceBar().Gain(sim, 12, spell.ActionID) - } - } - }, - })) -} - var petActionFireBolt = core.ActionID{SpellID: 3110} func (pet *WarlockPet) registerFireboltSpell() { @@ -270,33 +290,26 @@ func (pet *WarlockPet) registerFireboltSpell() { ClassSpellMask: WarlockSpellImpFireBolt, MissileSpeed: 16, - EnergyCost: core.EnergyCostOptions{ - Cost: 40, + ManaCost: core.ManaCostOptions{ + FlatCost: 145, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ - GCD: time.Second * 1, - CastTime: time.Millisecond * 1750, + GCD: time.Millisecond * 1500, + CastTime: time.Millisecond * 2000, }, }, DamageMultiplier: 1, - CritMultiplier: 2, + CritMultiplier: 1.5, ThreatMultiplier: 1, - BonusCoefficient: 0.907, + BonusCoefficient: 0.571, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcDamage(sim, target, pet.CalcScalingSpellDmg(0.907), spell.OutcomeMagicHitAndCrit) + result := spell.CalcDamage(sim, target, pet.CalcScalingSpellDmg(0.571), spell.OutcomeMagicHitAndCrit) spell.WaitTravelTime(sim, func(sim *core.Simulation) { spell.DealDamage(sim, result) }) - - if result.Landed() { - switch pet.Owner.Spec { - case proto.Spec_SpecDemonologyWarlock: - pet.Owner.Unit.GetSecondaryResourceBar().Gain(sim, 8, spell.ActionID) - } - } }, })) } @@ -309,8 +322,8 @@ func (pet *WarlockPet) registerLashOfPainSpell() { SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, ClassSpellMask: WarlockSpellSuccubusLashOfPain, - EnergyCost: core.EnergyCostOptions{ - Cost: 60, + ManaCost: core.ManaCostOptions{ + FlatCost: 190, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ @@ -319,24 +332,19 @@ func (pet *WarlockPet) registerLashOfPainSpell() { }, DamageMultiplier: 1, - CritMultiplier: 2, + CritMultiplier: 1.5, ThreatMultiplier: 1, BonusCoefficient: 0.907, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcAndDealDamage(sim, target, pet.CalcScalingSpellDmg(0.907), spell.OutcomeMagicHitAndCrit) - - if result.Landed() { - switch pet.Owner.Spec { - case proto.Spec_SpecDemonologyWarlock: - pet.Owner.Unit.GetSecondaryResourceBar().Gain(sim, 12, spell.ActionID) - } - } + result := spell.CalcDamage(sim, target, pet.CalcScalingSpellDmg(0.571), spell.OutcomeMagicHitAndCrit) + spell.DealDamage(sim, result) }, })) + } -var petActionTorment = core.ActionID{SpellID: 3716} +var petActionTorment = core.ActionID{SpellID: 27270} func (pet *WarlockPet) registerTormentSpell() { pet.AutoCastAbilities = append(pet.AutoCastAbilities, pet.RegisterSpell(core.SpellConfig{ @@ -344,28 +352,17 @@ func (pet *WarlockPet) registerTormentSpell() { SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, ClassSpellMask: WarlockSpellVoidwalkerTorment, - EnergyCost: core.EnergyCostOptions{ - Cost: 50, + ManaCost: core.ManaCostOptions{ + FlatCost: 130, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ GCD: core.GCDDefault, }, }, - - DamageMultiplier: 1, - CritMultiplier: 2, - ThreatMultiplier: 1, - BonusCoefficient: 0.3, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcAndDealDamage(sim, target, pet.CalcScalingSpellDmg(0.3), spell.OutcomeMagicHitAndCrit) - if result.Landed() { - switch pet.Owner.Spec { - case proto.Spec_SpecDemonologyWarlock: - pet.Owner.Unit.GetSecondaryResourceBar().Gain(sim, 10, spell.ActionID) - } - } + result := spell.CalcDamage(sim, target, pet.CalcScalingSpellDmg(0.571), spell.OutcomeMagicHitAndCrit) + spell.DealDamage(sim, result) }, })) } diff --git a/sim/warlock/seed.go b/sim/warlock/seed.go index e027309e23..239eaa8d6f 100644 --- a/sim/warlock/seed.go +++ b/sim/warlock/seed.go @@ -28,7 +28,7 @@ func (warlock *Warlock) registerSeed() { ClassSpellMask: WarlockSpellSeedOfCorruptionExplosion, DamageMultiplierAdditive: 1, - CritMultiplier: warlock.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultSpellCritMultiplier(), ThreatMultiplier: 1, BonusCoefficient: seedExploCoeff, @@ -68,7 +68,6 @@ func (warlock *Warlock) registerSeed() { }, }, - CritMultiplier: warlock.DefaultCritMultiplier(), DamageMultiplierAdditive: 1, ThreatMultiplier: 1, diff --git a/sim/warlock/shadowbolt.go b/sim/warlock/shadowbolt.go index 7f6b877367..cf5c35b750 100644 --- a/sim/warlock/shadowbolt.go +++ b/sim/warlock/shadowbolt.go @@ -26,7 +26,7 @@ func (warlock *Warlock) registerShadowBolt() { }, DamageMultiplierAdditive: 1, - CritMultiplier: warlock.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultSpellCritMultiplier(), ThreatMultiplier: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { diff --git a/sim/warlock/shadowburn.go b/sim/warlock/shadowburn.go index cfe2d6bee7..5483d6792f 100644 --- a/sim/warlock/shadowburn.go +++ b/sim/warlock/shadowburn.go @@ -27,7 +27,7 @@ func (warlock *Warlock) registerShadowBurn() { }, DamageMultiplier: 1, - CritMultiplier: warlock.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultSpellCritMultiplier(), ThreatMultiplier: 1, BonusCoefficient: shadowBurnCoeff, }) diff --git a/sim/warlock/soulfire.go b/sim/warlock/soulfire.go index dcec959414..091ae39263 100644 --- a/sim/warlock/soulfire.go +++ b/sim/warlock/soulfire.go @@ -31,7 +31,7 @@ func (warlock *Warlock) registerSoulfire() { }, DamageMultiplier: 1, - CritMultiplier: warlock.DefaultCritMultiplier(), + CritMultiplier: warlock.DefaultSpellCritMultiplier(), ThreatMultiplier: 1, BonusCoefficient: soulfireCoeff, }) diff --git a/sim/warlock/unstable_affliction.go b/sim/warlock/unstable_affliction.go index 8dd8459af0..85c3aa4cdd 100644 --- a/sim/warlock/unstable_affliction.go +++ b/sim/warlock/unstable_affliction.go @@ -25,7 +25,6 @@ func (warlock *Warlock) registerUnstableAffliction() { }, DamageMultiplierAdditive: 1, - CritMultiplier: warlock.DefaultCritMultiplier(), ThreatMultiplier: 1, Dot: core.DotConfig{ diff --git a/sim/warlock/warlock.go b/sim/warlock/warlock.go index 5a7de95107..a5544e0422 100644 --- a/sim/warlock/warlock.go +++ b/sim/warlock/warlock.go @@ -47,12 +47,12 @@ type Warlock struct { ImpShadowboltAura *core.Aura // Pets - // ActivePet *WarlockPet - // Felhunter *WarlockPet - // Felguard *WarlockPet - // Imp *WarlockPet - // Succubus *WarlockPet - // Voidwalker *WarlockPet + ActivePet *WarlockPet + Felhunter *WarlockPet + Felguard *WarlockPet + Imp *WarlockPet + Succubus *WarlockPet + Voidwalker *WarlockPet // Doomguard *DoomguardPet // Infernal *InfernalPet @@ -114,17 +114,18 @@ func (warlock *Warlock) Initialize() { // warlock.registerSummonDoomguard(doomguardInfernalTimer) // warlock.registerSummonInfernal(doomguardInfernalTimer) - // Fel Armor 10% Stamina - core.MakePermanent( - warlock.RegisterAura(core.Aura{ - Label: "Fel Armor", - ActionID: core.ActionID{SpellID: 104938}, - })) - warlock.MultiplyStat(stats.Stamina, 1.1) - warlock.MultiplyStat(stats.Health, 1.1) - - // 5% int passive - warlock.MultiplyStat(stats.Intellect, 1.05) + // Armor selection + switch warlock.Options.Armor { + + case proto.WarlockOptions_FelArmor: + warlock.PseudoStats.SelfHealingMultiplier *= 1.20 + (0.20 * 0.1 * float64(warlock.Talents.DemonicAegis)) + warlock.AddStat(stats.SpellDamage, (100.0 + 100.0*(0.1*float64(warlock.Talents.DemonicAegis)))) + + case proto.WarlockOptions_DemonArmor: + warlock.AddStat(stats.Armor, (660 + (660 * (0.1 * float64(warlock.Talents.DemonicAegis))))) + warlock.AddStat(stats.ShadowResistance, 18+(18*0.1*float64(warlock.Talents.DemonicAegis))) + //HP5 not a thing atm + } } func (warlock *Warlock) AddRaidBuffs(raidBuffs *proto.RaidBuffs) { @@ -155,7 +156,7 @@ func NewWarlock(character *core.Character, options *proto.Player, warlockOptions // warlock.Doomguard = warlock.NewDoomguardPet() // warlock.serviceTimer = character.NewTimer() - // warlock.registerPets() + warlock.registerPets() // warlock.registerGrimoireOfService() return warlock diff --git a/ui/core/components/character_stats.tsx b/ui/core/components/character_stats.tsx index 1cfa773a85..6250df4ca0 100644 --- a/ui/core/components/character_stats.tsx +++ b/ui/core/components/character_stats.tsx @@ -104,6 +104,12 @@ export class CharacterStats extends Component { StatGroup.Spell, [ UnitStat.fromStat(Stat.StatSpellPower), + UnitStat.fromStat(Stat.StatSpellDamage), + UnitStat.fromStat(Stat.StatFirePower), + UnitStat.fromStat(Stat.StatFrostPower), + UnitStat.fromStat(Stat.StatHolyPower), + UnitStat.fromStat(Stat.StatNaturePower), + UnitStat.fromStat(Stat.StatShadowPower), UnitStat.fromPseudoStat(PseudoStat.PseudoStatSpellHastePercent), UnitStat.fromPseudoStat(PseudoStat.PseudoStatSpellHitPercent), UnitStat.fromPseudoStat(PseudoStat.PseudoStatSpellCritPercent), @@ -178,6 +184,7 @@ export class CharacterStats extends Component { private updateStats(player: Player) { const playerStats = player.getCurrentStats(); + console.log('playerStats', playerStats); const statMods = this.modifyDisplayStats ? this.modifyDisplayStats(this.player) : {}; this.hasRacialHitBonus = this.player.getRace() === Race.RaceDraenei; this.activeRacialExpertiseBonuses = this.player.getActiveRacialExpertiseBonuses(); @@ -189,6 +196,7 @@ export class CharacterStats extends Component { const consumesStats = Stats.fromProto(playerStats.consumesStats); const bonusStats = player.getBonusStats(); + let finalStats = Stats.fromProto(playerStats.finalStats) .add(statMods.base || new Stats()) .add(statMods.gear || new Stats()) @@ -222,6 +230,7 @@ export class CharacterStats extends Component { let idx = 0; this.stats.forEach(unitStat => { + console.log('UNIT STAT', unitStat) const bonusStatValue = unitStat.hasRootStat() ? bonusStats.getStat(unitStat.getRootStat()) : 0; let contextualClass: string; if (bonusStatValue == 0) { diff --git a/ui/core/components/inputs/buffs_debuffs.ts b/ui/core/components/inputs/buffs_debuffs.ts index 1c1e36c2ea..afb047c04a 100644 --- a/ui/core/components/inputs/buffs_debuffs.ts +++ b/ui/core/components/inputs/buffs_debuffs.ts @@ -76,16 +76,6 @@ export const CritBuff = InputHelpers.makeMultiIconInput( i18n.t('settings_tab.raid_buffs.crit_percent'), ); -export const MasteryBuff = InputHelpers.makeMultiIconInput( - [ - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(19740), fieldName: 'blessingOfMight' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(93435), fieldName: 'roarOfCourage' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(128997), fieldName: 'spiritBeastBlessing' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(116956), fieldName: 'graceOfAir' }), - ], - i18n.t('settings_tab.raid_buffs.mastery'), -); - export const StaminaBuff = InputHelpers.makeMultiIconInput( [ // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(469), fieldName: 'commandingShout' }), diff --git a/ui/core/components/inputs/consumables.ts b/ui/core/components/inputs/consumables.ts index bf483fcd0f..1ec1ae499b 100644 --- a/ui/core/components/inputs/consumables.ts +++ b/ui/core/components/inputs/consumables.ts @@ -90,21 +90,21 @@ export const CONJURED_CONFIG = [ export const makeConjuredInput = makeConsumeInputFactory({ consumesFieldName: 'conjuredId' }); -export const ExplosiveBigDaddy = { - actionId: ActionId.fromItemId(63396), - value: 89637, +export const ExplosiveSuperSapperCharge = { + actionId: ActionId.fromItemId(23827), + value: 30560, showWhen: (player: Player) => player.hasProfession(Profession.Engineering), }; -export const HighpoweredBoltGun = { - actionId: ActionId.fromItemId(60223), - value: 82207, +export const GoblinSapperCharge = { + actionId: ActionId.fromItemId(10646), + value: 12760, showWhen: (player: Player) => player.hasProfession(Profession.Engineering), }; export const EXPLOSIVE_CONFIG = [ - { config: ExplosiveBigDaddy, stats: [] }, - { config: HighpoweredBoltGun, stats: [] }, + { config: ExplosiveSuperSapperCharge, stats: [] }, + { config: GoblinSapperCharge, stats: [] }, ] as ConsumableStatOption[]; export const makeExplosivesInput = makeConsumeInputFactory({ consumesFieldName: 'explosiveId' }); @@ -152,6 +152,10 @@ export function makeConsumableInput( if (options.consumesFieldName === 'battleElixirId' || options.consumesFieldName === 'guardianElixirId') { newConsumes.flaskId = 0; } + + if (options.consumesFieldName == 'imbueId') { + newConsumes.imbueId = 0; + } player.setConsumes(eventID, newConsumes); }, }; diff --git a/ui/core/components/raid_sim_action.tsx b/ui/core/components/raid_sim_action.tsx index 844fb22116..2cafb8a0b0 100644 --- a/ui/core/components/raid_sim_action.tsx +++ b/ui/core/components/raid_sim_action.tsx @@ -26,7 +26,7 @@ export function addRaidSimAction(simUI: SimUI): RaidSimResultsManager { value: simUI.sim.getIterations(), }); const button = ev.target as HTMLButtonElement; - button.disabled = true; + button.disabled = false; if (!isRunning) { isRunning = true; diff --git a/ui/core/individual_sim_ui.tsx b/ui/core/individual_sim_ui.tsx index 0c84a5e702..054b3a9383 100644 --- a/ui/core/individual_sim_ui.tsx +++ b/ui/core/individual_sim_ui.tsx @@ -429,6 +429,7 @@ export abstract class IndividualSimUI extends SimUI { private addSidebarComponents() { this.raidSimResultsManager = addRaidSimAction(this); this.sim.waitForInit().then(() => { + console.log('its inited') this.epWeightsModal = addStatWeightsAction(this, this.statWeightActionSettings); }); diff --git a/ui/core/launched_sims.tsx b/ui/core/launched_sims.tsx index bf06f2e0d6..d0a1bdd68a 100755 --- a/ui/core/launched_sims.tsx +++ b/ui/core/launched_sims.tsx @@ -102,7 +102,7 @@ export const simLaunchStatuses: Record = { // Warlock [Spec.SpecWarlock]: { phase: Phase.Phase1, - status: LaunchStatus.Unlaunched, + status: LaunchStatus.Launched, }, // Warrior [Spec.SpecDPSWarrior]: { diff --git a/ui/core/proto_utils/database.ts b/ui/core/proto_utils/database.ts index 1cc2e77a73..a4266ec39d 100644 --- a/ui/core/proto_utils/database.ts +++ b/ui/core/proto_utils/database.ts @@ -360,7 +360,7 @@ export class Database { return Database.getWowheadTooltipData(id, 'spell', { signal: options?.signal }); } private static async getWowheadTooltipData(id: number, tooltipPostfix: string, options: { signal?: AbortSignal } = {}): Promise { - const url = `https://nether.wowhead.com/mop-classic/tooltip/${tooltipPostfix}/${id}?lvl=${CHARACTER_LEVEL}&dataEnv=${WOWHEAD_EXPANSION_ENV}`; + const url = `https://nether.wowhead.com/tbc/tooltip/${tooltipPostfix}/${id}?lvl=${CHARACTER_LEVEL}&dataEnv=${WOWHEAD_EXPANSION_ENV}`; try { const response = await fetch(url, { signal: options?.signal }); const json = await response.json(); diff --git a/ui/core/proto_utils/stats.ts b/ui/core/proto_utils/stats.ts index ca6f3ffffd..198fad1ceb 100644 --- a/ui/core/proto_utils/stats.ts +++ b/ui/core/proto_utils/stats.ts @@ -380,6 +380,12 @@ export const displayStatOrder: Array = [ UnitStat.fromStat(Stat.StatIntellect), UnitStat.fromStat(Stat.StatSpirit), UnitStat.fromStat(Stat.StatSpellPower), + UnitStat.fromStat(Stat.StatSpellDamage), + UnitStat.fromStat(Stat.StatFirePower), + UnitStat.fromStat(Stat.StatFrostPower), + UnitStat.fromStat(Stat.StatHolyPower), + UnitStat.fromStat(Stat.StatNaturePower), + UnitStat.fromStat(Stat.StatShadowPower), UnitStat.fromPseudoStat(PseudoStat.PseudoStatSpellHitPercent), UnitStat.fromPseudoStat(PseudoStat.PseudoStatSpellCritPercent), UnitStat.fromPseudoStat(PseudoStat.PseudoStatSpellHastePercent), diff --git a/ui/core/worker_pool.ts b/ui/core/worker_pool.ts index e1b148d031..e6c39e06f9 100644 --- a/ui/core/worker_pool.ts +++ b/ui/core/worker_pool.ts @@ -115,7 +115,9 @@ export class WorkerPool { } async raidSimAsync(request: RaidSimRequest, onProgress: WorkerProgressCallback, signals: SimSignals): Promise { + console.log("BEFORE WORKER?") const worker = this.getLeastBusyWorker(); + console.log("RAID SIM REQUEST AFTER WORK BEFORE THING" + RaidSimRequest.toJsonString(request)) worker.log('Raid sim request: ' + RaidSimRequest.toJsonString(request)); const id = request.requestId; @@ -124,6 +126,7 @@ export class WorkerPool { }); const iterations = request.simOptions?.iterations ?? 3000; + console.log("RAIMD SIME ASYN", SimRequest.raidSimAsync) const result = await this.doAsyncRequest(SimRequest.raidSimAsync, RaidSimRequest.toBinary(request), id, worker, onProgress, iterations); // Don't print the logs because it just clogs the console. diff --git a/ui/core/wowhead.ts b/ui/core/wowhead.ts index 7e5ab8489a..31d7ee5660 100644 --- a/ui/core/wowhead.ts +++ b/ui/core/wowhead.ts @@ -83,7 +83,7 @@ export type WowheadTooltipSpellParams = { difficultyId?: 14 | 15 | 16; }; -export const WOWHEAD_EXPANSION_ENV = 15; +export const WOWHEAD_EXPANSION_ENV = 5; export const buildWowheadTooltipDataset = async (options: WowheadTooltipItemParams | WowheadTooltipSpellParams) => { const lang = getLang(); diff --git a/ui/rogue/dps/sim.ts b/ui/rogue/dps/sim.ts index c8db39ee67..0ab2beb550 100644 --- a/ui/rogue/dps/sim.ts +++ b/ui/rogue/dps/sim.ts @@ -75,7 +75,6 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecRogue, { includeBuffDebuffInputs: [ BuffDebuffInputs.CritBuff, BuffDebuffInputs.AttackPowerBuff, - BuffDebuffInputs.MasteryBuff, BuffDebuffInputs.StatsBuff, BuffDebuffInputs.AttackSpeedBuff, diff --git a/ui/scss/sims/index.scss b/ui/scss/sims/index.scss index 6af1f1dbed..c33463e8a4 100644 --- a/ui/scss/sims/index.scss +++ b/ui/scss/sims/index.scss @@ -4,13 +4,9 @@ @import 'druid/guardian/sim'; @import 'druid/restoration/sim'; // Hunter -@import 'hunter/beast_mastery/sim'; -@import 'hunter/marksmanship/sim'; -@import 'hunter/survival/sim'; +@import 'hunter/dps/sim'; // Mage -@import 'mage/arcane/sim'; -@import 'mage/fire/sim'; -@import 'mage/frost/sim'; +@import 'mage/dps/sim'; // Paladin @import 'paladin/holy/sim'; @import 'paladin/protection/sim'; @@ -20,18 +16,13 @@ @import 'priest/holy/sim'; @import 'priest/shadow/sim'; // Rogue -@import 'rogue/assassination/sim'; -@import 'rogue/combat/sim'; -@import 'rogue/subtlety/sim'; +@import 'rogue/dps/sim'; // Shaman @import 'shaman/elemental/sim'; @import 'shaman/enhancement/sim'; @import 'shaman/restoration/sim'; // Warlock -@import 'warlock/affliction/sim'; -@import 'warlock/demonology/sim'; -@import 'warlock/destruction/sim'; +@import 'warlock/dps/sim'; // Warrior -@import 'warrior/arms/sim'; -@import 'warrior/fury/sim'; +@import 'warrior/dps/sim'; @import 'warrior/protection/sim'; diff --git a/ui/warlock/dps/presets.ts b/ui/warlock/dps/presets.ts index 295f71ff84..7cf693905a 100644 --- a/ui/warlock/dps/presets.ts +++ b/ui/warlock/dps/presets.ts @@ -1,6 +1,6 @@ import * as PresetUtils from '../../core/preset_utils'; import { ConsumesSpec, PseudoStat, Stat } from '../../core/proto/common'; -import { Warlock_Options as WarlockOptions } from '../../core/proto/warlock'; +import { Warlock_Options as WarlockOptions, WarlockOptions_Armor, WarlockOptions_Summon } from '../../core/proto/warlock'; import { SavedTalents } from '../../core/proto/ui'; import { Stats } from '../../core/proto_utils/stats'; import BlankAPL from './apls/blank.apl.json' @@ -40,15 +40,20 @@ export const Talents = { export const DefaultOptions = WarlockOptions.create({ classOptions: { - + armor: WarlockOptions_Armor.FelArmor, + summon: WarlockOptions_Summon.Succubus, + detonateSeed: false, + useItemSwapBonusStats: false, + sacrificeSummon: true, }, }); export const DefaultConsumables = ConsumesSpec.create({ - flaskId: 76084, // Flask of the Winds - foodId: 74648, // Skewered Eel - potId: 76089, // Potion of the Tol'vir - prepotId: 76089, // Potion of the Tol'vir + flaskId: 22866, // Flask of Pure Death + foodId: 33825, // Poached Bluefish + potId: 22839, // Destructive Potion + prepotId: 22839, // Destructive Potion + imbueId: 20749 // Brilliant Wizard Oil }); export const OtherDefaults = { diff --git a/ui/worker/sim_worker.ts b/ui/worker/sim_worker.ts index e88a72eb4c..eff821f8e2 100644 --- a/ui/worker/sim_worker.ts +++ b/ui/worker/sim_worker.ts @@ -44,7 +44,7 @@ let inst: WebAssembly.Instance | null = null; WebAssembly.instantiateStreaming(fetch('lib.wasm'), go.importObject).then(async result => { inst = result.instance; - // console.log("loading wasm...") + console.log("loading wasm...") await go.run(inst); }); From f94568326d377e30ff15ffe6709556454252ac55 Mon Sep 17 00:00:00 2001 From: jazz405 Date: Sun, 7 Dec 2025 15:03:39 -0500 Subject: [PATCH 09/20] Spells mostly firing, fix calcs --- sim/core/dot.go | 3 +-- sim/core/spell_result.go | 6 +++--- sim/warlock/agony.go | 31 +++++++++++++---------------- sim/warlock/corruption.go | 32 ++++++++++++++++++++---------- sim/warlock/doom.go | 2 +- sim/warlock/immolate.go | 12 ++++++++--- sim/warlock/shadowburn.go | 8 ++++++++ sim/warlock/siphon_life.go | 31 +++++++++++++++++++---------- sim/warlock/soulfire.go | 8 ++++++++ sim/warlock/unstable_affliction.go | 20 +++++++++++++++---- ui/warlock/dps/sim.ts | 1 + 11 files changed, 102 insertions(+), 52 deletions(-) diff --git a/sim/core/dot.go b/sim/core/dot.go index 97449ddce9..37e3f773b3 100644 --- a/sim/core/dot.go +++ b/sim/core/dot.go @@ -84,7 +84,6 @@ func (dot *Dot) Apply(sim *Simulation) { } dot.TakeSnapshot(sim, false) - dot.recomputeAuraDuration(sim) dot.Activate(sim) } @@ -97,7 +96,7 @@ func (dot *Dot) ApplyRollover(sim *Simulation) { } dot.TakeSnapshot(sim, true) - dot.recomputeAuraDuration(sim) + dot.Activate(sim) } diff --git a/sim/core/spell_result.go b/sim/core/spell_result.go index e871828348..aa46b457ab 100644 --- a/sim/core/spell_result.go +++ b/sim/core/spell_result.go @@ -342,7 +342,7 @@ func (spell *Spell) CalcPeriodicDamage(sim *Simulation, target *Unit, baseDamage return spell.calcDamageInternal(sim, target, baseDamage, attackerMultiplier, true, outcomeApplier) } func (dot *Dot) CalcSnapshotDamage(sim *Simulation, target *Unit, outcomeApplier OutcomeApplier) *SpellResult { - return dot.Spell.calcDamageInternal(sim, target, dot.SnapshotBaseDamage, dot.SnapshotAttackerMultiplier, true, outcomeApplier) + return dot.Spell.calcDamageInternal(sim, target, dot.SnapshotBaseDamage, 1.0, true, outcomeApplier) } func (spell *Spell) DealOutcome(sim *Simulation, result *SpellResult) { @@ -526,8 +526,8 @@ func (dot *Dot) Snapshot(target *Unit, baseDamage float64) { } attackTable := dot.Spell.Unit.AttackTables[target.UnitIndex] dot.SnapshotCritChance = dot.Spell.SpellCritChance(target) - dot.SnapshotAttackerMultiplier = dot.Spell.AttackerDamageMultiplier(attackTable, true) * - dot.PeriodicDamageMultiplier + dot.SnapshotAttackerMultiplier = math.Min(dot.Spell.AttackerDamageMultiplier(attackTable, true)* + dot.PeriodicDamageMultiplier, 1.0) } func (dot *Dot) SnapshotPhysical(target *Unit, baseDamage float64) { diff --git a/sim/warlock/agony.go b/sim/warlock/agony.go index 173dcdce06..563e1048f7 100644 --- a/sim/warlock/agony.go +++ b/sim/warlock/agony.go @@ -20,7 +20,7 @@ func (warlock *Warlock) registerCurseOfAgony() { ThreatMultiplier: 1, DamageMultiplier: 1, BonusCoefficient: agonyCoeff, - + CritMultiplier: 1, Cast: core.CastConfig{ DefaultCast: core.Cast{ GCD: core.GCDDefault, @@ -30,34 +30,31 @@ func (warlock *Warlock) registerCurseOfAgony() { ManaCost: core.ManaCostOptions{ BaseCostPercent: 1, }, + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHit) + if result.Landed() { + spell.Dot(target).Apply(sim) + } + spell.DealOutcome(sim, result) + }, Dot: core.DotConfig{ Aura: core.Aura{ - Label: "Agony", - MaxStacks: 10, + Label: "Agony", + Tag: "Affliction", }, TickLength: 2 * time.Second, NumberOfTicks: 12, - AffectedByCastSpeed: true, + AffectedByCastSpeed: false, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, warlock.CalcScalingSpellDmg(agonyScale)) + dot.Snapshot(target, 1356) }, BonusCoefficient: agonyCoeff, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - var stacks int32 = 10 - - // on the last tick the aura seems to be deactivated first - if dot.Aura.IsActive() { - dot.Aura.AddStack(sim) - stacks = dot.Aura.GetStacks() - } - - result := dot.CalcSnapshotDamage(sim, target, dot.OutcomeMagicHitAndSnapshotCrit) - result.Damage *= float64(stacks) - dot.Spell.DealPeriodicDamage(sim, result) + dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) }, }, @@ -66,7 +63,7 @@ func (warlock *Warlock) registerCurseOfAgony() { // Always compare fully stacked agony damage if useSnapshot { - result := dot.CalcSnapshotDamage(sim, target, dot.OutcomeExpectedSnapshotCrit) + result := dot.CalcSnapshotDamage(sim, target, dot.OutcomeTick) result.Damage *= 10 result.Damage /= dot.TickPeriod().Seconds() return result diff --git a/sim/warlock/corruption.go b/sim/warlock/corruption.go index beced5ded1..53e914a6db 100644 --- a/sim/warlock/corruption.go +++ b/sim/warlock/corruption.go @@ -9,7 +9,7 @@ import ( const corruptionCoeff = 0.156 func (warlock *Warlock) registerCorruption() *core.Spell { - resultSlice := make(core.SpellResultSlice, 1) + // resultSlice := make(core.SpellResultSlice, 1) warlock.Corruption = warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 172}, @@ -18,7 +18,8 @@ func (warlock *Warlock) registerCorruption() *core.Spell { Flags: core.SpellFlagAPL, ClassSpellMask: WarlockSpellCorruption, - ManaCost: core.ManaCostOptions{FlatCost: 370}, + CritMultiplier: 1, + ManaCost: core.ManaCostOptions{FlatCost: 370}, Cast: core.CastConfig{ DefaultCast: core.Cast{ GCD: core.GCDDefault, @@ -26,6 +27,14 @@ func (warlock *Warlock) registerCorruption() *core.Spell { }, }, + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHit) + if result.Landed() { + spell.Dot(target).Apply(sim) + } + spell.DealOutcome(sim, result) + }, + Dot: core.DotConfig{ Aura: core.Aura{ Label: "Corruption", @@ -35,18 +44,16 @@ func (warlock *Warlock) registerCorruption() *core.Spell { NumberOfTicks: 6, TickLength: 3 * time.Second, AffectedByCastSpeed: false, - + BonusCoefficient: corruptionCoeff, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, warlock.CalcScalingSpellDmg(corruptionCoeff)) - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - resultSlice[0] = dot.CalcSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) - // if onTickCallback != nil { - // onTickCallback(resultSlice, dot.Spell, sim) - // // } + dot.Snapshot(target, 900) - dot.Spell.DealPeriodicDamage(sim, resultSlice[0]) + // println("CALC SCALINGSPELLDMG", warlock.CalcScalingSpellDmg(corruptionCoeff)) + // dot.Snapshot(target, warlock.CalcScalingSpellDmg(corruptionCoeff)) + }, + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) }, }, @@ -63,10 +70,13 @@ func (warlock *Warlock) registerCorruption() *core.Spell { if useSnapshot { result := dot.CalcSnapshotDamage(sim, target, dot.OutcomeExpectedSnapshotCrit) result.Damage /= dot.TickPeriod().Seconds() + println("AFTER MATH", result.Damage) return result } else { + println("NOT USESNPASHOT") result := spell.CalcPeriodicDamage(sim, target, warlock.CalcScalingSpellDmg(corruptionCoeff), spell.OutcomeExpectedMagicCrit) result.Damage /= dot.CalcTickPeriod().Round(time.Millisecond).Seconds() + println("CALCTICK DMG", result.Damage) return result } }, diff --git a/sim/warlock/doom.go b/sim/warlock/doom.go index 2fd8ece110..545935030b 100644 --- a/sim/warlock/doom.go +++ b/sim/warlock/doom.go @@ -40,7 +40,7 @@ func (warlock *Warlock) registerCurseOfDoom() { dot.Snapshot(target, warlock.CalcScalingSpellDmg(doomCoeff)) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) + dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) }, }, diff --git a/sim/warlock/immolate.go b/sim/warlock/immolate.go index 360db0b9d8..bdf1d660f5 100644 --- a/sim/warlock/immolate.go +++ b/sim/warlock/immolate.go @@ -17,7 +17,9 @@ func (warlock *Warlock) registerImmolate() { Flags: core.SpellFlagAPL, ClassSpellMask: WarlockSpellImmolate, - ManaCost: core.ManaCostOptions{BaseCostPercent: 3}, + ManaCost: core.ManaCostOptions{ + FlatCost: 445, + }, Cast: core.CastConfig{ DefaultCast: core.Cast{ GCD: core.GCDDefault, @@ -31,9 +33,10 @@ func (warlock *Warlock) registerImmolate() { BonusCoefficient: immolateCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + //coeffection 0.13 result := spell.CalcDamage(sim, target, warlock.CalcScalingSpellDmg(immolateCoeff), spell.OutcomeMagicHitAndCrit) if result.Landed() { - spell.RelatedDotSpell.Cast(sim, target) + spell.RelatedDotSpell.Dot(target).Apply(sim) } spell.DealDamage(sim, result) @@ -63,7 +66,10 @@ func (warlock *Warlock) registerImmolate() { TickLength: 3 * time.Second, BonusCoefficient: immolateCoeff, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, warlock.CalcScalingSpellDmg(immolateCoeff)) + dot.Snapshot(target, 510) // coefficient 0.2 + }, + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) }, }, diff --git a/sim/warlock/shadowburn.go b/sim/warlock/shadowburn.go index 5483d6792f..72b8a2a3a9 100644 --- a/sim/warlock/shadowburn.go +++ b/sim/warlock/shadowburn.go @@ -22,6 +22,7 @@ func (warlock *Warlock) registerShadowBurn() { GCD: core.GCDDefault, }, CD: core.Cooldown{ + Timer: warlock.NewTimer(), Duration: time.Second * 15, }, }, @@ -30,5 +31,12 @@ func (warlock *Warlock) registerShadowBurn() { CritMultiplier: warlock.DefaultSpellCritMultiplier(), ThreatMultiplier: 1, BonusCoefficient: shadowBurnCoeff, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + result := spell.CalcDamage(sim, target, warlock.CalcScalingSpellDmg(shadowBurnCoeff), spell.OutcomeMagicHitAndCrit) + spell.WaitTravelTime(sim, func(sim *core.Simulation) { + spell.DealDamage(sim, result) + }) + }, }) } diff --git a/sim/warlock/siphon_life.go b/sim/warlock/siphon_life.go index 16e92ace7e..af8d654723 100644 --- a/sim/warlock/siphon_life.go +++ b/sim/warlock/siphon_life.go @@ -9,15 +9,16 @@ import ( const siphonLifeCoeff = 0.1 func (warlock *Warlock) registerSiphonLifeSpell() { - actionID := core.ActionID{SpellID: 6353} + actionID := core.ActionID{SpellID: 18265} baseCost := 410.0 - resultSlice := make(core.SpellResultSlice, 1) + // resultSlice := make(core.SpellResultSlice, 1) healthMetrics := warlock.NewHealthMetrics(actionID) warlock.SiphonLife = warlock.RegisterSpell(core.SpellConfig{ ActionID: actionID, SpellSchool: core.SpellSchoolShadow, ClassSpellMask: WarlockSpellSiphonLife, + Flags: core.SpellFlagAPL, BaseCost: baseCost, Cast: core.CastConfig{ DefaultCast: core.Cast{ @@ -25,29 +26,37 @@ func (warlock *Warlock) registerSiphonLifeSpell() { GCD: core.GCDDefault, }, }, + CritMultiplier: 1, + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHit) + if result.Landed() { + spell.Dot(target).Apply(sim) + } + spell.DealOutcome(sim, result) + }, + Dot: core.DotConfig{ Aura: core.Aura{ - Label: "Corruption", - Tag: "Affliction", - ActionID: core.ActionID{SpellID: 6353}, + Label: "SiphonLife", + Tag: "Affliction", }, - NumberOfTicks: 6, + NumberOfTicks: 10, TickLength: 3 * time.Second, AffectedByCastSpeed: false, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, warlock.CalcScalingSpellDmg(siphonLifeCoeff)) + dot.Snapshot(target, 630) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - resultSlice[0] = dot.CalcSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) + // resultSlice[0] = dot.CalcSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) + var result = dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) // if onTickCallback != nil { // onTickCallback(resultSlice, dot.Spell, sim) // } + // dot.Spell.DealPeriodicDamage(sim, resultSlice[0]) - dot.Spell.DealPeriodicDamage(sim, resultSlice[0]) - - healthToRegain := resultSlice[0].Damage * (1 * warlock.PseudoStats.BonusHealingTaken) + healthToRegain := result.Damage * (1 * warlock.PseudoStats.BonusHealingTaken) warlock.GainHealth(sim, healthToRegain, healthMetrics) dot.Spell.ApplyAOEThreat(healthToRegain * 0.5) }, diff --git a/sim/warlock/soulfire.go b/sim/warlock/soulfire.go index 091ae39263..f2ffc86bbd 100644 --- a/sim/warlock/soulfire.go +++ b/sim/warlock/soulfire.go @@ -26,6 +26,7 @@ func (warlock *Warlock) registerSoulfire() { CastTime: time.Millisecond*6000 - (time.Millisecond * 400 * time.Duration(warlock.Talents.Bane)), }, CD: core.Cooldown{ + Timer: warlock.NewTimer(), Duration: 1 * time.Minute, }, }, @@ -34,6 +35,13 @@ func (warlock *Warlock) registerSoulfire() { CritMultiplier: warlock.DefaultSpellCritMultiplier(), ThreatMultiplier: 1, BonusCoefficient: soulfireCoeff, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + result := spell.CalcDamage(sim, target, warlock.CalcScalingSpellDmg(soulfireCoeff), spell.OutcomeMagicHitAndCrit) + spell.WaitTravelTime(sim, func(sim *core.Simulation) { + spell.DealDamage(sim, result) + }) + }, }) } diff --git a/sim/warlock/unstable_affliction.go b/sim/warlock/unstable_affliction.go index 85c3aa4cdd..63e2c08e1d 100644 --- a/sim/warlock/unstable_affliction.go +++ b/sim/warlock/unstable_affliction.go @@ -24,21 +24,33 @@ func (warlock *Warlock) registerUnstableAffliction() { }, }, + CritMultiplier: 1, DamageMultiplierAdditive: 1, ThreatMultiplier: 1, + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHit) + if result.Landed() { + spell.Dot(target).Apply(sim) + } + spell.DealOutcome(sim, result) + }, Dot: core.DotConfig{ - Aura: core.Aura{Label: "UnstableAffliction"}, - NumberOfTicks: 7, + Aura: core.Aura{ + Label: "Unstable Affliction", + Tag: "Affliction", + ActionID: core.ActionID{SpellID: 30108}, + }, + NumberOfTicks: 9, TickLength: 2 * time.Second, AffectedByCastSpeed: true, BonusCoefficient: uaCoeff, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, _ bool) { - dot.Snapshot(target, warlock.CalcScalingSpellDmg(uaCoeff)) + dot.Snapshot(target, 1150) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) + dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) }, }, diff --git a/ui/warlock/dps/sim.ts b/ui/warlock/dps/sim.ts index 7071044b77..498cc70386 100644 --- a/ui/warlock/dps/sim.ts +++ b/ui/warlock/dps/sim.ts @@ -48,6 +48,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarlock, { Stat.StatIntellect, Stat.StatSpirit, Stat.StatSpellPower, + Stat.StatSpellDamage, Stat.StatShadowPower, Stat.StatFirePower, Stat.StatMP5, From 4f5f89e0e7c662058e03bf386aebeeeeb1ee57e3 Mon Sep 17 00:00:00 2001 From: jazz405 Date: Mon, 8 Dec 2025 21:53:06 -0500 Subject: [PATCH 10/20] fill out pet talents --- sim/core/mana.go | 6 -- sim/warlock/conflagrate.go | 5 ++ sim/warlock/pets.go | 1 + sim/warlock/talents.go | 160 ++++++++++++++++++++++++++++++++----- sim/warlock/warlock.go | 2 - 5 files changed, 148 insertions(+), 26 deletions(-) diff --git a/sim/core/mana.go b/sim/core/mana.go index ea4f0c079b..ef5624c172 100644 --- a/sim/core/mana.go +++ b/sim/core/mana.go @@ -44,14 +44,8 @@ func (character *Character) EnableManaBar() { func (character *Character) EnableManaBarWithModifier() { if character.Unit.Type == PlayerUnit { - - // Patch 2.4 changed mp5 formula -> SpiritIntellectRegen = 5 * 0.00932715221261 * sqrt(Intellect) * Spirit - mp5 := 5 * 0.00932715221261 * math.Sqrt(character.stats[stats.Intellect]) * character.stats[stats.Spirit] - character.AddStat(stats.MP5, mp5) - // Pets might have different scaling so let them handle their scaling character.AddStatDependency(stats.Intellect, stats.SpellCritPercent, CritPerIntMaxLevel[character.Class]) - character.AddStatDependency(stats.Intellect, stats.Mana, 15) } diff --git a/sim/warlock/conflagrate.go b/sim/warlock/conflagrate.go index 435288897c..b8bc6fe7cf 100644 --- a/sim/warlock/conflagrate.go +++ b/sim/warlock/conflagrate.go @@ -9,6 +9,11 @@ import ( const conflagrateCoeff = 0.429 func (warlock *Warlock) registerConflagrate() { + + if !warlock.Talents.Conflagrate { + return + } + warlock.Conflagrate = warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 17962}, SpellSchool: core.SpellSchoolFire, diff --git a/sim/warlock/pets.go b/sim/warlock/pets.go index 6ed7f10fc9..79f71aa180 100644 --- a/sim/warlock/pets.go +++ b/sim/warlock/pets.go @@ -150,6 +150,7 @@ func (warlock *Warlock) makePet( // set pet class for proper scaling values if enabledOnStart { + warlock.ActivePet = pet warlock.RegisterResetEffect(func(sim *core.Simulation) { warlock.ActivePet = pet }) diff --git a/sim/warlock/talents.go b/sim/warlock/talents.go index b6e9193819..aa4f6363a2 100644 --- a/sim/warlock/talents.go +++ b/sim/warlock/talents.go @@ -4,6 +4,7 @@ import ( "time" "github.com/wowsims/tbc/sim/core" + "github.com/wowsims/tbc/sim/core/proto" "github.com/wowsims/tbc/sim/core/stats" ) @@ -21,10 +22,16 @@ func (warlock *Warlock) applyAfflictionTalents() { } func (warlock *Warlock) applyDemonologyTalents() { + warlock.appyImprovedImp() warlock.applyDemonicEmbrace() warlock.applyFelIntellect() warlock.applyFelStamina() + warlock.applyImprovedSayaad() + warlock.applyUnholyPower() + warlock.applyDemonicSacrifice() + warlock.applyMasterDemonologist() warlock.applySoulLink() + warlock.applyDemonicKnowledge() warlock.applyDemonicTactics() } @@ -32,12 +39,15 @@ func (warlock *Warlock) applyDemonologyTalents() { func (warlock *Warlock) applyDestructionTalents() { warlock.applyCataclysm() warlock.applyBane() + warlock.applyDevastation() + warlock.applyImprovedFirebolt() + warlock.applyImprovedLashOfPain() warlock.applyDestructiveReach() warlock.applyImprovedSearingPain() warlock.applyRuin() warlock.applyEmberstorm() warlock.applyBacklash() - warlock.applySoulLeech() + warlock.registerConflagrate() warlock.applyShadowAndFlame() } @@ -80,7 +90,7 @@ func (warlock *Warlock) applyImprovedCorruption() { } func (warlock *Warlock) registerAmplifyCurse() { - if warlock.Talents.AmplifyCurse == false { + if !warlock.Talents.AmplifyCurse { return } @@ -234,25 +244,47 @@ func (warlock *Warlock) applyUnstableAffliction() { Demonology Skipping so many for now */ +func (warlock *Warlock) appyImprovedImp() { + if warlock.Talents.ImprovedImp == 0 { + return + } + + warlock.Imp.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_DamageDone_Pct, + FloatValue: 0.1 * float64(warlock.Talents.ImprovedImp), + ClassMask: WarlockSpellImpFireBolt, + }) +} + func (warlock *Warlock) applyDemonicEmbrace() { if warlock.Talents.DemonicEmbrace == 0 { return } warlock.MultiplyStat(stats.Stamina, 1.0+(0.03)*float64(warlock.Talents.DemonicEmbrace)) - warlock.MultiplyStat(stats.Spirit, 1.0+(0.03)*float64(warlock.Talents.DemonicEmbrace)) - // warlock.AddStatDependency(stats.Stamina, stats.Stamina, (0.03)*float64(warlock.Talents.DemonicEmbrace)) - // warlock.AddStatDependency(stats.Spirit, stats.Spirit, (0.03)*float64(warlock.Talents.DemonicEmbrace)) + warlock.MultiplyStat(stats.Spirit, 1.0-(0.03)*float64(warlock.Talents.DemonicEmbrace)) } -// TODO - Add pet part func (warlock *Warlock) applyFelIntellect() { if warlock.Talents.FelIntellect == 0 { return } - warlock.AddStatDependency(stats.Intellect, stats.Mana, 15*(0.01)*float64(warlock.Talents.FelIntellect)) + warlock.MultiplyStat(stats.Mana, 1.0+(0.01)*float64(warlock.Talents.FelIntellect)) + warlock.ActivePet.MultiplyStat(stats.Mana, 1+(0.05)*float64(warlock.Talents.FelIntellect)) +} + +func (warlock *Warlock) applyImprovedSayaad() { + if warlock.Talents.ImprovedSayaad == 0 { + return + } + //This might not actually increase the damage, find a source to prove this + warlock.Succubus.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_DamageDone_Pct, + FloatValue: 0.1 * float64(warlock.Talents.ImprovedSayaad), + ClassMask: WarlockSpellSuccubusLashOfPain, + }) } func (warlock *Warlock) applyFelStamina() { @@ -261,19 +293,66 @@ func (warlock *Warlock) applyFelStamina() { } warlock.MultiplyStat(stats.Health, 1.0+0.01*float64(warlock.Talents.FelStamina)) + warlock.ActivePet.MultiplyStat(stats.Health, 1+(0.05)*float64(warlock.Talents.FelStamina)) + +} + +func (warlock *Warlock) applyUnholyPower() { + if warlock.Talents.UnholyPower == 0 { + return + } + + warlock.ActivePet.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] *= 1.0 + 0.04*float64(warlock.Talents.UnholyPower) + warlock.Imp.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_DamageDone_Pct, + FloatValue: 0.04 * float64(warlock.Talents.UnholyPower), + ClassMask: WarlockSpellImpFireBolt, + }) } -// Placeholder for Unholy Power -//func (warlock *Warlock) applyUnholyPower() {} +func (warlock *Warlock) applyDemonicSacrifice() { + if !warlock.Talents.DemonicSacrifice { + return + } + + switch warlock.Options.Summon { + case proto.WarlockOptions_Succubus: + warlock.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexShadow] *= 1.15 + case proto.WarlockOptions_Imp: + warlock.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexFire] *= 1.15 + case proto.WarlockOptions_Felguard: + warlock.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexShadow] *= 1.10 + warlock.AddStat(stats.MP5, warlock.GetStats()[stats.Intellect]*1.25) + case proto.WarlockOptions_Felhunter: + warlock.AddStat(stats.MP5, warlock.GetStats()[stats.Intellect]*1.6667) + } +} -//Placeholder for DSac -//func (warlock *Warlock) applyDemonicSacrifice(){} +func (warlock *Warlock) applyMasterDemonologist() { + if warlock.Talents.MasterDemonologist == 0 { + return + } -//Placeholder for MasterDemonologist -//func (warlock *Warlock) applyMasterDemonologist(){} + switch warlock.Options.Summon { + case proto.WarlockOptions_Imp: + warlock.PseudoStats.ThreatMultiplier *= 1.0 - (0.04 * float64(warlock.Talents.MasterDemonologist)) + case proto.WarlockOptions_Succubus: + warlock.PseudoStats.DamageDealtMultiplier *= 1.0 + 0.02*float64(warlock.Talents.MasterDemonologist) + case proto.WarlockOptions_Felguard: + warlock.PseudoStats.DamageDealtMultiplier *= 1.0 + 0.01*float64(warlock.Talents.MasterDemonologist) + case proto.WarlockOptions_Voidwalker: + warlock.PseudoStats.BonusPhysicalDamageTaken *= 1.0 - (0.02 * float64(warlock.Talents.MasterDemonologist)) + } +} -//Placeholder for Demonic Knowledge -//func (warlock *Warlock) applyDemonicKnowledge(){} +func (warlock *Warlock) applyDemonicKnowledge() { + if warlock.Talents.DemonicKnowledge == 0 { + return + } + + petStats := warlock.ActivePet.GetStats() + warlock.AddStat(stats.SpellDamage, (0.04*float64(warlock.Talents.DemonicKnowledge))*(petStats[stats.Stamina]+petStats[stats.Intellect])) +} func (warlock *Warlock) applySoulLink() { if !warlock.Talents.SoulLink { @@ -330,9 +409,41 @@ func (warlock *Warlock) applyBane() { }) } -//TODO - implement the pet talents -// func (warlock *Warlock) applyImprovedFirebolt(){} -// func (warlock *Warlock) applyImprovedLashOfPain(){} +func (warlock *Warlock) applyDevastation() { + if warlock.Talents.Devastation == 0 { + return + } + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_BonusCrit_Percent, + FloatValue: 5.0, + ClassMask: WarlockDestructionSpells, + }) +} + +func (warlock *Warlock) applyImprovedFirebolt() { + if warlock.Talents.ImprovedFirebolt == 0 { + return + } + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_CastTime_Flat, + TimeValue: -(time.Millisecond * 250) * time.Duration(warlock.Talents.ImprovedFirebolt), + ClassMask: WarlockSpellImpFireBolt, + }) +} + +func (warlock *Warlock) applyImprovedLashOfPain() { + if warlock.Talents.ImprovedLashOfPain == 0 { + return + } + + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_Cooldown_Flat, + TimeValue: -(time.Second * 3) * time.Duration(warlock.Talents.ImprovedFirebolt), + ClassMask: WarlockSpellSuccubusLashOfPain, + }) +} func (warlock *Warlock) applyDestructiveReach() { if warlock.Talents.DestructiveReach == 0 { @@ -401,11 +512,24 @@ func (warlock *Warlock) applyBacklash() { warlock.AddStat(stats.SpellCritPercent, float64(warlock.Talents.Backlash)) } +// ToDo func (warlock *Warlock) applySoulLeech() { if warlock.Talents.SoulLeech == 0 { return } + warlock.AddStaticMod(core.SpellModConfig{ + Kind: core.SpellMod_Custom, + ApplyCustom: func(mod *core.SpellMod, spell *core.Spell) { + + }, + RemoveCustom: func(mod *core.SpellMod, spell *core.Spell) { + + }, + ClassMask: WarlockSpellShadowBolt | WarlockSpellShadowBurn | WarlockSpellSoulFire | + WarlockSpellIncinerate | WarlockSpellSearingPain | WarlockSpellConflagrate, + FloatValue: 2.0, + }) } func (warlock *Warlock) applyShadowAndFlame() { diff --git a/sim/warlock/warlock.go b/sim/warlock/warlock.go index a5544e0422..d1c551e924 100644 --- a/sim/warlock/warlock.go +++ b/sim/warlock/warlock.go @@ -97,10 +97,8 @@ func (warlock *Warlock) Initialize() { warlock.registerCurseOfElements() warlock.registerCurseOfDoom() warlock.registerCurseOfAgony() - warlock.registerCorruption() warlock.registerSeed() - warlock.registerConflagrate() warlock.registerDrainLife() warlock.registerImmolate() warlock.registerIncinerate() From a4c27dbd128184467cb778a09a3df9c82f760931 Mon Sep 17 00:00:00 2001 From: jazz405 Date: Wed, 10 Dec 2025 20:44:28 -0500 Subject: [PATCH 11/20] add buff defaults --- ui/core/components/character_stats.tsx | 1 - ui/warlock/dps/sim.ts | 39 ++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/ui/core/components/character_stats.tsx b/ui/core/components/character_stats.tsx index 6250df4ca0..61703f983f 100644 --- a/ui/core/components/character_stats.tsx +++ b/ui/core/components/character_stats.tsx @@ -230,7 +230,6 @@ export class CharacterStats extends Component { let idx = 0; this.stats.forEach(unitStat => { - console.log('UNIT STAT', unitStat) const bonusStatValue = unitStat.hasRootStat() ? bonusStats.getStat(unitStat.getRootStat()) : 0; let contextualClass: string; if (bonusStatValue == 0) { diff --git a/ui/warlock/dps/sim.ts b/ui/warlock/dps/sim.ts index 498cc70386..cb05b0df97 100644 --- a/ui/warlock/dps/sim.ts +++ b/ui/warlock/dps/sim.ts @@ -4,7 +4,7 @@ import { IndividualSimUI, registerSpecConfig } from '../../core/individual_sim_u import { Player } from '../../core/player'; import { PlayerClasses } from '../../core/player_classes'; import { APLRotation } from '../../core/proto/apl'; -import { Debuffs, Faction, IndividualBuffs, ItemSlot, PartyBuffs, PseudoStat, Race, RaidBuffs, Spec, Stat } from '../../core/proto/common'; +import { Debuffs, Drums, Faction, IndividualBuffs, ItemSlot, PartyBuffs, PseudoStat, Race, RaidBuffs, Spec, Stat, TristateEffect } from '../../core/proto/common'; import { DEFAULT_CASTER_GEM_STATS, Stats, UnitStat } from '../../core/proto_utils/stats'; import { defaultRaidBuffMajorDamageCooldowns } from '../../core/proto_utils/utils'; import { TypedEvent } from '../../core/typed_event'; @@ -47,7 +47,6 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarlock, { Stat.StatStamina, Stat.StatIntellect, Stat.StatSpirit, - Stat.StatSpellPower, Stat.StatSpellDamage, Stat.StatShadowPower, Stat.StatFirePower, @@ -79,15 +78,45 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarlock, { // Default buffs and debuffs settings. raidBuffs: RaidBuffs.create({ ...defaultRaidBuffMajorDamageCooldowns(), + arcaneBrilliance: true, + powerWordFortitude: TristateEffect.TristateEffectImproved, + divineSpirit: TristateEffect.TristateEffectImproved, + giftOfTheWild: TristateEffect.TristateEffectImproved, + bloodlust: true, }), partyBuffs: PartyBuffs.create({ - + bloodPact: TristateEffect.TristateEffectMissing, + moonkinAura: TristateEffect.TristateEffectRegular, + totemOfWrath: 1, + wrathOfAirTotem: TristateEffect.TristateEffectRegular, + manaSpringTotem: TristateEffect.TristateEffectRegular, + draeneiRacialCaster: false, + ferociousInspiration: 0, + sanctityAura: TristateEffect.TristateEffectMissing, + drums: Drums.DrumsOfBattle }), individualBuffs: IndividualBuffs.create({ - + blessingOfKings: true, + blessingOfSalvation: false, + blessingOfWisdom: TristateEffect.TristateEffectRegular, + innervates: 0, + powerInfusions: 0, + shadowPriestDps: 0, }), debuffs: Debuffs.create({ - + bloodFrenzy: true, + curseOfElements: TristateEffect.TristateEffectRegular, + curseOfRecklessness: true, + faerieFire: TristateEffect.TristateEffectRegular, + huntersMark: TristateEffect.TristateEffectImproved, + exposeArmor: TristateEffect.TristateEffectRegular, + improvedScorch: false, + improvedSealOfTheCrusader: true, + isbUptime: 0, + misery: true, + shadowWeaving: true, + sunderArmor: true, + wintersChill: true, }), other: Presets.OtherDefaults, From cce817fa64cf6db266a92b80485797bbea1107ec Mon Sep 17 00:00:00 2001 From: jazz405 Date: Sat, 13 Dec 2025 09:56:11 -0500 Subject: [PATCH 12/20] buffs and debuffs in ui --- ui/core/components/character_stats.tsx | 1 - ui/core/components/icon_inputs.ts | 41 +- .../individual_sim_ui/settings_tab.tsx | 73 +- ui/core/components/inputs/buffs_debuffs.ts | 629 ++++++++++-------- ui/core/components/inputs/stat_options.ts | 12 +- ui/core/individual_sim_ui.tsx | 1 - ui/core/player_spec.ts | 9 + ui/core/player_specs/druid.ts | 10 +- ui/core/player_specs/hunter.ts | 5 +- ui/core/player_specs/mage.ts | 4 +- ui/core/player_specs/paladin.ts | 9 +- ui/core/player_specs/priest.ts | 11 +- ui/core/player_specs/rogue.ts | 5 +- ui/core/player_specs/shaman.ts | 10 +- ui/core/player_specs/warlock.ts | 4 +- ui/core/player_specs/warrior.ts | 8 +- ui/core/proto_utils/utils.ts | 1 + ui/druid/feralbear/sim.ts | 2 +- ui/druid/feralcat/sim.ts | 2 +- ui/hunter/dps/sim.ts | 2 +- ui/priest/shadow/sim.ts | 2 +- ui/rogue/dps/sim.ts | 12 +- .../individual_sim_ui/_settings_tab.scss | 2 +- ui/scss/shared/_variables.scss | 1 + ui/shaman/elemental/sim.ts | 3 +- ui/shaman/enhancement/sim.ts | 2 +- ui/warlock/dps/sim.ts | 11 +- ui/warrior/protection/sim.ts | 2 +- 28 files changed, 518 insertions(+), 356 deletions(-) diff --git a/ui/core/components/character_stats.tsx b/ui/core/components/character_stats.tsx index 61703f983f..a6f1dd22b4 100644 --- a/ui/core/components/character_stats.tsx +++ b/ui/core/components/character_stats.tsx @@ -184,7 +184,6 @@ export class CharacterStats extends Component { private updateStats(player: Player) { const playerStats = player.getCurrentStats(); - console.log('playerStats', playerStats); const statMods = this.modifyDisplayStats ? this.modifyDisplayStats(this.player) : {}; this.hasRacialHitBonus = this.player.getRace() === Race.RaceDraenei; this.activeRacialExpertiseBonuses = this.player.getActiveRacialExpertiseBonuses(); diff --git a/ui/core/components/icon_inputs.ts b/ui/core/components/icon_inputs.ts index 6a9b8aa6df..07462ac2ac 100644 --- a/ui/core/components/icon_inputs.ts +++ b/ui/core/components/icon_inputs.ts @@ -4,9 +4,10 @@ import { ConsumesSpec, Debuffs, Faction, IndividualBuffs, PartyBuffs, RaidBuffs, import { ActionId } from '../proto_utils/action_id.js'; import { Raid } from '../raid'; import { EventID, TypedEvent } from '../typed_event'; +import { ExclusivityTag } from './individual_sim_ui/settings_tab'; import * as InputHelpers from './input_helpers'; -import { IconEnumPicker } from './pickers/icon_enum_picker.jsx'; -import { IconPicker } from './pickers/icon_picker.jsx'; +import { IconEnumPicker, IconEnumPickerDirection, IconEnumValueConfig } from './pickers/icon_enum_picker.jsx'; +import { IconPicker, IconPickerConfig } from './pickers/icon_picker.jsx'; // Component Functions @@ -47,6 +48,7 @@ export function makeBooleanRaidBuffInput( setValue: (eventID: EventID, player: Player, newVal: RaidBuffs) => player.getRaid()!.setBuffs(eventID, newVal), changeEmitter: (player: Player) => TypedEvent.onAny([player.getRaid()!.buffsChangeEmitter, player.raceChangeEmitter]), }, + config.actionId, config.fieldName, config.value, @@ -66,9 +68,26 @@ export function makeBooleanPartyBuffInput( config.actionId, config.fieldName, config.value, + config.label ); } +export function makeEnumValuePartyBuffInput(id: ActionId, buffsFieldName: keyof PartyBuffs, enumValue: number, exclusivityTags?: Array): InputHelpers.TypedIconPickerConfig { + return { + id: id.name, + actionId: id, + type: 'icon', + states: 2, + changedEvent: (party: Party) => party.buffsChangeEmitter, + getValue: (party: Party) => party.getBuffs()[buffsFieldName] == enumValue, + setValue: (eventID: EventID, party: Party, newValue: boolean) => { + const newBuffs = party.getBuffs(); + (newBuffs[buffsFieldName] as number) = newValue ? enumValue : 0; + party.setBuffs(eventID, newBuffs); + }, + } +} + export function makeBooleanIndividualBuffInput( config: BooleanInputConfig, ): InputHelpers.TypedIconPickerConfig, boolean> { @@ -146,6 +165,24 @@ export function makeTristateRaidBuffInput( ); } +export function makeTristatePartyBuffInput( + config: TristateInputConfig, +): InputHelpers.TypedIconPickerConfig, number> { + return InputHelpers.makeTristateIconInput>( + { + getModObject: (player: Player) => player, + showWhen: (player: Player) => !config.faction || config.faction == player.getFaction(), + getValue: (player: Player) => player.getParty()?.getBuffs()!!, + setValue: (eventID: EventID, player: Player, newVal: PartyBuffs) => player.getParty()?.setBuffs(eventID, newVal), + changeEmitter: (player: Player) => TypedEvent.onAny([player.buffsChangeEmitter, player.raceChangeEmitter]), + }, + config.actionId, + config.impId, + config.fieldName, + config.label, + ); +} + export function makeTristateIndividualBuffInput( config: TristateInputConfig, ): InputHelpers.TypedIconPickerConfig, number> { diff --git a/ui/core/components/individual_sim_ui/settings_tab.tsx b/ui/core/components/individual_sim_ui/settings_tab.tsx index 4632d33d3f..28145ba46b 100644 --- a/ui/core/components/individual_sim_ui/settings_tab.tsx +++ b/ui/core/components/individual_sim_ui/settings_tab.tsx @@ -73,8 +73,6 @@ export class SettingsTab extends SimTab { if (!this.simUI.isWithinRaidSim) { this.buildBuffsSettings(); - this.raidExternalDamageCooldowns(); - this.raidExternalDefensiveCooldowns(); this.buildDebuffsSettings(); } @@ -203,59 +201,36 @@ export class SettingsTab extends SimTab { const contentBlock = new ContentBlock(this.column3, 'buffs-settings', { header: { title: i18n.t('settings_tab.raid_buffs.title'), tooltip: i18n.t('settings_tab.raid_buffs.tooltip') }, }); + contentBlock.headerElement?.appendChild(

{i18n.t('settings_tab.raid_buffs.description')}

, ); - const buffOptions = relevantStatOptions(BuffDebuffInputs.RAID_BUFFS_CONFIG, this.simUI); + + + const buffOptions = relevantStatOptions(BuffDebuffInputs.BUFFS_CONFIG, this.simUI); this.configureIconSection( contentBlock.bodyElement, buffOptions.map(options => options.picker && new options.picker(contentBlock.bodyElement, this.simUI.player, options.config as any, this.simUI)), ); - const miscBuffOptions = relevantStatOptions(BuffDebuffInputs.RAID_BUFFS_MISC_CONFIG, this.simUI); - if (miscBuffOptions.length > 0) { - new MultiIconPicker( - contentBlock.bodyElement, - this.simUI.player, - { - inputs: miscBuffOptions.map(option => option.config), - label: i18n.t('settings_tab.raid_buffs.misc.label'), - }, - this.simUI, - ); - } - } - - private raidExternalDamageCooldowns() { - const externalDamageCooldownOptions = relevantStatOptions(BuffDebuffInputs.RAID_BUFFS_EXTERNAL_DAMAGE_COOLDOWN, this.simUI); - if (externalDamageCooldownOptions.length > 0) { - const contentBlock = new ContentBlock(this.column3, 'buffs-settings', { - header: { title: i18n.t('settings_tab.external_damage_cooldowns.title'), tooltip: i18n.t('settings_tab.external_damage_cooldowns.tooltip') }, + const partyBuffOptions = relevantStatOptions(BuffDebuffInputs.PARTY_BUFFS_CONFIG, this.simUI); + if (partyBuffOptions.length > 0) { + const partyContentBlock = new ContentBlock(this.column3, 'buffs-settings', { + header: { title: i18n.t('settings_tab.raid_buffs.title'), tooltip: i18n.t('settings_tab.raid_buffs.tooltip') }, }); - this.configureIconSection( - contentBlock.bodyElement, - externalDamageCooldownOptions.map( - options => options.picker && new options.picker(contentBlock.bodyElement, this.simUI.player, options.config as any), - ), + partyContentBlock.headerElement?.appendChild( +

+ {i18n.t('settings_tab.raid_buffs.description')} +

, ); - } - } - private raidExternalDefensiveCooldowns() { - const externalDefensiveCooldownOptions = relevantStatOptions(BuffDebuffInputs.RAID_BUFFS_EXTERNAL_DEFENSIVE_COOLDOWN, this.simUI); - if (externalDefensiveCooldownOptions.length > 0) { - const contentBlock = new ContentBlock(this.column3, 'buffs-settings', { - header: { title: i18n.t('settings_tab.external_defensive_cooldowns.title'), tooltip: i18n.t('settings_tab.external_defensive_cooldowns.tooltip') }, - }); this.configureIconSection( - contentBlock.bodyElement, - externalDefensiveCooldownOptions.map( - options => options.picker && new options.picker(contentBlock.bodyElement, this.simUI.player, options.config as any), - ), + partyContentBlock.bodyElement, + partyBuffOptions.map(options => options.picker && new options.picker(partyContentBlock.bodyElement, this.simUI.player, options.config as any, this.simUI)), ); } } @@ -438,3 +413,23 @@ export class SettingsTab extends SimTab { } } } + +export type ExclusivityTag = + 'Battle Elixir' + | 'Drums' + | 'Food' + | 'Pet Food' + | 'Alchohol' + | 'Guardian Elixir' + | 'Potion' + | 'Conjured' + | 'Spirit' + | 'MH Weapon Imbue' + | 'OH Weapon Imbue'; + +export interface ExclusiveEffect { + tags: Array; + changedEvent: TypedEvent; + isActive: () => boolean; + deactivate: (eventID: EventID) => void; +} diff --git a/ui/core/components/inputs/buffs_debuffs.ts b/ui/core/components/inputs/buffs_debuffs.ts index afb047c04a..9b2ff60838 100644 --- a/ui/core/components/inputs/buffs_debuffs.ts +++ b/ui/core/components/inputs/buffs_debuffs.ts @@ -1,318 +1,401 @@ -import { Stat } from '../../proto/common'; +import { Drums, PseudoStat, RaidBuffs, Stat, TristateEffect } from '../../proto/common'; import { ActionId } from '../../proto_utils/action_id'; import i18n from '../../../i18n/config'; import { makeBooleanDebuffInput, makeBooleanIndividualBuffInput, makeBooleanRaidBuffInput, + makeEnumValuePartyBuffInput, makeMultistateIndividualBuffInput, + makeMultistatePartyBuffInput, makeMultistateRaidBuffInput, + makeTristateRaidBuffInput, + makeTristatePartyBuffInput, + makeTristateDebuffInput, makeTristateIndividualBuffInput, + makeBooleanPartyBuffInput, } from '../icon_inputs'; import * as InputHelpers from '../input_helpers'; import { IconPicker } from '../pickers/icon_picker'; import { MultiIconPicker } from '../pickers/multi_icon_picker'; import { IconPickerStatOption, PickerStatOptions } from './stat_options'; +import { Role } from '../../player_spec' /////////////////////////////////////////////////////////////////////////// // RAID BUFFS /////////////////////////////////////////////////////////////////////////// -export const StatsBuff = InputHelpers.makeMultiIconInput( - [ - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(20217), fieldName: 'blessingOfKings' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(1126), fieldName: 'markOfTheWild' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(90363), fieldName: 'embraceOfTheShaleSpider' }), - ], - i18n.t('settings_tab.raid_buffs.stats'), -); +// Raid Buffs +export const ArcaneBrilliance = makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(27127), fieldName: 'arcaneBrilliance', label: "Arcane Brilliance"}); +export const Bloodlust = makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(2825), fieldName: 'bloodlust', label: 'Bloodlust'}); +export const DivineSpirit = makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(25312), impId: ActionId.fromSpellId(33182), fieldName: 'divineSpirit', label: 'Divine Spirit'}); +export const GiftOfTheWild = makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(26991), impId: ActionId.fromSpellId(17055), fieldName: 'giftOfTheWild', label: 'Gift of the Wild'}); +export const Thorns = makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(26992), impId: ActionId.fromSpellId(16840), fieldName: 'thorns', label: 'Thorns'}); +export const PowerWordFortitude = makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(25389), impId: ActionId.fromSpellId(14767), fieldName: 'powerWordFortitude', label: 'Power Word: Fortitude'}); +export const ShadowProtection = makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(39374), fieldName: 'shadowProtection', label: 'Shadow Protection'}); -export const AttackPowerBuff = InputHelpers.makeMultiIconInput( - [ - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(19506), fieldName: 'trueshotAura' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(6673), fieldName: 'battleShout' }), - ], - i18n.t('settings_tab.raid_buffs.attack_power'), -); +// // Party Buffs +export const AtieshMage = makeMultistatePartyBuffInput(ActionId.fromSpellId(28142), 5, 'atieshMage', 'Atiesh - Mage'); +export const AtieshWarlock = makeMultistatePartyBuffInput( ActionId.fromSpellId(28143), 5, 'atieshWarlock', 'Atiesh - Warlock'); +export const BraidedEterniumChain = makeBooleanPartyBuffInput({actionId: ActionId.fromSpellId(31025), fieldName: 'braidedEterniumChain', label: 'Braided Eternium Chain'}); +export const ChainOfTheTwilightOwl = makeBooleanPartyBuffInput({actionId: ActionId.fromSpellId(31035), fieldName: 'chainOfTheTwilightOwl', label: 'Chain of the Twilight Owl'}); +export const CommandingShout = makeTristatePartyBuffInput({actionId: ActionId.fromSpellId(469), impId: ActionId.fromSpellId(12861), fieldName: 'commandingShout', label: 'Commanding Shout'}); +export const DevotionAura = makeTristatePartyBuffInput({actionId: ActionId.fromSpellId(27149), impId: ActionId.fromSpellId(20142), fieldName: 'devotionAura', label: 'Devotion Aura'}); +export const DraeneiRacialCaster = makeBooleanPartyBuffInput({actionId: ActionId.fromSpellId(28878), fieldName: 'draeneiRacialCaster', label: 'Inspiring Presense - Caster'}); +export const DraeneiRacialMelee = makeBooleanPartyBuffInput({actionId: ActionId.fromSpellId(6562), fieldName:'draeneiRacialMelee', label: 'Inspiring Presense - Melee'}); +export const EyeOfTheNight = makeBooleanPartyBuffInput({actionId: ActionId.fromSpellId(31033), fieldName: 'eyeOfTheNight', label: 'Eye of the Night'}); +export const FerociousInspiration = makeMultistatePartyBuffInput(ActionId.fromSpellId(34460), 5, 'ferociousInspiration', 'Ferocious Inspirtation'); +export const JadePendantOfBlasting = makeBooleanPartyBuffInput({actionId: ActionId.fromSpellId(25607), fieldName: 'jadePendantOfBlasting', label: 'Jade Pendant of Blasting'}); +export const LeaderOfThePack = makeTristatePartyBuffInput({actionId: ActionId.fromSpellId(17007), impId: ActionId.fromItemId(32387), fieldName: 'leaderOfThePack', label: 'Leader of the Pack'}); +export const ManaSpringTotem = makeTristatePartyBuffInput({actionId: ActionId.fromSpellId(25570), impId: ActionId.fromSpellId(16208), fieldName: 'manaSpringTotem', label: 'Mana Spring Totem'}); +export const ManaTideTotem = makeMultistatePartyBuffInput(ActionId.fromSpellId(16190), 5, 'manaTideTotems', 'Mana Tide Totem'); +export const MoonkinAura = makeTristatePartyBuffInput({actionId: ActionId.fromSpellId(24907), impId: ActionId.fromItemId(32387), fieldName: 'moonkinAura', label: 'Moonkin Aura'}); +export const RetributionAura = makeTristatePartyBuffInput({actionId: ActionId.fromSpellId(27150), impId: ActionId.fromSpellId(20092), fieldName: 'retributionAura', label: 'Retribution Aura'}); +export const SanctityAura = makeTristatePartyBuffInput({actionId: ActionId.fromSpellId(20218), impId: ActionId.fromSpellId(31870), fieldName: 'sanctityAura', label: 'Sanctity Aura'}); +export const TotemOfWrath = makeMultistatePartyBuffInput(ActionId.fromSpellId(30706), 5, 'totemOfWrath', 'Totem of Wrath'); +export const TrueshotAura = makeBooleanPartyBuffInput({actionId: ActionId.fromSpellId(27066), fieldName: 'trueshotAura', label: 'Trueshot Aura'}); +export const WrathOfAirTotem = makeTristatePartyBuffInput({actionId: ActionId.fromSpellId(3738), impId: ActionId.fromSpellId(37212), fieldName: 'wrathOfAirTotem', label: 'Wrath of Air Totem'}); +export const BloodPact = makeTristatePartyBuffInput({actionId: ActionId.fromSpellId(27268), impId: ActionId.fromSpellId(18696), fieldName: 'bloodPact', label: 'Bloodpact'}); -export const AttackSpeedBuff = InputHelpers.makeMultiIconInput( - [ - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(55610), fieldName: 'unholyAura' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(128433), fieldName: 'serpentsSwiftness' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(113742), fieldName: 'swiftbladesCunning' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(30809), fieldName: 'unleashedRage' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(128432), fieldName: 'cacklingHowl' }), - ], - i18n.t('settings_tab.raid_buffs.attack_speed'), -); +export const DrumsOfBattleBuff = makeEnumValuePartyBuffInput(ActionId.fromItemId(185848), 'drums', Drums.DrumsOfBattle, ['Drums']); +export const DrumsOfRestorationBuff = makeEnumValuePartyBuffInput(ActionId.fromItemId(185850), 'drums', Drums.DrumsOfRestoration, ['Drums']); -export const SpellPowerBuff = InputHelpers.makeMultiIconInput( - [ - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(1459), fieldName: 'arcaneBrilliance' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(126309), fieldName: 'stillWater' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(77747), fieldName: 'burningWrath' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(109773), fieldName: 'darkIntent' }), - ], - i18n.t('settings_tab.raid_buffs.spell_power'), -); +// Individual Buffs +export const BlessingOfKings = makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(25898), fieldName: 'blessingOfKings', label: 'Blessing of Kings'}); +export const BlessingOfMight = makeTristateIndividualBuffInput({actionId: ActionId.fromSpellId(27140), impId: ActionId.fromSpellId(20048), fieldName: 'blessingOfMight', label: 'Blessing of Might'}); +export const BlessingOfSalvation = makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(25895), fieldName: 'blessingOfSalvation', label: 'Blessing of Salvation'}); +export const BlessingOfSanctuary = makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(27169), fieldName: 'blessingOfSanctuary', label: 'BlessingOfSanctuary'}); +export const BlessingOfWisdom = makeTristateIndividualBuffInput({actionId: ActionId.fromSpellId(27143), impId: ActionId.fromSpellId(20245), fieldName: 'blessingOfWisdom', label: 'Blessing of Wisdom'}); +export const Innervate = makeMultistateIndividualBuffInput({actionId: ActionId.fromSpellId(29166), numStates: 11, fieldName: 'innervates', label: 'Innervates'}); +export const PowerInfusion = makeMultistateIndividualBuffInput({actionId: ActionId.fromSpellId(10060), numStates: 11, fieldName: 'powerInfusions', label: 'Power Infusions'}); +export const UnleashedRage = makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(30811), fieldName: 'unleashedRage', label: 'Unleashed Rage'}); -export const SpellHasteBuff = InputHelpers.makeMultiIconInput( - [ - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(24907), fieldName: 'moonkinAura' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(49868), fieldName: 'mindQuickening' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(51470), fieldName: 'elementalOath' }), - ], - i18n.t('settings_tab.raid_buffs.spell_haste'), -); - -export const CritBuff = InputHelpers.makeMultiIconInput( - [ - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(17007), fieldName: 'leaderOfThePack' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(24604), fieldName: 'furiousHowl' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(90309), fieldName: 'terrifyingRoar' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(1459), fieldName: 'arcaneBrilliance' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(126309), fieldName: 'stillWater' }), - ], - i18n.t('settings_tab.raid_buffs.crit_percent'), -); - -export const StaminaBuff = InputHelpers.makeMultiIconInput( - [ - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(469), fieldName: 'commandingShout' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(109773), fieldName: 'darkIntent' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(21562), fieldName: 'powerWordFortitude' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(90364), fieldName: 'qirajiFortitude' }), - ], - i18n.t('settings_tab.raid_buffs.stamina'), -); - -// Misc Buffs -// export const ManaTideTotem = makeMultistateRaidBuffInput({ actionId: ActionId.fromSpellId(16190), numStates: 5, fieldName: 'manaTideTotemCount' }); - -// External Damage Cooldowns -export const MajorHasteBuff = makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(2825), fieldName: 'bloodlust', label: i18n.t('settings_tab.external_damage_cooldowns.bloodlust') }); -// export const Skullbanner = makeMultistateRaidBuffInput({ -// actionId: ActionId.fromSpellId(114207), -// numStates: 11, -// fieldName: 'skullBannerCount', -// label: i18n.t('settings_tab.external_damage_cooldowns.skull_banner'), -// }); -// export const StormLashTotem = makeMultistateRaidBuffInput({ -// actionId: ActionId.fromSpellId(120668), -// numStates: 11, -// fieldName: 'stormlashTotemCount', -// label: i18n.t('settings_tab.external_damage_cooldowns.stormlash_totem'), -// }); -// export const TricksOfTheTrade = makeBooleanIndividualBuffInput({ -// actionId: ActionId.fromSpellId(57933), -// fieldName: 'tricksOfTheTrade', -// label: i18n.t('settings_tab.external_damage_cooldowns.tricks_of_the_trade'), -// }); -// export const UnholyFrenzy = makeMultistateIndividualBuffInput({ -// actionId: ActionId.fromSpellId(49016), -// numStates: 11, -// fieldName: 'unholyFrenzyCount', -// label: i18n.t('settings_tab.external_damage_cooldowns.unholy_frenzy'), -// }); -// export const ShatteringThrow = makeMultistateIndividualBuffInput({ -// actionId: ActionId.fromSpellId(1249459), -// numStates: 11, -// fieldName: 'shatteringThrowCount', -// label: i18n.t('settings_tab.external_damage_cooldowns.shattering_throw'), -// }); - -// External Defensive Cooldowns -// TODO: Look at these, what we want and how to structure them for multiple available -// export const VigilanceCount = makeMultistateIndividualBuffInput({ -// actionId: ActionId.fromSpellId(114030), -// numStates: 11, -// fieldName: 'vigilanceCount', -// label: i18n.t('settings_tab.external_defensive_cooldowns.vigilance'), -// }); -// export const DevotionAuraCount = makeMultistateIndividualBuffInput({ -// actionId: ActionId.fromSpellId(31821), -// numStates: 11, -// fieldName: 'devotionAuraCount', -// label: i18n.t('settings_tab.external_defensive_cooldowns.devotion_aura'), -// }); -// export const PainSuppressionCount = makeMultistateIndividualBuffInput({ -// actionId: ActionId.fromSpellId(33206), -// numStates: 11, -// fieldName: 'painSuppressionCount', -// label: i18n.t('settings_tab.external_defensive_cooldowns.pain_suppression'), -// }); -// // export const GuardianSpirits = makeMultistateIndividualBuffInput({ actionId: ActionId.fromSpellId(47788), numStates: 11, fieldName: 'guardianSpirits' }); -// export const RallyingCryCount = makeMultistateIndividualBuffInput({ -// actionId: ActionId.fromSpellId(97462), -// numStates: 11, -// fieldName: 'rallyingCryCount', -// label: i18n.t('settings_tab.external_defensive_cooldowns.rallying_cry'), -// }); -/////////////////////////////////////////////////////////////////////////// -// DEBUFFS -/////////////////////////////////////////////////////////////////////////// - -// export const MajorArmorDebuff = makeBooleanDebuffInput({ actionId: ActionId.fromSpellId(113746), fieldName: 'weakenedArmor', label: i18n.t('settings_tab.debuffs.armor_reduction') }); - -// export const DamageReduction = makeBooleanDebuffInput({ actionId: ActionId.fromSpellId(115798), fieldName: 'weakenedBlows', label: i18n.t('settings_tab.debuffs.phys_dmg_reduction') }); - -export const CastSpeedDebuff = InputHelpers.makeMultiIconInput( - [ - // makeBooleanDebuffInput({ actionId: ActionId.fromSpellId(73975), fieldName: 'necroticStrike' }), - // makeBooleanDebuffInput({ actionId: ActionId.fromSpellId(58604), fieldName: 'lavaBreath' }), - // makeBooleanDebuffInput({ actionId: ActionId.fromSpellId(50274), fieldName: 'sporeCloud' }), - // makeBooleanDebuffInput({ actionId: ActionId.fromSpellId(5761), fieldName: 'mindNumbingPoison' }), - // makeBooleanDebuffInput({ actionId: ActionId.fromSpellId(31589), fieldName: 'slow' }), - // makeBooleanDebuffInput({ actionId: ActionId.fromSpellId(109466), fieldName: 'curseOfEnfeeblement' }), - ], - i18n.t('settings_tab.debuffs.cast_speed'), -); - -// export const PhysicalDamageDebuff = makeBooleanDebuffInput({ -// actionId: ActionId.fromSpellId(81326), -// fieldName: 'physicalVulnerability', -// label: i18n.t('settings_tab.debuffs.phys_dmg'), -// }); - -export const SpellDamageDebuff = InputHelpers.makeMultiIconInput( - [ - // makeBooleanDebuffInput({ actionId: ActionId.fromSpellId(24844), fieldName: 'lightningBreath' }), - // makeBooleanDebuffInput({ actionId: ActionId.fromSpellId(1490), fieldName: 'curseOfElements' }), - // makeBooleanDebuffInput({ actionId: ActionId.fromSpellId(58410), fieldName: 'masterPoisoner' }), - // makeBooleanDebuffInput({ actionId: ActionId.fromSpellId(34889), fieldName: 'fireBreath' }), - ], - i18n.t('settings_tab.debuffs.spell_dmg'), -); - -/////////////////////////////////////////////////////////////////////////// -// CONFIGS -/////////////////////////////////////////////////////////////////////////// +export const PARTY_BUFFS_CONFIG = [ + { + config: BloodPact, + picker: IconPicker, + stats: [Stat.StatStamina] + }, + { + config: CommandingShout, + picker: IconPicker, + stats: [Stat.StatHealth] + }, + { + config: DevotionAura, + picker: IconPicker, + stats: [Stat.StatArmor] + }, + { + config: FerociousInspiration, + picker: IconPicker, + stats: [] + }, + { + config: LeaderOfThePack, + picker: IconPicker, + stats: [] + }, + { + config: ManaSpringTotem, + picker: IconPicker, + stats: [Stat.StatMP5] + }, + { + config: ManaTideTotem, + picker: IconPicker, + stats: [] + }, + { + config: MoonkinAura, + picker: IconPicker, + stats: [] + }, + { + config: RetributionAura, + picker: IconPicker, + stats: [] + }, + { + config: SanctityAura, + picker: IconPicker, + stats: [] + }, + { + config: TotemOfWrath, + picker: IconPicker, + stats: [] + }, + { + config: TrueshotAura, + picker: IconPicker, + stats: [] + }, + { + config: WrathOfAirTotem, + picker: IconPicker, + stats: [Stat.StatAgility] + }, + { + config: UnleashedRage, + picker: IconPicker, + stats: [Stat.StatAttackPower] + }, + { + config: AtieshMage, + picker: IconPicker, + stats: [Stat.StatSpellDamage, Stat.StatHealingPower] + }, + { + config: AtieshWarlock, + picker: IconPicker, + stats: [Stat.StatSpellDamage, Stat.StatHealingPower] + }, + { + config: BraidedEterniumChain, + picker: IconPicker, + stats: [Stat.StatSpellCritRating] + }, + { + config: ChainOfTheTwilightOwl, + picker: IconPicker, + stats: [Stat.StatSpellCritRating] + }, + { + config: DraeneiRacialCaster, + picker: IconPicker, + stats: [Stat.StatSpellHitRating] + }, + { + config: DraeneiRacialMelee, + picker: IconPicker, + stats: [Stat.StatMeleeHitRating] + }, + { + config: EyeOfTheNight, + picker: IconPicker, + stats: [Stat.StatSpellDamage] + }, + { + config: JadePendantOfBlasting, + picker: IconPicker, + stats: [Stat.StatSpellDamage], + }, +] as PickerStatOptions[]; -export const RAID_BUFFS_CONFIG = [ - // Standard buffs +export const BUFFS_CONFIG = [ + // Raid Buffs { - config: StatsBuff, - picker: MultiIconPicker, - stats: [Stat.StatStrength, Stat.StatAgility, Stat.StatIntellect], + config: ArcaneBrilliance, + picker: IconPicker, + stats: [Stat.StatIntellect], }, { - config: AttackPowerBuff, - picker: MultiIconPicker, - stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower], + config: BlessingOfKings, + picker: IconPicker, + stats: [ + Stat.StatAgility, + Stat.StatIntellect, + Stat.StatSpirit, + Stat.StatStamina, + Stat.StatStrength, + ] }, { - config: AttackSpeedBuff, - picker: MultiIconPicker, - stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower], + config: Bloodlust, + picker: IconPicker, + stats: [], }, { - config: SpellPowerBuff, - picker: MultiIconPicker, - stats: [Stat.StatSpellPower], + config: DivineSpirit, + picker: IconPicker, + stats: [Stat.StatSpirit], }, { - config: SpellHasteBuff, - picker: MultiIconPicker, - stats: [Stat.StatSpellPower], + config: GiftOfTheWild, + picker: IconPicker, + stats: [Stat.StatArmor, Stat.StatStrength, Stat.StatAgility, Stat.StatIntellect, Stat.StatSpirit, Stat.StatStamina], }, { - config: StaminaBuff, - picker: MultiIconPicker, + config: Thorns, + picker: IconPicker, + stats: [], + }, + { + config: PowerWordFortitude, + picker: IconPicker, stats: [Stat.StatStamina], }, + { + config: BlessingOfMight, + picker: IconPicker, + stats: [Stat.StatAttackPower] + }, + { + config: BlessingOfWisdom, + picker: IconPicker, + stats: [Stat.StatMP5] + }, + { + config: Innervate, + picker: IconPicker, + stats: [Stat.StatMP5] + }, + { + config: PowerInfusion, + picker: IconPicker, + stats: [] + } ] as PickerStatOptions[]; -export const RAID_BUFFS_MISC_CONFIG = [ - // { - // config: ManaTideTotem, - // picker: IconPicker, - // stats: [Stat.StatSpirit], - // }, -] as IconPickerStatOption[]; - -export const RAID_BUFFS_EXTERNAL_DAMAGE_COOLDOWN = [ - // { - // config: MajorHasteBuff, - // picker: IconPicker, - // stats: [Stat.StatHasteRating], - // }, - // { - // config: Skullbanner, - // picker: IconPicker, - // stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower, Stat.StatSpellPower], - // }, - // { - // config: StormLashTotem, - // picker: IconPicker, - // stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower, Stat.StatSpellPower], - // }, - // { - // config: TricksOfTheTrade, - // picker: IconPicker, - // stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower, Stat.StatSpellPower], - // }, - // { - // config: UnholyFrenzy, - // picker: IconPicker, - // stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower], - // }, - // { - // config: ShatteringThrow, - // picker: IconPicker, - // stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower], - // }, -] as IconPickerStatOption[]; -export const RAID_BUFFS_EXTERNAL_DEFENSIVE_COOLDOWN = [ - // { - // config: VigilanceCount, - // picker: IconPicker, - // stats: [Stat.StatStamina], - // }, - // { - // config: DevotionAuraCount, - // picker: IconPicker, - // stats: [Stat.StatStamina], - // }, - // { - // config: PainSuppressionCount, - // picker: IconPicker, - // stats: [Stat.StatStamina], - // }, - // { - // config: RallyingCryCount, - // picker: IconPicker, - // stats: [Stat.StatStamina], - // }, -] as IconPickerStatOption[]; +// Debuffs +export const BloodFrenzy = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(29859), fieldName: 'bloodFrenzy', label: 'Blood Frenzy'}); +export const HuntersMark = makeTristateDebuffInput({actionId: ActionId.fromSpellId(14325), impId: ActionId.fromSpellId(19425), fieldName: 'huntersMark', label: 'Hunter\'s Mark'}); +export const ImprovedScorch = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(12873), fieldName: 'improvedScorch', label: 'Improved Scorch'}); +export const ImprovedSealOfTheCrusader = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(20337), fieldName: 'improvedSealOfTheCrusader', label: 'Improved Seal of the Crusader'}); +export const JudgementOfWisdom = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(27164), fieldName: 'judgementOfWisdom', label: 'Judgement of Wisdom'}); +export const JudgementOfLight = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(27163), fieldName: 'judgementOfLight', label: 'Judgement of Light'}); +export const Mangle = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(33876), fieldName: 'mangle', label: 'Mangle'}); +export const Misery = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(33195), fieldName: 'misery', label: 'Misery'}); +export const ShadowWeaving = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(15334), fieldName: 'shadowWeaving', label: 'Shadow Weaving'}); +export const CurseOfElements = makeTristateDebuffInput({actionId: ActionId.fromSpellId(27228), impId: ActionId.fromSpellId(32484), fieldName: 'curseOfElements', label: 'Curse of Elements'}); +export const CurseOfRecklessness = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(27226), fieldName: 'curseOfRecklessness', label: 'Curse of Recklessness'}); +export const FaerieFire = makeTristateDebuffInput({actionId: ActionId.fromSpellId(26993), impId: ActionId.fromSpellId(33602), fieldName: 'faerieFire', label: 'Faerie Fire'}); +export const ExposeArmor = makeTristateDebuffInput({actionId: ActionId.fromSpellId(26866), impId: ActionId.fromSpellId(14169), fieldName: 'exposeArmor', label: 'Expose Armor'}); +export const SunderArmor = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(25225), fieldName: 'sunderArmor', label: 'Sunder Armor'}); +export const WintersChill = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(28595), fieldName: 'wintersChill', label: 'Winter\'s Chill'}); +export const GiftOfArthas = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(11374), fieldName: 'giftOfArthas', label: 'Gift of Arthas'}); +export const DemoralizingRoar = makeTristateDebuffInput({actionId: ActionId.fromSpellId(26998), impId: ActionId.fromSpellId(16862), fieldName: 'demoralizingRoar', label: 'Demoralizing Roar'}); +export const DemoralizingShout = makeTristateDebuffInput({actionId: ActionId.fromSpellId(25203), impId: ActionId.fromSpellId(12879), fieldName: 'demoralizingShout', label: 'Demoralizing Shout'}); +export const Screech = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(27051), fieldName: 'screech', label: 'Screech'}); +export const ThunderClap = makeTristateDebuffInput({actionId: ActionId.fromSpellId(25264), impId: ActionId.fromSpellId(12666), fieldName: 'thunderClap', label: 'Thunder Clap'}); +export const InsectSwarm = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(27013), fieldName: 'insectSwarm', label: 'Insect Swarm'}); +export const ScorpidSting = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(3043), fieldName: 'scorpidSting', label: 'Scorpid Sting'}); +export const ShadowEmbrace = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(32394), fieldName: 'shadowEmbrace', label: 'Shadow Embrace'}); export const DEBUFFS_CONFIG = [ - // { - // config: MajorArmorDebuff, - // picker: IconPicker, - // stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower], - // }, - // { - // config: PhysicalDamageDebuff, - // picker: IconPicker, - // stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower], - // }, - { - config: SpellDamageDebuff, - picker: MultiIconPicker, - // Enabled for all specs because it affects Stormlash Totem - stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower, Stat.StatSpellPower], - }, - // { - // config: DamageReduction, - // picker: IconPicker, - // stats: [Stat.StatStamina], - // }, - { - config: CastSpeedDebuff, - picker: MultiIconPicker, - stats: [Stat.StatStamina], + { + config: BloodFrenzy, + picker: IconPicker, + stats: [] }, -] as PickerStatOptions[]; + { + config: HuntersMark, + picker: IconPicker, + stats: [Stat.StatRangedAttackPower, Stat.StatAttackPower], + roles: [Role.MELEE, Role.RANGED, Role.TANK] + }, + { + config: ImprovedScorch, + picker: IconPicker, + stats: [], + roles: [Role.CASTER] + }, + { + config: ImprovedSealOfTheCrusader, + picker: IconPicker, + stats: [Stat.StatMeleeCritRating, Stat.StatSpellCritRating] + }, + { + config: JudgementOfLight, + picker: IconPicker, + stats: [], + roles: [Role.TANK] + }, + { + config: JudgementOfWisdom, + picker: IconPicker, + stats: [], + roles: [Role.CASTER, Role.HEALER] + }, + { + config: Mangle, + picker: IconPicker, + stats: [Role.MELEE] + }, + { + config: Misery, + picker: IconPicker, + stats: [Role.CASTER] + }, + { + config: ShadowWeaving, + picker: IconPicker, + stats: [Role.CASTER], + }, + { + config: CurseOfElements, + picker: IconPicker, + stats: [Role.CASTER] + }, + { + config: CurseOfRecklessness, + picker: IconPicker, + stats: [Role.MELEE, Role.RANGED, Role.TANK] + }, + { + config: FaerieFire, + picker: IconPicker, + stats: [Role.MELEE, Role.RANGED, Role.TANK] + }, + { + config: ExposeArmor, + picker: IconPicker, + stats: [Role.MELEE, Role.RANGED, Role.TANK] + }, + { + config: SunderArmor, + picker: IconPicker, + stats: [Role.MELEE, Role.RANGED, Role.TANK] + }, + { + config: WintersChill, + picker: IconPicker, + stats: [Role.CASTER] + }, + { + config: GiftOfArthas, + picker: IconPicker, + stats: [Role.MELEE, Role.RANGED, Role.TANK] + }, + { + config: DemoralizingRoar, + picker: IconPicker, + stats: [Role.TANK] + }, + { + config: DemoralizingShout, + picker: IconPicker, + stats: [Role.TANK] + }, + { + config: Screech, + picker: IconPicker, + stats: [Role.TANK] + }, + { + config: ThunderClap, + picker: IconPicker, + stats: [Role.TANK] + }, + { + config: InsectSwarm, + picker: IconPicker, + stats: [Role.TANK] + }, + { + config: ScorpidSting, + picker: IconPicker, + stats: [Role.TANK] + }, + { + config: ShadowEmbrace, + picker: IconPicker, + stats: [Role.TANK] + } +] as PickerStatOptions[] export const DEBUFFS_MISC_CONFIG = [] as IconPickerStatOption[]; + + diff --git a/ui/core/components/inputs/stat_options.ts b/ui/core/components/inputs/stat_options.ts index dcb6f37997..3e0ba049fc 100644 --- a/ui/core/components/inputs/stat_options.ts +++ b/ui/core/components/inputs/stat_options.ts @@ -2,9 +2,11 @@ import { IndividualSimUI } from '../../individual_sim_ui'; import { Player } from '../../player'; import { Faction, Stat } from '../../proto/common'; import { ActionId } from '../../proto_utils/action_id'; +import { BooleanPicker, BooleanPickerConfig } from '../pickers/boolean_picker'; import { IconEnumPicker, IconEnumPickerConfig } from '../pickers/icon_enum_picker'; import { IconPicker, IconPickerConfig } from '../pickers/icon_picker'; import { MultiIconPicker, MultiIconPickerConfig } from '../pickers/multi_icon_picker'; +import { Role } from '../../player_spec'; export interface ActionInputConfig { actionId: ActionId; @@ -15,10 +17,12 @@ export interface ActionInputConfig { export interface StatOption { stats: Array; + roles?: Array; } export interface ItemStatOption extends StatOption { config: ActionInputConfig; + } export interface PickerStatOption extends StatOption { @@ -32,8 +36,10 @@ export interface MultiIconPickerStatOption extends PickerStatOption, any>, IconEnumPickerConfig, any>> {} +export interface BooleanPickerStatOption extends PickerStatOption>, BooleanPickerConfig>> {} + export type ItemStatOptions = ItemStatOption; -export type PickerStatOptions = IconPickerStatOption | MultiIconPickerStatOption | IconEnumPickerStatOption; +export type PickerStatOptions = IconPickerStatOption | MultiIconPickerStatOption | IconEnumPickerStatOption | BooleanPickerStatOption; export type StatOptions | PickerStatOptions> = Array; export function relevantStatOptions | PickerStatOptions>( @@ -45,7 +51,9 @@ export function relevantStatOptions | option => option.stats.length == 0 || option.stats.some(stat => simUI.individualConfig.epStats.includes(stat)) || - simUI.individualConfig.includeBuffDebuffInputs.includes(option.config), + simUI.individualConfig.includeBuffDebuffInputs.includes(option.config) || + (option.roles && option.roles.includes(simUI.player.playerSpec.role)) + ) .filter(option => !simUI.individualConfig.excludeBuffDebuffInputs.includes(option.config)); } diff --git a/ui/core/individual_sim_ui.tsx b/ui/core/individual_sim_ui.tsx index 054b3a9383..0c84a5e702 100644 --- a/ui/core/individual_sim_ui.tsx +++ b/ui/core/individual_sim_ui.tsx @@ -429,7 +429,6 @@ export abstract class IndividualSimUI extends SimUI { private addSidebarComponents() { this.raidSimResultsManager = addRaidSimAction(this); this.sim.waitForInit().then(() => { - console.log('its inited') this.epWeightsModal = addStatWeightsAction(this, this.statWeightActionSettings); }); diff --git a/ui/core/player_spec.ts b/ui/core/player_spec.ts index 2bbb12e887..8eddb6fe21 100644 --- a/ui/core/player_spec.ts +++ b/ui/core/player_spec.ts @@ -22,8 +22,17 @@ export abstract class PlayerSpec { abstract readonly isHealingSpec: boolean; abstract readonly isRangedDpsSpec: boolean; abstract readonly isMeleeDpsSpec: boolean; + abstract readonly role: Role abstract readonly canDualWield: boolean; abstract getIcon(size: IconSize): string; } + +export enum Role { + CASTER, + HEALER, + MELEE, + RANGED, + TANK +} diff --git a/ui/core/player_specs/druid.ts b/ui/core/player_specs/druid.ts index 9b0d0d076a..aec97b178d 100644 --- a/ui/core/player_specs/druid.ts +++ b/ui/core/player_specs/druid.ts @@ -1,5 +1,5 @@ import { IconSize } from '../player_class'; -import { PlayerSpec } from '../player_spec'; +import { PlayerSpec, Role } from '../player_spec'; import { Class, Spec } from '../proto/common'; import { getSpecSiteUrl } from '../proto_utils/utils'; @@ -14,6 +14,7 @@ export class BalanceDruid extends PlayerSpec { static isHealingSpec = false; static isRangedDpsSpec = true; static isMeleeDpsSpec = false; + static role = Role.CASTER static canDualWield = false; @@ -27,6 +28,7 @@ export class BalanceDruid extends PlayerSpec { readonly isHealingSpec = BalanceDruid.isHealingSpec; readonly isRangedDpsSpec = BalanceDruid.isRangedDpsSpec; readonly isMeleeDpsSpec = BalanceDruid.isMeleeDpsSpec; + readonly role = BalanceDruid.role readonly canDualWield = BalanceDruid.canDualWield; @@ -50,6 +52,7 @@ export class FeralCatDruid extends PlayerSpec { static isHealingSpec = false; static isRangedDpsSpec = false; static isMeleeDpsSpec = true; + static role = Role.MELEE static canDualWield = false; @@ -63,6 +66,7 @@ export class FeralCatDruid extends PlayerSpec { readonly isHealingSpec = FeralCatDruid.isHealingSpec; readonly isRangedDpsSpec = FeralCatDruid.isRangedDpsSpec; readonly isMeleeDpsSpec = FeralCatDruid.isMeleeDpsSpec; + readonly role = FeralCatDruid.role readonly canDualWield = FeralCatDruid.canDualWield; @@ -86,6 +90,7 @@ export class FeralBearDruid extends PlayerSpec { static isHealingSpec = false; static isRangedDpsSpec = false; static isMeleeDpsSpec = true; + static role = Role.TANK static canDualWield = false; @@ -99,6 +104,7 @@ export class FeralBearDruid extends PlayerSpec { readonly isHealingSpec = FeralBearDruid.isHealingSpec; readonly isRangedDpsSpec = FeralBearDruid.isRangedDpsSpec; readonly isMeleeDpsSpec = FeralBearDruid.isMeleeDpsSpec; + readonly role = FeralBearDruid.role readonly canDualWield = FeralBearDruid.canDualWield; @@ -122,6 +128,7 @@ export class RestorationDruid extends PlayerSpec { static isHealingSpec = true; static isRangedDpsSpec = false; static isMeleeDpsSpec = false; + static role = Role.HEALER static canDualWield = false; @@ -135,6 +142,7 @@ export class RestorationDruid extends PlayerSpec { readonly isHealingSpec = RestorationDruid.isHealingSpec; readonly isRangedDpsSpec = RestorationDruid.isRangedDpsSpec; readonly isMeleeDpsSpec = RestorationDruid.isMeleeDpsSpec; + readonly role = RestorationDruid.role readonly canDualWield = RestorationDruid.canDualWield; diff --git a/ui/core/player_specs/hunter.ts b/ui/core/player_specs/hunter.ts index 196c3f7de6..1278c4f682 100644 --- a/ui/core/player_specs/hunter.ts +++ b/ui/core/player_specs/hunter.ts @@ -1,5 +1,5 @@ import { IconSize } from '../player_class'; -import { PlayerSpec } from '../player_spec'; +import { PlayerSpec, Role } from '../player_spec'; import { Class, Spec } from '../proto/common'; import { getSpecSiteUrl } from '../proto_utils/utils'; @@ -14,6 +14,7 @@ export class Hunter extends PlayerSpec { static isHealingSpec = false; static isRangedDpsSpec = true; static isMeleeDpsSpec = false; + static role = Role.RANGED static canDualWield = true; @@ -27,7 +28,7 @@ export class Hunter extends PlayerSpec { readonly isHealingSpec = Hunter.isHealingSpec; readonly isRangedDpsSpec = Hunter.isRangedDpsSpec; readonly isMeleeDpsSpec = Hunter.isMeleeDpsSpec; - + readonly role = Hunter.role readonly canDualWield = Hunter.canDualWield; static getIcon = (size: IconSize): string => { diff --git a/ui/core/player_specs/mage.ts b/ui/core/player_specs/mage.ts index 277fd7aa09..c10b728c6e 100644 --- a/ui/core/player_specs/mage.ts +++ b/ui/core/player_specs/mage.ts @@ -1,5 +1,5 @@ import { IconSize } from '../player_class'; -import { PlayerSpec } from '../player_spec'; +import { PlayerSpec, Role } from '../player_spec'; import { Class, Spec } from '../proto/common'; import { getSpecSiteUrl } from '../proto_utils/utils'; @@ -14,6 +14,7 @@ export class Mage extends PlayerSpec { static isHealingSpec = false; static isRangedDpsSpec = true; static isMeleeDpsSpec = false; + static role = Role.CASTER static canDualWield = false; @@ -27,6 +28,7 @@ export class Mage extends PlayerSpec { readonly isHealingSpec = Mage.isHealingSpec; readonly isRangedDpsSpec = Mage.isRangedDpsSpec; readonly isMeleeDpsSpec = Mage.isMeleeDpsSpec; + readonly role = Mage.role readonly canDualWield = Mage.canDualWield; diff --git a/ui/core/player_specs/paladin.ts b/ui/core/player_specs/paladin.ts index 3d1f6720f4..af76535a75 100644 --- a/ui/core/player_specs/paladin.ts +++ b/ui/core/player_specs/paladin.ts @@ -1,5 +1,5 @@ import { IconSize } from '../player_class'; -import { PlayerSpec } from '../player_spec'; +import { PlayerSpec, Role } from '../player_spec'; import { Class, Spec } from '../proto/common'; import { getSpecSiteUrl } from '../proto_utils/utils'; @@ -14,6 +14,7 @@ export class HolyPaladin extends PlayerSpec { static isHealingSpec = true; static isRangedDpsSpec = false; static isMeleeDpsSpec = false; + static role = Role.HEALER static canDualWield = false; @@ -27,6 +28,7 @@ export class HolyPaladin extends PlayerSpec { readonly isHealingSpec = HolyPaladin.isHealingSpec; readonly isRangedDpsSpec = HolyPaladin.isRangedDpsSpec; readonly isMeleeDpsSpec = HolyPaladin.isMeleeDpsSpec; + readonly role = HolyPaladin.role readonly canDualWield = HolyPaladin.canDualWield; @@ -50,6 +52,7 @@ export class ProtectionPaladin extends PlayerSpec { static isHealingSpec = false; static isRangedDpsSpec = false; static isMeleeDpsSpec = false; + static role = Role.TANK static canDualWield = false; @@ -63,6 +66,7 @@ export class ProtectionPaladin extends PlayerSpec { readonly isHealingSpec = ProtectionPaladin.isHealingSpec; readonly isRangedDpsSpec = ProtectionPaladin.isRangedDpsSpec; readonly isMeleeDpsSpec = ProtectionPaladin.isMeleeDpsSpec; + readonly role = ProtectionPaladin.role readonly canDualWield = ProtectionPaladin.canDualWield; @@ -86,7 +90,7 @@ export class RetributionPaladin extends PlayerSpec static isHealingSpec = false; static isRangedDpsSpec = false; static isMeleeDpsSpec = true; - + static role = Role.MELEE static canDualWield = false; readonly specIndex = RetributionPaladin.specIndex; @@ -99,6 +103,7 @@ export class RetributionPaladin extends PlayerSpec readonly isHealingSpec = RetributionPaladin.isHealingSpec; readonly isRangedDpsSpec = RetributionPaladin.isRangedDpsSpec; readonly isMeleeDpsSpec = RetributionPaladin.isMeleeDpsSpec; + readonly role = RetributionPaladin.role readonly canDualWield = RetributionPaladin.canDualWield; diff --git a/ui/core/player_specs/priest.ts b/ui/core/player_specs/priest.ts index 0174274105..eb23df328b 100644 --- a/ui/core/player_specs/priest.ts +++ b/ui/core/player_specs/priest.ts @@ -1,5 +1,5 @@ import { IconSize } from '../player_class'; -import { PlayerSpec } from '../player_spec'; +import { PlayerSpec, Role } from '../player_spec'; import { Class, Spec } from '../proto/common'; import { getSpecSiteUrl } from '../proto_utils/utils'; @@ -14,7 +14,7 @@ export class DisciplinePriest extends PlayerSpec { static isHealingSpec = true; static isRangedDpsSpec = false; static isMeleeDpsSpec = false; - + static role = Role.HEALER static canDualWield = false; readonly specIndex = DisciplinePriest.specIndex; @@ -27,6 +27,7 @@ export class DisciplinePriest extends PlayerSpec { readonly isHealingSpec = DisciplinePriest.isHealingSpec; readonly isRangedDpsSpec = DisciplinePriest.isRangedDpsSpec; readonly isMeleeDpsSpec = DisciplinePriest.isMeleeDpsSpec; + readonly role = DisciplinePriest.role readonly canDualWield = DisciplinePriest.canDualWield; @@ -50,7 +51,7 @@ export class HolyPriest extends PlayerSpec { static isHealingSpec = true; static isRangedDpsSpec = false; static isMeleeDpsSpec = false; - + static role = Role.HEALER static canDualWield = false; readonly specIndex = HolyPriest.specIndex; @@ -63,6 +64,7 @@ export class HolyPriest extends PlayerSpec { readonly isHealingSpec = HolyPriest.isHealingSpec; readonly isRangedDpsSpec = HolyPriest.isRangedDpsSpec; readonly isMeleeDpsSpec = HolyPriest.isMeleeDpsSpec; + readonly role = HolyPriest.role readonly canDualWield = HolyPriest.canDualWield; @@ -86,7 +88,7 @@ export class ShadowPriest extends PlayerSpec { static isHealingSpec = false; static isRangedDpsSpec = true; static isMeleeDpsSpec = false; - + static role = Role.CASTER static canDualWield = false; readonly specIndex = ShadowPriest.specIndex; @@ -99,6 +101,7 @@ export class ShadowPriest extends PlayerSpec { readonly isHealingSpec = ShadowPriest.isHealingSpec; readonly isRangedDpsSpec = ShadowPriest.isRangedDpsSpec; readonly isMeleeDpsSpec = ShadowPriest.isMeleeDpsSpec; + readonly role = ShadowPriest.role readonly canDualWield = ShadowPriest.canDualWield; diff --git a/ui/core/player_specs/rogue.ts b/ui/core/player_specs/rogue.ts index bc0e39132c..25ddfbbb10 100644 --- a/ui/core/player_specs/rogue.ts +++ b/ui/core/player_specs/rogue.ts @@ -1,5 +1,5 @@ import { IconSize } from '../player_class'; -import { PlayerSpec } from '../player_spec'; +import { PlayerSpec, Role } from '../player_spec'; import { Class, Spec } from '../proto/common'; import { getSpecSiteUrl } from '../proto_utils/utils'; @@ -14,7 +14,7 @@ export class Rogue extends PlayerSpec { static isHealingSpec = false; static isRangedDpsSpec = false; static isMeleeDpsSpec = true; - + static role = Role.MELEE static canDualWield = true; readonly specIndex = Rogue.specIndex; @@ -27,6 +27,7 @@ export class Rogue extends PlayerSpec { readonly isHealingSpec = Rogue.isHealingSpec; readonly isRangedDpsSpec = Rogue.isRangedDpsSpec; readonly isMeleeDpsSpec = Rogue.isMeleeDpsSpec; + readonly role = Rogue.role readonly canDualWield = Rogue.canDualWield; diff --git a/ui/core/player_specs/shaman.ts b/ui/core/player_specs/shaman.ts index 281a233f74..9bf5b93f36 100644 --- a/ui/core/player_specs/shaman.ts +++ b/ui/core/player_specs/shaman.ts @@ -1,5 +1,5 @@ import { IconSize } from '../player_class'; -import { PlayerSpec } from '../player_spec'; +import { PlayerSpec, Role } from '../player_spec'; import { Class, Spec } from '../proto/common'; import { getSpecSiteUrl } from '../proto_utils/utils'; @@ -14,7 +14,7 @@ export class ElementalShaman extends PlayerSpec { static isHealingSpec = false; static isRangedDpsSpec = true; static isMeleeDpsSpec = false; - + static role = Role.CASTER static canDualWield = false; readonly specIndex = ElementalShaman.specIndex; @@ -27,6 +27,7 @@ export class ElementalShaman extends PlayerSpec { readonly isHealingSpec = ElementalShaman.isHealingSpec; readonly isRangedDpsSpec = ElementalShaman.isRangedDpsSpec; readonly isMeleeDpsSpec = ElementalShaman.isMeleeDpsSpec; + readonly role = ElementalShaman.role readonly canDualWield = ElementalShaman.canDualWield; @@ -50,7 +51,7 @@ export class EnhancementShaman extends PlayerSpec { static isHealingSpec = false; static isRangedDpsSpec = false; static isMeleeDpsSpec = true; - + static role = Role.MELEE static canDualWield = true; readonly specIndex = EnhancementShaman.specIndex; @@ -63,6 +64,7 @@ export class EnhancementShaman extends PlayerSpec { readonly isHealingSpec = EnhancementShaman.isHealingSpec; readonly isRangedDpsSpec = EnhancementShaman.isRangedDpsSpec; readonly isMeleeDpsSpec = EnhancementShaman.isMeleeDpsSpec; + readonly role = EnhancementShaman.role readonly canDualWield = EnhancementShaman.canDualWield; @@ -86,6 +88,7 @@ export class RestorationShaman extends PlayerSpec { static isHealingSpec = true; static isRangedDpsSpec = false; static isMeleeDpsSpec = false; + static role = Role.HEALER; static canDualWield = false; @@ -99,6 +102,7 @@ export class RestorationShaman extends PlayerSpec { readonly isHealingSpec = RestorationShaman.isHealingSpec; readonly isRangedDpsSpec = RestorationShaman.isRangedDpsSpec; readonly isMeleeDpsSpec = RestorationShaman.isMeleeDpsSpec; + readonly role = RestorationShaman.role readonly canDualWield = RestorationShaman.canDualWield; diff --git a/ui/core/player_specs/warlock.ts b/ui/core/player_specs/warlock.ts index 6f3ce0b72d..1a6f6ed28c 100644 --- a/ui/core/player_specs/warlock.ts +++ b/ui/core/player_specs/warlock.ts @@ -1,5 +1,5 @@ import { IconSize } from '../player_class'; -import { PlayerSpec } from '../player_spec'; +import { PlayerSpec, Role } from '../player_spec'; import { Class, Spec } from '../proto/common'; import { getSpecSiteUrl } from '../proto_utils/utils'; @@ -14,6 +14,7 @@ export class Warlock extends PlayerSpec { static isHealingSpec = false; static isRangedDpsSpec = true; static isMeleeDpsSpec = false; + static role = Role.CASTER static canDualWield = false; @@ -27,6 +28,7 @@ export class Warlock extends PlayerSpec { readonly isHealingSpec = Warlock.isHealingSpec; readonly isRangedDpsSpec = Warlock.isRangedDpsSpec; readonly isMeleeDpsSpec = Warlock.isMeleeDpsSpec; + readonly role = Warlock.role readonly canDualWield = Warlock.canDualWield; diff --git a/ui/core/player_specs/warrior.ts b/ui/core/player_specs/warrior.ts index ec905e8ae0..f603d4e279 100644 --- a/ui/core/player_specs/warrior.ts +++ b/ui/core/player_specs/warrior.ts @@ -1,5 +1,5 @@ import { IconSize } from '../player_class'; -import { PlayerSpec } from '../player_spec'; +import { PlayerSpec, Role } from '../player_spec'; import { Class, Spec } from '../proto/common'; import { getSpecSiteUrl } from '../proto_utils/utils'; @@ -14,7 +14,7 @@ export class DPSWarrior extends PlayerSpec { static isHealingSpec = false; static isRangedDpsSpec = false; static isMeleeDpsSpec = true; - + static role = Role.MELEE static canDualWield = true; readonly specIndex = DPSWarrior.specIndex; @@ -27,6 +27,7 @@ export class DPSWarrior extends PlayerSpec { readonly isHealingSpec = DPSWarrior.isHealingSpec; readonly isRangedDpsSpec = DPSWarrior.isRangedDpsSpec; readonly isMeleeDpsSpec = DPSWarrior.isMeleeDpsSpec; + readonly role = Role.MELEE readonly canDualWield = DPSWarrior.canDualWield; @@ -50,7 +51,7 @@ export class ProtectionWarrior extends PlayerSpec { static isHealingSpec = false; static isRangedDpsSpec = false; static isMeleeDpsSpec = false; - + static role = Role.TANK; static canDualWield = true; readonly specIndex = ProtectionWarrior.specIndex; @@ -63,6 +64,7 @@ export class ProtectionWarrior extends PlayerSpec { readonly isHealingSpec = ProtectionWarrior.isHealingSpec; readonly isRangedDpsSpec = ProtectionWarrior.isRangedDpsSpec; readonly isMeleeDpsSpec = ProtectionWarrior.isMeleeDpsSpec; + readonly role = ProtectionWarrior.role readonly canDualWield = ProtectionWarrior.canDualWield; diff --git a/ui/core/proto_utils/utils.ts b/ui/core/proto_utils/utils.ts index 435356ff0f..ca16391369 100644 --- a/ui/core/proto_utils/utils.ts +++ b/ui/core/proto_utils/utils.ts @@ -1410,6 +1410,7 @@ export const AL_CATEGORY_TITAN_RUNE = 'Titan Rune'; export const defaultRaidBuffMajorDamageCooldowns = (classID?: Class): Partial => { return RaidBuffs.create({ + bloodlust: true, //skullBannerCount: classID == Class.ClassWarrior ? 1 : 2, //stormlashTotemCount: classID == Class.ClassShaman ? 3 : 4, }); diff --git a/ui/druid/feralbear/sim.ts b/ui/druid/feralbear/sim.ts index 48a8d42a19..52e6bfbdec 100644 --- a/ui/druid/feralbear/sim.ts +++ b/ui/druid/feralbear/sim.ts @@ -90,7 +90,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecFeralBearDruid, { // Inputs to include in the 'Rotation' section on the settings tab. rotationInputs: DruidInputs.GuardianDruidRotationConfig, // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. - includeBuffDebuffInputs: [BuffDebuffInputs.SpellDamageDebuff], + includeBuffDebuffInputs: [], excludeBuffDebuffInputs: [], // Inputs to include in the 'Other' section on the settings tab. otherInputs: { diff --git a/ui/druid/feralcat/sim.ts b/ui/druid/feralcat/sim.ts index 680474ba0e..6542600b0f 100644 --- a/ui/druid/feralcat/sim.ts +++ b/ui/druid/feralcat/sim.ts @@ -77,7 +77,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecFeralCatDruid, { // Inputs to include in the 'Rotation' section on the settings tab. rotationInputs: FeralInputs.FeralDruidRotationConfig, // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. - includeBuffDebuffInputs: [BuffDebuffInputs.SpellPowerBuff, BuffDebuffInputs.SpellDamageDebuff, BuffDebuffInputs.SpellHasteBuff], + includeBuffDebuffInputs: [, , ], excludeBuffDebuffInputs: [], // Inputs to include in the 'Other' section on the settings tab. otherInputs: { diff --git a/ui/hunter/dps/sim.ts b/ui/hunter/dps/sim.ts index 7c907adb4b..2790bcde29 100644 --- a/ui/hunter/dps/sim.ts +++ b/ui/hunter/dps/sim.ts @@ -65,7 +65,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecHunter, { rotationInputs: Inputs.MMRotationConfig, petConsumeInputs: [], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. - includeBuffDebuffInputs: [BuffDebuffInputs.StaminaBuff, BuffDebuffInputs.SpellDamageDebuff], + includeBuffDebuffInputs: [, ], excludeBuffDebuffInputs: [], // Inputs to include in the 'Other' section on the settings tab. otherInputs: { diff --git a/ui/priest/shadow/sim.ts b/ui/priest/shadow/sim.ts index 74f501697b..885d73dc8c 100644 --- a/ui/priest/shadow/sim.ts +++ b/ui/priest/shadow/sim.ts @@ -79,7 +79,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecShadowPriest, { // IconInputs to include in the 'Player' section on the settings tab. playerIconInputs: [PriestInputs.ArmorInput()], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. - includeBuffDebuffInputs: [BuffDebuffInputs.AttackSpeedBuff], + includeBuffDebuffInputs: [], excludeBuffDebuffInputs: [], // Inputs to include in the 'Other' section on the settings tab. otherInputs: { diff --git a/ui/rogue/dps/sim.ts b/ui/rogue/dps/sim.ts index 0ab2beb550..677a17cad3 100644 --- a/ui/rogue/dps/sim.ts +++ b/ui/rogue/dps/sim.ts @@ -73,14 +73,14 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecRogue, { playerIconInputs: [RogueInputs.LethalPoison()], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ - BuffDebuffInputs.CritBuff, - BuffDebuffInputs.AttackPowerBuff, - BuffDebuffInputs.StatsBuff, - BuffDebuffInputs.AttackSpeedBuff, + , + , + , + , - BuffDebuffInputs.MajorHasteBuff, + , - BuffDebuffInputs.SpellDamageDebuff, + , ], excludeBuffDebuffInputs: [], // Inputs to include in the 'Other' section on the settings tab. diff --git a/ui/scss/core/components/individual_sim_ui/_settings_tab.scss b/ui/scss/core/components/individual_sim_ui/_settings_tab.scss index a52e0a19f4..08e2864234 100644 --- a/ui/scss/core/components/individual_sim_ui/_settings_tab.scss +++ b/ui/scss/core/components/individual_sim_ui/_settings_tab.scss @@ -38,7 +38,7 @@ .content-block-body { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-columns: repeat(4, minmax(0, 1fr)); grid-gap: var(--block-spacer); @include media-breakpoint-down(md) { diff --git a/ui/scss/shared/_variables.scss b/ui/scss/shared/_variables.scss index ebdc1cf84d..2f928ad1e0 100644 --- a/ui/scss/shared/_variables.scss +++ b/ui/scss/shared/_variables.scss @@ -230,6 +230,7 @@ $theme-colors: map-merge($theme-colors, $table-colors); --content-font-size: 0.75rem; --icon-size-sm: 1rem; --icon-size-md: 2.5rem; + --icon-size-lg: 3rem; } // Z-index variables diff --git a/ui/shaman/elemental/sim.ts b/ui/shaman/elemental/sim.ts index 8492c644f9..3ffe6d5ee5 100644 --- a/ui/shaman/elemental/sim.ts +++ b/ui/shaman/elemental/sim.ts @@ -1,4 +1,3 @@ -import { AttackSpeedBuff } from '../../core/components/inputs/buffs_debuffs'; import * as OtherInputs from '../../core/components/inputs/other_inputs.js'; import { IndividualSimUI, registerSpecConfig } from '../../core/individual_sim_ui.js'; import { Player } from '../../core/player.js'; @@ -57,7 +56,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecElementalShaman, { // IconInputs to include in the 'Player' section on the settings tab. playerIconInputs: [ShamanInputs.ShamanShieldInput()], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. - includeBuffDebuffInputs: [AttackSpeedBuff], + includeBuffDebuffInputs: [], excludeBuffDebuffInputs: [], // Inputs to include in the 'Other' section on the settings tab. otherInputs: { diff --git a/ui/shaman/enhancement/sim.ts b/ui/shaman/enhancement/sim.ts index c3d0770008..dffa05ea8d 100644 --- a/ui/shaman/enhancement/sim.ts +++ b/ui/shaman/enhancement/sim.ts @@ -106,7 +106,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecEnhancementShaman, { ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [], - excludeBuffDebuffInputs: [BuffDebuffInputs.SpellPowerBuff], + excludeBuffDebuffInputs: [], // Inputs to include in the 'Other' section on the settings tab. otherInputs: { inputs: [EnhancementInputs.SyncTypeInput, OtherInputs.InputDelay, OtherInputs.TankAssignment, OtherInputs.InFrontOfTarget], diff --git a/ui/warlock/dps/sim.ts b/ui/warlock/dps/sim.ts index cb05b0df97..8153898405 100644 --- a/ui/warlock/dps/sim.ts +++ b/ui/warlock/dps/sim.ts @@ -16,7 +16,6 @@ const modifyDisplayStats = (player: Player) => { TypedEvent.freezeAllAndDo(() => { const currentStats = player.getCurrentStats().finalStats?.stats; - console.log("currentStatrs", currentStats) if (currentStats === undefined) { return {}; } @@ -36,7 +35,12 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarlock, { knownIssues: [], // All stats for which EP should be calculated. - epStats: [Stat.StatIntellect, Stat.StatSpellPower], + epStats: [ + Stat.StatIntellect, + Stat.StatSpellDamage, + Stat.StatSpellCritRating, + Stat.StatSpellHasteRating + ], // Reference stat against which to calculate EP. DPS classes use either spell power or attack power. epReferenceStat: Stat.StatSpellPower, // Which stats to display in the Character Stats section, at the bottom of the left-hand sidebar. @@ -82,7 +86,6 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarlock, { powerWordFortitude: TristateEffect.TristateEffectImproved, divineSpirit: TristateEffect.TristateEffectImproved, giftOfTheWild: TristateEffect.TristateEffectImproved, - bloodlust: true, }), partyBuffs: PartyBuffs.create({ bloodPact: TristateEffect.TristateEffectMissing, @@ -130,7 +133,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarlock, { ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. - includeBuffDebuffInputs: [BuffDebuffInputs.AttackSpeedBuff], + includeBuffDebuffInputs: [], excludeBuffDebuffInputs: [], petConsumeInputs: [], // Inputs to include in the 'Other' section on the settings tab. diff --git a/ui/warrior/protection/sim.ts b/ui/warrior/protection/sim.ts index 6443e5b254..13a3872bf9 100644 --- a/ui/warrior/protection/sim.ts +++ b/ui/warrior/protection/sim.ts @@ -80,7 +80,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecProtectionWarrior, { // IconInputs to include in the 'Player' section on the settings tab. playerIconInputs: [], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. - includeBuffDebuffInputs: [BuffDebuffInputs.StaminaBuff], + includeBuffDebuffInputs: [], excludeBuffDebuffInputs: [], // Inputs to include in the 'Other' section on the settings tab. otherInputs: { From c8a8b642fce53dd1f7803a148358a80ade8ab02f Mon Sep 17 00:00:00 2001 From: jazz405 Date: Sun, 14 Dec 2025 14:19:25 -0500 Subject: [PATCH 13/20] updating otherinputs --- proto/common.proto | 1 + sim/core/buffs.go | 91 --- sim/core/debuffs.go | 631 ++++++++++++------ sim/core/spell_result.go | 7 +- sim/core/unit.go | 1 + sim/core/utils.go | 9 + sim/druid/druid.go | 4 - sim/warlock/curse_of_elements.go | 2 +- sim/warlock/talents.go | 46 +- sim/warrior/warrior.go | 2 - .../individual_sim_ui/settings_tab.tsx | 3 +- ui/core/components/input_helpers.ts | 1 + ui/core/components/inputs/buffs_debuffs.ts | 51 +- ui/core/components/inputs/other_inputs.ts | 34 + ui/core/components/inputs/stat_options.ts | 2 +- ui/core/individual_sim_ui.tsx | 3 +- ui/core/worker_pool.ts | 2 +- ui/warlock/dps/sim.ts | 8 +- 18 files changed, 533 insertions(+), 365 deletions(-) diff --git a/proto/common.proto b/proto/common.proto index f8f238f853..769569cb4d 100644 --- a/proto/common.proto +++ b/proto/common.proto @@ -565,6 +565,7 @@ message Debuffs { bool scorpid_sting = 24; bool shadow_embrace = 25; bool screech = 26; + double hemorrhage_uptime = 27; } message ConsumesSpec { diff --git a/sim/core/buffs.go b/sim/core/buffs.go index 77c1a35f38..458b90f173 100644 --- a/sim/core/buffs.go +++ b/sim/core/buffs.go @@ -1014,97 +1014,6 @@ func RallyingCryAuraArray(unit *Unit, actionTag int32) AuraArray { } -const ShatteringThrowCD = time.Minute * 5 - -func registerShatteringThrowCD(agent Agent, numShatteringThrows int32) { - if numShatteringThrows == 0 { - return - } - - stAura := ShatteringThrowAura(agent.GetCharacter().Env.GetTargetUnitByIndex(0), -1) - - registerExternalConsecutiveCDApproximation( - agent, - externalConsecutiveCDApproximation{ - ActionID: ActionID{SpellID: 1249459, Tag: -1}, - AuraTag: ShatteringThrowAuraTag, - CooldownPriority: CooldownPriorityDefault, - RelatedSelfBuff: stAura, - AuraDuration: ShatteringThrowDuration, - AuraCD: ShatteringThrowCD, - Type: CooldownTypeDPS, - - ShouldActivate: func(sim *Simulation, character *Character) bool { - return true - }, - AddAura: func(sim *Simulation, character *Character) { - stAura.Activate(sim) - }, - }, - numShatteringThrows) -} - -var SkullBannerActionID = ActionID{SpellID: 114206} - -const SkullBannerAuraTag = "SkullBanner" -const SkullBannerDuration = time.Second * 10 -const SkullBannerCD = time.Minute * 3 - -func registerSkullBannerCD(agent Agent, numSkullBanners int32) { - if numSkullBanners == 0 { - return - } - - sbAura := SkullBannerAura(agent.GetCharacter(), -1) - - registerExternalConsecutiveCDApproximation( - agent, - externalConsecutiveCDApproximation{ - ActionID: SkullBannerActionID.WithTag(-1), - AuraTag: SkullBannerAuraTag, - CooldownPriority: CooldownPriorityDefault, - RelatedSelfBuff: sbAura, - AuraDuration: SkullBannerDuration, - AuraCD: SkullBannerCD, - Type: CooldownTypeDPS, - - ShouldActivate: func(sim *Simulation, character *Character) bool { - return true - }, - AddAura: func(sim *Simulation, character *Character) { - sbAura.Activate(sim) - }, - }, - numSkullBanners) -} - -func SkullBannerAura(character *Character, actionTag int32) *Aura { - for _, pet := range character.Pets { - if !pet.IsGuardian() { - SkullBannerAura(&pet.Character, actionTag) - } - } - - return character.GetOrRegisterAura(Aura{ - Label: "Skull Banner", - Tag: SkullBannerAuraTag, - ActionID: SkullBannerActionID.WithTag(actionTag), - Duration: SkullBannerDuration, - - OnGain: func(aura *Aura, sim *Simulation) { - character.PseudoStats.CritDamageMultiplier *= 1.2 - for _, pet := range character.Pets { - if pet.IsEnabled() && !pet.IsGuardian() { - pet.GetAura(aura.Label).Activate(sim) - } - } - }, - OnExpire: func(aura *Aura, sim *Simulation) { - character.PseudoStats.CritDamageMultiplier /= 1.2 - }, - }) -} - var ManaTideTotemActionID = ActionID{SpellID: 16190} var ManaTideTotemAuraTag = "ManaTideTotem" diff --git a/sim/core/debuffs.go b/sim/core/debuffs.go index 7e1c72e189..d72279f728 100644 --- a/sim/core/debuffs.go +++ b/sim/core/debuffs.go @@ -1,6 +1,7 @@ package core import ( + "math/rand" "time" "github.com/wowsims/tbc/sim/core/proto" @@ -9,287 +10,479 @@ import ( // applyRaidDebuffEffects applies all raid-level debuffs based on the provided Debuffs proto. func applyDebuffEffects(target *Unit, targetIdx int, debuffs *proto.Debuffs, raid *proto.Raid) { - // // –10% Physical damage dealt for 30s - // if debuffs.WeakenedBlows { - // MakePermanent(WeakenedBlowsAura(target)) - // } - - // // +4% Physical damage taken for 30s - // if debuffs.PhysicalVulnerability { - // MakePermanent(PhysVulnerabilityAura(target)) - // } - - // // –4% Armor for 30s, stacks 3 times - // if debuffs.WeakenedArmor { - // aura := MakePermanent(WeakenedArmorAura(target)) - - // aura.OnReset = func(aura *Aura, sim *Simulation) { - // // Ferals can require a global to put this up on pull. - // pa := sim.GetConsumedPendingActionFromPool() - // pa.NextActionAt = sim.CurrentTime + GCDMin - // pa.Priority = ActionPriorityDOT - - // pa.OnAction = func(sim *Simulation) { - // aura.Activate(sim) - // aura.SetStacks(sim, 3) - // } - - // sim.AddPendingAction(pa) - // } - // } - - // // Spell‐damage‐taken sources - // if debuffs.FireBreath { - // MakePermanent(FireBreathDebuff(target)) - // } - // if debuffs.LightningBreath { - // MakePermanent(LightningBreathDebuff(target)) - // } - // if debuffs.MasterPoisoner { - // MakePermanent(MasterPoisonerDebuff(target)) - // } - // if debuffs.CurseOfElements { - // MakePermanent(CurseOfElementsAura(target)) - // } - - // // Casting‐speed‐reduction sources - // if debuffs.NecroticStrike { - // MakePermanent(NecroticStrikeAura(target)) - // } - // if debuffs.LavaBreath { - // MakePermanent(LavaBreathAura(target)) - // } - // if debuffs.SporeCloud { - // MakePermanent(SporeCloud(target)) - // } - // if debuffs.Slow { - // MakePermanent(SlowAura(target)) - // } - // if debuffs.MindNumbingPoison { - // MakePermanent(MindNumbingPoisonAura(target)) - // } - // if debuffs.CurseOfEnfeeblement { - // MakePermanent(CurseOfEnfeeblement(target)) - // } -} -const WeakenedBlowsDuration = time.Second * 30 + if debuffs.BloodFrenzy { -// –10% Physical damage dealt -func WeakenedBlowsAura(target *Unit) *Aura { - return physDamageDealtAura(target, "Weakened Blows", 115798, WeakenedBlowsDuration, 10) -} -func DemoralizingScreech(target *Unit) *Aura { - return physDamageDealtAura(target, "Demoralizing Screech", 24423, time.Second*10, 10) -} -func DemoralizingRoar(target *Unit) *Aura { - return physDamageDealtAura(target, "Demoralizing Roar", 50256, time.Second*15, 10) -} + MakePermanent(BloodFrenzy(target)) + } + + if debuffs.CurseOfElements != proto.TristateEffect_TristateEffectMissing { + MakePermanent(CurseOfElements(target, IsImproved(debuffs.CurseOfElements))) + } + + if debuffs.CurseOfRecklessness { + MakePermanent(CurseOfRecklessness(target)) + } + + if debuffs.DemoralizingRoar != proto.TristateEffect_TristateEffectMissing { + MakePermanent(DemoralizingRoar(target, IsImproved(debuffs.DemoralizingRoar))) + } + + if debuffs.DemoralizingShout != proto.TristateEffect_TristateEffectMissing { + MakePermanent(DemoralizingRoar(target, IsImproved(debuffs.DemoralizingRoar))) + } + + if debuffs.ExposeArmor != proto.TristateEffect_TristateEffectMissing { + MakePermanent(ExposeArmor(target, IsImproved(debuffs.ExposeArmor))) + } + + if debuffs.ExposeWeaknessUptime > 0.0 { + MakePermanent(ExposeWeakness(target, debuffs.ExposeWeaknessUptime, debuffs.ExposeWeaknessHunterAgility)) + } + + if debuffs.FaerieFire != proto.TristateEffect_TristateEffectMissing { + MakePermanent(FaerieFire(target, IsImproved(debuffs.FaerieFire))) + } + + if debuffs.HemorrhageUptime > 0.0 { + MakePermanent(Hemorrhage(target, debuffs.HemorrhageUptime)) + } + + if debuffs.GiftOfArthas { + MakePermanent(GiftOfArthas(target)) + } + + if debuffs.HuntersMark != proto.TristateEffect_TristateEffectMissing { + MakePermanent(HuntersMark(target, IsImproved(debuffs.HuntersMark))) + } + + if debuffs.ImprovedScorch { + MakePermanent(ImprovedScorch(target)) + } + + if debuffs.ImprovedSealOfTheCrusader { + MakePermanent(ImprovedSealOfTheCrusader(target)) + } + + if debuffs.InsectSwarm { + MakePermanent((InsectSwarm(target))) + } + + if debuffs.IsbUptime > 0.0 { + MakePermanent(ImprovedShadowBolt(target, debuffs.IsbUptime)) + } + + if debuffs.JudgementOfLight { + MakePermanent(JudgementOfLight(target)) + } -func ShadowEmbraceAura(target *Unit, level int32, duration time.Duration) *Aura { - return physDamageDealtAura(target, "Shadow Embrace", 32385, duration, level) + if debuffs.JudgementOfWisdom { + MakePermanent(JudgementOfWisdom(target)) + } + + if debuffs.Mangle { + MakePermanent(Mangle(target)) + } + + if debuffs.Misery { + MakePermanent(Misery(target)) + } + + if debuffs.ScorpidSting { + MakePermanent(ScorpidSting(target)) + } + + if debuffs.Screech { + MakePermanent(Screech(target)) + } + + if debuffs.ShadowEmbrace { + MakePermanent(ShadowEmbrace(target)) + } + + if debuffs.ShadowWeaving { + MakePermanent(ShadowWeaving(target)) + } + + if debuffs.SunderArmor { + MakePermanent(SunderArmor(target)) + } + + if debuffs.WintersChill { + MakePermanent(WintersChill(target)) + } } -func physDamageDealtAura(target *Unit, label string, spellID int32, duration time.Duration, level int32) *Aura { - aura := target.GetOrRegisterAura(Aura{ - Label: label, - ActionID: ActionID{SpellID: spellID}, - Duration: duration, - }) - PhysDamageReductionEffect(aura, 0.1*float64(level)) - return aura +// Physical anmd Armor Related Debuffs +func BloodFrenzy(target *Unit) *Aura { + return damageTakenDebuff(target, "Blood Frenzy", 29859, []stats.SchoolIndex{stats.SchoolIndexPhysical}, 1.04, time.Second*21) } -// +4% Physical damage taken -func PhysVulnerabilityAura(target *Unit) *Aura { - return physVulnerabilityAura(target, "Physical Vulnerability", 81326, time.Second*30) +// Damage Taken Debuffs +func CurseOfElements(target *Unit, improved bool) *Aura { + multiplier := 1.10 + if improved { + multiplier += 0.03 + } + + return damageTakenDebuff(target, "Curse of Elements", 27228, + []stats.SchoolIndex{ + stats.SchoolIndexArcane, + stats.SchoolIndexFire, + stats.SchoolIndexFrost, + stats.SchoolIndexShadow, + }, + multiplier, + time.Minute*5, + ) } -func AcidSpitAura(target *Unit) *Aura { - return physVulnerabilityAura(target, "Acid Spit", 55749, time.Second*25) + +func CurseOfRecklessness(target *Unit) *Aura { + return statsDebuff(target, "Curse of Recklesness", 27226, stats.Stats{stats.Armor: -8000, stats.AttackPower: 135}) } -func StampedeAura(target *Unit) *Aura { - return physVulnerabilityAura(target, "Stampede", 57386, time.Second*30) + +func DemoralizingRoar(target *Unit, improved bool) *Aura { + apReduction := 248.0 + if improved { + apReduction *= 1.4 + } + + return statsDebuff(target, "Demoralizing Roar", 26998, stats.Stats{stats.AttackPower: apReduction}) } -func RavageAura(target *Unit) *Aura { - return physVulnerabilityAura(target, "Ravage", 50518, time.Second*25) + +func DemoralizingShout(target *Unit, improved bool) *Aura { + apReduction := 300.0 + if improved { + apReduction *= 1.4 + } + + return statsDebuff(target, "Demoralizing Shout", 25203, stats.Stats{stats.AttackPower: apReduction}) } -func GoreAura(target *Unit) *Aura { - return physVulnerabilityAura(target, "Gore", 35290, time.Second*30) + +func ExposeArmor(target *Unit, improved bool) *Aura { + eaValue := 2050.0 + if improved { + eaValue *= 1.50 + } + return statsDebuff(target, "Expose Armor", 26866, stats.Stats{stats.Armor: eaValue}) } -func physVulnerabilityAura(target *Unit, label string, spellID int32, duration time.Duration) *Aura { - aura := target.GetOrRegisterAura(Aura{ - Label: label, - ActionID: ActionID{SpellID: spellID}, - Duration: duration, +func ExposeWeakness(target *Unit, uptime float64, hunterAgility float64) *Aura { + apBonus := hunterAgility * 0.25 * uptime + + return target.GetOrRegisterAura(Aura{ + Label: "Expose Weakness", + Tag: "ExposeWeakness", + ActionID: ActionID{SpellID: 34503}, + Duration: time.Second * 7, + OnGain: func(aura *Aura, sim *Simulation) { + aura.Unit.AddStat(stats.AttackPower, apBonus) + aura.Unit.AddStat(stats.RangedAttackPower, apBonus) + }, + OnExpire: func(aura *Aura, sim *Simulation) { + aura.Unit.AddStat(stats.AttackPower, -apBonus) + aura.Unit.AddStat(stats.RangedAttackPower, -apBonus) + }, }) - PhysDamageTakenEffect(aura, 1.04) - return aura + } -// –4% Armor stacks 3 -func WeakenedArmorAura(target *Unit) *Aura { - var effect *ExclusiveEffect - aura := target.GetOrRegisterAura(Aura{ - Label: "Weakened Armor", - ActionID: ActionID{SpellID: 113746}, - Duration: time.Second * 30, - MaxStacks: 3, - OnStacksChange: func(_ *Aura, sim *Simulation, oldStacks int32, newStacks int32) { - effect.SetPriority(sim, 0.04*float64(newStacks)) +func FaerieFire(target *Unit, improved bool) *Aura { + return target.GetOrRegisterAura(Aura{ + Label: "Faerie Fire", + ActionID: ActionID{SpellID: 26993}, + Duration: time.Second * 40, + OnGain: func(aura *Aura, sim *Simulation) { + aura.Unit.AddStatsDynamic(sim, stats.Stats{stats.Armor: -6100}) + aura.Unit.PseudoStats.ReducedPhysicalHitTakenChance -= 3.0 + }, + OnExpire: func(aura *Aura, sim *Simulation) { + aura.Unit.AddStatsDynamic(sim, stats.Stats{stats.Armor: 6100}) + aura.Unit.PseudoStats.ReducedPhysicalHitTakenChance += 3.0 }, }) - effect = registerMajorArpEffect(aura, 0) - return aura } -func MortalWoundsAura(target *Unit) *Aura { - return majorHealingReductionAura(target, "Mortal Wounds", 115804, 0.25) +func GiftOfArthas(target *Unit) *Aura { + return target.GetOrRegisterAura(Aura{ + Label: "Gift of Arthas", + ActionID: ActionID{SpellID: 11374}, + Duration: time.Minute * 3, + OnGain: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.BonusPhysicalDamageTaken += 8 + }, + OnExpire: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.BonusPhysicalDamageTaken -= 8 + }, + }) } -// Spell‐damage‐taken sources -func FireBreathDebuff(target *Unit) *Aura { - return spellDamageEffectAura(Aura{Label: "Fire Breath", ActionID: ActionID{SpellID: 34889}, Duration: time.Second * 45}, target, 1.05) -} -func LightningBreathDebuff(target *Unit) *Aura { - return spellDamageEffectAura(Aura{Label: "Lightning Breath", ActionID: ActionID{SpellID: 24844}, Duration: time.Second * 45}, target, 1.05) -} -func MasterPoisonerDebuff(target *Unit) *Aura { - return spellDamageEffectAura(Aura{Label: "Master Poisoner", ActionID: ActionID{SpellID: 58410}, Duration: time.Second * 15}, target, 1.05) -} -func CurseOfElementsAura(target *Unit) *Aura { - return spellDamageEffectAura(Aura{Label: "Curse of Elements", ActionID: ActionID{SpellID: 1490}, Duration: time.Minute * 5}, target, 1.05) +func Hemorrhage(target *Unit, uptime float64) *Aura { + return target.GetOrRegisterAura(Aura{ + Label: "Hemorrhage", + ActionID: ActionID{SpellID: 33876}, + Duration: time.Second * 15, + MaxStacks: 15, + OnGain: func(aura *Aura, sim *Simulation) { + aura.stacks = 15 + }, + OnExpire: func(aura *Aura, sim *Simulation) { + aura.stacks = 15 + }, + OnSpellHitTaken: func(aura *Aura, sim *Simulation, spell *Spell, result *SpellResult) { + if !spell.SpellSchool.Matches(SpellSchoolPhysical) { + return + } + + if aura.stacks <= 0 { + aura.Deactivate(sim) + return + } + + result.Damage += 42 * uptime + spell.DealDamage(sim, result) + + aura.stacks-- + }, + }) } -func majorHealingReductionAura(target *Unit, label string, spellID int32, multiplier float64) *Aura { - aura := target.GetOrRegisterAura(Aura{Label: label, ActionID: ActionID{SpellID: spellID}, Duration: time.Second * 30}) - aura.NewExclusiveEffect("HealingReduction", false, ExclusiveEffect{ - Priority: multiplier, - OnGain: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.PseudoStats.HealingTakenMultiplier *= multiplier +func HuntersMark(target *Unit, improved bool) *Aura { + maxBonus := 440.0 + + return target.GetOrRegisterAura(Aura{ + Label: "HuntersMark", + Tag: "HuntersMark", + ActionID: ActionID{SpellID: 14325}, + Duration: NeverExpires, + OnGain: func(aura *Aura, sim *Simulation) { + if improved { + aura.Unit.AddStat(stats.AttackPower, maxBonus) + } + aura.Unit.AddStat(stats.RangedAttackPower, maxBonus) }, - OnExpire: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.PseudoStats.HealingTakenMultiplier /= multiplier + OnExpire: func(aura *Aura, sim *Simulation) { + if improved { + aura.Unit.AddStat(stats.AttackPower, -maxBonus) + } + aura.Unit.AddStat(stats.RangedAttackPower, -maxBonus) }, }) - return aura } -// Casting‐speed‐reduction sources -func NecroticStrikeAura(target *Unit) *Aura { - return castSpeedReductionAura(target, "Necrotic Strike", 73975, 1.5, time.Second*10) -} -func LavaBreathAura(target *Unit) *Aura { - return castSpeedReductionAura(target, "Lava Breath", 58604, 1.5, time.Second*10) -} -func SporeCloud(target *Unit) *Aura { - return castSpeedReductionAura(target, "Spore Cloud", 50274, 1.5, time.Second*10) -} -func MindNumbingPoisonAura(target *Unit) *Aura { - return castSpeedReductionAura(target, "Mind-numbing Poison", 5760, 1.5, time.Second*10) -} -func CurseOfEnfeeblement(target *Unit) *Aura { - return castSpeedReductionAura(target, "Curse of Enfeeblement", 109466, 1.5, time.Second*30) +func ImprovedScorch(target *Unit) *Aura { + return damageTakenDebuff(target, "Improved Scorch", 12873, []stats.SchoolIndex{stats.SchoolIndexFire}, 1.15, time.Second*30) } -func SlowAura(target *Unit) *Aura { - return castSpeedReductionAura(target, "Slow", 31589, 1.5, time.Second*15) -} -func castSpeedReductionAura(target *Unit, label string, spellID int32, multiplier float64, duration time.Duration) *Aura { - aura := target.GetOrRegisterAura(Aura{Label: label, ActionID: ActionID{SpellID: spellID}, Duration: duration}) - aura.NewExclusiveEffect("CastSpdReduction", false, ExclusiveEffect{ - Priority: multiplier, - OnGain: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.MultiplyCastSpeed(sim, 1/multiplier) + +func ImprovedSealOfTheCrusader(target *Unit) *Aura { + return target.GetOrRegisterAura(Aura{ + Label: "Improved Seal of the Crusader", + ActionID: ActionID{SpellID: 20337}, + Duration: time.Second * 60, + OnGain: func(aura *Aura, sim *Simulation) { + target.PseudoStats.ReducedCritTakenChance -= 3.0 }, - OnExpire: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.MultiplyCastSpeed(sim, multiplier) + OnExpire: func(aura *Aura, sim *Simulation) { + target.PseudoStats.ReducedCritTakenChance += 3.0 }, }) - return aura + } -const SpellDamageEffectAuraTag = "SpellDamageAuraTag" - -func spellDamageEffectAura(auraConfig Aura, target *Unit, multiplier float64) *Aura { - auraConfig.Tag = SpellDamageEffectAuraTag - aura := target.GetOrRegisterAura(auraConfig) - aura.NewExclusiveEffect("SpellDamageTaken%", true, ExclusiveEffect{ - Priority: multiplier, - OnGain: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexArcane] *= multiplier - ee.Aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFire] *= multiplier - ee.Aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFrost] *= multiplier - ee.Aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexShadow] *= multiplier - ee.Aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexNature] *= multiplier - ee.Aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexHoly] *= multiplier +func ImprovedShadowBolt(target *Unit, uptime float64) *Aura { + multiplier := 1.2 * uptime + + return target.GetOrRegisterAura(Aura{ + Label: "ImprovedShadowBolt", + ActionID: ActionID{SpellID: 17803}, + Duration: time.Second * 12, + MaxStacks: 4, + OnGain: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexShadow] *= multiplier }, - OnExpire: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexArcane] /= multiplier - ee.Aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFire] /= multiplier - ee.Aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFrost] /= multiplier - ee.Aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexShadow] /= multiplier - ee.Aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexNature] /= multiplier - ee.Aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexHoly] /= multiplier + OnExpire: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexShadow] /= multiplier + }, + }) +} + +func InsectSwarm(target *Unit) *Aura { + return statsDebuff(target, "Insect Swarm", 27013, stats.Stats{ + stats.AllPhysHitRating: 0.98, + stats.SpellHitPercent: 0.98, + }) +} + +func JudgementOfLight(target *Unit) *Aura { + actionId := ActionID{SpellID: 27163} + + return target.GetOrRegisterAura(Aura{ + Label: "Judgement of Light", + ActionID: actionId, + Duration: time.Second * 20, + OnSpellHitTaken: func(aura *Aura, sim *Simulation, spell *Spell, result *SpellResult) { + var healthMetrics *ResourceMetrics + healthMetrics = aura.Unit.NewHealthMetrics(actionId) + if !spell.ProcMask.Matches(ProcMaskMelee) || !result.Landed() { + return + } + + if spell.ActionID.SpellID == 35395 { + aura.Refresh(sim) + } + + if rand.Float64() < 50.0 { + aura.Unit.GainHealth(sim, 95.0, healthMetrics) + } }, }) - return aura } -var majorArmorReductionEffectCategory = "MajorArmorReduction" +func JudgementOfWisdom(target *Unit) *Aura { + actionId := ActionID{SpellID: 27167} + + return target.GetOrRegisterAura(Aura{ + Label: "Judgement of Light", + ActionID: actionId, + Duration: time.Second * 20, + OnSpellHitTaken: func(aura *Aura, sim *Simulation, spell *Spell, result *SpellResult) { + var manaMetrics *ResourceMetrics + manaMetrics = aura.Unit.NewManaMetrics(actionId) + if !spell.ProcMask.Matches(ProcMaskMelee) || !result.Landed() { + return + } + + if spell.ActionID.SpellID == 35395 { + aura.Refresh(sim) + } -func registerMajorArpEffect(aura *Aura, initialArp float64) *ExclusiveEffect { - return aura.NewExclusiveEffect(majorArmorReductionEffectCategory, true, ExclusiveEffect{ - Priority: initialArp, - OnGain: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.PseudoStats.ArmorMultiplier *= 1 - ee.Priority + if rand.Float64() < 50.0 { + aura.Unit.AddMana(sim, 121.0, manaMetrics) + } }, - OnExpire: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.PseudoStats.ArmorMultiplier /= 1 - ee.Priority + }) +} + +func Mangle(target *Unit) *Aura { + return target.GetOrRegisterAura(Aura{ + Label: "Mangle", + ActionID: ActionID{SpellID: 33876}, + Duration: time.Second * 15, + OnGain: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.PeriodicPhysicalDamageTakenMultiplier *= 1.3 + }, + OnExpire: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.PeriodicPhysicalDamageTakenMultiplier /= 1.3 }, }) } -var ShatteringThrowAuraTag = "ShatteringThrow" -var ShatteringThrowDuration = time.Second * 10 +func Misery(target *Unit) *Aura { + return damageTakenDebuff(target, "Misery", 33195, []stats.SchoolIndex{ + stats.SchoolIndexArcane, + stats.SchoolIndexFire, + stats.SchoolIndexFrost, + stats.SchoolIndexHoly, + stats.SchoolIndexNature, + stats.SchoolIndexShadow, + }, 1.05, time.Minute*1) +} + +func ScorpidSting(target *Unit) *Aura { + return statsDebuff(target, "Scorpid Sting", 3043, stats.Stats{stats.AllPhysHitRating: -5.0}) +} + +func Screech(target *Unit) *Aura { + return statsDebuff(target, "Screech", 27051, stats.Stats{stats.AttackPower: -210}) +} + +func ShadowEmbrace(target *Unit) *Aura { + return damageDealtDebuff(target, "Shadow Embrace", 32394, []stats.SchoolIndex{stats.SchoolIndexPhysical}, 0.95, NeverExpires) +} + +func ShadowWeaving(target *Unit) *Aura { + return damageTakenDebuff(target, "Shadow Weaving", 15334, []stats.SchoolIndex{stats.SchoolIndexShadow}, 1.10, time.Second*15) +} -func ShatteringThrowAura(target *Unit, actionTag int32) *Aura { - armorReduction := 0.2 +func Stormstrike(target *Unit, uptime float64) *Aura { + multiplier := 1.20 + if uptime != 0 { + multiplier *= uptime + } + return damageTakenDebuff(target, "Stormstrike", 17364, []stats.SchoolIndex{stats.SchoolIndexNature}, multiplier, time.Second*12) +} + +func SunderArmor(target *Unit) *Aura { + return statsDebuff(target, "Sunder Amor", 25225, stats.Stats{stats.Armor: 2600}) +} + +func WintersChill(target *Unit) *Aura { + return target.GetOrRegisterAura(Aura{ + Label: "Winter's Chill", + ActionID: ActionID{SpellID: 28595}, + Duration: time.Second * 15, + OnGain: func(aura *Aura, sim *Simulation) { + aura.Unit.AddStaticMod(SpellModConfig{ + Kind: SpellMod_BonusCrit_Percent, + FloatValue: 10.0, + School: SpellSchoolFire, + }) + }, + }) +} +func damageTakenDebuff(target *Unit, label string, spellID int32, schools []stats.SchoolIndex, multiplier float64, duration time.Duration) *Aura { return target.GetOrRegisterAura(Aura{ - Label: "Shattering Throw", - Tag: ShatteringThrowAuraTag, - ActionID: ActionID{SpellID: 1249459, Tag: actionTag}, - Duration: ShatteringThrowDuration, + Label: label, + ActionID: ActionID{SpellID: spellID}, + Duration: time.Second * 30, OnGain: func(aura *Aura, sim *Simulation) { - aura.Unit.PseudoStats.ArmorMultiplier *= (1.0 - armorReduction) + for _, school := range schools { + target.PseudoStats.SchoolDamageTakenMultiplier[school] *= multiplier + } }, + OnExpire: func(aura *Aura, sim *Simulation) { - aura.Unit.PseudoStats.ArmorMultiplier /= (1.0 - armorReduction) + for _, school := range schools { + target.PseudoStats.SchoolDamageDealtMultiplier[school] /= -multiplier + } }, }) } -func PhysDamageTakenEffect(aura *Aura, multiplier float64) *ExclusiveEffect { - return aura.NewExclusiveEffect("PhysicalDmg", false, ExclusiveEffect{ - Priority: multiplier, - OnGain: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexPhysical] *= multiplier +func damageDealtDebuff(target *Unit, label string, spellID int32, schools []stats.SchoolIndex, multiplier float64, duration time.Duration) *Aura { + return target.GetOrRegisterAura(Aura{ + Label: label, + ActionID: ActionID{SpellID: spellID}, + Duration: time.Second * 30, + + OnGain: func(aura *Aura, sim *Simulation) { + for _, school := range schools { + target.PseudoStats.SchoolDamageDealtMultiplier[school] *= 1.0 - multiplier + } + }, - OnExpire: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexPhysical] /= multiplier + + OnExpire: func(aura *Aura, sim *Simulation) { + for _, school := range schools { + target.PseudoStats.SchoolDamageDealtMultiplier[school] /= 1.0 - multiplier + } }, }) } -func PhysDamageReductionEffect(aura *Aura, dmgReduction float64) *ExclusiveEffect { - reductionMult := 1.0 - dmgReduction - return aura.NewExclusiveEffect("PhysDamageReduction", false, ExclusiveEffect{ - Priority: dmgReduction, - OnGain: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] *= reductionMult +func statsDebuff(target *Unit, label string, spellID int32, stats stats.Stats) *Aura { + return target.GetOrRegisterAura(Aura{ + Label: label, + ActionID: ActionID{SpellID: spellID}, + Duration: time.Second * 30, + + OnGain: func(aura *Aura, sim *Simulation) { + aura.Unit.AddStatsDynamic(sim, stats) }, - OnExpire: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] /= reductionMult + + OnExpire: func(aura *Aura, sim *Simulation) { + aura.Unit.AddStatsDynamic(sim, stats.Multiply(-1)) }, }) } diff --git a/sim/core/spell_result.go b/sim/core/spell_result.go index aa46b457ab..7d4ae65a31 100644 --- a/sim/core/spell_result.go +++ b/sim/core/spell_result.go @@ -163,7 +163,7 @@ func (spell *Spell) DodgeParrySuppression() float64 { } func (spell *Spell) PhysicalHitChance(attackTable *AttackTable) float64 { - hitPercent := spell.Unit.stats[stats.PhysicalHitPercent] + spell.BonusHitPercent + hitPercent := spell.Unit.stats[stats.PhysicalHitPercent] + spell.BonusHitPercent - attackTable.Defender.PseudoStats.ReducedPhysicalHitTakenChance return hitPercent / 100 } func (spell *Spell) PhysicalHitCheck(sim *Simulation, attackTable *AttackTable) bool { @@ -208,7 +208,8 @@ func (spell *Spell) SpellCritChance(target *Unit) float64 { attackTable := spell.Unit.AttackTables[target.UnitIndex] critPercent := spell.Unit.stats[stats.SpellCritPercent] + spell.BonusCritPercent + - attackTable.BonusSpellCritPercent + attackTable.BonusSpellCritPercent - + target.PseudoStats.ReducedCritTakenChance return max(critPercent/100-attackTable.SpellCritSuppression, 0) } func (spell *Spell) MagicCritCheck(sim *Simulation, target *Unit) bool { @@ -695,7 +696,7 @@ func (result *SpellResult) applyTargetModifiers(sim *Simulation, spell *Spell, a if spell.Flags.Matches(SpellFlagIgnoreTargetModifiers) { return } - + result.Damage += attackTable.Defender.PseudoStats.BonusPhysicalDamageTaken result.Damage *= spell.TargetDamageMultiplier(sim, attackTable, isPeriodic) } func (spell *Spell) TargetDamageMultiplier(sim *Simulation, attackTable *AttackTable, isPeriodic bool) float64 { diff --git a/sim/core/unit.go b/sim/core/unit.go index 0537cb5623..7dfaf6f94b 100644 --- a/sim/core/unit.go +++ b/sim/core/unit.go @@ -868,6 +868,7 @@ func (unit *Unit) GetTotalParryChanceAsDefender(atkTable *AttackTable) float64 { } func (unit *Unit) GetTotalChanceToBeMissedAsDefender(atkTable *AttackTable) float64 { + chance := atkTable.BaseMissChance + unit.PseudoStats.ReducedPhysicalHitTakenChance return math.Max(chance, 0.0) diff --git a/sim/core/utils.go b/sim/core/utils.go index 03d5459927..b70a4fb023 100644 --- a/sim/core/utils.go +++ b/sim/core/utils.go @@ -50,6 +50,7 @@ func (unit *Unit) ExecuteResourceGain(sim *Simulation, resource proto.ResourceTy panic("Unsupported Resource Type in ExecuteResourceGain") } } + func GetTristateValueInt32(effect proto.TristateEffect, regularValue int32, impValue int32) int32 { if effect == proto.TristateEffect_TristateEffectRegular { return regularValue @@ -70,6 +71,14 @@ func GetTristateValueFloat(effect proto.TristateEffect, regularValue float64, im } } +func IsImproved(effect proto.TristateEffect) bool { + if effect == proto.TristateEffect_TristateEffectImproved { + return true + } else { + return false + } +} + func MakeTristateValue(hasRegular bool, hasImproved bool) proto.TristateEffect { if !hasRegular { return proto.TristateEffect_TristateEffectMissing diff --git a/sim/druid/druid.go b/sim/druid/druid.go index 0cd87386ac..9c18b0b018 100644 --- a/sim/druid/druid.go +++ b/sim/druid/druid.go @@ -224,10 +224,6 @@ func (druid *Druid) Initialize() { } }) - druid.WeakenedBlowsAuras = druid.NewEnemyAuraArray(func(target *core.Unit) *core.Aura { - return core.WeakenedBlowsAura(target) - }) - druid.RegisterBaselineSpells() } diff --git a/sim/warlock/curse_of_elements.go b/sim/warlock/curse_of_elements.go index e4ee72c16d..8c4c6db069 100644 --- a/sim/warlock/curse_of_elements.go +++ b/sim/warlock/curse_of_elements.go @@ -7,7 +7,7 @@ import ( ) func (warlock *Warlock) registerCurseOfElements() { - warlock.CurseOfElementsAuras = warlock.NewEnemyAuraArray(core.CurseOfElementsAura) + // warlock.CurseOfElementsAuras = warlock.NewEnemyAuraArray(core.CurseOfElementsAura) warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 1490}, diff --git a/sim/warlock/talents.go b/sim/warlock/talents.go index aa4f6363a2..eff79b3a3d 100644 --- a/sim/warlock/talents.go +++ b/sim/warlock/talents.go @@ -15,7 +15,7 @@ func (warlock *Warlock) applyAfflictionTalents() { warlock.applyImprovedCurseOfAgony() warlock.applyNightfall() warlock.applyEmpoweredCorruption() - warlock.applyShadowEmbrace() + // warlock.applyShadowEmbrace() warlock.applyShadowMastery() warlock.applyContagion() warlock.applyUnstableAffliction() @@ -187,28 +187,28 @@ func (warlock *Warlock) applyEmpoweredCorruption() { }) } -func (warlock *Warlock) applyShadowEmbrace() { - if warlock.Talents.ShadowEmbrace == 0 { - return - } - - warlock.RegisterAura(core.Aura{ - Label: "Shadow Embrace Talent", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, spellEffect *core.SpellResult) { - if !spellEffect.Landed() { - return - } - - if spell == warlock.Corruption || spell == warlock.SiphonLife || spell == warlock.CurseOfAgony || spell.SameAction(warlock.Seed.ActionID) { - core.ShadowEmbraceAura(spellEffect.Target, warlock.Talents.ShadowEmbrace, spell.Dot(spellEffect.Target).Duration).Activate(sim) - } - }, - }) -} +// func (warlock *Warlock) applyShadowEmbrace() { +// if warlock.Talents.ShadowEmbrace == 0 { +// return +// } + +// warlock.RegisterAura(core.Aura{ +// Label: "Shadow Embrace Talent", +// Duration: core.NeverExpires, +// OnReset: func(aura *core.Aura, sim *core.Simulation) { +// aura.Activate(sim) +// }, +// OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, spellEffect *core.SpellResult) { +// if !spellEffect.Landed() { +// return +// } + +// if spell == warlock.Corruption || spell == warlock.SiphonLife || spell == warlock.CurseOfAgony || spell.SameAction(warlock.Seed.ActionID) { +// core.ShadowEmbrace(spellEffect.Target, warlock.Talents.ShadowEmbrace, spell.Dot(spellEffect.Target).Duration).Activate(sim) +// } +// }, +// }) +// } func (warlock *Warlock) applyShadowMastery() { if warlock.Talents.ShadowMastery == 0 { diff --git a/sim/warrior/warrior.go b/sim/warrior/warrior.go index fa1a633cb8..75d0a7b152 100644 --- a/sim/warrior/warrior.go +++ b/sim/warrior/warrior.go @@ -132,8 +132,6 @@ func (warrior *Warrior) Initialize() { warrior.sharedHSCleaveCD = warrior.NewTimer() warrior.sharedShoutsCD = warrior.NewTimer() - warrior.WeakenedArmorAuras = warrior.NewEnemyAuraArray(core.WeakenedArmorAura) - // warrior.registerStances() // warrior.registerShouts() warrior.registerPassives() diff --git a/ui/core/components/individual_sim_ui/settings_tab.tsx b/ui/core/components/individual_sim_ui/settings_tab.tsx index 28145ba46b..060524e162 100644 --- a/ui/core/components/individual_sim_ui/settings_tab.tsx +++ b/ui/core/components/individual_sim_ui/settings_tab.tsx @@ -175,6 +175,7 @@ export class SettingsTab extends SimTab { private buildOtherSettings() { const settings = this.simUI.individualConfig.otherInputs?.inputs.filter(inputs => !inputs.extraCssClasses?.includes('within-raid-sim-hide') || true); + console.log("SETTINGS", settings); const swapSlots = this.simUI.individualConfig.itemSwapSlots || []; if (settings.length > 0 || swapSlots.length > 0) { @@ -392,7 +393,7 @@ export class SettingsTab extends SimTab { private configureInputSection(sectionElem: HTMLElement, sectionConfig: InputSection) { sectionConfig.inputs.forEach(inputConfig => { if (inputConfig.type == 'number') { - new NumberPicker(sectionElem, this.simUI.player, inputConfig); + new NumberPicker(sectionElem, this.simUI.player, inputConfig); } else if (inputConfig.type == 'boolean') { new BooleanPicker(sectionElem, this.simUI.player, { ...inputConfig, reverse: true }); } else if (inputConfig.type == 'enum') { diff --git a/ui/core/components/input_helpers.ts b/ui/core/components/input_helpers.ts index f7710ced2e..287ca98a8d 100644 --- a/ui/core/components/input_helpers.ts +++ b/ui/core/components/input_helpers.ts @@ -144,6 +144,7 @@ export function makeRotationBooleanInput( ///////////////////////////////////////////////////////////////////////////////// export interface TypedNumberPickerConfig extends NumberPickerConfig { type: 'number'; + raid?: boolean } interface WrappedNumberInputConfig extends NumberPickerConfig { diff --git a/ui/core/components/inputs/buffs_debuffs.ts b/ui/core/components/inputs/buffs_debuffs.ts index 9b2ff60838..983b2a0d5e 100644 --- a/ui/core/components/inputs/buffs_debuffs.ts +++ b/ui/core/components/inputs/buffs_debuffs.ts @@ -312,87 +312,104 @@ export const DEBUFFS_CONFIG = [ { config: Mangle, picker: IconPicker, - stats: [Role.MELEE] + stats: [], + roles: [Role.MELEE] }, { config: Misery, picker: IconPicker, - stats: [Role.CASTER] + stats: [], + roles: [Role.CASTER] }, { config: ShadowWeaving, picker: IconPicker, - stats: [Role.CASTER], + stats: [], + roles: [Role.CASTER], }, { config: CurseOfElements, picker: IconPicker, - stats: [Role.CASTER] + stats: [], + roles: [Role.CASTER] }, { config: CurseOfRecklessness, picker: IconPicker, - stats: [Role.MELEE, Role.RANGED, Role.TANK] + stats: [], + roles: [Role.MELEE, Role.RANGED, Role.TANK] }, { config: FaerieFire, picker: IconPicker, - stats: [Role.MELEE, Role.RANGED, Role.TANK] + stats: [], + roles: [Role.MELEE, Role.RANGED, Role.TANK] }, { config: ExposeArmor, picker: IconPicker, - stats: [Role.MELEE, Role.RANGED, Role.TANK] + stats: [], + roles: [Role.MELEE, Role.RANGED, Role.TANK] }, { config: SunderArmor, picker: IconPicker, - stats: [Role.MELEE, Role.RANGED, Role.TANK] + stats: [], + roles: [Role.MELEE, Role.RANGED, Role.TANK] }, { config: WintersChill, picker: IconPicker, - stats: [Role.CASTER] + stats: [], + roles: [Role.CASTER] }, { config: GiftOfArthas, picker: IconPicker, - stats: [Role.MELEE, Role.RANGED, Role.TANK] + stats: [], + roles: [Role.MELEE, Role.RANGED, Role.TANK] }, { config: DemoralizingRoar, picker: IconPicker, - stats: [Role.TANK] + stats: [], + roles: [Role.TANK] }, { config: DemoralizingShout, picker: IconPicker, - stats: [Role.TANK] + stats: [], + roles: [Role.TANK] }, { config: Screech, picker: IconPicker, - stats: [Role.TANK] + stats: [], + roles: [Role.TANK] }, { config: ThunderClap, picker: IconPicker, - stats: [Role.TANK] + stats: [], + roles: [Role.TANK] }, { config: InsectSwarm, picker: IconPicker, - stats: [Role.TANK] + stats: [], + roles: [Role.TANK] }, { config: ScorpidSting, picker: IconPicker, - stats: [Role.TANK] + stats: [], + roles: [Role.TANK] }, { config: ShadowEmbrace, picker: IconPicker, - stats: [Role.TANK] + stats: [], + roles: [Role.TANK] } ] as PickerStatOptions[] diff --git a/ui/core/components/inputs/other_inputs.ts b/ui/core/components/inputs/other_inputs.ts index 76ee497ea1..75303e57b9 100644 --- a/ui/core/components/inputs/other_inputs.ts +++ b/ui/core/components/inputs/other_inputs.ts @@ -5,6 +5,8 @@ import { Sim } from '../../sim.js'; import { EventID } from '../../typed_event.js'; import { BooleanPicker } from '../pickers/boolean_picker.js'; import { EnumPicker } from '../pickers/enum_picker.js'; +import {Raid} from '../../raid'; +import { InputConfig } from '../../individual_sim_ui'; import i18n from '../../../i18n/config.js'; export function makeShow1hWeaponsSelector(parent: HTMLElement, sim: Sim): BooleanPicker { @@ -269,3 +271,35 @@ export const HpPercentForDefensives = { player.setSimpleCooldowns(eventID, cooldowns); }, }; + +export const IsbUptime = { + id: 'isbUptime', + type: 'number' as const, + raid: true, + float: true, + label: 'ISB Uptime', + labelTooltip: 'Amount of uptime for ISB', + changedEvent: (raid: Raid) => raid.debuffsChangeEmitter, + getValue: (raid: Raid) => Math.round(raid.getDebuffs().isbUptime * 100), + setValue: (eventID: EventID, raid: Raid, newValue: number) => { + const newDebuffs = raid.getDebuffs(); + newDebuffs.isbUptime = newValue / 100; + raid.setDebuffs(eventID, newDebuffs); + }, +} + +export const HemoUptime = { + id: 'hemoUptime', + type: 'number' as const, + raid: true, + float: true, + label: 'Hemorrhage Uptime', + labelTooltip: 'Amount of time hemorrhage is on the boss from a subtely rogue', + changedEvent: (raid: Raid) => raid.debuffsChangeEmitter, + getValue: (raid: Raid) => Math.round(raid.getDebuffs().hemorrhageUptime * 100), + setValue: (eventID: EventID, raid: Raid, newValue: number) => { + const newDebuffs = raid.getDebuffs(); + newDebuffs.hemorrhageUptime = newValue / 100; + raid.setDebuffs(eventID, newDebuffs); + }, +} diff --git a/ui/core/components/inputs/stat_options.ts b/ui/core/components/inputs/stat_options.ts index 3e0ba049fc..8a70a4e0c5 100644 --- a/ui/core/components/inputs/stat_options.ts +++ b/ui/core/components/inputs/stat_options.ts @@ -52,7 +52,7 @@ export function relevantStatOptions | option.stats.length == 0 || option.stats.some(stat => simUI.individualConfig.epStats.includes(stat)) || simUI.individualConfig.includeBuffDebuffInputs.includes(option.config) || - (option.roles && option.roles.includes(simUI.player.playerSpec.role)) + option.roles?.includes(simUI.player.playerSpec.role) ) .filter(option => !simUI.individualConfig.excludeBuffDebuffInputs.includes(option.config)); diff --git a/ui/core/individual_sim_ui.tsx b/ui/core/individual_sim_ui.tsx index 0c84a5e702..6026e67932 100644 --- a/ui/core/individual_sim_ui.tsx +++ b/ui/core/individual_sim_ui.tsx @@ -67,6 +67,7 @@ import { SimUI, SimWarning } from './sim_ui'; import { EventID, TypedEvent } from './typed_event'; import { isDevMode } from './utils'; import { CURRENT_API_VERSION } from './constants/other'; +import { Raid } from './raid'; const SAVED_GEAR_STORAGE_KEY = '__savedGear__'; const SAVED_EP_WEIGHTS_STORAGE_KEY = '__savedEPWeights__'; @@ -81,7 +82,7 @@ export type InputConfig = export interface InputSection { tooltip?: string; - inputs: Array>>; + inputs: Array> } export interface OtherDefaults { diff --git a/ui/core/worker_pool.ts b/ui/core/worker_pool.ts index e6c39e06f9..6ddbc58253 100644 --- a/ui/core/worker_pool.ts +++ b/ui/core/worker_pool.ts @@ -115,7 +115,7 @@ export class WorkerPool { } async raidSimAsync(request: RaidSimRequest, onProgress: WorkerProgressCallback, signals: SimSignals): Promise { - console.log("BEFORE WORKER?") + const worker = this.getLeastBusyWorker(); console.log("RAID SIM REQUEST AFTER WORK BEFORE THING" + RaidSimRequest.toJsonString(request)) worker.log('Raid sim request: ' + RaidSimRequest.toJsonString(request)); diff --git a/ui/warlock/dps/sim.ts b/ui/warlock/dps/sim.ts index 8153898405..5f4746f3d8 100644 --- a/ui/warlock/dps/sim.ts +++ b/ui/warlock/dps/sim.ts @@ -115,6 +115,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarlock, { exposeArmor: TristateEffect.TristateEffectRegular, improvedScorch: false, improvedSealOfTheCrusader: true, + hemorrhageUptime: 0, isbUptime: 0, misery: true, shadowWeaving: true, @@ -138,7 +139,12 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarlock, { petConsumeInputs: [], // Inputs to include in the 'Other' section on the settings tab. otherInputs: { - inputs: [OtherInputs.InputDelay, OtherInputs.DistanceFromTarget, OtherInputs.TankAssignment, OtherInputs.ChannelClipDelay], + inputs: [ + OtherInputs.IsbUptime, + OtherInputs.HemoUptime, + OtherInputs.DistanceFromTarget, + OtherInputs.TankAssignment, + ], }, itemSwapSlots: [ItemSlot.ItemSlotMainHand, ItemSlot.ItemSlotOffHand, ItemSlot.ItemSlotTrinket1, ItemSlot.ItemSlotTrinket2], encounterPicker: { From 578cfb78d825f0467c1b7b5083f6569238f2c074 Mon Sep 17 00:00:00 2001 From: jazz405 Date: Sun, 14 Dec 2025 14:40:31 -0500 Subject: [PATCH 14/20] missed save --- ui/core/components/inputs/buffs_debuffs.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/ui/core/components/inputs/buffs_debuffs.ts b/ui/core/components/inputs/buffs_debuffs.ts index 4b4d2a6f10..983b2a0d5e 100644 --- a/ui/core/components/inputs/buffs_debuffs.ts +++ b/ui/core/components/inputs/buffs_debuffs.ts @@ -60,7 +60,6 @@ export const BloodPact = makeTristatePartyBuffInput({actionId: ActionId.fromSpel export const DrumsOfBattleBuff = makeEnumValuePartyBuffInput(ActionId.fromItemId(185848), 'drums', Drums.DrumsOfBattle, ['Drums']); export const DrumsOfRestorationBuff = makeEnumValuePartyBuffInput(ActionId.fromItemId(185850), 'drums', Drums.DrumsOfRestoration, ['Drums']); -<<<<<<< HEAD // Individual Buffs export const BlessingOfKings = makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(25898), fieldName: 'blessingOfKings', label: 'Blessing of Kings'}); export const BlessingOfMight = makeTristateIndividualBuffInput({actionId: ActionId.fromSpellId(27140), impId: ActionId.fromSpellId(20048), fieldName: 'blessingOfMight', label: 'Blessing of Might'}); @@ -70,17 +69,6 @@ export const BlessingOfWisdom = makeTristateIndividualBuffInput({actionId: Actio export const Innervate = makeMultistateIndividualBuffInput({actionId: ActionId.fromSpellId(29166), numStates: 11, fieldName: 'innervates', label: 'Innervates'}); export const PowerInfusion = makeMultistateIndividualBuffInput({actionId: ActionId.fromSpellId(10060), numStates: 11, fieldName: 'powerInfusions', label: 'Power Infusions'}); export const UnleashedRage = makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(30811), fieldName: 'unleashedRage', label: 'Unleashed Rage'}); -======= -export const SpellDamageBuff = InputHelpers.makeMultiIconInput( - [ - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(1459), fieldName: 'arcaneBrilliance' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(126309), fieldName: 'stillWater' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(77747), fieldName: 'burningWrath' }), - // makeBooleanRaidBuffInput({ actionId: ActionId.fromSpellId(109773), fieldName: 'darkIntent' }), - ], - i18n.t('settings_tab.raid_buffs.spell_power'), -); ->>>>>>> b3ce9f60d9f9cb1a719bfc3b8d850772c937187f export const PARTY_BUFFS_CONFIG = [ { @@ -99,7 +87,6 @@ export const PARTY_BUFFS_CONFIG = [ stats: [Stat.StatArmor] }, { -<<<<<<< HEAD config: FerociousInspiration, picker: IconPicker, stats: [] @@ -108,11 +95,6 @@ export const PARTY_BUFFS_CONFIG = [ config: LeaderOfThePack, picker: IconPicker, stats: [] -======= - config: SpellDamageBuff, - picker: MultiIconPicker, - stats: [Stat.StatSpellDamage], ->>>>>>> b3ce9f60d9f9cb1a719bfc3b8d850772c937187f }, { config: ManaSpringTotem, From 4c11d368d3800ec17f7147bbb7c407f139b1db2d Mon Sep 17 00:00:00 2001 From: jazz405 Date: Sun, 14 Dec 2025 21:51:54 -0500 Subject: [PATCH 15/20] most of the debuffs tested except for tank ones --- sim/core/debuffs.go | 130 +++++++++++------- sim/core/dot_test.go | 2 +- sim/core/spell_result.go | 2 +- sim/core/stats/stats.go | 10 +- sim/core/unit.go | 2 +- sim/warlock/pets.go | 2 +- sim/warlock/shadowbolt.go | 2 +- tools/database/dbc/util.go | 12 +- tools/tooltip/tooltip_parser_test.go | 2 +- .../individual_sim_ui/settings_tab.tsx | 2 +- ui/core/components/inputs/other_inputs.ts | 22 +-- ui/warlock/dps/sim.ts | 2 +- 12 files changed, 115 insertions(+), 75 deletions(-) diff --git a/sim/core/debuffs.go b/sim/core/debuffs.go index d72279f728..548f0e7106 100644 --- a/sim/core/debuffs.go +++ b/sim/core/debuffs.go @@ -69,6 +69,7 @@ func applyDebuffEffects(target *Unit, targetIdx int, debuffs *proto.Debuffs, rai } if debuffs.IsbUptime > 0.0 { + println("ISB UPTIME") MakePermanent(ImprovedShadowBolt(target, debuffs.IsbUptime)) } @@ -193,11 +194,11 @@ func FaerieFire(target *Unit, improved bool) *Aura { ActionID: ActionID{SpellID: 26993}, Duration: time.Second * 40, OnGain: func(aura *Aura, sim *Simulation) { - aura.Unit.AddStatsDynamic(sim, stats.Stats{stats.Armor: -6100}) + aura.Unit.AddStatsDynamic(sim, stats.Stats{stats.Armor: -610}) aura.Unit.PseudoStats.ReducedPhysicalHitTakenChance -= 3.0 }, OnExpire: func(aura *Aura, sim *Simulation) { - aura.Unit.AddStatsDynamic(sim, stats.Stats{stats.Armor: 6100}) + aura.Unit.AddStatsDynamic(sim, stats.Stats{stats.Armor: 610}) aura.Unit.PseudoStats.ReducedPhysicalHitTakenChance += 3.0 }, }) @@ -219,30 +220,14 @@ func GiftOfArthas(target *Unit) *Aura { func Hemorrhage(target *Unit, uptime float64) *Aura { return target.GetOrRegisterAura(Aura{ - Label: "Hemorrhage", - ActionID: ActionID{SpellID: 33876}, - Duration: time.Second * 15, - MaxStacks: 15, + Label: "Hemorrhage", + ActionID: ActionID{SpellID: 33876}, + Duration: time.Second * 15, OnGain: func(aura *Aura, sim *Simulation) { - aura.stacks = 15 + aura.Unit.PseudoStats.BonusPhysicalDamageTaken += 42 * uptime }, OnExpire: func(aura *Aura, sim *Simulation) { - aura.stacks = 15 - }, - OnSpellHitTaken: func(aura *Aura, sim *Simulation, spell *Spell, result *SpellResult) { - if !spell.SpellSchool.Matches(SpellSchoolPhysical) { - return - } - - if aura.stacks <= 0 { - aura.Deactivate(sim) - return - } - - result.Damage += 42 * uptime - spell.DealDamage(sim, result) - - aura.stacks-- + aura.Unit.PseudoStats.BonusPhysicalDamageTaken -= 42 * uptime }, }) } @@ -256,16 +241,25 @@ func HuntersMark(target *Unit, improved bool) *Aura { ActionID: ActionID{SpellID: 14325}, Duration: NeverExpires, OnGain: func(aura *Aura, sim *Simulation) { - if improved { - aura.Unit.AddStat(stats.AttackPower, maxBonus) + for _, unit := range sim.AllUnits { + if unit.Type == PlayerUnit || unit.Type == PetUnit { + if improved { + unit.PseudoStats.BonusAttackPower += maxBonus + } + unit.PseudoStats.BonusRangedAttackPower += maxBonus + } } - aura.Unit.AddStat(stats.RangedAttackPower, maxBonus) + }, OnExpire: func(aura *Aura, sim *Simulation) { - if improved { - aura.Unit.AddStat(stats.AttackPower, -maxBonus) + for _, unit := range sim.AllUnits { + if unit.Type == PlayerUnit || unit.Type == PetUnit { + if improved { + unit.PseudoStats.BonusAttackPower -= maxBonus + } + unit.PseudoStats.BonusRangedAttackPower -= maxBonus + } } - aura.Unit.AddStat(stats.RangedAttackPower, -maxBonus) }, }) } @@ -290,18 +284,40 @@ func ImprovedSealOfTheCrusader(target *Unit) *Aura { } func ImprovedShadowBolt(target *Unit, uptime float64) *Aura { - multiplier := 1.2 * uptime + multiplier := 0.20 * uptime return target.GetOrRegisterAura(Aura{ Label: "ImprovedShadowBolt", ActionID: ActionID{SpellID: 17803}, Duration: time.Second * 12, MaxStacks: 4, + OnGain: func(aura *Aura, sim *Simulation) { - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexShadow] *= multiplier + for _, unit := range sim.AllUnits { + if unit.Type == PlayerUnit || unit.Type == PetUnit { + unit.AddStaticMod(SpellModConfig{ + Kind: SpellMod_DamageDone_Pct, + FloatValue: (0.20 * uptime), + School: SpellSchoolShadow, + ProcMask: ProcMaskSpellDamage, + ShouldApplyToPets: true, + }) + } + } }, OnExpire: func(aura *Aura, sim *Simulation) { - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexShadow] /= multiplier + inverse := (1.0 / (1.0 + multiplier)) - 1.0 + for _, unit := range sim.AllUnits { + if unit.Type == PlayerUnit { + unit.AddStaticMod(SpellModConfig{ + Kind: SpellMod_DamageDone_Pct, + FloatValue: inverse, + School: SpellSchoolShadow, + ProcMask: ProcMaskSpellDamage, + ShouldApplyToPets: true, + }) + } + } }, }) } @@ -342,22 +358,29 @@ func JudgementOfWisdom(target *Unit) *Aura { actionId := ActionID{SpellID: 27167} return target.GetOrRegisterAura(Aura{ - Label: "Judgement of Light", + Label: "Judgement of Wisdom", ActionID: actionId, Duration: time.Second * 20, OnSpellHitTaken: func(aura *Aura, sim *Simulation, spell *Spell, result *SpellResult) { - var manaMetrics *ResourceMetrics - manaMetrics = aura.Unit.NewManaMetrics(actionId) - if !spell.ProcMask.Matches(ProcMaskMelee) || !result.Landed() { + if spell.ProcMask.Matches(ProcMaskEmpty) { + return // Phantom spells (Romulo's, Lightning Capacitor, etc) don't proc JoW. + } + + // Melee claim that wisdom can proc on misses. + if !spell.ProcMask.Matches(ProcMaskMeleeOrRanged) && !result.Landed() { return } - if spell.ActionID.SpellID == 35395 { - aura.Refresh(sim) + unit := spell.Unit + if unit.HasManaBar() { + if unit.JowManaMetrics == nil { + unit.JowManaMetrics = unit.NewManaMetrics(actionId) + } + unit.AddMana(sim, 121.0, unit.JowManaMetrics) } - if rand.Float64() < 50.0 { - aura.Unit.AddMana(sim, 121.0, manaMetrics) + if spell.ActionID.SpellID == 35395 { + aura.Refresh(sim) } }, }) @@ -413,7 +436,7 @@ func Stormstrike(target *Unit, uptime float64) *Aura { } func SunderArmor(target *Unit) *Aura { - return statsDebuff(target, "Sunder Amor", 25225, stats.Stats{stats.Armor: 2600}) + return statsDebuff(target, "Sunder Amor", 25225, stats.Stats{stats.Armor: -2600}) } func WintersChill(target *Unit) *Aura { @@ -422,11 +445,26 @@ func WintersChill(target *Unit) *Aura { ActionID: ActionID{SpellID: 28595}, Duration: time.Second * 15, OnGain: func(aura *Aura, sim *Simulation) { - aura.Unit.AddStaticMod(SpellModConfig{ - Kind: SpellMod_BonusCrit_Percent, - FloatValue: 10.0, - School: SpellSchoolFire, - }) + for _, unit := range sim.AllUnits { + if unit.Type == PlayerUnit || unit.Type == PetUnit { + unit.AddStaticMod(SpellModConfig{ + Kind: SpellMod_BonusCrit_Percent, + FloatValue: 10.0, + School: SpellSchoolFrost, + }) + } + } + }, + OnExpire: func(aura *Aura, sim *Simulation) { + for _, unit := range sim.AllUnits { + if unit.Type == PlayerUnit || unit.Type == PetUnit { + unit.AddStaticMod(SpellModConfig{ + Kind: SpellMod_BonusCrit_Percent, + FloatValue: -10.0, + School: SpellSchoolFrost, + }) + } + } }, }) } diff --git a/sim/core/dot_test.go b/sim/core/dot_test.go index 2a236064d1..2794a47f65 100644 --- a/sim/core/dot_test.go +++ b/sim/core/dot_test.go @@ -72,7 +72,7 @@ func NewFakeElementalShaman(char *Character, _ *proto.Player) Agent { AffectedByCastSpeed: true, BonusCoefficient: 1, - OnSnapshot: func(sim *Simulation, target *Unit, dot *Dot, isRollover bool) { + OnSnapshot: func(sim *Simulation, target *Unit, dot *Dot) { dot.Snapshot(target, 100) }, OnTick: func(sim *Simulation, target *Unit, dot *Dot) { diff --git a/sim/core/spell_result.go b/sim/core/spell_result.go index 82ce1a0e06..7b4cb7682b 100644 --- a/sim/core/spell_result.go +++ b/sim/core/spell_result.go @@ -153,7 +153,7 @@ func (spell *Spell) MeleeAttackPower() float64 { } func (spell *Spell) RangedAttackPower() float64 { - return spell.Unit.stats[stats.RangedAttackPower] + return spell.Unit.stats[stats.RangedAttackPower] + spell.Unit.PseudoStats.BonusRangedAttackPower } func (spell *Spell) DodgeParrySuppression() float64 { diff --git a/sim/core/stats/stats.go b/sim/core/stats/stats.go index 0b3d428c56..0d7c818c6a 100644 --- a/sim/core/stats/stats.go +++ b/sim/core/stats/stats.go @@ -464,6 +464,9 @@ type PseudoStats struct { PeriodicHealingDealtMultiplier float64 // All periodic healing (on top of HealingDealtMultiplier) CritDamageMultiplier float64 // All multiplicative crit damage + BonusRangedAttackPower float64 // Hunter's mark + BonusAttackPower float64 // Also Hunter's Mark + // Important when unit is attacker or target BlockDamageReduction float64 @@ -488,10 +491,9 @@ type PseudoStats struct { ReducedCritTakenChance float64 // Reduces chance to be crit. - BonusHealingTaken float64 // Talisman of Troll Divinity - BonusRangedAttackPowerTaken float64 // Hunters mark - BonusSpellCritPercentTaken float64 // Imp Shadow Bolt / Imp Scorch / Winter's Chill debuff - BonusPhysicalDamageTaken float64 // Hemo, Gift of Arthas, etc + BonusHealingTaken float64 // Talisman of Troll Divinity + BonusSpellCritPercentTaken float64 // Imp Shadow Bolt / Imp Scorch / Winter's Chill debuff + BonusPhysicalDamageTaken float64 // Hemo, Gift of Arthas, etc DamageTakenMultiplier float64 // All damage SchoolDamageTakenMultiplier [SchoolLen]float64 // For specific spell schools (arcane, fire, shadow, etc.) diff --git a/sim/core/unit.go b/sim/core/unit.go index 0326638b66..d7e0d8940a 100644 --- a/sim/core/unit.go +++ b/sim/core/unit.go @@ -209,7 +209,7 @@ func (unit *Unit) getSpellDamageValueImpl(spell *Spell) float64 { } func (unit *Unit) getAttackPowerValueImpl(spell *Spell) float64 { - return unit.stats[stats.AttackPower] + return unit.stats[stats.AttackPower] + unit.PseudoStats.BonusAttackPower } // Units can be disabled for several reasons: diff --git a/sim/warlock/pets.go b/sim/warlock/pets.go index 311c301b7c..964f2d2111 100644 --- a/sim/warlock/pets.go +++ b/sim/warlock/pets.go @@ -338,7 +338,7 @@ func (pet *WarlockPet) registerLashOfPainSpell() { BonusCoefficient: 0.907, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcDamage(sim, target, 5000, spell.OutcomeMagicHitAndCrit) + result := spell.CalcDamage(sim, target, 500, spell.OutcomeMagicHitAndCrit) spell.DealDamage(sim, result) }, })) diff --git a/sim/warlock/shadowbolt.go b/sim/warlock/shadowbolt.go index a06bdb9185..b547fda925 100644 --- a/sim/warlock/shadowbolt.go +++ b/sim/warlock/shadowbolt.go @@ -30,7 +30,7 @@ func (warlock *Warlock) registerShadowBolt() { ThreatMultiplier: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcDamage(sim, target, .517, spell.OutcomeMagicHitAndCrit) + result := spell.CalcDamage(sim, target, 1000, spell.OutcomeMagicHitAndCrit) spell.WaitTravelTime(sim, func(sim *core.Simulation) { spell.DealDamage(sim, result) }) diff --git a/tools/database/dbc/util.go b/tools/database/dbc/util.go index 99c5b748de..9055f2463e 100644 --- a/tools/database/dbc/util.go +++ b/tools/database/dbc/util.go @@ -213,17 +213,17 @@ func ConvertTargetResistanceFlagToPenetrationStat(flag int) proto.Stat { func ConvertSpellDamageFlagToSchoolDamageStat(flag int) proto.Stat { switch flag { case 2: - return proto.Stat_StatHolyPower + return proto.Stat_StatHolyDamage case 4: - return proto.Stat_StatFirePower + return proto.Stat_StatFireDamage case 8: - return proto.Stat_StatNaturePower + return proto.Stat_StatNatureDamage case 16: - return proto.Stat_StatFrostPower + return proto.Stat_StatFrostDamage case 32: - return proto.Stat_StatShadowPower + return proto.Stat_StatShadowDamage case 64: - return proto.Stat_StatArcanePower + return proto.Stat_StatArcaneDamage default: return proto.Stat_StatSpellDamage } diff --git a/tools/tooltip/tooltip_parser_test.go b/tools/tooltip/tooltip_parser_test.go index ff12a5519c..e329dee99b 100644 --- a/tools/tooltip/tooltip_parser_test.go +++ b/tools/tooltip/tooltip_parser_test.go @@ -10,7 +10,7 @@ var db = dbc.GetDBC() func Test_WhenInvalidTernaryGiven_ThenProperlyApplyFixes(t *testing.T) { tp, error := ParseTooltip("$ damage every ${$16914d3/10}.2 seconds$?$w1!=0[ and movement slowed by $w1%][].", - NewTestDataProvider(CharacterConfig{SpellPower: 1000}), + NewTestDataProvider(CharacterConfig{SpellDamage: 1000}), 16914, ) diff --git a/ui/core/components/individual_sim_ui/settings_tab.tsx b/ui/core/components/individual_sim_ui/settings_tab.tsx index 060524e162..b9355dfade 100644 --- a/ui/core/components/individual_sim_ui/settings_tab.tsx +++ b/ui/core/components/individual_sim_ui/settings_tab.tsx @@ -175,7 +175,6 @@ export class SettingsTab extends SimTab { private buildOtherSettings() { const settings = this.simUI.individualConfig.otherInputs?.inputs.filter(inputs => !inputs.extraCssClasses?.includes('within-raid-sim-hide') || true); - console.log("SETTINGS", settings); const swapSlots = this.simUI.individualConfig.itemSwapSlots || []; if (settings.length > 0 || swapSlots.length > 0) { @@ -183,6 +182,7 @@ export class SettingsTab extends SimTab { header: { title: i18n.t('settings_tab.other.title') }, }); + if (settings.length > 0) { this.configureInputSection(contentBlock.bodyElement, this.simUI.individualConfig.otherInputs); contentBlock.bodyElement.querySelectorAll('.input-root').forEach(elem => { diff --git a/ui/core/components/inputs/other_inputs.ts b/ui/core/components/inputs/other_inputs.ts index 75303e57b9..0acf23ece5 100644 --- a/ui/core/components/inputs/other_inputs.ts +++ b/ui/core/components/inputs/other_inputs.ts @@ -279,12 +279,12 @@ export const IsbUptime = { float: true, label: 'ISB Uptime', labelTooltip: 'Amount of uptime for ISB', - changedEvent: (raid: Raid) => raid.debuffsChangeEmitter, - getValue: (raid: Raid) => Math.round(raid.getDebuffs().isbUptime * 100), - setValue: (eventID: EventID, raid: Raid, newValue: number) => { - const newDebuffs = raid.getDebuffs(); + changedEvent: (player: Player) => player.getRaid()!.debuffsChangeEmitter, + getValue: (player: Player) => Math.round(player.getRaid()!.getDebuffs().isbUptime! * 100), + setValue: (eventID: EventID, player: Player, newValue: number) => { + const newDebuffs = player.getRaid()!.getDebuffs()!; newDebuffs.isbUptime = newValue / 100; - raid.setDebuffs(eventID, newDebuffs); + player.getRaid()!.setDebuffs(eventID, newDebuffs); }, } @@ -295,11 +295,11 @@ export const HemoUptime = { float: true, label: 'Hemorrhage Uptime', labelTooltip: 'Amount of time hemorrhage is on the boss from a subtely rogue', - changedEvent: (raid: Raid) => raid.debuffsChangeEmitter, - getValue: (raid: Raid) => Math.round(raid.getDebuffs().hemorrhageUptime * 100), - setValue: (eventID: EventID, raid: Raid, newValue: number) => { - const newDebuffs = raid.getDebuffs(); - newDebuffs.hemorrhageUptime = newValue / 100; - raid.setDebuffs(eventID, newDebuffs); + changedEvent: (player: Player) => player.getRaid()!.debuffsChangeEmitter, + getValue: (player: Player) => Math.round(player.getRaid()!.getDebuffs().hemorrhageUptime! * 100), + setValue: (eventID: EventID, player: Player, newValue: number) => { + const newDebuffs = player.getRaid()!.getDebuffs(); + newDebuffs!.hemorrhageUptime = newValue / 100; + player.getRaid()!.setDebuffs(eventID, newDebuffs!); }, } diff --git a/ui/warlock/dps/sim.ts b/ui/warlock/dps/sim.ts index d1efdee219..6010e5a6d2 100644 --- a/ui/warlock/dps/sim.ts +++ b/ui/warlock/dps/sim.ts @@ -134,7 +134,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarlock, { ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. - includeBuffDebuffInputs: [], + includeBuffDebuffInputs: [BuffDebuffInputs.HuntersMark], excludeBuffDebuffInputs: [], petConsumeInputs: [], // Inputs to include in the 'Other' section on the settings tab. From b13e4d77eafbd31377c44d14f41342387cc65ff8 Mon Sep 17 00:00:00 2001 From: jazz405 Date: Mon, 22 Dec 2025 20:39:42 -0500 Subject: [PATCH 16/20] commit latest --- sim/core/buffs.go | 240 +++++++++++++++++--------------------- sim/core/debuffs.go | 158 ++++++++++++------------- sim/warlock/shadowbolt.go | 13 +++ sim/warlock/talents.go | 11 +- 4 files changed, 205 insertions(+), 217 deletions(-) diff --git a/sim/core/buffs.go b/sim/core/buffs.go index 2b33d52c7a..67ef10d16d 100644 --- a/sim/core/buffs.go +++ b/sim/core/buffs.go @@ -36,14 +36,18 @@ func makeExclusiveMultiplierBuff(aura *Aura, stat stats.Stat, value float64) { }) } -func makeExclusiveFlatStatBuff(aura *Aura, stat stats.Stat, value float64) { - aura.NewExclusiveEffect(stat.StatName()+"Buff", false, ExclusiveEffect{ - Priority: value, - OnGain: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.AddStatDynamic(sim, stat, value) +func makeStatsBuff(unit *Unit, label string, spellID int32, duration time.Duration, stats stats.Stats) *Aura { + return unit.GetOrRegisterAura(Aura{ + Label: label + "Buff", + ActionID: ActionID{SpellID: spellID}, + Duration: duration, + + OnGain: func(aura *Aura, sim *Simulation) { + aura.Unit.AddStatsDynamic(sim, stats) }, - OnExpire: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.AddStatDynamic(sim, stat, -value) + + OnExpire: func(aura *Aura, sim *Simulation) { + aura.Unit.AddStatsDynamic(sim, stats.Multiply(-1)) }, }) } @@ -90,128 +94,103 @@ func makeExclusiveBuff(unit *Unit, config BuffConfig) *Aura { // Applies buffs that affect individual players. func applyBuffEffects(agent Agent, raidBuffs *proto.RaidBuffs, _ *proto.PartyBuffs, individual *proto.IndividualBuffs) { - //char := agent.GetCharacter() - //u := &char.Unit - - // // +10% Attack Power - // if raidBuffs.TrueshotAura { - // TrueShotAura(u) - // } - // if raidBuffs.BattleShout { - // BattleShoutAura(u, true) - // } - - // // +10% Melee and Ranged Attack Speed - // if raidBuffs.UnholyAura { - // UnholyAura(u) - // } - // if raidBuffs.CacklingHowl { - // CacklingHowlAura(u) - // } - // if raidBuffs.SerpentsSwiftness { - // SerpentsSwiftnessAura(u) - // } - // if raidBuffs.SwiftbladesCunning { - // SwiftbladesCunningAura(u) - // } - // if raidBuffs.UnleashedRage { - // UnleashedRageAura(u) - // } - - // // +10% Spell Power - // if raidBuffs.StillWater { - // StillWaterAura(u) - // } - // if raidBuffs.ArcaneBrilliance { - // ArcaneBrilliance(u) - // } - // if raidBuffs.BurningWrath { - // BurningWrathAura(u) - // } - // if raidBuffs.DarkIntent { - // MakePermanent(DarkIntentAura(u)) - // } - - // // +5% Spell Haste - // if raidBuffs.MoonkinAura { - // MoonkinAura(u) - // } - // if raidBuffs.MindQuickening { - // MindQuickeningAura(u) - // } - - // if raidBuffs.ElementalOath { - // ElementalOath(u) - // } - - // // +5% Critical Strike Chance - // if raidBuffs.LeaderOfThePack { - // LeaderOfThePack(u) - // } - // if raidBuffs.TerrifyingRoar { - // TerrifyingRoar(u) - // } - // if raidBuffs.FuriousHowl { - // FuriousHowl(u) - // } - - // // +3000 Mastery Rating - // if raidBuffs.RoarOfCourage { - // RoarOfCourageAura(u) - // } - // if raidBuffs.SpiritBeastBlessing { - // SpiritBeastBlessingAura(u) - // } - // if raidBuffs.BlessingOfMight { - // BlessingOfMightAura(u) - // } - // if raidBuffs.GraceOfAir { - // GraceOfAirAura(u) - // } - - // // +5% Strength, Agility, Intellect - // if raidBuffs.MarkOfTheWild { - // MarkOfTheWildAura(u) - // } - // if raidBuffs.EmbraceOfTheShaleSpider { - // EmbraceOfTheShaleSpiderAura(u) - // } - // if raidBuffs.BlessingOfKings { - // BlessingOfKingsAura(u) - // } - - // // Stamina & Strength/Agility secondary grouping - // applyStaminaBuffs(u, raidBuffs) - - // registerManaTideTotemCD(agent, raidBuffs.ManaTideTotemCount) - // registerSkullBannerCD(agent, raidBuffs.SkullBannerCount) - // registerStormLashCD(agent, raidBuffs.StormlashTotemCount) - - // // Individual cooldowns and major buffs - // if len(char.Env.Raid.AllPlayerUnits)-char.Env.Raid.NumTargetDummies == 1 { - // // Major Haste - // if raidBuffs.Bloodlust { - // registerBloodlustCD(agent, 2825) - // } - - // // Other individual CDs - // registerUnholyFrenzyCD(agent, individual.UnholyFrenzyCount) - // if individual.TricksOfTheTrade { - // registerTricksOfTheTradeCD(agent) - // } - // registerDevotionAuraCD(agent, individual.DevotionAuraCount) - // registerVigilanceCD(agent, individual.VigilanceCount) - // registerPainSuppressionCD(agent, individual.PainSuppressionCount) - // registerGuardianSpiritCD(agent, individual.GuardianSpiritCount) - // registerRallyingCryCD(agent, individual.RallyingCryCount) - // registerShatteringThrowCD(agent, individual.ShatteringThrowCount) - // } + char := agent.GetCharacter() + u := &char.Unit + + if raidBuffs.ArcaneBrilliance { + MakePermanent(ArcaneBrilliance(u)) + } + + if raidBuffs.DivineSpirit != proto.TristateEffect_TristateEffectMissing { + MakePermanent(DivineSpirit(u, IsImproved(raidBuffs.DivineSpirit))) + } + + if raidBuffs.GiftOfTheWild != proto.TristateEffect_TristateEffectMissing { + MakePermanent(GiftOfTheWild(u, IsImproved(raidBuffs.GiftOfTheWild))) + } + + if raidBuffs.PowerWordFortitude != proto.TristateEffect_TristateEffectMissing { + MakePermanent(PowerWordFortitude(u, IsImproved(raidBuffs.PowerWordFortitude))) + } + + if raidBuffs.ShadowProtection { + MakePermanent(ShadowProtection(u)) + } + } /////////////////////////////////////////////////////////////////////////// -// Strength, Agility, Intellect 5% +// Raid Buffs /////////////////////////////////////////////////////////////////////////// +func ArcaneBrilliance(unit *Unit) *Aura { + // Mages: +10% Spell Power + return makeStatsBuff(unit, "Arcane Brilliance", 27127, time.Hour*1, stats.Stats{stats.Intellect: 40}) +} + +func DivineSpirit(unit *Unit, improved bool) *Aura { + spiritBuff := stats.Stats{stats.Spirit: 50} + + dsSDStatDep := unit.NewDynamicStatDependency(stats.Spirit, stats.SpellDamage, 10) + dsHPStatDep := unit.NewDynamicStatDependency(stats.Spirit, stats.HealingPower, 10) + + return unit.GetOrRegisterAura(Aura{ + Label: "Divine Spirit Buff", + ActionID: ActionID{SpellID: 25312}, + Duration: time.Minute * 30, + + OnGain: func(aura *Aura, sim *Simulation) { + unit.AddStatsDynamic(sim, spiritBuff) + if improved { + unit.EnableBuildPhaseStatDep(sim, dsSDStatDep) + unit.EnableBuildPhaseStatDep(sim, dsHPStatDep) + } + }, + + OnExpire: func(aura *Aura, sim *Simulation) { + unit.AddStatsDynamic(sim, spiritBuff.Invert()) + if improved { + unit.DisableBuildPhaseStatDep(sim, dsSDStatDep) + unit.DisableBuildPhaseStatDep(sim, dsHPStatDep) + } + }, + }) +} + +func GiftOfTheWild(unit *Unit, improved bool) *Aura { + mod := 1.0 + if improved { + mod = 1.35 + } + gotwStats := stats.Stats{ + stats.Armor: 340 * mod, + stats.Stamina: 14 * mod, + stats.Strength: 14 * mod, + stats.Agility: 14 * mod, + stats.Intellect: 14 * mod, + stats.Spirit: 14 * mod, + stats.ArcaneResistance: 25 * mod, + stats.FireResistance: 25 * mod, + stats.FrostResistance: 25 * mod, + stats.NatureResistance: 25 * mod, + stats.ShadowResistance: 25 * mod, + } + + return makeStatsBuff(unit, "Gift of the Wild", 26991, time.Hour*1, gotwStats) +} + +func PowerWordFortitude(unit *Unit, improved bool) *Aura { + mod := 1.0 + if improved { + mod = 1.3 + } + return makeStatsBuff(unit, "Power Word: Fortitude", 25389, time.Hour*1, stats.Stats{stats.Stamina: 79.0 * mod}) +} + +func ShadowProtection(unit *Unit) *Aura { + return makeStatsBuff(unit, "Shadow Protection", 10958, time.Minute*10, stats.Stats{stats.ShadowResistance: 60}) +} + func BlessingOfKingsAura(unit *Unit) *Aura { return makeExclusiveAllStatPercentBuff(unit, "Blessing of Kings", ActionID{SpellID: 20217}, 1.05) } @@ -226,7 +205,7 @@ func EmbraceOfTheShaleSpiderAura(u *Unit) *Aura { } /////////////////////////////////////////////////////////////////////////// -// Stamina +// Party Buffs /////////////////////////////////////////////////////////////////////////// // https://www.wowhead.com/mop-classic/spell=21562/power-word-fortitude @@ -260,7 +239,7 @@ func CommandingShoutAura(unit *Unit, asExternal bool) *Aura { } /////////////////////////////////////////////////////////////////////////// -// Attack Power +// Individual Buffs /////////////////////////////////////////////////////////////////////////// func TrueShotAura(unit *Unit) *Aura { @@ -427,14 +406,7 @@ func StillWaterAura(u *Unit) *Aura { {stats.PhysicalCritPercent, 5, false}, {stats.SpellCritPercent, 5, false}}}) } -func ArcaneBrilliance(u *Unit) *Aura { - // Mages: +10% Spell Power - return makeExclusiveBuff(u, BuffConfig{"Arcane Brilliance", ActionID{SpellID: 1459}, - []StatConfig{ - {stats.SpellDamage, 1.10, true}, - {stats.PhysicalCritPercent, 5, false}, - {stats.SpellCritPercent, 5, false}}}) -} + func BurningWrathAura(u *Unit) *Aura { return makeExclusiveBuff(u, BuffConfig{"Burning Wrath", ActionID{SpellID: 77747}, []StatConfig{{stats.SpellDamage, 1.10, true}}}) } diff --git a/sim/core/debuffs.go b/sim/core/debuffs.go index 548f0e7106..81ce71fa72 100644 --- a/sim/core/debuffs.go +++ b/sim/core/debuffs.go @@ -2,6 +2,7 @@ package core import ( "math/rand" + "strconv" "time" "github.com/wowsims/tbc/sim/core/proto" @@ -12,115 +13,113 @@ import ( func applyDebuffEffects(target *Unit, targetIdx int, debuffs *proto.Debuffs, raid *proto.Raid) { if debuffs.BloodFrenzy { - - MakePermanent(BloodFrenzy(target)) + MakePermanent(BloodFrenzyAura(target)) } if debuffs.CurseOfElements != proto.TristateEffect_TristateEffectMissing { - MakePermanent(CurseOfElements(target, IsImproved(debuffs.CurseOfElements))) + MakePermanent(CurseOfElementsAura(target, IsImproved(debuffs.CurseOfElements))) } if debuffs.CurseOfRecklessness { - MakePermanent(CurseOfRecklessness(target)) + MakePermanent(CurseOfRecklessnessAura(target)) } if debuffs.DemoralizingRoar != proto.TristateEffect_TristateEffectMissing { - MakePermanent(DemoralizingRoar(target, IsImproved(debuffs.DemoralizingRoar))) + MakePermanent(DemoralizingRoarAura(target, IsImproved(debuffs.DemoralizingRoar))) } if debuffs.DemoralizingShout != proto.TristateEffect_TristateEffectMissing { - MakePermanent(DemoralizingRoar(target, IsImproved(debuffs.DemoralizingRoar))) + MakePermanent(DemoralizingRoarAura(target, IsImproved(debuffs.DemoralizingRoar))) } if debuffs.ExposeArmor != proto.TristateEffect_TristateEffectMissing { - MakePermanent(ExposeArmor(target, IsImproved(debuffs.ExposeArmor))) + MakePermanent(ExposeArmorAura(target, IsImproved(debuffs.ExposeArmor))) } if debuffs.ExposeWeaknessUptime > 0.0 { - MakePermanent(ExposeWeakness(target, debuffs.ExposeWeaknessUptime, debuffs.ExposeWeaknessHunterAgility)) + MakePermanent(ExposeWeaknessAura(target, debuffs.ExposeWeaknessUptime, debuffs.ExposeWeaknessHunterAgility)) } if debuffs.FaerieFire != proto.TristateEffect_TristateEffectMissing { - MakePermanent(FaerieFire(target, IsImproved(debuffs.FaerieFire))) + MakePermanent(FaerieFireAura(target, IsImproved(debuffs.FaerieFire))) } if debuffs.HemorrhageUptime > 0.0 { - MakePermanent(Hemorrhage(target, debuffs.HemorrhageUptime)) + MakePermanent(HemorrhageAura(target, debuffs.HemorrhageUptime)) } if debuffs.GiftOfArthas { - MakePermanent(GiftOfArthas(target)) + MakePermanent(GiftOfArthasAura(target)) } if debuffs.HuntersMark != proto.TristateEffect_TristateEffectMissing { - MakePermanent(HuntersMark(target, IsImproved(debuffs.HuntersMark))) + MakePermanent(HuntersMarkAura(target, IsImproved(debuffs.HuntersMark))) } if debuffs.ImprovedScorch { - MakePermanent(ImprovedScorch(target)) + MakePermanent(ImprovedScorchAura(target)) } if debuffs.ImprovedSealOfTheCrusader { - MakePermanent(ImprovedSealOfTheCrusader(target)) + MakePermanent(ImprovedSealOfTheCrusaderAura(target)) } if debuffs.InsectSwarm { - MakePermanent((InsectSwarm(target))) + MakePermanent((InsectSwarmAura(target))) } if debuffs.IsbUptime > 0.0 { - println("ISB UPTIME") - MakePermanent(ImprovedShadowBolt(target, debuffs.IsbUptime)) + MakePermanent(ImprovedShadowBoltAura(target, debuffs.IsbUptime, 5)) } if debuffs.JudgementOfLight { - MakePermanent(JudgementOfLight(target)) + MakePermanent(JudgementOfLightAura(target)) } if debuffs.JudgementOfWisdom { - MakePermanent(JudgementOfWisdom(target)) + MakePermanent(JudgementOfWisdomAura(target)) } if debuffs.Mangle { - MakePermanent(Mangle(target)) + MakePermanent(MangleAura(target)) } if debuffs.Misery { - MakePermanent(Misery(target)) + MakePermanent(MiseryAura(target)) } if debuffs.ScorpidSting { - MakePermanent(ScorpidSting(target)) + MakePermanent(ScorpidStingAura(target)) } if debuffs.Screech { - MakePermanent(Screech(target)) + MakePermanent(ScreechAura(target)) } if debuffs.ShadowEmbrace { - MakePermanent(ShadowEmbrace(target)) + MakePermanent(ShadowEmbraceAura(target)) } if debuffs.ShadowWeaving { - MakePermanent(ShadowWeaving(target)) + MakePermanent(ShadowWeavingAura(target)) } if debuffs.SunderArmor { - MakePermanent(SunderArmor(target)) + MakePermanent(SunderArmorAura(target)) } if debuffs.WintersChill { - MakePermanent(WintersChill(target)) + MakePermanent(WintersChillAura(target)) } } // Physical anmd Armor Related Debuffs -func BloodFrenzy(target *Unit) *Aura { +func BloodFrenzyAura(target *Unit) *Aura { return damageTakenDebuff(target, "Blood Frenzy", 29859, []stats.SchoolIndex{stats.SchoolIndexPhysical}, 1.04, time.Second*21) } // Damage Taken Debuffs -func CurseOfElements(target *Unit, improved bool) *Aura { +func CurseOfElementsAura(target *Unit, improved bool) *Aura { multiplier := 1.10 if improved { multiplier += 0.03 @@ -138,11 +137,11 @@ func CurseOfElements(target *Unit, improved bool) *Aura { ) } -func CurseOfRecklessness(target *Unit) *Aura { +func CurseOfRecklessnessAura(target *Unit) *Aura { return statsDebuff(target, "Curse of Recklesness", 27226, stats.Stats{stats.Armor: -8000, stats.AttackPower: 135}) } -func DemoralizingRoar(target *Unit, improved bool) *Aura { +func DemoralizingRoarAura(target *Unit, improved bool) *Aura { apReduction := 248.0 if improved { apReduction *= 1.4 @@ -151,7 +150,7 @@ func DemoralizingRoar(target *Unit, improved bool) *Aura { return statsDebuff(target, "Demoralizing Roar", 26998, stats.Stats{stats.AttackPower: apReduction}) } -func DemoralizingShout(target *Unit, improved bool) *Aura { +func DemoralizingShoutAura(target *Unit, improved bool) *Aura { apReduction := 300.0 if improved { apReduction *= 1.4 @@ -160,7 +159,7 @@ func DemoralizingShout(target *Unit, improved bool) *Aura { return statsDebuff(target, "Demoralizing Shout", 25203, stats.Stats{stats.AttackPower: apReduction}) } -func ExposeArmor(target *Unit, improved bool) *Aura { +func ExposeArmorAura(target *Unit, improved bool) *Aura { eaValue := 2050.0 if improved { eaValue *= 1.50 @@ -168,7 +167,7 @@ func ExposeArmor(target *Unit, improved bool) *Aura { return statsDebuff(target, "Expose Armor", 26866, stats.Stats{stats.Armor: eaValue}) } -func ExposeWeakness(target *Unit, uptime float64, hunterAgility float64) *Aura { +func ExposeWeaknessAura(target *Unit, uptime float64, hunterAgility float64) *Aura { apBonus := hunterAgility * 0.25 * uptime return target.GetOrRegisterAura(Aura{ @@ -188,7 +187,7 @@ func ExposeWeakness(target *Unit, uptime float64, hunterAgility float64) *Aura { } -func FaerieFire(target *Unit, improved bool) *Aura { +func FaerieFireAura(target *Unit, improved bool) *Aura { return target.GetOrRegisterAura(Aura{ Label: "Faerie Fire", ActionID: ActionID{SpellID: 26993}, @@ -204,7 +203,7 @@ func FaerieFire(target *Unit, improved bool) *Aura { }) } -func GiftOfArthas(target *Unit) *Aura { +func GiftOfArthasAura(target *Unit) *Aura { return target.GetOrRegisterAura(Aura{ Label: "Gift of Arthas", ActionID: ActionID{SpellID: 11374}, @@ -218,7 +217,7 @@ func GiftOfArthas(target *Unit) *Aura { }) } -func Hemorrhage(target *Unit, uptime float64) *Aura { +func HemorrhageAura(target *Unit, uptime float64) *Aura { return target.GetOrRegisterAura(Aura{ Label: "Hemorrhage", ActionID: ActionID{SpellID: 33876}, @@ -232,7 +231,7 @@ func Hemorrhage(target *Unit, uptime float64) *Aura { }) } -func HuntersMark(target *Unit, improved bool) *Aura { +func HuntersMarkAura(target *Unit, improved bool) *Aura { maxBonus := 440.0 return target.GetOrRegisterAura(Aura{ @@ -264,11 +263,11 @@ func HuntersMark(target *Unit, improved bool) *Aura { }) } -func ImprovedScorch(target *Unit) *Aura { +func ImprovedScorchAura(target *Unit) *Aura { return damageTakenDebuff(target, "Improved Scorch", 12873, []stats.SchoolIndex{stats.SchoolIndexFire}, 1.15, time.Second*30) } -func ImprovedSealOfTheCrusader(target *Unit) *Aura { +func ImprovedSealOfTheCrusaderAura(target *Unit) *Aura { return target.GetOrRegisterAura(Aura{ Label: "Improved Seal of the Crusader", ActionID: ActionID{SpellID: 20337}, @@ -283,53 +282,50 @@ func ImprovedSealOfTheCrusader(target *Unit) *Aura { } -func ImprovedShadowBolt(target *Unit, uptime float64) *Aura { +func ImprovedShadowBoltAura(target *Unit, uptime float64, points int32) *Aura { + bonus := 0.04 * float64(points) + multiplier := 1 + bonus + if uptime != 0.0 { + multiplier = 1 + bonus*uptime + } - multiplier := 0.20 * uptime - return target.GetOrRegisterAura(Aura{ - Label: "ImprovedShadowBolt", + config := Aura{ + Label: "ImprovedShadowBolt-" + strconv.Itoa(int(points)), + Tag: "ImprovedShadowBolt", ActionID: ActionID{SpellID: 17803}, Duration: time.Second * 12, MaxStacks: 4, - OnGain: func(aura *Aura, sim *Simulation) { - for _, unit := range sim.AllUnits { - if unit.Type == PlayerUnit || unit.Type == PetUnit { - unit.AddStaticMod(SpellModConfig{ - Kind: SpellMod_DamageDone_Pct, - FloatValue: (0.20 * uptime), - School: SpellSchoolShadow, - ProcMask: ProcMaskSpellDamage, - ShouldApplyToPets: true, - }) - } - } + aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexShadow] *= multiplier }, OnExpire: func(aura *Aura, sim *Simulation) { - inverse := (1.0 / (1.0 + multiplier)) - 1.0 - for _, unit := range sim.AllUnits { - if unit.Type == PlayerUnit { - unit.AddStaticMod(SpellModConfig{ - Kind: SpellMod_DamageDone_Pct, - FloatValue: inverse, - School: SpellSchoolShadow, - ProcMask: ProcMaskSpellDamage, - ShouldApplyToPets: true, - }) - } - } + aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexShadow] /= multiplier }, - }) + } + + if uptime == 0 { + config.OnSpellHitTaken = func(aura *Aura, sim *Simulation, spell *Spell, result *SpellResult) { + if spell.SpellSchool != SpellSchoolShadow { + return + } + if !result.Landed() || result.Damage == 0 || !spell.ProcMask.Matches(ProcMaskSpellDamage) { + return + } + aura.RemoveStack(sim) + } + + } + return target.GetOrRegisterAura(config) } -func InsectSwarm(target *Unit) *Aura { +func InsectSwarmAura(target *Unit) *Aura { return statsDebuff(target, "Insect Swarm", 27013, stats.Stats{ stats.AllPhysHitRating: 0.98, stats.SpellHitPercent: 0.98, }) } -func JudgementOfLight(target *Unit) *Aura { +func JudgementOfLightAura(target *Unit) *Aura { actionId := ActionID{SpellID: 27163} return target.GetOrRegisterAura(Aura{ @@ -354,7 +350,7 @@ func JudgementOfLight(target *Unit) *Aura { }) } -func JudgementOfWisdom(target *Unit) *Aura { +func JudgementOfWisdomAura(target *Unit) *Aura { actionId := ActionID{SpellID: 27167} return target.GetOrRegisterAura(Aura{ @@ -386,7 +382,7 @@ func JudgementOfWisdom(target *Unit) *Aura { }) } -func Mangle(target *Unit) *Aura { +func MangleAura(target *Unit) *Aura { return target.GetOrRegisterAura(Aura{ Label: "Mangle", ActionID: ActionID{SpellID: 33876}, @@ -400,7 +396,7 @@ func Mangle(target *Unit) *Aura { }) } -func Misery(target *Unit) *Aura { +func MiseryAura(target *Unit) *Aura { return damageTakenDebuff(target, "Misery", 33195, []stats.SchoolIndex{ stats.SchoolIndexArcane, stats.SchoolIndexFire, @@ -411,23 +407,23 @@ func Misery(target *Unit) *Aura { }, 1.05, time.Minute*1) } -func ScorpidSting(target *Unit) *Aura { +func ScorpidStingAura(target *Unit) *Aura { return statsDebuff(target, "Scorpid Sting", 3043, stats.Stats{stats.AllPhysHitRating: -5.0}) } -func Screech(target *Unit) *Aura { +func ScreechAura(target *Unit) *Aura { return statsDebuff(target, "Screech", 27051, stats.Stats{stats.AttackPower: -210}) } -func ShadowEmbrace(target *Unit) *Aura { +func ShadowEmbraceAura(target *Unit) *Aura { return damageDealtDebuff(target, "Shadow Embrace", 32394, []stats.SchoolIndex{stats.SchoolIndexPhysical}, 0.95, NeverExpires) } -func ShadowWeaving(target *Unit) *Aura { +func ShadowWeavingAura(target *Unit) *Aura { return damageTakenDebuff(target, "Shadow Weaving", 15334, []stats.SchoolIndex{stats.SchoolIndexShadow}, 1.10, time.Second*15) } -func Stormstrike(target *Unit, uptime float64) *Aura { +func StormstrikeAura(target *Unit, uptime float64) *Aura { multiplier := 1.20 if uptime != 0 { multiplier *= uptime @@ -435,11 +431,11 @@ func Stormstrike(target *Unit, uptime float64) *Aura { return damageTakenDebuff(target, "Stormstrike", 17364, []stats.SchoolIndex{stats.SchoolIndexNature}, multiplier, time.Second*12) } -func SunderArmor(target *Unit) *Aura { +func SunderArmorAura(target *Unit) *Aura { return statsDebuff(target, "Sunder Amor", 25225, stats.Stats{stats.Armor: -2600}) } -func WintersChill(target *Unit) *Aura { +func WintersChillAura(target *Unit) *Aura { return target.GetOrRegisterAura(Aura{ Label: "Winter's Chill", ActionID: ActionID{SpellID: 28595}, diff --git a/sim/warlock/shadowbolt.go b/sim/warlock/shadowbolt.go index b547fda925..33d9d160f4 100644 --- a/sim/warlock/shadowbolt.go +++ b/sim/warlock/shadowbolt.go @@ -9,6 +9,7 @@ import ( const shadowBoltCoeff = 1.38 func (warlock *Warlock) registerShadowBolt() { + warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 686}, SpellSchool: core.SpellSchoolShadow, @@ -31,6 +32,18 @@ func (warlock *Warlock) registerShadowBolt() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { result := spell.CalcDamage(sim, target, 1000, spell.OutcomeMagicHitAndCrit) + existingAura := target.GetAurasWithTag("ImprovedShadowBolt") + + if len(existingAura) == 0 || existingAura[0].Duration != core.NeverExpires { + if result.Landed() && result.Outcome.Matches(core.OutcomeCrit) { + if !warlock.ImpShadowboltAura.IsActive() { + + warlock.ImpShadowboltAura.Activate(sim) + } + warlock.ImpShadowboltAura.SetStacks(sim, 4) + } + } + spell.WaitTravelTime(sim, func(sim *core.Simulation) { spell.DealDamage(sim, result) }) diff --git a/sim/warlock/talents.go b/sim/warlock/talents.go index eff79b3a3d..8f3224cf63 100644 --- a/sim/warlock/talents.go +++ b/sim/warlock/talents.go @@ -42,6 +42,7 @@ func (warlock *Warlock) applyDestructionTalents() { warlock.applyDevastation() warlock.applyImprovedFirebolt() warlock.applyImprovedLashOfPain() + warlock.applyImprovedShadowBolt() warlock.applyDestructiveReach() warlock.applyImprovedSearingPain() warlock.applyRuin() @@ -375,9 +376,15 @@ func (warlock *Warlock) applyDemonicTactics() { /* Destruction Skip for now: - - Improved shadowbolt - included in shadowbolt.go - - ImprovedImmolate - include in immolate.go + - Improved shadowbolt - included in shadowbolt.go + - ImprovedImmolate - include in immolate.go */ +func (warlock *Warlock) applyImprovedShadowBolt() { + if warlock.Talents.ImprovedShadowBolt == 0 { + return + } + warlock.ImpShadowboltAura = core.ImprovedShadowBoltAura(warlock.CurrentTarget, 0, warlock.Talents.ImprovedShadowBolt) +} func (warlock *Warlock) applyCataclysm() { if warlock.Talents.Cataclysm == 0 { From 14db76151241b5103bcf19ba9b076e8fe99f62ac Mon Sep 17 00:00:00 2001 From: jazz405 Date: Fri, 26 Dec 2025 22:19:53 -0500 Subject: [PATCH 17/20] buffs implemented --- proto/common.proto | 2 + sim/core/buffs.go | 1618 +++++++++++----------- sim/core/spell_result.go | 8 +- sim/core/stats/stats.go | 1 + sim/core/unit.go | 6 +- sim/paladin/holy/holy.go | 2 - sim/paladin/paladin.go | 8 + sim/priest/shadow/shadow.go | 2 +- ui/core/components/inputs/consumables.ts | 4 - ui/warlock/dps/presets.ts | 2 +- 10 files changed, 849 insertions(+), 804 deletions(-) diff --git a/proto/common.proto b/proto/common.proto index b3a3823d60..4ef6eb96f5 100644 --- a/proto/common.proto +++ b/proto/common.proto @@ -481,7 +481,9 @@ message PartyBuffs { Drums drums = 11; // Item Buffs + int32 atiesh_druid = 35; int32 atiesh_mage = 12; + int32 atiesh_priest = 36; int32 atiesh_warlock = 13; bool braided_eternium_chain = 14; bool eye_of_the_night = 15; diff --git a/sim/core/buffs.go b/sim/core/buffs.go index 67ef10d16d..3f232ec4f8 100644 --- a/sim/core/buffs.go +++ b/sim/core/buffs.go @@ -1,6 +1,7 @@ package core import ( + "slices" "time" googleProto "google.golang.org/protobuf/proto" @@ -23,98 +24,193 @@ type StatConfig struct { IsMultiplicative bool } -func makeExclusiveMultiplierBuff(aura *Aura, stat stats.Stat, value float64) { - dep := aura.Unit.NewDynamicMultiplyStat(stat, value) - aura.NewExclusiveEffect(stat.StatName()+"%Buff", false, ExclusiveEffect{ - Priority: value, - OnGain: func(ee *ExclusiveEffect, s *Simulation) { - ee.Aura.Unit.EnableBuildPhaseStatDep(s, dep) - }, - OnExpire: func(ee *ExclusiveEffect, s *Simulation) { - ee.Aura.Unit.DisableBuildPhaseStatDep(s, dep) - }, - }) -} - -func makeStatsBuff(unit *Unit, label string, spellID int32, duration time.Duration, stats stats.Stats) *Aura { - return unit.GetOrRegisterAura(Aura{ - Label: label + "Buff", - ActionID: ActionID{SpellID: spellID}, +func makeMultiplierBuff(char *Character, label string, spellId int32, duration time.Duration, stats []stats.Stat, amount float64) *Aura { + return char.GetOrRegisterAura(Aura{ + Label: label, + ActionID: ActionID{SpellID: spellId}, Duration: duration, OnGain: func(aura *Aura, sim *Simulation) { - aura.Unit.AddStatsDynamic(sim, stats) + for _, stat := range stats { + dep := aura.Unit.NewDynamicMultiplyStat(stat, amount) + aura.Unit.EnableBuildPhaseStatDep(sim, dep) + } }, - OnExpire: func(aura *Aura, sim *Simulation) { - aura.Unit.AddStatsDynamic(sim, stats.Multiply(-1)) + for _, stat := range stats { + dep := aura.Unit.NewDynamicMultiplyStat(stat, amount) + aura.Unit.DisableBuildPhaseStatDep(sim, dep) + } }, }) } -func registerExlusiveEffects(aura *Aura, config []StatConfig) { - for _, statConfig := range config { - if statConfig.IsMultiplicative { - makeExclusiveMultiplierBuff(aura, statConfig.Stat, statConfig.Amount) - } else { - makeExclusiveFlatStatBuff(aura, statConfig.Stat, statConfig.Amount) - } +// Applies buffs that affect individual players. +func applyBuffEffects(agent Agent, raidBuffs *proto.RaidBuffs, partyBuffs *proto.PartyBuffs, individual *proto.IndividualBuffs) { + char := agent.GetCharacter() + // u := &char.Unit + + // Raid Buffs + if raidBuffs.ArcaneBrilliance { + MakePermanent(ArcaneBrillianceAura(char)) } -} -func makeExclusiveAllStatPercentBuff(unit *Unit, label string, actionID ActionID, value float64) *Aura { - return makeExclusiveBuff(unit, BuffConfig{ - label, - actionID, - []StatConfig{ - {stats.Agility, value, true}, - {stats.Strength, value, true}, - {stats.Intellect, value, true}, - }}) -} + if raidBuffs.DivineSpirit != proto.TristateEffect_TristateEffectMissing { + MakePermanent(DivineSpiritAura(char, IsImproved(raidBuffs.DivineSpirit))) + } -func makeExclusiveBuff(unit *Unit, config BuffConfig) *Aura { - if config.Label == "" { - panic("Buff without label.") + if raidBuffs.GiftOfTheWild != proto.TristateEffect_TristateEffectMissing { + MakePermanent(GiftOfTheWildAura(char, IsImproved(raidBuffs.GiftOfTheWild))) } - if ActionID.IsEmptyAction(config.ActionID) { - panic("Buff without ActionID") + if raidBuffs.PowerWordFortitude != proto.TristateEffect_TristateEffectMissing { + MakePermanent(PowerWordFortitudeAura(char, IsImproved(raidBuffs.PowerWordFortitude))) } - baseAura := MakePermanent(unit.GetOrRegisterAura(Aura{ - Label: config.Label, - ActionID: config.ActionID, - BuildPhase: CharacterBuildPhaseBuffs, - })) + if raidBuffs.ShadowProtection { + MakePermanent(ShadowProtectionAura(char)) + } - registerExlusiveEffects(baseAura, config.Stats) - return baseAura -} + // Party Buffs + if partyBuffs.AtieshDruid > 0 { + MakePermanent(AtieshAura(char, proto.Class_ClassDruid.Enum(), float64(partyBuffs.AtieshDruid))) + } -// Applies buffs that affect individual players. -func applyBuffEffects(agent Agent, raidBuffs *proto.RaidBuffs, _ *proto.PartyBuffs, individual *proto.IndividualBuffs) { - char := agent.GetCharacter() - u := &char.Unit + if partyBuffs.AtieshMage > 0 { + MakePermanent(AtieshAura(char, proto.Class_ClassMage.Enum(), float64(partyBuffs.AtieshMage))) + } - if raidBuffs.ArcaneBrilliance { - MakePermanent(ArcaneBrilliance(u)) + if partyBuffs.AtieshPriest > 0 { + MakePermanent(AtieshAura(char, proto.Class_ClassPriest.Enum(), float64(partyBuffs.AtieshPriest))) } - if raidBuffs.DivineSpirit != proto.TristateEffect_TristateEffectMissing { - MakePermanent(DivineSpirit(u, IsImproved(raidBuffs.DivineSpirit))) + if partyBuffs.AtieshWarlock > 0 { + MakePermanent(AtieshAura(char, proto.Class_ClassWarlock.Enum(), float64(partyBuffs.AtieshMage))) } - if raidBuffs.GiftOfTheWild != proto.TristateEffect_TristateEffectMissing { - MakePermanent(GiftOfTheWild(u, IsImproved(raidBuffs.GiftOfTheWild))) + if partyBuffs.BattleShout != proto.TristateEffect_TristateEffectMissing { + MakePermanent(BattleShoutAura(char, IsImproved(partyBuffs.BattleShout), partyBuffs.BsSolarianSapphire)) } - if raidBuffs.PowerWordFortitude != proto.TristateEffect_TristateEffectMissing { - MakePermanent(PowerWordFortitude(u, IsImproved(raidBuffs.PowerWordFortitude))) + if partyBuffs.BloodPact != proto.TristateEffect_TristateEffectMissing { + MakePermanent(BloodPactAura(char, IsImproved(partyBuffs.BloodPact))) } - if raidBuffs.ShadowProtection { - MakePermanent(ShadowProtection(u)) + if partyBuffs.BraidedEterniumChain { + MakePermanent(BraidedEterniumChainAura(char)) + } + + if partyBuffs.ChainOfTheTwilightOwl { + MakePermanent(ChainOfTheTwilightOwlAura(char)) + } + + if partyBuffs.CommandingShout != proto.TristateEffect_TristateEffectMissing { + MakePermanent(CommandingShoutAura(char, IsImproved(partyBuffs.CommandingShout))) + } + + if partyBuffs.DevotionAura != proto.TristateEffect_TristateEffectMissing { + MakePermanent(DevotionAuraBuff(char, IsImproved(partyBuffs.DevotionAura))) + } + + if partyBuffs.DraeneiRacialCaster { + MakePermanent(DraneiRacialAura(char, true)) + } + + if partyBuffs.DraeneiRacialMelee { + MakePermanent(DraneiRacialAura(char, false)) + } + + if partyBuffs.EyeOfTheNight { + MakePermanent(EyeOfTheNightAura(char)) + } + + if partyBuffs.FerociousInspiration > 0 { + MakePermanent(FerociousInspiration(char, partyBuffs.FerociousInspiration)) + } + + if partyBuffs.GraceOfAirTotem != proto.TristateEffect_TristateEffectMissing { + MakePermanent(GraceOfAirTotemAura(char, IsImproved(partyBuffs.GraceOfAirTotem))) + } + + if partyBuffs.JadePendantOfBlasting { + MakePermanent(JadePendantOfBlastingAura(char)) + } + + if partyBuffs.LeaderOfThePack != proto.TristateEffect_TristateEffectMissing { + MakePermanent(LeaderOfThePackAura(char, IsImproved(partyBuffs.LeaderOfThePack))) + } + + if partyBuffs.MoonkinAura != proto.TristateEffect_TristateEffectMissing { + MakePermanent(MoonkinAuraBuff(char, IsImproved(partyBuffs.MoonkinAura))) + } + + if partyBuffs.RetributionAura != proto.TristateEffect_TristateEffectMissing { + MakePermanent(RetributionAuraBuff(char, IsImproved(partyBuffs.RetributionAura), 5)) + } + + if partyBuffs.SanctityAura != proto.TristateEffect_TristateEffectMissing { + MakePermanent(SanctityAuraBuff(char, IsImproved(partyBuffs.SanctityAura))) + } + + if partyBuffs.StrengthOfEarthTotem != proto.StrengthOfEarthType_None { + MakePermanent(StrengthOfEarthTotemAura(char, partyBuffs.StrengthOfEarthTotem.Enum())) + } + + if partyBuffs.TotemOfWrath > 0 { + MakePermanent(TotemOfWrathAura(char, partyBuffs.TotemOfWrath)) + } + + if partyBuffs.TranquilAirTotem { + MakePermanent(TranquilAirTotemAura(char)) + } + + if partyBuffs.TrueshotAura { + MakePermanent(TrueShotAuraBuff(char)) + } + + if partyBuffs.WindfuryTotemRank > 0 && char.AutoAttacks.anyEnabled() { + MakePermanent(WindfuryTotemAura(char, partyBuffs.WindfuryTotemIwt)) + } + + if partyBuffs.WrathOfAirTotem != proto.TristateEffect_TristateEffectMissing { + MakePermanent(WrathOfAirTotemAura(char, IsImproved(partyBuffs.WrathOfAirTotem))) + } + + // Individual Buffs + if individual.BlessingOfKings { + MakePermanent(BlessingOfKingsAura(char)) + } + + if individual.BlessingOfMight != proto.TristateEffect_TristateEffectMissing { + MakePermanent(BlessingOfMightAura(char, IsImproved(individual.BlessingOfMight))) + } + + if individual.BlessingOfSalvation { + MakePermanent(BlessingOfSalvationAura(char)) + } + + if individual.BlessingOfSanctuary { + MakePermanent(BlessingOfSanctuaryAura(char)) + } + + if individual.BlessingOfWisdom != proto.TristateEffect_TristateEffectMissing { + MakePermanent(BlessingOfWisdomAura(char, IsImproved(individual.BlessingOfWisdom))) + } + + if individual.Innervates > 0 { + registerInnervateCD(char, individual.Innervates) + } + + if individual.PowerInfusions > 0 { + registerPowerInfusionCD(char, individual.PowerInfusions) + } + + if individual.ShadowPriestDps > 0 { + MakePermanent(ShadowPriestDPSManaAura(char, float64(individual.ShadowPriestDps))) + } + + if individual.UnleashedRage { + MakePermanent(UnleashedRageAura(char)) } } @@ -123,41 +219,41 @@ func applyBuffEffects(agent Agent, raidBuffs *proto.RaidBuffs, _ *proto.PartyBuf // Raid Buffs /////////////////////////////////////////////////////////////////////////// -func ArcaneBrilliance(unit *Unit) *Aura { - // Mages: +10% Spell Power - return makeStatsBuff(unit, "Arcane Brilliance", 27127, time.Hour*1, stats.Stats{stats.Intellect: 40}) +func ArcaneBrillianceAura(char *Character) *Aura { + + return char.NewTemporaryStatsAura("Arcane Brilliance", ActionID{SpellID: 27127}, stats.Stats{stats.Intellect: 40}, time.Hour*1).Aura } -func DivineSpirit(unit *Unit, improved bool) *Aura { +func DivineSpiritAura(char *Character, improved bool) *Aura { spiritBuff := stats.Stats{stats.Spirit: 50} - dsSDStatDep := unit.NewDynamicStatDependency(stats.Spirit, stats.SpellDamage, 10) - dsHPStatDep := unit.NewDynamicStatDependency(stats.Spirit, stats.HealingPower, 10) + dsSDStatDep := char.NewDynamicStatDependency(stats.Spirit, stats.SpellDamage, 10) + dsHPStatDep := char.NewDynamicStatDependency(stats.Spirit, stats.HealingPower, 10) - return unit.GetOrRegisterAura(Aura{ + return char.GetOrRegisterAura(Aura{ Label: "Divine Spirit Buff", ActionID: ActionID{SpellID: 25312}, Duration: time.Minute * 30, OnGain: func(aura *Aura, sim *Simulation) { - unit.AddStatsDynamic(sim, spiritBuff) + char.AddStatsDynamic(sim, spiritBuff) if improved { - unit.EnableBuildPhaseStatDep(sim, dsSDStatDep) - unit.EnableBuildPhaseStatDep(sim, dsHPStatDep) + char.EnableBuildPhaseStatDep(sim, dsSDStatDep) + char.EnableBuildPhaseStatDep(sim, dsHPStatDep) } }, OnExpire: func(aura *Aura, sim *Simulation) { - unit.AddStatsDynamic(sim, spiritBuff.Invert()) + char.AddStatsDynamic(sim, spiritBuff.Invert()) if improved { - unit.DisableBuildPhaseStatDep(sim, dsSDStatDep) - unit.DisableBuildPhaseStatDep(sim, dsHPStatDep) + char.DisableBuildPhaseStatDep(sim, dsSDStatDep) + char.DisableBuildPhaseStatDep(sim, dsHPStatDep) } }, }) } -func GiftOfTheWild(unit *Unit, improved bool) *Aura { +func GiftOfTheWildAura(char *Character, improved bool) *Aura { mod := 1.0 if improved { mod = 1.35 @@ -176,248 +272,696 @@ func GiftOfTheWild(unit *Unit, improved bool) *Aura { stats.ShadowResistance: 25 * mod, } - return makeStatsBuff(unit, "Gift of the Wild", 26991, time.Hour*1, gotwStats) + return char.NewTemporaryStatsAura("Gift of the Wild", ActionID{SpellID: 26991}, gotwStats, time.Hour*1).Aura } -func PowerWordFortitude(unit *Unit, improved bool) *Aura { +func PowerWordFortitudeAura(char *Character, improved bool) *Aura { mod := 1.0 if improved { mod = 1.3 } - return makeStatsBuff(unit, "Power Word: Fortitude", 25389, time.Hour*1, stats.Stats{stats.Stamina: 79.0 * mod}) + return char.NewTemporaryStatsAura("Power Word: Fortitude", ActionID{SpellID: 25389}, stats.Stats{stats.Stamina: 79.0 * mod}, time.Hour*1).Aura } -func ShadowProtection(unit *Unit) *Aura { - return makeStatsBuff(unit, "Shadow Protection", 10958, time.Minute*10, stats.Stats{stats.ShadowResistance: 60}) +func ShadowProtectionAura(char *Character) *Aura { + return char.NewTemporaryStatsAura("Shadow Protection", ActionID{SpellID: 10958}, stats.Stats{stats.ShadowResistance: 60}, time.Minute*10).Aura } -func BlessingOfKingsAura(unit *Unit) *Aura { - return makeExclusiveAllStatPercentBuff(unit, "Blessing of Kings", ActionID{SpellID: 20217}, 1.05) +/////////////////////////////////////////////////////////////////////////// +// Party Buffs +/////////////////////////////////////////////////////////////////////////// + +func BattleShoutAura(char *Character, improved bool, sapphire bool) *Aura { + apBuff := 306.0 + if improved { + apBuff *= 1.25 + } + + if sapphire { + apBuff += 70 + } + return char.NewTemporaryStatsAura("Battle Shout", ActionID{SpellID: 2048}, stats.Stats{stats.AttackPower: apBuff}, time.Minute*2).Aura } -func MarkOfTheWildAura(unit *Unit) *Aura { - aura := makeExclusiveAllStatPercentBuff(unit, "Mark of the Wild", ActionID{SpellID: 1126}, 1.05) - return aura +func BloodPactAura(char *Character, improved bool) *Aura { + stamBuff := 70.0 + if improved { + stamBuff *= 1.3 + } + return char.NewTemporaryStatsAura("Blood Pact", ActionID{SpellID: 27268}, stats.Stats{stats.Stamina: stamBuff}, NeverExpires).Aura } -func EmbraceOfTheShaleSpiderAura(u *Unit) *Aura { - return makeExclusiveAllStatPercentBuff(u, "Embrace of the Shale Spider", ActionID{SpellID: 90363}, 1.05) +func CommandingShoutAura(char *Character, improved bool) *Aura { + hpBuff := 1080.0 + if improved { + hpBuff *= 1.25 + } + return char.NewTemporaryStatsAura("Battle Shout", ActionID{SpellID: 469}, stats.Stats{stats.Health: hpBuff}, time.Minute*2).Aura } -/////////////////////////////////////////////////////////////////////////// -// Party Buffs -/////////////////////////////////////////////////////////////////////////// +func DevotionAuraBuff(char *Character, improved bool) *Aura { + armorBuff := 861.0 + if improved { + armorBuff *= 1.40 + } -// https://www.wowhead.com/mop-classic/spell=21562/power-word-fortitude -func PowerWordFortitudeAura(unit *Unit) *Aura { - return makeExclusiveBuff(unit, BuffConfig{ - "Power Word: Fortitude", - ActionID{SpellID: 21562}, - []StatConfig{ - {stats.Stamina, 1.1, true}, + return char.NewTemporaryStatsAura("Devotion Aura", ActionID{SpellID: 27149}, stats.Stats{stats.Armor: armorBuff}, NeverExpires).Aura +} + +func FerociousInspiration(char *Character, count int32) *Aura { + dmgBuff := 0.03 * float64(count) + + return char.GetOrRegisterAura(Aura{ + Label: "Ferocious Inspiration", + ActionID: ActionID{SpellID: 34460}, + Duration: time.Second * 10, + + OnGain: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.DamageDealtMultiplier *= 1 + dmgBuff + }, + OnExpire: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.DamageDealtMultiplier /= 1 + dmgBuff }, }) } -func QirajiFortitudeAura(u *Unit) *Aura { - return makeExclusiveBuff(u, BuffConfig{"Qiraji Fortitude", ActionID{SpellID: 90364}, []StatConfig{{stats.Stamina, 1.1, true}}}) +func LeaderOfThePackAura(char *Character, improved bool) *Aura { + packBuff := stats.Stats{ + stats.MeleeCritRating: 5 * PhysicalCritRatingPerCritPercent, + stats.RangedCritPercent: 5, + } + if improved { + packBuff.Add(stats.Stats{stats.AllPhysCritRating: 22}) + } + return char.NewTemporaryStatsAura("Leader of the Pack", ActionID{SpellID: 17007}, packBuff, NeverExpires).Aura } -func CommandingShoutAura(unit *Unit, asExternal bool) *Aura { - baseAura := makeExclusiveBuff(unit, BuffConfig{ - "Commanding Shout", - ActionID{SpellID: 469}, - []StatConfig{ - {stats.Stamina, 1.1, true}, - }}) - if asExternal { - return baseAura - } - - baseAura.OnReset = nil - baseAura.Duration = time.Minute * 5 - return baseAura + +func MoonkinAuraBuff(char *Character, improved bool) *Aura { + auraBuff := stats.Stats{stats.SpellCritPercent: 5} + if improved { + auraBuff.Add(stats.Stats{stats.SpellCritRating: 20}) + } + return char.NewTemporaryStatsAura("Moonkin Aura", ActionID{SpellID: 24907}, auraBuff, NeverExpires).Aura } -/////////////////////////////////////////////////////////////////////////// -// Individual Buffs -/////////////////////////////////////////////////////////////////////////// +func RetributionAuraBuff(char *Character, improved bool, points int32) *Aura { + actionID := ActionID{SpellID: 27150} + + procSpell := char.RegisterSpell(SpellConfig{ + ActionID: actionID, + SpellSchool: SpellSchoolHoly, + Flags: SpellFlagBinary, + + ApplyEffects: func(sim *Simulation, target *Unit, spell *Spell) { + baseDamage := 26 * (1 + 0.25*float64(points)) + if improved { + baseDamage *= 1.50 + } + result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeAlwaysHit) + spell.DealDamage(sim, result) + }, + }) -func TrueShotAura(unit *Unit) *Aura { - return makeExclusiveBuff(unit, BuffConfig{ - "Trueshot Aura", - ActionID{SpellID: 19506}, - []StatConfig{ - {stats.AttackPower, 1.1, true}, - {stats.RangedAttackPower, 1.1, true}, - }}) + return char.RegisterAura(Aura{ + Label: "Retribution Aura", + ActionID: actionID, + Duration: NeverExpires, + OnReset: func(aura *Aura, sim *Simulation) { + aura.Activate(sim) + }, + OnSpellHitTaken: func(aura *Aura, sim *Simulation, spell *Spell, result *SpellResult) { + if result.Landed() && spell.SpellSchool == SpellSchoolPhysical { + procSpell.Cast(sim, spell.Unit) + } + }, + }) } -func BattleShoutAura(unit *Unit, asExternal bool) *Aura { - baseAura := makeExclusiveBuff(unit, BuffConfig{ - "Battle Shout", - ActionID{SpellID: 6673}, - []StatConfig{ - {stats.AttackPower, 1.1, true}, - {stats.RangedAttackPower, 1.1, true}, - }}) +func SanctityAuraBuff(char *Character, improved bool) *Aura { + return char.GetOrRegisterAura(Aura{ + Label: "Sanctity Aura Buff", + ActionID: ActionID{SpellID: 20218}, + Duration: NeverExpires, - if asExternal { - return baseAura - } + OnGain: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexHoly] *= 1.10 + if improved { + aura.Unit.PseudoStats.DamageDealtMultiplier *= 1.02 + } + }, + + OnExpire: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexHoly] /= 1.10 + if improved { + aura.Unit.PseudoStats.DamageDealtMultiplier /= 1.02 + } + }, + }) + +} + +func TrueShotAuraBuff(char *Character) *Aura { + return char.NewTemporaryStatsAura("Trueshot Aura", ActionID{SpellID: 27066}, stats.Stats{stats.RangedAttackPower: 125}, NeverExpires).Aura +} - baseAura.OnReset = nil - baseAura.Duration = time.Minute * 5 - baseAura.BuildPhase = CharacterBuildPhaseNone - return baseAura +func UnleashedRageAura(char *Character) *Aura { + return makeMultiplierBuff(char, "Unleashed Rage", 30811, time.Second*10, []stats.Stat{stats.AttackPower: 10.0}, 1.1) } -// ///////////////////////////////////////////////////////////////////////// +// ////////////////////////// // -// Melee Haste +// Totems // -// ///////////////////////////////////////////////////////////////////////// -func registerExclusiveMeleeHaste(aura *Aura, value float64) { - aura.NewExclusiveEffect("AttackSpeed%", false, ExclusiveEffect{ - OnGain: func(ee *ExclusiveEffect, s *Simulation) { - ee.Aura.Unit.MultiplyMeleeSpeed(s, value) - ee.Aura.Unit.MultiplyRangedSpeed(s, value) +// ////////////////////////// +func GraceOfAirTotemAura(char *Character, improved bool) *Aura { + agiBuff := 77.0 + if improved { + agiBuff *= 1.15 + } + return char.NewTemporaryStatsAura("Grace of Air Totem", ActionID{SpellID: 25359}, stats.Stats{stats.Agility: agiBuff}, time.Minute*2).Aura +} + +func StrengthOfEarthTotemAura(char *Character, totem *proto.StrengthOfEarthType) *Aura { + strBuff := 86.0 + + switch totem { + case proto.StrengthOfEarthType_CycloneBonus.Enum(): + strBuff = 98 + case proto.StrengthOfEarthType_EnhancingTotems.Enum(): + strBuff = 98 + case proto.StrengthOfEarthType_EnhancingAndCyclone.Enum(): + strBuff = 112 + } + return char.NewTemporaryStatsAura("Strength of Earth Totem", ActionID{SpellID: 25528}, stats.Stats{stats.Strength: strBuff}, time.Minute*2).Aura +} + +func TotemOfWrathAura(char *Character, count int32) *Aura { + modValue := 3.0 * float64(count) + return char.NewTemporaryStatsAura("Totem of Wrath", ActionID{SpellID: 30706}, stats.Stats{ + stats.SpellCritPercent: modValue, + stats.SpellHitPercent: modValue, + }, time.Minute*2).Aura +} + +func TranquilAirTotemAura(char *Character) *Aura { + return char.GetOrRegisterAura(Aura{ + Label: "Tranquil Air Totem", + ActionID: ActionID{SpellID: 25909}, + Duration: time.Minute * 2, + OnGain: func(aura *Aura, sim *Simulation) { + char.PseudoStats.ThreatMultiplier *= 0.80 }, - OnExpire: func(ee *ExclusiveEffect, s *Simulation) { - ee.Aura.Unit.MultiplyMeleeSpeed(s, 1/value) - ee.Aura.Unit.MultiplyRangedSpeed(s, 1/value) + OnExpire: func(aura *Aura, sim *Simulation) { + char.PseudoStats.ThreatMultiplier /= 0.80 }, }) } -func UnholyAura(u *Unit) *Aura { - aura := makeExclusiveBuff(u, BuffConfig{"Unholy Aura", ActionID{SpellID: 55610}, nil}) - registerExclusiveMeleeHaste(aura, 1.10) - return aura + +func WindfuryTotemAura(char *Character, iwtTalentPoints int32) *Aura { + buffActionID := ActionID{SpellID: 25587} + apBonus := 445.0 + apBonus *= 1 + 0.15*float64(iwtTalentPoints) + + var charges int32 + icd := Cooldown{ + Timer: char.NewTimer(), + Duration: 1, + } + + wfBuffAura := char.NewTemporaryStatsAuraWrapped("Windfury Buff", buffActionID, stats.Stats{stats.AttackPower: apBonus}, time.Millisecond*1500, func(config *Aura) { + config.OnSpellHitDealt = func(aura *Aura, sim *Simulation, spell *Spell, result *SpellResult) { + // *Special Case* Windfury should not proc on Seal of Command + if spell.ActionID.SpellID == 20424 { + return + } + if !spell.ProcMask.Matches(ProcMaskMeleeWhiteHit) || spell.ProcMask.Matches(ProcMaskMeleeSpecial) { + return + } + charges-- + if charges == 0 { + aura.Deactivate(sim) + } + } + }) + const procChance = 0.2 + var wfSpell *Spell + + return char.GetOrRegisterAura(Aura{ + Label: "Windfury Totem", + ActionID: ActionID{SpellID: 25587}, + OnInit: func(aura *Aura, sim *Simulation) { + wfSpell = char.GetOrRegisterSpell(SpellConfig{ + ActionID: buffActionID, // temporary buff ("Windfury Attack") spell id + SpellSchool: SpellSchoolPhysical, + Flags: SpellFlagMeleeMetrics | SpellFlagNoOnCastComplete, + + ApplyEffects: func(sim *Simulation, target *Unit, spell *Spell) { + wfSwing := char.AutoAttacks.MHAuto() + wfSwing.BonusSpellDamage = 445 + wfSwing.Cast(sim, target) + }, + }) + }, + OnReset: func(aura *Aura, sim *Simulation) { + aura.Activate(sim) + }, + OnSpellHitDealt: func(aura *Aura, sim *Simulation, spell *Spell, result *SpellResult) { + // *Special Case* Windfury should not proc on Seal of Command + if spell.ActionID.SpellID == 20424 { + return + } + if !result.Landed() || !spell.ProcMask.Matches(ProcMaskMeleeMHAuto) { + return + } + + if wfBuffAura.IsActive() { + return + } + if !icd.IsReady(sim) { + // Checking for WF buff aura isn't quite enough now that we refactored auras. + // TODO: Clean this up to remove the need for an instant ICD. + return + } + + if sim.RandomFloat("Windfury Totem") > procChance { + return + } + + // TODO: the current proc system adds auras after cast and damage, in game they're added after cast + startCharges := int32(2) + if !spell.ProcMask.Matches(ProcMaskMeleeMHSpecial) { + startCharges-- + } + charges = startCharges + wfBuffAura.Activate(sim) + icd.Use(sim) + + aura.Unit.AutoAttacks.MaybeReplaceMHSwing(sim, wfSpell).Cast(sim, result.Target) + }, + }) } -func CacklingHowlAura(u *Unit) *Aura { - aura := makeExclusiveBuff(u, BuffConfig{"Cackling Howl", ActionID{SpellID: 128432}, nil}) - registerExclusiveMeleeHaste(aura, 1.10) - return aura + +func WrathOfAirTotemAura(char *Character, improved bool) *Aura { + buff := 101.0 + if improved { + buff += 20.0 + } + return char.NewTemporaryStatsAura("Wrath of Air Totem", ActionID{SpellID: 3738}, stats.Stats{ + stats.SpellDamage: buff, + stats.HealingPower: buff, + }, time.Minute*2).Aura } -func SerpentsSwiftnessAura(u *Unit) *Aura { - aura := makeExclusiveBuff(u, BuffConfig{"Serpent's Swiftness", ActionID{SpellID: 128433}, nil}) - registerExclusiveMeleeHaste(aura, 1.10) - return aura + +//////////////////////////// +// Item Buffs +//////////////////////////// + +func AtieshAura(char *Character, class *proto.Class, numStaves float64) *Aura { + switch class { + case proto.Class_ClassDruid.Enum(): + return char.NewTemporaryStatsAura("Power of the Guardian - Druid", ActionID{SpellID: 28145}, stats.Stats{stats.MP5: 11 * numStaves}, NeverExpires).Aura + case proto.Class_ClassMage.Enum(): + return char.NewTemporaryStatsAura("Power of the Guardian - Mage", ActionID{SpellID: 28142}, stats.Stats{stats.SpellCritRating: 28 * numStaves}, NeverExpires).Aura + case proto.Class_ClassPriest.Enum(): + return char.NewTemporaryStatsAura("Power of the Guardian - Priest", ActionID{SpellID: 28144}, stats.Stats{stats.HealingPower: 62 * numStaves}, NeverExpires).Aura + default: + // Use warlock as default to satisfy compiler + return char.NewTemporaryStatsAura("Power of the Guardian - Warlock", ActionID{SpellID: 28143}, stats.Stats{ + stats.SpellDamage: 33 * numStaves, + stats.HealingPower: 33 * numStaves, + }, NeverExpires).Aura + } + } -func SwiftbladesCunningAura(u *Unit) *Aura { - aura := makeExclusiveBuff(u, BuffConfig{"Swiftblade's Cunning", ActionID{SpellID: 113742}, nil}) - registerExclusiveMeleeHaste(aura, 1.10) - return aura + +func BraidedEterniumChainAura(char *Character) *Aura { + return char.NewTemporaryStatsAura("Braided Eternium Chain", ActionID{SpellID: 31025}, stats.Stats{stats.AllPhysCritRating: 28}, time.Minute*30).Aura } -func UnleashedRageAura(u *Unit) *Aura { - aura := makeExclusiveBuff(u, BuffConfig{"Unleashed Rage", ActionID{SpellID: 30809}, nil}) - registerExclusiveMeleeHaste(aura, 1.10) - return aura + +func ChainOfTheTwilightOwlAura(char *Character) *Aura { + return char.NewTemporaryStatsAura("Chain of the Twlight Owl", ActionID{SpellID: 31035}, stats.Stats{stats.SpellCritPercent: 2}, time.Minute*30).Aura +} + +func DraneiRacialAura(char *Character, caster bool) *Aura { + alliance := []proto.Race{ + proto.Race_RaceDraenei, + proto.Race_RaceDwarf, + proto.Race_RaceGnome, + proto.Race_RaceHuman, + proto.Race_RaceNightElf, + } + if !slices.Contains(alliance, char.Race) { + return nil + } + + if caster { + return char.NewTemporaryStatsAura("Inspiring Presence", ActionID{SpellID: 6562}, stats.Stats{stats.SpellHitPercent: 1}, NeverExpires).Aura + } else { + return char.NewTemporaryStatsAura("Heroic Presence", ActionID{SpellID: 28878}, stats.Stats{stats.SpellHitPercent: 1}, NeverExpires).Aura + } +} + +func EyeOfTheNightAura(char *Character) *Aura { + return char.NewTemporaryStatsAura("Eye of the Night", ActionID{SpellID: 31033}, stats.Stats{stats.SpellDamage: 33}, time.Minute*30).Aura +} + +func JadePendantOfBlastingAura(char *Character) *Aura { + return char.NewTemporaryStatsAura("Jade Pendant of Blasting", ActionID{SpellID: 25607}, stats.Stats{stats.SpellDamage: 15}, time.Minute*30).Aura +} + +/////////////////////////////////////////////////////////////////////////// +// Individual Buffs +/////////////////////////////////////////////////////////////////////////// + +func AmplifyMagicAura(char *Character, improved bool) *Aura { + baseMod := 120.0 + if improved { + baseMod *= 1.50 + } + return char.GetOrRegisterAura(Aura{ + Label: "Amplify Magic", + ActionID: ActionID{SpellID: 33946}, + Duration: time.Minute * 10, + + OnGain: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.BonusHealingTaken += baseMod * 2 + aura.Unit.PseudoStats.BonusPhysicalDamageTaken += baseMod + }, + + OnExpire: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.BonusHealingTaken -= baseMod * 2 + aura.Unit.PseudoStats.BonusPhysicalDamageTaken -= baseMod + }, + }) +} + +func DampenMagicAura(char *Character, improved bool) *Aura { + baseMod := 120.0 + if improved { + baseMod *= 1.50 + } + return char.GetOrRegisterAura(Aura{ + Label: "Amplify Magic", + ActionID: ActionID{SpellID: 33946}, + Duration: time.Minute * 10, + + OnGain: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.BonusHealingTaken -= baseMod * 2 + aura.Unit.PseudoStats.BonusSpellDamageTaken -= baseMod + }, + + OnExpire: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.BonusHealingTaken += baseMod * 2 + aura.Unit.PseudoStats.BonusSpellDamageTaken += baseMod + }, + }) } -// ///////////////////////////////////////////////////////////////////////// +// ////////////////////////// // -// +Crit % +// Blessings // -// ///////////////////////////////////////////////////////////////////////// - -func LeaderOfThePack(unit *Unit) *Aura { - baseAura := makeExclusiveBuff(unit, BuffConfig{ - "Leader Of The Pack", - ActionID{SpellID: 17007}, - []StatConfig{ - {stats.PhysicalCritPercent, 5, false}, - {stats.SpellCritPercent, 5, false}, - }}) - - return baseAura +// ////////////////////////// +func BlessingOfKingsAura(char *Character) *Aura { + + bokStats := []stats.Stat{ + stats.Agility, + stats.Strength, + stats.Stamina, + stats.Intellect, + stats.Spirit, + } + + return makeMultiplierBuff(char, "Blessing of Kings", 20217, time.Hour*1, bokStats, 1.1) +} + +func BlessingOfLight(char *Character) *Aura { + return char.GetOrRegisterAura(Aura{ + Label: "Blessing of Light", + ActionID: ActionID{SpellID: 27145}, + Duration: time.Minute * 30, + + OnApplyEffects: func(aura *Aura, sim *Simulation, target *Unit, spell *Spell) { + if spell.ProcMask != ProcMaskSpellHealing { + return + } + + if spell.Unit.ownerClass != proto.Class_ClassPaladin { + return + } + + // Keep an eye on if this changes in paladin.go + // FlashOfLight = 2 + // HolyLight = 3 + if spell.ClassSpellMask != 2 || spell.ClassSpellMask != 3 { + return + } + + if spell.ClassSpellMask == 2 { + spell.BonusSpellDamage += 185 + } else { + spell.BonusSpellDamage += 580 + } + }, + }) } -func TerrifyingRoar(unit *Unit) *Aura { - baseAura := makeExclusiveBuff(unit, BuffConfig{ - "Terrifying Roar", - ActionID{SpellID: 90309}, - []StatConfig{ - {stats.PhysicalCritPercent, 5, false}, - {stats.SpellCritPercent, 5, false}, - }}) +func BlessingOfMightAura(char *Character, improved bool) *Aura { + apBuff := 220.0 + if improved { + apBuff *= 1.2 + } + return char.NewTemporaryStatsAura("Blessing Of Might", ActionID{SpellID: 27141}, stats.Stats{ + stats.AttackPower: apBuff, + stats.RangedAttackPower: apBuff, + }, time.Minute*30).Aura +} + +func BlessingOfSalvationAura(char *Character) *Aura { + return char.GetOrRegisterAura(Aura{ + Label: "Blessing Of Salvation", + ActionID: ActionID{SpellID: 25895}, + Duration: time.Minute * 30, + + OnGain: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.ThreatMultiplier *= 0.7 + }, + OnExpire: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.ThreatMultiplier /= 0.7 + }, + }) +} + +func BlessingOfSanctuaryAura(char *Character) *Aura { + actionID := ActionID{SpellID: 27169} + + procSpell := char.RegisterSpell(SpellConfig{ + ActionID: actionID, + SpellSchool: SpellSchoolHoly, + Flags: SpellFlagBinary, + + // ApplyEffects: ApplyEffectFuncDirectDamage(SpellEffect{ + // ProcMask: ProcMaskEmpty, + // DamageMultiplier: 1, + // ThreatMultiplier: 1, + + // BaseDamage: BaseDamageConfigFlat(46), + // OutcomeApplier: character.OutcomeFuncMagicHitBinary(), + // }), + ApplyEffects: func(sim *Simulation, target *Unit, spell *Spell) { + spell.CalcAndDealDamage(sim, target, 46, spell.OutcomeAlwaysHit) + }, + }) + + return char.RegisterAura(Aura{ + Label: "Blessing of Sanctuary", + ActionID: actionID, + Duration: NeverExpires, + OnReset: func(aura *Aura, sim *Simulation) { + aura.Activate(sim) + }, + OnGain: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.BonusPhysicalDamageTaken -= 80 + }, + OnExpire: func(aura *Aura, sim *Simulation) { + aura.Unit.PseudoStats.BonusPhysicalDamageTaken += 80 + }, + OnSpellHitTaken: func(aura *Aura, sim *Simulation, spell *Spell, result *SpellResult) { + if result.Outcome.Matches(OutcomeBlock) { + procSpell.Cast(sim, spell.Unit) + } + }, + }) +} + +func BlessingOfWisdomAura(char *Character, improved bool) *Aura { + mp5Buff := 41.0 + if improved { + mp5Buff *= 1.20 + } + return char.NewTemporaryStatsAura("Blessing of Wisdom", ActionID{SpellID: 25894}, stats.Stats{stats.MP5: mp5Buff}, time.Minute*30).Aura +} + +//////////////////////////// +// Individual Buffs +//////////////////////////// + +func ShadowPriestDPSManaAura(char *Character, dps float64) *Aura { + return char.NewTemporaryStatsAura("Vampiric Touch", ActionID{SpellID: 34914}, stats.Stats{stats.MP5: dps * 0.25}, time.Second*15).Aura +} + +//////////////////////////// +// Cooldowns +//////////////////////////// + +var PowerInfusionAuraTag = "PowerInfusion" + +const PowerInfusionDuration = time.Second * 15 +const PowerInfusionCD = time.Minute * 3 + +func registerPowerInfusionCD(char *Character, numPowerInfusions int32) { + if numPowerInfusions == 0 { + return + } + + piAura := PowerInfusionAura(char, -1) + + registerExternalConsecutiveCDApproximation( + char, + externalConsecutiveCDApproximation{ + ActionID: ActionID{SpellID: 10060, Tag: -1}, + AuraTag: PowerInfusionAuraTag, + CooldownPriority: CooldownPriorityDefault, + AuraDuration: PowerInfusionDuration, + AuraCD: PowerInfusionCD, + Type: CooldownTypeDPS, + + ShouldActivate: func(sim *Simulation, character *Character) bool { + // Haste portion doesn't stack with Bloodlust, so prefer to wait. + return !character.HasActiveAuraWithTag(BloodlustAuraTag) + }, + AddAura: func(sim *Simulation, character *Character) { piAura.Activate(sim) }, + }, + numPowerInfusions) +} + +func PowerInfusionAura(char *Character, actionTag int32) *Aura { + actionID := ActionID{SpellID: 10060, Tag: actionTag} + + return char.GetOrRegisterAura(Aura{ + Label: "PowerInfusion-" + actionID.String(), + Tag: PowerInfusionAuraTag, + ActionID: actionID, + Duration: PowerInfusionDuration, + OnGain: func(aura *Aura, sim *Simulation) { + if char.HasManaBar() { + // TODO: Double-check this is how the calculation works. + char.PseudoStats.SpellCostPercentModifier *= 80 + + } + if !char.HasActiveAuraWithTag(BloodlustAuraTag) { + char.MultiplyCastSpeed(sim, 1.2) + } + }, + OnExpire: func(aura *Aura, sim *Simulation) { + if char.HasManaBar() { + char.PseudoStats.SpellCostPercentModifier /= 80 + } + if !char.HasActiveAuraWithTag(BloodlustAuraTag) { + char.MultiplyCastSpeed(sim, 1/1.2) + } + }, + }) +} + +var InnervateAuraTag = "Innervate" - return baseAura +const InnervateDuration = time.Second * 20 +const InnervateCD = time.Minute * 6 + +func InnervateManaThreshold(character *Character) float64 { + if character.Class == proto.Class_ClassMage { + // Mages burn mana really fast so they need a higher threshold. + return character.MaxMana() * 0.7 + } else { + return 1000 + } } -func FuriousHowl(unit *Unit) *Aura { - baseAura := makeExclusiveBuff(unit, BuffConfig{ - "Furious Howl", - ActionID{SpellID: 24604}, - []StatConfig{ - {stats.PhysicalCritPercent, 5, false}, - {stats.SpellCritPercent, 5, false}, - }}) +func registerInnervateCD(char *Character, numInnervates int32) { + if numInnervates == 0 { + return + } + + innervateThreshold := 0.0 + expectedManaPerInnervate := 0.0 + var innervateAura *Aura + + char.Env.RegisterPostFinalizeEffect(func() { + innervateThreshold = InnervateManaThreshold(char) + expectedManaPerInnervate = char.SpiritManaRegenPerSecond() * 5 * 20 + innervateAura = InnervateAura(char, expectedManaPerInnervate, -1) + }) + + registerExternalConsecutiveCDApproximation( + char, + externalConsecutiveCDApproximation{ + ActionID: ActionID{SpellID: 29166, Tag: -1}, + AuraTag: InnervateAuraTag, + CooldownPriority: CooldownPriorityDefault, + AuraDuration: InnervateDuration, + AuraCD: InnervateCD, + Type: CooldownTypeMana, + ShouldActivate: func(sim *Simulation, character *Character) bool { + // Only cast innervate when very low on mana, to make sure all other mana CDs are prioritized. + if character.CurrentMana() > innervateThreshold { + return false + } + return true + }, + AddAura: func(sim *Simulation, character *Character) { + innervateAura.Activate(sim) + + // newRemainingUsages := int(sim.GetRemainingDuration() / InnervateCD) + // AddInnervateAura already accounts for 1 usage, which is why we subtract 1 less. + // character.ExpectedBonusMana -= expectedManaPerInnervate * MaxFloat(0, float64(remainingInnervateUsages-newRemainingUsages-1)) + // remainingInnervateUsages = newRemainingUsages - return baseAura + }, + }, + numInnervates) } -// ///////////////////////////////////////////////////////////////////////// -// -// Spell Haste -// -// ///////////////////////////////////////////////////////////////////////// -// Builds an ExclusiveEffect representing a SpellHaste bonus multiplier -// spellHastePercent should be given as the percent value i.E. 0.05 for +5% -func registerExclusiveSpellHaste(aura *Aura, spellHastePercent float64) { - aura.NewExclusiveEffect("SpellHaste%Buff", false, ExclusiveEffect{ - Priority: spellHastePercent, - OnGain: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.MultiplyCastSpeed(sim, 1+ee.Priority) +func InnervateAura(character *Character, expectedBonusManaReduction float64, actionTag int32) *Aura { + actionID := ActionID{SpellID: 29166, Tag: actionTag} + var manaMetrics *ResourceMetrics + return character.GetOrRegisterAura(Aura{ + Label: "Innervate-" + actionID.String(), + Tag: InnervateAuraTag, + ActionID: actionID, + Duration: InnervateDuration, + OnGain: func(aura *Aura, sim *Simulation) { + character.PseudoStats.ForceFullSpiritRegen = true + character.PseudoStats.SpiritRegenMultiplier *= 5.0 + character.UpdateManaRegenRates() + + expectedBonusManaPerTick := expectedBonusManaReduction / 10 + StartPeriodicAction(sim, PeriodicActionOptions{ + Period: InnervateDuration / 10, + NumTicks: 10, + OnAction: func(sim *Simulation) { + manaMetrics.AddEvent(expectedBonusManaPerTick, expectedBonusManaPerTick) + }, + }) }, - OnExpire: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.MultiplyCastSpeed(sim, 1/(1+ee.Priority)) + OnExpire: func(aura *Aura, sim *Simulation) { + character.PseudoStats.ForceFullSpiritRegen = false + character.PseudoStats.SpiritRegenMultiplier /= 5.0 + character.UpdateManaRegenRates() }, }) } -func MoonkinAura(unit *Unit) *Aura { - aura := makeExclusiveBuff(unit, BuffConfig{"Moonkin Aura", ActionID{SpellID: 24907}, nil}) - registerExclusiveSpellHaste(aura, 0.05) - return aura -} - -func MindQuickeningAura(u *Unit) *Aura { - aura := makeExclusiveBuff(u, BuffConfig{"Mind Quickening", ActionID{SpellID: 49868}, nil}) - registerExclusiveSpellHaste(aura, 0.05) - return aura -} - -func ElementalOath(u *Unit) *Aura { - aura := makeExclusiveBuff(u, BuffConfig{"Elemental Oath", ActionID{SpellID: 51470}, nil}) - registerExclusiveSpellHaste(aura, 0.05) - return aura -} - -// ///////////////////////////////////////////////////////////////////////// -// -// Spell Power -// -// ///////////////////////////////////////////////////////////////////////// - -func StillWaterAura(u *Unit) *Aura { - return makeExclusiveBuff(u, BuffConfig{"Still Water", ActionID{SpellID: 126309}, - []StatConfig{ - {stats.SpellDamage, 1.10, true}, - {stats.PhysicalCritPercent, 5, false}, - {stats.SpellCritPercent, 5, false}}}) -} - -func BurningWrathAura(u *Unit) *Aura { - return makeExclusiveBuff(u, BuffConfig{"Burning Wrath", ActionID{SpellID: 77747}, []StatConfig{{stats.SpellDamage, 1.10, true}}}) -} -func DarkIntentAura(u *Unit) *Aura { - return makeExclusiveBuff(u, BuffConfig{"Dark Intent", ActionID{SpellID: 109773}, []StatConfig{{stats.SpellDamage, 1.10, true}, {stats.Stamina, 1.10, true}}}) -} - -///////////// -/// OLD ///// -//////////// - // Applies buffs to pets. func applyPetBuffEffects(petAgent PetAgent, raidBuffs *proto.RaidBuffs, partyBuffs *proto.PartyBuffs, individualBuffs *proto.IndividualBuffs) { // Summoned pets, like Mage Water Elemental, aren't around to receive raid buffs. @@ -463,21 +1007,20 @@ type externalConsecutiveCDApproximation struct { // numSources is the number of other players assigned to apply the buff to this player. // E.g. the number of other shaman in the group using bloodlust. -func registerExternalConsecutiveCDApproximation(agent Agent, config externalConsecutiveCDApproximation, numSources int32) { +func registerExternalConsecutiveCDApproximation(char *Character, config externalConsecutiveCDApproximation, numSources int32) { if numSources == 0 { panic("Need at least 1 source!") } - character := agent.GetCharacter() var nextExternalIndex int externalTimers := make([]*Timer, numSources) for i := 0; i < int(numSources); i++ { - externalTimers[i] = character.NewTimer() + externalTimers[i] = char.NewTimer() } - sharedTimer := character.NewTimer() + sharedTimer := char.NewTimer() - spell := character.RegisterSpell(SpellConfig{ + spell := char.RegisterSpell(SpellConfig{ ActionID: config.ActionID, Flags: SpellFlagNoOnCastComplete | SpellFlagNoMetrics | SpellFlagNoLogs, @@ -492,7 +1035,7 @@ func registerExternalConsecutiveCDApproximation(agent Agent, config externalCons return false } - if character.HasActiveAuraWithTag(config.AuraTag) { + if char.HasActiveAuraWithTag(config.AuraTag) { return false } @@ -500,7 +1043,7 @@ func registerExternalConsecutiveCDApproximation(agent Agent, config externalCons }, ApplyEffects: func(sim *Simulation, _ *Unit, _ *Spell) { - config.AddAura(sim, character) + config.AddAura(sim, char) externalTimers[nextExternalIndex].Set(sim.CurrentTime + config.AuraCD) nextExternalIndex = (nextExternalIndex + 1) % len(externalTimers) @@ -515,7 +1058,7 @@ func registerExternalConsecutiveCDApproximation(agent Agent, config externalCons RelatedAuraArrays: config.RelatedAuraArrays, }) - character.AddMajorCooldown(MajorCooldown{ + char.AddMajorCooldown(MajorCooldown{ Spell: spell, Priority: config.CooldownPriority, Type: config.Type, @@ -580,263 +1123,32 @@ func BloodlustAura(character *Character, actionTag int32) *Aura { Duration: BloodlustDuration, OnGain: func(aura *Aura, sim *Simulation) { aura.Unit.MultiplyAttackSpeed(sim, 1.3) + aura.Unit.MultiplyCastSpeed(sim, 1.3) sated.Activate(sim) }, OnExpire: func(aura *Aura, sim *Simulation) { aura.Unit.MultiplyAttackSpeed(sim, 1/1.3) - }, - }) - - multiplyCastSpeedEffect(aura, 1.3) - return aura -} - -func multiplyCastSpeedEffect(aura *Aura, multiplier float64) *ExclusiveEffect { - return aura.NewExclusiveEffect("MultiplyCastSpeed", false, ExclusiveEffect{ - Priority: multiplier, - OnGain: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.MultiplyCastSpeed(sim, multiplier) - }, - OnExpire: func(ee *ExclusiveEffect, sim *Simulation) { - ee.Aura.Unit.MultiplyCastSpeed(sim, 1/multiplier) - }, - }) -} - -var TricksOfTheTradeAuraTag = "TricksOfTheTrade" - -func registerTricksOfTheTradeCD(agent Agent) { - unit := &agent.GetCharacter().Unit - tricksAura := TricksOfTheTradeAura(unit, -1, 1.15) - - // Add a small offset to the tooltip CD to account for input delays - // between the Rogue pressing Tricks and hitting a target. - effectiveCD := time.Second*30 + unit.ReactionTime - - registerExternalConsecutiveCDApproximation( - agent, - externalConsecutiveCDApproximation{ - ActionID: ActionID{SpellID: 57933, Tag: -1}, - AuraTag: TricksOfTheTradeAuraTag, - CooldownPriority: CooldownPriorityDefault, - RelatedSelfBuff: tricksAura, - AuraDuration: tricksAura.Duration, - AuraCD: effectiveCD, - Type: CooldownTypeDPS, - - ShouldActivate: func(sim *Simulation, character *Character) bool { - return !character.GetExclusiveEffectCategory("PercentDamageModifier").AnyActive() - }, - AddAura: func(sim *Simulation, character *Character) { - tricksAura.Activate(sim) - }, - }, - 1) -} - -func TricksOfTheTradeAura(character *Unit, actionTag int32, damageMult float64) *Aura { - actionID := ActionID{SpellID: 57933, Tag: actionTag} - - aura := character.GetOrRegisterAura(Aura{ - Label: "TricksOfTheTrade-" + actionID.String(), - Tag: TricksOfTheTradeAuraTag, - ActionID: actionID, - Duration: time.Second * 6, - }).AttachMultiplicativePseudoStatBuff(&character.PseudoStats.DamageDealtMultiplier, damageMult) - - RegisterPercentDamageModifierEffect(aura, damageMult) - return aura -} - -var UnholyFrenzyAuraTag = "UnholyFrenzy" - -const UnholyFrenzyDuration = time.Second * 30 -const UnholyFrenzyCD = time.Minute * 3 - -func registerUnholyFrenzyCD(agent Agent, numUnholyFrenzy int32) { - if numUnholyFrenzy == 0 { - return - } - - ufAura := UnholyFrenzyAura(&agent.GetCharacter().Unit, -1, func() bool { return false }) - - registerExternalConsecutiveCDApproximation( - agent, - externalConsecutiveCDApproximation{ - ActionID: ActionID{SpellID: 49016, Tag: -1}, - AuraTag: UnholyFrenzyAuraTag, - CooldownPriority: CooldownPriorityDefault, - RelatedSelfBuff: ufAura, - AuraDuration: UnholyFrenzyDuration, - AuraCD: UnholyFrenzyCD, - Type: CooldownTypeDPS, - - ShouldActivate: func(sim *Simulation, character *Character) bool { - return !character.GetExclusiveEffectCategory("PercentDamageModifier").AnyActive() - }, - AddAura: func(sim *Simulation, character *Character) { ufAura.Activate(sim) }, - }, - numUnholyFrenzy) -} - -func UnholyFrenzyAura(character *Unit, actionTag int32, has2pT14 func() bool) *Aura { - actionID := ActionID{SpellID: 49016, Tag: actionTag} - - var activeMultiplier float64 - // TODO: Should also lose 2% max hp every 3 sec. - aura := character.GetOrRegisterAura(Aura{ - Label: "UnholyFrenzy-" + actionID.String(), - Tag: UnholyFrenzyAuraTag, - ActionID: actionID, - Duration: UnholyFrenzyDuration, - OnGain: func(aura *Aura, sim *Simulation) { - activeMultiplier = TernaryFloat64(has2pT14(), 1.3, 1.2) - aura.Unit.MultiplyAttackSpeed(sim, activeMultiplier) - }, - OnExpire: func(aura *Aura, sim *Simulation) { - aura.Unit.MultiplyAttackSpeed(sim, 1/activeMultiplier) + aura.Unit.MultiplyCastSpeed(sim, 1/1.3) }, }) return aura } -func RegisterPercentDamageModifierEffect(aura *Aura, percentDamageModifier float64) *ExclusiveEffect { - return aura.NewExclusiveEffect("PercentDamageModifier", true, ExclusiveEffect{ - Priority: percentDamageModifier, - }) -} - -var DevotionAuraTag = "DevotionAura" - -var DevotionAuraActionID = ActionID{SpellID: 31821} - -const DevotionAuraDuration = time.Second * 6 -const DevotionAuraCD = time.Minute * 3 - -func registerDevotionAuraCD(agent Agent, numDevotionAuras int32) { - if numDevotionAuras == 0 { - return - } - - // TODO: Config for specifying the amount of Holy spec Devotion Auras? - devAura := DevotionAuraAura(&agent.GetCharacter().Unit, -1, true) - - registerExternalConsecutiveCDApproximation( - agent, - externalConsecutiveCDApproximation{ - ActionID: DevotionAuraActionID.WithTag(-1), - AuraTag: DevotionAuraTag, - CooldownPriority: CooldownPriorityLow, - RelatedSelfBuff: devAura, - AuraDuration: DevotionAuraDuration, - AuraCD: DevotionAuraCD, - Type: CooldownTypeSurvival, - - ShouldActivate: func(sim *Simulation, character *Character) bool { - return true - }, - AddAura: func(sim *Simulation, character *Character) { devAura.Activate(sim) }, - }, - numDevotionAuras) -} - -func DevotionAuraAura(unit *Unit, actionTag int32, isHoly bool) *Aura { - actionID := DevotionAuraActionID.WithTag(actionTag) - - auraConfig := Aura{ - Label: "DevotionAura-" + actionID.String(), - Tag: DevotionAuraTag, - ActionID: actionID, - Duration: DevotionAuraDuration, - } - - if isHoly { - // Beta changes 2025-06-13: https://www.wowhead.com/mop-classic/news/additional-holy-priest-and-paladin-changes-coming-to-mists-of-pandaria-classic-377264 - // - Devotion Aura cast by a Holy Paladin will now reduce all damage by 20% (was Magical damage only). - // - Developers’ notes: Changing Devotion Aura to reduce all damage makes it beneficial in more situations and aligns with other damage reducing abilities like Power Word: Barrier. - // EffectIndex 2 on the Holy specific Hotfix Passive https://wago.tools/db2/SpellEffect?build=5.5.0.61496&filter%5BSpellID%5D=137029&page=1 - auraConfig.AttachMultiplicativePseudoStatBuff(&unit.PseudoStats.DamageTakenMultiplier, 0.8) - } else { - auraConfig.OnGain = func(aura *Aura, sim *Simulation) { - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexArcane] *= 0.8 - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFire] *= 0.8 - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFrost] *= 0.8 - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexHoly] *= 0.8 - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexNature] *= 0.8 - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexShadow] *= 0.8 - } - auraConfig.OnExpire = func(aura *Aura, sim *Simulation) { - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexArcane] /= 0.8 - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFire] /= 0.8 - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFrost] /= 0.8 - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexHoly] /= 0.8 - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexNature] /= 0.8 - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexShadow] /= 0.8 - } - } - - return unit.GetOrRegisterAura(auraConfig) -} - -const VigilanceAuraTag = "Vigilance" -const VigilanceDuration = time.Second * 12 -const VigilanceCD = time.Minute * 2 -const VigilanceSpellID int32 = 114030 - -func registerVigilanceCD(agent Agent, numWarriors int32) { - if numWarriors == 0 { - return - } - - buffAura := VigilanceAura(agent.GetCharacter(), -1) - - registerExternalConsecutiveCDApproximation( - agent, - externalConsecutiveCDApproximation{ - ActionID: ActionID{SpellID: VigilanceSpellID, Tag: -1}, - AuraTag: VigilanceAuraTag, - CooldownPriority: CooldownPriorityLow, - RelatedSelfBuff: buffAura, - AuraDuration: VigilanceDuration, - AuraCD: VigilanceCD, - Type: CooldownTypeSurvival, - - ShouldActivate: func(sim *Simulation, character *Character) bool { - return true - }, - AddAura: func(sim *Simulation, character *Character) { - buffAura.Activate(sim) - }, - }, - numWarriors) -} - -func VigilanceAura(character *Character, actionTag int32) *Aura { - actionID := ActionID{SpellID: VigilanceSpellID, Tag: actionTag} - - return character.GetOrRegisterAura(Aura{ - Label: "Vigilance-" + actionID.String(), - Tag: VigilanceAuraTag, - ActionID: actionID, - Duration: VigilanceDuration, - }).AttachMultiplicativePseudoStatBuff(&character.PseudoStats.DamageTakenMultiplier, 0.7) -} - var PainSuppressionAuraTag = "PainSuppression" const PainSuppressionDuration = time.Second * 8 const PainSuppressionCD = time.Minute * 3 -func registerPainSuppressionCD(agent Agent, numPainSuppressions int32) { +func registerPainSuppressionCD(char *Character, numPainSuppressions int32) { if numPainSuppressions == 0 { return } - psAura := PainSuppressionAura(agent.GetCharacter(), -1) + psAura := PainSuppressionAura(char, -1) registerExternalConsecutiveCDApproximation( - agent, + char, externalConsecutiveCDApproximation{ ActionID: ActionID{SpellID: 33206, Tag: -1}, AuraTag: PainSuppressionAuraTag, @@ -867,132 +1179,13 @@ func PainSuppressionAura(character *Character, actionTag int32) *Aura { }).AttachMultiplicativePseudoStatBuff(&character.PseudoStats.DamageTakenMultiplier, 0.6) } -var GuardianSpiritAuraTag = "GuardianSpirit" - -const GuardianSpiritDuration = time.Second * 10 -const GuardianSpiritCD = time.Minute * 3 - -func registerGuardianSpiritCD(agent Agent, numGuardianSpirits int32) { - if numGuardianSpirits == 0 { - return - } - - character := agent.GetCharacter() - gsAura := GuardianSpiritAura(character, -1) - healthMetrics := character.NewHealthMetrics(ActionID{SpellID: 47788}) - - character.AddDynamicDamageTakenModifier(func(sim *Simulation, _ *Spell, result *SpellResult, isPeriodic bool) { - if (result.Damage >= character.CurrentHealth()) && gsAura.IsActive() { - result.Damage = character.CurrentHealth() - character.GainHealth(sim, 0.5*character.MaxHealth(), healthMetrics) - gsAura.Deactivate(sim) - } - }) - - registerExternalConsecutiveCDApproximation( - agent, - externalConsecutiveCDApproximation{ - ActionID: ActionID{SpellID: 47788, Tag: -1}, - AuraTag: GuardianSpiritAuraTag, - CooldownPriority: CooldownPriorityLow, - RelatedSelfBuff: gsAura, - AuraDuration: GuardianSpiritDuration, - AuraCD: GuardianSpiritCD, - Type: CooldownTypeSurvival, - - ShouldActivate: func(sim *Simulation, character *Character) bool { - return true - }, - AddAura: func(sim *Simulation, character *Character) { - gsAura.Activate(sim) - }, - }, - numGuardianSpirits) -} - -func GuardianSpiritAura(character *Character, actionTag int32) *Aura { - actionID := ActionID{SpellID: 47788, Tag: actionTag} - - return character.GetOrRegisterAura(Aura{ - Label: "GuardianSpirit-" + actionID.String(), - Tag: GuardianSpiritAuraTag, - ActionID: actionID, - Duration: GuardianSpiritDuration, - }).AttachMultiplicativePseudoStatBuff(&character.PseudoStats.HealingTakenMultiplier, 1.4) -} - -var RallyingCryAuraTag = "RallyingCry" -var RallyingCryActionID = ActionID{SpellID: 97462} - -const RallyingCryDuration = time.Second * 10 -const RallyingCryCD = time.Minute * 3 - -func registerRallyingCryCD(agent Agent, numRallyingCries int32) { - if numRallyingCries == 0 { - return - } - - rallyingCryArray := RallyingCryAuraArray(&agent.GetCharacter().Unit, -1) - - registerExternalConsecutiveCDApproximation( - agent, - externalConsecutiveCDApproximation{ - ActionID: RallyingCryActionID.WithTag(-1), - AuraTag: RallyingCryAuraTag, - CooldownPriority: CooldownPriorityLow, - RelatedAuraArrays: rallyingCryArray.ToMap(), - AuraDuration: RallyingCryDuration, - AuraCD: RallyingCryCD, - Type: CooldownTypeSurvival, - - ShouldActivate: func(_ *Simulation, _ *Character) bool { - return true - }, - - AddAura: func(sim *Simulation, _ *Character) { - rallyingCryArray.ActivateAll(sim) - }, - }, - numRallyingCries, - ) -} - -func RallyingCryAuraArray(unit *Unit, actionTag int32) AuraArray { - actionID := RallyingCryActionID.WithTag(actionTag) - - return unit.NewAllyAuraArray(func(allyUnit *Unit) *Aura { - if !allyUnit.HasHealthBar() { - return nil - } - - healthMetrics := allyUnit.NewHealthMetrics(actionID) - var bonusHealth float64 - return allyUnit.GetOrRegisterAura(Aura{ - Label: "RallyingCry-" + actionID.String(), - Tag: RallyingCryAuraTag, - ActionID: actionID, - Duration: RallyingCryDuration, - - OnGain: func(_ *Aura, sim *Simulation) { - bonusHealth = allyUnit.MaxHealth() * 0.2 - allyUnit.UpdateMaxHealth(sim, bonusHealth, healthMetrics) - }, - - OnExpire: func(_ *Aura, sim *Simulation) { - allyUnit.UpdateMaxHealth(sim, -bonusHealth, healthMetrics) - }, - }) - }) - -} - var ManaTideTotemActionID = ActionID{SpellID: 16190} var ManaTideTotemAuraTag = "ManaTideTotem" const ManaTideTotemDuration = time.Second * 12 const ManaTideTotemCD = time.Minute * 5 -func registerManaTideTotemCD(agent Agent, numManaTideTotems int32) { +func registerManaTideTotemCD(char *Character, numManaTideTotems int32) { if numManaTideTotems == 0 { return } @@ -1000,16 +1193,15 @@ func registerManaTideTotemCD(agent Agent, numManaTideTotems int32) { initialDelay := time.Duration(0) var mttAura *Aura - character := agent.GetCharacter() - mttAura = ManaTideTotemAura(character, -1) + mttAura = ManaTideTotemAura(char, -1) - character.Env.RegisterPostFinalizeEffect(func() { + char.Env.RegisterPostFinalizeEffect(func() { // Use first MTT at 60s, or halfway through the fight, whichever comes first. - initialDelay = min(character.Env.BaseDuration/2, time.Second*60) + initialDelay = min(char.Env.BaseDuration/2, time.Second*60) }) registerExternalConsecutiveCDApproximation( - agent, + char, externalConsecutiveCDApproximation{ ActionID: ManaTideTotemActionID.WithTag(-1), AuraTag: ManaTideTotemAuraTag, @@ -1039,165 +1231,3 @@ func ManaTideTotemAura(character *Character, actionTag int32) *Aura { Duration: ManaTideTotemDuration, }).AttachStatDependency(dep) } - -const StormLashAuraTag = "StormLash" -const StormLashDuration = time.Second * 10 -const StormLashCD = time.Minute * 5 - -func registerStormLashCD(agent Agent, numStormLashes int32) { - if numStormLashes == 0 { - return - } - - sbAura := StormLashAura(agent.GetCharacter(), -1) - - registerExternalConsecutiveCDApproximation( - agent, - externalConsecutiveCDApproximation{ - ActionID: ActionID{SpellID: 120668, Tag: -1}, - AuraTag: StormLashAuraTag, - CooldownPriority: CooldownPriorityDefault, - RelatedSelfBuff: sbAura, - AuraDuration: StormLashDuration, - AuraCD: StormLashCD, - Type: CooldownTypeDPS, - - ShouldActivate: func(sim *Simulation, character *Character) bool { - return true - }, - AddAura: func(sim *Simulation, character *Character) { - sbAura.Activate(sim) - }, - }, - numStormLashes) -} - -var StormLashSpellExceptions = map[int32]float64{ - 1120: 2.0, // Drain Soul - 403: 2.0, // Lightning Bolt - 51505: 2.0, // Lava Burst - 103103: 1.0, // Malefic Grasp - 15407: 1.0, // Mind Flay - 129197: 1.0, // Mind Flay - Insanity - 120360: 1.0, // Barrage - 1752: 0.5, // Sinister Strike - 50286: 0.0, // Starfall -} - -// Source: https://www.wowhead.com/mop-classic/spell=120668/stormlash-totem#comments -func StormLashAura(character *Character, actionTag int32) *Aura { - actionId := ActionID{SpellID: 120687, Tag: actionTag} - for _, pet := range character.Pets { - if !pet.IsGuardian() { - StormLashAura(&pet.Character, actionTag) - } - } - - damage := 0.0 - - stormlashSpell := character.RegisterSpell(SpellConfig{ - ActionID: actionId, - Flags: SpellFlagNoOnCastComplete | SpellFlagPassiveSpell, - SpellSchool: SpellSchoolNature, - ProcMask: ProcMaskEmpty, - - DamageMultiplier: 1, - CritMultiplier: character.DefaultMeleeCritMultiplier(), - - ApplyEffects: func(sim *Simulation, target *Unit, spell *Spell) { - spell.CalcAndDealDamage(sim, target, damage, spell.OutcomeMagicHitAndCrit) - }, - }) - - handler := func(aura *Aura, sim *Simulation, spell *Spell, result *SpellResult) { - if !aura.Icd.IsReady(sim) || !result.Landed() || result.Damage <= 0 || !spell.ProcMask.Matches(ProcMaskDirect|ProcMaskSpecial) || !sim.Proc(0.5, "Stormlash") { - return - } - - ap := Ternary(spell.IsRanged(), stormlashSpell.RangedAttackPower(), stormlashSpell.MeleeAttackPower()) - sp := stormlashSpell.SpellDamage() - scaledAP := ap * 0.2 - scaledSP := sp * 0.3 - - baseDamage := max(scaledAP, scaledSP) - baseMultiplier := 2.0 - speedMultiplier := 1.0 - if multiplier, ok := StormLashSpellExceptions[spell.ActionID.SpellID]; ok && multiplier != 0 { - baseMultiplier = baseMultiplier * multiplier - } - if spell.Unit.Type == PetUnit { - baseMultiplier *= 0.2 - } - - if spell.ProcMask.Matches(ProcMaskWhiteHit) { - swingSpeed := 0.0 - baseMultiplier *= 0.4 - - if spell.IsRanged() { - ranged := spell.Unit.AutoAttacks.Ranged() - if ranged != nil { - swingSpeed = ranged.SwingSpeed - } - } else if spell.IsMH() { - mh := spell.Unit.AutoAttacks.MH() - if mh != nil { - swingSpeed = mh.SwingSpeed - } - } else { - baseMultiplier /= 2 - oh := spell.Unit.AutoAttacks.OH() - if oh != nil { - swingSpeed = oh.SwingSpeed - } - } - - speedMultiplier = swingSpeed / 2.6 - } else { - speedMultiplier = max(spell.DefaultCast.CastTime.Seconds(), 1.5) / 1.5 - } - - avg := baseDamage * baseMultiplier * speedMultiplier - min, max := ApplyVarianceMinMax(avg, 0.30) - damage = sim.RollWithLabel(min, max, StormLashAuraTag) - - if sim.Log != nil { - var chosenStat = Ternary(scaledAP > scaledSP, stats.AttackPower, stats.SpellDamage) - var statValue = Ternary(chosenStat == stats.AttackPower, ap, sp) - - character.Log(sim, "[DEBUG] Damage portion for Stormlash procced by %s: Stat=%s, BaseStatValue=%0.2f, BaseDamage=%0.2f, BaseMultiplier=%0.2f, SpeedMultiplier=%0.2f, PreOutcomeDamageAvg=%0.2f, PreOutcomeDamageMin=%0.2f, PreOutcomeDamageMax=%0.2f, PreOutcomeDamageActual=%0.2f", - spell.ActionID, chosenStat.StatName(), statValue, baseDamage, baseMultiplier, speedMultiplier, avg, min, max, damage) - } - stormlashSpell.Cast(sim, result.Target) - aura.Icd.Use(sim) - } - - return character.GetOrRegisterAura(Aura{ - Label: "Stormlash Totem-" + actionId.String(), - Tag: StormLashAuraTag, - ActionID: ActionID{SpellID: 120668, Tag: actionTag}, - Duration: StormLashDuration, - Icd: &Cooldown{ - Timer: character.NewTimer(), - Duration: time.Millisecond * 70, - }, - OnGain: func(aura *Aura, sim *Simulation) { - for _, pet := range character.Pets { - if pet.IsEnabled() && !pet.IsGuardian() { - pet.GetAura(aura.Label).Activate(sim) - } - } - }, - OnSpellHitDealt: func(aura *Aura, sim *Simulation, spell *Spell, result *SpellResult) { - // Some Spells are DoT-like, so filter them - if multiplier, ok := StormLashSpellExceptions[spell.ActionID.SpellID]; !ok || (ok && multiplier != 0) { - handler(aura, sim, spell, result) - } - }, - OnPeriodicDamageDealt: func(aura *Aura, sim *Simulation, spell *Spell, result *SpellResult) { - // All DoTs that can trigger Stormlash are exceptions - if _, ok := StormLashSpellExceptions[spell.ActionID.SpellID]; ok { - handler(aura, sim, spell, result) - } - }, - }) -} diff --git a/sim/core/spell_result.go b/sim/core/spell_result.go index 7b4cb7682b..f10b74823d 100644 --- a/sim/core/spell_result.go +++ b/sim/core/spell_result.go @@ -693,7 +693,13 @@ func (result *SpellResult) applyTargetModifiers(sim *Simulation, spell *Spell, a if spell.Flags.Matches(SpellFlagIgnoreTargetModifiers) { return } - result.Damage += attackTable.Defender.PseudoStats.BonusPhysicalDamageTaken + + if spell.SpellSchool == SpellSchoolPhysical { + result.Damage += attackTable.Defender.PseudoStats.BonusPhysicalDamageTaken + } else if spell.SpellSchool != SpellSchoolPhysical && spell.SpellSchool != SpellSchoolNone { + result.Damage += attackTable.Defender.PseudoStats.BonusSpellDamageTaken + } + result.Damage *= spell.TargetDamageMultiplier(sim, attackTable, isPeriodic) } func (spell *Spell) TargetDamageMultiplier(sim *Simulation, attackTable *AttackTable, isPeriodic bool) float64 { diff --git a/sim/core/stats/stats.go b/sim/core/stats/stats.go index 0d7c818c6a..b478169356 100644 --- a/sim/core/stats/stats.go +++ b/sim/core/stats/stats.go @@ -494,6 +494,7 @@ type PseudoStats struct { BonusHealingTaken float64 // Talisman of Troll Divinity BonusSpellCritPercentTaken float64 // Imp Shadow Bolt / Imp Scorch / Winter's Chill debuff BonusPhysicalDamageTaken float64 // Hemo, Gift of Arthas, etc + BonusSpellDamageTaken float64 // Amp Magic DamageTakenMultiplier float64 // All damage SchoolDamageTakenMultiplier [SchoolLen]float64 // For specific spell schools (arcane, fire, shadow, etc.) diff --git a/sim/core/unit.go b/sim/core/unit.go index d7e0d8940a..8f4e587f1d 100644 --- a/sim/core/unit.go +++ b/sim/core/unit.go @@ -209,7 +209,11 @@ func (unit *Unit) getSpellDamageValueImpl(spell *Spell) float64 { } func (unit *Unit) getAttackPowerValueImpl(spell *Spell) float64 { - return unit.stats[stats.AttackPower] + unit.PseudoStats.BonusAttackPower + bonusAP := 0.0 + if spell.ProcMask == ProcMaskMeleeMHAuto { + bonusAP = spell.BonusSpellDamage + } + return unit.stats[stats.AttackPower] + unit.PseudoStats.BonusAttackPower + bonusAP } // Units can be disabled for several reasons: diff --git a/sim/paladin/holy/holy.go b/sim/paladin/holy/holy.go index 4cd5bcc2d3..025b157fa0 100644 --- a/sim/paladin/holy/holy.go +++ b/sim/paladin/holy/holy.go @@ -3,7 +3,6 @@ package holy import ( "github.com/wowsims/tbc/sim/core" "github.com/wowsims/tbc/sim/core/proto" - "github.com/wowsims/tbc/sim/core/stats" "github.com/wowsims/tbc/sim/paladin" ) @@ -44,7 +43,6 @@ func (holy *HolyPaladin) GetPaladin() *paladin.Paladin { func (holy *HolyPaladin) ApplyTalents() { holy.Paladin.ApplyTalents() - holy.ApplyArmorSpecializationEffect(stats.Intellect, proto.ArmorType_ArmorTypePlate, 86525) } func (holy *HolyPaladin) Initialize() { diff --git a/sim/paladin/paladin.go b/sim/paladin/paladin.go index b0078607f8..d30e865444 100644 --- a/sim/paladin/paladin.go +++ b/sim/paladin/paladin.go @@ -189,3 +189,11 @@ func (paladin *Paladin) AnyActiveDefensiveCooldown() bool { return false } + +const ( + PaladinSpellFlagNone int64 = 0 + PaladinSpellConsecration int64 = 1 << iota + PaladinSpellFlashOfLight + PaladinSpellHolyLight + PaladinSpellAll int64 = 1< Date: Fri, 26 Dec 2025 22:42:19 -0500 Subject: [PATCH 18/20] ignore tests for removed things --- sim/core/{armor_test.go => _armor_test.go} | 32 +++++++++---------- ...fect_test.go => _exclusive_effect_test.go} | 0 2 files changed, 16 insertions(+), 16 deletions(-) rename sim/core/{armor_test.go => _armor_test.go} (66%) rename sim/core/{exclusive_effect_test.go => _exclusive_effect_test.go} (100%) diff --git a/sim/core/armor_test.go b/sim/core/_armor_test.go similarity index 66% rename from sim/core/armor_test.go rename to sim/core/_armor_test.go index 1373fb1dfe..f71a1be878 100644 --- a/sim/core/armor_test.go +++ b/sim/core/_armor_test.go @@ -62,21 +62,21 @@ func TestDamageReductionFromArmor(t *testing.T) { t.Fatalf("Expected no armor modifiers to result in %f damage reduction got %f", expectedDamageReduction, 1-attackTable.getArmorDamageModifier()) } - // Major - weakenedArmorAura := WeakenedArmorAura(&target) - weakenedArmorAura.Activate(&sim) - weakenedArmorAura.SetStacks(&sim, 3) - expectedDamageReduction = 0.320864 - if !WithinToleranceFloat64(1-expectedDamageReduction, attackTable.getArmorDamageModifier(), tolerance) { - t.Fatalf("Expected major armor modifier to result in %f damage reduction got %f", expectedDamageReduction, 1-attackTable.getArmorDamageModifier()) - } - weakenedArmorAura.Deactivate(&sim) + // // Major + // weakenedArmorAura := WeakenedArmorAura(&target) + // weakenedArmorAura.Activate(&sim) + // weakenedArmorAura.SetStacks(&sim, 3) + // expectedDamageReduction = 0.320864 + // if !WithinToleranceFloat64(1-expectedDamageReduction, attackTable.getArmorDamageModifier(), tolerance) { + // t.Fatalf("Expected major armor modifier to result in %f damage reduction got %f", expectedDamageReduction, 1-attackTable.getArmorDamageModifier()) + // } + // weakenedArmorAura.Deactivate(&sim) - // Major Multi - shatteringThrowAura := ShatteringThrowAura(&target, attacker.UnitIndex) - shatteringThrowAura.Activate(&sim) - expectedDamageReduction = 0.300459 - if !WithinToleranceFloat64(1-expectedDamageReduction, attackTable.getArmorDamageModifier(), tolerance) { - t.Fatalf("Expected major & shattering modifier to result in %f damage reduction got %f", expectedDamageReduction, 1-attackTable.getArmorDamageModifier()) - } + // // Major Multi + // shatteringThrowAura := ShatteringThrowAura(&target, attacker.UnitIndex) + // shatteringThrowAura.Activate(&sim) + // expectedDamageReduction = 0.300459 + // if !WithinToleranceFloat64(1-expectedDamageReduction, attackTable.getArmorDamageModifier(), tolerance) { + // t.Fatalf("Expected major & shattering modifier to result in %f damage reduction got %f", expectedDamageReduction, 1-attackTable.getArmorDamageModifier()) + // } } diff --git a/sim/core/exclusive_effect_test.go b/sim/core/_exclusive_effect_test.go similarity index 100% rename from sim/core/exclusive_effect_test.go rename to sim/core/_exclusive_effect_test.go From 332c061d59e1fad3566b3c9afae552e41403e2fd Mon Sep 17 00:00:00 2001 From: jazz405 Date: Fri, 26 Dec 2025 22:51:22 -0500 Subject: [PATCH 19/20] comment out BoL for now --- sim/core/buffs.go | 60 +++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/sim/core/buffs.go b/sim/core/buffs.go index 3f232ec4f8..b36f8569ce 100644 --- a/sim/core/buffs.go +++ b/sim/core/buffs.go @@ -695,36 +695,36 @@ func BlessingOfKingsAura(char *Character) *Aura { return makeMultiplierBuff(char, "Blessing of Kings", 20217, time.Hour*1, bokStats, 1.1) } -func BlessingOfLight(char *Character) *Aura { - return char.GetOrRegisterAura(Aura{ - Label: "Blessing of Light", - ActionID: ActionID{SpellID: 27145}, - Duration: time.Minute * 30, - - OnApplyEffects: func(aura *Aura, sim *Simulation, target *Unit, spell *Spell) { - if spell.ProcMask != ProcMaskSpellHealing { - return - } - - if spell.Unit.ownerClass != proto.Class_ClassPaladin { - return - } - - // Keep an eye on if this changes in paladin.go - // FlashOfLight = 2 - // HolyLight = 3 - if spell.ClassSpellMask != 2 || spell.ClassSpellMask != 3 { - return - } - - if spell.ClassSpellMask == 2 { - spell.BonusSpellDamage += 185 - } else { - spell.BonusSpellDamage += 580 - } - }, - }) -} +// func BlessingOfLight(char *Character) *Aura { +// return char.GetOrRegisterAura(Aura{ +// Label: "Blessing of Light", +// ActionID: ActionID{SpellID: 27145}, +// Duration: time.Minute * 30, + +// OnApplyEffects: func(aura *Aura, sim *Simulation, target *Unit, spell *Spell) { +// if spell.ProcMask != ProcMaskSpellHealing { +// return +// } + +// if spell.Unit.ownerClass != proto.Class_ClassPaladin { +// return +// } + +// // Keep an eye on if this changes in paladin.go +// // FlashOfLight = 2 +// // HolyLight = 3 +// if spell.ClassSpellMask != 2 || spell.ClassSpellMask != 3 { +// return +// } + +// if spell.ClassSpellMask == 2 { +// spell.BonusSpellDamage += 185 +// } else { +// spell.BonusSpellDamage += 580 +// } +// }, +// }) +// } func BlessingOfMightAura(char *Character, improved bool) *Aura { apBuff := 220.0 From d0063338ace5e4a62bb8d38cf480a0d9ffb23a23 Mon Sep 17 00:00:00 2001 From: jazz405 Date: Sat, 27 Dec 2025 10:29:23 -0500 Subject: [PATCH 20/20] Fix or ignore tests --- sim/core/{dot_test.go => _dot_test.go} | 2 +- sim/core/stats/deps.go | 2 +- sim/core/stats/stats.go | 20 ++++++++++++++++++++ sim/core/stats/stats_test.go | 4 +++- 4 files changed, 25 insertions(+), 3 deletions(-) rename sim/core/{dot_test.go => _dot_test.go} (99%) diff --git a/sim/core/dot_test.go b/sim/core/_dot_test.go similarity index 99% rename from sim/core/dot_test.go rename to sim/core/_dot_test.go index 2794a47f65..4c1dd2163a 100644 --- a/sim/core/dot_test.go +++ b/sim/core/_dot_test.go @@ -69,7 +69,7 @@ func NewFakeElementalShaman(char *Character, _ *proto.Player) Agent { }, NumberOfTicks: 6, TickLength: time.Second * 3, - AffectedByCastSpeed: true, + AffectedByCastSpeed: false, BonusCoefficient: 1, OnSnapshot: func(sim *Simulation, target *Unit, dot *Dot) { diff --git a/sim/core/stats/deps.go b/sim/core/stats/deps.go index 1a7c0aa25d..0d3a08a2ff 100644 --- a/sim/core/stats/deps.go +++ b/sim/core/stats/deps.go @@ -19,7 +19,7 @@ var safeDepsOrder = []Stat{ Armor, AttackPower, RangedAttackPower, - SpellPower, + SpellDamage, Health, Mana, MP5, diff --git a/sim/core/stats/stats.go b/sim/core/stats/stats.go index b478169356..883787f3a0 100644 --- a/sim/core/stats/stats.go +++ b/sim/core/stats/stats.go @@ -174,6 +174,10 @@ func (s Stat) StatName() string { return "RangedAttackPower" case FeralAttackPower: return "FeralAttackPower" + case HealingPower: + return "HealingPower" + case SpellPower: + return "SpellPower" case SpellDamage: return "SpellDamage" case ArcaneDamage: @@ -210,6 +214,22 @@ func (s Stat) StatName() string { return "SpellCritPercent" case BlockPercent: return "BlockPercent" + case DefenseRating: + return "DefenseRating" + case BlockRating: + return "BlockRating" + case BlockValue: + return "BlockValue" + case ArcaneResistance: + return "ArcaneResistance" + case FireResistance: + return "FireResistance" + case FrostResistance: + return "FrostResistance" + case NatureResistance: + return "NatureResistance" + case ShadowResistance: + return "ShadowResistance" } return "none" diff --git a/sim/core/stats/stats_test.go b/sim/core/stats/stats_test.go index e4eacf2629..682d22a5ee 100644 --- a/sim/core/stats/stats_test.go +++ b/sim/core/stats/stats_test.go @@ -85,8 +85,10 @@ func TestStatsProtoInSync(t *testing.T) { protoName := enum.Name() goName := Stat(enum.Number()).StatName() sanitizedGoName := strings.ReplaceAll(goName, " ", "") - if string(protoName) != "Stat"+sanitizedGoName { + if string(protoName) != "Stat"+sanitizedGoName && !strings.Contains(string(protoName), "AllPhys") { t.Fatalf("Encountered stat enum %d in proto.Stats with name %s differs from Go enum name %s (ignoring Stat prefix)", enum.Number(), protoName, goName) + } else if strings.Contains(string(protoName), "AllPhys") && string(protoName) != "StatAllPhys"+sanitizedGoName { + t.Fatalf("Encountered stat enum %d in proto.Stats with name %s differs from Go enum name %s (ignoring AllPhys prefix)", enum.Number(), protoName, goName) } } }