From 0865c004cee1892480fae90772d72714b1c1b6c9 Mon Sep 17 00:00:00 2001 From: Josh DM Date: Tue, 5 Sep 2023 15:23:39 -0400 Subject: [PATCH] implement nibelung --- sim/common/wotlk/nibelung.go | 140 +++++++++++++++++++++++++++++ sim/common/wotlk/other_effects.go | 11 +++ sim/druid/balance/balance.go | 2 + sim/mage/mage.go | 2 + sim/priest/shadow/shadow_priest.go | 2 + sim/warlock/warlock.go | 2 + ui/core/proto_utils/action_id.ts | 1 + 7 files changed, 160 insertions(+) create mode 100644 sim/common/wotlk/nibelung.go diff --git a/sim/common/wotlk/nibelung.go b/sim/common/wotlk/nibelung.go new file mode 100644 index 0000000000..a7e0f91ed2 --- /dev/null +++ b/sim/common/wotlk/nibelung.go @@ -0,0 +1,140 @@ +package wotlk + +import ( + "github.com/wowsims/wotlk/sim/core" + "github.com/wowsims/wotlk/sim/core/stats" + "time" +) + +var valkyrStats = stats.Stats{ + stats.Stamina: 1260, +} + +type ValkyrPet struct { + core.Pet + smite *core.Spell + healthMetrics *core.ResourceMetrics +} + +func newValkyr(character *core.Character) *ValkyrPet { + return &ValkyrPet{ + Pet: core.NewPet( + "Valkyr", + character, + valkyrStats, + func(ownerStats stats.Stats) stats.Stats { + return stats.Stats{} + }, + nil, + false, + true, + ), + } +} + +func getSmiteConfig(valkyr *ValkyrPet, spellId int32, damageMin float64, damageMax float64) core.SpellConfig { + return core.SpellConfig{ + ActionID: core.ActionID{SpellID: spellId}, + SpellSchool: core.SpellSchoolHoly, + ProcMask: core.ProcMaskEmpty, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: time.Second * 30 / 16, // 1.875s (16 casts per 30s) + CastTime: 0, + }, + }, + DamageMultiplier: 1, + ThreatMultiplier: 1, + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + baseDamage := sim.Roll(damageMin, damageMax) + result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeCritFixedChance(0.05)) + spell.DealDamage(sim, result) + valkyr.GainHealth(sim, result.Damage*0.25, valkyr.healthMetrics) + }, + CritMultiplier: valkyr.DefaultSpellCritMultiplier(), + } + +} + +func (valkyr *ValkyrPet) registerSmite(isHeroic bool) { + spellId := int32(71842) + if isHeroic { + spellId = 71841 + } + + if valkyr.healthMetrics == nil { + valkyr.healthMetrics = valkyr.NewHealthMetrics(core.ActionID{SpellID: spellId}) + } + + if isHeroic { + smite := getSmiteConfig(valkyr, spellId, 1804, 2022) + valkyr.smite = valkyr.GetOrRegisterSpell(smite) + } else { + smite := getSmiteConfig(valkyr, spellId, 1591, 1785) + valkyr.smite = valkyr.GetOrRegisterSpell(smite) + } +} + +func (valkyr *ValkyrPet) Initialize() {} + +func (valkyr *ValkyrPet) Reset(sim *core.Simulation) {} + +func (valkyr *ValkyrPet) OnGCDReady(sim *core.Simulation) { + target := valkyr.CurrentTarget + + if valkyr.smite.CanCast(sim, target) { + valkyr.smite.Cast(sim, target) + } +} + +func (valkyr *ValkyrPet) GetPet() *core.Pet { + return &valkyr.Pet +} + +func MakeNibelungTriggerAura(agent core.Agent, isHeroic bool) { + var auraSpellId, procSpellId int32 + + if isHeroic { + auraSpellId = 71844 + procSpellId = 71846 + } else { + auraSpellId = 71843 + procSpellId = 71845 + } + + character := agent.GetCharacter() + valkyrAura := character.RegisterAura(core.Aura{ + Label: "Summon Val'kyr", + ActionID: core.ActionID{SpellID: auraSpellId}, + Duration: time.Second * 30, + }) + + core.MakeProcTriggerAura(&character.Unit, core.ProcTrigger{ + Name: "Nibelung Proc", + Callback: core.CallbackOnCastComplete, + ProcMask: core.ProcMaskSpellOrProc, + Harmful: true, + ProcChance: 0.02, + ICD: time.Millisecond * 250, + ActionID: core.ActionID{SpellID: procSpellId}, + Handler: func(sim *core.Simulation, _ *core.Spell, _ *core.SpellResult) { + for _, pet := range character.Pets { + valkyr, ok := pet.(*ValkyrPet) + if ok && !valkyr.IsEnabled() { + valkyr.registerSmite(isHeroic) + valkyr.EnableWithTimeout(sim, pet, valkyrAura.Duration) + break + } + } + + valkyrAura.Activate(sim) + }, + }) +} + +func ConstructValkyrPets(character *core.Character) { + for i := 0; i < 10; i++ { + valkyr := newValkyr(character) + character.AddPet(valkyr) + } +} diff --git a/sim/common/wotlk/other_effects.go b/sim/common/wotlk/other_effects.go index ea3d753d6e..c1c87d8dcf 100644 --- a/sim/common/wotlk/other_effects.go +++ b/sim/common/wotlk/other_effects.go @@ -1075,4 +1075,15 @@ func init() { Type: core.CooldownTypeSurvival, }) }) + + NewItemEffectWithHeroic(func(isHeroic bool) { + itemId := int32(49992) + if isHeroic { + itemId = 50648 + } + + core.NewItemEffect(itemId, func(agent core.Agent) { + MakeNibelungTriggerAura(agent, isHeroic) + }) + }) } diff --git a/sim/druid/balance/balance.go b/sim/druid/balance/balance.go index 358bca42ac..740c4d7053 100644 --- a/sim/druid/balance/balance.go +++ b/sim/druid/balance/balance.go @@ -1,6 +1,7 @@ package balance import ( + "github.com/wowsims/wotlk/sim/common/wotlk" "github.com/wowsims/wotlk/sim/core" "github.com/wowsims/wotlk/sim/core/proto" "github.com/wowsims/wotlk/sim/core/stats" @@ -39,6 +40,7 @@ func NewBalanceDruid(character core.Character, options *proto.Player) *BalanceDr } moonkin.EnableResumeAfterManaWait(moonkin.tryUseGCD) + wotlk.ConstructValkyrPets(&moonkin.Character) return moonkin } diff --git a/sim/mage/mage.go b/sim/mage/mage.go index 17bcc5c6b9..6be22565e4 100644 --- a/sim/mage/mage.go +++ b/sim/mage/mage.go @@ -1,6 +1,7 @@ package mage import ( + "github.com/wowsims/wotlk/sim/common/wotlk" "time" "github.com/wowsims/wotlk/sim/core" @@ -204,6 +205,7 @@ func NewMage(character core.Character, options *proto.Player) *Mage { mage.waterElemental = mage.NewWaterElemental(mage.Rotation.WaterElementalDisobeyChance) } + wotlk.ConstructValkyrPets(&mage.Character) return mage } diff --git a/sim/priest/shadow/shadow_priest.go b/sim/priest/shadow/shadow_priest.go index f9337b7c63..85af224887 100644 --- a/sim/priest/shadow/shadow_priest.go +++ b/sim/priest/shadow/shadow_priest.go @@ -1,6 +1,7 @@ package shadow import ( + "github.com/wowsims/wotlk/sim/common/wotlk" "time" "github.com/wowsims/wotlk/sim/core" @@ -48,6 +49,7 @@ func NewShadowPriest(character core.Character, options *proto.Player) *ShadowPri spriest.EnableResumeAfterManaWait(spriest.tryUseGCD) spriest.CanRolloverSWP = spriest.Talents.MindFlay && spriest.Talents.PainAndSuffering > 0 + wotlk.ConstructValkyrPets(&spriest.Character) return spriest } diff --git a/sim/warlock/warlock.go b/sim/warlock/warlock.go index 41b4530632..0c0781f1e0 100644 --- a/sim/warlock/warlock.go +++ b/sim/warlock/warlock.go @@ -1,6 +1,7 @@ package warlock import ( + "github.com/wowsims/wotlk/sim/common/wotlk" "time" "github.com/wowsims/wotlk/sim/core" @@ -239,6 +240,7 @@ func NewWarlock(character core.Character, options *proto.Player) *Warlock { } warlock.applyWeaponImbue() + wotlk.ConstructValkyrPets(&warlock.Character) return warlock } diff --git a/ui/core/proto_utils/action_id.ts b/ui/core/proto_utils/action_id.ts index e55f97e0d8..9ba0425b18 100644 --- a/ui/core/proto_utils/action_id.ts +++ b/ui/core/proto_utils/action_id.ts @@ -603,6 +603,7 @@ const petNameToActionId: Record = { 'Gargoyle': ActionId.fromSpellId(49206), 'Ghoul': ActionId.fromSpellId(46584), 'Army of the Dead': ActionId.fromSpellId(42650), + 'Valkyr': ActionId.fromSpellId(71844), }; // https://wowhead.com/wotlk/hunter-pets