Skip to content

Commit

Permalink
Merge pull request #860 from wowsims/p4/general
Browse files Browse the repository at this point in the history
Add Magmadar's Call and Spirit ofEskhandar set bonuses
  • Loading branch information
kayla-glick authored Jul 5, 2024
2 parents d9a9202 + a2d921c commit 5b39df5
Show file tree
Hide file tree
Showing 17 changed files with 356 additions and 82 deletions.
79 changes: 79 additions & 0 deletions sim/common/guardians/core_hound.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package guardians

import (
"slices"

"github.com/wowsims/sod/sim/core"
"github.com/wowsims/sod/sim/core/stats"
)

// https://www.wowhead.com/classic/item-set=1779/core-hounds-call
// https://www.wowhead.com/classic/spell=461267/core-hounds-call
// https://www.wowhead.com/classic/npc=229001/core-hound

type CoreHound struct {
core.Pet
}

func NewCoreHound(character *core.Character) *CoreHound {
coreHound := &CoreHound{
Pet: core.NewPet("Core Hound", character, stats.Stats{}, coreHoundStatInheritance(), false, true),
}
// TODO: Verify
coreHound.Level = 60

coreHound.EnableAutoAttacks(coreHound, core.AutoAttackOptions{
// TODO: Need Core Hound data
MainHand: core.Weapon{
BaseDamageMin: 1,
BaseDamageMax: 1,
SwingSpeed: 2.0,
SpellSchool: core.SpellSchoolPhysical,
},
AutoSwingMelee: true,
})

return coreHound
}

func coreHoundStatInheritance() core.PetStatInheritance {
return func(ownerStats stats.Stats) stats.Stats {
// TODO: Needs more verification
return stats.Stats{}
}
}

func (hound *CoreHound) Initialize() {
}

func (hound *CoreHound) ExecuteCustomRotation(sim *core.Simulation) {
// Run the cast check only on swings or cast completes
if hound.AutoAttacks.NextAttackAt() != sim.CurrentTime+hound.AutoAttacks.MainhandSwingSpeed() && hound.AutoAttacks.NextAnyAttackAt()-1 > sim.CurrentTime {
hound.WaitUntil(sim, hound.AutoAttacks.NextAttackAt()-1)
return
}

hound.WaitUntil(sim, hound.AutoAttacks.NextAttackAt()-1)
}

func (hound *CoreHound) Reset(sim *core.Simulation) {
hound.Disable(sim)
}

func (hound *CoreHound) OnPetDisable(sim *core.Simulation) {
}

func (hound *CoreHound) GetPet() *core.Pet {
return &hound.Pet
}

func constructCoreHound(character *core.Character) {
// Can't use the set bonus itself because of an import cycle
hasSetBonus := slices.ContainsFunc(character.GetActiveSetBonuses(), func(bonus core.ActiveSetBonus) bool {
return bonus.Name == "Core Hound's Call" && bonus.NumPieces >= 2
})

if hasSetBonus {
character.AddPet(NewCoreHound(character))
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package vanilla
package guardians

import (
"time"
Expand Down Expand Up @@ -129,32 +129,7 @@ func (whelp *EmeraldDragonWhelp) registerAcidSpitSpell() {
})
}

func MakeEmeraldDragonWhelpTriggerAura(agent core.Agent, itemId int32) {
character := agent.GetCharacter()

procMask := character.GetProcMaskForItem(itemId)

core.MakeProcTriggerAura(&character.Unit, core.ProcTrigger{
ActionID: core.ActionID{SpellID: 13049},
Name: "Emerald Dragon Whelp Proc",
Callback: core.CallbackOnSpellHitDealt,
Outcome: core.OutcomeLanded,
ProcMask: procMask,
PPM: 1.0, // Reported by armaments discord
ICD: time.Minute * 1,
Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) {
for _, petAgent := range character.PetAgents {
if whelp, ok := petAgent.(*EmeraldDragonWhelp); ok {
whelp.EnableWithTimeout(sim, whelp, time.Second*15)
whelp.disabledAt = sim.CurrentTime + time.Second*15
break
}
}
},
})
}

