From f45c5efde378571a8d904994aeda04b3bb08990b Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 4 Feb 2024 15:24:09 -0500 Subject: [PATCH 01/18] implement fire totems --- sim/shaman/earth_totems.go | 92 ++++++++++ sim/shaman/fire_totems.go | 367 ++++++++++++++++++++++++++----------- sim/shaman/shaman.go | 40 ++-- sim/shaman/totems.go | 65 +++---- 4 files changed, 400 insertions(+), 164 deletions(-) create mode 100644 sim/shaman/earth_totems.go diff --git a/sim/shaman/earth_totems.go b/sim/shaman/earth_totems.go new file mode 100644 index 0000000000..2804284fcc --- /dev/null +++ b/sim/shaman/earth_totems.go @@ -0,0 +1,92 @@ +package shaman + +import ( + "time" + + "github.com/wowsims/sod/sim/core" +) + +const StrengthOfEarthTotemRanks = 5 + +var StrengthOfEarthTotemSpellId = [StrengthOfEarthTotemRanks + 1]int32{0, 8075, 8160, 8161, 10442, 25361} +var StrengthOfEarthTotemManaCost = [StrengthOfEarthTotemRanks + 1]float64{0, 25, 65, 125, 225, 275} +var StrengthOfEarthTotemLevel = [StrengthOfEarthTotemRanks + 1]int{0, 10, 24, 38, 52, 60} + +func (shaman *Shaman) registerStrengthOfEarthTotemSpell() { + shaman.StrengthOfEarthTotem = make([]*core.Spell, StrengthOfEarthTotemRanks+1) + + for rank := 1; rank <= StrengthOfEarthTotemRanks; rank++ { + config := shaman.newStrengthOfEarthTotemSpellConfig(rank) + + if config.RequiredLevel <= int(shaman.Level) { + shaman.StrengthOfEarthTotem[rank] = shaman.RegisterSpell(config) + } + } +} + +func (shaman *Shaman) newStrengthOfEarthTotemSpellConfig(rank int) core.SpellConfig { + spellId := StrengthOfEarthTotemSpellId[rank] + manaCost := StrengthOfEarthTotemManaCost[rank] + level := StrengthOfEarthTotemLevel[rank] + + duration := time.Second * 120 + + spell := shaman.newTotemSpellConfig(manaCost, spellId) + spell.RequiredLevel = level + spell.Rank = rank + spell.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + shaman.ActiveTotems[EarthTotem] = spell + shaman.TotemExpirations[EarthTotem] = sim.CurrentTime + duration + } + return spell +} + +const StoneskinTotemRanks = 6 + +var StoneskinTotemSpellId = [StoneskinTotemRanks + 1]int32{0, 8071, 8154, 8155, 10406, 10407, 10408} +var StoneskinTotemManaCost = [StoneskinTotemRanks + 1]float64{0, 30, 60, 90, 115, 160, 210} +var StoneskinTotemLevel = [StoneskinTotemRanks + 1]int{0, 4, 14, 24, 34, 44, 54} + +func (shaman *Shaman) registerStoneskinTotemSpell() { + shaman.StoneskinTotem = make([]*core.Spell, StoneskinTotemRanks+1) + + for rank := 1; rank <= StoneskinTotemRanks; rank++ { + config := shaman.newStoneskinTotemSpellConfig(rank) + + if config.RequiredLevel <= int(shaman.Level) { + shaman.StoneskinTotem[rank] = shaman.RegisterSpell(config) + } + } +} + +func (shaman *Shaman) newStoneskinTotemSpellConfig(rank int) core.SpellConfig { + spellId := StoneskinTotemSpellId[rank] + manaCost := StoneskinTotemManaCost[rank] + level := StoneskinTotemLevel[rank] + + duration := time.Second * 120 + + spell := shaman.newTotemSpellConfig(manaCost, spellId) + spell.RequiredLevel = level + spell.Rank = rank + spell.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + shaman.ActiveTotems[EarthTotem] = spell + shaman.TotemExpirations[EarthTotem] = sim.CurrentTime + duration + } + return spell +} + +func (shaman *Shaman) registerTremorTotemSpell() { + spellId := int32(8143) + manaCost := float64(60) + duration := time.Second * 120 + level := 18 + + spell := shaman.newTotemSpellConfig(manaCost, spellId) + spell.RequiredLevel = level + spell.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + shaman.ActiveTotems[EarthTotem] = spell + shaman.TotemExpirations[EarthTotem] = sim.CurrentTime + duration + } + shaman.TremorTotem = shaman.RegisterSpell(spell) +} diff --git a/sim/shaman/fire_totems.go b/sim/shaman/fire_totems.go index b0047e5d66..d176ba5561 100644 --- a/sim/shaman/fire_totems.go +++ b/sim/shaman/fire_totems.go @@ -1,106 +1,265 @@ package shaman -// import ( -// "time" - -// "github.com/wowsims/sod/sim/core" -// ) - -// func (shaman *Shaman) registerSearingTotemSpell() { -// shaman.SearingTotem = shaman.RegisterSpell(core.SpellConfig{ -// ActionID: core.ActionID{SpellID: 58704}, -// SpellSchool: core.SpellSchoolFire, -// ProcMask: core.ProcMaskEmpty, -// Flags: SpellFlagTotem | core.SpellFlagAPL, - -// ManaCost: core.ManaCostOptions{ -// BaseCost: 0.07, -// Multiplier: 1 - -// 0.05*float64(shaman.Talents.TotemicFocus) - -// 0.02*float64(shaman.Talents.MentalQuickness), -// }, -// Cast: core.CastConfig{ -// DefaultCast: core.Cast{ -// GCD: time.Second, -// }, -// }, - -// BonusHitRating: float64(shaman.Talents.ElementalPrecision) * core.SpellHitRatingPerHitChance, -// DamageMultiplier: 1 + float64(shaman.Talents.CallOfFlame)*0.05, -// CritMultiplier: shaman.ElementalCritMultiplier(0), - -// Dot: core.DotConfig{ -// Aura: core.Aura{ -// Label: "SearingTotem", -// }, -// // These are the real tick values, but searing totem doesn't start its next -// // cast until the previous missile hits the target. We don't have an option -// // for target distance yet so just pretend the tick rate is lower. -// // https://wotlk.wowhead.com/spell=25530/attack -// //NumberOfTicks: 30, -// //TickLength: time.Second * 2.2, -// NumberOfTicks: 24, -// TickLength: time.Second * 60 / 24, -// OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { -// baseDamage := sim.Roll(90, 120) + 0.167*dot.Spell.SpellPower() -// dot.Spell.CalcAndDealDamage(sim, target, baseDamage, dot.Spell.OutcomeMagicHitAndCrit) -// }, -// }, - -// ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { -// shaman.MagmaTotem.AOEDot().Cancel(sim) -// spell.Dot(sim.GetTargetUnit(0)).Apply(sim) -// // +1 needed because of rounding issues with totem tick time. -// shaman.NextTotemDrops[FireTotem] = sim.CurrentTime + time.Second*60 + 1 -// }, -// }) -// } - -// func (shaman *Shaman) registerMagmaTotemSpell() { -// shaman.MagmaTotem = shaman.RegisterSpell(core.SpellConfig{ -// ActionID: core.ActionID{SpellID: 58734}, -// SpellSchool: core.SpellSchoolFire, -// ProcMask: core.ProcMaskEmpty, -// Flags: SpellFlagTotem | core.SpellFlagAPL, - -// ManaCost: core.ManaCostOptions{ -// BaseCost: 0.27, -// Multiplier: 1 - -// 0.05*float64(shaman.Talents.TotemicFocus) - -// 0.02*float64(shaman.Talents.MentalQuickness), -// }, -// Cast: core.CastConfig{ -// DefaultCast: core.Cast{ -// GCD: time.Second, -// }, -// }, - -// BonusHitRating: float64(shaman.Talents.ElementalPrecision) * core.SpellHitRatingPerHitChance, -// DamageMultiplier: 1 + float64(shaman.Talents.CallOfFlame)*0.05, -// CritMultiplier: shaman.ElementalCritMultiplier(0), - -// Dot: core.DotConfig{ -// IsAOE: true, -// Aura: core.Aura{ -// Label: "MagmaTotem", -// }, -// NumberOfTicks: 10, -// TickLength: time.Second * 2, - -// OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { -// baseDamage := 371 + 0.1*dot.Spell.SpellPower() -// baseDamage *= sim.Encounter.AOECapMultiplier() -// for _, aoeTarget := range sim.Encounter.TargetUnits { -// dot.Spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, dot.Spell.OutcomeMagicHitAndCrit) -// } -// }, -// }, - -// ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { -// shaman.SearingTotem.Dot(shaman.CurrentTarget).Cancel(sim) -// spell.AOEDot().Apply(sim) -// // +1 needed because of rounding issues with totem tick time. -// shaman.NextTotemDrops[FireTotem] = sim.CurrentTime + time.Second*20 + 1 -// }, -// }) -// } +import ( + "time" + + "github.com/wowsims/sod/sim/core" +) + +const SearingTotemRanks = 6 + +var SearingTotemSpellId = [SearingTotemRanks + 1]int32{0, 3599, 6363, 6364, 6365, 10437, 10438} +var SearingTotemBaseDamage = [SearingTotemRanks + 1][]float64{{0}, {9, 11}, {13, 17}, {19, 25}, {26, 34}, {33, 45}, {40, 54}} +var SearingTotemSpellCoef = [SearingTotemRanks + 1]float64{0, 0, .267, .147, .227, 0, 0} +var SearingTotemManaCost = [SearingTotemRanks + 1]float64{0, 25, 45, 75, 110, 145, 170} +var SearingTotemDuration = [SearingTotemRanks + 1]int{0, 30, 35, 40, 45, 50, 55} +var SearingTotemLevel = [SearingTotemRanks + 1]int{0, 10, 20, 30, 40, 50, 60} + +func (shaman *Shaman) registerSearingTotemSpell() { + shaman.SearingTotem = make([]*core.Spell, SearingTotemRanks+1) + + for rank := 1; rank <= SearingTotemRanks; rank++ { + config := shaman.newSearingTotemSpellConfig(rank) + + if config.RequiredLevel <= int(shaman.Level) { + shaman.SearingTotem[rank] = shaman.RegisterSpell(config) + } + } +} + +func (shaman *Shaman) newSearingTotemSpellConfig(rank int) core.SpellConfig { + spellId := SearingTotemSpellId[rank] + baseDamageLow := SearingTotemBaseDamage[rank][0] + baseDamageHigh := SearingTotemBaseDamage[rank][1] + spellCoeff := SearingTotemSpellCoef[rank] + manaCost := SearingTotemManaCost[rank] + duration := time.Second * time.Duration(SearingTotemDuration[rank]) + level := SearingTotemLevel[rank] + + attackInterval := time.Millisecond * 2500 + + spell := core.SpellConfig{ + ActionID: core.ActionID{SpellID: spellId}, + SpellSchool: core.SpellSchoolFire, + ProcMask: core.ProcMaskEmpty, + Flags: SpellFlagTotem | core.SpellFlagAPL, + + RequiredLevel: level, + Rank: rank, + + ManaCost: core.ManaCostOptions{ + FlatCost: manaCost, + Multiplier: shaman.TotemManaMultiplier(), + }, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + }, + + DamageMultiplier: 1 + float64(shaman.Talents.CallOfFlame)*0.05, + CritMultiplier: shaman.ElementalCritMultiplier(0), + + Dot: core.DotConfig{ + Aura: core.Aura{ + Label: "SearingTotem", + }, + // These are the real tick values, but searing totem doesn't start its next + // cast until the previous missile hits the target. We don't have an option + // for target distance yet so just pretend the tick rate is lower. + // https://wotlk.wowhead.com/spell=25530/attack + //TickLength: time.Second * 2.2, + NumberOfTicks: int32(duration / attackInterval), + TickLength: attackInterval, + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + baseDamage := sim.Roll(baseDamageLow, baseDamageHigh) + spellCoeff*dot.Spell.SpellPower() + dot.Spell.CalcAndDealDamage(sim, target, baseDamage, dot.Spell.OutcomeMagicHitAndCrit) + }, + }, + + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + if shaman.ActiveTotems[FireTotem] != nil && shaman.ActiveTotems[FireTotem].SpellCode == int(SpellCode_MagmaTotem) { + shaman.ActiveTotems[FireTotem].AOEDot().Cancel(sim) + } + spell.Dot(sim.GetTargetUnit(0)).Apply(sim) + // +1 needed because of rounding issues with totem tick time. + shaman.TotemExpirations[FireTotem] = sim.CurrentTime + duration + 1 + shaman.ActiveTotems[FireTotem] = spell + }, + } + + return spell +} + +const MagmaTotemRanks = 4 + +var MagmaTotemSpellId = [MagmaTotemRanks + 1]int32{0, 8190, 10585, 10586, 10587} +var MagmaTotemBaseDamage = [MagmaTotemRanks + 1]float64{0, 22, 37, 54, 75} +var MagmaTotemSpellCoeff = [MagmaTotemRanks + 1]float64{0, .033, .033, .033, .033} +var MagmaTotemManaCost = [MagmaTotemRanks + 1]float64{0, 230, 360, 500, 650} +var MagmaTotemLevel = [MagmaTotemRanks + 1]int{0, 26, 36, 46, 56} + +func (shaman *Shaman) registerMagmaTotemSpell() { + shaman.MagmaTotem = make([]*core.Spell, MagmaTotemRanks+1) + + for rank := 1; rank <= MagmaTotemRanks; rank++ { + config := shaman.newMagmaTotemSpellConfig(rank) + + if config.RequiredLevel <= int(shaman.Level) { + shaman.MagmaTotem[rank] = shaman.RegisterSpell(config) + } + } +} + +func (shaman *Shaman) newMagmaTotemSpellConfig(rank int) core.SpellConfig { + spellId := MagmaTotemSpellId[rank] + baseDamage := MagmaTotemBaseDamage[rank] + spellCoeff := MagmaTotemSpellCoeff[rank] + manaCost := MagmaTotemManaCost[rank] + level := MagmaTotemLevel[rank] + + duration := time.Second * 20 + attackInterval := time.Second * 2 + + spell := core.SpellConfig{ + ActionID: core.ActionID{SpellID: spellId}, + SpellSchool: core.SpellSchoolFire, + ProcMask: core.ProcMaskEmpty, + Flags: SpellFlagTotem | core.SpellFlagAPL, + + RequiredLevel: level, + Rank: rank, + + ManaCost: core.ManaCostOptions{ + FlatCost: manaCost, + Multiplier: shaman.TotemManaMultiplier(), + }, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + }, + + DamageMultiplier: 1 + float64(shaman.Talents.CallOfFlame)*0.05, + CritMultiplier: shaman.ElementalCritMultiplier(0), + + Dot: core.DotConfig{ + IsAOE: true, + Aura: core.Aura{ + Label: "MagmaTotem", + }, + NumberOfTicks: int32(duration / attackInterval), + TickLength: attackInterval, + + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + baseDamage := baseDamage + spellCoeff*dot.Spell.SpellPower() + baseDamage *= sim.Encounter.AOECapMultiplier() + for _, aoeTarget := range sim.Encounter.TargetUnits { + dot.Spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, dot.Spell.OutcomeMagicHitAndCrit) + } + }, + }, + + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + if shaman.ActiveTotems[FireTotem] != nil && shaman.ActiveTotems[FireTotem].SpellCode == int(SpellCode_SearingTotem) { + shaman.ActiveTotems[FireTotem].Dot(shaman.CurrentTarget).Cancel(sim) + } + spell.AOEDot().Apply(sim) + // +1 needed because of rounding issues with totem tick time. + shaman.TotemExpirations[FireTotem] = sim.CurrentTime + duration + 1 + shaman.ActiveTotems[FireTotem] = spell + }, + } + + return spell +} + +const FireNovaTotemRanks = 5 + +var FireNovaTotemSpellId = [FireNovaTotemRanks + 1]int32{0, 1535, 8498, 8499, 11314, 11315} +var FireNovaTotemBaseDamage = [FireNovaTotemRanks + 1][]float64{{0, 0}, {53, 62}, {110, 124}, {195, 219}, {295, 331}, {413, 459}} +var FireNovaTotemSpellCoeff = [FireNovaTotemRanks + 1]float64{0, .1, .143, .143, .143, .143} +var FireNovaTotemManaCost = [FireNovaTotemRanks + 1]float64{0, 95, 170, 280, 395, 520} +var FireNovaTotemLevel = [FireNovaTotemRanks + 1]int{0, 12, 22, 32, 42, 52} + +func (shaman *Shaman) registerFireNovaTotemSpell() { + shaman.FireNovaTotem = make([]*core.Spell, FireNovaTotemRanks+1) + + for rank := 1; rank <= FireNovaTotemRanks; rank++ { + config := shaman.newFireNovaTotemSpellConfig(rank) + + if config.RequiredLevel <= int(shaman.Level) { + shaman.FireNovaTotem[rank] = shaman.RegisterSpell(config) + } + } +} + +func (shaman *Shaman) newFireNovaTotemSpellConfig(rank int) core.SpellConfig { + spellId := FireNovaTotemSpellId[rank] + baseDamageLow := FireNovaTotemBaseDamage[rank][0] + baseDamageHigh := FireNovaTotemBaseDamage[rank][1] + spellCoeff := FireNovaTotemSpellCoeff[rank] + manaCost := FireNovaTotemManaCost[rank] + level := FireNovaTotemLevel[rank] + + duration := time.Second * 5 + attackInterval := duration + + spell := core.SpellConfig{ + ActionID: core.ActionID{SpellID: spellId}, + SpellSchool: core.SpellSchoolFire, + ProcMask: core.ProcMaskEmpty, + Flags: SpellFlagTotem | core.SpellFlagAPL, + + RequiredLevel: level, + Rank: rank, + + ManaCost: core.ManaCostOptions{ + FlatCost: manaCost, + Multiplier: shaman.TotemManaMultiplier(), + }, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + }, + + DamageMultiplier: 1 + float64(shaman.Talents.CallOfFlame)*0.05, + CritMultiplier: shaman.ElementalCritMultiplier(0), + + Dot: core.DotConfig{ + IsAOE: true, + Aura: core.Aura{ + Label: "FireNovaTotem", + }, + NumberOfTicks: 1, + TickLength: attackInterval, + + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + baseDamage := sim.Roll(baseDamageLow, baseDamageHigh) + spellCoeff*dot.Spell.SpellPower() + baseDamage *= sim.Encounter.AOECapMultiplier() + for _, aoeTarget := range sim.Encounter.TargetUnits { + dot.Spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, dot.Spell.OutcomeMagicHitAndCrit) + } + }, + }, + + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + if shaman.ActiveTotems[FireTotem] != nil && shaman.ActiveTotems[FireTotem].SpellCode == int(SpellCode_SearingTotem) { + shaman.ActiveTotems[FireTotem].Dot(shaman.CurrentTarget).Cancel(sim) + } + if shaman.ActiveTotems[FireTotem] != nil && shaman.ActiveTotems[FireTotem].SpellCode == int(SpellCode_MagmaTotem) { + shaman.ActiveTotems[FireTotem].AOEDot().Cancel(sim) + } + spell.AOEDot().Apply(sim) + // +1 needed because of rounding issues with totem tick time. + shaman.TotemExpirations[FireTotem] = sim.CurrentTime + duration + 1 + shaman.ActiveTotems[FireTotem] = spell + }, + } + + return spell +} diff --git a/sim/shaman/shaman.go b/sim/shaman/shaman.go index 49fd009818..68d7ee00b1 100644 --- a/sim/shaman/shaman.go +++ b/sim/shaman/shaman.go @@ -68,6 +68,8 @@ type ShamanSpellCode int const ( SpellCode_ShamanLightningBolt ShamanSpellCode = iota SpellCode_ShamanChainLightning + SpellCode_SearingTotem + SpellCode_MagmaTotem ) // Shaman represents a shaman character. @@ -100,18 +102,19 @@ type Shaman struct { FrostShock []*core.Spell // Totems - StoneskinTotem *core.Spell - StrengthOfEarthTotem *core.Spell + ActiveTotems [4]*core.Spell + + StrengthOfEarthTotem []*core.Spell + StoneskinTotem []*core.Spell TremorTotem *core.Spell - SearingTotem *core.Spell - MagmaTotem *core.Spell - FireNovaTotem *core.Spell + SearingTotem []*core.Spell + MagmaTotem []*core.Spell + FireNovaTotem []*core.Spell - HealingStreamTotem *core.Spell - ManaSpringTotem *core.Spell + HealingStreamTotem []*core.Spell + ManaSpringTotem []*core.Spell - TotemOfWrath *core.Spell WindfuryTotem *core.Spell GraceOfAirTotem *core.Spell @@ -204,18 +207,19 @@ func (shaman *Shaman) AddPartyBuffs(partyBuffs *proto.PartyBuffs) { func (shaman *Shaman) Initialize() { shaman.registerChainLightningSpell() // shaman.registerFeralSpirit() - // shaman.registerFireNovaSpell() shaman.registerLightningBoltSpell() // shaman.registerLightningShieldSpell() - // shaman.registerMagmaTotemSpell() - // shaman.registerManaSpringTotemSpell() - // shaman.registerHealingStreamTotemSpell() - // shaman.registerSearingTotemSpell() shaman.registerShocks() // shaman.registerStormstrikeSpell() - // shaman.registerStrengthOfEarthTotemSpell() - // shaman.registerTremorTotemSpell() - // shaman.registerStoneskinTotemSpell() + + shaman.registerStrengthOfEarthTotemSpell() + shaman.registerStoneskinTotemSpell() + shaman.registerTremorTotemSpell() + shaman.registerSearingTotemSpell() + shaman.registerMagmaTotemSpell() + shaman.registerFireNovaTotemSpell() + // shaman.registerManaSpringTotemSpell() + // shaman.registerHealingStreamTotemSpell() // shaman.registerWindfuryTotemSpell() // shaman.registerGraceofAirTotem() @@ -246,3 +250,7 @@ func (shaman *Shaman) ElementalCritMultiplier(secondary float64) float64 { func (shaman *Shaman) ShamanThreatMultiplier(secondary float64) float64 { return core.TernaryFloat64(shaman.HasRune(proto.ShamanRune_RuneLegsWayOfEarth), 1.5, 1) * secondary } + +func (shaman *Shaman) TotemManaMultiplier() float64 { + return 1 - 0.05*float64(shaman.Talents.TotemicFocus) +} diff --git a/sim/shaman/totems.go b/sim/shaman/totems.go index f9f7729c17..2e04f6abe8 100644 --- a/sim/shaman/totems.go +++ b/sim/shaman/totems.go @@ -1,25 +1,26 @@ package shaman -// import ( -// "time" - -// "github.com/wowsims/sod/sim/core" -// "github.com/wowsims/sod/sim/core/proto" -// ) - -// func (shaman *Shaman) newTotemSpellConfig(baseCost float64, spellID int32) core.SpellConfig { -// return core.SpellConfig{ -// ActionID: core.ActionID{SpellID: spellID}, -// Flags: SpellFlagTotem | core.SpellFlagAPL, - -// ManaCost: core.ManaCostOptions{ -// BaseCost: baseCost, -// Multiplier: 1 - -// 0.05*float64(shaman.Talents.TotemicFocus) - -// 0.02*float64(shaman.Talents.MentalQuickness), -// }, -// } -// } +import ( + "github.com/wowsims/sod/sim/core" +) + +func (shaman *Shaman) newTotemSpellConfig(flatCost float64, spellID int32) core.SpellConfig { + return core.SpellConfig{ + ActionID: core.ActionID{SpellID: spellID}, + Flags: SpellFlagTotem | core.SpellFlagAPL, + + ManaCost: core.ManaCostOptions{ + FlatCost: flatCost, + Multiplier: shaman.TotemManaMultiplier(), + }, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + }, + } +} // func (shaman *Shaman) registerWindfuryTotemSpell() { // config := shaman.newTotemSpellConfig(0.11, 8512) @@ -96,30 +97,6 @@ package shaman // shaman.FlametongueTotem = shaman.RegisterSpell(config) // } -// func (shaman *Shaman) registerStrengthOfEarthTotemSpell() { -// config := shaman.newTotemSpellConfig(0.1, 58643) -// config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { -// shaman.TotemExpirations[EarthTotem] = sim.CurrentTime + time.Second*300 -// } -// shaman.StrengthOfEarthTotem = shaman.RegisterSpell(config) -// } - -// func (shaman *Shaman) registerTremorTotemSpell() { -// config := shaman.newTotemSpellConfig(0.02, 8143) -// config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { -// shaman.TotemExpirations[EarthTotem] = sim.CurrentTime + time.Second*300 -// } -// shaman.TremorTotem = shaman.RegisterSpell(config) -// } - -// func (shaman *Shaman) registerStoneskinTotemSpell() { -// config := shaman.newTotemSpellConfig(0.1, 58753) -// config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { -// shaman.TotemExpirations[EarthTotem] = sim.CurrentTime + time.Second*300 -// } -// shaman.StoneskinTotem = shaman.RegisterSpell(config) -// } - // func (shaman *Shaman) registerCallOfTheElements() { // airTotem := shaman.getAirTotemSpell(shaman.Totems.Air) // earthTotem := shaman.getEarthTotemSpell(shaman.Totems.Earth) From 107c1ef8c4e8ee778b22b02cd968e47fc6f4a861 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 4 Feb 2024 16:46:32 -0500 Subject: [PATCH 02/18] implement totems + UI inputs --- proto/shaman.proto | 3 +- sim/shaman/air_totems.go | 81 ++++++++++++ sim/shaman/earth_totems.go | 6 +- sim/shaman/shaman.go | 8 +- sim/shaman/totems.go | 75 ----------- sim/shaman/water_totems.go | 120 ++++++++++++++++++ .../individual_sim_ui/settings_tab.ts | 4 +- ui/core/components/totem_inputs.ts | 79 ++++++++++-- ui/elemental_shaman/inputs.ts | 15 ++- ui/enhancement_shaman/inputs.ts | 17 ++- ui/restoration_shaman/inputs.ts | 17 ++- 11 files changed, 316 insertions(+), 109 deletions(-) create mode 100644 sim/shaman/air_totems.go create mode 100644 sim/shaman/water_totems.go diff --git a/proto/shaman.proto b/proto/shaman.proto index 4f44f1c04d..cee0798c1b 100644 --- a/proto/shaman.proto +++ b/proto/shaman.proto @@ -102,8 +102,7 @@ enum FireTotem { NoFireTotem = 0; MagmaTotem = 1; SearingTotem = 2; - TotemOfWrath = 3; - FlametongueTotem = 4; + FireNovaTotem = 3; } enum WaterTotem { diff --git a/sim/shaman/air_totems.go b/sim/shaman/air_totems.go new file mode 100644 index 0000000000..72e362a06b --- /dev/null +++ b/sim/shaman/air_totems.go @@ -0,0 +1,81 @@ +package shaman + +import ( + "time" + + "github.com/wowsims/sod/sim/core" +) + +const WindfuryTotemRanks = 3 + +var WindfuryTotemSpellId = [WindfuryTotemRanks + 1]int32{0, 8512, 10613, 10614} +var WindfuryTotemBonusDamage = [WindfuryTotemRanks + 1]float64{0, 122, 229, 315} +var WindfuryTotemManaCost = [WindfuryTotemRanks + 1]float64{0, 115, 175, 250} +var WindfuryTotemLevel = [WindfuryTotemRanks + 1]int{0, 32, 42, 52} + +func (shaman *Shaman) registerWindfuryTotemSpell() { + shaman.WindfuryTotem = make([]*core.Spell, WindfuryTotemRanks+1) + + for rank := 1; rank <= WindfuryTotemRanks; rank++ { + config := shaman.newWindfuryTotemSpellConfig(rank) + + if config.RequiredLevel <= int(shaman.Level) { + shaman.WindfuryTotem[rank] = shaman.RegisterSpell(config) + } + } +} + +func (shaman *Shaman) newWindfuryTotemSpellConfig(rank int) core.SpellConfig { + spellId := WindfuryTotemSpellId[rank] + // TODO: The sim won't respect the value of a totem dropped via the APL. It uses hard-coded values from buffs.go + // bonusDamage := WindfuryTotemBonusDamage[rank] + manaCost := WindfuryTotemManaCost[rank] + level := WindfuryTotemLevel[rank] + + duration := time.Second * 120 + + spell := shaman.newTotemSpellConfig(manaCost, spellId) + spell.RequiredLevel = level + spell.Rank = rank + spell.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { + shaman.TotemExpirations[AirTotem] = sim.CurrentTime + duration + } + return spell +} + +const GraceOfAirTotemRanks = 3 + +var GraceOfAirTotemSpellId = [GraceOfAirTotemRanks + 1]int32{0, 8835, 10627, 25359} +var GraceOfAirTotemBonusAgi = [GraceOfAirTotemRanks + 1]float64{0, 43, 67, 77} +var GraceOfAirTotemManaCost = [GraceOfAirTotemRanks + 1]float64{0, 155, 250, 310} +var GraceOfAirTotemLevel = [GraceOfAirTotemRanks + 1]int{0, 42, 56, 60} + +func (shaman *Shaman) registerGraceOfAirTotemSpell() { + shaman.GraceOfAirTotem = make([]*core.Spell, GraceOfAirTotemRanks+1) + + for rank := 1; rank <= GraceOfAirTotemRanks; rank++ { + config := shaman.newGraceOfAirTotemSpellConfig(rank) + + if config.RequiredLevel <= int(shaman.Level) { + shaman.GraceOfAirTotem[rank] = shaman.RegisterSpell(config) + } + } +} + +func (shaman *Shaman) newGraceOfAirTotemSpellConfig(rank int) core.SpellConfig { + spellId := GraceOfAirTotemSpellId[rank] + // TODO: The sim won't respect the value of a totem dropped via the APL. It uses hard-coded values from buffs.go + // bonusDamage := GraceOfAirTotemBonusAgi[rank] + manaCost := GraceOfAirTotemManaCost[rank] + level := GraceOfAirTotemLevel[rank] + + duration := time.Second * 120 + + spell := shaman.newTotemSpellConfig(manaCost, spellId) + spell.RequiredLevel = level + spell.Rank = rank + spell.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { + shaman.TotemExpirations[AirTotem] = sim.CurrentTime + duration + } + return spell +} diff --git a/sim/shaman/earth_totems.go b/sim/shaman/earth_totems.go index 2804284fcc..0d837fc4fe 100644 --- a/sim/shaman/earth_totems.go +++ b/sim/shaman/earth_totems.go @@ -35,8 +35,8 @@ func (shaman *Shaman) newStrengthOfEarthTotemSpellConfig(rank int) core.SpellCon spell.RequiredLevel = level spell.Rank = rank spell.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { - shaman.ActiveTotems[EarthTotem] = spell shaman.TotemExpirations[EarthTotem] = sim.CurrentTime + duration + shaman.ActiveTotems[EarthTotem] = spell } return spell } @@ -70,8 +70,8 @@ func (shaman *Shaman) newStoneskinTotemSpellConfig(rank int) core.SpellConfig { spell.RequiredLevel = level spell.Rank = rank spell.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { - shaman.ActiveTotems[EarthTotem] = spell shaman.TotemExpirations[EarthTotem] = sim.CurrentTime + duration + shaman.ActiveTotems[EarthTotem] = spell } return spell } @@ -85,8 +85,8 @@ func (shaman *Shaman) registerTremorTotemSpell() { spell := shaman.newTotemSpellConfig(manaCost, spellId) spell.RequiredLevel = level spell.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { - shaman.ActiveTotems[EarthTotem] = spell shaman.TotemExpirations[EarthTotem] = sim.CurrentTime + duration + shaman.ActiveTotems[EarthTotem] = spell } shaman.TremorTotem = shaman.RegisterSpell(spell) } diff --git a/sim/shaman/shaman.go b/sim/shaman/shaman.go index 68d7ee00b1..fc3121b230 100644 --- a/sim/shaman/shaman.go +++ b/sim/shaman/shaman.go @@ -115,8 +115,8 @@ type Shaman struct { HealingStreamTotem []*core.Spell ManaSpringTotem []*core.Spell - WindfuryTotem *core.Spell - GraceOfAirTotem *core.Spell + WindfuryTotem []*core.Spell + GraceOfAirTotem []*core.Spell // Healing Spells tidalWaveProc *core.Aura @@ -218,8 +218,8 @@ func (shaman *Shaman) Initialize() { shaman.registerSearingTotemSpell() shaman.registerMagmaTotemSpell() shaman.registerFireNovaTotemSpell() - // shaman.registerManaSpringTotemSpell() - // shaman.registerHealingStreamTotemSpell() + shaman.registerHealingStreamTotemSpell() + shaman.registerManaSpringTotemSpell() // shaman.registerWindfuryTotemSpell() // shaman.registerGraceofAirTotem() diff --git a/sim/shaman/totems.go b/sim/shaman/totems.go index 2e04f6abe8..23d8fa060a 100644 --- a/sim/shaman/totems.go +++ b/sim/shaman/totems.go @@ -22,81 +22,6 @@ func (shaman *Shaman) newTotemSpellConfig(flatCost float64, spellID int32) core. } } -// func (shaman *Shaman) registerWindfuryTotemSpell() { -// config := shaman.newTotemSpellConfig(0.11, 8512) -// config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { -// shaman.TotemExpirations[AirTotem] = sim.CurrentTime + time.Second*300 -// } -// shaman.WindfuryTotem = shaman.RegisterSpell(config) -// } - -// func (shaman *Shaman) registerManaSpringTotemSpell() { -// config := shaman.newTotemSpellConfig(0.04, 58774) -// config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { -// shaman.TotemExpirations[WaterTotem] = sim.CurrentTime + time.Second*300 -// } -// shaman.ManaSpringTotem = shaman.RegisterSpell(config) -// } - -// func (shaman *Shaman) registerHealingStreamTotemSpell() { -// config := shaman.newTotemSpellConfig(0.03, 58757) -// hsHeal := shaman.RegisterSpell(core.SpellConfig{ -// ActionID: core.ActionID{SpellID: 52042}, -// SpellSchool: core.SpellSchoolNature, -// ProcMask: core.ProcMaskEmpty, -// Flags: core.SpellFlagHelpful | core.SpellFlagNoOnCastComplete, -// DamageMultiplier: 1 + (.02 * float64(shaman.Talents.Purification)) + 0.15*float64(shaman.Talents.RestorativeTotems), -// CritMultiplier: 1, -// ThreatMultiplier: 1 - (float64(shaman.Talents.HealingGrace) * 0.05), -// ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { -// // TODO: find healing stream coeff -// healing := 25 + spell.HealingPower(target)*0.08272 -// spell.CalcAndDealHealing(sim, target, healing, spell.OutcomeHealing) -// }, -// }) -// config.Hot = core.DotConfig{ -// Aura: core.Aura{ -// Label: "HealingStreamHot", -// }, -// NumberOfTicks: 150, -// TickLength: time.Second * 2, -// OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { -// hsHeal.Cast(sim, target) -// }, -// } -// config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { -// shaman.TotemExpirations[WaterTotem] = sim.CurrentTime + time.Second*300 -// for _, agent := range shaman.Party.Players { -// spell.Hot(&agent.GetCharacter().Unit).Activate(sim) -// } -// } -// shaman.HealingStreamTotem = shaman.RegisterSpell(config) -// } - -// func (shaman *Shaman) registerTotemOfWrathSpell() { -// config := shaman.newTotemSpellConfig(0.05, 57722) -// config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { -// shaman.TotemExpirations[FireTotem] = sim.CurrentTime + time.Second*300 -// shaman.applyToWDebuff(sim) -// } -// shaman.TotemOfWrath = shaman.RegisterSpell(config) -// } - -// func (shaman *Shaman) applyToWDebuff(sim *core.Simulation) { -// for _, target := range sim.Encounter.TargetUnits { -// auraDef := core.TotemOfWrathDebuff(target) -// auraDef.Activate(sim) -// } -// } - -// func (shaman *Shaman) registerFlametongueTotemSpell() { -// config := shaman.newTotemSpellConfig(0.11, 58656) -// config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { -// shaman.TotemExpirations[FireTotem] = sim.CurrentTime + time.Second*300 -// } -// shaman.FlametongueTotem = shaman.RegisterSpell(config) -// } - // func (shaman *Shaman) registerCallOfTheElements() { // airTotem := shaman.getAirTotemSpell(shaman.Totems.Air) // earthTotem := shaman.getEarthTotemSpell(shaman.Totems.Earth) diff --git a/sim/shaman/water_totems.go b/sim/shaman/water_totems.go new file mode 100644 index 0000000000..04d871423f --- /dev/null +++ b/sim/shaman/water_totems.go @@ -0,0 +1,120 @@ +package shaman + +import ( + "time" + + "github.com/wowsims/sod/sim/core" +) + +const HealingStreamTotemRanks = 5 + +var HealingStreamTotemSpellId = [HealingStreamTotemRanks + 1]int32{0, 5394, 6375, 6377, 10462, 10463} +var HealingStreamTotemHealId = [HealingStreamTotemRanks + 1]int32{0, 5672, 6371, 6372, 10460, 10461} +var HealingStreamTotemBaseHealing = [HealingStreamTotemRanks + 1]float64{0, 6, 8, 10, 12, 14} +var HealingStreamTotemSpellCoeff = [HealingStreamTotemRanks + 1]float64{0, .022, .022, .022, .022, .022} +var HealingStreamTotemManaCost = [HealingStreamTotemRanks + 1]float64{0, 40, 50, 60, 70, 80} +var HealingStreamTotemLevel = [HealingStreamTotemRanks + 1]int{0, 20, 30, 40, 50, 60} + +func (shaman *Shaman) registerHealingStreamTotemSpell() { + shaman.HealingStreamTotem = make([]*core.Spell, HealingStreamTotemRanks+1) + + for rank := 1; rank <= HealingStreamTotemRanks; rank++ { + config := shaman.newHealingStreamTotemSpellConfig(rank) + + if config.RequiredLevel <= int(shaman.Level) { + shaman.HealingStreamTotem[rank] = shaman.RegisterSpell(config) + } + } +} + +func (shaman *Shaman) newHealingStreamTotemSpellConfig(rank int) core.SpellConfig { + spellId := HealingStreamTotemSpellId[rank] + healId := HealingStreamTotemHealId[rank] + baseHealing := HealingStreamTotemBaseHealing[rank] + spellCoeff := HealingStreamTotemSpellCoeff[rank] + manaCost := HealingStreamTotemManaCost[rank] + level := HealingStreamTotemLevel[rank] + + duration := time.Second * 60 + healInterval := time.Second * 2 + + config := shaman.newTotemSpellConfig(manaCost, spellId) + healSpell := shaman.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: healId}, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagHelpful | core.SpellFlagNoOnCastComplete, + + RequiredLevel: level, + Rank: rank, + + DamageMultiplier: 1 + (.02 * float64(shaman.Talents.Purification)) + 0.05*float64(shaman.Talents.RestorativeTotems), + CritMultiplier: 1, + ThreatMultiplier: 1 - (float64(shaman.Talents.HealingGrace) * 0.05), + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + healing := baseHealing + spellCoeff*spell.HealingPower(target) + spell.CalcAndDealHealing(sim, target, healing, spell.OutcomeHealing) + }, + }) + + config.Hot = core.DotConfig{ + Aura: core.Aura{ + Label: "HealingStreamHot", + }, + NumberOfTicks: int32(duration / healInterval), + TickLength: healInterval, + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + healSpell.Cast(sim, target) + }, + } + + config.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + shaman.TotemExpirations[WaterTotem] = sim.CurrentTime + duration + shaman.ActiveTotems[WaterTotem] = spell + + for _, agent := range shaman.Party.Players { + spell.Hot(&agent.GetCharacter().Unit).Activate(sim) + } + } + + return config +} + +const ManaSpringTotemRanks = 4 + +var ManaSpringTotemSpellId = [ManaSpringTotemRanks + 1]int32{0, 5675, 10495, 10496, 10497} +var ManaSpringTotemManaRestore = [ManaSpringTotemRanks + 1]int32{0, 4, 6, 8, 10} +var ManaSpringTotemManaCost = [ManaSpringTotemRanks + 1]float64{0, 40, 60, 80, 100} +var ManaSpringTotemLevel = [ManaSpringTotemRanks + 1]int{0, 26, 36, 46, 56} + +func (shaman *Shaman) registerManaSpringTotemSpell() { + shaman.ManaSpringTotem = make([]*core.Spell, ManaSpringTotemRanks+1) + + for rank := 1; rank <= ManaSpringTotemRanks; rank++ { + config := shaman.newManaSpringTotemSpellConfig(rank) + + if config.RequiredLevel <= int(shaman.Level) { + shaman.ManaSpringTotem[rank] = shaman.RegisterSpell(config) + } + } +} + +func (shaman *Shaman) newManaSpringTotemSpellConfig(rank int) core.SpellConfig { + spellId := ManaSpringTotemSpellId[rank] + // TODO: The sim won't respect the value of a totem dropped via the APL. It uses hard-coded values from buffs.go + // manaRestoreBase := ManaSpringTotemManaRestore[rank] + manaCost := ManaSpringTotemManaCost[rank] + level := ManaSpringTotemLevel[rank] + + duration := time.Second * 60 + + spell := shaman.newTotemSpellConfig(manaCost, spellId) + spell.RequiredLevel = level + spell.Rank = rank + spell.ApplyEffects = func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + shaman.TotemExpirations[WaterTotem] = sim.CurrentTime + duration + shaman.ActiveTotems[WaterTotem] = spell + } + return spell +} diff --git a/ui/core/components/individual_sim_ui/settings_tab.ts b/ui/core/components/individual_sim_ui/settings_tab.ts index 903511ad94..a6cd1ec8a2 100644 --- a/ui/core/components/individual_sim_ui/settings_tab.ts +++ b/ui/core/components/individual_sim_ui/settings_tab.ts @@ -196,10 +196,12 @@ export class SettingsTab extends SimTab { private buildOtherSettings() { // const column = this.simUI.isWithinRaidSim ? this.column4 : this.column2; const settings = this.simUI.individualConfig.otherInputs?.inputs.filter(inputs => - !inputs.extraCssClasses?.includes('within-raid-sim-hide') || true + !inputs.extraCssClasses || !inputs.extraCssClasses?.includes('within-raid-sim-hide') ) const swapSlots = this.simUI.individualConfig.itemSwapSlots || []; + + console.log(settings, swapSlots) if (settings.length || swapSlots.length) { const contentBlock = new ContentBlock(this.column2, 'other-settings', { header: { title: 'Other' } diff --git a/ui/core/components/totem_inputs.ts b/ui/core/components/totem_inputs.ts index d8fe33c4f2..b656f52b6d 100644 --- a/ui/core/components/totem_inputs.ts +++ b/ui/core/components/totem_inputs.ts @@ -9,7 +9,6 @@ import { WaterTotem, ShamanTotems, } from '../proto/shaman.js'; -import { ActionId } from '../proto_utils/action_id.js'; import { ShamanSpecs } from '../proto_utils/utils.js'; import { EventID, TypedEvent } from '../typed_event.js'; @@ -58,12 +57,12 @@ export function TotemsSection(parentElem: HTMLElement, simUI: IndividualSimUI a == b, zeroValue: FireTotem.NoFireTotem, - changedEvent: (player: Player) => player.specOptionsChangeEmitter, + changedEvent: (player: Player) => TypedEvent.onAny([player.specOptionsChangeEmitter, player.levelChangeEmitter]), getValue: (player: Player) => player.getSpecOptions().totems?.fire || FireTotem.NoFireTotem, setValue: (eventID: EventID, player: Player, newValue: number) => { const newOptions = player.getSpecOptions(); @@ -81,12 +80,12 @@ export function TotemsSection(parentElem: HTMLElement, simUI: IndividualSimUI ActionId.fromSpellId(58774), value: WaterTotem.ManaSpringTotem }, - { actionId: () => ActionId.fromSpellId(58757), value: WaterTotem.HealingStreamTotem }, + HealingStreamTotem, + ManaSpringTotem, ], equals: (a: WaterTotem, b: WaterTotem) => a == b, zeroValue: WaterTotem.NoWaterTotem, - changedEvent: (player: Player) => player.specOptionsChangeEmitter, + changedEvent: (player: Player) => TypedEvent.onAny([player.specOptionsChangeEmitter, player.levelChangeEmitter]), getValue: (player: Player) => player.getSpecOptions().totems?.water || WaterTotem.NoWaterTotem, setValue: (eventID: EventID, player: Player, newValue: number) => { const newOptions = player.getSpecOptions(); @@ -104,12 +103,12 @@ export function TotemsSection(parentElem: HTMLElement, simUI: IndividualSimUI ActionId.fromSpellId(8512), value: AirTotem.WindfuryTotem }, - { actionId: () => ActionId.fromSpellId(8835), value: AirTotem.GraceOfAirTotem }, + WindfuryTotem, + GraceOfAirTotem, ], equals: (a: AirTotem, b: AirTotem) => a == b, zeroValue: AirTotem.NoAirTotem, - changedEvent: (player: Player) => player.specOptionsChangeEmitter, + changedEvent: (player: Player) => TypedEvent.onAny([player.specOptionsChangeEmitter, player.levelChangeEmitter]), getValue: (player: Player) => player.getSpecOptions().totems?.air || AirTotem.NoAirTotem, setValue: (eventID: EventID, player: Player, newValue: number) => { const newOptions = player.getSpecOptions(); @@ -173,18 +172,70 @@ export const SearingTotem = { value: FireTotem.SearingTotem, }; -export const MagmaTotem = { - +export const FireNovaTotem = { + actionId: (player: Player) => player.getMatchingSpellActionId([ + { id: 1535, minLevel: 12, maxLevel: 21 }, + { id: 8498, minLevel: 22, maxLevel: 31 }, + { id: 8499, minLevel: 32, maxLevel: 41 }, + { id: 11314, minLevel: 42, maxLevel: 51 }, + { id: 11315, minLevel: 52 }, + ]), + value: FireTotem.FireNovaTotem, }; -export const FireNovaTotem = { - +export const MagmaTotem = { + actionId: (player: Player) => player.getMatchingSpellActionId([ + { id: 8190, minLevel: 26, maxLevel: 35 }, + { id: 10585, minLevel: 36, maxLevel: 45 }, + { id: 10586, minLevel: 46, maxLevel: 55 }, + { id: 10587, minLevel: 56 }, + ]), + value: FireTotem.FireNovaTotem, }; /////////////////////////////////////////////////////////////////////////// // Water Totems /////////////////////////////////////////////////////////////////////////// +export const HealingStreamTotem = { + actionId: (player: Player) => player.getMatchingSpellActionId([ + { id: 5394, minLevel: 20, maxLevel: 29 }, + { id: 6375, minLevel: 30, maxLevel: 39 }, + { id: 6377, minLevel: 40, maxLevel: 49 }, + { id: 10462, minLevel: 50, maxLevel: 59 }, + { id: 10463, minLevel: 60 }, + ]), + value: WaterTotem.HealingStreamTotem, +}; + +export const ManaSpringTotem = { + actionId: (player: Player) => player.getMatchingSpellActionId([ + { id: 5675, minLevel: 26, maxLevel: 35 }, + { id: 10495, minLevel: 36, maxLevel: 45 }, + { id: 10496, minLevel: 46, maxLevel: 55 }, + { id: 10497, minLevel: 56 }, + ]), + value: WaterTotem.ManaSpringTotem, +}; + /////////////////////////////////////////////////////////////////////////// // Air Totems /////////////////////////////////////////////////////////////////////////// + +export const WindfuryTotem = { + actionId: (player: Player) => player.getMatchingSpellActionId([ + { id: 8512, minLevel: 32, maxLevel: 41 }, + { id: 10613, minLevel: 42, maxLevel: 51 }, + { id: 25359, minLevel: 52 }, + ]), + value: AirTotem.WindfuryTotem, +}; + +export const GraceOfAirTotem = { + actionId: (player: Player) => player.getMatchingSpellActionId([ + { id: 10627, minLevel: 42, maxLevel: 55 }, + { id: 10627, minLevel: 56, maxLevel: 59 }, + { id: 25359, minLevel: 60 }, + ]), + value: AirTotem.GraceOfAirTotem, +}; diff --git a/ui/elemental_shaman/inputs.ts b/ui/elemental_shaman/inputs.ts index 0533de5e0b..000be09bdd 100644 --- a/ui/elemental_shaman/inputs.ts +++ b/ui/elemental_shaman/inputs.ts @@ -1,6 +1,6 @@ +import { Player } from '../core/player.js'; import { Spec } from '../core/proto/common.js'; import { ShamanShield } from '../core/proto/shaman.js'; -import { ActionId } from '../core/proto_utils/action_id.js'; import * as InputHelpers from '../core/components/input_helpers.js'; @@ -11,7 +11,16 @@ export const ShamanShieldInput = InputHelpers.makeSpecOptionsEnumIconInput ActionId.fromSpellId(57960), value: ShamanShield.WaterShield }, - { actionId: () => ActionId.fromSpellId(49281), value: ShamanShield.LightningShield }, + { + actionId: (player: Player) => player.getMatchingSpellActionId([ + { id: 324, minLevel: 8, maxLevel: 15 }, + { id: 325, minLevel: 16, maxLevel: 23 }, + { id: 905, minLevel: 24, maxLevel: 31 }, + { id: 945, minLevel: 32, maxLevel: 39 }, + { id: 8134, minLevel: 40, maxLevel: 47 }, + { id: 10431, minLevel: 48, maxLevel: 55 }, + { id: 10432, minLevel: 56 }, + ]), + value: ShamanShield.LightningShield }, ], }); diff --git a/ui/enhancement_shaman/inputs.ts b/ui/enhancement_shaman/inputs.ts index 8ce3e3ecc0..d59caba287 100644 --- a/ui/enhancement_shaman/inputs.ts +++ b/ui/enhancement_shaman/inputs.ts @@ -1,3 +1,4 @@ +import { Player } from '../core/player.js'; import { Spec } from '../core/proto/common.js'; import { ShamanImbue, @@ -11,15 +12,25 @@ import * as InputHelpers from '../core/components/input_helpers.js'; // Configuration for spec-specific UI elements on the settings tab. // These don't need to be in a separate file but it keeps things cleaner. -export const ShamanShieldInput = InputHelpers.makeSpecOptionsEnumIconInput({ +export const ShamanShieldInput = InputHelpers.makeSpecOptionsEnumIconInput({ fieldName: 'shield', values: [ { value: ShamanShield.NoShield, tooltip: 'No Shield' }, - { actionId: () => ActionId.fromSpellId(57960), value: ShamanShield.WaterShield }, - { actionId: () => ActionId.fromSpellId(49281), value: ShamanShield.LightningShield }, + { + actionId: (player: Player) => player.getMatchingSpellActionId([ + { id: 324, minLevel: 8, maxLevel: 15 }, + { id: 325, minLevel: 16, maxLevel: 23 }, + { id: 905, minLevel: 24, maxLevel: 31 }, + { id: 945, minLevel: 32, maxLevel: 39 }, + { id: 8134, minLevel: 40, maxLevel: 47 }, + { id: 10431, minLevel: 48, maxLevel: 55 }, + { id: 10432, minLevel: 56 }, + ]), + value: ShamanShield.LightningShield }, ], }); + export const ShamanImbueMH = InputHelpers.makeSpecOptionsEnumIconInput({ fieldName: 'imbueMh', values: [ diff --git a/ui/restoration_shaman/inputs.ts b/ui/restoration_shaman/inputs.ts index 2c6e18f821..28ceb0fb5d 100644 --- a/ui/restoration_shaman/inputs.ts +++ b/ui/restoration_shaman/inputs.ts @@ -1,5 +1,5 @@ +import { Player } from '../core/player.js'; import { Spec } from '../core/proto/common.js'; -import { ActionId } from '../core/proto_utils/action_id.js'; import { ShamanShield, @@ -10,11 +10,20 @@ import * as InputHelpers from '../core/components/input_helpers.js'; // Configuration for spec-specific UI elements on the settings tab. // These don't need to be in a separate file but it keeps things cleaner. -export const ShamanShieldInput = InputHelpers.makeSpecOptionsEnumIconInput({ +export const ShamanShieldInput = InputHelpers.makeSpecOptionsEnumIconInput({ fieldName: 'shield', values: [ { value: ShamanShield.NoShield, tooltip: 'No Shield' }, - { actionId: () => ActionId.fromSpellId(57960), value: ShamanShield.WaterShield }, - { actionId: () => ActionId.fromSpellId(49281), value: ShamanShield.LightningShield }, + { + actionId: (player: Player) => player.getMatchingSpellActionId([ + { id: 324, minLevel: 8, maxLevel: 15 }, + { id: 325, minLevel: 16, maxLevel: 23 }, + { id: 905, minLevel: 24, maxLevel: 31 }, + { id: 945, minLevel: 32, maxLevel: 39 }, + { id: 8134, minLevel: 40, maxLevel: 47 }, + { id: 10431, minLevel: 48, maxLevel: 55 }, + { id: 10432, minLevel: 56 }, + ]), + value: ShamanShield.LightningShield }, ], }); From cec35b01e3ce0da5c407ff2fbf28fc3d2f3a5aac Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 4 Feb 2024 17:40:54 -0500 Subject: [PATCH 03/18] add UI shaman shield + weapon imbue inputs --- proto/shaman.proto | 7 ++ sim/shaman/elemental/elemental_test.go | 2 +- sim/shaman/weapon_imbues.go | 50 +++++------ .../individual_sim_ui/apl_helpers.ts | 2 +- .../individual_sim_ui/apl_rotation_picker.ts | 2 +- .../individual_sim_ui/settings_tab.ts | 1 - ui/core/components/inputs/consumables.ts | 24 +++--- ui/core/components/inputs/shaman_imbues.ts | 84 +++++++++++++++++++ ui/core/components/inputs/shaman_shields.ts | 28 +++++++ .../components/{ => inputs}/string_picker.ts | 4 +- .../components/{ => inputs}/totem_inputs.ts | 18 ++-- ui/elemental_shaman/inputs.ts | 24 ------ ui/elemental_shaman/sim.ts | 9 +- ui/enhancement_shaman/inputs.ts | 47 +---------- ui/enhancement_shaman/sim.ts | 19 ++--- ui/restoration_shaman/inputs.ts | 27 ------ ui/restoration_shaman/sim.ts | 8 +- 17 files changed, 189 insertions(+), 167 deletions(-) create mode 100644 ui/core/components/inputs/shaman_imbues.ts create mode 100644 ui/core/components/inputs/shaman_shields.ts rename ui/core/components/{ => inputs}/string_picker.ts (95%) rename ui/core/components/{ => inputs}/totem_inputs.ts (94%) diff --git a/proto/shaman.proto b/proto/shaman.proto index cee0798c1b..9614ca1cab 100644 --- a/proto/shaman.proto +++ b/proto/shaman.proto @@ -154,6 +154,7 @@ enum ShamanImbue { WindfuryWeapon = 1; FlametongueWeapon = 2; FrostbrandWeapon = 3; + RockbiterWeapon = 4; } enum ShamanSyncType { @@ -167,9 +168,12 @@ message ElementalShaman { message Rotation { } + // NextIndex: 6 message Options { ShamanShield shield = 1; ShamanTotems totems = 3; + ShamanImbue imbue_mh = 4; + ShamanImbue imbue_oh = 5; } Options options = 3; } @@ -178,6 +182,7 @@ message EnhancementShaman { message Rotation { } + // NextIndex: 7 message Options { ShamanShield shield = 1; bool bloodlust = 2 [deprecated = true]; @@ -200,9 +205,11 @@ message RestorationShaman { message Rotation { } + // NextIndex: 8 message Options { ShamanShield shield = 1; ShamanImbue imbue_mh = 4; + ShamanImbue imbue_oh = 7; int32 earth_shield_p_p_m = 5; ShamanTotems totems = 6; } diff --git a/sim/shaman/elemental/elemental_test.go b/sim/shaman/elemental/elemental_test.go index 4681a5f3d7..6ed631e24d 100644 --- a/sim/shaman/elemental/elemental_test.go +++ b/sim/shaman/elemental/elemental_test.go @@ -85,7 +85,7 @@ var BasicTotems = &proto.ShamanTotems{ Earth: proto.EarthTotem_TremorTotem, Air: proto.AirTotem_WindfuryTotem, Water: proto.WaterTotem_ManaSpringTotem, - Fire: proto.FireTotem_TotemOfWrath, + Fire: proto.FireTotem_SearingTotem, } var PlayerOptionsAdaptive = &proto.Player_ElementalShaman{ diff --git a/sim/shaman/weapon_imbues.go b/sim/shaman/weapon_imbues.go index 20e9325ca9..c0986d6ea6 100644 --- a/sim/shaman/weapon_imbues.go +++ b/sim/shaman/weapon_imbues.go @@ -1,33 +1,27 @@ package shaman -// import ( -// "time" - -// "github.com/wowsims/sod/sim/core" -// "github.com/wowsims/sod/sim/core/stats" -// ) - -// var TotemOfTheAstralWinds int32 = 27815 -// var TotemOfSplintering int32 = 40710 - -// func (shaman *Shaman) RegisterOnItemSwapWithImbue(effectID int32, procMask *core.ProcMask, aura *core.Aura) { -// shaman.RegisterOnItemSwap(func(sim *core.Simulation) { -// mask := core.ProcMaskUnknown -// if shaman.MainHand().TempEnchant == effectID { -// mask |= core.ProcMaskMeleeMH -// } -// if shaman.OffHand().TempEnchant == effectID { -// mask |= core.ProcMaskMeleeOH -// } -// *procMask = mask - -// if mask == core.ProcMaskUnknown { -// aura.Deactivate(sim) -// } else { -// aura.Activate(sim) -// } -// }) -// } +import ( + "github.com/wowsims/sod/sim/core" +) + +func (shaman *Shaman) RegisterOnItemSwapWithImbue(effectID int32, procMask *core.ProcMask, aura *core.Aura) { + shaman.RegisterOnItemSwap(func(sim *core.Simulation) { + mask := core.ProcMaskUnknown + if shaman.MainHand().TempEnchant == effectID { + mask |= core.ProcMaskMeleeMH + } + if shaman.OffHand().TempEnchant == effectID { + mask |= core.ProcMaskMeleeOH + } + *procMask = mask + + if mask == core.ProcMaskUnknown { + aura.Deactivate(sim) + } else { + aura.Activate(sim) + } + }) +} // func (shaman *Shaman) newWindfuryImbueSpell(isMH bool) *core.Spell { // apBonus := 1250.0 diff --git a/ui/core/components/individual_sim_ui/apl_helpers.ts b/ui/core/components/individual_sim_ui/apl_helpers.ts index 04bf5138f8..e2b5851822 100644 --- a/ui/core/components/individual_sim_ui/apl_helpers.ts +++ b/ui/core/components/individual_sim_ui/apl_helpers.ts @@ -3,7 +3,7 @@ import { ActionId, defaultTargetIcon, getPetIconFromName } from '../../proto_uti import { Player, UnitMetadata } from '../../player.js'; import { EventID, TypedEvent } from '../../typed_event.js'; import { bucket } from '../../utils.js'; -import { AdaptiveStringPicker } from '../string_picker.js'; +import { AdaptiveStringPicker } from '../inputs/string_picker.js'; import { NumberPicker, NumberPickerConfig } from '../number_picker.js'; import { DropdownPicker, DropdownPickerConfig, DropdownValueConfig, TextDropdownPicker } from '../dropdown_picker.js'; import { UnitPicker, UnitPickerConfig, UnitValue } from '../unit_picker.js'; diff --git a/ui/core/components/individual_sim_ui/apl_rotation_picker.ts b/ui/core/components/individual_sim_ui/apl_rotation_picker.ts index 0b669b4ec4..9d2659a385 100644 --- a/ui/core/components/individual_sim_ui/apl_rotation_picker.ts +++ b/ui/core/components/individual_sim_ui/apl_rotation_picker.ts @@ -9,7 +9,7 @@ import { } from '../../proto/apl.js'; import { EventID, TypedEvent } from '../../typed_event.js'; import { ListItemPickerConfig, ListPicker } from '../list_picker.js'; -import { AdaptiveStringPicker } from '../string_picker.js'; +import { AdaptiveStringPicker } from '../inputs/string_picker.js'; import { ActionId } from '../../proto_utils/action_id.js'; import { SimUI } from '../../sim_ui.js'; diff --git a/ui/core/components/individual_sim_ui/settings_tab.ts b/ui/core/components/individual_sim_ui/settings_tab.ts index a6cd1ec8a2..b152bdcd79 100644 --- a/ui/core/components/individual_sim_ui/settings_tab.ts +++ b/ui/core/components/individual_sim_ui/settings_tab.ts @@ -201,7 +201,6 @@ export class SettingsTab extends SimTab { const swapSlots = this.simUI.individualConfig.itemSwapSlots || []; - console.log(settings, swapSlots) if (settings.length || swapSlots.length) { const contentBlock = new ContentBlock(this.column2, 'other-settings', { header: { title: 'Other' } diff --git a/ui/core/components/inputs/consumables.ts b/ui/core/components/inputs/consumables.ts index 6eaaf9693e..a5c1221980 100644 --- a/ui/core/components/inputs/consumables.ts +++ b/ui/core/components/inputs/consumables.ts @@ -468,6 +468,14 @@ export const makeshadowPowerConsumeInput = makeConsumeInputFactory({consumesFiel // Weapon Imbues /////////////////////////////////////////////////////////////////////////// +// Wild Strikes +export const WildStrikes: ConsumableInputConfig = { + actionId: () => ActionId.fromSpellId(407975), + value: WeaponImbue.WildStrikes, +}; + +// Other Imbues + export const ElementalSharpeningStone: ConsumableInputConfig = { actionId: (player) => player.getMatchingItemActionId([ { id: 18262, minLevel: 50 }, @@ -502,23 +510,19 @@ export const BlackfathomSharpeningStone: ConsumableInputConfig = { actionId: () => ActionId.fromItemId(211845), value: WeaponImbue.BlackfathomSharpeningStone, }; -export const WildStrikes: ConsumableInputConfig = { - actionId: () => ActionId.fromSpellId(407975), - value: WeaponImbue.WildStrikes, -}; export const WEAPON_IMBUES_OH_CONFIG: ConsumableStatOption[] = [ { config: ElementalSharpeningStone, stats: [Stat.StatAttackPower] }, - { config: BrillianWizardOil, stats: [Stat.StatSpellPower] }, - { config: BrilliantManaOil, stats: [Stat.StatHealing, Stat.StatSpellPower] }, - { config: DenseSharpeningStone, stats: [Stat.StatAttackPower] }, - { config: BlackfathomManaOil, stats: [Stat.StatSpellPower, Stat.StatMP5] }, - { config: BlackfathomSharpeningStone, stats: [Stat.StatMeleeHit] }, + { config: BrillianWizardOil, stats: [Stat.StatSpellPower] }, + { config: BrilliantManaOil, stats: [Stat.StatHealing, Stat.StatSpellPower] }, + { config: DenseSharpeningStone, stats: [Stat.StatAttackPower] }, + { config: BlackfathomManaOil, stats: [Stat.StatSpellPower, Stat.StatMP5] }, + { config: BlackfathomSharpeningStone, stats: [Stat.StatMeleeHit] }, ]; export const WEAPON_IMBUES_MH_CONFIG: ConsumableStatOption[] = [ - ...WEAPON_IMBUES_OH_CONFIG, { config: WildStrikes, stats: [Stat.StatMeleeHit] }, + ...WEAPON_IMBUES_OH_CONFIG, ]; export const makeMainHandImbuesInput = makeConsumeInputFactory({ diff --git a/ui/core/components/inputs/shaman_imbues.ts b/ui/core/components/inputs/shaman_imbues.ts new file mode 100644 index 0000000000..b3692f7326 --- /dev/null +++ b/ui/core/components/inputs/shaman_imbues.ts @@ -0,0 +1,84 @@ +import { ItemSlot, Spec } from "../../proto/common.js"; +import { ShamanImbue } from "../../proto/shaman"; +import { TypedEvent } from "../../typed_event.js"; + +import { ConsumableInputConfig } from "./consumables"; + +import * as InputHelpers from '../../components/input_helpers.js'; + +// Shaman Imbues +export const RockbiterWeaponImbue: ConsumableInputConfig = { + actionId: (player) => player.getMatchingSpellActionId([ + { id: 8017, minLevel: 1, maxLevel: 7 }, + { id: 8018, minLevel: 8, maxLevel: 15 }, + { id: 8019, minLevel: 16, maxLevel: 23 }, + { id: 10399, minLevel: 24, maxLevel: 33 }, + { id: 16314, minLevel: 34, maxLevel: 43 }, + { id: 16315, minLevel: 44, maxLevel: 53 }, + { id: 16316, minLevel: 54 }, + ]), + value: ShamanImbue.RockbiterWeapon, +}; + +export const FlametongueWeaponImbue: ConsumableInputConfig = { + actionId: (player) => player.getMatchingSpellActionId([ + { id: 8024, minLevel: 10, maxLevel: 17 }, + { id: 8027, minLevel: 18, maxLevel: 25 }, + { id: 8030, minLevel: 26, maxLevel: 35 }, + { id: 16339, minLevel: 36, maxLevel: 45 }, + { id: 16341, minLevel: 46, maxLevel: 55 }, + { id: 16342, minLevel: 56 }, + ]), + value: ShamanImbue.FlametongueWeapon, +} + +export const WindfuryWeaponImbue: ConsumableInputConfig = { + actionId: (player) => player.getMatchingSpellActionId([ + { id: 8232, minLevel: 30, maxLevel: 39 }, + { id: 8235, minLevel: 40, maxLevel: 49 }, + { id: 10486, minLevel: 50, maxLevel: 59 }, + { id: 16362, minLevel: 60 }, + ]), + value: ShamanImbue.WindfuryWeapon, +} + +export const FrostbrandWeaponImbue: ConsumableInputConfig = { + actionId: (player) => player.getMatchingSpellActionId([ + { id: 8033, minLevel: 20, maxLevel: 27 }, + { id: 8038, minLevel: 28, maxLevel: 37 }, + { id: 10456, minLevel: 38, maxLevel: 47 }, + { id: 16355, minLevel: 48, maxLevel: 57 }, + { id: 16356, minLevel: 58, }, + ]), + value: ShamanImbue.FrostbrandWeapon, +} + +type ShamanSpec = Spec.SpecElementalShaman | Spec.SpecEnhancementShaman | Spec.SpecRestorationShaman + +export const ShamanImbueInputMH = () => + InputHelpers.makeSpecOptionsEnumIconInput({ + fieldName: 'imbueMh', + values: [ + { value: ShamanImbue.NoImbue, tooltip: 'No Main Hand Enchant' }, + RockbiterWeaponImbue, + WindfuryWeaponImbue, + FlametongueWeaponImbue, + FrostbrandWeaponImbue, + ], + showWhen: (player) => player.getEquippedItem(ItemSlot.ItemSlotMainHand) != null, + changeEmitter: (player) => TypedEvent.onAny([player.specOptionsChangeEmitter, player.levelChangeEmitter]), + }); + +export const ShamanImbueInputOH = () => + InputHelpers.makeSpecOptionsEnumIconInput({ + fieldName: 'imbueOh', + values: [ + { value: ShamanImbue.NoImbue, tooltip: 'No Off Hand Enchant' }, + RockbiterWeaponImbue, + WindfuryWeaponImbue, + FlametongueWeaponImbue, + FrostbrandWeaponImbue, + ], + showWhen: (player) => player.getEquippedItem(ItemSlot.ItemSlotOffHand) != null, + changeEmitter: (player) => TypedEvent.onAny([player.specOptionsChangeEmitter, player.levelChangeEmitter]), + }); diff --git a/ui/core/components/inputs/shaman_shields.ts b/ui/core/components/inputs/shaman_shields.ts new file mode 100644 index 0000000000..d8e19e4da4 --- /dev/null +++ b/ui/core/components/inputs/shaman_shields.ts @@ -0,0 +1,28 @@ +import { Player } from '../../player.js'; +import { Spec } from '../../proto/common.js'; +import { ShamanShield } from '../../proto/shaman.js'; +import { TypedEvent } from '../../typed_event.js'; + +import * as InputHelpers from '../../components/input_helpers.js'; + +type ShamanSpec = Spec.SpecElementalShaman | Spec.SpecEnhancementShaman | Spec.SpecRestorationShaman + +export const ShamanShieldInput = () => + InputHelpers.makeSpecOptionsEnumIconInput({ + fieldName: 'shield', + values: [ + { value: ShamanShield.NoShield, tooltip: 'No Shield' }, + { + actionId: (player: Player) => player.getMatchingSpellActionId([ + { id: 324, minLevel: 8, maxLevel: 15 }, + { id: 325, minLevel: 16, maxLevel: 23 }, + { id: 905, minLevel: 24, maxLevel: 31 }, + { id: 945, minLevel: 32, maxLevel: 39 }, + { id: 8134, minLevel: 40, maxLevel: 47 }, + { id: 10431, minLevel: 48, maxLevel: 55 }, + { id: 10432, minLevel: 56 }, + ]), + value: ShamanShield.LightningShield }, + ], + changeEmitter: (player) => TypedEvent.onAny([player.specOptionsChangeEmitter, player.levelChangeEmitter]), + }); diff --git a/ui/core/components/string_picker.ts b/ui/core/components/inputs/string_picker.ts similarity index 95% rename from ui/core/components/string_picker.ts rename to ui/core/components/inputs/string_picker.ts index 9a8aa032fa..64f79567aa 100644 --- a/ui/core/components/string_picker.ts +++ b/ui/core/components/inputs/string_picker.ts @@ -1,6 +1,6 @@ -import { EventID, TypedEvent } from '../typed_event.js'; +import { EventID, TypedEvent } from '../../typed_event.js'; -import { Input, InputConfig } from './input.js'; +import { Input, InputConfig } from '../input.js'; /** * Data for creating a string picker. diff --git a/ui/core/components/totem_inputs.ts b/ui/core/components/inputs/totem_inputs.ts similarity index 94% rename from ui/core/components/totem_inputs.ts rename to ui/core/components/inputs/totem_inputs.ts index b656f52b6d..309e7f6e7d 100644 --- a/ui/core/components/totem_inputs.ts +++ b/ui/core/components/inputs/totem_inputs.ts @@ -1,19 +1,19 @@ -import { IconEnumPicker } from '../components/icon_enum_picker.js'; -import { IndividualSimUI } from '../individual_sim_ui.js'; -import { Player } from '../player.js'; -import { Spec } from '../proto/common.js'; +import { IconEnumPicker } from '../icon_enum_picker.js'; +import { IndividualSimUI } from '../../individual_sim_ui.js'; +import { Player } from '../../player.js'; +import { Spec } from '../../proto/common.js'; import { AirTotem, EarthTotem, FireTotem, WaterTotem, ShamanTotems, -} from '../proto/shaman.js'; -import { ShamanSpecs } from '../proto_utils/utils.js'; -import { EventID, TypedEvent } from '../typed_event.js'; +} from '../../proto/shaman.js'; +import { ShamanSpecs } from '../../proto_utils/utils.js'; +import { EventID, TypedEvent } from '../../typed_event.js'; -import { ContentBlock } from './content_block.js'; -import { Input } from './input.js'; +import { ContentBlock } from '../content_block.js'; +import { Input } from '../input.js'; export function TotemsSection(parentElem: HTMLElement, simUI: IndividualSimUI): ContentBlock { let contentBlock = new ContentBlock(parentElem, 'totems-settings', { diff --git a/ui/elemental_shaman/inputs.ts b/ui/elemental_shaman/inputs.ts index 000be09bdd..47588684c9 100644 --- a/ui/elemental_shaman/inputs.ts +++ b/ui/elemental_shaman/inputs.ts @@ -1,26 +1,2 @@ -import { Player } from '../core/player.js'; -import { Spec } from '../core/proto/common.js'; -import { ShamanShield } from '../core/proto/shaman.js'; - -import * as InputHelpers from '../core/components/input_helpers.js'; - // Configuration for spec-specific UI elements on the settings tab. // These don't need to be in a separate file but it keeps things cleaner. - -export const ShamanShieldInput = InputHelpers.makeSpecOptionsEnumIconInput({ - fieldName: 'shield', - values: [ - { value: ShamanShield.NoShield, tooltip: 'No Shield' }, - { - actionId: (player: Player) => player.getMatchingSpellActionId([ - { id: 324, minLevel: 8, maxLevel: 15 }, - { id: 325, minLevel: 16, maxLevel: 23 }, - { id: 905, minLevel: 24, maxLevel: 31 }, - { id: 945, minLevel: 32, maxLevel: 39 }, - { id: 8134, minLevel: 40, maxLevel: 47 }, - { id: 10431, minLevel: 48, maxLevel: 55 }, - { id: 10432, minLevel: 56 }, - ]), - value: ShamanShield.LightningShield }, - ], -}); diff --git a/ui/elemental_shaman/sim.ts b/ui/elemental_shaman/sim.ts index 3f29d8713f..75e12b2f22 100644 --- a/ui/elemental_shaman/sim.ts +++ b/ui/elemental_shaman/sim.ts @@ -1,3 +1,6 @@ +import { ShamanImbueInputMH, ShamanImbueInputOH } from '../core/components/inputs/shaman_imbues.js'; +import { ShamanShieldInput } from '../core/components/inputs/shaman_shields.js'; +import { TotemsSection } from '../core/components/inputs/totem_inputs.js'; import { Class, Debuffs, @@ -17,11 +20,9 @@ import { Player } from '../core/player.js'; import { Stats } from '../core/proto_utils/stats.js'; import { getSpecIcon, specNames } from '../core/proto_utils/utils.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; -import { TotemsSection } from '../core/components/totem_inputs.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as Mechanics from '../core/constants/mechanics.js'; -import * as ShamanInputs from './inputs.js'; import * as Presets from './presets.js'; const SPEC_CONFIG = registerSpecConfig(Spec.SpecElementalShaman, { @@ -110,7 +111,9 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecElementalShaman, { }, // IconInputs to include in the 'Player' section on the settings tab. playerIconInputs: [ - ShamanInputs.ShamanShieldInput, + ShamanShieldInput(), + ShamanImbueInputMH(), + ShamanImbueInputOH(), ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ diff --git a/ui/enhancement_shaman/inputs.ts b/ui/enhancement_shaman/inputs.ts index d59caba287..582d0da784 100644 --- a/ui/enhancement_shaman/inputs.ts +++ b/ui/enhancement_shaman/inputs.ts @@ -1,56 +1,11 @@ -import { Player } from '../core/player.js'; import { Spec } from '../core/proto/common.js'; -import { - ShamanImbue, - ShamanShield, - ShamanSyncType -} from '../core/proto/shaman.js'; -import { ActionId } from '../core/proto_utils/action_id.js'; +import { ShamanSyncType } from '../core/proto/shaman.js'; import * as InputHelpers from '../core/components/input_helpers.js'; // Configuration for spec-specific UI elements on the settings tab. // These don't need to be in a separate file but it keeps things cleaner. -export const ShamanShieldInput = InputHelpers.makeSpecOptionsEnumIconInput({ - fieldName: 'shield', - values: [ - { value: ShamanShield.NoShield, tooltip: 'No Shield' }, - { - actionId: (player: Player) => player.getMatchingSpellActionId([ - { id: 324, minLevel: 8, maxLevel: 15 }, - { id: 325, minLevel: 16, maxLevel: 23 }, - { id: 905, minLevel: 24, maxLevel: 31 }, - { id: 945, minLevel: 32, maxLevel: 39 }, - { id: 8134, minLevel: 40, maxLevel: 47 }, - { id: 10431, minLevel: 48, maxLevel: 55 }, - { id: 10432, minLevel: 56 }, - ]), - value: ShamanShield.LightningShield }, - ], -}); - - -export const ShamanImbueMH = InputHelpers.makeSpecOptionsEnumIconInput({ - fieldName: 'imbueMh', - values: [ - { value: ShamanImbue.NoImbue, tooltip: 'No Main Hand Enchant' }, - { actionId: () => ActionId.fromSpellId(58804), value: ShamanImbue.WindfuryWeapon }, - { actionId: () => ActionId.fromSpellId(58790), value: ShamanImbue.FlametongueWeapon, text: 'R10' }, - { actionId: () => ActionId.fromSpellId(58796), value: ShamanImbue.FrostbrandWeapon }, - ], -}); - -export const ShamanImbueOH = InputHelpers.makeSpecOptionsEnumIconInput({ - fieldName: 'imbueOh', - values: [ - { value: ShamanImbue.NoImbue, tooltip: 'No Off Hand Enchant' }, - { actionId: () => ActionId.fromSpellId(58804), value: ShamanImbue.WindfuryWeapon }, - { actionId: () => ActionId.fromSpellId(58790), value: ShamanImbue.FlametongueWeapon, text: 'R10' }, - { actionId: () => ActionId.fromSpellId(58796), value: ShamanImbue.FrostbrandWeapon }, - ], -}); - export const SyncTypeInput = InputHelpers.makeSpecOptionsEnumInput({ fieldName: 'syncType', label: 'Sync/Stagger Setting', diff --git a/ui/enhancement_shaman/sim.ts b/ui/enhancement_shaman/sim.ts index 22681376e0..5da5528981 100644 --- a/ui/enhancement_shaman/sim.ts +++ b/ui/enhancement_shaman/sim.ts @@ -1,4 +1,6 @@ -import { TotemsSection } from '../core/components/totem_inputs.js'; +import { ShamanImbueInputMH, ShamanImbueInputOH } from '../core/components/inputs/shaman_imbues.js'; +import { ShamanShieldInput } from '../core/components/inputs/shaman_shields.js'; +import { TotemsSection } from '../core/components/inputs/totem_inputs.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; import { Player } from '../core/player.js'; import { APLRotation } from '../core/proto/apl.js'; @@ -113,9 +115,9 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecEnhancementShaman, { // IconInputs to include in the 'Player' section on the settings tab. playerIconInputs: [ - ShamanInputs.ShamanShieldInput, - ShamanInputs.ShamanImbueMH, - ShamanInputs.ShamanImbueOH, + ShamanShieldInput(), + ShamanImbueInputMH(), + ShamanImbueInputOH(), ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ @@ -162,16 +164,9 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecEnhancementShaman, { }, autoRotation: (player: Player): APLRotation => { - const hasT94P = player.getCurrentStats().sets.includes('Triumphant Nobundo\'s Battlegear (4pc)') - || player.getCurrentStats().sets.includes('Nobundo\'s Battlegear (4pc)') - || player.getCurrentStats().sets.includes('Triumphant Thrall\'s Battlegear (4pc)') - || player.getCurrentStats().sets.includes('Thrall\'s Battlegear (4pc)'); const options = player.getSpecOptions(); - if (hasT94P) { - console.log("has set"); - return Presets.ROTATION_PHASE_3.rotation.rotation!; - } else if (options.imbueMh == ShamanImbue.FlametongueWeapon) { + if (options.imbueMh == ShamanImbue.FlametongueWeapon) { return Presets.ROTATION_FT_DEFAULT.rotation.rotation!; } else { return Presets.ROTATION_WF_DEFAULT.rotation.rotation!; diff --git a/ui/restoration_shaman/inputs.ts b/ui/restoration_shaman/inputs.ts index 28ceb0fb5d..47588684c9 100644 --- a/ui/restoration_shaman/inputs.ts +++ b/ui/restoration_shaman/inputs.ts @@ -1,29 +1,2 @@ -import { Player } from '../core/player.js'; -import { Spec } from '../core/proto/common.js'; - -import { - ShamanShield, -} from '../core/proto/shaman.js'; - -import * as InputHelpers from '../core/components/input_helpers.js'; - // Configuration for spec-specific UI elements on the settings tab. // These don't need to be in a separate file but it keeps things cleaner. - -export const ShamanShieldInput = InputHelpers.makeSpecOptionsEnumIconInput({ - fieldName: 'shield', - values: [ - { value: ShamanShield.NoShield, tooltip: 'No Shield' }, - { - actionId: (player: Player) => player.getMatchingSpellActionId([ - { id: 324, minLevel: 8, maxLevel: 15 }, - { id: 325, minLevel: 16, maxLevel: 23 }, - { id: 905, minLevel: 24, maxLevel: 31 }, - { id: 945, minLevel: 32, maxLevel: 39 }, - { id: 8134, minLevel: 40, maxLevel: 47 }, - { id: 10431, minLevel: 48, maxLevel: 55 }, - { id: 10432, minLevel: 56 }, - ]), - value: ShamanShield.LightningShield }, - ], -}); diff --git a/ui/restoration_shaman/sim.ts b/ui/restoration_shaman/sim.ts index 2ea26cc4fe..1a2a753124 100644 --- a/ui/restoration_shaman/sim.ts +++ b/ui/restoration_shaman/sim.ts @@ -1,3 +1,6 @@ +import { ShamanImbueInputMH, ShamanImbueInputOH } from '../core/components/inputs/shaman_imbues.js'; +import { ShamanShieldInput } from '../core/components/inputs/shaman_shields.js'; +import { TotemsSection } from '../core/components/inputs/totem_inputs.js'; import { Class, Debuffs, @@ -17,7 +20,6 @@ import { Player } from '../core/player.js'; import { Stats } from '../core/proto_utils/stats.js'; import { getSpecIcon, specNames } from '../core/proto_utils/utils.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; -import { TotemsSection } from '../core/components/totem_inputs.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as Mechanics from '../core/constants/mechanics.js'; @@ -103,7 +105,9 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecRestorationShaman, { }, // IconInputs to include in the 'Player' section on the settings tab. playerIconInputs: [ - ShamanInputs.ShamanShieldInput, + ShamanShieldInput(), + ShamanImbueInputMH(), + ShamanImbueInputOH(), ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ From 83f6c52159e7f1f86b188abdcc4539b753d908fb Mon Sep 17 00:00:00 2001 From: Connor Bratten Date: Sun, 4 Feb 2024 15:48:03 -0500 Subject: [PATCH 04/18] Add p1 gear set --- .../gear_sets/phase_1.gear.json | 59 +++++++++++++++++++ ui/enhancement_shaman/presets.ts | 6 +- 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 ui/enhancement_shaman/gear_sets/phase_1.gear.json diff --git a/ui/enhancement_shaman/gear_sets/phase_1.gear.json b/ui/enhancement_shaman/gear_sets/phase_1.gear.json new file mode 100644 index 0000000000..e164536516 --- /dev/null +++ b/ui/enhancement_shaman/gear_sets/phase_1.gear.json @@ -0,0 +1,59 @@ +{ + "items": [ + { + "id": 211789 + }, + { + "id": 209422 + }, + { + "id": 14573 + }, + { + "id": 213087, + "rune": 410096 + }, + { + "id": 211512 + }, + { + "id": 209524 + }, + { + "id": 211423, + "rune": 410104 + }, + { + "id": 16659 + }, + { + "id": 10410, + "rune": 410107 + }, + { + "id": 211511 + }, + { + "id": 211467 + }, + { + "id": 13097 + }, + { + "id": 211449 + }, + { + "id": 21566 + }, + { + "id": 209436 + }, + { + "id": 1454 + }, + {}, + { + "id": 209575 + } + ] +} diff --git a/ui/enhancement_shaman/presets.ts b/ui/enhancement_shaman/presets.ts index 017433e9fe..2aef123d17 100644 --- a/ui/enhancement_shaman/presets.ts +++ b/ui/enhancement_shaman/presets.ts @@ -18,6 +18,7 @@ import { import * as PresetUtils from '../core/preset_utils.js'; import BlankGear from './gear_sets/blank.gear.json'; +import Phase1Gear from './gear_sets/phase_1.gear.json'; import DefaultFt from './apls/default_ft.apl.json'; import DefaultWf from './apls/default_wf.apl.json'; @@ -27,7 +28,10 @@ import Phase3Apl from './apls/phase_3.apl.json'; // Eventually we will import these values for the raid sim too, so its good to // keep them in a separate file. -export const DefaultGear = PresetUtils.makePresetGear('Blank', BlankGear); +export const BlankPresetGear = PresetUtils.makePresetGear('Blank', BlankGear); +export const Phase1PresetGear = PresetUtils.makePresetGear('Phase 1', Phase1Gear); + +export const DefaultGear = Phase1PresetGear export const ROTATION_FT_DEFAULT = PresetUtils.makePresetAPLRotation('Default FT', DefaultFt); export const ROTATION_WF_DEFAULT = PresetUtils.makePresetAPLRotation('Default WF', DefaultWf); From 3c04dcfa101caa2632285ecf0ed11bec5f68c63d Mon Sep 17 00:00:00 2001 From: Connor Bratten Date: Sun, 4 Feb 2024 16:07:40 -0500 Subject: [PATCH 05/18] Add enchants to p1 set --- .../gear_sets/phase_1.gear.json | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/ui/enhancement_shaman/gear_sets/phase_1.gear.json b/ui/enhancement_shaman/gear_sets/phase_1.gear.json index e164536516..c4121bdbde 100644 --- a/ui/enhancement_shaman/gear_sets/phase_1.gear.json +++ b/ui/enhancement_shaman/gear_sets/phase_1.gear.json @@ -11,27 +11,33 @@ }, { "id": 213087, - "rune": 410096 + "enchant": 247 }, { - "id": 211512 + "id": 211512, + "rune": 410096, + "enchant":847 }, { - "id": 209524 + "id": 209524, + "enchant": 823 }, { "id": 211423, - "rune": 410104 + "rune": 410104, + "enchant": 17 }, { "id": 16659 }, { "id": 10410, - "rune": 410107 + "rune": 410107, + "enchant": 17 }, { - "id": 211511 + "id": 211511, + "enchant": 17 }, { "id": 211467 @@ -43,13 +49,15 @@ "id": 211449 }, { - "id": 21566 + "id": 4381 }, { - "id": 209436 + "id": 209436, + "enchant": 241 }, { - "id": 1454 + "id": 1454, + "enchant": 241 }, {}, { From 2fa43d0148f136ab2162e482f03dfabc00c2b2ba Mon Sep 17 00:00:00 2001 From: Connor Bratten Date: Sun, 4 Feb 2024 18:58:10 -0500 Subject: [PATCH 06/18] Remove armor kits and fix leg rune --- ui/enhancement_shaman/gear_sets/phase_1.gear.json | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ui/enhancement_shaman/gear_sets/phase_1.gear.json b/ui/enhancement_shaman/gear_sets/phase_1.gear.json index c4121bdbde..86813ec11e 100644 --- a/ui/enhancement_shaman/gear_sets/phase_1.gear.json +++ b/ui/enhancement_shaman/gear_sets/phase_1.gear.json @@ -24,20 +24,17 @@ }, { "id": 211423, - "rune": 410104, - "enchant": 17 + "rune": 410104 }, { "id": 16659 }, { "id": 10410, - "rune": 410107, - "enchant": 17 + "rune": 425343 }, { - "id": 211511, - "enchant": 17 + "id": 211511 }, { "id": 211467 From b14ba296c85726221245f3b739ac9f5fa9c2a163 Mon Sep 17 00:00:00 2001 From: Connor Bratten Date: Sun, 4 Feb 2024 19:29:59 -0500 Subject: [PATCH 07/18] Fix runes --- ui/enhancement_shaman/gear_sets/phase_1.gear.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/enhancement_shaman/gear_sets/phase_1.gear.json b/ui/enhancement_shaman/gear_sets/phase_1.gear.json index 86813ec11e..c36aefa98d 100644 --- a/ui/enhancement_shaman/gear_sets/phase_1.gear.json +++ b/ui/enhancement_shaman/gear_sets/phase_1.gear.json @@ -15,7 +15,7 @@ }, { "id": 211512, - "rune": 410096, + "rune": 408496, "enchant":847 }, { @@ -24,14 +24,14 @@ }, { "id": 211423, - "rune": 410104 + "rune": 408507 }, { "id": 16659 }, { "id": 10410, - "rune": 425343 + "rune": 425336 }, { "id": 211511 From 843a513ae508d23c53791baca40f9a1161866071 Mon Sep 17 00:00:00 2001 From: rosenrusinov Date: Mon, 5 Feb 2024 01:47:08 +0100 Subject: [PATCH 08/18] add siphon life, dark pact and conflagate --- sim/warlock/_conflagrate.go | 86 --------------------------- sim/warlock/_dark_pact.go | 43 -------------- sim/warlock/conflagrate.go | 80 +++++++++++++++++++++++++ sim/warlock/corruption.go | 2 +- sim/warlock/curses.go | 2 +- sim/warlock/dark_pact.go | 58 ++++++++++++++++++ sim/warlock/drain_life.go | 2 +- sim/warlock/pet.go | 114 +++++++++++++++++++++++++++++------- sim/warlock/siphon_life.go | 101 ++++++++++++++++++++++++++++++++ sim/warlock/warlock.go | 22 +++---- 10 files changed, 345 insertions(+), 165 deletions(-) delete mode 100644 sim/warlock/_conflagrate.go delete mode 100644 sim/warlock/_dark_pact.go create mode 100644 sim/warlock/conflagrate.go create mode 100644 sim/warlock/dark_pact.go create mode 100644 sim/warlock/siphon_life.go diff --git a/sim/warlock/_conflagrate.go b/sim/warlock/_conflagrate.go deleted file mode 100644 index f851bdc876..0000000000 --- a/sim/warlock/_conflagrate.go +++ /dev/null @@ -1,86 +0,0 @@ -package warlock - -import ( - "time" - - "github.com/wowsims/sod/sim/core" -) - -func (warlock *Warlock) registerConflagrateSpell() { - if !warlock.Talents.Conflagrate { - return - } - - warlock.Conflagrate = warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 17962}, - SpellSchool: core.SpellSchoolFire, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.16, - Multiplier: 1 - []float64{0, .04, .07, .10}[warlock.Talents.Cataclysm], - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - CD: core.Cooldown{ - Timer: warlock.NewTimer(), - Duration: time.Second * 10, - }, - }, - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return warlock.Immolate.Dot(target).IsActive() - }, - - BonusCritRating: 0 + - core.TernaryFloat64(warlock.Talents.Devastation, 5*core.CritRatingPerCritChance, 0) + - 5*float64(warlock.Talents.FireAndBrimstone)*core.CritRatingPerCritChance, - DamageMultiplierAdditive: 1 + - warlock.GrandFirestoneBonus() + - 0.03*float64(warlock.Talents.Emberstorm) + - 0.03*float64(warlock.Talents.Aftermath) + - 0.1*float64(warlock.Talents.ImprovedImmolate) + - core.TernaryFloat64(warlock.HasSetBonus(ItemSetDeathbringerGarb, 2), 0.1, 0) + - core.TernaryFloat64(warlock.HasSetBonus(ItemSetGuldansRegalia, 4), 0.1, 0), - CritMultiplier: warlock.SpellCritMultiplier(1, float64(warlock.Talents.Ruin)/5), - ThreatMultiplier: 1 - 0.1*float64(warlock.Talents.DestructiveReach), - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Conflagrate", - }, - NumberOfTicks: 3, - TickLength: time.Second * 2, - - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.SnapshotBaseDamage = (314.0 / 3) + (0.4/3)*dot.Spell.SpellPower() - attackTable := dot.Spell.Unit.AttackTables[target.UnitIndex] - dot.SnapshotCritChance = dot.Spell.SpellCritChance(target) - - // DoT does not benefit from firestone and also not from spellstone - dot.Spell.DamageMultiplierAdditive -= warlock.GrandFirestoneBonus() - dot.SnapshotAttackerMultiplier = dot.Spell.AttackerDamageMultiplier(attackTable) - dot.Spell.DamageMultiplierAdditive += warlock.GrandFirestoneBonus() - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - // takes the SP of the immolate (or shadowflame) dot on the target - baseDamage := 471.0 + 0.6*warlock.Immolate.Dot(target).Spell.SpellPower() - result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - if !result.Landed() { - return - } - - spell.Dot(target).Apply(sim) - - warlock.Immolate.Dot(target).Deactivate(sim) - //warlock.ShadowflameDot.Deactivate(sim) - }, - }) -} diff --git a/sim/warlock/_dark_pact.go b/sim/warlock/_dark_pact.go deleted file mode 100644 index 4c243324f5..0000000000 --- a/sim/warlock/_dark_pact.go +++ /dev/null @@ -1,43 +0,0 @@ -package warlock - -import ( - "math" - - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/stats" -) - -func (warlock *Warlock) registerDarkPactSpell() { - if !warlock.Talents.DarkPact { - return - } - - actionID := core.ActionID{SpellID: 59092} - baseRestore := 1200.0 - manaMetrics := warlock.NewManaMetrics(actionID) - petManaMetrics := warlock.Pet.NewManaMetrics(actionID) - - warlock.DarkPact = warlock.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagAPL, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - ThreatMultiplier: 1 - 0.1*float64(warlock.Talents.ImprovedDrainSoul), - FlatThreatBonus: 80, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - maxDrain := baseRestore + 0.96*warlock.GetStat(stats.SpellPower) - actualDrain := math.Min(maxDrain, warlock.Pet.CurrentMana()) - - warlock.Pet.SpendMana(sim, actualDrain, petManaMetrics) - warlock.AddMana(sim, actualDrain, manaMetrics) - }, - }) -} diff --git a/sim/warlock/conflagrate.go b/sim/warlock/conflagrate.go new file mode 100644 index 0000000000..7d7b3e48e1 --- /dev/null +++ b/sim/warlock/conflagrate.go @@ -0,0 +1,80 @@ +package warlock + +import ( + "time" + + "github.com/wowsims/sod/sim/core" +) + +func (warlock *Warlock) getConflagrateConfig(rank int) core.SpellConfig { + spellId := [5]int32{0, 17962, 18930, 18931, 18932}[rank] + baseDamageMin := [5]float64{0, 249, 319, 395, 447}[rank] + baseDamageMax := [5]float64{0, 316, 400, 491, 557}[rank] + manaCost := [5]float64{0, 165, 200, 230, 255}[rank] + level := [5]int{0, 0, 48, 54, 60}[rank] + + spCoeff := 0.429 + + return core.SpellConfig{ + ActionID: core.ActionID{SpellID: spellId}, + SpellSchool: core.SpellSchoolFire, + ProcMask: core.ProcMaskSpellDamage, + Flags: core.SpellFlagAPL, + Rank: rank, + RequiredLevel: level, + + ManaCost: core.ManaCostOptions{ + FlatCost: manaCost, + Multiplier: 1 - float64(warlock.Talents.Cataclysm)*0.01, + }, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + CD: core.Cooldown{ + Timer: warlock.NewTimer(), + Duration: time.Second * 10, + }, + }, + ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { + return warlock.Immolate.Dot(target).IsActive() + }, + + BonusCritRating: 0 + float64(warlock.Talents.Devastation)*core.CritRatingPerCritChance, + DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.Emberstorm), + CritMultiplier: warlock.SpellCritMultiplier(1, core.TernaryFloat64(warlock.Talents.Ruin, 1, 0)), + ThreatMultiplier: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + // takes the SP of the immolate (or shadowflame) dot on the target + baseDamage := sim.Roll(baseDamageMin, baseDamageMax) + spCoeff*spell.SpellPower() + + if warlock.LakeOfFireAuras != nil && warlock.LakeOfFireAuras.Get(target).IsActive() { + baseDamage *= 1.4 + } + + result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) + if !result.Landed() { + return + } + warlock.Immolate.Dot(target).Deactivate(sim) + //warlock.ShadowflameDot.Deactivate(sim) + }, + } +} + +func (warlock *Warlock) registerConflagrateSpell() { + if !warlock.Talents.Conflagrate { + return + } + + maxRank := 4 + + for i := 1; i <= maxRank; i++ { + config := warlock.getConflagrateConfig(i) + + if config.RequiredLevel <= int(warlock.Level) { + warlock.Conflagrate = warlock.GetOrRegisterSpell(config) + } + } +} diff --git a/sim/warlock/corruption.go b/sim/warlock/corruption.go index 778ca728ac..c0ec541f7d 100644 --- a/sim/warlock/corruption.go +++ b/sim/warlock/corruption.go @@ -37,7 +37,7 @@ func (warlock *Warlock) getCorruptionConfig(rank int) core.SpellConfig { }, }, - BonusHitRating: float64(warlock.Talents.Suppression) * 2 * core.CritRatingPerCritChance, + BonusHitRating: float64(warlock.Talents.Suppression) * 2 * core.SpellHitRatingPerHitChance, BonusCritRating: 0, DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.ShadowMastery), diff --git a/sim/warlock/curses.go b/sim/warlock/curses.go index 9d5d8191ac..42537ca337 100644 --- a/sim/warlock/curses.go +++ b/sim/warlock/curses.go @@ -33,7 +33,7 @@ func (warlock *Warlock) getCurseOfAgonyBaseConfig(rank int) core.SpellConfig { }, }, - BonusHitRating: 2 * float64(warlock.Talents.Suppression) * core.CritRatingPerCritChance, + BonusHitRating: 2 * float64(warlock.Talents.Suppression) * core.SpellHitRatingPerHitChance, DamageMultiplier: 1 * (1 + 0.02*float64(warlock.Talents.ImprovedCurseOfWeakness)) * (1 + 0.02*float64(warlock.Talents.ShadowMastery)), diff --git a/sim/warlock/dark_pact.go b/sim/warlock/dark_pact.go new file mode 100644 index 0000000000..475bebcd1d --- /dev/null +++ b/sim/warlock/dark_pact.go @@ -0,0 +1,58 @@ +package warlock + +import ( + "math" + + "github.com/wowsims/sod/sim/core" +) + +func (warlock *Warlock) getDarkPactConfig(rank int) core.SpellConfig { + spellId := [4]int32{0, 18220, 18937, 18938}[rank] + manaRestore := [4]float64{0, 150, 200, 250}[rank] + level := [4]int{0, 0, 50, 60}[rank] + + actionID := core.ActionID{SpellID: spellId} + manaMetrics := warlock.NewManaMetrics(actionID) + petManaMetrics := warlock.Pet.NewManaMetrics(actionID) + + return core.SpellConfig{ + ActionID: actionID, + SpellSchool: core.SpellSchoolShadow, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagAPL, + Rank: rank, + RequiredLevel: level, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + }, + + FlatThreatBonus: 80, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + maxDrain := manaRestore + actualDrain := math.Min(maxDrain, warlock.Pet.CurrentMana()) + + warlock.Pet.SpendMana(sim, actualDrain, petManaMetrics) + warlock.AddMana(sim, actualDrain, manaMetrics) + }, + } +} + +func (warlock *Warlock) registerDarkPactSpell() { + if !warlock.Talents.DarkPact { + return + } + + maxRank := 3 + + for i := 1; i <= maxRank; i++ { + config := warlock.getDarkPactConfig(i) + + if config.RequiredLevel <= int(warlock.Level) { + warlock.DarkPact = warlock.GetOrRegisterSpell(config) + } + } +} diff --git a/sim/warlock/drain_life.go b/sim/warlock/drain_life.go index 51a83d4b65..54b9cb9e7a 100644 --- a/sim/warlock/drain_life.go +++ b/sim/warlock/drain_life.go @@ -45,7 +45,7 @@ func (warlock *Warlock) getDrainLifeBaseConfig(rank int) core.SpellConfig { }, }, - BonusHitRating: float64(warlock.Talents.Suppression) * 2 * core.CritRatingPerCritChance, + BonusHitRating: float64(warlock.Talents.Suppression) * 2 * core.SpellHitRatingPerHitChance, DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.ShadowMastery), DamageMultiplier: 1 + 0.02*float64(warlock.Talents.ImprovedDrainLife), diff --git a/sim/warlock/pet.go b/sim/warlock/pet.go index 6c688614a2..c692959184 100644 --- a/sim/warlock/pet.go +++ b/sim/warlock/pet.go @@ -57,7 +57,7 @@ func (warlock *Warlock) NewWarlockPet() *WarlockPet { stats.Stamina: 67, stats.Intellect: 163, stats.Spirit: 163, - stats.Mana: 149, + stats.Mana: 318, stats.MP5: 0, stats.MeleeCrit: 3.454 * core.CritRatingPerCritChance, stats.SpellCrit: 0.9075 * core.CritRatingPerCritChance, @@ -70,7 +70,7 @@ func (warlock *Warlock) NewWarlockPet() *WarlockPet { stats.Stamina: 67, stats.Intellect: 163, stats.Spirit: 163, - stats.Mana: 149, + stats.Mana: 318, stats.MP5: 0, stats.MeleeCrit: 3.454 * core.CritRatingPerCritChance, stats.SpellCrit: 0.9075 * core.CritRatingPerCritChance, @@ -83,7 +83,7 @@ func (warlock *Warlock) NewWarlockPet() *WarlockPet { stats.Stamina: 67, stats.Intellect: 163, stats.Spirit: 163, - stats.Mana: 149, + stats.Mana: 318, stats.MP5: 0, stats.MeleeCrit: 3.454 * core.CritRatingPerCritChance, stats.SpellCrit: 0.9075 * core.CritRatingPerCritChance, @@ -93,25 +93,95 @@ func (warlock *Warlock) NewWarlockPet() *WarlockPet { case proto.WarlockOptions_Succubus: cfg.Name = "Succubus" cfg.PowerModifier = 0.77 // GetUnitPowerModifier("pet") - cfg.Stats = stats.Stats{ - stats.Strength: 50, - stats.Agility: 40, - stats.Stamina: 87, - stats.Intellect: 35, - stats.Spirit: 61, - stats.Mana: 60, - stats.MP5: 0, - stats.MeleeCrit: 3.2685 * core.CritRatingPerCritChance, - stats.SpellCrit: 3.3355 * core.CritRatingPerCritChance, - } - cfg.AutoAttacks = core.AutoAttackOptions{ - MainHand: core.Weapon{ - BaseDamageMin: 23, - BaseDamageMax: 38, - SwingSpeed: 2, - CritMultiplier: 2, - }, - AutoSwingMelee: true, + switch warlock.Level { + case 25: + cfg.Stats = stats.Stats{ + stats.Strength: 50, + stats.Agility: 40, + stats.Stamina: 87, + stats.Intellect: 35, + stats.Spirit: 61, + stats.Mana: 119, + stats.MP5: 0, + stats.MeleeCrit: 3.2685 * core.CritRatingPerCritChance, + stats.SpellCrit: 3.3355 * core.CritRatingPerCritChance, + } + cfg.AutoAttacks = core.AutoAttackOptions{ + MainHand: core.Weapon{ + BaseDamageMin: 23, + BaseDamageMax: 38, + SwingSpeed: 2, + CritMultiplier: 2, + }, + AutoSwingMelee: true, + } + break + case 40: + cfg.Stats = stats.Stats{ + stats.Strength: 74, + stats.Agility: 58, + stats.Stamina: 148, + stats.Intellect: 49, + stats.Spirit: 97, + stats.Mana: 521, + stats.MP5: 0, + stats.MeleeCrit: 3.2685 * core.CritRatingPerCritChance, + stats.SpellCrit: 3.3355 * core.CritRatingPerCritChance, + } + cfg.AutoAttacks = core.AutoAttackOptions{ + MainHand: core.Weapon{ + BaseDamageMin: 41, + BaseDamageMax: 61, + SwingSpeed: 2, + CritMultiplier: 2, + }, + AutoSwingMelee: true, + } + break + case 50: + cfg.Stats = stats.Stats{ + stats.Strength: 74, + stats.Agility: 58, + stats.Stamina: 148, + stats.Intellect: 49, + stats.Spirit: 97, + stats.Mana: 521, + stats.MP5: 0, + stats.MeleeCrit: 3.2685 * core.CritRatingPerCritChance, + stats.SpellCrit: 3.3355 * core.CritRatingPerCritChance, + } + cfg.AutoAttacks = core.AutoAttackOptions{ + MainHand: core.Weapon{ + BaseDamageMin: 41, + BaseDamageMax: 61, + SwingSpeed: 2, + CritMultiplier: 2, + }, + AutoSwingMelee: true, + } + break + case 60: + cfg.Stats = stats.Stats{ + stats.Strength: 74, + stats.Agility: 58, + stats.Stamina: 148, + stats.Intellect: 49, + stats.Spirit: 97, + stats.Mana: 521, + stats.MP5: 0, + stats.MeleeCrit: 3.2685 * core.CritRatingPerCritChance, + stats.SpellCrit: 3.3355 * core.CritRatingPerCritChance, + } + cfg.AutoAttacks = core.AutoAttackOptions{ + MainHand: core.Weapon{ + BaseDamageMin: 41, + BaseDamageMax: 61, + SwingSpeed: 2, + CritMultiplier: 2, + }, + AutoSwingMelee: true, + } + break } case proto.WarlockOptions_Voidwalker: cfg.Name = "Voidwalker" diff --git a/sim/warlock/siphon_life.go b/sim/warlock/siphon_life.go new file mode 100644 index 0000000000..d39a09c63e --- /dev/null +++ b/sim/warlock/siphon_life.go @@ -0,0 +1,101 @@ +package warlock + +import ( + "strconv" + "time" + + "github.com/wowsims/sod/sim/core" +) + +func (warlock *Warlock) getSiphonLifeBaseConfig(rank int) core.SpellConfig { + spellId := [5]int32{0, 18265, 18879, 18880, 18881}[rank] + baseDamage := [5]float64{0, 15, 22, 33, 45}[rank] + manaCost := [5]float64{0, 150, 205, 285, 365}[rank] + level := [5]int{0, 0, 38, 48, 58}[rank] + + spellCoeff := 0.05 + actionID := core.ActionID{SpellID: spellId} + healthMetrics := warlock.NewHealthMetrics(actionID) + + return core.SpellConfig{ + ActionID: actionID, + SpellSchool: core.SpellSchoolShadow, + ProcMask: core.ProcMaskSpellDamage, + Flags: core.SpellFlagHauntSE | core.SpellFlagAPL | core.SpellFlagResetAttackSwing | core.SpellFlagBinary, + RequiredLevel: level, + Rank: rank, + + ManaCost: core.ManaCostOptions{ + FlatCost: manaCost, + }, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + }, + + BonusHitRating: float64(warlock.Talents.Suppression) * 2 * core.SpellHitRatingPerHitChance, + DamageMultiplierAdditive: 1 + + 0.02*float64(warlock.Talents.ShadowMastery), + DamageMultiplier: 1, + + Dot: core.DotConfig{ + Aura: core.Aura{ + Label: "Siphon Life-" + warlock.Label + strconv.Itoa(rank), + }, + NumberOfTicks: 10, + TickLength: 3 * time.Second, + AffectedByCastSpeed: false, + + OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { + baseDmg := baseDamage + spellCoeff*dot.Spell.SpellPower() + + dot.SnapshotBaseDamage = baseDmg + dot.SnapshotAttackerMultiplier = dot.Spell.AttackerDamageMultiplier(dot.Spell.Unit.AttackTables[target.UnitIndex]) + + // Siphon Life heals so it snapshots target modifiers + dot.SnapshotAttackerMultiplier *= dot.Spell.TargetDamageMultiplier(dot.Spell.Unit.AttackTables[target.UnitIndex], true) + }, + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + // Remove target modifiers for the tick only + dot.Spell.Flags |= core.SpellFlagIgnoreTargetModifiers + result := dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTickCounted) + // revert flag changes + dot.Spell.Flags ^= core.SpellFlagIgnoreTargetModifiers + + health := result.Damage + warlock.GainHealth(sim, health, healthMetrics) + }, + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHit) + if result.Landed() { + spell.SpellMetrics[target.UnitIndex].Hits-- + + spell.Dot(target).Apply(sim) + } + }, + ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, useSnapshot bool) *core.SpellResult { + if useSnapshot { + dot := spell.Dot(target) + return dot.CalcSnapshotDamage(sim, target, spell.OutcomeExpectedMagicAlwaysHit) + } else { + baseDmg := baseDamage + spellCoeff*spell.SpellPower() + return spell.CalcPeriodicDamage(sim, target, baseDmg, spell.OutcomeExpectedMagicAlwaysHit) + } + }, + } +} + +func (warlock *Warlock) registerSiphonLifeSpell() { + maxRank := 4 + + for i := 1; i <= maxRank; i++ { + config := warlock.getSiphonLifeBaseConfig(i) + + if config.RequiredLevel <= int(warlock.Level) { + warlock.SiphonLife = warlock.GetOrRegisterSpell(config) + } + } +} diff --git a/sim/warlock/warlock.go b/sim/warlock/warlock.go index 2c506ef035..4a08777f0b 100644 --- a/sim/warlock/warlock.go +++ b/sim/warlock/warlock.go @@ -34,6 +34,7 @@ type Warlock struct { DemonicGrace *core.Spell DrainLife *core.Spell RainOfFire *core.Spell + SiphonLife *core.Spell CurseOfElements *core.Spell CurseOfElementsAuras core.AuraArray @@ -85,24 +86,16 @@ func (warlock *Warlock) Initialize() { warlock.registerIncinerateSpell() warlock.registerShadowBoltSpell() warlock.registerShadowCleaveSpell() - - warlock.registerCurseOfElementsSpell() - warlock.registerCurseOfShadowSpell() - warlock.registerCurseOfRecklessnessSpell() - warlock.registerCurseOfAgonySpell() - warlock.registerAmplifyCurseSpell() - // warlock.registerCurseOfDoomSpell() - warlock.registerLifeTapSpell() // warlock.registerSeedSpell() // warlock.registerSoulFireSpell() // warlock.registerUnstableAfflictionSpell() // warlock.registerDrainSoulSpell() - // warlock.registerConflagrateSpell() + warlock.registerConflagrateSpell() warlock.registerHauntSpell() - // warlock.registerDemonicEmpowermentSpell() + warlock.registerSiphonLifeSpell() warlock.registerMetamorphosisSpell() - // warlock.registerDarkPactSpell() + warlock.registerDarkPactSpell() warlock.registerShadowBurnSpell() warlock.registerSearingPainSpell() // warlock.registerInfernoSpell() @@ -110,6 +103,13 @@ func (warlock *Warlock) Initialize() { warlock.registerDemonicGraceSpell() warlock.registerDrainLifeSpell() warlock.registerRainOfFireSpell() + + warlock.registerCurseOfElementsSpell() + warlock.registerCurseOfShadowSpell() + warlock.registerCurseOfRecklessnessSpell() + warlock.registerCurseOfAgonySpell() + warlock.registerAmplifyCurseSpell() + // warlock.registerCurseOfDoomSpell() } func (warlock *Warlock) AddRaidBuffs(raidBuffs *proto.RaidBuffs) { From 9fb389e38a5771569acc1c33c58e1e04ef1f1c51 Mon Sep 17 00:00:00 2001 From: rosenrusinov Date: Mon, 5 Feb 2024 01:52:39 +0100 Subject: [PATCH 09/18] add fell intellect --- sim/warlock/pet.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sim/warlock/pet.go b/sim/warlock/pet.go index c692959184..f328f8ce00 100644 --- a/sim/warlock/pet.go +++ b/sim/warlock/pet.go @@ -298,6 +298,10 @@ func (warlock *Warlock) NewWarlockPet() *WarlockPet { } } + if warlock.Talents.FelIntellect > 0 { + wp.AddStatDependency(stats.Mana, stats.Mana, 1+0.03*float64(warlock.Talents.FelIntellect)) + } + if warlock.HasRune(proto.WarlockRune_RuneBootsDemonicKnowledge) { oldPetEnable := wp.OnPetEnable wp.OnPetEnable = func(sim *core.Simulation) { From 1e175250f3ba72584e6bea4a3e26cfb1e17bc31c Mon Sep 17 00:00:00 2001 From: rosenrusinov Date: Mon, 5 Feb 2024 02:07:58 +0100 Subject: [PATCH 10/18] fix fel intellect --- sim/warlock/pet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim/warlock/pet.go b/sim/warlock/pet.go index f328f8ce00..15388416f8 100644 --- a/sim/warlock/pet.go +++ b/sim/warlock/pet.go @@ -299,7 +299,7 @@ func (warlock *Warlock) NewWarlockPet() *WarlockPet { } if warlock.Talents.FelIntellect > 0 { - wp.AddStatDependency(stats.Mana, stats.Mana, 1+0.03*float64(warlock.Talents.FelIntellect)) + wp.MultiplyStat(stats.Mana, 1+0.03*float64(warlock.Talents.FelIntellect)) } if warlock.HasRune(proto.WarlockRune_RuneBootsDemonicKnowledge) { From fb4b397b89018f5fc408cb2e098a6867c5278c46 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 4 Feb 2024 21:18:33 -0500 Subject: [PATCH 11/18] fix item filtering --- ui/core/proto_utils/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/core/proto_utils/utils.ts b/ui/core/proto_utils/utils.ts index c807ecb80f..8270dec5c9 100644 --- a/ui/core/proto_utils/utils.ts +++ b/ui/core/proto_utils/utils.ts @@ -1445,7 +1445,7 @@ export function canEquipItem(player: Player, it } // Most items are filtered by required level but some items slip past, so fall back to +10 ilvl for players under level 60 for now - if (item.requiresLevel > player.getLevel() || (player.getLevel() < 60 && item.ilvl - 10 > player.getLevel())) { + if (item.requiresLevel > player.getLevel() || (item.requiresLevel == 0 && player.getLevel() < 60 && item.ilvl - 10 > player.getLevel())) { return false } From 3d5899652a250ee138de8838cfd6bef72c01f15a Mon Sep 17 00:00:00 2001 From: Kayla Glick <12898988+kayla-glick@users.noreply.github.com> Date: Mon, 5 Feb 2024 00:41:53 -0500 Subject: [PATCH 12/18] Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index c056df1b3e..a1bc373b10 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 wowsims team +Copyright (c) 2024 wowsims team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 5be8bc7337e317022cd1cb3205a1248d923a8ef6 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Mon, 5 Feb 2024 01:34:20 -0500 Subject: [PATCH 13/18] implement imbues, other enhancement/ele touch-ups --- proto/shaman.proto | 1 - sim/core/racials.go | 2 +- sim/register_all.go | 5 +- sim/shaman/elemental/elemental.go | 10 +- .../enhancement.go | 41 +-- .../enhancement_test.go | 0 sim/shaman/flametongue_weapon.go | 116 ++++++ sim/shaman/frostbrand_weapon.go | 102 ++++++ sim/shaman/rockbiter_weapon.go | 89 +++++ sim/shaman/shaman.go | 42 ++- sim/shaman/weapon_imbues.go | 345 ------------------ sim/shaman/windfury_weapon.go | 122 +++++++ .../individual_sim_ui/consumes_picker.ts | 5 +- ui/core/components/inputs/shaman_imbues.ts | 4 +- ui/elemental_shaman/presets.ts | 40 +- ui/elemental_shaman/sim.ts | 16 +- .../apls/default_ft.apl.json | 20 - .../apls/default_wf.apl.json | 20 - ui/enhancement_shaman/apls/phase_1.apl.json | 7 + ui/enhancement_shaman/apls/phase_3.apl.json | 20 - ui/enhancement_shaman/presets.ts | 30 +- ui/enhancement_shaman/sim.ts | 16 +- ui/raid/import_export.ts | 2 - 23 files changed, 539 insertions(+), 516 deletions(-) rename sim/shaman/{_enhancement => enhancement}/enhancement.go (71%) rename sim/shaman/{_enhancement => enhancement}/enhancement_test.go (100%) create mode 100644 sim/shaman/flametongue_weapon.go create mode 100644 sim/shaman/frostbrand_weapon.go create mode 100644 sim/shaman/rockbiter_weapon.go create mode 100644 sim/shaman/windfury_weapon.go delete mode 100644 ui/enhancement_shaman/apls/default_ft.apl.json delete mode 100644 ui/enhancement_shaman/apls/default_wf.apl.json create mode 100644 ui/enhancement_shaman/apls/phase_1.apl.json delete mode 100644 ui/enhancement_shaman/apls/phase_3.apl.json diff --git a/proto/shaman.proto b/proto/shaman.proto index 9614ca1cab..1acd91ae1e 100644 --- a/proto/shaman.proto +++ b/proto/shaman.proto @@ -185,7 +185,6 @@ message EnhancementShaman { // NextIndex: 7 message Options { ShamanShield shield = 1; - bool bloodlust = 2 [deprecated = true]; ShamanSyncType sync_type = 3; ShamanImbue imbue_mh = 4; ShamanImbue imbue_oh = 5; diff --git a/sim/core/racials.go b/sim/core/racials.go index 3944cf64cd..470872cc8c 100644 --- a/sim/core/racials.go +++ b/sim/core/racials.go @@ -67,7 +67,7 @@ func applyRaceEffects(agent Agent) { } // Blood Fury - actionID := ActionID{SpellID: 33697} + actionID := ActionID{SpellID: 20572} apBonus := float64(character.Level)*4 + 2 spBonus := float64(character.Level)*2 + 3 bloodFuryAura := character.NewTemporaryStatsAura("Blood Fury", actionID, stats.Stats{stats.AttackPower: apBonus, stats.RangedAttackPower: apBonus, stats.SpellPower: spBonus}, time.Second*15) diff --git a/sim/register_all.go b/sim/register_all.go index 90b47d0471..2084811a3f 100644 --- a/sim/register_all.go +++ b/sim/register_all.go @@ -4,6 +4,7 @@ import ( _ "github.com/wowsims/sod/sim/common" "github.com/wowsims/sod/sim/druid/balance" "github.com/wowsims/sod/sim/shaman/elemental" + "github.com/wowsims/sod/sim/shaman/enhancement" "github.com/wowsims/sod/sim/druid/feral" // restoDruid "github.com/wowsims/sod/sim/druid/restoration" @@ -18,8 +19,6 @@ import ( // healingPriest "github.com/wowsims/sod/sim/priest/healing" "github.com/wowsims/sod/sim/priest/shadow" // "github.com/wowsims/sod/sim/rogue" - // "github.com/wowsims/sod/sim/shaman/elemental" - // "github.com/wowsims/sod/sim/shaman/enhancement" // restoShaman "github.com/wowsims/sod/sim/shaman/restoration" dpsWarlock "github.com/wowsims/sod/sim/warlock/dps" tankWarlock "github.com/wowsims/sod/sim/warlock/tank" @@ -40,7 +39,7 @@ func RegisterAll() { // feralTank.RegisterFeralTankDruid() // restoDruid.RegisterRestorationDruid() elemental.RegisterElementalShaman() - // enhancement.RegisterEnhancementShaman() + enhancement.RegisterEnhancementShaman() // restoShaman.RegisterRestorationShaman() hunter.RegisterHunter() mage.RegisterMage() diff --git a/sim/shaman/elemental/elemental.go b/sim/shaman/elemental/elemental.go index 73c61b4dab..3237be3cc0 100644 --- a/sim/shaman/elemental/elemental.go +++ b/sim/shaman/elemental/elemental.go @@ -24,15 +24,17 @@ func RegisterElementalShaman() { } func NewElementalShaman(character *core.Character, options *proto.Player) *ElementalShaman { - eleShamOptions := options.GetElementalShaman() + eleOptions := options.GetElementalShaman() selfBuffs := shaman.SelfBuffs{ - Shield: eleShamOptions.Options.Shield, + Shield: eleOptions.Options.Shield, + ImbueMH: eleOptions.Options.ImbueMh, + ImbueOH: eleOptions.Options.ImbueOh, } totems := &proto.ShamanTotems{} - if eleShamOptions.Options.Totems != nil { - totems = eleShamOptions.Options.Totems + if eleOptions.Options.Totems != nil { + totems = eleOptions.Options.Totems totems.UseFireMcd = true // Control fire totems as MCD. } diff --git a/sim/shaman/_enhancement/enhancement.go b/sim/shaman/enhancement/enhancement.go similarity index 71% rename from sim/shaman/_enhancement/enhancement.go rename to sim/shaman/enhancement/enhancement.go index c2be850e92..b4acebe1fc 100644 --- a/sim/shaman/_enhancement/enhancement.go +++ b/sim/shaman/enhancement/enhancement.go @@ -40,7 +40,7 @@ func NewEnhancementShaman(character *core.Character, options *proto.Player) *Enh } enh := &EnhancementShaman{ - Shaman: shaman.NewShaman(character, options.TalentsString, totems, selfBuffs, true), + Shaman: shaman.NewShaman(character, options.TalentsString, totems, selfBuffs), } // Enable Auto Attacks for this spec @@ -52,36 +52,9 @@ func NewEnhancementShaman(character *core.Character, options *proto.Player) *Enh enh.ApplySyncType(enhOptions.Options.SyncType) - if !enh.HasMHWeapon() { - enh.SelfBuffs.ImbueMH = proto.ShamanImbue_NoImbue - } - - if !enh.HasOHWeapon() { - enh.SelfBuffs.ImbueOH = proto.ShamanImbue_NoImbue - } - - enh.RegisterFlametongueImbue(enh.getImbueProcMask(proto.ShamanImbue_FlametongueWeapon), false) - enh.RegisterWindfuryImbue(enh.getImbueProcMask(proto.ShamanImbue_WindfuryWeapon)) - - enh.SpiritWolves = &shaman.SpiritWolves{ - SpiritWolf1: enh.NewSpiritWolf(1), - SpiritWolf2: enh.NewSpiritWolf(2), - } - return enh } -func (enh *EnhancementShaman) getImbueProcMask(imbue proto.ShamanImbue) core.ProcMask { - var mask core.ProcMask - if enh.SelfBuffs.ImbueMH == imbue { - mask |= core.ProcMaskMeleeMH - } - if enh.SelfBuffs.ImbueOH == imbue { - mask |= core.ProcMaskMeleeOH - } - return mask -} - type EnhancementShaman struct { *shaman.Shaman } @@ -92,18 +65,6 @@ func (enh *EnhancementShaman) GetShaman() *shaman.Shaman { func (enh *EnhancementShaman) Initialize() { enh.Shaman.Initialize() - // In the Initialize due to frost brand adding the aura to the enemy - enh.RegisterFrostbrandImbue(enh.getImbueProcMask(proto.ShamanImbue_FrostbrandWeapon)) - - if enh.ItemSwap.IsEnabled() { - mh := enh.ItemSwap.GetItem(proto.ItemSlot_ItemSlotMainHand) - enh.ApplyFlametongueImbueToItem(mh, true) - oh := enh.ItemSwap.GetItem(proto.ItemSlot_ItemSlotOffHand) - enh.ApplyFlametongueImbueToItem(oh, false) - enh.RegisterOnItemSwap(func(_ *core.Simulation) { - enh.ApplySyncType(proto.ShamanSyncType_Auto) - }) - } } func (enh *EnhancementShaman) Reset(sim *core.Simulation) { diff --git a/sim/shaman/_enhancement/enhancement_test.go b/sim/shaman/enhancement/enhancement_test.go similarity index 100% rename from sim/shaman/_enhancement/enhancement_test.go rename to sim/shaman/enhancement/enhancement_test.go diff --git a/sim/shaman/flametongue_weapon.go b/sim/shaman/flametongue_weapon.go new file mode 100644 index 0000000000..be05fe216c --- /dev/null +++ b/sim/shaman/flametongue_weapon.go @@ -0,0 +1,116 @@ +package shaman + +import ( + "time" + + "github.com/wowsims/sod/sim/core" +) + +const FlametongueWeaponRanks = 6 + +var FlametongueWeaponSpellId = [FlametongueWeaponRanks + 1]int32{0, 8024, 8027, 8030, 16339, 16341, 16342} +var FlametongueWeaponEnchantId = [FlametongueWeaponRanks + 1]int32{0, 5, 4, 3, 523, 1665, 1666} +var FlametongueWeaponMaxDamage = [FlametongueWeaponRanks + 1]float64{0, 18, 26, 42, 69, 95, 112} +var FlametongueWeaponLevel = [FlametongueWeaponRanks + 1]int32{0, 10, 18, 26, 36, 46, 56} + +var FlametongueWeaponRankByLevel = map[int32]int32{ + 25: 2, + 40: 4, + 50: 5, + 60: 6, +} + +func (shaman *Shaman) newFlametongueImbueSpell(weapon *core.Item) *core.Spell { + level := shaman.GetCharacter().Level + rank := FlametongueWeaponRankByLevel[level] + spellID := FlametongueWeaponSpellId[rank] + maxDamage := FlametongueWeaponMaxDamage[rank] + + baseDamage := maxDamage / 4 + spellCoeff := .1 + + return shaman.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: int32(spellID)}, + SpellSchool: core.SpellSchoolFire, + ProcMask: core.ProcMaskWeaponProc, + + DamageMultiplier: 1, + CritMultiplier: shaman.ElementalCritMultiplier(0), + ThreatMultiplier: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + if weapon.SwingSpeed != 0 { + damage := weapon.SwingSpeed * (baseDamage + spellCoeff*spell.SpellPower()) + damage *= 1 + .05*float64(shaman.Talents.ElementalWeapons) + spell.CalcAndDealDamage(sim, target, damage, spell.OutcomeMagicHitAndCrit) + } + }, + }) +} + +func (shaman *Shaman) ApplyFlametongueImbueToItem(item *core.Item) { + if item == nil { + return + } + + level := shaman.GetCharacter().Level + rank := FlametongueWeaponRankByLevel[level] + enchantId := FlametongueWeaponEnchantId[rank] + + item.TempEnchant = enchantId +} + +func (shaman *Shaman) ApplyFlametongueImbue(procMask core.ProcMask) { + if procMask.Matches(core.ProcMaskMeleeMH) && shaman.HasMHWeapon() { + shaman.ApplyFlametongueImbueToItem(shaman.MainHand()) + } + + if procMask.Matches(core.ProcMaskMeleeOH) && shaman.HasOHWeapon() { + shaman.ApplyFlametongueImbueToItem(shaman.OffHand()) + } +} + +func (shaman *Shaman) RegisterFlametongueImbue(procMask core.ProcMask) { + if procMask == core.ProcMaskUnknown && !shaman.ItemSwap.IsEnabled() { + return + } + + level := shaman.GetCharacter().Level + rank := FlametongueWeaponRankByLevel[level] + enchantId := FlametongueWeaponEnchantId[rank] + + icd := core.Cooldown{ + Timer: shaman.NewTimer(), + Duration: time.Millisecond, + } + + mhSpell := shaman.newFlametongueImbueSpell(shaman.MainHand()) + ohSpell := shaman.newFlametongueImbueSpell(shaman.OffHand()) + + aura := shaman.RegisterAura(core.Aura{ + Label: "Flametongue Imbue", + Duration: core.NeverExpires, + OnReset: func(aura *core.Aura, sim *core.Simulation) { + aura.Activate(sim) + }, + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if !result.Landed() || !spell.ProcMask.Matches(procMask) { + return + } + + if !icd.IsReady(sim) { + return + } + + icd.Use(sim) + + if spell.IsMH() { + mhSpell.Cast(sim, result.Target) + } else { + ohSpell.Cast(sim, result.Target) + } + }, + }) + + shaman.RegisterOnItemSwapWithImbue(enchantId, &procMask, aura) +} diff --git a/sim/shaman/frostbrand_weapon.go b/sim/shaman/frostbrand_weapon.go new file mode 100644 index 0000000000..a032553d14 --- /dev/null +++ b/sim/shaman/frostbrand_weapon.go @@ -0,0 +1,102 @@ +package shaman + +import ( + "time" + + "github.com/wowsims/sod/sim/core" +) + +const FrostbrandWeaponRanks = 5 + +var FrostbrandWeaponSpellId = [FrostbrandWeaponRanks + 1]int32{0, 8033, 8038, 10456, 16355, 16356} +var FrostbrandWeaponEnchantId = [FrostbrandWeaponRanks + 1]int32{0, 2, 12, 524, 1667, 1668} +var FrostbrandWeaponBaseDamage = [FrostbrandWeaponRanks + 1]float64{0, 48, 77, 124, 166, 187} +var FrostbrandWeaponLevel = [FrostbrandWeaponRanks + 1]int32{0, 20, 28, 38, 48, 58} + +var FrostbrandWeaponRankByLevel = map[int32]int32{ + 25: 1, + 40: 3, + 50: 4, + 60: 5, +} + +func (shaman *Shaman) FrostbrandDebuffAura(target *core.Unit, level int32) *core.Aura { + rank := FrostbrandWeaponRankByLevel[level] + spellId := FrostbrandWeaponSpellId[rank] + + return target.GetOrRegisterAura(core.Aura{ + Label: "Frostbrand Attack-" + shaman.Label, + ActionID: core.ActionID{SpellID: spellId}, + Duration: time.Second * 8, + }) +} + +func (shaman *Shaman) newFrostbrandImbueSpell() *core.Spell { + level := shaman.GetCharacter().Level + rank := FrostbrandWeaponRankByLevel[level] + spellId := FrostbrandWeaponSpellId[rank] + baseDamage := FrostbrandWeaponBaseDamage[rank] + + return shaman.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: spellId}, + SpellSchool: core.SpellSchoolFrost, + ProcMask: core.ProcMaskEmpty, + + DamageMultiplier: 1, + CritMultiplier: shaman.ElementalCritMultiplier(0), + ThreatMultiplier: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + baseDamage := baseDamage + 0.1*spell.SpellPower() + spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) + }, + }) +} + +func (shaman *Shaman) RegisterFrostbrandImbue(procMask core.ProcMask) { + if procMask == core.ProcMaskUnknown { + return + } + + level := shaman.GetCharacter().Level + rank := FrostbrandWeaponRankByLevel[level] + enchantId := FrostbrandWeaponEnchantId[rank] + + if procMask.Matches(core.ProcMaskMeleeMH) { + shaman.MainHand().TempEnchant = enchantId + } + if procMask.Matches(core.ProcMaskMeleeOH) { + shaman.OffHand().TempEnchant = enchantId + } + + ppmm := shaman.AutoAttacks.NewPPMManager(9.0, procMask) + + mhSpell := shaman.newFrostbrandImbueSpell() + ohSpell := shaman.newFrostbrandImbueSpell() + + fbDebuffAuras := shaman.NewEnemyAuraArray(shaman.FrostbrandDebuffAura) + + aura := shaman.RegisterAura(core.Aura{ + Label: "Frostbrand Imbue", + Duration: core.NeverExpires, + OnReset: func(aura *core.Aura, sim *core.Simulation) { + aura.Activate(sim) + }, + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if !result.Landed() { + return + } + + if ppmm.Proc(sim, spell.ProcMask, "Frostbrand Weapon") { + if spell.IsMH() { + mhSpell.Cast(sim, result.Target) + } else { + ohSpell.Cast(sim, result.Target) + } + fbDebuffAuras.Get(result.Target).Activate(sim) + } + }, + }) + + shaman.ItemSwap.RegisterOnSwapItemForEffectWithPPMManager(3784, 9.0, &ppmm, aura) +} diff --git a/sim/shaman/rockbiter_weapon.go b/sim/shaman/rockbiter_weapon.go new file mode 100644 index 0000000000..17b8f6e348 --- /dev/null +++ b/sim/shaman/rockbiter_weapon.go @@ -0,0 +1,89 @@ +package shaman + +import ( + "time" + + "github.com/wowsims/sod/sim/core" + "github.com/wowsims/sod/sim/core/stats" +) + +const RockbiterWeaponRanks = 7 + +var RockbiterWeaponEnchantId = [RockbiterWeaponRanks + 1]int32{0, 29, 6, 1, 503, 1663, 683, 1664} +var RockbiterWeaponBonusAP = [RockbiterWeaponRanks + 1]float64{0, 50, 79, 118, 194, 355, 522, 653} +var RockbiterWeaponLevel = [RockbiterWeaponRanks + 1]int32{0, 1, 8, 16, 24, 34, 44, 54} + +var RockbiterRankByLevel = map[int32]int32{ + 25: 4, + 40: 5, + 50: 6, + 60: 7, +} + +func (shaman *Shaman) RegisterRockbiterImbue(procMask core.ProcMask) { + if procMask == core.ProcMaskUnknown { + return + } + + level := shaman.GetCharacter().Level + rank := RockbiterRankByLevel[level] + enchantId := RockbiterWeaponEnchantId[rank] + bonusAP := RockbiterWeaponBonusAP[rank] * (1 + .07*float64(shaman.Talents.ElementalWeapons)) + + duration := time.Minute * 5 + + if procMask.Matches(core.ProcMaskMeleeMH) { + shaman.MainHand().TempEnchant = enchantId + } + if procMask.Matches(core.ProcMaskMeleeOH) { + shaman.OffHand().TempEnchant = enchantId + } + + // TODO: Rockbiter +threat + + aura := shaman.RegisterAura(core.Aura{ + Label: "Rockbiter Imbue", + Duration: duration, + OnGain: func(aura *core.Aura, sim *core.Simulation) { + aura.Unit.AddStatsDynamic(sim, stats.Stats{ + stats.AttackPower: bonusAP, + }) + aura.Unit.AddStatsDynamic(sim, stats.Stats{ + stats.AttackPower: bonusAP, + }) + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + aura.Unit.AddStatsDynamic(sim, stats.Stats{ + stats.AttackPower: -1 * bonusAP, + }) + }, + }) + + shaman.RegisterOnItemSwapWithImbue(enchantId, &procMask, aura) +} + +func (shaman *Shaman) ApplyRockbiterImbue(procMask core.ProcMask) { + if procMask.Matches(core.ProcMaskMeleeMH) && shaman.HasMHWeapon() { + shaman.ApplyRockbiterImbueToItem(shaman.MainHand()) + } + + if procMask.Matches(core.ProcMaskMeleeOH) && shaman.HasOHWeapon() { + shaman.ApplyRockbiterImbueToItem(shaman.OffHand()) + } +} + +func (shaman *Shaman) ApplyRockbiterImbueToItem(item *core.Item) { + if item == nil { + return + } + + level := shaman.GetCharacter().Level + rank := RockbiterRankByLevel[level] + enchantId := RockbiterWeaponEnchantId[rank] + bonusAP := RockbiterWeaponBonusAP[rank] + + newStats := stats.Stats{stats.AttackPower: bonusAP} + + item.Stats = item.Stats.Add(newStats) + item.TempEnchant = enchantId +} diff --git a/sim/shaman/shaman.go b/sim/shaman/shaman.go index fc3121b230..e5e22bea92 100644 --- a/sim/shaman/shaman.go +++ b/sim/shaman/shaman.go @@ -45,9 +45,31 @@ func NewShaman(character *core.Character, talents string, totems *proto.ShamanTo shaman.AddStat(stats.MP5, shaman.MaxMana()*.01) } + shaman.ApplyRockbiterImbue(shaman.getImbueProcMask(proto.ShamanImbue_RockbiterWeapon)) + shaman.ApplyFlametongueImbue(shaman.getImbueProcMask(proto.ShamanImbue_FlametongueWeapon)) + + if !shaman.HasMHWeapon() { + shaman.SelfBuffs.ImbueMH = proto.ShamanImbue_NoImbue + } + + if !shaman.HasOHWeapon() { + shaman.SelfBuffs.ImbueOH = proto.ShamanImbue_NoImbue + } + return shaman } +func (shaman *Shaman) getImbueProcMask(imbue proto.ShamanImbue) core.ProcMask { + var mask core.ProcMask + if shaman.SelfBuffs.ImbueMH == imbue { + mask |= core.ProcMaskMeleeMH + } + if shaman.SelfBuffs.ImbueOH == imbue { + mask |= core.ProcMaskMeleeOH + } + return mask +} + // Which buffs this shaman is using. type SelfBuffs struct { Shield proto.ShamanShield @@ -205,13 +227,31 @@ func (shaman *Shaman) AddPartyBuffs(partyBuffs *proto.PartyBuffs) { } func (shaman *Shaman) Initialize() { + // Core abilities shaman.registerChainLightningSpell() - // shaman.registerFeralSpirit() shaman.registerLightningBoltSpell() // shaman.registerLightningShieldSpell() shaman.registerShocks() // shaman.registerStormstrikeSpell() + // Imbues + // In the Initialize due to frost brand adding the aura to the enemy + shaman.RegisterRockbiterImbue(shaman.getImbueProcMask(proto.ShamanImbue_RockbiterWeapon)) + shaman.RegisterFlametongueImbue(shaman.getImbueProcMask(proto.ShamanImbue_FlametongueWeapon)) + shaman.RegisterWindfuryImbue(shaman.getImbueProcMask(proto.ShamanImbue_WindfuryWeapon)) + shaman.RegisterFrostbrandImbue(shaman.getImbueProcMask(proto.ShamanImbue_FrostbrandWeapon)) + + // if shaman.ItemSwap.IsEnabled() { + // mh := shaman.ItemSwap.GetItem(proto.ItemSlot_ItemSlotMainHand) + // shaman.ApplyFlametongueImbueToItem(mh, true) + // oh := shaman.ItemSwap.GetItem(proto.ItemSlot_ItemSlotOffHand) + // shaman.ApplyFlametongueImbueToItem(oh, false) + // shaman.RegisterOnItemSwap(func(_ *core.Simulation) { + // shaman.ApplySyncType(proto.ShamanSyncType_Auto) + // }) + // } + + // Totems shaman.registerStrengthOfEarthTotemSpell() shaman.registerStoneskinTotemSpell() shaman.registerTremorTotemSpell() diff --git a/sim/shaman/weapon_imbues.go b/sim/shaman/weapon_imbues.go index c0986d6ea6..84669efd73 100644 --- a/sim/shaman/weapon_imbues.go +++ b/sim/shaman/weapon_imbues.go @@ -22,348 +22,3 @@ func (shaman *Shaman) RegisterOnItemSwapWithImbue(effectID int32, procMask *core } }) } - -// func (shaman *Shaman) newWindfuryImbueSpell(isMH bool) *core.Spell { -// apBonus := 1250.0 -// if shaman.Ranged().ID == TotemOfTheAstralWinds { -// apBonus += 80 -// } else if shaman.Ranged().ID == TotemOfSplintering { -// apBonus += 212 -// } - -// tag := 1 -// procMask := core.ProcMaskMeleeMHSpecial -// weaponDamageFunc := shaman.MHWeaponDamage -// if !isMH { -// tag = 2 -// procMask = core.ProcMaskMeleeOHSpecial -// weaponDamageFunc = shaman.OHWeaponDamage -// apBonus *= 2 // applied after 50% offhand penalty -// } - -// spellConfig := core.SpellConfig{ -// ActionID: core.ActionID{SpellID: 58804, Tag: int32(tag)}, -// SpellSchool: core.SpellSchoolPhysical, -// ProcMask: procMask, -// Flags: core.SpellFlagMeleeMetrics | core.SpellFlagIncludeTargetBonusDamage, - -// DamageMultiplier: []float64{1, 1.13, 1.27, 1.4}[shaman.Talents.ElementalWeapons], -// CritMultiplier: shaman.DefaultMeleeCritMultiplier(), -// ThreatMultiplier: 1, - -// ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { -// constBaseDamage := spell.BonusWeaponDamage() -// mAP := spell.MeleeAttackPower() + apBonus - -// baseDamage1 := constBaseDamage + weaponDamageFunc(sim, mAP) -// baseDamage2 := constBaseDamage + weaponDamageFunc(sim, mAP) -// result1 := spell.CalcDamage(sim, target, baseDamage1, spell.OutcomeMeleeSpecialHitAndCrit) -// result2 := spell.CalcDamage(sim, target, baseDamage2, spell.OutcomeMeleeSpecialHitAndCrit) -// spell.DealDamage(sim, result1) -// spell.DealDamage(sim, result2) -// }, -// } - -// return shaman.RegisterSpell(spellConfig) -// } - -// func (shaman *Shaman) RegisterWindfuryImbue(procMask core.ProcMask) { -// if procMask == core.ProcMaskUnknown { -// return -// } - -// if procMask.Matches(core.ProcMaskMeleeMH) { -// shaman.MainHand().TempEnchant = 3787 -// } -// if procMask.Matches(core.ProcMaskMeleeOH) { -// shaman.OffHand().TempEnchant = 3787 -// } - -// var proc = 0.2 -// if procMask == core.ProcMaskMelee { -// proc = 0.36 -// } - -// icd := core.Cooldown{ -// Timer: shaman.NewTimer(), -// Duration: time.Second * 3, -// } - -// mhSpell := shaman.newWindfuryImbueSpell(true) -// ohSpell := shaman.newWindfuryImbueSpell(false) - -// aura := shaman.RegisterAura(core.Aura{ -// Label: "Windfury Imbue", -// Duration: core.NeverExpires, -// OnReset: func(aura *core.Aura, sim *core.Simulation) { -// aura.Activate(sim) -// }, -// OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { -// if !result.Landed() || !spell.ProcMask.Matches(procMask) { -// return -// } - -// if !icd.IsReady(sim) { -// return -// } - -// if sim.RandomFloat("Windfury Imbue") < proc { -// icd.Use(sim) - -// if spell.IsMH() { -// mhSpell.Cast(sim, result.Target) -// } else { -// ohSpell.Cast(sim, result.Target) -// } -// } -// }, -// }) - -// shaman.RegisterOnItemSwapWithImbue(3787, &procMask, aura) -// } - -// func (shaman *Shaman) ApplyFlametongueImbueToItem(item *core.Item) { -// if item == nil || item.TempEnchant == 3781 || item.TempEnchant == 3780 { -// return -// } - -// spBonus := 211.0 -// enchantID := 3781 - -// spMod := 1.0 + 0.1*float64(shaman.Talents.ElementalWeapons) - -// newStats := stats.Stats{stats.SpellPower: spBonus * spMod} - -// item.Stats = item.Stats.Add(newStats) -// item.TempEnchant = int32(enchantID) -// } - -// func (shaman *Shaman) RegisterFlametongueImbue(procMask core.ProcMask) { -// if procMask == core.ProcMaskUnknown && !shaman.ItemSwap.IsEnabled() { -// return -// } - -// if procMask.Matches(core.ProcMaskMeleeMH) { -// shaman.ApplyFlametongueImbueToItem(shaman.MainHand()) -// } -// if procMask.Matches(core.ProcMaskMeleeOH) { -// shaman.ApplyFlametongueImbueToItem(shaman.OffHand()) -// } - -// icd := core.Cooldown{ -// Timer: shaman.NewTimer(), -// Duration: time.Millisecond, -// } - -// mhSpell := shaman.newFlametongueImbueSpell(shaman.MainHand()) -// ohSpell := shaman.newFlametongueImbueSpell(shaman.OffHand()) - -// label := "Flametongue Imbue" -// enchantID := 3781 - -// aura := shaman.RegisterAura(core.Aura{ -// Label: label, -// Duration: core.NeverExpires, -// OnReset: func(aura *core.Aura, sim *core.Simulation) { -// aura.Activate(sim) -// }, -// OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { -// if !result.Landed() || !spell.ProcMask.Matches(procMask) { -// return -// } - -// if !icd.IsReady(sim) { -// return -// } - -// icd.Use(sim) - -// if spell.IsMH() { -// mhSpell.Cast(sim, result.Target) -// } else { -// ohSpell.Cast(sim, result.Target) -// } -// }, -// }) - -// shaman.RegisterOnItemSwapWithImbue(int32(enchantID), &procMask, aura) -// } - -// func (shaman *Shaman) FrostbrandDebuffAura(target *core.Unit) *core.Aura { -// multiplier := 1 + 0.05*float64(shaman.Talents.FrozenPower) -// return target.GetOrRegisterAura(core.Aura{ -// Label: "Frostbrand Attack-" + shaman.Label, -// ActionID: core.ActionID{SpellID: 58799}, -// Duration: time.Second * 8, -// OnGain: func(aura *core.Aura, sim *core.Simulation) { -// shaman.LightningBolt.DamageMultiplier *= multiplier -// shaman.ChainLightning.DamageMultiplier *= multiplier -// if shaman.LavaLash != nil { -// shaman.LavaLash.DamageMultiplier *= multiplier -// } -// shaman.EarthShock.DamageMultiplier *= multiplier -// shaman.FlameShock.DamageMultiplier *= multiplier -// shaman.FrostShock.DamageMultiplier *= multiplier -// }, -// OnExpire: func(aura *core.Aura, sim *core.Simulation) { -// shaman.LightningBolt.DamageMultiplier /= multiplier -// shaman.ChainLightning.DamageMultiplier /= multiplier -// if shaman.LavaLash != nil { -// shaman.LavaLash.DamageMultiplier /= multiplier -// } -// shaman.EarthShock.DamageMultiplier /= multiplier -// shaman.FlameShock.DamageMultiplier /= multiplier -// shaman.FrostShock.DamageMultiplier /= multiplier -// }, -// }) -// } - -// func (shaman *Shaman) newFrostbrandImbueSpell() *core.Spell { -// return shaman.RegisterSpell(core.SpellConfig{ -// ActionID: core.ActionID{SpellID: 58796}, -// SpellSchool: core.SpellSchoolFrost, -// ProcMask: core.ProcMaskEmpty, - -// BonusHitRating: float64(shaman.Talents.ElementalPrecision) * core.SpellHitRatingPerHitChance, -// DamageMultiplier: 1, -// CritMultiplier: shaman.ElementalCritMultiplier(0), -// ThreatMultiplier: 1, - -// ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { -// baseDamage := 530 + 0.1*spell.SpellPower() -// spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) -// }, -// }) -// } - -// func (shaman *Shaman) RegisterFrostbrandImbue(procMask core.ProcMask) { -// if procMask == core.ProcMaskUnknown { -// return -// } - -// if procMask.Matches(core.ProcMaskMeleeMH) { -// shaman.MainHand().TempEnchant = 3784 -// } -// if procMask.Matches(core.ProcMaskMeleeOH) { -// shaman.OffHand().TempEnchant = 3784 -// } - -// ppmm := shaman.AutoAttacks.NewPPMManager(9.0, procMask) - -// mhSpell := shaman.newFrostbrandImbueSpell() -// ohSpell := shaman.newFrostbrandImbueSpell() - -// fbDebuffAuras := shaman.NewEnemyAuraArray(shaman.FrostbrandDebuffAura) - -// aura := shaman.RegisterAura(core.Aura{ -// Label: "Frostbrand Imbue", -// Duration: core.NeverExpires, -// OnReset: func(aura *core.Aura, sim *core.Simulation) { -// aura.Activate(sim) -// }, -// OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { -// if !result.Landed() { -// return -// } - -// if ppmm.Proc(sim, spell.ProcMask, "Frostbrand Weapon") { -// if spell.IsMH() { -// mhSpell.Cast(sim, result.Target) -// } else { -// ohSpell.Cast(sim, result.Target) -// } -// fbDebuffAuras.Get(result.Target).Activate(sim) -// } -// }, -// }) - -// shaman.ItemSwap.RegisterOnSwapItemForEffectWithPPMManager(3784, 9.0, &ppmm, aura) -// } - -// func (shaman *Shaman) newEarthlivingImbueSpell() *core.Spell { -// return shaman.RegisterSpell(core.SpellConfig{ -// ActionID: core.ActionID{SpellID: 51994}, -// SpellSchool: core.SpellSchoolNature, -// ProcMask: core.ProcMaskEmpty, - -// DamageMultiplier: 1, -// CritMultiplier: shaman.ElementalCritMultiplier(0), -// ThreatMultiplier: 1, - -// Hot: core.DotConfig{ -// Aura: core.Aura{ -// Label: "Earthliving", -// ActionID: core.ActionID{SpellID: 52000}, -// }, -// NumberOfTicks: 4, -// TickLength: time.Second * 3, -// OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, _ bool) { -// dot.SnapshotBaseDamage = 280 + 0.171*dot.Spell.HealingPower(target) -// dot.SnapshotAttackerMultiplier = dot.Spell.CasterHealingMultiplier() -// }, -// OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { -// dot.CalcAndDealPeriodicSnapshotHealing(sim, target, dot.OutcomeTick) -// }, -// }, - -// ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { -// spell.SpellMetrics[target.UnitIndex].Hits++ -// spell.Hot(target).Apply(sim) -// }, -// }) -// } - -// func (shaman *Shaman) ApplyEarthlivingImbueToItem(item *core.Item) { -// if item == nil || item.TempEnchant == 3350 || item.TempEnchant == 3349 { -// // downranking not implemented yet but put the temp enchant ID there. -// return -// } - -// spBonus := 150.0 -// spMod := 1.0 + 0.1*float64(shaman.Talents.ElementalWeapons) -// id := 3350 - -// newStats := stats.Stats{stats.SpellPower: spBonus * spMod} -// item.Stats = item.Stats.Add(newStats) -// item.TempEnchant = int32(id) -// } - -// func (shaman *Shaman) RegisterEarthlivingImbue(procMask core.ProcMask) { -// if procMask == core.ProcMaskEmpty && !shaman.ItemSwap.IsEnabled() { -// return -// } - -// if procMask.Matches(core.ProcMaskMeleeMH) { -// shaman.ApplyEarthlivingImbueToItem(shaman.MainHand()) -// } -// if procMask.Matches(core.ProcMaskMeleeOH) { -// shaman.ApplyEarthlivingImbueToItem(shaman.OffHand()) -// } - -// procChance := 0.2 - -// imbueSpell := shaman.newEarthlivingImbueSpell() - -// aura := shaman.RegisterAura(core.Aura{ -// Label: "Earthliving Imbue", -// Duration: core.NeverExpires, -// OnReset: func(aura *core.Aura, sim *core.Simulation) { -// aura.Activate(sim) -// }, -// OnHealDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { -// if spell != shaman.ChainHeal && spell != shaman.LesserHealingWave && spell != shaman.HealingWave && spell != shaman.Riptide { -// return -// } - -// if procMask.Matches(core.ProcMaskMeleeMH) && sim.RandomFloat("earthliving") < procChance { -// imbueSpell.Cast(sim, result.Target) -// } - -// if procMask.Matches(core.ProcMaskMeleeOH) && sim.RandomFloat("earthliving") < procChance { -// imbueSpell.Cast(sim, result.Target) -// } -// }, -// }) - -// shaman.RegisterOnItemSwapWithImbue(3350, &procMask, aura) -// } diff --git a/sim/shaman/windfury_weapon.go b/sim/shaman/windfury_weapon.go new file mode 100644 index 0000000000..581fd42b1e --- /dev/null +++ b/sim/shaman/windfury_weapon.go @@ -0,0 +1,122 @@ +package shaman + +import ( + "time" + + "github.com/wowsims/sod/sim/core" +) + +const WindfuryWeaponRanks = 4 + +var WindfuryWeaponSpellId = [WindfuryWeaponRanks + 1]int32{0, 8232, 8235, 10486, 16362} +var WindfuryWeaponEnchantId = [WindfuryWeaponRanks + 1]int32{0, 283, 284, 525, 1669} +var WindfuryWeaponBonusAP = [WindfuryWeaponRanks + 1]float64{0, 104, 222, 316, 333} +var WindfuryWeaponLevel = [WindfuryWeaponRanks + 1]int32{0, 30, 40, 50, 60} + +var WindfuryWeaponRankByLevel = map[int32]int32{ + 25: 0, + 40: 2, + 50: 3, + 60: 4, +} + +func (shaman *Shaman) newWindfuryImbueSpell(isMH bool) *core.Spell { + level := shaman.GetCharacter().Level + rank := WindfuryWeaponRankByLevel[level] + SpellId := WindfuryWeaponSpellId[rank] + bonusAP := WindfuryWeaponBonusAP[rank] * (1 + .13*float64(shaman.Talents.ElementalWeapons)) + + tag := 1 + procMask := core.ProcMaskMeleeMHSpecial + weaponDamageFunc := shaman.MHWeaponDamage + if !isMH { + tag = 2 + procMask = core.ProcMaskMeleeOHSpecial + weaponDamageFunc = shaman.OHWeaponDamage + bonusAP *= 2 // applied after 50% offhand penalty + } + + spellConfig := core.SpellConfig{ + ActionID: core.ActionID{SpellID: SpellId, Tag: int32(tag)}, + SpellSchool: core.SpellSchoolPhysical, + ProcMask: procMask, + Flags: core.SpellFlagMeleeMetrics | core.SpellFlagIncludeTargetBonusDamage, + + DamageMultiplier: 1, + CritMultiplier: shaman.DefaultMeleeCritMultiplier(), + ThreatMultiplier: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + constBaseDamage := spell.BonusWeaponDamage() + mAP := spell.MeleeAttackPower() + bonusAP + + baseDamage1 := constBaseDamage + weaponDamageFunc(sim, mAP) + baseDamage2 := constBaseDamage + weaponDamageFunc(sim, mAP) + result1 := spell.CalcDamage(sim, target, baseDamage1, spell.OutcomeMeleeSpecialHitAndCrit) + result2 := spell.CalcDamage(sim, target, baseDamage2, spell.OutcomeMeleeSpecialHitAndCrit) + spell.DealDamage(sim, result1) + spell.DealDamage(sim, result2) + }, + } + + return shaman.RegisterSpell(spellConfig) +} + +func (shaman *Shaman) RegisterWindfuryImbue(procMask core.ProcMask) { + if procMask == core.ProcMaskUnknown { + return + } + + level := shaman.GetCharacter().Level + rank := WindfuryWeaponRankByLevel[level] + enchantId := WindfuryWeaponEnchantId[rank] + + if procMask.Matches(core.ProcMaskMeleeMH) { + shaman.MainHand().TempEnchant = enchantId + } + if procMask.Matches(core.ProcMaskMeleeOH) { + shaman.OffHand().TempEnchant = enchantId + } + + var proc = 0.2 + if procMask == core.ProcMaskMelee { + proc = 0.36 + } + + icd := core.Cooldown{ + Timer: shaman.NewTimer(), + Duration: time.Second * 3, + } + + mhSpell := shaman.newWindfuryImbueSpell(true) + ohSpell := shaman.newWindfuryImbueSpell(false) + + aura := shaman.RegisterAura(core.Aura{ + Label: "Windfury Imbue", + Duration: core.NeverExpires, + OnReset: func(aura *core.Aura, sim *core.Simulation) { + aura.Activate(sim) + }, + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if !result.Landed() || !spell.ProcMask.Matches(procMask) { + return + } + + if !icd.IsReady(sim) { + return + } + + if sim.RandomFloat("Windfury Imbue") < proc { + icd.Use(sim) + + if spell.IsMH() { + mhSpell.Cast(sim, result.Target) + } else { + ohSpell.Cast(sim, result.Target) + } + } + }, + }) + + shaman.RegisterOnItemSwapWithImbue(enchantId, &procMask, aura) +} diff --git a/ui/core/components/individual_sim_ui/consumes_picker.ts b/ui/core/components/individual_sim_ui/consumes_picker.ts index afd2fdcba2..883c04a1d7 100644 --- a/ui/core/components/individual_sim_ui/consumes_picker.ts +++ b/ui/core/components/individual_sim_ui/consumes_picker.ts @@ -1,6 +1,7 @@ import { IndividualSimUI } from "../../individual_sim_ui"; import { Player } from "../../player"; import { + Class, Spec, Stat, } from "../../proto/common"; @@ -23,7 +24,9 @@ export class ConsumesPicker extends Component { this.buildPotionsPicker(); this.buildFlaskPicker(); - this.buildWeaponImbuePicker(); + if (this.simUI.player.getClass() != Class.ClassShaman) { + this.buildWeaponImbuePicker(); + } this.buildFoodPicker(); this.buildPhysicalBuffPicker(); this.buildSpellPowerBuffPicker(); diff --git a/ui/core/components/inputs/shaman_imbues.ts b/ui/core/components/inputs/shaman_imbues.ts index b3692f7326..05f5fa521b 100644 --- a/ui/core/components/inputs/shaman_imbues.ts +++ b/ui/core/components/inputs/shaman_imbues.ts @@ -66,7 +66,7 @@ export const ShamanImbueInputMH = () => FrostbrandWeaponImbue, ], showWhen: (player) => player.getEquippedItem(ItemSlot.ItemSlotMainHand) != null, - changeEmitter: (player) => TypedEvent.onAny([player.specOptionsChangeEmitter, player.levelChangeEmitter]), + changeEmitter: (player) => TypedEvent.onAny([player.specOptionsChangeEmitter, player.levelChangeEmitter, player.gearChangeEmitter]), }); export const ShamanImbueInputOH = () => @@ -80,5 +80,5 @@ export const ShamanImbueInputOH = () => FrostbrandWeaponImbue, ], showWhen: (player) => player.getEquippedItem(ItemSlot.ItemSlotOffHand) != null, - changeEmitter: (player) => TypedEvent.onAny([player.specOptionsChangeEmitter, player.levelChangeEmitter]), + changeEmitter: (player) => TypedEvent.onAny([player.specOptionsChangeEmitter, player.levelChangeEmitter, player.gearChangeEmitter]), }); diff --git a/ui/elemental_shaman/presets.ts b/ui/elemental_shaman/presets.ts index 0bc27f9616..dea3bcc793 100644 --- a/ui/elemental_shaman/presets.ts +++ b/ui/elemental_shaman/presets.ts @@ -1,8 +1,11 @@ import { Consumes, + Debuffs, Flask, Food, Profession, + RaidBuffs, + TristateEffect, } from '../core/proto/common.js'; import { SavedTalents } from '../core/proto/ui.js'; @@ -11,6 +14,7 @@ import { EarthTotem, ElementalShaman_Options as ElementalShamanOptions, FireTotem, + ShamanImbue, ShamanShield, ShamanTotems, WaterTotem, @@ -47,22 +51,38 @@ export const StandardTalents = { export const DefaultOptions = ElementalShamanOptions.create({ shield: ShamanShield.WaterShield, + imbueMh: ShamanImbue.RockbiterWeapon, + imbueOh: ShamanImbue.RockbiterWeapon, totems: ShamanTotems.create({ earth: EarthTotem.StrengthOfEarthTotem, + fire: FireTotem.SearingTotem, + water: WaterTotem.HealingStreamTotem, air: AirTotem.WindfuryTotem, - fire: FireTotem.TotemOfWrath, - water: WaterTotem.ManaSpringTotem, }), }); -export const OtherDefaults = { - distanceFromTarget: 20, - profession1: Profession.Engineering, - profession2: Profession.Tailoring, - nibelungAverageCasts: 11, -} - export const DefaultConsumes = Consumes.create({ flask: Flask.FlaskUnknown, food: Food.FoodUnknown, -}); \ No newline at end of file +}); + +export const DefaultRaidBuffs = RaidBuffs.create({ + aspectOfTheLion: true, + giftOfTheWild: TristateEffect.TristateEffectImproved, + arcaneBrilliance: true, + leaderOfThePack: true, + moonkinAura: true, + divineSpirit: true, +}); + +export const DefaultDebuffs = Debuffs.create({ + curseOfElements: true, + improvedScorch: true, +}); + +export const OtherDefaults = { + distanceFromTarget: 20, + profession1: Profession.Engineering, + profession2: Profession.Tailoring, + nibelungAverageCasts: 11, +} diff --git a/ui/elemental_shaman/sim.ts b/ui/elemental_shaman/sim.ts index 75e12b2f22..16fda196bf 100644 --- a/ui/elemental_shaman/sim.ts +++ b/ui/elemental_shaman/sim.ts @@ -90,24 +90,12 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecElementalShaman, { specOptions: Presets.DefaultOptions, other: Presets.OtherDefaults, // Default raid/party buffs settings. - raidBuffs: RaidBuffs.create({ - arcaneBrilliance: true, - aspectOfTheLion: true, - divineSpirit: true, - giftOfTheWild: TristateEffect.TristateEffectImproved, - moonkinAura: true, - }), + raidBuffs: Presets.DefaultRaidBuffs, partyBuffs: PartyBuffs.create({ }), individualBuffs: IndividualBuffs.create({ - blessingOfKings: true, - blessingOfWisdom: 2, - }), - debuffs: Debuffs.create({ - faerieFire: true, - judgementOfWisdom: true, - curseOfElements: true, }), + debuffs: Presets.DefaultDebuffs, }, // IconInputs to include in the 'Player' section on the settings tab. playerIconInputs: [ diff --git a/ui/enhancement_shaman/apls/default_ft.apl.json b/ui/enhancement_shaman/apls/default_ft.apl.json deleted file mode 100644 index a30001456d..0000000000 --- a/ui/enhancement_shaman/apls/default_ft.apl.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "type": "TypeAPL", - "prepullActions": [ - {"action":{"castSpell":{"spellId":{"spellId":66842}}},"doAtValue":{"const":{"val":"-3s"}}}, - {"action":{"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}},"doAtValue":{"const":{"val":"-1s"}}} - ], - "priorityList": [ - {"action":{"autocastOtherCooldowns":{}}}, - {"action":{"condition":{"cmp":{"op":"OpEq","lhs":{"auraNumStacks":{"auraId":{"spellId":53817}}},"rhs":{"const":{"val":"5"}}}},"castSpell":{"spellId":{"spellId":49238}}}}, - {"action":{"castSpell":{"spellId":{"spellId":17364}}}}, - {"action":{"condition":{"cmp":{"op":"OpLe","lhs":{"dotRemainingTime":{"spellId":{"spellId":49233}}},"rhs":{"const":{"val":"0s"}}}},"castSpell":{"spellId":{"spellId":49233}}}}, - {"action":{"condition":{"not":{"val":{"auraIsActive":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":17364}}}}},"castSpell":{"spellId":{"spellId":17364}}}}, - {"action":{"castSpell":{"spellId":{"spellId":49231}}}}, - {"action":{"condition":{"cmp":{"op":"OpLt","lhs":{"totemRemainingTime":{"totemType":"Water"}},"rhs":{"const":{"val":"20s"}}}},"castSpell":{"spellId":{"spellId":66842}}}}, - {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpLe","lhs":{"dotRemainingTime":{"spellId":{"spellId":58734}}},"rhs":{"const":{"val":"100ms"}}}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":2894}}}}}]}},"castSpell":{"spellId":{"spellId":58734}}}}, - {"action":{"castSpell":{"spellId":{"spellId":61657}}}}, - {"action":{"condition":{"not":{"val":{"auraIsActive":{"auraId":{"spellId":49281}}}}},"castSpell":{"spellId":{"spellId":49281}}}}, - {"action":{"castSpell":{"spellId":{"spellId":60103}}}} - ] -} \ No newline at end of file diff --git a/ui/enhancement_shaman/apls/default_wf.apl.json b/ui/enhancement_shaman/apls/default_wf.apl.json deleted file mode 100644 index d692ef4d32..0000000000 --- a/ui/enhancement_shaman/apls/default_wf.apl.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "type": "TypeAPL", - "prepullActions": [ - {"action":{"castSpell":{"spellId":{"spellId":66842}}},"doAtValue":{"const":{"val":"-3s"}}}, - {"action":{"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}},"doAtValue":{"const":{"val":"-1s"}}} - ], - "priorityList": [ - {"action":{"autocastOtherCooldowns":{}}}, - {"action":{"condition":{"cmp":{"op":"OpEq","lhs":{"auraNumStacks":{"auraId":{"spellId":53817}}},"rhs":{"const":{"val":"5"}}}},"castSpell":{"spellId":{"spellId":49238}}}}, - {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpGe","lhs":{"auraNumStacks":{"auraId":{"spellId":53817}}},"rhs":{"const":{"val":"3"}}}},{"cmp":{"op":"OpLt","lhs":{"math":{"op":"OpAdd","lhs":{"const":{"val":"300ms"}},"rhs":{"spellCastTime":{"spellId":{"spellId":49238}}}}},"rhs":{"autoTimeToNext":{}}}}]}},"castSpell":{"spellId":{"spellId":49238}}}}, - {"action":{"castSpell":{"spellId":{"spellId":17364}}}}, - {"action":{"condition":{"cmp":{"op":"OpLe","lhs":{"dotRemainingTime":{"spellId":{"spellId":49233}}},"rhs":{"const":{"val":"0s"}}}},"castSpell":{"spellId":{"spellId":49233}}}}, - {"action":{"castSpell":{"spellId":{"spellId":49231}}}}, - {"action":{"condition":{"cmp":{"op":"OpLt","lhs":{"totemRemainingTime":{"totemType":"Water"}},"rhs":{"const":{"val":"20s"}}}},"castSpell":{"spellId":{"spellId":66842}}}}, - {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpLe","lhs":{"dotRemainingTime":{"spellId":{"spellId":58734}}},"rhs":{"const":{"val":"100ms"}}}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":2894}}}}}]}},"castSpell":{"spellId":{"spellId":58734}}}}, - {"action":{"castSpell":{"spellId":{"spellId":61657}}}}, - {"action":{"condition":{"not":{"val":{"auraIsActive":{"auraId":{"spellId":49281}}}}},"castSpell":{"spellId":{"spellId":49281}}}}, - {"action":{"castSpell":{"spellId":{"spellId":60103}}}} - ] -} \ No newline at end of file diff --git a/ui/enhancement_shaman/apls/phase_1.apl.json b/ui/enhancement_shaman/apls/phase_1.apl.json new file mode 100644 index 0000000000..5a08983019 --- /dev/null +++ b/ui/enhancement_shaman/apls/phase_1.apl.json @@ -0,0 +1,7 @@ +{ + "type": "TypeAPL", + "prepullActions": [ + ], + "priorityList": [ + ] +} \ No newline at end of file diff --git a/ui/enhancement_shaman/apls/phase_3.apl.json b/ui/enhancement_shaman/apls/phase_3.apl.json deleted file mode 100644 index b15a8dc720..0000000000 --- a/ui/enhancement_shaman/apls/phase_3.apl.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "type": "TypeAPL", - "prepullActions": [ - {"action":{"castSpell":{"spellId":{"spellId":66842}}},"doAtValue":{"const":{"val":"-3s"}}}, - {"action":{"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}},"doAtValue":{"const":{"val":"-1s"}}} - ], - "priorityList": [ - {"action":{"autocastOtherCooldowns":{}}}, - {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpLe","lhs":{"dotRemainingTime":{"spellId":{"spellId":49233}}},"rhs":{"const":{"val":"0s"}}}},{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"8s"}}}}]}},"castSpell":{"spellId":{"spellId":49233}}}}, - {"action":{"condition":{"cmp":{"op":"OpEq","lhs":{"auraNumStacks":{"auraId":{"spellId":53817}}},"rhs":{"const":{"val":"5"}}}},"castSpell":{"spellId":{"spellId":49238}}}}, - {"action":{"condition":{"not":{"val":{"auraIsActive":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":17364}}}}},"castSpell":{"spellId":{"spellId":17364}}}}, - {"action":{"castSpell":{"spellId":{"spellId":49231}}}}, - {"action":{"condition":{"cmp":{"op":"OpLt","lhs":{"totemRemainingTime":{"totemType":"Water"}},"rhs":{"const":{"val":"20s"}}}},"castSpell":{"spellId":{"spellId":66842}}}}, - {"action":{"castSpell":{"spellId":{"spellId":17364}}}}, - {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpLe","lhs":{"dotRemainingTime":{"spellId":{"spellId":58734}}},"rhs":{"const":{"val":"100ms"}}}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":2894}}}}}]}},"castSpell":{"spellId":{"spellId":58734}}}}, - {"action":{"castSpell":{"spellId":{"spellId":61657}}}}, - {"action":{"condition":{"not":{"val":{"auraIsActive":{"auraId":{"spellId":49281}}}}},"castSpell":{"spellId":{"spellId":49281}}}}, - {"action":{"castSpell":{"spellId":{"spellId":60103}}}} - ] -} \ No newline at end of file diff --git a/ui/enhancement_shaman/presets.ts b/ui/enhancement_shaman/presets.ts index 2aef123d17..89eb4357cc 100644 --- a/ui/enhancement_shaman/presets.ts +++ b/ui/enhancement_shaman/presets.ts @@ -20,9 +20,7 @@ import * as PresetUtils from '../core/preset_utils.js'; import BlankGear from './gear_sets/blank.gear.json'; import Phase1Gear from './gear_sets/phase_1.gear.json'; -import DefaultFt from './apls/default_ft.apl.json'; -import DefaultWf from './apls/default_wf.apl.json'; -import Phase3Apl from './apls/phase_3.apl.json'; +import Phase1APL from './apls/phase_1.apl.json'; // Preset options for this spec. // Eventually we will import these values for the raid sim too, so its good to @@ -33,30 +31,23 @@ export const Phase1PresetGear = PresetUtils.makePresetGear('Phase 1', Phase1Gear export const DefaultGear = Phase1PresetGear -export const ROTATION_FT_DEFAULT = PresetUtils.makePresetAPLRotation('Default FT', DefaultFt); -export const ROTATION_WF_DEFAULT = PresetUtils.makePresetAPLRotation('Default WF', DefaultWf); -export const ROTATION_PHASE_3 = PresetUtils.makePresetAPLRotation('Phase 3', Phase3Apl); +export const Phase1PresetAPL = PresetUtils.makePresetAPLRotation('P1 Preset', Phase1APL); + +export const DefaultAPL = Phase1PresetAPL // Default talents. Uses the wowhead calculator format, make the talents on // https://wowhead.com/classic/talent-calc and copy the numbers in the url. export const StandardTalents = { - name: 'Standard', - data: SavedTalents.create({ - talentsString: '053030152-30405003105021333031131031051', - }), -}; - -export const Phase3Talents = { - name: 'Phase 3', + name: 'P1 Preset', data: SavedTalents.create({ - talentsString: '053030152-30505003105001333031131131051', + talentsString: '-5005202101', }), }; export const DefaultOptions = EnhancementShamanOptions.create({ shield: ShamanShield.LightningShield, - imbueMh: ShamanImbue.WindfuryWeapon, - imbueOh: ShamanImbue.FlametongueWeapon, + imbueMh: ShamanImbue.RockbiterWeapon, + imbueOh: ShamanImbue.RockbiterWeapon, syncType: ShamanSyncType.Auto, }); @@ -66,6 +57,8 @@ export const DefaultConsumes = Consumes.create({ }); export const DefaultRaidBuffs = RaidBuffs.create({ + aspectOfTheLion: true, + strengthOfEarthTotem: TristateEffect.TristateEffectImproved, giftOfTheWild: TristateEffect.TristateEffectImproved, arcaneBrilliance: true, leaderOfThePack: true, @@ -76,10 +69,9 @@ export const DefaultRaidBuffs = RaidBuffs.create({ export const DefaultDebuffs = Debuffs.create({ sunderArmor: true, - curseOfWeakness: TristateEffect.TristateEffectRegular, curseOfElements: true, + curseOfRecklessness: true, faerieFire: true, - judgementOfWisdom: true, }); export const OtherDefaults = { diff --git a/ui/enhancement_shaman/sim.ts b/ui/enhancement_shaman/sim.ts index 5da5528981..acce8d8fc0 100644 --- a/ui/enhancement_shaman/sim.ts +++ b/ui/enhancement_shaman/sim.ts @@ -16,7 +16,6 @@ import { Stat, TristateEffect, } from '../core/proto/common.js'; -import { ShamanImbue } from '../core/proto/shaman.js'; import { Stats } from '../core/proto_utils/stats.js'; import { getSpecIcon, specNames } from '../core/proto_utils/utils.js'; @@ -149,13 +148,10 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecEnhancementShaman, { // Preset talents that the user can quickly select. talents: [ Presets.StandardTalents, - Presets.Phase3Talents, ], // Preset rotations that the user can quickly select. rotations: [ - Presets.ROTATION_FT_DEFAULT, - Presets.ROTATION_WF_DEFAULT, - Presets.ROTATION_PHASE_3, + Presets.Phase1PresetAPL, ], // Preset gear configurations that the user can quickly select. gear: [ @@ -163,14 +159,8 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecEnhancementShaman, { ], }, - autoRotation: (player: Player): APLRotation => { - const options = player.getSpecOptions(); - - if (options.imbueMh == ShamanImbue.FlametongueWeapon) { - return Presets.ROTATION_FT_DEFAULT.rotation.rotation!; - } else { - return Presets.ROTATION_WF_DEFAULT.rotation.rotation!; - } + autoRotation: (_): APLRotation => { + return Presets.DefaultAPL.rotation.rotation!; }, raidSimPresets: [ diff --git a/ui/raid/import_export.ts b/ui/raid/import_export.ts index e7a7ecab25..abee5028fa 100644 --- a/ui/raid/import_export.ts +++ b/ui/raid/import_export.ts @@ -639,8 +639,6 @@ const fullTypeToSpec: Record = { const racialSpells: Array<{ id: number, name: string, race: Race }> = [ { id: 26297, name: 'Berserking', race: Race.RaceTroll }, { id: 20572, name: 'Blood Fury (AP)', race: Race.RaceOrc }, - { id: 33697, name: 'Blood Fury (AP+SP)', race: Race.RaceOrc }, - { id: 33702, name: 'Blood Fury (SP)', race: Race.RaceOrc }, { id: 20589, name: 'Escape Artist', race: Race.RaceGnome }, { id: 20594, name: 'Stoneform', race: Race.RaceDwarf }, { id: 20549, name: 'War Stomp', race: Race.RaceTauren }, From 21f1b80cb247a9b29970615929c94f8cd603cbc5 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Mon, 5 Feb 2024 01:46:17 -0500 Subject: [PATCH 14/18] fix enhancement tests --- sim/raid_bench_test.go | 9 +- .../enhancement/TestEnhancement.results | 174 ++++++++++++++++++ sim/shaman/enhancement/enhancement_test.go | 20 +- .../gear_sets/phase_1.gear.json | 77 ++------ ui/enhancement_shaman/sim.ts | 3 +- 5 files changed, 202 insertions(+), 81 deletions(-) create mode 100644 sim/shaman/enhancement/TestEnhancement.results diff --git a/sim/raid_bench_test.go b/sim/raid_bench_test.go index 5ca89d894b..193ab314de 100644 --- a/sim/raid_bench_test.go +++ b/sim/raid_bench_test.go @@ -59,7 +59,7 @@ var castersWithElemental = &proto.Party{ Totems: &proto.ShamanTotems{ Earth: proto.EarthTotem_TremorTotem, Air: proto.AirTotem_WindfuryTotem, - Fire: proto.FireTotem_TotemOfWrath, + Fire: proto.FireTotem_SearingTotem, Water: proto.WaterTotem_ManaSpringTotem, }, }, @@ -175,13 +175,12 @@ func BenchmarkSimulate(b *testing.B) { Spec: &proto.Player_EnhancementShaman{ EnhancementShaman: &proto.EnhancementShaman{ Options: &proto.EnhancementShaman_Options{ - Shield: proto.ShamanShield_LightningShield, - Bloodlust: true, - SyncType: proto.ShamanSyncType_SyncMainhandOffhandSwings, + Shield: proto.ShamanShield_LightningShield, + SyncType: proto.ShamanSyncType_SyncMainhandOffhandSwings, Totems: &proto.ShamanTotems{ Earth: proto.EarthTotem_TremorTotem, Air: proto.AirTotem_WindfuryTotem, - Fire: proto.FireTotem_TotemOfWrath, + Fire: proto.FireTotem_SearingTotem, Water: proto.WaterTotem_ManaSpringTotem, }, }, diff --git a/sim/shaman/enhancement/TestEnhancement.results b/sim/shaman/enhancement/TestEnhancement.results new file mode 100644 index 0000000000..c8ea4da347 --- /dev/null +++ b/sim/shaman/enhancement/TestEnhancement.results @@ -0,0 +1,174 @@ +character_stats_results: { + key: "TestEnhancement-CharacterStats-Default" + value: { + final_stats: 132.44 + final_stats: 121.44 + final_stats: 136.84 + final_stats: 62.04 + final_stats: 68.64 + final_stats: 0 + final_stats: 0 + final_stats: 0 + final_stats: 0 + final_stats: 0 + final_stats: 0 + final_stats: 0 + final_stats: 20 + final_stats: 1 + final_stats: 8.29033 + final_stats: 0 + final_stats: 0 + final_stats: 490.88 + final_stats: 1 + final_stats: 16.49182 + final_stats: 0 + final_stats: 0 + final_stats: 0 + final_stats: 1155.6 + final_stats: 0 + final_stats: 0 + final_stats: 1966.38 + final_stats: 54 + final_stats: 0 + final_stats: 0 + final_stats: 0 + final_stats: 0 + final_stats: 0 + final_stats: 0 + final_stats: 1445.4 + final_stats: 0 + final_stats: 0 + final_stats: 0 + final_stats: 0 + final_stats: 10 + final_stats: 70 + final_stats: 0 + final_stats: 0 + final_stats: 0 + final_stats: 0 + } +} +dps_results: { + key: "TestEnhancement-AllItems-BlackfathomAvenger'sMail" + value: {} +} +dps_results: { + key: "TestEnhancement-AllItems-BlackfathomElementalist'sHide" + value: {} +} +dps_results: { + key: "TestEnhancement-AllItems-BlackfathomSlayer'sLeather" + value: {} +} +dps_results: { + key: "TestEnhancement-AllItems-StormshroudArmor" + value: {} +} +dps_results: { + key: "TestEnhancement-AllItems-TwilightInvoker'sVestments" + value: {} +} +dps_results: { + key: "TestEnhancement-Average-Default" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Orc-25-phase_1-FT-phase_1-FullBuffs-LongMultiTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Orc-25-phase_1-FT-phase_1-FullBuffs-LongSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Orc-25-phase_1-FT-phase_1-FullBuffs-ShortSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Orc-25-phase_1-FT-phase_1-NoBuffs-LongMultiTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Orc-25-phase_1-FT-phase_1-NoBuffs-LongSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Orc-25-phase_1-FT-phase_1-NoBuffs-ShortSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Orc-25-phase_1-WF-phase_1-FullBuffs-LongMultiTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Orc-25-phase_1-WF-phase_1-FullBuffs-LongSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Orc-25-phase_1-WF-phase_1-FullBuffs-ShortSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Orc-25-phase_1-WF-phase_1-NoBuffs-LongMultiTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Orc-25-phase_1-WF-phase_1-NoBuffs-LongSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Orc-25-phase_1-WF-phase_1-NoBuffs-ShortSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Troll-25-phase_1-FT-phase_1-FullBuffs-LongMultiTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Troll-25-phase_1-FT-phase_1-FullBuffs-LongSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Troll-25-phase_1-FT-phase_1-FullBuffs-ShortSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Troll-25-phase_1-FT-phase_1-NoBuffs-LongMultiTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Troll-25-phase_1-FT-phase_1-NoBuffs-LongSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Troll-25-phase_1-FT-phase_1-NoBuffs-ShortSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Troll-25-phase_1-WF-phase_1-FullBuffs-LongMultiTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Troll-25-phase_1-WF-phase_1-FullBuffs-LongSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Troll-25-phase_1-WF-phase_1-FullBuffs-ShortSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Troll-25-phase_1-WF-phase_1-NoBuffs-LongMultiTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Troll-25-phase_1-WF-phase_1-NoBuffs-LongSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-Settings-Troll-25-phase_1-WF-phase_1-NoBuffs-ShortSingleTarget" + value: {} +} +dps_results: { + key: "TestEnhancement-SwitchInFrontOfTarget-Default" + value: {} +} diff --git a/sim/shaman/enhancement/enhancement_test.go b/sim/shaman/enhancement/enhancement_test.go index dde3e4f6c2..0bf6479b84 100644 --- a/sim/shaman/enhancement/enhancement_test.go +++ b/sim/shaman/enhancement/enhancement_test.go @@ -18,18 +18,15 @@ func TestEnhancement(t *testing.T) { Race: proto.Race_RaceTroll, OtherRaces: []proto.Race{proto.Race_RaceOrc}, - GearSet: core.GetGearSet("../../../ui/enhancement_shaman/gear_sets", "p1"), + GearSet: core.GetGearSet("../../../ui/enhancement_shaman/gear_sets", "phase_1"), Talents: StandardTalents, Consumes: FullConsumes, SpecOptions: core.SpecOptionsCombo{Label: "FT", SpecOptions: PlayerOptionsFTFT}, OtherSpecOptions: []core.SpecOptionsCombo{ {Label: "WF", SpecOptions: PlayerOptionsWFWF}, }, - Rotation: core.GetAplRotation("../../../ui/enhancement_shaman/apls", "default_ft"), - OtherRotations: []core.RotationCombo{ - core.GetAplRotation("../../../ui/enhancement_shaman/apls", "default_wf"), - core.GetAplRotation("../../../ui/enhancement_shaman/apls", "phase_3"), - }, + Rotation: core.GetAplRotation("../../../ui/enhancement_shaman/apls", "phase_1"), + OtherRotations: []core.RotationCombo{}, ItemFilter: core.ItemFilter{ WeaponTypes: []proto.WeaponType{ @@ -55,7 +52,7 @@ func BenchmarkSimulate(b *testing.B) { &proto.Player{ Race: proto.Race_RaceOrc, Class: proto.Class_ClassShaman, - Equipment: core.GetGearSet("../../../ui/enhancement_shaman/gear_sets", "p1").GearSet, + Equipment: core.GetGearSet("../../../ui/enhancement_shaman/gear_sets", "phase_1").GearSet, TalentsString: StandardTalents, Consumes: FullConsumes, Spec: PlayerOptionsFTFT, @@ -76,7 +73,7 @@ func BenchmarkSimulate(b *testing.B) { core.RaidBenchmark(b, rsr) } -var StandardTalents = "053030152-30405003105021333031131031051" +var StandardTalents = "-5005202101" var PlayerOptionsWFWF = &proto.Player_EnhancementShaman{ EnhancementShaman: &proto.EnhancementShaman{ @@ -110,13 +107,6 @@ var enhShamFTFT = &proto.EnhancementShaman_Options{ }, } -var enhShamWFFT = &proto.EnhancementShaman_Options{ - Shield: proto.ShamanShield_LightningShield, - SyncType: proto.ShamanSyncType_NoSync, - ImbueMh: proto.ShamanImbue_WindfuryWeapon, - ImbueOh: proto.ShamanImbue_FlametongueWeapon, -} - var FullConsumes = &proto.Consumes{ // DefaultConjured: proto.Conjured_ConjuredFlameCap, } diff --git a/ui/enhancement_shaman/gear_sets/phase_1.gear.json b/ui/enhancement_shaman/gear_sets/phase_1.gear.json index c36aefa98d..8d6baeca47 100644 --- a/ui/enhancement_shaman/gear_sets/phase_1.gear.json +++ b/ui/enhancement_shaman/gear_sets/phase_1.gear.json @@ -1,64 +1,21 @@ { "items": [ - { - "id": 211789 - }, - { - "id": 209422 - }, - { - "id": 14573 - }, - { - "id": 213087, - "enchant": 247 - }, - { - "id": 211512, - "rune": 408496, - "enchant":847 - }, - { - "id": 209524, - "enchant": 823 - }, - { - "id": 211423, - "rune": 408507 - }, - { - "id": 16659 - }, - { - "id": 10410, - "rune": 425336 - }, - { - "id": 211511 - }, - { - "id": 211467 - }, - { - "id": 13097 - }, - { - "id": 211449 - }, - { - "id": 4381 - }, - { - "id": 209436, - "enchant": 241 - }, - { - "id": 1454, - "enchant": 241 - }, - {}, - { - "id": 209575 - } + {"id":211789}, + {"id":209422}, + {"id":14573}, + {"id":213087,"enchant":247}, + {"id":211512,"enchant":847,"rune":408496}, + {"id":209524,"enchant":823}, + {"id":211423,"rune":408507}, + {"id":16659}, + {"id":10410,"rune":425336}, + {"id":211511}, + {"id":211467}, + {"id":13097}, + {"id":211449}, + {"id":4381}, + {"id":209436}, + {"id":1454}, + {"id":209575} ] } diff --git a/ui/enhancement_shaman/sim.ts b/ui/enhancement_shaman/sim.ts index acce8d8fc0..20c358d9cd 100644 --- a/ui/enhancement_shaman/sim.ts +++ b/ui/enhancement_shaman/sim.ts @@ -155,7 +155,8 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecEnhancementShaman, { ], // Preset gear configurations that the user can quickly select. gear: [ - Presets.DefaultGear, + Presets.BlankPresetGear, + Presets.Phase1PresetGear, ], }, From aa839f43aa814a8e13164b02c21e6ba72d05e79f Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Mon, 5 Feb 2024 01:49:00 -0500 Subject: [PATCH 15/18] update wawrrior on landing page --- ui/index.html | 46 ++++++++-------------------------------------- 1 file changed, 8 insertions(+), 38 deletions(-) diff --git a/ui/index.html b/ui/index.html index 754c04b83e..421929c6b5 100644 --- a/ui/index.html +++ b/ui/index.html @@ -310,45 +310,15 @@

