From 044964325bf80a51f151f93c88090af2885d27be Mon Sep 17 00:00:00 2001 From: sanguinerarogue Date: Sun, 29 Dec 2024 13:29:33 -0700 Subject: [PATCH 1/5] First pass at hunter --- proto/hunter.proto | 75 +--- sim/core/attack.go | 8 + sim/hunter/_item_sets_pvp.go | 4 +- sim/hunter/aimed_shot.go | 4 +- sim/hunter/aspects.go | 65 +--- sim/hunter/hunter.go | 9 - sim/hunter/items.go | 425 +-------------------- sim/hunter/kill_command.go | 70 ---- sim/hunter/multi_shot.go | 4 +- sim/hunter/pet.go | 104 +---- sim/hunter/pet_abilities.go | 4 - sim/hunter/pet_talents.go | 4 - sim/hunter/serpent_sting.go | 3 +- sim/hunter/talents.go | 11 +- ui/core/components/inputs/buffs_debuffs.ts | 29 +- ui/core/launched_sims.ts | 2 +- ui/core/talents/hunter_pet.ts | 5 +- ui/hunter/presets.ts | 2 - ui/index.html | 4 +- 19 files changed, 65 insertions(+), 767 deletions(-) delete mode 100644 sim/hunter/kill_command.go delete mode 100644 sim/hunter/pet_talents.go diff --git a/proto/hunter.proto b/proto/hunter.proto index 217f68e5ce..ee95c11e36 100644 --- a/proto/hunter.proto +++ b/proto/hunter.proto @@ -57,51 +57,6 @@ message HunterTalents { bool wyvern_sting = 46; } -message HunterPetTalents { - // Cunning - int32 cobra_reflexes = 1; - bool dive = 2; - int32 great_stamina = 3; - int32 natural_armor = 4; - bool boars_speed = 5; - int32 mobility = 6; - int32 owls_focus = 7; - int32 spiked_collar = 8; - int32 culling_the_herd = 9; - int32 lionhearted = 10; - bool carrion_feeder = 11; - int32 great_resistance = 12; - int32 cornered = 13; - int32 feeding_frenzy = 14; - bool wolverine_bite = 15; - bool roar_of_recovery = 16; - bool bullheaded = 17; - int32 grace_of_the_mantis = 18; - int32 wild_hunt = 19; - bool roar_of_sacrifice = 20; - - // Ferocity - int32 improved_cower = 21; - int32 bloodthirsty = 22; - bool heart_of_the_pheonix = 23; - int32 spiders_bite = 24; - bool rabid = 25; - bool lick_your_wounds = 26; - bool call_of_the_wild = 27; - int32 shark_attack = 28; - - // Tenacity - bool charge = 29; - int32 blood_of_the_rhino = 30; - int32 pet_barding = 31; - int32 guard_dog = 32; - bool thunderstomp = 33; - bool last_stand = 34; - bool taunt = 35; - bool intervene = 36; - int32 silverback = 37; -} - message Hunter { message Rotation { enum RotationType { @@ -165,16 +120,15 @@ message Hunter { Owl = 6; Boar = 7; CarrionBird = 8; - CoreHound = 10; - Crab = 11; - Crocolisk = 12; - Gorilla = 15; - Hyena = 16; - Raptor = 17; - Scorpid = 18; - Spider = 21; - Tallstrider = 24; - Turtle = 25; + Crab = 9; + Crocolisk = 10; + Gorilla = 11; + Hyena = 12; + Raptor = 13; + Scorpid = 14; + Spider = 15; + Tallstrider = 16; + Turtle = 17; } enum PetAttackSpeed { @@ -191,16 +145,13 @@ message Hunter { } PetType pet_type = 3; - HunterPetTalents pet_talents = 4; - double pet_uptime = 5; - - double sniper_training_uptime = 6; + double pet_uptime = 4; - double pet_attack_speed_old = 7; + double pet_attack_speed_old = 5; - bool new_raptor_strike = 8; + bool new_raptor_strike = 6; - PetAttackSpeed pet_attack_speed = 9; + PetAttackSpeed pet_attack_speed = 7; } Options options = 2; } diff --git a/sim/core/attack.go b/sim/core/attack.go index a1ca049dee..d101c248e5 100644 --- a/sim/core/attack.go +++ b/sim/core/attack.go @@ -842,6 +842,14 @@ func (aa *AutoAttacks) StopMeleeUntil(sim *Simulation, readyAt time.Duration, de } } +func (aa *AutoAttacks) StopRangedUntil(sim *Simulation, readyAt time.Duration) { + if !aa.AutoSwingRanged { // if not auto swinging, don't auto restart. + return + } + aa.ranged.swingAt = readyAt + aa.ranged.curSwingDuration + sim.rescheduleWeaponAttack(aa.ranged.swingAt) +} + // Delays all swing timers for the specified amount. Only used by Slam. func (aa *AutoAttacks) DelayMeleeBy(sim *Simulation, delay time.Duration) { if delay <= 0 { diff --git a/sim/hunter/_item_sets_pvp.go b/sim/hunter/_item_sets_pvp.go index f5035860c0..07b1481bbb 100644 --- a/sim/hunter/_item_sets_pvp.go +++ b/sim/hunter/_item_sets_pvp.go @@ -6,7 +6,7 @@ import ( ) /////////////////////////////////////////////////////////////////////////// -// SoD Phase 3 Item Sets +// Classic Phase 2 /////////////////////////////////////////////////////////////////////////// var ItemSetBloodGuardsChain = core.NewItemSet(core.ItemSet{ @@ -40,7 +40,7 @@ var ItemSetKnightLieutenantsChain = core.NewItemSet(core.ItemSet{ }) /////////////////////////////////////////////////////////////////////////// -// SoD Phase 4 Item Sets +// Classic Phase 3 /////////////////////////////////////////////////////////////////////////// var ItemSetChampionsPursuit = core.NewItemSet(core.ItemSet{ diff --git a/sim/hunter/aimed_shot.go b/sim/hunter/aimed_shot.go index 681148f632..bfd7b47cee 100644 --- a/sim/hunter/aimed_shot.go +++ b/sim/hunter/aimed_shot.go @@ -37,8 +37,10 @@ func (hunter *Hunter) getAimedShotConfig(rank int, timer *core.Timer) core.Spell Timer: timer, Duration: time.Second * 6, }, - ModifyCast: func(_ *core.Simulation, spell *core.Spell, cast *core.Cast) { + ModifyCast: func(sim *core.Simulation, spell *core.Spell, cast *core.Cast) { cast.CastTime = spell.CastTime() + // + 1ms to fix engine issue with clipping + hunter.AutoAttacks.DelayRangedUntil(sim, sim.CurrentTime+spell.CastTime() + time.Millisecond *1) }, IgnoreHaste: true, // Hunter GCD is locked at 1.5s CastTime: func(spell *core.Spell) time.Duration { diff --git a/sim/hunter/aspects.go b/sim/hunter/aspects.go index ceddc16ab0..c77d24448a 100644 --- a/sim/hunter/aspects.go +++ b/sim/hunter/aspects.go @@ -120,67 +120,4 @@ func (hunter *Hunter) registerAspectOfTheHawkSpell() { maxRank := hunter.getMaxHawkRank() config := hunter.getAspectOfTheHawkSpellConfig(maxRank) hunter.GetOrRegisterSpell(config) -} - -func (hunter *Hunter) registerAspectOfTheViperSpell() { - actionID := core.ActionID{SpellID: 415423} - manaMetrics := hunter.NewManaMetrics(actionID) - - var manaPA *core.PendingAction - - baseManaRegenMultiplier := 0.02 - - aspectOfTheViperAura := hunter.GetOrRegisterAura(core.Aura{ - Label: "Aspect of the Viper", - ActionID: actionID, - Duration: core.NeverExpires, - - OnGain: func(aura *core.Aura, sim *core.Simulation) { - hunter.PseudoStats.DamageDealtMultiplier *= 0.9 - - manaPA = core.StartPeriodicAction(sim, core.PeriodicActionOptions{ - Period: time.Second * 3, - OnAction: func(s *core.Simulation) { - hunter.AddMana(sim, hunter.MaxMana()*0.1, manaMetrics) - }, - }) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - hunter.PseudoStats.DamageDealtMultiplier /= 0.9 - manaPA.Cancel(sim) - }, - - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell == hunter.AutoAttacks.RangedAuto() { - manaPerRangedHitMultiplier := baseManaRegenMultiplier * hunter.AutoAttacks.Ranged().SwingSpeed - hunter.AddMana(sim, hunter.MaxMana()*manaPerRangedHitMultiplier, manaMetrics) - } else if spell == hunter.AutoAttacks.MHAuto() { - manaPerMHHitMultiplier := baseManaRegenMultiplier * hunter.AutoAttacks.MH().SwingSpeed - hunter.AddMana(sim, hunter.MaxMana()*manaPerMHHitMultiplier, manaMetrics) - } else if spell == hunter.AutoAttacks.OHAuto() { - manaPerOHHitMultiplier := baseManaRegenMultiplier * hunter.AutoAttacks.OH().SwingSpeed - hunter.AddMana(sim, hunter.MaxMana()*manaPerOHHitMultiplier, manaMetrics) - } - }, - }) - - aspectOfTheViperAura.NewExclusiveEffect("Aspect", true, core.ExclusiveEffect{}) - - hunter.GetOrRegisterSpell(core.SpellConfig{ - ActionID: actionID, - Flags: core.SpellFlagAPL, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return !aspectOfTheViperAura.IsActive() - }, - - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { - aspectOfTheViperAura.Activate(sim) - }, - }) -} +} \ No newline at end of file diff --git a/sim/hunter/hunter.go b/sim/hunter/hunter.go index 957fde5410..2280c90b27 100644 --- a/sim/hunter/hunter.go +++ b/sim/hunter/hunter.go @@ -134,13 +134,6 @@ func (hunter *Hunter) AddRaidBuffs(raidBuffs *proto.RaidBuffs) { }[hunter.Level]) } - // Hunter gains an additional 10% stats from Aspect of the Lion - statMultiply := 1.1 - hunter.MultiplyStat(stats.Strength, statMultiply) - hunter.MultiplyStat(stats.Stamina, statMultiply) - hunter.MultiplyStat(stats.Agility, statMultiply) - hunter.MultiplyStat(stats.Intellect, statMultiply) - hunter.MultiplyStat(stats.Spirit, statMultiply) } func (hunter *Hunter) AddPartyBuffs(_ *proto.PartyBuffs) { } @@ -163,7 +156,6 @@ func (hunter *Hunter) Initialize() { }) hunter.registerAspectOfTheHawkSpell() - hunter.registerAspectOfTheViperSpell() multiShotTimer := hunter.NewTimer() arcaneShotTimer := hunter.NewTimer() @@ -185,7 +177,6 @@ func (hunter *Hunter) Initialize() { hunter.registerImmolationTrapSpell(traps) hunter.registerFreezingTrapSpell(traps) - // hunter.registerKillCommand() hunter.registerRapidFire() } diff --git a/sim/hunter/items.go b/sim/hunter/items.go index 666c4cb3f1..b1056bc9db 100644 --- a/sim/hunter/items.go +++ b/sim/hunter/items.go @@ -10,49 +10,8 @@ import ( const ( DevilsaurEye = 19991 DevilsaurTooth = 19992 - // SignetOfBeasts = 209823 - // BloodlashBow = 216516 - // GurubashiPitFightersBow = 221450 - // BloodChainVices = 227075 - // KnightChainVices = 227077 - // BloodChainGrips = 227081 - // KnightChainGrips = 227087 - // WhistleOfTheBeast = 228432 - // ArcaneInfusedGem = 230237 - // RenatakisCharmOfRavaging = 231288 - // MaelstromsWrath = 231320 - // ZandalarPredatorsMantle = 231321 - // ZandalarPredatorsBelt = 231322 - // ZandalarPredatorsBracers = 231323 - // MarshalChainGrips = 231560 - // GeneralChainGrips = 231569 - // GeneralChainVices = 231575 - // MarshalChainVices = 231578 - // Kestrel = 231754 - // Peregrine = 231755 - // CloakOfTheUnseenPath = 233420 - // ScytheOfTheUnseenPath = 233421 - // SignetOfTheUnseenPath = 233422 ) -/* func applyRaptorStrikeDamageEffect(agent core.Agent, multiplier float64) { - hunter := agent.(HunterAgent).GetHunter() - hunter.OnSpellRegistered(func(spell *core.Spell) { - if spell.SpellCode == SpellCode_HunterRaptorStrikeHit { - spell.DamageMultiplier *= multiplier - } - }) -} - -func applyMultiShotDamageEffect(agent core.Agent, multiplier float64) { - hunter := agent.(HunterAgent).GetHunter() - hunter.OnSpellRegistered(func(spell *core.Spell) { - if spell.SpellCode == SpellCode_HunterMultiShot { - spell.DamageMultiplier *= multiplier - } - }) -} */ - func init() { core.NewItemEffect(DevilsaurEye, func(agent core.Agent) { hunter := agent.(HunterAgent).GetHunter() @@ -169,386 +128,4 @@ func init() { }, }) }) - - /* core.NewItemEffect(SignetOfBeasts, func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - if hunter.pet != nil { - hunter.pet.PseudoStats.DamageDealtMultiplier *= 1.01 - } - }) - - core.NewItemEffect(BloodlashBow, func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - hunter.newBloodlashProcItem(50, 436471) - }) - - core.NewItemEffect(GurubashiPitFightersBow, func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - hunter.newBloodlashProcItem(75, 446723) - }) - - // https://www.wowhead.com/classic/item=228432/whistle-of-the-beast - // Use: Your pet's next attack is guaranteed to critically strike if that attack is capable of striking critically. (1 Min Cooldown) - core.NewItemEffect(WhistleOfTheBeast, func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - - if hunter.pet == nil { - return - } - - hunter.pet.PseudoStats.DamageDealtMultiplier *= 1.03 - hunter.pet.MultiplyStat(stats.Health, 1.03) - hunter.pet.MultiplyStat(stats.Armor, 1.10) - hunter.pet.AddStat(stats.MeleeCrit, 2*core.CritRatingPerCritChance) - hunter.pet.AddStat(stats.SpellCrit, 2*core.SpellCritRatingPerCritChance) - - actionID := core.ActionID{ItemID: WhistleOfTheBeast} - - trackingAura := hunter.GetOrRegisterAura(core.Aura{ - Label: "Whistle of the Beast Hunter", - ActionID: actionID, - Duration: core.NeverExpires, - }) - - aura := hunter.pet.GetOrRegisterAura(core.Aura{ - Label: "Whistle of the Beast", - ActionID: actionID, - Duration: core.NeverExpires, - - OnGain: func(aura *core.Aura, sim *core.Simulation) { - if hunter.pet.focusDump != nil { - hunter.pet.focusDump.BonusCritRating += 100 - } - if hunter.pet.specialAbility != nil { - hunter.pet.specialAbility.BonusCritRating += 100 - } - trackingAura.Activate(sim) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - if hunter.pet.focusDump != nil { - hunter.pet.focusDump.BonusCritRating -= 100 - } - if hunter.pet.specialAbility != nil { - hunter.pet.specialAbility.BonusCritRating -= 100 - } - trackingAura.Deactivate(sim) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell == hunter.pet.focusDump || spell == hunter.pet.specialAbility { - aura.Deactivate(sim) - } - }, - }) - - spell := hunter.GetOrRegisterSpell(core.SpellConfig{ - ActionID: actionID, - Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagOffensiveEquipment, - - Cast: core.CastConfig{ - CD: core.Cooldown{ - Timer: hunter.NewTimer(), - Duration: time.Minute * 1, - }, - }, - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return hunter.pet.IsEnabled() - }, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - aura.Activate(sim) - }, - }) - - hunter.AddMajorCooldown(core.MajorCooldown{ - Spell: spell, - Type: core.CooldownTypeDPS, - ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { - return hunter.pet != nil && hunter.pet.IsEnabled() - }, - }) - }) - - core.NewItemEffect(BloodChainGrips, func(agent core.Agent) { - applyRaptorStrikeDamageEffect(agent, 1.04) - }) - - core.NewItemEffect(KnightChainGrips, func(agent core.Agent) { - applyRaptorStrikeDamageEffect(agent, 1.04) - }) - - core.NewItemEffect(GeneralChainGrips, func(agent core.Agent) { - applyRaptorStrikeDamageEffect(agent, 1.04) - }) - - core.NewItemEffect(MarshalChainGrips, func(agent core.Agent) { - applyRaptorStrikeDamageEffect(agent, 1.04) - }) - - core.NewItemEffect(BloodChainVices, func(agent core.Agent) { - applyMultiShotDamageEffect(agent, 1.04) - }) - - core.NewItemEffect(KnightChainVices, func(agent core.Agent) { - applyMultiShotDamageEffect(agent, 1.04) - }) - - core.NewItemEffect(GeneralChainVices, func(agent core.Agent) { - applyMultiShotDamageEffect(agent, 1.04) - }) - - core.NewItemEffect(MarshalChainVices, func(agent core.Agent) { - applyMultiShotDamageEffect(agent, 1.04) - }) - - core.NewItemEffect(MaelstromsWrath, func(a core.Agent) { - hunter := a.(HunterAgent).GetHunter() - if hunter.pet == nil { - return - } - - hunter.pet.PseudoStats.DamageDealtMultiplier *= 1.02 - - if !hunter.Talents.BestialWrath { - return - } - - hunter.RegisterAura(core.Aura{ - Label: "Maelstroms's Wrath Bestial Wrath", - OnInit: func(aura *core.Aura, sim *core.Simulation) { - hunter.BestialWrathPetAura.Duration += (time.Second * 3) - }, - }) - }) - - core.NewItemEffect(ZandalarPredatorsMantle, func(a core.Agent) { - hunter := a.(HunterAgent).GetHunter() - if hunter.pet == nil { - return - } - - hunter.pet.PseudoStats.DamageDealtMultiplier *= 1.03 - }) - - core.NewItemEffect(ZandalarPredatorsBelt, func(a core.Agent) { - hunter := a.(HunterAgent).GetHunter() - if hunter.pet == nil { - return - } - - hunter.pet.PseudoStats.DamageDealtMultiplier *= 1.02 - }) - - core.NewItemEffect(ZandalarPredatorsBracers, func(a core.Agent) { - hunter := a.(HunterAgent).GetHunter() - - if hunter.pet == nil { - return - } - - hunter.pet.PseudoStats.DamageDealtMultiplier *= 1.01 - }) - - // https://www.wowhead.com/classic/item=231755/peregrine - // Chance on hit: Instantly gain 1 extra attack with both weapons. - // Main-hand attack is treated like a normal extra-attack, Off-hand attack is a spell that uses your off-hand damage but won't glance - core.NewItemEffect(Peregrine, func(agent core.Agent) { - character := agent.GetCharacter() - peregrineOHAttack := character.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 469140}, - SpellSchool: core.SpellSchoolPhysical, - DefenseType: core.DefenseTypeMelee, - ProcMask: core.ProcMaskMeleeOHSpecial, - Flags: core.SpellFlagMeleeMetrics, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - damage := character.OHWeaponDamage(sim, spell.MeleeAttackPower()) * character.AutoAttacks.OHConfig().DamageMultiplier - spell.CalcAndDealDamage(sim, target, damage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) - }, - }) - core.MakeProcTriggerAura(&character.Unit, core.ProcTrigger{ - Name: "Peregrine Trigger", - Callback: core.CallbackOnSpellHitDealt, - Outcome: core.OutcomeLanded, - ProcMask: core.ProcMaskMeleeOH, - SpellFlagsExclude: core.SpellFlagSuppressWeaponProcs, - PPM: 1.0, - Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - character.AutoAttacks.ExtraMHAttackProc(sim, 1, core.ActionID{SpellID: 469140}, spell) - peregrineOHAttack.Cast(sim, result.Target) - }, - }) - }) - - itemhelpers.CreateWeaponProcAura(Kestrel, "Kestrel", 1, func(character *core.Character) *core.Aura { - return character.GetOrRegisterAura(core.Aura{ - Label: "Kestrel Move Speed Aura", - ActionID: core.ActionID{SpellID: 469148}, - Duration: time.Second * 10, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - character.AddMoveSpeedModifier(&aura.ActionID, 1.40) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - character.RemoveMoveSpeedModifier(&aura.ActionID) - }, - }) - }) - - // https://www.wowhead.com/classic/item=231288/renatakis-charm-of-ravaging - core.NewItemEffect(RenatakisCharmOfRavaging, func(agent core.Agent) { - character := agent.GetCharacter() - - lockedIn := character.RegisterAura(core.Aura{ - Label: "Locked In", - ActionID: core.ActionID{SpellID: 468388}, - Duration: time.Second * 20, - MaxStacks: 2, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - if spell.Flags.Matches(SpellFlagShot) || spell.ProcMask.Matches(core.ProcMaskMeleeSpecial) && spell.CD.Timer != nil { - spell.CD.Reset() - aura.RemoveStack(sim) - } - }, - }) - - spell := character.GetOrRegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 468388}, - Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagOffensiveEquipment, - - Cast: core.CastConfig{ - CD: core.Cooldown{ - Timer: character.NewTimer(), - Duration: time.Minute * 2, - }, - }, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - lockedIn.Activate(sim) - lockedIn.SetStacks(sim, lockedIn.MaxStacks) - }, - }) - - character.AddMajorCooldown(core.MajorCooldown{ - Spell: spell, - Type: core.CooldownTypeDPS, - }) - }) - - // https://www.wowhead.com/classic/item=230237/arcane-infused-gem - core.NewItemEffect(ArcaneInfusedGem, func(agent core.Agent) { - character := agent.GetCharacter() - - arcaneDetonation := character.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 467447}, - SpellSchool: core.SpellSchoolArcane, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - for _, aoeTarget := range sim.Encounter.TargetUnits { - damage := sim.Roll(185, 210) - spell.CalcAndDealDamage(sim, aoeTarget, damage, spell.OutcomeMagicHitAndCrit) - } - }, - }) - - maxCarveTargetsPerCast := int32(5) - maxMultishotTargetsPerCast := int32(3) - - arcaneInfused := character.RegisterAura(core.Aura{ - Label: "Arcane Infused", - ActionID: core.ActionID{SpellID: 467446}, - Duration: time.Second * 15, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - maxCarveTargetsPerCast = min(sim.Environment.GetNumTargets(), 5) - maxMultishotTargetsPerCast = min(sim.Environment.GetNumTargets(), 3) - }, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - // Uses same targeting code as multi-shot however the detonations occur at cast time rather than when the shots land - if spell.SpellCode == SpellCode_HunterMultiShot { - curTarget := sim.Environment.Encounter.TargetUnits[0] - for hitIndex := int32(0); hitIndex < maxMultishotTargetsPerCast; hitIndex++ { - arcaneDetonation.Cast(sim, curTarget) - curTarget = sim.Environment.NextTargetUnit(curTarget) - } - } - // 1 explosion per target up to 5 targets per carve cast - if spell.SpellCode == SpellCode_HunterCarve { - curTarget := sim.Environment.Encounter.TargetUnits[0] - for hitIndex := int32(0); hitIndex < maxCarveTargetsPerCast; hitIndex++ { - arcaneDetonation.Cast(sim, curTarget) - curTarget = sim.Environment.NextTargetUnit(curTarget) - } - } - }, - }) - - spell := character.GetOrRegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: arcaneInfused.ActionID.SpellID}, - Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagOffensiveEquipment, - - Cast: core.CastConfig{ - CD: core.Cooldown{ - Timer: character.NewTimer(), - Duration: time.Second * 90, - }, - SharedCD: core.Cooldown{ - Timer: character.GetOffensiveTrinketCD(), - Duration: arcaneInfused.Duration, - }, - }, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - arcaneInfused.Activate(sim) - }, - }) - - character.AddMajorCooldown(core.MajorCooldown{ - Spell: spell, - Type: core.CooldownTypeDPS, - }) - }) - - core.NewItemEffect(CloakOfTheUnseenPath, func(a core.Agent) { - hunter := a.(HunterAgent).GetHunter() - if hunter.pet == nil { - return - } - - hunter.pet.PseudoStats.DamageDealtMultiplier *= 1.02 - }) - - core.NewItemEffect(ScytheOfTheUnseenPath, func(a core.Agent) { - hunter := a.(HunterAgent).GetHunter() - if hunter.pet == nil { - return - } - - hunter.pet.PseudoStats.DamageDealtMultiplier *= 1.03 - }) - - core.NewItemEffect(SignetOfTheUnseenPath, func(a core.Agent) { - hunter := a.(HunterAgent).GetHunter() - if hunter.pet == nil { - return - } - - hunter.pet.PseudoStats.DamageDealtMultiplier *= 1.02 - }) */ -} - -/* func (hunter *Hunter) newBloodlashProcItem(bonusStrength float64, spellId int32) { - procAura := hunter.NewTemporaryStatsAura("Bloodlash", core.ActionID{SpellID: spellId}, stats.Stats{stats.Strength: bonusStrength}, time.Second*15) - ppm := hunter.AutoAttacks.NewPPMManager(1.0, core.ProcMaskMeleeOrRanged) - core.MakePermanent(hunter.GetOrRegisterAura(core.Aura{ - Label: "Bloodlash Trigger", - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if result.Landed() && ppm.Proc(sim, spell.ProcMask, "Bloodlash Proc") { - procAura.Activate(sim) - } - }, - })) -} */ +} \ No newline at end of file diff --git a/sim/hunter/kill_command.go b/sim/hunter/kill_command.go deleted file mode 100644 index a30f2dbbfb..0000000000 --- a/sim/hunter/kill_command.go +++ /dev/null @@ -1,70 +0,0 @@ -package hunter - -// TODO: 2024-06-13 - Rune changed from Kill Command to Kill Shot. Unsure if Kill Command still in the game. -// func (hunter *Hunter) registerKillCommand() { -// if hunter.pet == nil || !hunter.HasRune(proto.HunterRune_RuneLegsKillCommand) { -// return -// } - -// actionID := core.ActionID{SpellID: 409379} -// hasCatlikeReflexes := hunter.HasRune(proto.HunterRune_RuneHelmCatlikeReflexes) - -// cooldownModifier := 1.0 -// if hasCatlikeReflexes { -// cooldownModifier *= 0.5 -// } - -// // For tracking in timeline -// hunterAura := hunter.RegisterAura(core.Aura{ -// Label: "Kill Command", -// ActionID: actionID, -// Duration: time.Second * 30, -// MaxStacks: 3, -// }) - -// hunter.pet.killCommandAura = hunter.pet.RegisterAura(core.Aura{ -// Label: "Kill Command", -// ActionID: actionID, -// Duration: time.Second * 30, -// MaxStacks: 3, -// OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { -// // TODO: Make it only work on Claw/Bite after pet abilities refactor -// if spell.ProcMask.Matches(core.ProcMaskMeleeSpecial | core.ProcMaskSpellDamage) { -// aura.RemoveStack(sim) -// hunterAura.RemoveStack(sim) -// } -// }, -// }) - -// hunter.KillCommand = hunter.RegisterSpell(core.SpellConfig{ -// ActionID: actionID, -// SpellSchool: core.SpellSchoolPhysical, -// Flags: core.SpellFlagNoOnCastComplete, - -// ManaCost: core.ManaCostOptions{ -// BaseCost: 0.015, -// }, -// Cast: core.CastConfig{ -// CD: core.Cooldown{ -// Timer: hunter.NewTimer(), -// Duration: time.Second * time.Duration(60*cooldownModifier), -// }, -// }, -// ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { -// return hunter.pet.IsEnabled() -// }, - -// ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { -// hunter.pet.killCommandAura.Activate(sim) -// hunter.pet.killCommandAura.SetStacks(sim, 3) - -// hunterAura.Activate(sim) -// hunterAura.SetStacks(sim, 3) -// }, -// }) - -// hunter.AddMajorCooldown(core.MajorCooldown{ -// Spell: hunter.KillCommand, -// Type: core.CooldownTypeDPS, -// }) -// } diff --git a/sim/hunter/multi_shot.go b/sim/hunter/multi_shot.go index eb605c7065..4f17bd23a0 100644 --- a/sim/hunter/multi_shot.go +++ b/sim/hunter/multi_shot.go @@ -36,8 +36,10 @@ func (hunter *Hunter) getMultiShotConfig(rank int, timer *core.Timer) core.Spell GCD: core.GCDDefault, CastTime: time.Millisecond * 500, }, - ModifyCast: func(_ *core.Simulation, spell *core.Spell, cast *core.Cast) { + ModifyCast: func(sim *core.Simulation, spell *core.Spell, cast *core.Cast) { cast.CastTime = spell.CastTime() + // + 1ms to fix engine issue with clipping + hunter.AutoAttacks.DelayRangedUntil(sim, sim.CurrentTime+spell.CastTime() + time.Millisecond *1) }, IgnoreHaste: true, // Hunter GCD is locked at 1.5s CD: core.Cooldown{ diff --git a/sim/hunter/pet.go b/sim/hunter/pet.go index 77d35b7301..7b8be4c271 100644 --- a/sim/hunter/pet.go +++ b/sim/hunter/pet.go @@ -16,8 +16,6 @@ type HunterPet struct { hunterOwner *Hunter - killCommandAura *core.Aura - specialAbility *core.Spell focusDump *core.Spell @@ -63,68 +61,16 @@ func (hunter *Hunter) NewHunterPet() *HunterPet { attackSpeed = 2.5 } - switch hunter.Level { - case 25: - baseMinDamage = 6.5 * attackSpeed - baseMaxDamage = 12.5 * attackSpeed - hunterPetBaseStats = stats.Stats{ - stats.Strength: 53, - stats.Agility: 45, - stats.Stamina: 120, - stats.Intellect: 29, - stats.Spirit: 39, - - stats.AttackPower: -20, - - // Add 1.8% because pets aren't affected by that component of crit suppression. - stats.MeleeCrit: (3.2 + 1.8) * core.CritRatingPerCritChance, - } - case 40: - baseMinDamage = 9.5 * attackSpeed - baseMaxDamage = 15.5 * attackSpeed - hunterPetBaseStats = stats.Stats{ - stats.Strength: 78, - stats.Agility: 66, - stats.Stamina: 160, - stats.Intellect: 37, - stats.Spirit: 55, - - stats.AttackPower: -20, - - // Add 1.8% because pets aren't affected by that component of crit suppression. - stats.MeleeCrit: (3.2 + 1.8) * core.CritRatingPerCritChance, - } - case 50: - baseMinDamage = 23.5 * attackSpeed - baseMaxDamage = 27.5 * attackSpeed - hunterPetBaseStats = stats.Stats{ - stats.Strength: 113, - stats.Agility: 82, - stats.Stamina: 257, - stats.Intellect: 43, - stats.Spirit: 67, - - stats.AttackPower: -20, - - // Add 1.8% because pets aren't affected by that component of crit suppression. - stats.MeleeCrit: (3.2 + 1.8) * core.CritRatingPerCritChance, - } - case 60: - // TODO: - baseMinDamage = 18.5 * attackSpeed - baseMaxDamage = 28.0 * attackSpeed - hunterPetBaseStats = stats.Stats{ - stats.Strength: 136, - stats.Agility: 100, - stats.Stamina: 274, - stats.Intellect: 50, - stats.Spirit: 80, - - stats.AttackPower: -20, - - // Add 1.8% because pets aren't affected by that component of crit suppression. - stats.MeleeCrit: (3.2 + 1.8) * core.CritRatingPerCritChance, - } + baseMinDamage = 18.5 * attackSpeed + baseMaxDamage = 28.0 * attackSpeed + hunterPetBaseStats = stats.Stats{ + stats.Strength: 136, + stats.Agility: 100, + stats.Stamina: 274, + stats.Intellect: 50, + stats.Spirit: 80, + + stats.AttackPower: -20, } hp := &HunterPet{ @@ -146,14 +92,10 @@ func (hunter *Hunter) NewHunterPet() *HunterPet { AutoSwingMelee: true, }) - // After checking numerous logs it seems pet auto attacks are hitting for less then what they should if following standard attack formulas - // TODO: Figure out from where this difference comes - // TODO: Phase2 this no longer seems to apply - //hp.AutoAttacks.MHConfig().DamageMultiplier *= 0.45 - // Happiness hp.PseudoStats.DamageDealtMultiplier *= 1.25 + // This stuff probably need removedD? // Family scalars hp.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] *= hp.config.Damage hp.PseudoStats.ArmorMultiplier *= hp.config.Armor @@ -176,13 +118,6 @@ func (hp *HunterPet) GetPet() *core.Pet { return &hp.Pet } -func (hp *HunterPet) Talents() *proto.HunterPetTalents { - if talents := hp.hunterOwner.Options.PetTalents; talents != nil { - return talents - } - return &proto.HunterPetTalents{} -} - func (hp *HunterPet) Initialize() { hp.specialAbility = hp.NewPetAbility(hp.config.SpecialAbility, true) hp.focusDump = hp.NewPetAbility(hp.config.FocusDump, false) @@ -252,13 +187,6 @@ func (hp *HunterPet) ExecuteCustomRotation(sim *core.Simulation) { } } -func (hp *HunterPet) killCommandMult() float64 { - if hp.killCommandAura == nil { - return 1 - } - return 1 + 0.2*float64(hp.killCommandAura.GetStacks()) -} - func (hunter *Hunter) makeStatInheritance() core.PetStatInheritance { return func(ownerStats stats.Stats) stats.Stats { // EJ posts claim this value is passed through math.Floor, but in-game testing @@ -399,16 +327,6 @@ var PetConfigs = map[proto.Hunter_Options_PetType]PetConfig{ Armor: 1.00, Damage: 1.07, }, - proto.Hunter_Options_CoreHound: { - Name: "Core Hound", - MobType: proto.MobType_MobTypeBeast, - - FocusDump: LavaBreath, - - Health: 1.06, - Armor: 1.01, - Damage: 1.02, - }, proto.Hunter_Options_Crab: { Name: "Crab", MobType: proto.MobType_MobTypeBeast, diff --git a/sim/hunter/pet_abilities.go b/sim/hunter/pet_abilities.go index 8ee40d24cc..2cbc36f193 100644 --- a/sim/hunter/pet_abilities.go +++ b/sim/hunter/pet_abilities.go @@ -95,8 +95,6 @@ func (hp *HunterPet) newClaw() *core.Spell { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := sim.Roll(baseDamageMin, baseDamageMax) + (spell.MeleeAttackPower() * ApCoeff) - baseDamage *= hp.killCommandMult() - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) }, }) @@ -154,8 +152,6 @@ func (hp *HunterPet) newBite() *core.Spell { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := sim.Roll(baseDamageMin, baseDamageMax) + (spell.MeleeAttackPower() * ApCoeff) - baseDamage *= hp.killCommandMult() - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) }, }) diff --git a/sim/hunter/pet_talents.go b/sim/hunter/pet_talents.go deleted file mode 100644 index 4a0f02d5b8..0000000000 --- a/sim/hunter/pet_talents.go +++ /dev/null @@ -1,4 +0,0 @@ -package hunter - -func (hp *HunterPet) ApplyTalents() { -} diff --git a/sim/hunter/serpent_sting.go b/sim/hunter/serpent_sting.go index c3a9cf4433..1490c3fba7 100644 --- a/sim/hunter/serpent_sting.go +++ b/sim/hunter/serpent_sting.go @@ -53,8 +53,7 @@ func (hunter *Hunter) getSerpentStingConfig(rank int) core.SpellConfig { BonusCoefficient: spellCoeff, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - // As of phase 5 the only time serpent sting scales with AP is using the Dragonstalker's Pursuit 6P - this AP scaling doesn't benefit from target AP modifiers - damage := baseDamage + (hunter.SerpentStingAPCoeff*dot.Spell.RangedAttackPower(target, true))/5 + damage := baseDamage dot.Snapshot(target, damage, isRollover) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { diff --git a/sim/hunter/talents.go b/sim/hunter/talents.go index 65fd958713..0d84056b8e 100644 --- a/sim/hunter/talents.go +++ b/sim/hunter/talents.go @@ -61,7 +61,12 @@ func (hunter *Hunter) ApplyTalents() { hunter.AddStat(stats.MeleeCrit, float64(hunter.Talents.KillerInstinct)*1*core.CritRatingPerCritChance) if hunter.Talents.LethalShots > 0 { - hunter.AddStat(stats.MeleeCrit, 1*float64(hunter.Talents.LethalShots)*core.CritRatingPerCritChance) + for _, spell := range hunter.Shots { + if spell != nil { + spell.BonusCritRating += 1*float64(hunter.Talents.LethalShots)*core.CritRatingPerCritChance + } + } + hunter.AutoAttacks.RangedConfig().BonusCritRating += 1*float64(hunter.Talents.LethalShots)*core.CritRatingPerCritChance } if hunter.Talents.RangedWeaponSpecialization > 0 { @@ -198,8 +203,8 @@ func (hunter *Hunter) applyCleverTraps() { func (hunter *Hunter) applyEfficiency() { hunter.OnSpellRegistered(func(spell *core.Spell) { - // applies to Stings, Shots, Strikes and Volley - if spell.Cost != nil && spell.Flags.Matches(SpellFlagSting|SpellFlagShot|SpellFlagStrike) || spell.SpellCode == SpellCode_HunterVolley { + // applies to Stings, Shots, and Volley + if spell.Cost != nil && spell.Flags.Matches(SpellFlagSting|SpellFlagShot) || spell.SpellCode == SpellCode_HunterVolley { spell.Cost.Multiplier -= 2 * hunter.Talents.Efficiency } }) diff --git a/ui/core/components/inputs/buffs_debuffs.ts b/ui/core/components/inputs/buffs_debuffs.ts index f830f50173..f3645a70b0 100644 --- a/ui/core/components/inputs/buffs_debuffs.ts +++ b/ui/core/components/inputs/buffs_debuffs.ts @@ -296,12 +296,13 @@ export const BattleSquawkBuff = makeMultistateRaidBuffInput({ // WORLD BUFFS /////////////////////////////////////////////////////////////////////////// -export const RallyingCryOfTheDragonslayer = makeBooleanIndividualBuffInput({ - actionId: () => ActionId.fromSpellId(22888), - fieldName: 'rallyingCryOfTheDragonslayer', -}); -export const DragonslayerBuffInput = InputHelpers.makeMultiIconInput({ values: [RallyingCryOfTheDragonslayer], label: 'Dragonslayer Buff' }); - +export const RallyingCryOfTheDragonslayer = withLabel( + makeBooleanIndividualBuffInput({ + actionId: () => ActionId.fromSpellId(22888), + fieldName: 'rallyingCryOfTheDragonslayer', + }), + 'Rallying Cry Of The Dragonslayer', +); export const SpiritOfZandalar = withLabel( makeBooleanIndividualBuffInput({ actionId: () => ActionId.fromSpellId(24425), @@ -679,20 +680,10 @@ export const MISC_BUFFS_CONFIG = [ ] as PickerStatOptions[]; export const WORLD_BUFFS_CONFIG = [ - // { - // config: RallyingCryOfTheDragonslayer, - // picker: IconPicker, - // stats: [ - // Stat.StatMeleeCrit, - // // TODO: Stat.StatRangedCrit, - // Stat.StatSpellCrit, - // Stat.StatAttackPower, - // ], - // }, { - config: DragonslayerBuffInput, - picker: MultiIconPicker, - stats: [], + config: RallyingCryOfTheDragonslayer, + picker: IconPicker, + stats: [Stat.StatMeleeCrit, Stat.StatSpellCrit, Stat.StatAttackPower], }, { config: SongflowerSerenade, diff --git a/ui/core/launched_sims.ts b/ui/core/launched_sims.ts index ce28e5eedd..142820e46e 100644 --- a/ui/core/launched_sims.ts +++ b/ui/core/launched_sims.ts @@ -58,7 +58,7 @@ export const simLaunchStatuses: Record = { }, [Spec.SpecHunter]: { phase: Phase.Phase1, - status: LaunchStatus.Unlaunched, + status: LaunchStatus.Alpha, }, [Spec.SpecMage]: { phase: Phase.Phase1, diff --git a/ui/core/talents/hunter_pet.ts b/ui/core/talents/hunter_pet.ts index 4712cdfde5..37aae6f50b 100644 --- a/ui/core/talents/hunter_pet.ts +++ b/ui/core/talents/hunter_pet.ts @@ -1,11 +1,9 @@ +import * as InputHelpers from '../components/input_helpers.js'; import { Player } from '../player.js'; import { Spec } from '../proto/common.js'; import { Hunter_Options_PetType as PetType } from '../proto/hunter.js'; import { ActionId } from '../proto_utils/action_id.js'; - -import * as InputHelpers from '../components/input_helpers.js'; - export function makePetTypeInputConfig(_: boolean): InputHelpers.TypedIconEnumPickerConfig, PetType> { return InputHelpers.makeSpecOptionsEnumIconInput({ fieldName: 'petType', @@ -15,7 +13,6 @@ export function makePetTypeInputConfig(_: boolean): InputHelpers.TypedIconEnumPi // TODO: Organize pets into phases maybe? { value: PetType.PetNone, tooltip: 'No Pet' }, { actionId: () => ActionId.fromPetName('Cat'), tooltip: 'Cat', value: PetType.Cat }, - { actionId: () => ActionId.fromPetName('Core Hound'), tooltip: 'Core Hound (Exotic)', value: PetType.CoreHound }, { actionId: () => ActionId.fromPetName('Raptor'), tooltip: 'Raptor', value: PetType.Raptor }, { actionId: () => ActionId.fromPetName('Owl'), tooltip: 'Owl', value: PetType.Owl }, { actionId: () => ActionId.fromPetName('Carrion Bird'), tooltip: 'Carrion Bird', value: PetType.CarrionBird }, diff --git a/ui/hunter/presets.ts b/ui/hunter/presets.ts index 519e5029c7..475badde84 100644 --- a/ui/hunter/presets.ts +++ b/ui/hunter/presets.ts @@ -86,10 +86,8 @@ export const DefaultOptions = HunterOptions.create({ ammo: Ammo.ThoriumHeadedArrow, quiverBonus: Hunter_Options_QuiverBonus.Speed15, petAttackSpeed: 2.0, - petTalents: {}, petType: PetType.PetNone, petUptime: 1, - sniperTrainingUptime: 1.0, }); export const DefaultConsumes = Consumes.create({ diff --git a/ui/index.html b/ui/index.html index 9baf77679e..6e34ace359 100644 --- a/ui/index.html +++ b/ui/index.html @@ -159,12 +159,12 @@

Classic

- + From e9e043a195fd72779129f346a2868291f64d4a29 Mon Sep 17 00:00:00 2001 From: sanguinerarogue Date: Mon, 30 Dec 2024 18:55:23 -0700 Subject: [PATCH 2/5] updates --- sim/hunter/hunter.go | 1 - sim/hunter/pet.go | 25 +++--------- sim/hunter/pet_abilities.go | 79 ++++--------------------------------- sim/hunter/talents.go | 5 ++- 4 files changed, 15 insertions(+), 95 deletions(-) diff --git a/sim/hunter/hunter.go b/sim/hunter/hunter.go index 2280c90b27..9afb4dc5e5 100644 --- a/sim/hunter/hunter.go +++ b/sim/hunter/hunter.go @@ -47,7 +47,6 @@ const ( SpellCode_HunterPetClaw SpellCode_HunterPetBite SpellCode_HunterPetLightningBreath - SpellCode_HunterPetLavaBreath SpellCode_HunterPetScreech SpellCode_HunterPetScorpidPoison ) diff --git a/sim/hunter/pet.go b/sim/hunter/pet.go index 7b8be4c271..9c1a4fcef3 100644 --- a/sim/hunter/pet.go +++ b/sim/hunter/pet.go @@ -61,8 +61,9 @@ func (hunter *Hunter) NewHunterPet() *HunterPet { attackSpeed = 2.5 } - baseMinDamage = 18.5 * attackSpeed - baseMaxDamage = 28.0 * attackSpeed + baseMinDamage = 18.17 * attackSpeed + baseMaxDamage = 27.66 * attackSpeed + hunterPetBaseStats = stats.Stats{ stats.Strength: 136, stats.Agility: 100, @@ -95,7 +96,6 @@ func (hunter *Hunter) NewHunterPet() *HunterPet { // Happiness hp.PseudoStats.DamageDealtMultiplier *= 1.25 - // This stuff probably need removedD? // Family scalars hp.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] *= hp.config.Damage hp.PseudoStats.ArmorMultiplier *= hp.config.Armor @@ -189,23 +189,8 @@ func (hp *HunterPet) ExecuteCustomRotation(sim *core.Simulation) { func (hunter *Hunter) makeStatInheritance() core.PetStatInheritance { return func(ownerStats stats.Stats) stats.Stats { - // EJ posts claim this value is passed through math.Floor, but in-game testing - // shows pets benefit from each point of owner hit rating in WotLK Classic. - // https://web.archive.org/web/20120112003252/http://elitistjerks.com/f80/t100099-demonology_releasing_demon_you - ownerHitChance := ownerStats[stats.MeleeHit] / core.MeleeHitRatingPerHitChance - hitRatingFromOwner := ownerHitChance * core.MeleeHitRatingPerHitChance - - return stats.Stats{ - stats.Stamina: ownerStats[stats.Stamina] * 0.3, - stats.Armor: ownerStats[stats.Armor] * 0.35, - stats.AttackPower: ownerStats[stats.RangedAttackPower] * 0.22, - - stats.MeleeCrit: ownerStats[stats.MeleeCrit], - stats.SpellCrit: ownerStats[stats.MeleeCrit], - - stats.MeleeHit: hitRatingFromOwner, - stats.SpellHit: hitRatingFromOwner * 2, - } + // No stat inheritance in classic + return stats.Stats{} } } diff --git a/sim/hunter/pet_abilities.go b/sim/hunter/pet_abilities.go index 2cbc36f193..e6604817e9 100644 --- a/sim/hunter/pet_abilities.go +++ b/sim/hunter/pet_abilities.go @@ -19,7 +19,6 @@ const ( FuriousHowl LightningBreath ScorpidPoison - LavaBreath ) func (hp *HunterPet) NewPetAbility(abilityType PetAbilityType, isPrimary bool) *core.Spell { @@ -38,8 +37,6 @@ func (hp *HunterPet) NewPetAbility(abilityType PetAbilityType, isPrimary bool) * return hp.newScorpidPoison() // case Swipe: // return hp.newSwipe() - case LavaBreath: - return hp.newLavaBreath() case Unknown: return nil default: @@ -69,8 +66,6 @@ func (hp *HunterPet) newClaw() *core.Spell { 60: 3009, }[hp.Owner.Level] - ApCoeff := 1.5 / 14 - return hp.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: spellID}, SpellCode: SpellCode_HunterPetClaw, @@ -94,7 +89,7 @@ func (hp *HunterPet) newClaw() *core.Spell { BonusCoefficient: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := sim.Roll(baseDamageMin, baseDamageMax) + (spell.MeleeAttackPower() * ApCoeff) + baseDamage := sim.Roll(baseDamageMin, baseDamageMax) spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) }, }) @@ -122,8 +117,6 @@ func (hp *HunterPet) newBite() *core.Spell { 60: 17261, }[hp.Owner.Level] - ApCoeff := 2.15/14 - return hp.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: spellID}, SpellCode: SpellCode_HunterPetBite, @@ -151,7 +144,7 @@ func (hp *HunterPet) newBite() *core.Spell { BonusCoefficient: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := sim.Roll(baseDamageMin, baseDamageMax) + (spell.MeleeAttackPower() * ApCoeff) + baseDamage := sim.Roll(baseDamageMin, baseDamageMax) spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) }, }) @@ -179,9 +172,6 @@ func (hp *HunterPet) newLightningBreath() *core.Spell { 60: 25012, }[hp.Owner.Level] - ApCoeff := 3.65/14 - SpCoeff := 0.429 - return hp.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: spellID}, SpellCode: SpellCode_HunterPetLightningBreath, @@ -201,10 +191,10 @@ func (hp *HunterPet) newLightningBreath() *core.Spell { DamageMultiplier: 1, ThreatMultiplier: 1, - BonusCoefficient: SpCoeff, + BonusCoefficient: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := sim.Roll(baseDamageMin, baseDamageMax) + (spell.MeleeAttackPower() * ApCoeff) + baseDamage := sim.Roll(baseDamageMin, baseDamageMax) spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) }, @@ -233,8 +223,6 @@ func (hp *HunterPet) newScreech() *core.Spell { 60: 24582, }[hp.Owner.Level] - ApCoeff := 1.15/14 - return hp.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: spellID}, SpellCode: SpellCode_HunterPetScreech, @@ -257,7 +245,7 @@ func (hp *HunterPet) newScreech() *core.Spell { ThreatMultiplier: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := sim.Roll(baseDamageMin, baseDamageMax) + (spell.MeleeAttackPower() * ApCoeff) + baseDamage := sim.Roll(baseDamageMin, baseDamageMax) // This ability also applies a melee attack power reduction similar to demoralizing shout - left it out for now spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) }, @@ -324,8 +312,6 @@ func (hp *HunterPet) newScorpidPoison() *core.Spell { 60: 24587, }[hp.Owner.Level] - ApCoeff := 0.07/5 - return hp.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: spellID}, SpellCode: SpellCode_HunterPetScorpidPoison, @@ -372,7 +358,7 @@ func (hp *HunterPet) newScorpidPoison() *core.Spell { dot.SnapshotBaseDamage = 0 } - dot.SnapshotBaseDamage += baseDamageTick + ApCoeff*dot.Spell.MeleeAttackPower() + dot.SnapshotBaseDamage += baseDamageTick }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) @@ -393,55 +379,4 @@ func (hp *HunterPet) newScorpidPoison() *core.Spell { } }, }) -} - -func (hp *HunterPet) newLavaBreath() *core.Spell { - baseDamageMin := map[int32]float64{ - 50: 78, - 60: 101, - }[hp.Owner.Level] - baseDamageMax := map[int32]float64{ - 50: 91, - 60: 116, - }[hp.Owner.Level] - spellID := map[int32]int32{ - 50: 444678, - 60: 444681, - }[hp.Owner.Level] - - ApCoeff := 3.65/14 - SpCoeff := 0.429 - - return hp.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: spellID}, - SpellCode: SpellCode_HunterPetLavaBreath, - SpellSchool: core.SpellSchoolFire, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagMeleeMetrics, - - FocusCost: core.FocusCostOptions{ - Cost: 50, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: PetGCD, - }, - IgnoreHaste: true, - }, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: SpCoeff, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - damage := sim.Roll(baseDamageMin, baseDamageMax) + ApCoeff*spell.MeleeAttackPower() - spell.CalcAndDealDamage(sim, target, damage, spell.OutcomeMagicHitAndCrit) - if sim.Environment.GetNumTargets() > 1 { - target = sim.Environment.NextTargetUnit(target) - spell.CalcAndDealDamage(sim, target, damage, spell.OutcomeMagicHitAndCrit) - } - - }, - }) -} +} \ No newline at end of file diff --git a/sim/hunter/talents.go b/sim/hunter/talents.go index 0d84056b8e..d27e57f0da 100644 --- a/sim/hunter/talents.go +++ b/sim/hunter/talents.go @@ -61,12 +61,13 @@ func (hunter *Hunter) ApplyTalents() { hunter.AddStat(stats.MeleeCrit, float64(hunter.Talents.KillerInstinct)*1*core.CritRatingPerCritChance) if hunter.Talents.LethalShots > 0 { + lethalBonus := 1*float64(hunter.Talents.LethalShots)*core.CritRatingPerCritChance for _, spell := range hunter.Shots { if spell != nil { - spell.BonusCritRating += 1*float64(hunter.Talents.LethalShots)*core.CritRatingPerCritChance + spell.BonusCritRating += lethalBonus } } - hunter.AutoAttacks.RangedConfig().BonusCritRating += 1*float64(hunter.Talents.LethalShots)*core.CritRatingPerCritChance + hunter.AutoAttacks.RangedConfig().BonusCritRating += lethalBonus } if hunter.Talents.RangedWeaponSpecialization > 0 { From c84f25ccb4d31e471e3a84797ec1eeb757860eac Mon Sep 17 00:00:00 2001 From: sanguinerarogue Date: Mon, 30 Dec 2024 23:21:53 -0700 Subject: [PATCH 3/5] multi/aimed fix --- sim/hunter/aimed_shot.go | 4 ++-- sim/hunter/multi_shot.go | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/sim/hunter/aimed_shot.go b/sim/hunter/aimed_shot.go index bfd7b47cee..e619f6eae1 100644 --- a/sim/hunter/aimed_shot.go +++ b/sim/hunter/aimed_shot.go @@ -39,8 +39,7 @@ func (hunter *Hunter) getAimedShotConfig(rank int, timer *core.Timer) core.Spell }, ModifyCast: func(sim *core.Simulation, spell *core.Spell, cast *core.Cast) { cast.CastTime = spell.CastTime() - // + 1ms to fix engine issue with clipping - hunter.AutoAttacks.DelayRangedUntil(sim, sim.CurrentTime+spell.CastTime() + time.Millisecond *1) + hunter.Unit.AutoAttacks.CancelAutoSwing(sim) }, IgnoreHaste: true, // Hunter GCD is locked at 1.5s CastTime: func(spell *core.Spell) time.Duration { @@ -63,6 +62,7 @@ func (hunter *Hunter) getAimedShotConfig(rank int, timer *core.Timer) core.Spell baseDamage result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeRangedHitAndCrit) + hunter.Unit.AutoAttacks.EnableAutoSwing(sim) spell.WaitTravelTime(sim, func(s *core.Simulation) { spell.DealDamage(sim, result) }) diff --git a/sim/hunter/multi_shot.go b/sim/hunter/multi_shot.go index 4f17bd23a0..7aaa59645a 100644 --- a/sim/hunter/multi_shot.go +++ b/sim/hunter/multi_shot.go @@ -38,8 +38,7 @@ func (hunter *Hunter) getMultiShotConfig(rank int, timer *core.Timer) core.Spell }, ModifyCast: func(sim *core.Simulation, spell *core.Spell, cast *core.Cast) { cast.CastTime = spell.CastTime() - // + 1ms to fix engine issue with clipping - hunter.AutoAttacks.DelayRangedUntil(sim, sim.CurrentTime+spell.CastTime() + time.Millisecond *1) + hunter.Unit.AutoAttacks.CancelAutoSwing(sim) }, IgnoreHaste: true, // Hunter GCD is locked at 1.5s CD: core.Cooldown{ @@ -72,7 +71,7 @@ func (hunter *Hunter) getMultiShotConfig(rank int, timer *core.Timer) core.Spell curTarget = sim.Environment.NextTargetUnit(curTarget) } - + hunter.Unit.AutoAttacks.EnableAutoSwing(sim) spell.WaitTravelTime(sim, func(s *core.Simulation) { for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { spell.DealDamage(sim, results[hitIndex]) @@ -80,7 +79,7 @@ func (hunter *Hunter) getMultiShotConfig(rank int, timer *core.Timer) core.Spell curTarget = sim.Environment.NextTargetUnit(curTarget) } }) - + }, } } From 3b2d271fc97930912248ed1a198211a6fffbc3f0 Mon Sep 17 00:00:00 2001 From: sanguinerarogue Date: Wed, 1 Jan 2025 15:30:12 -0700 Subject: [PATCH 4/5] Hunter Sets --- sim/core/debuffs.go | 18 ++ sim/hunter/TestP1Hunter.results | 123 +++++--- sim/hunter/_item_sets_pve.go | 481 -------------------------------- sim/hunter/aspects.go | 51 ++-- sim/hunter/hunter.go | 2 +- sim/hunter/item_sets_pve.go | 290 +++++++++++++++++++ sim/hunter/items.go | 35 +++ sim/hunter/rapid_fire.go | 4 - sim/hunter/serpent_sting.go | 1 - sim/rogue/items.go | 1 - ui/hunter/presets.ts | 10 +- 11 files changed, 458 insertions(+), 558 deletions(-) delete mode 100644 sim/hunter/_item_sets_pve.go create mode 100644 sim/hunter/item_sets_pve.go diff --git a/sim/core/debuffs.go b/sim/core/debuffs.go index 2b856a8db4..851b5f53f8 100644 --- a/sim/core/debuffs.go +++ b/sim/core/debuffs.go @@ -744,6 +744,24 @@ func HuntersMarkAura(target *Unit, points int32) *Aura { return aura } +func ExposeWeaknessAura(target *Unit) *Aura { + bonus := 450.0 + + aura := target.GetOrRegisterAura(Aura{ + ActionID: ActionID{SpellID: 23577}, + Label: "Expose Weakness", + Duration: time.Second * 7, + OnGain: func(aura *Aura, sim *Simulation) { + target.PseudoStats.BonusRangedAttackPowerTaken += bonus + }, + OnExpire: func(aura *Aura, sim *Simulation) { + target.PseudoStats.BonusRangedAttackPowerTaken -= bonus + }, + }) + + return aura +} + func DemoralizingRoarAura(target *Unit, points int32) *Aura { baseAPReduction := 138.0 diff --git a/sim/hunter/TestP1Hunter.results b/sim/hunter/TestP1Hunter.results index 8927f82ef7..8b0e088102 100644 --- a/sim/hunter/TestP1Hunter.results +++ b/sim/hunter/TestP1Hunter.results @@ -1,11 +1,11 @@ character_stats_results: { key: "TestP1Hunter-Phase1-CharacterStats-Default" value: { - final_stats: 264.385 - final_stats: 497.145 - final_stats: 490.25075 - final_stats: 217.58 - final_stats: 244.145 + final_stats: 240.35 + final_stats: 451.95 + final_stats: 445.6825 + final_stats: 197.8 + final_stats: 221.95 final_stats: 150 final_stats: 0 final_stats: 0 @@ -15,27 +15,27 @@ character_stats_results: { final_stats: 0 final_stats: 47.25 final_stats: 3 - final_stats: 28.19007 + final_stats: 27.8637 final_stats: 0 final_stats: 0 - final_stats: 1622.53 + final_stats: 1553.3 final_stats: 3 - final_stats: 31.39604 + final_stats: 25.54186 final_stats: 0 final_stats: 0 final_stats: 0 - final_stats: 4703.7 + final_stats: 4407 final_stats: 0 final_stats: 0 - final_stats: 3241.29 - final_stats: 1714.29 + final_stats: 3150.9 + final_stats: 1623.9 final_stats: 0 final_stats: 5 final_stats: 0 final_stats: 0 final_stats: 5 final_stats: 0 - final_stats: 6878.87795 + final_stats: 6406.4545 final_stats: 35 final_stats: 68 final_stats: 68 @@ -51,7 +51,7 @@ stat_weights_results: { key: "TestP1Hunter-Phase1-StatWeights-Default" value: { weights: 0 - weights: 0.27871 + weights: 0.13131 weights: 0 weights: 0 weights: 0 @@ -67,9 +67,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.09096 - weights: 6.3706 - weights: 2.00452 + weights: 0.08555 + weights: 1.45092 + weights: 1.46158 weights: 0 weights: 0 weights: 0 @@ -77,7 +77,6 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.04101 weights: 0 weights: 0 weights: 0 @@ -94,105 +93,155 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 + weights: 0 + } +} +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-BeastmasterArmor" + value: { + dps: 295.25177 + tps: 148.05127 + } +} +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-CryptstalkerArmor" + value: { + dps: 326.50148 + tps: 179.28647 } } dps_results: { key: "TestP1Hunter-Phase1-AllItems-DevilsaurEye-19991" value: { - dps: 486.76691 - tps: 162.94333 + dps: 295.59501 + tps: 148.39451 } } dps_results: { key: "TestP1Hunter-Phase1-AllItems-DevilsaurTooth-19992" value: { - dps: 481.62357 - tps: 159.59641 + dps: 293.0934 + tps: 145.25309 + } +} +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-DragonstalkerArmor" + value: { + dps: 305.55196 + tps: 158.34988 + } +} +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-GiantstalkerArmor" + value: { + dps: 302.92974 + tps: 155.72767 + } +} +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-Predator'sArmor" + value: { + dps: 296.76353 + tps: 149.55692 + } +} +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-Renataki'sCharmofBeasts-19953" + value: { + dps: 292.46447 + tps: 145.26397 + } +} +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-Striker'sGarb" + value: { + dps: 301.97156 + tps: 154.88863 } } dps_results: { key: "TestP1Hunter-Phase1-Average-Default" value: { - dps: 477.36962 - tps: 159.07056 + dps: 291.06476 + tps: 144.34627 } } dps_results: { key: "TestP1Hunter-Phase1-Settings-Dwarf-p0.bis-Hunter-p1-FullBuffs-P1-Consumes-LongMultiTarget" value: { - dps: 305.40016 + dps: 138.81564 } } dps_results: { key: "TestP1Hunter-Phase1-Settings-Dwarf-p0.bis-Hunter-p1-FullBuffs-P1-Consumes-LongSingleTarget" value: { - dps: 305.40016 + dps: 138.81564 } } dps_results: { key: "TestP1Hunter-Phase1-Settings-Dwarf-p0.bis-Hunter-p1-FullBuffs-P1-Consumes-ShortSingleTarget" value: { - dps: 320.75686 + dps: 143.24669 } } dps_results: { key: "TestP1Hunter-Phase1-Settings-Dwarf-p0.bis-Hunter-p1-NoBuffs-P1-Consumes-LongMultiTarget" value: { - dps: 82.02021 + dps: 44.29107 } } dps_results: { key: "TestP1Hunter-Phase1-Settings-Dwarf-p0.bis-Hunter-p1-NoBuffs-P1-Consumes-LongSingleTarget" value: { - dps: 82.02021 + dps: 44.29107 } } dps_results: { key: "TestP1Hunter-Phase1-Settings-Dwarf-p0.bis-Hunter-p1-NoBuffs-P1-Consumes-ShortSingleTarget" value: { - dps: 88.52133 + dps: 46.16053 } } dps_results: { key: "TestP1Hunter-Phase1-Settings-Orc-p0.bis-Hunter-p1-FullBuffs-P1-Consumes-LongMultiTarget" value: { - dps: 319.99574 + dps: 146.2664 } } dps_results: { key: "TestP1Hunter-Phase1-Settings-Orc-p0.bis-Hunter-p1-FullBuffs-P1-Consumes-LongSingleTarget" value: { - dps: 319.99574 + dps: 146.2664 } } dps_results: { key: "TestP1Hunter-Phase1-Settings-Orc-p0.bis-Hunter-p1-FullBuffs-P1-Consumes-ShortSingleTarget" value: { - dps: 336.90654 + dps: 151.46541 } } dps_results: { key: "TestP1Hunter-Phase1-Settings-Orc-p0.bis-Hunter-p1-NoBuffs-P1-Consumes-LongMultiTarget" value: { - dps: 86.22379 + dps: 46.50562 } } dps_results: { key: "TestP1Hunter-Phase1-Settings-Orc-p0.bis-Hunter-p1-NoBuffs-P1-Consumes-LongSingleTarget" value: { - dps: 86.22379 + dps: 46.50562 } } dps_results: { key: "TestP1Hunter-Phase1-Settings-Orc-p0.bis-Hunter-p1-NoBuffs-P1-Consumes-ShortSingleTarget" value: { - dps: 93.06899 + dps: 48.46856 } } dps_results: { key: "TestP1Hunter-Phase1-SwitchInFrontOfTarget-Default" value: { - dps: 457.42663 - tps: 136.77363 + dps: 270.50999 + tps: 123.30949 } } diff --git a/sim/hunter/_item_sets_pve.go b/sim/hunter/_item_sets_pve.go deleted file mode 100644 index 2802ac1327..0000000000 --- a/sim/hunter/_item_sets_pve.go +++ /dev/null @@ -1,481 +0,0 @@ -package hunter - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" - "github.com/wowsims/classic/sim/core/stats" -) - -/////////////////////////////////////////////////////////////////////////// -// SoD Phase 3 Item Sets -/////////////////////////////////////////////////////////////////////////// - -var ItemSetDreadHuntersChain = core.NewItemSet(core.ItemSet{ - Name: "Dread Hunter's Chain", - Bonuses: map[int32]core.ApplyEffect{ - 2: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.AttackPower, 20) - c.AddStat(stats.RangedAttackPower, 20) - }, - 3: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.MeleeCrit, 1*core.CritRatingPerCritChance) - }, - }, -}) - -/////////////////////////////////////////////////////////////////////////// -// SoD Phase 4 Item Sets -/////////////////////////////////////////////////////////////////////////// - -var ItemSetBeastmasterArmor = core.NewItemSet(core.ItemSet{ - Name: "Beastmaster Armor", - Bonuses: map[int32]core.ApplyEffect{ - // +40 Attack Power. - 2: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStats(stats.Stats{ - stats.AttackPower: 40, - stats.RangedAttackPower: 40, - }) - }, - // Your melee and ranged autoattacks have a 6% chance to energize you for 300 mana. - 4: func(agent core.Agent) { - c := agent.GetCharacter() - actionID := core.ActionID{SpellID: 450577} - manaMetrics := c.NewManaMetrics(actionID) - - core.MakeProcTriggerAura(&c.Unit, core.ProcTrigger{ - ActionID: actionID, - Name: "S03 - Mana Proc on Cast - Beaststalker Armor", - Callback: core.CallbackOnSpellHitDealt, - Outcome: core.OutcomeLanded, - ProcMask: core.ProcMaskWhiteHit, - ProcChance: 0.06, - Handler: func(sim *core.Simulation, spell *core.Spell, _ *core.SpellResult) { - if c.HasManaBar() { - c.AddMana(sim, 300, manaMetrics) - } - }, - }) - }, - // +8 All Resistances. - 6: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddResistances(8) - }, - // +200 Armor. - 8: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Armor, 200) - }, - }, -}) - -var ItemSetGiantstalkerProwess = core.NewItemSet(core.ItemSet{ - Name: "Giantstalker Prowess", - Bonuses: map[int32]core.ApplyEffect{ - // Your Mongoose Bite also reduces its target's chance to Dodge by 1% and increases your chance to hit by 1% for 30 sec. - 2: func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - - procBonus := stats.Stats{ - stats.SpellHit: 1, - stats.MeleeHit: 1, - } - - stalkerAura := hunter.RegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 458403}, - Label: "Stalker", - Duration: time.Second * 30, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.AddStatsDynamic(sim, procBonus) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.AddStatsDynamic(sim, procBonus.Invert()) - }, - }) - - debuffAuras := hunter.NewEnemyAuraArray(core.MeleeHunterDodgeReductionAura) - core.MakePermanent(hunter.RegisterAura(core.Aura{ - Label: "S03 - Item - T1 - Hunter - Melee 2P Bonus Trigger", - OnSpellHitDealt: func(_ *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.SpellCode == SpellCode_HunterMongooseBite && result.Landed() { - debuffAuras.Get(result.Target).Activate(sim) - stalkerAura.Activate(sim) - } - }, - })) - }, - // While tracking a creature type, you deal 3% increased damage to that creature type. - // Unsure if this stacks with the Pursuit 4p - 4: func(agent core.Agent) { - c := agent.GetCharacter() - // Just adding 3% damage to assume the hunter is tracking their target's type - c.PseudoStats.DamageDealtMultiplier *= 1.03 - }, - // Mongoose Bite also activates for 5 sec whenever your target Parries or Blocks or when your melee attack misses. - 6: func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - core.MakePermanent(hunter.RegisterAura(core.Aura{ - Label: "S03 - Item - T1 - Hunter - Melee 6P Bonus Trigger", - OnSpellHitDealt: func(_ *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.ProcMask.Matches(core.ProcMaskMelee) && (result.Outcome == core.OutcomeMiss || result.Outcome == core.OutcomeBlock || result.Outcome == core.OutcomeParry) { - hunter.DefensiveState.Activate(sim) - } - }, - })) - }, - }, -}) - -var ItemSetGiantstalkerPursuit = core.NewItemSet(core.ItemSet{ - Name: "Giantstalker Pursuit", - Bonuses: map[int32]core.ApplyEffect{ - // You generate 100% more threat for 8 sec after using Distracting Shot. - 2: func(agent core.Agent) { - // Nothing to do - }, - // While tracking a creature type, you deal 3% increased damage to that creature type. - // Unsure if this stacks with the Prowess 4p - 4: func(agent core.Agent) { - c := agent.GetCharacter() - // Just adding 3% damage to assume the hunter is tracking their target's type - c.PseudoStats.DamageDealtMultiplier *= 1.03 - }, - // Your next Shot ability within 12 sec after Aimed Shot deals 20% more damage. - 6: func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - - if !hunter.Talents.AimedShot { - return - } - - procAura := hunter.RegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 456379}, - Label: "S03 - Item - T1 - Hunter - Ranged 6P Bonus", - Duration: time.Second * 12, - - OnGain: func(aura *core.Aura, sim *core.Simulation) { - for _, spell := range hunter.Shots { - if spell != nil { - spell.DamageMultiplier *= 1.20 - } - } - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - for _, spell := range hunter.Shots { - if spell != nil { - spell.DamageMultiplier /= 1.20 - } - } - }, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - if !spell.Flags.Matches(SpellFlagShot) || (aura.RemainingDuration(sim) == aura.Duration && spell.SpellCode == SpellCode_HunterAimedShot) { - return - } - - aura.Deactivate(sim) - }, - }) - - core.MakePermanent(hunter.RegisterAura(core.Aura{ - Label: "S03 - Item - T1 - Hunter - Ranged 6P Bonus Trigger", - OnCastComplete: func(_ *core.Aura, sim *core.Simulation, spell *core.Spell) { - if spell.SpellCode == SpellCode_HunterAimedShot { - procAura.Activate(sim) - } - }, - })) - }, - }, -}) - -var ItemSetDragonstalkerProwess = core.NewItemSet(core.ItemSet{ - Name: "Dragonstalker's Prowess", - Bonuses: map[int32]core.ApplyEffect{ - // Raptor Strike increases the damage done by your next other melee ability within 5 sec by 20%. - 2: func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - - affectedSpells := make(map[*core.Spell]bool) - - procAura := hunter.RegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 467331}, - Label: "Clever Strikes", - Duration: time.Second * 5, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - for _, spell := range hunter.MeleeSpells { - if spell.SpellCode != SpellCode_HunterRaptorStrikeHit && spell.SpellCode != SpellCode_HunterRaptorStrike && spell.SpellCode != SpellCode_HunterWingClip { - affectedSpells[spell] = true - } - } - }, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - for spell := range affectedSpells { - spell.DamageMultiplier *= 1.20 - } - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - for spell := range affectedSpells { - spell.DamageMultiplier /= 1.20 - } - }, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - if !affectedSpells[spell] { - return - } - - aura.Deactivate(sim) - }, - }) - - core.MakePermanent(hunter.RegisterAura(core.Aura{ - Label: "S03 - Item - T2 - Hunter - Melee 2P Bonus Trigger", - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.SpellCode == SpellCode_HunterRaptorStrikeHit { - procAura.Activate(sim) - } - }, - })) - }, - // Increases damage dealt by your main hand weapon with Raptor Strike and Wyvern Strike by 20%. - 4: func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - - hunter.OnSpellRegistered(func(spell *core.Spell) { - if spell.SpellCode == SpellCode_HunterWyvernStrike || (spell.SpellCode == SpellCode_HunterRaptorStrikeHit && spell.ProcMask.Matches(core.ProcMaskMeleeMHSpecial)) { - spell.DamageMultiplier *= 1.20 - } - }) - }, - // Your periodic damage has a 5% chance to reset the cooldown on one of your Strike abilities. The Strike with the longest remaining cooldown is always chosen. - 6: func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - core.MakePermanent(hunter.RegisterAura(core.Aura{ - Label: "S03 - Item - T2 - Hunter - Melee 6P Bonus Trigger", - ActionID: core.ActionID{SpellID: 467334}, - OnPeriodicDamageDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if sim.Proc(0.05, "T2 Melee 6PC Strike Reset") { - maxSpell := hunter.RaptorStrike - - for _, strike := range hunter.Strikes { - if strike.TimeToReady(sim) > maxSpell.TimeToReady(sim) { - maxSpell = strike - } - } - - maxSpell.CD.Reset() - aura.Activate(sim) // used for metrics - } - }, - })) - }, - }, -}) - -var ItemSetDragonstalkerPursuit = core.NewItemSet(core.ItemSet{ - Name: "Dragonstalker's Pursuit", - Bonuses: map[int32]core.ApplyEffect{ - // Your Aimed Shot deals 20% more damage to targets afflicted by one of your trap effects. - 2: func(agent core.Agent) { - // Implemented in aimed_shot.go - }, - // Your damaging Shot abilities deal 10% increased damage if the previous damaging Shot used was different than the current one. - 4: func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - - shotSpells := []*core.Spell{} - procAura := hunter.RegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 467312}, - Label: "S03 - Item - T2 - Hunter - Ranged 4P Bonus", - Duration: time.Second * 12, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - shotSpells = core.FilterSlice(hunter.Shots, func(s *core.Spell) bool { return s != nil }) - }, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - for _, spell := range shotSpells { - if spell.SpellCode != hunter.LastShot.SpellCode { - spell.DamageMultiplier *= 1.10 - } - } - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - for _, spell := range shotSpells { - if spell.SpellCode != hunter.LastShot.SpellCode { - spell.DamageMultiplier /= 1.10 - } - } - }, - }) - - core.MakePermanent(hunter.RegisterAura(core.Aura{ - Label: "S03 - Item - T2 - Hunter - Ranged 4P Bonus Trigger", - OnCastComplete: func(_ *core.Aura, sim *core.Simulation, spell *core.Spell) { - if spell.Flags.Matches(SpellFlagShot) { - procAura.Deactivate(sim) - hunter.LastShot = spell - procAura.Activate(sim) - } - }, - })) - }, - // Your Serpent Sting damage is increased by 25% of your Attack Power over its normal duration. - 6: func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - core.MakePermanent(hunter.RegisterAura(core.Aura{ - Label: "S03 - Item - T2 - Hunter - Ranged 6P Bonus", - OnInit: func(aura *core.Aura, sim *core.Simulation) { - hunter.SerpentStingAPCoeff += 0.25 - }, - })) - }, - }, -}) - -var ItemSetPredatorArmor = core.NewItemSet(core.ItemSet{ - Name: "Predator's Armor", - Bonuses: map[int32]core.ApplyEffect{ - // +20 Attack Power. - 2: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.AttackPower, 20) - c.AddStat(stats.RangedAttackPower, 20) - }, - // Increases the Attack Power your Beast pet gains from your attributes by 20%. - 3: func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - if hunter.pet == nil { - return - } - - core.MakePermanent(hunter.RegisterAura(core.Aura{ - Label: "Predator's Armor 3P", - OnInit: func(aura *core.Aura, sim *core.Simulation) { - oldStatInheritance := hunter.pet.GetStatInheritance() - hunter.pet.UpdateStatInheritance( - func(ownerStats stats.Stats) stats.Stats { - s := oldStatInheritance(ownerStats) - s[stats.AttackPower] *= 1.20 - return s - }, - ) - }, - })) - }, - // Increases the Focus regeneration of your Beast pet by 20%. - 5: func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - if hunter.pet == nil { - return - } - - hunter.RegisterAura(core.Aura{ - Label: "Predator's Armor 5P", - OnInit: func(aura *core.Aura, sim *core.Simulation) { - hunter.pet.AddFocusRegenMultiplier(0.20) - }, - }) - }, - }, -}) - -var TrappingsOfTheUnseenPath = core.NewItemSet(core.ItemSet{ - Name: "Trappings of the Unseen Path", - Bonuses: map[int32]core.ApplyEffect{ - // Increases the Focus regeneration of your Beast pet by 100%. - 3: func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - if hunter.pet == nil { - return - } - - hunter.RegisterAura(core.Aura{ - Label: "Trappings of the Unseen Path 3P", - OnInit: func(aura *core.Aura, sim *core.Simulation) { - hunter.pet.AddFocusRegenMultiplier(1.00) - }, - }) - }, - }, -}) - -var StrikersProwess = core.NewItemSet(core.ItemSet{ - Name: "Striker's Prowess", - Bonuses: map[int32]core.ApplyEffect{ - // Increases Wyvern Strike DoT by 50% - 2: func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - if !hunter.Talents.WyvernSting || !hunter.HasRune(proto.HunterRune_RuneBootsWyvernStrike) { - return - } - - hunter.RegisterAura(core.Aura{ - Label: "Striker's Prowess 2P", - OnInit: func(aura *core.Aura, sim *core.Simulation) { - hunter.WyvernStrike.PeriodicDamageMultiplierAdditive += 0.50 - }, - }) - }, - // Increases the Impact Damage of Mongoose Bite and all Strikes by 10% - 4: func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - hunter.RegisterAura(core.Aura{ - Label: "Striker's Prowess 4P", - OnInit: func(aura *core.Aura, sim *core.Simulation) { - for _, spell := range hunter.Strikes { - spell.ImpactDamageMultiplierAdditive += 0.10 - } - hunter.RaptorStrikeMH.ImpactDamageMultiplierAdditive += 0.10 - hunter.RaptorStrikeOH.ImpactDamageMultiplierAdditive += 0.10 - hunter.MongooseBite.ImpactDamageMultiplierAdditive += 0.10 - }, - }) - }, - }, -}) - -var StrikersPursuit = core.NewItemSet(core.ItemSet{ - Name: "Striker's Pursuit", - Bonuses: map[int32]core.ApplyEffect{ - // Kill Shot's remaining cooldown is reduced by 50% when used on targets between 20% and 50% health, and has no cooldown while your Rapid Fire is active - 2: func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - if !hunter.HasRune(proto.HunterRune_RuneLegsKillShot) { - return - } - - core.MakePermanent(hunter.RegisterAura(core.Aura{ - Label: "Striker's Pursuit 2P", - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - if spell.SpellCode != SpellCode_HunterKillShot { - return - } - - if hunter.HasActiveAura("Rapid Fire") { - spell.CD.Reset() - } else if sim.CurrentTime > sim.Encounter.Duration/2 { - spell.CD.Set(sim.CurrentTime + spell.CD.TimeToReady(sim)/2) - } - }, - })) - }, - // Increases Kill Shot damage by 50% - 4: func(agent core.Agent) { - hunter := agent.(HunterAgent).GetHunter() - if !hunter.HasRune(proto.HunterRune_RuneLegsKillShot) { - return - } - - hunter.RegisterAura(core.Aura{ - Label: "Striker's Pursuit 4P", - OnInit: func(aura *core.Aura, sim *core.Simulation) { - hunter.KillShot.DamageMultiplier *= 1.50 - }, - }) - }, - }, -}) diff --git a/sim/hunter/aspects.go b/sim/hunter/aspects.go index c77d24448a..02a8c6f3fa 100644 --- a/sim/hunter/aspects.go +++ b/sim/hunter/aspects.go @@ -3,31 +3,22 @@ package hunter import ( "strconv" "time" - "github.com/wowsims/classic/sim/core" "github.com/wowsims/classic/sim/core/stats" ) // Utility function to create an Improved Hawk Aura -func (hunter *Hunter) createImprovedHawkAura(auraLabel string, actionID core.ActionID, isMelee bool) *core.Aura { +func (hunter *Hunter) createImprovedHawkAura(auraLabel string, actionID core.ActionID) *core.Aura { bonusMultiplier := 1.3 return hunter.GetOrRegisterAura(core.Aura{ Label: auraLabel, ActionID: actionID, Duration: time.Second * 12, OnGain: func(aura *core.Aura, sim *core.Simulation) { - if isMelee { - aura.Unit.MultiplyMeleeSpeed(sim, bonusMultiplier) - } else { - aura.Unit.MultiplyRangedSpeed(sim, bonusMultiplier) - } + aura.Unit.MultiplyRangedSpeed(sim, bonusMultiplier) }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { - if isMelee { - aura.Unit.MultiplyMeleeSpeed(sim, 1/bonusMultiplier) - } else { - aura.Unit.MultiplyRangedSpeed(sim, 1/bonusMultiplier) - } + aura.Unit.MultiplyRangedSpeed(sim, 1/bonusMultiplier) }, }) } @@ -69,30 +60,33 @@ func (hunter *Hunter) getAspectOfTheHawkSpellConfig(rank int) core.SpellConfig { impHawkAura = hunter.createImprovedHawkAura( "Quick Shots", core.ActionID{SpellID: 6150}, - false, // Ranged ) } // Use utility function to get the attack power based on rank rap := hunter.getMaxAspectOfTheHawkAttackPower(rank) actionID := core.ActionID{SpellID: spellId} - aspectOfTheHawkAura := hunter.NewTemporaryStatsAuraWrapped( - "Aspect of the Hawk"+strconv.Itoa(rank), - actionID, - stats.Stats{stats.RangedAttackPower: rap}, - core.NeverExpires, - func(aura *core.Aura) { - aura.OnSpellHitDealt = func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if !spell.ProcMask.Matches(core.ProcMaskRangedAuto) { - return - } - - if impHawkAura != nil && sim.Proc(improvedHawkProcChance, "Imp Aspect of the Hawk") { - impHawkAura.Activate(sim) - } + aspectOfTheHawkAura := hunter.GetOrRegisterAura(core.Aura{ + Label: "Aspect of the Hawk"+strconv.Itoa(rank), + ActionID: actionID, + Duration: core.NeverExpires, + OnGain: func(aura *core.Aura, sim *core.Simulation) { + aura.Unit.AddStatDynamic(sim, stats.RangedAttackPower, rap * hunter.AspectOfTheHawkAPMultiplier) + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + aura.Unit.AddStatDynamic(sim, stats.RangedAttackPower, -rap * hunter.AspectOfTheHawkAPMultiplier) + }, + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if !spell.ProcMask.Matches(core.ProcMaskRangedAuto) { + return } - }) + if impHawkAura != nil && sim.Proc(improvedHawkProcChance, "Imp Aspect of the Hawk") { + impHawkAura.Activate(sim) + } + }, + }) + aspectOfTheHawkAura.NewExclusiveEffect("Aspect", true, core.ExclusiveEffect{}) return core.SpellConfig{ @@ -117,6 +111,7 @@ func (hunter *Hunter) getAspectOfTheHawkSpellConfig(rank int) core.SpellConfig { } func (hunter *Hunter) registerAspectOfTheHawkSpell() { + hunter.AspectOfTheHawkAPMultiplier = 1.0 maxRank := hunter.getMaxHawkRank() config := hunter.getAspectOfTheHawkSpellConfig(maxRank) hunter.GetOrRegisterSpell(config) diff --git a/sim/hunter/hunter.go b/sim/hunter/hunter.go index 9afb4dc5e5..8aa9b10a68 100644 --- a/sim/hunter/hunter.go +++ b/sim/hunter/hunter.go @@ -81,7 +81,7 @@ type Hunter struct { NormalizedAmmoDamageBonus float64 // Miscellaneous set bonuses that require extra logic inside of spells - SerpentStingAPCoeff float64 + AspectOfTheHawkAPMultiplier float64 curQueueAura *core.Aura curQueuedAutoSpell *core.Spell diff --git a/sim/hunter/item_sets_pve.go b/sim/hunter/item_sets_pve.go new file mode 100644 index 0000000000..e0eb28210d --- /dev/null +++ b/sim/hunter/item_sets_pve.go @@ -0,0 +1,290 @@ +package hunter + +import ( + "time" + "github.com/wowsims/classic/sim/core" + "github.com/wowsims/classic/sim/core/stats" +) + + +/////////////////////////////////////////////////////////////////////////// +// Phase 1 Item Sets - Molten Core +/////////////////////////////////////////////////////////////////////////// + +var ItemSetGiantStalkers = core.NewItemSet(core.ItemSet{ + Name: "Giantstalker Armor", + Bonuses: map[int32]core.ApplyEffect{ + // (3) Set: Increases the range of your Mend Pet spell by 50% and the effect by 10%. Also reduces the cost by 30%. + 3: func(agent core.Agent) { + // Not implemented in sim + }, + // (5) Set: Increases your pet's stamina by 30 and all spell resistances by 40. + 5: func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + if hunter.pet == nil { + return + } + core.MakePermanent(hunter.RegisterAura(core.Aura{ + Label: "Nature's Ally", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + hunter.pet.AddStatDynamic(sim, stats.Stamina, 30) + hunter.pet.AddResistancesDynamic(sim, 40) + }, + })) + }, + // (8) Set: Increases the damage of Multi-shot and Volley by 15%. + 8: func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + hunter.RegisterAura(core.Aura{ + Label: "Improved Volley and Multishot", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + hunter.Volley.BaseDamageMultiplierAdditive += 0.15 + hunter.MultiShot.BaseDamageMultiplierAdditive += 0.15 + }, + }) + }, + }, +}) + +/////////////////////////////////////////////////////////////////////////// +// Phase 2 Item Sets - Dire Maul +/////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// Phase 3 Item Sets - BWL +/////////////////////////////////////////////////////////////////////////// + +var ItemSetDragonstalkersArmor = core.NewItemSet(core.ItemSet{ + Name: "Dragonstalker Armor", + Bonuses: map[int32]core.ApplyEffect{ + // (3) Set: Increases the Ranged Attack Power bonus of your Aspect of the Hawk by 20%. + 3: func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + core.MakePermanent(hunter.RegisterAura(core.Aura{ + Label: "Improved Aspect of the Hawk", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + hunter.AspectOfTheHawkAPMultiplier += 0.25 + }, + })) + }, + // (5) Set: Increases your pet's stamina by 40 and all spell resistances by 60. + 5: func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + if hunter.pet == nil { + return + } + + core.MakePermanent(hunter.RegisterAura(core.Aura{ + Label: "Nature's Ally", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + hunter.pet.AddStatDynamic(sim, stats.Stamina, 40) + hunter.pet.AddResistancesDynamic(sim, 60) + }, + })) + }, + // (8) Set: You have a chance whenever you deal ranged damage to apply an Expose Weakness effect to the target. Expose Weakness increases the Ranged Attack Power of all attackers against that target by 450 for 7 sec. + 8: func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + + debuffAuras := hunter.NewEnemyAuraArray(core.ExposeWeaknessAura) + + core.MakeProcTriggerAura(&hunter.Unit, core.ProcTrigger{ + Name: "T2 - Hunter - Ranged 8P Bonus Trigger", + Callback: core.CallbackOnSpellHitDealt, + Outcome: core.OutcomeLanded, + ProcMask: core.ProcMaskRanged, + PPM: 0.5, + Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + debuffAuras.Get(result.Target).Activate(sim) + }, + }) + }, + }, +}) + +var ItemSetPredatorsArmor = core.NewItemSet(core.ItemSet{ + Name: "Predator's Armor", + Bonuses: map[int32]core.ApplyEffect{ + // (2) Set: +20 Attack Power. + 2: func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + hunter.AddStats(stats.Stats{ + stats.AttackPower: 20, + stats.RangedAttackPower: 20, + }) + }, + // (3) Set: Decreases the cooldown of Concussive Shot by 1 sec. + 3: func(agent core.Agent) { + // Concussive Shot not implemented in sim + }, + // (5) Set: Increases the duration of Serpent Sting by 3 sec. + 5: func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + + core.MakePermanent(hunter.RegisterAura(core.Aura{ + Label: "Improved Serpent Sting", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + for _, dot := range hunter.SerpentSting.Dots() { + if dot != nil { + dot.NumberOfTicks += 1 + dot.RecomputeAuraDuration() + } + } + }, + })) + }, + }, +}) + +/////////////////////////////////////////////////////////////////////////// +// Phase 4 Item Sets - AQ +/////////////////////////////////////////////////////////////////////////// + +// hhttps://www.wowhead.com/classic/item-set=515/beastmaster-armor +var ItemSetBeastmasterArmor = core.NewItemSet(core.ItemSet{ + Name: "Beastmaster Armor", + Bonuses: map[int32]core.ApplyEffect{ + // +8 All Resistances. + 2: func(agent core.Agent) { + c := agent.GetCharacter() + c.AddResistances(8) + }, + // Your normal ranged attacks have a 4% chance of restoring 200 mana. + 4: func(agent core.Agent) { + c := agent.GetCharacter() + actionID := core.ActionID{SpellID: 27785} + manaMetrics := c.NewManaMetrics(actionID) + + core.MakeProcTriggerAura(&c.Unit, core.ProcTrigger{ + ActionID: actionID, + Name: "Hunter Armor Energize", + Callback: core.CallbackOnSpellHitDealt, + Outcome: core.OutcomeLanded, + ProcMask: core.ProcMaskWhiteHit, + ProcChance: 0.04, + Handler: func(sim *core.Simulation, spell *core.Spell, _ *core.SpellResult) { + if c.HasManaBar() { + c.AddMana(sim, 200, manaMetrics) + } + }, + }) + }, + // +40 Attack Power. + 6: func(agent core.Agent) { + c := agent.GetCharacter() + c.AddStats(stats.Stats{ + stats.AttackPower: 40, + stats.RangedAttackPower: 40, + }) + }, + // +200 Armor. + 8: func(agent core.Agent) { + c := agent.GetCharacter() + c.AddStat(stats.Armor, 200) + }, + }, +}) + +// https://www.wowhead.com/classic/item-set=509/strikers-garb +var ItemSetStrikersGarb = core.NewItemSet(core.ItemSet{ + Name: "Striker's Garb", + Bonuses: map[int32]core.ApplyEffect{ + // (3) Set : Reduces the cost of your Arcane Shots by 10%. + 3: func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + core.MakePermanent(hunter.RegisterAura(core.Aura{ + Label: "Striker's Arcane Shot Bonus", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + if hunter.ArcaneShot != nil { + hunter.ArcaneShot.Cost.Multiplier -= 10.0 + } + }, + })) + }, + // (5) Set : Reduces the cooldown of your Rapid Fire ability by 2 minutes. + 5: func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + core.MakePermanent(hunter.RegisterAura(core.Aura{ + Label: "Striker's Rapid Bonus", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + hunter.RapidFire.CD.Duration -= time.Minute * 2 + }, + })) + }, + }, +}) + +/////////////////////////////////////////////////////////////////////////// +// Phase 5 Item Sets - Naxx +/////////////////////////////////////////////////////////////////////////// + +// https://www.wowhead.com/classic/item-set=530/cryptstalker-armor +var ItemSetCryptstalkerArmor = core.NewItemSet(core.ItemSet{ + Name: "Cryptstalker Armor", + Bonuses: map[int32]core.ApplyEffect{ + // (2) Set : Increases the duration of your Rapid Fire by 4 secs. + 2: func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + core.MakePermanent(hunter.RegisterAura(core.Aura{ + Label: "Rapid Fire Duration", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + hunter.RapidFireAura.Duration += time.Second * 4 + }, + })) + }, + // (4) Set : Increases Attack Power by 50 for both you and your pet. + 4: func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + hunter.AddStats(stats.Stats{ + stats.AttackPower: 50, + stats.RangedAttackPower: 50, + }) + if hunter.pet == nil { + return + } + + core.MakePermanent(hunter.RegisterAura(core.Aura{ + Label: "Stalker's Ally", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + hunter.pet.AddStatsDynamic(sim, stats.Stats{ + stats.AttackPower: 50, + stats.RangedAttackPower: 50, + }) + }, + })) + }, + // (6) Set : Your ranged critical hits cause an Adrenaline Rush, granting you 50 mana. + 6: func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + actionID := core.ActionID{SpellID: 28753} + manaMetrics := hunter.NewManaMetrics(actionID) + + hunter.RegisterAura(core.Aura{ + Label: "Adrenaline Rush", + Duration: core.NeverExpires, + OnReset: func(aura *core.Aura, sim *core.Simulation) { + aura.Activate(sim) + }, + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if spell.ProcMask.Matches(core.ProcMaskRanged) && result.DidCrit() { + hunter.AddMana(sim, 50, manaMetrics) + } + }, + }) + + }, + // (8) Set : Reduces the mana cost of your Multi-Shot and Aimed Shot by 20. + 8: func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + core.MakePermanent(hunter.RegisterAura(core.Aura{ + Label: "Cryptstalker Aimed and Multishot Bonus", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + if hunter.AimedShot != nil { + hunter.AimedShot.Cost.FlatModifier -= 20.0 + } + hunter.MultiShot.Cost.FlatModifier -= 20.0 + }, + })) + }, + }, +}) diff --git a/sim/hunter/items.go b/sim/hunter/items.go index b1056bc9db..384b4680c1 100644 --- a/sim/hunter/items.go +++ b/sim/hunter/items.go @@ -8,11 +8,46 @@ import ( ) const ( + RenatakisCharmofBeasts = 19953 DevilsaurEye = 19991 DevilsaurTooth = 19992 ) func init() { + // Use: Instantly clears the cooldowns of Aimed Shot, Multishot, Volley, and Arcane Shot. (cooldown 3 min) + core.NewItemEffect(RenatakisCharmofBeasts, func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + + spell := hunter.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{ItemID: RenatakisCharmofBeasts}, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagOffensiveEquipment, + + Cast: core.CastConfig{ + CD: core.Cooldown{ + Timer: hunter.NewTimer(), + Duration: time.Second * 180, + }, + SharedCD: core.Cooldown{ + Timer: hunter.GetOffensiveTrinketCD(), + Duration: time.Second * 10, + }, + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + hunter.AimedShot.CD.Reset() + hunter.MultiShot.CD.Reset() + hunter.Volley.CD.Reset() + hunter.ArcaneShot.CD.Reset() + }, + }) + + hunter.AddMajorCooldown(core.MajorCooldown{ + Type: core.CooldownTypeDPS, + Spell: spell, + }) + }) + core.NewItemEffect(DevilsaurEye, func(agent core.Agent) { hunter := agent.(HunterAgent).GetHunter() diff --git a/sim/hunter/rapid_fire.go b/sim/hunter/rapid_fire.go index 92d71ec25f..c43b48f4e2 100644 --- a/sim/hunter/rapid_fire.go +++ b/sim/hunter/rapid_fire.go @@ -38,10 +38,6 @@ func (hunter *Hunter) registerRapidFire() { Timer: hunter.NewTimer(), Duration: cooldown, }, - SharedCD: core.Cooldown{ - Timer: hunter.GetAttackSpeedBuffCD(), - Duration: cooldown, - }, }, ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { diff --git a/sim/hunter/serpent_sting.go b/sim/hunter/serpent_sting.go index 1490c3fba7..f3fdeb7db3 100644 --- a/sim/hunter/serpent_sting.go +++ b/sim/hunter/serpent_sting.go @@ -76,7 +76,6 @@ func (hunter *Hunter) getSerpentStingConfig(rank int) core.SpellConfig { } func (hunter *Hunter) registerSerpentStingSpell() { - hunter.SerpentStingAPCoeff = 0 maxRank := core.TernaryInt(core.IncludeAQ, 9, 8) for rank := maxRank; rank >= 0; rank-- { diff --git a/sim/rogue/items.go b/sim/rogue/items.go index 3e74b62f48..c764751ab5 100644 --- a/sim/rogue/items.go +++ b/sim/rogue/items.go @@ -49,7 +49,6 @@ func init() { return rogue.CurrentEnergy() <= 40 }, }) - }) // https://www.wowhead.com/classic/item=230250/venomous-totem diff --git a/ui/hunter/presets.ts b/ui/hunter/presets.ts index 475badde84..1b95d26d4b 100644 --- a/ui/hunter/presets.ts +++ b/ui/hunter/presets.ts @@ -120,7 +120,7 @@ export const DefaultRaidBuffs = RaidBuffs.create({ fireResistanceTotem: true, giftOfTheWild: TristateEffect.TristateEffectImproved, graceOfAirTotem: TristateEffect.TristateEffectImproved, - leaderOfThePack: true, + leaderOfThePack: false, manaSpringTotem: TristateEffect.TristateEffectRegular, powerWordFortitude: TristateEffect.TristateEffectImproved, strengthOfEarthTotem: TristateEffect.TristateEffectImproved, @@ -131,13 +131,13 @@ export const DefaultIndividualBuffs = IndividualBuffs.create({ blessingOfMight: TristateEffect.TristateEffectRegular, blessingOfWisdom: TristateEffect.TristateEffectImproved, fengusFerocity: true, - moldarsMoxie: true, + moldarsMoxie: false, rallyingCryOfTheDragonslayer: true, saygesFortune: SaygesFortune.SaygesDamage, - slipkiksSavvy: true, + slipkiksSavvy: false, songflowerSerenade: true, - spiritOfZandalar: true, - warchiefsBlessing: true, + spiritOfZandalar: false, + warchiefsBlessing: false, }); export const DefaultDebuffs = Debuffs.create({ From 4cc82e5f945e1814a180fe4fb861dee2a77d8482 Mon Sep 17 00:00:00 2001 From: sanguinerarogue Date: Wed, 1 Jan 2025 15:51:20 -0700 Subject: [PATCH 5/5] pvp items --- sim/hunter/TestP1Hunter.results | 70 ++++++++++ sim/hunter/_item_sets_pvp.go | 220 -------------------------------- sim/hunter/item_sets_pvp.go | 96 ++++++++++++++ sim/hunter/items.go | 71 +++++++++++ 4 files changed, 237 insertions(+), 220 deletions(-) delete mode 100644 sim/hunter/_item_sets_pvp.go create mode 100644 sim/hunter/item_sets_pvp.go diff --git a/sim/hunter/TestP1Hunter.results b/sim/hunter/TestP1Hunter.results index 8b0e088102..7d347e1e42 100644 --- a/sim/hunter/TestP1Hunter.results +++ b/sim/hunter/TestP1Hunter.results @@ -103,6 +103,27 @@ dps_results: { tps: 148.05127 } } +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-BloodGuard'sChainGauntlets-16530" + value: { + dps: 292.04584 + tps: 144.84534 + } +} +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-BloodGuard'sChainVices-22862" + value: { + dps: 293.022 + tps: 145.8215 + } +} +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-Champion'sPursuance" + value: { + dps: 298.93967 + tps: 151.7376 + } +} dps_results: { key: "TestP1Hunter-Phase1-AllItems-CryptstalkerArmor" value: { @@ -131,6 +152,20 @@ dps_results: { tps: 158.34988 } } +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-FieldMarshal'sPursuit" + value: { + dps: 306.67445 + tps: 159.47237 + } +} +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-General'sChainGloves-16571" + value: { + dps: 294.73892 + tps: 147.53842 + } +} dps_results: { key: "TestP1Hunter-Phase1-AllItems-GiantstalkerArmor" value: { @@ -138,6 +173,34 @@ dps_results: { tps: 155.72767 } } +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-Knight-Lieutenant'sChainGauntlets-16403" + value: { + dps: 292.04584 + tps: 144.84534 + } +} +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-Knight-Lieutenant'sChainVices-23279" + value: { + dps: 293.022 + tps: 145.8215 + } +} +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-LieutenantCommander'sPursuance" + value: { + dps: 298.93967 + tps: 151.7376 + } +} +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-Marshal'sChainGrips-16463" + value: { + dps: 294.73892 + tps: 147.53842 + } +} dps_results: { key: "TestP1Hunter-Phase1-AllItems-Predator'sArmor" value: { @@ -159,6 +222,13 @@ dps_results: { tps: 154.88863 } } +dps_results: { + key: "TestP1Hunter-Phase1-AllItems-Warlord'sPursuit" + value: { + dps: 306.67445 + tps: 159.47237 + } +} dps_results: { key: "TestP1Hunter-Phase1-Average-Default" value: { diff --git a/sim/hunter/_item_sets_pvp.go b/sim/hunter/_item_sets_pvp.go deleted file mode 100644 index 07b1481bbb..0000000000 --- a/sim/hunter/_item_sets_pvp.go +++ /dev/null @@ -1,220 +0,0 @@ -package hunter - -import ( - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/stats" -) - -/////////////////////////////////////////////////////////////////////////// -// Classic Phase 2 -/////////////////////////////////////////////////////////////////////////// - -var ItemSetBloodGuardsChain = core.NewItemSet(core.ItemSet{ - Name: "Blood Guard's Chain", - Bonuses: map[int32]core.ApplyEffect{ - 3: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Stamina, 15) - }, - 6: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.AttackPower, 30) - c.AddStat(stats.RangedAttackPower, 20) - }, - }, -}) - -var ItemSetKnightLieutenantsChain = core.NewItemSet(core.ItemSet{ - Name: "Knight-Lieutenant's Chain", - Bonuses: map[int32]core.ApplyEffect{ - 3: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Stamina, 15) - }, - 6: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.AttackPower, 30) - c.AddStat(stats.RangedAttackPower, 20) - }, - }, -}) - -/////////////////////////////////////////////////////////////////////////// -// Classic Phase 3 -/////////////////////////////////////////////////////////////////////////// - -var ItemSetChampionsPursuit = core.NewItemSet(core.ItemSet{ - Name: "Champion's Pursuit", - Bonuses: map[int32]core.ApplyEffect{ - // +20 Agility. - 2: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Agility, 20) - }, - // Reduces the cooldown of your Concussive Shot by 1 sec. - 4: func(agent core.Agent) { - // Nothing to do - }, - // +20 Stamina. - 6: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Stamina, 20) - }, - }, -}) - -var ItemSetChampionsProwess = core.NewItemSet(core.ItemSet{ - Name: "Champion's Prowess", - Bonuses: map[int32]core.ApplyEffect{ - // +40 Attack Power. - 2: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStats(stats.Stats{ - stats.AttackPower: 40, - stats.RangedAttackPower: 40, - }) - }, - // Increases the duration of your Wing Clip by 2 sec. - 4: func(agent core.Agent) { - // Nothing to do - }, - // +20 Stamina. - 6: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Stamina, 20) - }, - }, -}) - -var ItemSetLieutenantCommandersPursuit = core.NewItemSet(core.ItemSet{ - Name: "Lieutenant Commander's Pursuit", - Bonuses: map[int32]core.ApplyEffect{ - // +20 Agility. - 2: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Agility, 20) - }, - // Reduces the cooldown of your Concussive Shot by 1 sec. - 4: func(agent core.Agent) { - // Nothing to do - }, - // +20 Stamina. - 6: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Stamina, 20) - }, - }, -}) - -var ItemSetLieutenantCommandersProwess = core.NewItemSet(core.ItemSet{ - Name: "Lieutenant Commander's Prowess", - Bonuses: map[int32]core.ApplyEffect{ - // +40 Attack Power. - 2: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStats(stats.Stats{ - stats.AttackPower: 40, - stats.RangedAttackPower: 40, - }) - }, - // Increases the duration of your Wing Clip by 2 sec. - 4: func(agent core.Agent) { - // Nothing to do - }, - // +20 Stamina. - 6: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Stamina, 20) - }, - }, -}) - -/////////////////////////////////////////////////////////////////////////// -// SoD Phase 5 Item Sets -/////////////////////////////////////////////////////////////////////////// - -var ItemSetWarlordsPursuit = core.NewItemSet(core.ItemSet{ - Name: "Warlord's Pursuit", - Bonuses: map[int32]core.ApplyEffect{ - // 20 Stamina - 2: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Stamina, 20) - }, - // Reduces the cooldown of your Concussive Shot by 1 sec. - 4: func(agent core.Agent) { - // Nothing to do - }, - // +20 Agi - 6: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Agility, 20) - }, - }, -}) - -var ItemSetWarlordsProwess = core.NewItemSet(core.ItemSet{ - Name: "Warlord's Prowess", - Bonuses: map[int32]core.ApplyEffect{ - // +20 stamina - 2: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Stamina, 20) - }, - // Increases the duration of your Wing Clip by 2 sec. - 4: func(agent core.Agent) { - // Nothing to do - }, - // +40 Agnostic Attack Power - 6: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStats(stats.Stats{ - stats.AttackPower: 40, - stats.RangedAttackPower: 40, - }) - }, - }, -}) - -var ItemSetFieldMarshalsPursuit = core.NewItemSet(core.ItemSet{ - Name: "Field Marshal's Pursuit", - Bonuses: map[int32]core.ApplyEffect{ - // 20 stamina - 2: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Stamina, 20) - }, - // Reduces the cooldown of your Concussive Shot by 1 sec. - 4: func(agent core.Agent) { - // Nothing to do - }, - // +20 Agi - 6: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Agility, 20) - }, - }, -}) - -var ItemSetFieldMarshalsProwess = core.NewItemSet(core.ItemSet{ - Name: "Field Marshal's Prowess", - Bonuses: map[int32]core.ApplyEffect{ - //20 stamina - 2: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Stamina, 20) - }, - // Increases the duration of your Wing Clip by 2 sec. - 4: func(agent core.Agent) { - // Nothing to do - }, - // +40 Agnostic Attack Power. - 6: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStats(stats.Stats{ - stats.AttackPower: 40, - stats.RangedAttackPower: 40, - }) - }, - }, -}) diff --git a/sim/hunter/item_sets_pvp.go b/sim/hunter/item_sets_pvp.go new file mode 100644 index 0000000000..3a7649f40e --- /dev/null +++ b/sim/hunter/item_sets_pvp.go @@ -0,0 +1,96 @@ +package hunter + +import ( + "github.com/wowsims/classic/sim/core" + "github.com/wowsims/classic/sim/core/stats" +) + +/////////////////////////////////////////////////////////////////////////// +// Classic Phase 2 +/////////////////////////////////////////////////////////////////////////// + +// https://www.wowhead.com/classic/item-set=543/champions-pursuance +var ItemSetChampionsPursuance = core.NewItemSet(core.ItemSet{ + Name: "Champion's Pursuance", + Bonuses: map[int32]core.ApplyEffect{ + // +20 Agility. + 2: func(agent core.Agent) { + c := agent.GetCharacter() + c.AddStat(stats.Agility, 20) + }, + // Reduces the cooldown of your Concussive Shot by 1 sec. + 4: func(agent core.Agent) { + // Nothing to do + }, + // +20 Stamina. + 6: func(agent core.Agent) { + c := agent.GetCharacter() + c.AddStat(stats.Stamina, 20) + }, + }, +}) + +// https://www.wowhead.com/classic/item-set=550/lieutenant-commanders-pursuance +var ItemSetLieutenantCommandersPursuance = core.NewItemSet(core.ItemSet{ + Name: "Lieutenant Commander's Pursuance", + Bonuses: map[int32]core.ApplyEffect{ + // +20 Agility. + 2: func(agent core.Agent) { + c := agent.GetCharacter() + c.AddStat(stats.Agility, 20) + }, + // Reduces the cooldown of your Concussive Shot by 1 sec. + 4: func(agent core.Agent) { + // Nothing to do + }, + // +20 Stamina. + 6: func(agent core.Agent) { + c := agent.GetCharacter() + c.AddStat(stats.Stamina, 20) + }, + }, +}) + +/////////////////////////////////////////////////////////////////////////// +// Classic Phase 3 +/////////////////////////////////////////////////////////////////////////// + +var ItemSetWarlordsPursuit = core.NewItemSet(core.ItemSet{ + Name: "Warlord's Pursuit", + Bonuses: map[int32]core.ApplyEffect{ + // 20 Stamina + 2: func(agent core.Agent) { + c := agent.GetCharacter() + c.AddStat(stats.Stamina, 20) + }, + // Reduces the cooldown of your Concussive Shot by 1 sec. + 4: func(agent core.Agent) { + // Nothing to do + }, + // +20 Agi + 6: func(agent core.Agent) { + c := agent.GetCharacter() + c.AddStat(stats.Agility, 20) + }, + }, +}) + +var ItemSetFieldMarshalsPursuit = core.NewItemSet(core.ItemSet{ + Name: "Field Marshal's Pursuit", + Bonuses: map[int32]core.ApplyEffect{ + // 20 stamina + 2: func(agent core.Agent) { + c := agent.GetCharacter() + c.AddStat(stats.Stamina, 20) + }, + // Reduces the cooldown of your Concussive Shot by 1 sec. + 4: func(agent core.Agent) { + // Nothing to do + }, + // +20 Agi + 6: func(agent core.Agent) { + c := agent.GetCharacter() + c.AddStat(stats.Agility, 20) + }, + }, +}) \ No newline at end of file diff --git a/sim/hunter/items.go b/sim/hunter/items.go index 384b4680c1..dc26a75a01 100644 --- a/sim/hunter/items.go +++ b/sim/hunter/items.go @@ -8,12 +8,83 @@ import ( ) const ( + KnightLieutenantsChainGauntlets = 16403 + BloodGuardsChainGauntlets = 16530 + MarshalsChainGrips = 16463 + GeneralsChainGloves = 16571 RenatakisCharmofBeasts = 19953 DevilsaurEye = 19991 DevilsaurTooth = 19992 + KnightLieutenantsChainVices= 23279 + BloodGuardsChainVices = 22862 ) func init() { + // Equip: Reduces the mana cost of your Arcane Shot by 15. + core.NewItemEffect(KnightLieutenantsChainGauntlets, func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + core.MakePermanent(hunter.RegisterAura(core.Aura{ + Label: "Arcane Shot Mana Reduction", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + if hunter.ArcaneShot != nil { + hunter.ArcaneShot.Cost.FlatModifier -= 15.0 + } + }, + })) + }) + // Equip: Reduces the mana cost of your Arcane Shot by 15. + core.NewItemEffect(BloodGuardsChainGauntlets, func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + core.MakePermanent(hunter.RegisterAura(core.Aura{ + Label: "Arcane Shot Mana Reduction", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + if hunter.ArcaneShot != nil { + hunter.ArcaneShot.Cost.FlatModifier -= 15.0 + } + }, + })) + }) + + // Equip: Increases the damage done by your Multi-Shot by 4% + core.NewItemEffect(MarshalsChainGrips, func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + core.MakePermanent(hunter.RegisterAura(core.Aura{ + Label: "Multi-Shot Damage Increase", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + hunter.MultiShot.BaseDamageMultiplierAdditive += 0.04 + }, + })) + }) + // Equip: Increases the damage done by your Multi-Shot by 4% + core.NewItemEffect(GeneralsChainGloves, func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + core.MakePermanent(hunter.RegisterAura(core.Aura{ + Label: "Multi-Shot Damage Increase", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + hunter.MultiShot.BaseDamageMultiplierAdditive += 0.04 + }, + })) + }) + // Equip: Increases the damage done by your Multi-Shot by 4% + core.NewItemEffect(KnightLieutenantsChainVices, func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + core.MakePermanent(hunter.RegisterAura(core.Aura{ + Label: "Multi-Shot Damage Increase", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + hunter.MultiShot.BaseDamageMultiplierAdditive += 0.04 + }, + })) + }) + // Equip: Increases the damage done by your Multi-Shot by 4% + core.NewItemEffect(BloodGuardsChainVices, func(agent core.Agent) { + hunter := agent.(HunterAgent).GetHunter() + core.MakePermanent(hunter.RegisterAura(core.Aura{ + Label: "Multi-Shot Damage Increase", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + hunter.MultiShot.BaseDamageMultiplierAdditive += 0.04 + }, + })) + }) // Use: Instantly clears the cooldowns of Aimed Shot, Multishot, Volley, and Arcane Shot. (cooldown 3 min) core.NewItemEffect(RenatakisCharmofBeasts, func(agent core.Agent) { hunter := agent.(HunterAgent).GetHunter()