func ConstructEmeralDragonWhelpPets(character *core.Character) {
func constructEmeralDragonWhelps(character *core.Character) {
if character.HasMHWeapon() && character.GetMHWeapon().ID == DragonsCry ||
character.HasOHWeapon() && character.GetOHWeapon().ID == DragonsCry {
// Original could have up to 3 whelps active at a time however the SoD version seems to only summon 1 whelp on a 1 minute cooldown
Expand Down
80 changes: 80 additions & 0 deletions sim/common/guardians/eskhandar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package guardians

import (
"slices"

"github.com/wowsims/sod/sim/core"
"github.com/wowsims/sod/sim/core/stats"
)

// https://www.wowhead.com/classic/item-set=1781/spirit-of-eskhandar
// https://www.wowhead.com/classic/spell=461990/call-of-eskhandar
// https://www.wowhead.com/classic/npc=14306/eskhandar

type Eskhandar struct {
core.Pet
}

func NewEskhandar(character *core.Character) *Eskhandar {
eskhandar := &Eskhandar{
Pet: core.NewPet("Eskhandar", character, stats.Stats{}, eskhandarStatInheritance(), false, true),
}

// TODO: Verify
eskhandar.Level = 60

eskhandar.EnableAutoAttacks(eskhandar, core.AutoAttackOptions{
// TODO: Need Core Hound data
MainHand: core.Weapon{
BaseDamageMin: 1,
BaseDamageMax: 1,
SwingSpeed: 2.0,
SpellSchool: core.SpellSchoolPhysical,
},
AutoSwingMelee: true,
})

return eskhandar
}

func eskhandarStatInheritance() core.PetStatInheritance {
return func(ownerStats stats.Stats) stats.Stats {
// TODO: Needs more verification
return stats.Stats{}
}
}

func (eskhandar *Eskhandar) Initialize() {
}

func (eskhandar *Eskhandar) ExecuteCustomRotation(sim *core.Simulation) {
// Run the cast check only on swings or cast completes
if eskhandar.AutoAttacks.NextAttackAt() != sim.CurrentTime+eskhandar.AutoAttacks.MainhandSwingSpeed() && eskhandar.AutoAttacks.NextAnyAttackAt()-1 > sim.CurrentTime {
eskhandar.WaitUntil(sim, eskhandar.AutoAttacks.NextAttackAt()-1)
return
}

eskhandar.WaitUntil(sim, eskhandar.AutoAttacks.NextAttackAt()-1)
}

func (eskhandar *Eskhandar) Reset(sim *core.Simulation) {
eskhandar.Disable(sim)
}

func (eskhandar *Eskhandar) OnPetDisable(sim *core.Simulation) {
}

func (eskhandar *Eskhandar) GetPet() *core.Pet {
return &eskhandar.Pet
}

func constructEskhandar(character *core.Character) {
// Can't use the set bonus itself because of an import cycle
hasSetBonus := slices.ContainsFunc(character.GetActiveSetBonuses(), func(bonus core.ActiveSetBonus) bool {
return bonus.Name == "Spirit of Eskhandar" && bonus.NumPieces == 4
})

if hasSetBonus {
character.AddPet(NewEskhandar(character))
}
}
9 changes: 9 additions & 0 deletions sim/common/guardians/guardians.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package guardians

import "github.com/wowsims/sod/sim/core"

func ConstructGuardians(character *core.Character) {
constructEmeralDragonWhelps(character)
constructEskhandar(character)
constructCoreHound(character)
}
23 changes: 21 additions & 2 deletions sim/common/sod/item_effects/phase_3.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package item_effects
import (
"time"

"github.com/wowsims/sod/sim/common/guardians"
"github.com/wowsims/sod/sim/common/itemhelpers"
"github.com/wowsims/sod/sim/common/vanilla"
"github.com/wowsims/sod/sim/core"
"github.com/wowsims/sod/sim/core/proto"
"github.com/wowsims/sod/sim/core/stats"
Expand Down Expand Up @@ -442,7 +442,26 @@ func init() {
})

core.NewItemEffect(DragonsCry, func(agent core.Agent) {
vanilla.MakeEmeraldDragonWhelpTriggerAura(agent, DragonsCry)
character := agent.GetCharacter()

procMask := character.GetProcMaskForItem(DragonsCry)

core.MakeProcTriggerAura(&character.Unit, core.ProcTrigger{
Name: "Emerald Dragon Whelp Proc",
Callback: core.CallbackOnSpellHitDealt,
Outcome: core.OutcomeLanded,
ProcMask: procMask,
PPM: 1.0, // Reported by armaments discord
ICD: time.Minute * 1,
Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) {
for _, petAgent := range character.PetAgents {
if whelp, ok := petAgent.(*guardians.EmeraldDragonWhelp); ok {
whelp.EnableWithTimeout(sim, whelp, time.Second*15)
break
}
}
},
})
})

core.NewItemEffect(CobraFangClaw, func(agent core.Agent) {
Expand Down
91 changes: 91 additions & 0 deletions sim/common/sod/items_sets/phase_4.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package item_sets
import (
"time"

"github.com/wowsims/sod/sim/common/guardians"
"github.com/wowsims/sod/sim/core"
"github.com/wowsims/sod/sim/core/stats"
)
Expand Down Expand Up @@ -457,3 +458,93 @@ var ItemSetShardOfTheGods = core.NewItemSet(core.ItemSet{
},
},
})

var ItemSetSpiritOfEskhandar = core.NewItemSet(core.ItemSet{
Name: "Spirit of Eskhandar",
Bonuses: map[int32]core.ApplyEffect{
// Improves your chance to hit with all spells and attacks by 1%.
2: func(agent core.Agent) {
character := agent.GetCharacter()
character.AddStat(stats.MeleeHit, 1)
character.AddStat(stats.SpellHit, 1)
},
// Improves your chance to get a critical strike with all spells and attacks by 1%.
3: func(agent core.Agent) {
character := agent.GetCharacter()
character.AddStat(stats.MeleeCrit, 1*core.CritRatingPerCritChance)
character.AddStat(stats.SpellCrit, 1*core.SpellCritRatingPerCritChance)
},
// 1% chance on a melee hit to call forth the spirit of Eskhandar to protect you in battle for 2 min.
4: func(agent core.Agent) {
character := agent.GetCharacter()
core.MakeProcTriggerAura(&character.Unit, core.ProcTrigger{
Name: "Call of Eskhandar Trigger",
Callback: core.CallbackOnSpellHitDealt,
Outcome: core.OutcomeLanded,
ProcMask: core.ProcMaskMelee,
ProcChance: 1,
ICD: time.Minute * 1,
Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) {
for _, petAgent := range character.PetAgents {
if eskhandar, ok := petAgent.(*guardians.Eskhandar); ok {
eskhandar.EnableWithTimeout(sim, eskhandar, time.Minute*2)
break
}
}
},
})
},
},
})

var ItemSetCoreHoundsCall = core.NewItemSet(core.ItemSet{
Name: "Core Hound's Call",
Bonuses: map[int32]core.ApplyEffect{
// Small chance on melee hit to call forth a Core Hound for 1 min.
2: func(agent core.Agent) {
character := agent.GetCharacter()
core.MakeProcTriggerAura(&character.Unit, core.ProcTrigger{
Name: "Core Hound's Call Trigger",
Callback: core.CallbackOnSpellHitDealt,
Outcome: core.OutcomeLanded,
ProcMask: core.ProcMaskMelee,
ProcChance: 1,
ICD: time.Minute * 1,
Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) {
for _, petAgent := range character.PetAgents {
if coreHound, ok := petAgent.(*guardians.CoreHound); ok {
coreHound.EnableWithTimeout(sim, coreHound, time.Minute*1)
break
}
}
},
})
},
// Small chance on melee hit to call forth the Spirit of Magmadar to assist you in battle. Increasing your attack speed by 10% for 20 sec.
3: func(agent core.Agent) {
character := agent.GetCharacter()
procAura := character.RegisterAura(core.Aura{
ActionID: core.ActionID{SpellID: 461270},
Label: "Magmadar's Return",
Duration: time.Second * 20,
OnGain: func(aura *core.Aura, sim *core.Simulation) {
character.MultiplyAttackSpeed(sim, 1.1)
},
OnExpire: func(aura *core.Aura, sim *core.Simulation) {
character.MultiplyAttackSpeed(sim, 1/1.1)
},
})
core.MakeProcTriggerAura(&character.Unit, core.ProcTrigger{
Name: "Magmadar's Return Trigger",
Callback: core.CallbackOnSpellHitDealt,
Outcome: core.OutcomeLanded,
ProcMask: core.ProcMaskMelee,
ProcChance: 1,
ICD: time.Minute * 1,
Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) {
procAura.Activate(sim)
},
})
},
},
})
13 changes: 8 additions & 5 deletions sim/druid/druid.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package druid
import (
"time"

"github.com/wowsims/sod/sim/common/guardians"
"github.com/wowsims/sod/sim/core"
"github.com/wowsims/sod/sim/core/proto"
"github.com/wowsims/sod/sim/core/stats"
Expand Down Expand Up @@ -243,9 +244,9 @@ func (druid *Druid) Reset(_ *core.Simulation) {
druid.disabledMCDs = []*core.MajorCooldown{}
}

func New(char *core.Character, form DruidForm, selfBuffs SelfBuffs, talents string) *Druid {
func New(character *core.Character, form DruidForm, selfBuffs SelfBuffs, talents string) *Druid {
druid := &Druid{
Character: *char,
Character: *character,
SelfBuffs: selfBuffs,
Talents: &proto.DruidTalents{},
StartingForm: form,
Expand All @@ -257,17 +258,19 @@ func New(char *core.Character, form DruidForm, selfBuffs SelfBuffs, talents stri
// TODO: Class druid physical stats
druid.AddStatDependency(stats.Strength, stats.AttackPower, 2)
druid.AddStatDependency(stats.BonusArmor, stats.Armor, 1)
druid.AddStatDependency(stats.Agility, stats.MeleeCrit, core.CritPerAgiAtLevel[char.Class][int(druid.Level)]*core.CritRatingPerCritChance)
druid.AddStatDependency(stats.Intellect, stats.SpellCrit, core.CritPerIntAtLevel[char.Class][int(druid.Level)]*core.SpellCritRatingPerCritChance)
druid.AddStatDependency(stats.Agility, stats.MeleeCrit, core.CritPerAgiAtLevel[character.Class][int(druid.Level)]*core.CritRatingPerCritChance)
druid.AddStatDependency(stats.Intellect, stats.SpellCrit, core.CritPerIntAtLevel[character.Class][int(druid.Level)]*core.SpellCritRatingPerCritChance)
// TODO: Update DodgePerAgiAtLevel with the appropriate value for each level
druid.AddStatDependency(stats.Agility, stats.Dodge, core.DodgePerAgiAtLevel[char.Class][int(druid.Level)])
druid.AddStatDependency(stats.Agility, stats.Dodge, core.DodgePerAgiAtLevel[character.Class][int(druid.Level)])

// Druids get extra melee haste
// druid.PseudoStats.MeleeHasteRatingPerHastePercent /= 1.3

// Switch to using AddStat as PseudoStat is being removed
// druid.PseudoStats.BaseDodge += 0.056097

guardians.ConstructGuardians(&druid.Character)

return druid
}

Expand Down
Loading

0 comments on commit 5b39df5

Please sign in to comment.