Season of Discovery

- From 17bd04a9d8c7654cc9ade3da9ef4634ac83c8c13 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Mon, 5 Feb 2024 01:51:06 -0500 Subject: [PATCH 16/18] linting --- ui/elemental_shaman/sim.ts | 4 +--- ui/restoration_shaman/sim.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ui/elemental_shaman/sim.ts b/ui/elemental_shaman/sim.ts index 16fda196bf..27c752d01d 100644 --- a/ui/elemental_shaman/sim.ts +++ b/ui/elemental_shaman/sim.ts @@ -3,15 +3,12 @@ import { ShamanShieldInput } from '../core/components/inputs/shaman_shields.js'; import { TotemsSection } from '../core/components/inputs/totem_inputs.js'; import { Class, - Debuffs, Faction, IndividualBuffs, PartyBuffs, Race, - RaidBuffs, Spec, Stat, - TristateEffect, } from '../core/proto/common.js'; import { APLRotation, @@ -23,6 +20,7 @@ import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.j import * as OtherInputs from '../core/components/other_inputs.js'; import * as Mechanics from '../core/constants/mechanics.js'; +// import * as ShamanInputs from './inputs.js'; import * as Presets from './presets.js'; const SPEC_CONFIG = registerSpecConfig(Spec.SpecElementalShaman, { diff --git a/ui/restoration_shaman/sim.ts b/ui/restoration_shaman/sim.ts index 1a2a753124..ff0a8acd54 100644 --- a/ui/restoration_shaman/sim.ts +++ b/ui/restoration_shaman/sim.ts @@ -23,7 +23,7 @@ import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.j import * as OtherInputs from '../core/components/other_inputs.js'; import * as Mechanics from '../core/constants/mechanics.js'; -import * as ShamanInputs from './inputs.js'; +// import * as ShamanInputs from './inputs.js'; import * as Presets from './presets.js'; const SPEC_CONFIG = registerSpecConfig(Spec.SpecRestorationShaman, { From e4734e6769277b56d8a783bf1ceb4661ad9b56cb Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Mon, 5 Feb 2024 02:20:47 -0500 Subject: [PATCH 17/18] add warrior gear presets --- ui/warrior/gear_sets/phase_1.gear.json | 21 +++++++++++++++++++++ ui/warrior/gear_sets/phase_1_dw.gear.json | 21 +++++++++++++++++++++ ui/warrior/presets.ts | 14 ++++++++++---- ui/warrior/sim.ts | 4 ++++ 4 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 ui/warrior/gear_sets/phase_1.gear.json create mode 100644 ui/warrior/gear_sets/phase_1_dw.gear.json diff --git a/ui/warrior/gear_sets/phase_1.gear.json b/ui/warrior/gear_sets/phase_1.gear.json new file mode 100644 index 0000000000..d96c171094 --- /dev/null +++ b/ui/warrior/gear_sets/phase_1.gear.json @@ -0,0 +1,21 @@ +{ + "items": [ + {"id":211505}, + {"id":209422}, + {"id":9817,"randomSuffix":680}, + {"id":213088,"enchant":247}, + {"id":211504,"enchant":847,"rune":402911}, + {"id":209524,"enchant":823}, + {"id":209568,"rune":429765}, + {"id":6460}, + {"id":209566,"rune":425418}, + {"id":211506,"enchant":247}, + {"id":20429}, + {"id":211467}, + {"id":211449}, + {"id":21568}, + {"id":209562}, + {}, + {"id":209563} + ] +} \ No newline at end of file diff --git a/ui/warrior/gear_sets/phase_1_dw.gear.json b/ui/warrior/gear_sets/phase_1_dw.gear.json new file mode 100644 index 0000000000..84d67c4b56 --- /dev/null +++ b/ui/warrior/gear_sets/phase_1_dw.gear.json @@ -0,0 +1,21 @@ +{ + "items": [ + {"id":211505}, + {"id":209422}, + {"id":9817,"randomSuffix":680}, + {"id":213088,"enchant":247}, + {"id":211504,"enchant":847,"rune":402911}, + {"id":209524,"enchant":823}, + {"id":209568,"rune":413404}, + {"id":6460}, + {"id":209566,"rune":425418}, + {"id":211506,"enchant":247}, + {"id":20429}, + {"id":211467}, + {"id":211449}, + {"id":21568}, + {"id":209525}, + {"id":209694}, + {"id":209563} + ] +} \ No newline at end of file diff --git a/ui/warrior/presets.ts b/ui/warrior/presets.ts index 09eb639a15..c3a74d4a59 100644 --- a/ui/warrior/presets.ts +++ b/ui/warrior/presets.ts @@ -1,5 +1,4 @@ import { - Conjured, Consumes, Flask, Food, @@ -16,16 +15,23 @@ import { import * as PresetUtils from '../core/preset_utils.js'; import BlankGear from './gear_sets/blank.gear.json'; +import Phase1Gear from './gear_sets/phase_1.gear.json'; +import Phase1DWGear from './gear_sets/phase_1_dw.gear.json'; import DefaultAPL from './apls/default.apl.json'; -import { BlackfathomSharpeningStone, ConjuredMinorRecombobulator } from 'ui/core/components/inputs/consumables.js'; // Preset options for this spec. // Eventually we will import these values for the raid sim too, so its good to // keep them in a separate file. -export const GearArmsDefault = PresetUtils.makePresetGear('Blank', BlankGear, { talentTree: 0 }); -export const GearFuryDefault = PresetUtils.makePresetGear('Blank', BlankGear, { talentTree: 1 }); +export const GearBlank = PresetUtils.makePresetGear('Blank', BlankGear); + +export const GearArmsPhase1 = PresetUtils.makePresetGear('P1 Arms 2H', Phase1Gear, { talentTree: 0 }); +export const GearArmsDWPhase1 = PresetUtils.makePresetGear('P1 Arms DW', Phase1DWGear, { talentTree: 0 }); +export const GearFuryPhase1 = PresetUtils.makePresetGear('P1 Fury', Phase1Gear, { talentTree: 1 }); + +export const GearArmsDefault = GearArmsPhase1; +export const GearFuryDefault = GearFuryPhase1; export const RotationArmsDefault = PresetUtils.makePresetAPLRotation('Default', DefaultAPL); export const RotationFuryDefault = PresetUtils.makePresetAPLRotation('Default', DefaultAPL); diff --git a/ui/warrior/sim.ts b/ui/warrior/sim.ts index d30c0614ff..1fb4284ac0 100644 --- a/ui/warrior/sim.ts +++ b/ui/warrior/sim.ts @@ -146,6 +146,10 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarrior, { ], // Preset gear configurations that the user can quickly select. gear: [ + Presets.GearBlank, + Presets.GearArmsPhase1, + Presets.GearArmsDWPhase1, + Presets.GearFuryPhase1, ], }, From d08cda6f3a4a1d88a37061d9ebfaa8f63e138b45 Mon Sep 17 00:00:00 2001 From: rosenrusinov Date: Mon, 5 Feb 2024 11:16:58 +0100 Subject: [PATCH 18/18] add demonic sacrifice, master demonologist and soul link --- sim/warlock/pet.go | 55 ------------- sim/warlock/runes.go | 17 ++++ sim/warlock/talents.go | 183 +++++++++++++++++++++++++++++++++++++++++ sim/warlock/warlock.go | 6 +- 4 files changed, 204 insertions(+), 57 deletions(-) diff --git a/sim/warlock/pet.go b/sim/warlock/pet.go index 15388416f8..13d80d9fce 100644 --- a/sim/warlock/pet.go +++ b/sim/warlock/pet.go @@ -261,65 +261,10 @@ func (warlock *Warlock) NewWarlockPet() *WarlockPet { wp.EnableAutoAttacks(wp, cfg.AutoAttacks) } - if warlock.Talents.MasterDemonologist > 0 { - md := core.Aura{ - Label: "Master Demonologist", - ActionID: core.ActionID{SpellID: 23825}, - Duration: core.NeverExpires, - OnGain: func(aura *core.Aura, _ *core.Simulation) { - switch warlock.Options.Summon { - case proto.WarlockOptions_Imp: - aura.Unit.PseudoStats.ThreatMultiplier /= 1 + 0.04*float64(warlock.Talents.MasterDemonologist) - case proto.WarlockOptions_Succubus: - aura.Unit.PseudoStats.DamageDealtMultiplier *= 1 + 0.02*float64(warlock.Talents.MasterDemonologist) - } - }, - OnExpire: func(aura *core.Aura, _ *core.Simulation) { - switch warlock.Options.Summon { - case proto.WarlockOptions_Imp: - aura.Unit.PseudoStats.ThreatMultiplier *= 1 + 0.04*float64(warlock.Talents.MasterDemonologist) - case proto.WarlockOptions_Succubus: - aura.Unit.PseudoStats.DamageDealtMultiplier /= 1 + 0.02*float64(warlock.Talents.MasterDemonologist) - } - }, - } - - mdLockAura := warlock.RegisterAura(md) - mdPetAura := wp.RegisterAura(md) - - wp.OnPetEnable = func(sim *core.Simulation) { - mdLockAura.Activate(sim) - mdPetAura.Activate(sim) - } - - wp.OnPetDisable = func(sim *core.Simulation) { - mdLockAura.Deactivate(sim) - mdPetAura.Deactivate(sim) - } - } - if warlock.Talents.FelIntellect > 0 { wp.MultiplyStat(stats.Mana, 1+0.03*float64(warlock.Talents.FelIntellect)) } - if warlock.HasRune(proto.WarlockRune_RuneBootsDemonicKnowledge) { - oldPetEnable := wp.OnPetEnable - wp.OnPetEnable = func(sim *core.Simulation) { - if oldPetEnable != nil { - oldPetEnable(sim) - } - warlock.DemonicKnowledgeAura.Activate(sim) - } - - oldPetDisable := wp.OnPetDisable - wp.OnPetDisable = func(sim *core.Simulation) { - if oldPetDisable != nil { - oldPetDisable(sim) - } - warlock.DemonicKnowledgeAura.Deactivate(sim) - } - } - // core.ApplyPetConsumeEffects(&wp.Character, warlock.Consumes) warlock.AddPet(wp) diff --git a/sim/warlock/runes.go b/sim/warlock/runes.go index 7bee86a095..2b25670290 100644 --- a/sim/warlock/runes.go +++ b/sim/warlock/runes.go @@ -41,6 +41,23 @@ func (warlock *Warlock) applyDemonicKnowledge() { return } + wp := warlock.Pet + oldPetEnable := wp.OnPetEnable + wp.OnPetEnable = func(sim *core.Simulation) { + if oldPetEnable != nil { + oldPetEnable(sim) + } + warlock.DemonicKnowledgeAura.Activate(sim) + } + + oldPetDisable := wp.OnPetDisable + wp.OnPetDisable = func(sim *core.Simulation) { + if oldPetDisable != nil { + oldPetDisable(sim) + } + warlock.DemonicKnowledgeAura.Deactivate(sim) + } + warlock.DemonicKnowledgeAura = warlock.GetOrRegisterAura(core.Aura{ Label: "Demonic Knowledge", ActionID: core.ActionID{SpellID: 412732}, diff --git a/sim/warlock/talents.go b/sim/warlock/talents.go index 9875aa4a6d..c7906919c4 100644 --- a/sim/warlock/talents.go +++ b/sim/warlock/talents.go @@ -21,6 +21,189 @@ func (warlock *Warlock) ApplyTalents() { } warlock.applyNightfall() + warlock.applyMasterDemonologist() + warlock.applyDemonicSacrifice() + warlock.applySoulLink() +} + +func (warlock *Warlock) applyMasterDemonologist() { + if warlock.Talents.MasterDemonologist == 0 || warlock.Pet == nil { + return + } + + masterDemonologistConfig := core.Aura{ + Label: "Master Demonologist", + ActionID: core.ActionID{SpellID: 23825}, + Duration: core.NeverExpires, + OnGain: func(aura *core.Aura, _ *core.Simulation) { + switch warlock.Options.Summon { + case proto.WarlockOptions_Imp: + aura.Unit.PseudoStats.ThreatMultiplier /= 1 + 0.04*float64(warlock.Talents.MasterDemonologist) + case proto.WarlockOptions_Succubus: + aura.Unit.PseudoStats.DamageDealtMultiplier *= 1 + 0.02*float64(warlock.Talents.MasterDemonologist) + case proto.WarlockOptions_Voidwalker: + aura.Unit.PseudoStats.DamageTakenMultiplier /= 1 + 0.02*float64(warlock.Talents.MasterDemonologist) + } + }, + OnExpire: func(aura *core.Aura, _ *core.Simulation) { + switch warlock.Options.Summon { + case proto.WarlockOptions_Imp: + aura.Unit.PseudoStats.ThreatMultiplier *= 1 + 0.04*float64(warlock.Talents.MasterDemonologist) + case proto.WarlockOptions_Succubus: + aura.Unit.PseudoStats.DamageDealtMultiplier /= 1 + 0.02*float64(warlock.Talents.MasterDemonologist) + case proto.WarlockOptions_Voidwalker: + aura.Unit.PseudoStats.DamageTakenMultiplier *= 1 + 0.02*float64(warlock.Talents.MasterDemonologist) + } + }, + } + + wp := warlock.Pet + + mdLockAura := warlock.RegisterAura(masterDemonologistConfig) + mdPetAura := wp.RegisterAura(masterDemonologistConfig) + + wp.OnPetEnable = func(sim *core.Simulation) { + mdLockAura.Activate(sim) + mdPetAura.Activate(sim) + } + + wp.OnPetDisable = func(sim *core.Simulation) { + mdLockAura.Deactivate(sim) + mdPetAura.Deactivate(sim) + } +} + +func (warlock *Warlock) applySoulLink() { + if !warlock.Talents.SoulLink || warlock.Pet == nil { + return + } + + actionID := core.ActionID{SpellID: 19028} + soulLinkConfig := core.Aura{ + Label: "Soul Link Aura", + ActionID: actionID, + Duration: core.NeverExpires, + + OnGain: func(aura *core.Aura, sim *core.Simulation) { + aura.Unit.PseudoStats.DamageDealtMultiplier *= 1.03 + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + aura.Unit.PseudoStats.DamageDealtMultiplier /= 1.03 + }, + } + + warlockSlAura := warlock.RegisterAura(soulLinkConfig) + petSlAura := warlock.Pet.RegisterAura(soulLinkConfig) + + wp := warlock.Pet + oldPetDisable := wp.OnPetDisable + wp.OnPetDisable = func(sim *core.Simulation) { + if oldPetDisable != nil { + oldPetDisable(sim) + } + if warlockSlAura.IsActive() { + warlockSlAura.Deactivate(sim) + } + if petSlAura.IsActive() { + petSlAura.Deactivate(sim) + } + } + + warlock.RegisterSpell(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 warlock.Pet != nil && warlock.Pet.IsActive() + }, + + ManaCost: core.ManaCostOptions{ + BaseCost: 0.2, + }, + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { + warlockSlAura.Activate(sim) + petSlAura.Activate(sim) + }, + }) +} + +func (warlock *Warlock) applyDemonicSacrifice() { + if !warlock.Talents.DemonicSacrifice || warlock.Pet == nil { + return + } + + wp := warlock.Pet + oldPetEnable := wp.OnPetEnable + wp.OnPetEnable = func(sim *core.Simulation) { + if oldPetEnable != nil { + oldPetEnable(sim) + } + if warlock.demonicSacrificeAura.IsActive() { + warlock.demonicSacrificeAura.Deactivate(sim) + warlock.demonicSacrificeAura = nil + } + } + + impAura := warlock.GetOrRegisterAura(core.Aura{ + Label: "Burning Wish", + ActionID: core.ActionID{SpellID: 18789}, + Duration: 30 * time.Minute, + + OnGain: func(aura *core.Aura, sim *core.Simulation) { + warlock.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexFire] *= 1.15 + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + warlock.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexFire] /= 1.15 + }, + }) + + succubusAura := warlock.GetOrRegisterAura(core.Aura{ + Label: "Touch of Shadow", + ActionID: core.ActionID{SpellID: 18791}, + Duration: 30 * time.Minute, + + OnGain: func(aura *core.Aura, sim *core.Simulation) { + warlock.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexShadow] *= 1.15 + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + warlock.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexShadow] /= 1.15 + }, + }) + + warlock.GetOrRegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: 18788}, + SpellSchool: core.SpellSchoolShadow, + Flags: core.SpellFlagAPL, + + ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { + return warlock.Pet != nil && warlock.Pet.IsActive() + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + switch warlock.Options.Summon { + case proto.WarlockOptions_Imp: + warlock.demonicSacrificeAura = impAura + break + case proto.WarlockOptions_Succubus: + warlock.demonicSacrificeAura = succubusAura + break + case proto.WarlockOptions_Voidwalker: + break + case proto.WarlockOptions_Felhunter: + break + } + + if warlock.demonicSacrificeAura != nil { + warlock.demonicSacrificeAura.Activate(sim) + warlock.Pet.Disable(sim) + } + }, + }) } func (warlock *Warlock) applyImprovedShadowBolt() { diff --git a/sim/warlock/warlock.go b/sim/warlock/warlock.go index 4a08777f0b..2b1abbc813 100644 --- a/sim/warlock/warlock.go +++ b/sim/warlock/warlock.go @@ -67,8 +67,10 @@ type Warlock struct { DPSPAggregate float64 PreviousTime time.Duration - petStmBonusSP float64 - demonicKnowledgeSp float64 + petStmBonusSP float64 + demonicKnowledgeSp float64 + demonicSacrificeAura *core.Aura + soulLinkAura *core.Aura } func (warlock *Warlock) GetCharacter() *core.Character {