From 2dbe554aae3db929541fc051fcd301d0d1be5041 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Mon, 5 Feb 2024 17:34:27 -0500 Subject: [PATCH 1/6] update presets + fix healing stream --- sim/shaman/water_totems.go | 8 ++++---- ui/elemental_shaman/apls/phase_1.apl.json | 6 +++++- ui/elemental_shaman/presets.ts | 19 ++++++++++++++----- ui/elemental_shaman/sim.ts | 11 ++++++----- ui/enhancement_shaman/apls/phase_1.apl.json | 17 ++++++++++++----- ui/enhancement_shaman/presets.ts | 21 +++++++++++++++------ ui/enhancement_shaman/sim.ts | 11 ++++++----- 7 files changed, 62 insertions(+), 31 deletions(-) diff --git a/sim/shaman/water_totems.go b/sim/shaman/water_totems.go index 04d871423f..c21de21998 100644 --- a/sim/shaman/water_totems.go +++ b/sim/shaman/water_totems.go @@ -39,15 +39,15 @@ func (shaman *Shaman) newHealingStreamTotemSpellConfig(rank int) core.SpellConfi healInterval := time.Second * 2 config := shaman.newTotemSpellConfig(manaCost, spellId) + config.RequiredLevel = level + config.Rank = rank + healSpell := shaman.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: healId}, SpellSchool: core.SpellSchoolNature, - ProcMask: core.ProcMaskEmpty, + ProcMask: core.ProcMaskSpellHealing, 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), diff --git a/ui/elemental_shaman/apls/phase_1.apl.json b/ui/elemental_shaman/apls/phase_1.apl.json index 6d732aaa2e..4a1cebeb5b 100644 --- a/ui/elemental_shaman/apls/phase_1.apl.json +++ b/ui/elemental_shaman/apls/phase_1.apl.json @@ -1,11 +1,15 @@ { "type": "TypeAPL", "prepullActions": [ + {"action":{"castSpell":{"spellId":{"spellId":5394,"rank":1}}},"doAtValue":{"const":{"val":"-7.55s"}}}, + {"action":{"castSpell":{"spellId":{"spellId":8160,"rank":2}}},"doAtValue":{"const":{"val":"-6.05s"}}}, + {"action":{"castSpell":{"spellId":{"spellId":6363,"rank":2}}},"doAtValue":{"const":{"val":"-4.55s"}}}, {"action":{"castSpell":{"spellId":{"spellId":915,"rank":4}}},"doAtValue":{"const":{"val":"-3.05s"}}} ], "priorityList": [ {"action":{"condition":{"cmp":{"op":"OpLe","lhs":{"dotRemainingTime":{"spellId":{"spellId":8052,"rank":2}}},"rhs":{"const":{"val":"2"}}}},"castSpell":{"spellId":{"spellId":8052,"rank":2}}}}, {"action":{"castSpell":{"spellId":{"spellId":408490}}}}, + {"action":{"condition":{"not":{"val":{"dotIsActive":{"spellId":{"spellId":6363,"rank":2}}}}},"castSpell":{"spellId":{"spellId":6363,"rank":2}}}}, {"action":{"condition":{"cmp":{"op":"OpGt","lhs":{"dotRemainingTime":{"spellId":{"spellId":8052,"rank":2}}},"rhs":{"spellCastTime":{"spellId":{"spellId":915,"rank":4}}}}},"castSpell":{"spellId":{"spellId":915,"rank":4}}}} ] -} +} \ No newline at end of file diff --git a/ui/elemental_shaman/presets.ts b/ui/elemental_shaman/presets.ts index dea3bcc793..9026caf9ae 100644 --- a/ui/elemental_shaman/presets.ts +++ b/ui/elemental_shaman/presets.ts @@ -31,24 +31,33 @@ import Phase1APL from './apls/phase_1.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 BlankPresetGear = PresetUtils.makePresetGear('Blank', BlankGear); -export const Phase1PresetGear = PresetUtils.makePresetGear('Phase 1', Phase1Gear); +export const GearBlank = PresetUtils.makePresetGear('Blank', BlankGear); +export const GearPhase1 = PresetUtils.makePresetGear('Phase 1', Phase1Gear); -export const DefaultGear = Phase1PresetGear; +export const DefaultGear = GearPhase1; export const Phase1PresetAPL = PresetUtils.makePresetAPLRotation('Phase 1', Phase1APL); -export const DefaultAPL = Phase1PresetAPL +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 = { +export const TalentsPhase1 = { name: 'Phase 1', data: SavedTalents.create({ talentsString: '25003105', }), }; +export const TalentsPhase2 = { + name: 'Phase 2', + data: SavedTalents.create({ + talentsString: '350031550002151', + }), +}; + +export const DefaultTalents = TalentsPhase1; + export const DefaultOptions = ElementalShamanOptions.create({ shield: ShamanShield.WaterShield, imbueMh: ShamanImbue.RockbiterWeapon, diff --git a/ui/elemental_shaman/sim.ts b/ui/elemental_shaman/sim.ts index f1ea322faa..fbd1fa221e 100644 --- a/ui/elemental_shaman/sim.ts +++ b/ui/elemental_shaman/sim.ts @@ -82,7 +82,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecElementalShaman, { // Default consumes settings. consumes: Presets.DefaultConsumes, // Default talents. - talents: Presets.StandardTalents.data, + talents: Presets.DefaultTalents.data, // Default spec-specific settings. specOptions: Presets.DefaultOptions, other: Presets.OtherDefaults, @@ -122,7 +122,8 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecElementalShaman, { presets: { // Preset talents that the user can quickly select. talents: [ - Presets.StandardTalents, + Presets.TalentsPhase1, + Presets.TalentsPhase2, ], // Preset rotations that the user can quickly select. rotations: [ @@ -130,8 +131,8 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecElementalShaman, { ], // Preset gear configurations that the user can quickly select. gear: [ - Presets.BlankPresetGear, - Presets.Phase1PresetGear, + Presets.GearBlank, + Presets.GearPhase1, ], }, @@ -146,7 +147,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecElementalShaman, { defaultName: 'Elemental', iconUrl: getSpecIcon(Class.ClassShaman, 0), - talents: Presets.StandardTalents.data, + talents: Presets.DefaultTalents.data, specOptions: Presets.DefaultOptions, consumes: Presets.DefaultConsumes, defaultFactionRaces: { diff --git a/ui/enhancement_shaman/apls/phase_1.apl.json b/ui/enhancement_shaman/apls/phase_1.apl.json index 5a08983019..603947443f 100644 --- a/ui/enhancement_shaman/apls/phase_1.apl.json +++ b/ui/enhancement_shaman/apls/phase_1.apl.json @@ -1,7 +1,14 @@ { - "type": "TypeAPL", - "prepullActions": [ - ], - "priorityList": [ - ] + "type": "TypeAPL", + "prepullActions": [ + {"action":{"castSpell":{"spellId":{"spellId":5394,"rank":1}}},"doAtValue":{"const":{"val":"-4.5s"}}}, + {"action":{"castSpell":{"spellId":{"spellId":8160,"rank":2}}},"doAtValue":{"const":{"val":"-3s"}}}, + {"action":{"castSpell":{"spellId":{"spellId":6363,"rank":2}}},"doAtValue":{"const":{"val":"-1.5s"}}} + ], + "priorityList": [ + {"action":{"condition":{"not":{"val":{"dotIsActive":{"spellId":{"spellId":8052,"rank":2}}}}},"castSpell":{"spellId":{"spellId":8052,"rank":2}}}}, + {"action":{"castSpell":{"spellId":{"spellId":408507}}}}, + {"action":{"castSpell":{"spellId":{"spellId":8056,"rank":1}}}}, + {"action":{"condition":{"not":{"val":{"dotIsActive":{"spellId":{"spellId":6363,"rank":2}}}}},"castSpell":{"spellId":{"spellId":6363,"rank":2}}}} + ] } \ No newline at end of file diff --git a/ui/enhancement_shaman/presets.ts b/ui/enhancement_shaman/presets.ts index 89eb4357cc..5d16932bc0 100644 --- a/ui/enhancement_shaman/presets.ts +++ b/ui/enhancement_shaman/presets.ts @@ -26,24 +26,33 @@ import Phase1APL from './apls/phase_1.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 BlankPresetGear = PresetUtils.makePresetGear('Blank', BlankGear); -export const Phase1PresetGear = PresetUtils.makePresetGear('Phase 1', Phase1Gear); +export const GearBlank = PresetUtils.makePresetGear('Blank', BlankGear); +export const GearPhase1 = PresetUtils.makePresetGear('Phase 1', Phase1Gear); -export const DefaultGear = Phase1PresetGear +export const DefaultGear = GearPhase1 -export const Phase1PresetAPL = PresetUtils.makePresetAPLRotation('P1 Preset', Phase1APL); +export const Phase1PresetAPL = PresetUtils.makePresetAPLRotation('Phase 1', 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: 'P1 Preset', +export const TalentsPhase1 = { + name: 'Phase 1', data: SavedTalents.create({ talentsString: '-5005202101', }), }; +export const TalentsPhase2 = { + name: 'Phase 2', + data: SavedTalents.create({ + talentsString: '-5005202105023051', + }), +}; + +export const DefaultTalents = TalentsPhase1; + export const DefaultOptions = EnhancementShamanOptions.create({ shield: ShamanShield.LightningShield, imbueMh: ShamanImbue.RockbiterWeapon, diff --git a/ui/enhancement_shaman/sim.ts b/ui/enhancement_shaman/sim.ts index 53d37df8d8..04348c6d23 100644 --- a/ui/enhancement_shaman/sim.ts +++ b/ui/enhancement_shaman/sim.ts @@ -96,7 +96,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecEnhancementShaman, { // Default consumes settings. consumes: Presets.DefaultConsumes, // Default talents. - talents: Presets.StandardTalents.data, + talents: Presets.DefaultTalents.data, // Default spec-specific settings. specOptions: Presets.DefaultOptions, // Default raid/party buffs settings. @@ -146,7 +146,8 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecEnhancementShaman, { presets: { // Preset talents that the user can quickly select. talents: [ - Presets.StandardTalents, + Presets.TalentsPhase1, + Presets.TalentsPhase2, ], // Preset rotations that the user can quickly select. rotations: [ @@ -154,8 +155,8 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecEnhancementShaman, { ], // Preset gear configurations that the user can quickly select. gear: [ - Presets.BlankPresetGear, - Presets.Phase1PresetGear, + Presets.GearBlank, + Presets.GearPhase1, ], }, @@ -170,7 +171,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecEnhancementShaman, { defaultName: 'Balance', iconUrl: getSpecIcon(Class.ClassDruid, 0), - talents: Presets.StandardTalents.data, + talents: Presets.DefaultTalents.data, specOptions: Presets.DefaultOptions, consumes: Presets.DefaultConsumes, otherDefaults: Presets.OtherDefaults, From 8973b0973beb2c4a2d8440714885175611cd52b5 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Mon, 5 Feb 2024 18:00:08 -0500 Subject: [PATCH 2/6] more rune updates --- sim/shaman/chain_heal.go | 1 + sim/shaman/fire_totems.go | 3 + sim/shaman/flame_shock.go | 10 ++ sim/shaman/runes.go | 106 +++----------------- sim/shaman/shaman.go | 4 + sim/shaman/shamanistic_rage.go | 75 ++++++++++++++ ui/enhancement_shaman/apls/phase_1.apl.json | 1 + 7 files changed, 110 insertions(+), 90 deletions(-) create mode 100644 sim/shaman/shamanistic_rage.go diff --git a/sim/shaman/chain_heal.go b/sim/shaman/chain_heal.go index 07f6c843c6..86175b1d1a 100644 --- a/sim/shaman/chain_heal.go +++ b/sim/shaman/chain_heal.go @@ -60,6 +60,7 @@ func (shaman *Shaman) newChainHealSpellConfig(rank int, isOverload bool) core.Sp spell := core.SpellConfig{ ActionID: core.ActionID{SpellID: spellId}, + SpellCode: int32(SpellCode_ChainHeal), SpellSchool: core.SpellSchoolNature, ProcMask: core.ProcMaskSpellHealing, Flags: flags, diff --git a/sim/shaman/fire_totems.go b/sim/shaman/fire_totems.go index c52afa926e..5529a7f005 100644 --- a/sim/shaman/fire_totems.go +++ b/sim/shaman/fire_totems.go @@ -57,6 +57,7 @@ func (shaman *Shaman) newSearingTotemSpellConfig(rank int) core.SpellConfig { DefaultCast: core.Cast{ GCD: core.GCDDefault, }, + IgnoreHaste: true, }, DamageMultiplier: 1 + float64(shaman.Talents.CallOfFlame)*0.05, @@ -145,6 +146,7 @@ func (shaman *Shaman) newMagmaTotemSpellConfig(rank int) core.SpellConfig { DefaultCast: core.Cast{ GCD: core.GCDDefault, }, + IgnoreHaste: true, }, DamageMultiplier: 1 + float64(shaman.Talents.CallOfFlame)*0.05, @@ -235,6 +237,7 @@ func (shaman *Shaman) newFireNovaTotemSpellConfig(rank int) core.SpellConfig { DefaultCast: core.Cast{ GCD: core.GCDDefault, }, + IgnoreHaste: true, CD: core.Cooldown{ Timer: shaman.NewTimer(), Duration: cooldown, diff --git a/sim/shaman/flame_shock.go b/sim/shaman/flame_shock.go index 11935158c3..80ef3a16cc 100644 --- a/sim/shaman/flame_shock.go +++ b/sim/shaman/flame_shock.go @@ -51,6 +51,8 @@ func (shaman *Shaman) newFlameShockSpellConfig(rank int, shockTimer *core.Timer) spell.RequiredLevel = level spell.Rank = rank + spell.Cast.IgnoreHaste = true + spell.ApplyEffects = func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { damage := baseDamage + baseSpellCoeff*spell.SpellPower() result := spell.CalcDamage(sim, target, damage, spell.OutcomeMagicHitAndCrit) @@ -61,6 +63,10 @@ func (shaman *Shaman) newFlameShockSpellConfig(rank int, shockTimer *core.Timer) if shaman.HasRune(proto.ShamanRune_RuneLegsAncestralGuidance) { shaman.lastFlameShockTarget = target } + + if shaman.HasRune(proto.ShamanRune_RuneWaistPowerSurge) && sim.RandomFloat("Power Surge Proc") < ShamanPowerSurgeProcChance { + shaman.PowerSurgeAura.Activate(sim) + } } spell.DealDamage(sim, result) } @@ -95,6 +101,10 @@ func (shaman *Shaman) newFlameShockSpellConfig(rank int, shockTimer *core.Timer) if shaman.HasRune(proto.ShamanRune_RuneHandsMoltenBlast) && result.Landed() && sim.RandomFloat("Molten Blast Reset") < ShamanMoltenBlastResetChance { shaman.MoltenBlast.CD.Reset() } + + if shaman.HasRune(proto.ShamanRune_RuneWaistPowerSurge) && sim.RandomFloat("Power Surge Proc") < ShamanPowerSurgeProcChance { + shaman.PowerSurgeAura.Activate(sim) + } }, } diff --git a/sim/shaman/runes.go b/sim/shaman/runes.go index fef34a5834..4237c0a93c 100644 --- a/sim/shaman/runes.go +++ b/sim/shaman/runes.go @@ -1,7 +1,6 @@ package shaman import ( - "slices" "time" "github.com/wowsims/sod/sim/core" @@ -129,21 +128,25 @@ func (shaman *Shaman) applyPowerSurge() { return } - var affectedSpells []*core.Spell + intMP5Rate := .15 - procAura := shaman.RegisterAura(core.Aura{ + shaman.AddStats( + stats.Stats{ + stats.MP5: shaman.GetStat(stats.Intellect) * intMP5Rate, + }, + ) + + affectedSpells := core.FilterSlice( + core.Flatten([][]*core.Spell{ + shaman.ChainLightning, + shaman.ChainHeal, + {shaman.LavaBurst}, + }), func(spell *core.Spell) bool { return spell != nil }) + + shaman.PowerSurgeAura = shaman.RegisterAura(core.Aura{ Label: "Power Surge Proc", ActionID: core.ActionID{SpellID: 440285}, Duration: time.Second * 10, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - affectedSpells = core.FilterSlice( - core.Flatten([][]*core.Spell{ - shaman.ChainLightning, - shaman.ChainHeal, - {shaman.LavaBurst}, - }), func(spell *core.Spell) bool { return spell != nil }, - ) - }, OnGain: func(aura *core.Aura, sim *core.Simulation) { shaman.LavaBurst.CD.Reset() for _, spell := range affectedSpells { @@ -155,31 +158,12 @@ func (shaman *Shaman) applyPowerSurge() { core.Each(affectedSpells, func(spell *core.Spell) { spell.CastTimeMultiplier += 1 }) }, OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.SpellCode != int32(SpellCode_ShamanChainLightning) && spell != shaman.LavaBurst { + if spell.SpellCode != int32(SpellCode_ShamanChainLightning) && spell.SpellCode != int32(SpellCode_ChainHeal) && spell != shaman.LavaBurst { return } aura.Deactivate(sim) }, }) - - shaman.RegisterAura(core.Aura{ - Label: "Power Surge", - ActionID: core.ActionID{SpellID: int32(proto.ShamanRune_RuneWaistPowerSurge)}, - 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() && slices.Contains(shaman.FlameShock, spell) && sim.RandomFloat("Power Surge Proc") < ShamanPowerSurgeProcChance { - procAura.Activate(sim) - } - }, - OnPeriodicDamageDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if result.Landed() && slices.Contains(shaman.FlameShock, spell) && sim.RandomFloat("Power Surge Proc") < ShamanPowerSurgeProcChance { - procAura.Activate(sim) - } - }, - }) } func (shaman *Shaman) applyWayOfEarth() { @@ -196,61 +180,3 @@ func (shaman *Shaman) applyWayOfEarth() { }, }) } - -func (shaman *Shaman) applyShamanisticRage() { - if !shaman.HasRune(proto.ShamanRune_RuneLegsShamanisticRage) { - return - } - - actionID := core.ActionID{SpellID: int32(proto.ShamanRune_RuneLegsShamanisticRage)} - manaMetrics := shaman.NewManaMetrics(actionID) - srAura := shaman.RegisterAura(core.Aura{ - Label: "Shamanistic Rage", - ActionID: actionID, - Duration: time.Second * 15, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.PseudoStats.DamageTakenMultiplier *= 0.8 - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.PseudoStats.DamageTakenMultiplier /= 0.8 - }, - }) - - spell := shaman.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - Flags: core.SpellFlagNoOnCastComplete, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - IgnoreHaste: true, - CD: core.Cooldown{ - Timer: shaman.NewTimer(), - Duration: time.Minute * 1, - }, - }, - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { - srAura.Activate(sim) - core.StartPeriodicAction(sim, core.PeriodicActionOptions{ - NumTicks: 15, - Period: time.Second * 1, - OnAction: func(sim *core.Simulation) { - mana := max( - shaman.GetStat(stats.AttackPower)*0.15, - shaman.GetStat(stats.SpellPower)*0.10, - shaman.GetStat(stats.Healing)*0.06, - ) - shaman.AddMana(sim, mana, manaMetrics) - }, - }) - }, - }) - - shaman.AddMajorCooldown(core.MajorCooldown{ - Spell: spell, - Type: core.CooldownTypeMana, - ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { - return character.CurrentManaPercent() <= 0.2 - }, - }) -} diff --git a/sim/shaman/shaman.go b/sim/shaman/shaman.go index 334b299310..4ba5c4eb88 100644 --- a/sim/shaman/shaman.go +++ b/sim/shaman/shaman.go @@ -90,6 +90,9 @@ type ShamanSpellCode int const ( SpellCode_ShamanLightningBolt ShamanSpellCode = iota SpellCode_ShamanChainLightning + + SpellCode_ChainHeal + SpellCode_SearingTotem SpellCode_MagmaTotem SpellCode_FireNovaTotem @@ -161,6 +164,7 @@ type Shaman struct { LavaLash *core.Spell EarthShield *core.Spell + PowerSurgeAura *core.Aura MaelstromWeaponAura *core.Aura // Used by Ancestral Guidance rune diff --git a/sim/shaman/shamanistic_rage.go b/sim/shaman/shamanistic_rage.go new file mode 100644 index 0000000000..c2f456cf1b --- /dev/null +++ b/sim/shaman/shamanistic_rage.go @@ -0,0 +1,75 @@ +package shaman + +import ( + "time" + + "github.com/wowsims/sod/sim/core" + "github.com/wowsims/sod/sim/core/proto" + "github.com/wowsims/sod/sim/core/stats" +) + +func (shaman *Shaman) applyShamanisticRage() { + if !shaman.HasRune(proto.ShamanRune_RuneLegsShamanisticRage) { + return + } + + apCoeff := .15 + spCoeff := .10 + hpCoeff := .06 + damageTakenMultiplier := .8 + duration := time.Second * 15 + cooldown := time.Minute * 1 + + actionID := core.ActionID{SpellID: int32(proto.ShamanRune_RuneLegsShamanisticRage)} + manaMetrics := shaman.NewManaMetrics(actionID) + srAura := shaman.RegisterAura(core.Aura{ + Label: "Shamanistic Rage", + ActionID: actionID, + Duration: duration, + OnGain: func(aura *core.Aura, sim *core.Simulation) { + aura.Unit.PseudoStats.DamageTakenMultiplier *= damageTakenMultiplier + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + aura.Unit.PseudoStats.DamageTakenMultiplier /= damageTakenMultiplier + }, + }) + + spell := shaman.RegisterSpell(core.SpellConfig{ + ActionID: actionID, + Flags: core.SpellFlagNoOnCastComplete, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + IgnoreHaste: true, + CD: core.Cooldown{ + Timer: shaman.NewTimer(), + Duration: cooldown, + }, + }, + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { + // TODO: Raid mana regain + srAura.Activate(sim) + core.StartPeriodicAction(sim, core.PeriodicActionOptions{ + NumTicks: 15, + Period: time.Second * 1, + OnAction: func(sim *core.Simulation) { + mana := max( + shaman.GetStat(stats.AttackPower)*apCoeff, + shaman.GetStat(stats.SpellPower)*spCoeff, + shaman.GetStat(stats.Healing)*hpCoeff, + ) + shaman.AddMana(sim, mana, manaMetrics) + }, + }) + }, + }) + + shaman.AddMajorCooldown(core.MajorCooldown{ + Spell: spell, + Type: core.CooldownTypeMana, + ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { + return character.CurrentManaPercent() <= 0.2 + }, + }) +} diff --git a/ui/enhancement_shaman/apls/phase_1.apl.json b/ui/enhancement_shaman/apls/phase_1.apl.json index 603947443f..ccfda3603a 100644 --- a/ui/enhancement_shaman/apls/phase_1.apl.json +++ b/ui/enhancement_shaman/apls/phase_1.apl.json @@ -6,6 +6,7 @@ {"action":{"castSpell":{"spellId":{"spellId":6363,"rank":2}}},"doAtValue":{"const":{"val":"-1.5s"}}} ], "priorityList": [ + {"action":{"condition":{"cmp":{"op":"OpLe","lhs":{"currentManaPercent":{}},"rhs":{"const":{"val":"75"}}}},"castSpell":{"spellId":{"spellId":425336}}}}, {"action":{"condition":{"not":{"val":{"dotIsActive":{"spellId":{"spellId":8052,"rank":2}}}}},"castSpell":{"spellId":{"spellId":8052,"rank":2}}}}, {"action":{"castSpell":{"spellId":{"spellId":408507}}}}, {"action":{"castSpell":{"spellId":{"spellId":8056,"rank":1}}}}, From 603611fc59fe9c7381fa91d8b890b36390b1bb4a Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Mon, 5 Feb 2024 18:29:37 -0500 Subject: [PATCH 3/6] fix dot issues, add more shaman talent+rune support --- sim/druid/moonfire.go | 3 +- sim/shaman/fire_totems.go | 12 +- sim/shaman/flame_shock.go | 3 +- sim/shaman/healing_wave.go | 1 + sim/shaman/lesser_healing_wave.go | 1 + sim/shaman/runes.go | 15 +- sim/shaman/shaman.go | 9 +- sim/shaman/stormstrike.go | 257 ++++++++++------------ sim/shaman/talents.go | 353 +++++++++++------------------- ui/balance_druid/presets.ts | 2 +- 10 files changed, 273 insertions(+), 383 deletions(-) diff --git a/sim/druid/moonfire.go b/sim/druid/moonfire.go index dfc4e1f3b9..ccc5b2008b 100644 --- a/sim/druid/moonfire.go +++ b/sim/druid/moonfire.go @@ -1,6 +1,7 @@ package druid import ( + "fmt" "time" "github.com/wowsims/sod/sim/core" @@ -61,7 +62,7 @@ func (druid *Druid) getMoonfireBaseConfig(rank int) core.SpellConfig { }, Dot: core.DotConfig{ Aura: core.Aura{ - Label: "Moonfire", + Label: fmt.Sprintf("Moonfire (Rank %d)", rank), ActionID: core.ActionID{SpellID: spellId}, }, NumberOfTicks: ticks, diff --git a/sim/shaman/fire_totems.go b/sim/shaman/fire_totems.go index 5529a7f005..fe02dec2a2 100644 --- a/sim/shaman/fire_totems.go +++ b/sim/shaman/fire_totems.go @@ -1,9 +1,11 @@ package shaman import ( + "fmt" "time" "github.com/wowsims/sod/sim/core" + "github.com/wowsims/sod/sim/core/proto" ) const SearingTotemRanks = 6 @@ -65,7 +67,7 @@ func (shaman *Shaman) newSearingTotemSpellConfig(rank int) core.SpellConfig { Dot: core.DotConfig{ Aura: core.Aura{ - Label: "SearingTotem", + Label: fmt.Sprintf("Searing Totem (Rank %d)", rank), }, // 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 @@ -155,7 +157,7 @@ func (shaman *Shaman) newMagmaTotemSpellConfig(rank int) core.SpellConfig { Dot: core.DotConfig{ IsAOE: true, Aura: core.Aura{ - Label: "MagmaTotem", + Label: fmt.Sprintf("Magma Totem (Rank %d)", rank), }, NumberOfTicks: int32(duration / attackInterval), TickLength: attackInterval, @@ -195,6 +197,10 @@ var FireNovaTotemManaCost = [FireNovaTotemRanks + 1]float64{0, 95, 170, 280, 395 var FireNovaTotemLevel = [FireNovaTotemRanks + 1]int{0, 12, 22, 32, 42, 52} func (shaman *Shaman) registerFireNovaTotemSpell() { + if shaman.HasRune(proto.ShamanRune_RuneWaistFireNova) { + return + } + shaman.FireNovaTotem = make([]*core.Spell, FireNovaTotemRanks+1) for rank := 1; rank <= FireNovaTotemRanks; rank++ { @@ -250,7 +256,7 @@ func (shaman *Shaman) newFireNovaTotemSpellConfig(rank int) core.SpellConfig { Dot: core.DotConfig{ IsAOE: true, Aura: core.Aura{ - Label: "FireNovaTotem", + Label: fmt.Sprintf("Fire Nova Totem (Rank %d)", rank), }, NumberOfTicks: 1, TickLength: attackInterval, diff --git a/sim/shaman/flame_shock.go b/sim/shaman/flame_shock.go index 80ef3a16cc..9b6c112ca8 100644 --- a/sim/shaman/flame_shock.go +++ b/sim/shaman/flame_shock.go @@ -1,6 +1,7 @@ package shaman import ( + "fmt" "time" "github.com/wowsims/sod/sim/core" @@ -73,7 +74,7 @@ func (shaman *Shaman) newFlameShockSpellConfig(rank int, shockTimer *core.Timer) spell.Dot = core.DotConfig{ Aura: core.Aura{ - Label: "Flame Shock", + Label: fmt.Sprintf("Flame Shock (Rank %d)", rank), OnGain: func(aura *core.Aura, sim *core.Simulation) { if shaman.HasRune(proto.ShamanRune_RuneHandsLavaBurst) { shaman.LavaBurst.BonusCritRating += 100 * core.CritRatingPerCritChance diff --git a/sim/shaman/healing_wave.go b/sim/shaman/healing_wave.go index d923f3ee2d..f91c96ba1b 100644 --- a/sim/shaman/healing_wave.go +++ b/sim/shaman/healing_wave.go @@ -56,6 +56,7 @@ func (shaman *Shaman) newHealingWaveSpellConfig(rank int, isOverload bool) core. spell := core.SpellConfig{ ActionID: core.ActionID{SpellID: spellId}, + SpellCode: int32(SpellCode_HealingWave), SpellSchool: core.SpellSchoolNature, ProcMask: core.ProcMaskSpellHealing, Flags: flags, diff --git a/sim/shaman/lesser_healing_wave.go b/sim/shaman/lesser_healing_wave.go index 708f2b0806..584b873132 100644 --- a/sim/shaman/lesser_healing_wave.go +++ b/sim/shaman/lesser_healing_wave.go @@ -39,6 +39,7 @@ func (shaman *Shaman) newLesserHealingWaveSpellConfig(rank int) core.SpellConfig spell := core.SpellConfig{ ActionID: core.ActionID{SpellID: spellId}, + SpellCode: int32(SpellCode_LesserHealingWave), SpellSchool: core.SpellSchoolNature, ProcMask: core.ProcMaskSpellHealing, Flags: core.SpellFlagHelpful | core.SpellFlagAPL, diff --git a/sim/shaman/runes.go b/sim/shaman/runes.go index 4237c0a93c..2855548f00 100644 --- a/sim/shaman/runes.go +++ b/sim/shaman/runes.go @@ -136,17 +136,20 @@ func (shaman *Shaman) applyPowerSurge() { }, ) - affectedSpells := core.FilterSlice( - core.Flatten([][]*core.Spell{ - shaman.ChainLightning, - shaman.ChainHeal, - {shaman.LavaBurst}, - }), func(spell *core.Spell) bool { return spell != nil }) + var affectedSpells []*core.Spell shaman.PowerSurgeAura = shaman.RegisterAura(core.Aura{ Label: "Power Surge Proc", ActionID: core.ActionID{SpellID: 440285}, Duration: time.Second * 10, + OnInit: func(aura *core.Aura, sim *core.Simulation) { + affectedSpells = core.FilterSlice( + core.Flatten([][]*core.Spell{ + shaman.ChainLightning, + shaman.ChainHeal, + {shaman.LavaBurst}, + }), func(spell *core.Spell) bool { return spell != nil }) + }, OnGain: func(aura *core.Aura, sim *core.Simulation) { shaman.LavaBurst.CD.Reset() for _, spell := range affectedSpells { diff --git a/sim/shaman/shaman.go b/sim/shaman/shaman.go index 4ba5c4eb88..f45467c245 100644 --- a/sim/shaman/shaman.go +++ b/sim/shaman/shaman.go @@ -91,6 +91,8 @@ const ( SpellCode_ShamanLightningBolt ShamanSpellCode = iota SpellCode_ShamanChainLightning + SpellCode_HealingWave + SpellCode_LesserHealingWave SpellCode_ChainHeal SpellCode_SearingTotem @@ -116,7 +118,6 @@ type Shaman struct { ChainLightning []*core.Spell ChainLightningOverload []*core.Spell - FireNova *core.Spell Stormstrike *core.Spell LightningShield *core.Spell @@ -164,8 +165,10 @@ type Shaman struct { LavaLash *core.Spell EarthShield *core.Spell - PowerSurgeAura *core.Aura + FireNova []*core.Spell + MaelstromWeaponAura *core.Aura + PowerSurgeAura *core.Aura // Used by Ancestral Guidance rune lastFlameShockTarget *core.Unit @@ -237,7 +240,7 @@ func (shaman *Shaman) Initialize() { shaman.registerLightningBoltSpell() // shaman.registerLightningShieldSpell() shaman.registerShocks() - // shaman.registerStormstrikeSpell() + shaman.registerStormstrikeSpell() // Imbues // In the Initialize due to frost brand adding the aura to the enemy diff --git a/sim/shaman/stormstrike.go b/sim/shaman/stormstrike.go index b76885b3c2..8cb37cfc99 100644 --- a/sim/shaman/stormstrike.go +++ b/sim/shaman/stormstrike.go @@ -1,144 +1,117 @@ package shaman -// import ( -// "time" - -// "github.com/wowsims/sod/sim/core" -// "github.com/wowsims/sod/sim/core/stats" -// ) - -// var StormstrikeActionID = core.ActionID{SpellID: 17364} -// var TotemOfTheDancingFlame int32 = 45169 -// var TotemOfDueling int32 = 40322 - -// func (shaman *Shaman) StormstrikeDebuffAura(target *core.Unit) *core.Aura { -// return target.GetOrRegisterAura(core.Aura{ -// Label: "Stormstrike-" + shaman.Label, -// ActionID: StormstrikeActionID, -// Duration: time.Second * 12, -// MaxStacks: 4, -// OnGain: func(aura *core.Aura, sim *core.Simulation) { -// shaman.AttackTables[aura.Unit.UnitIndex].NatureDamageTakenMultiplier *= 1.2 -// }, -// OnExpire: func(aura *core.Aura, sim *core.Simulation) { -// shaman.AttackTables[aura.Unit.UnitIndex].NatureDamageTakenMultiplier /= 1.2 -// }, -// OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { -// if spell.Unit != &shaman.Unit { -// return -// } -// if spell.SpellSchool != core.SpellSchoolNature { -// return -// } -// if !result.Landed() || result.Damage == 0 { -// return -// } -// aura.RemoveStack(sim) -// }, -// }) -// } - -// func (shaman *Shaman) newStormstrikeHitSpell(isMH bool) func(*core.Simulation, *core.Unit, *core.Spell) { -// var flatDamageBonus float64 = 0 -// if shaman.Ranged().ID == TotemOfTheDancingFlame { -// flatDamageBonus += 155 -// } - -// var procMask core.ProcMask -// if isMH { -// procMask = core.ProcMaskMeleeMHSpecial -// } else { -// procMask = core.ProcMaskMeleeOHSpecial -// } - -// return func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { -// var baseDamage float64 -// spell.ProcMask = procMask -// if isMH { -// baseDamage = flatDamageBonus + -// spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) + -// spell.BonusWeaponDamage() -// } else { -// baseDamage = flatDamageBonus + -// spell.Unit.OHWeaponDamage(sim, spell.MeleeAttackPower()) + -// spell.BonusWeaponDamage() -// } - -// spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialCritOnly) -// } -// } - -// func (shaman *Shaman) registerStormstrikeSpell() { -// mhHit := shaman.newStormstrikeHitSpell(true) -// ohHit := shaman.newStormstrikeHitSpell(false) - -// ssDebuffAuras := shaman.NewEnemyAuraArray(shaman.StormstrikeDebuffAura) - -// var skyshatterAura *core.Aura -// if shaman.HasSetBonus(ItemSetSkyshatterHarness, 4) { -// skyshatterAura = shaman.NewTemporaryStatsAura("Skyshatter 4pc AP Bonus", core.ActionID{SpellID: 38432}, stats.Stats{stats.AttackPower: 70}, time.Second*12) -// } -// var totemOfDuelingAura *core.Aura -// if shaman.Ranged().ID == TotemOfDueling { -// totemOfDuelingAura = shaman.NewTemporaryStatsAura("Essense of the Storm", core.ActionID{SpellID: 60766}, -// stats.Stats{stats.MeleeHaste: 60, stats.SpellHaste: 60}, time.Second*6) -// } - -// manaMetrics := shaman.NewManaMetrics(core.ActionID{SpellID: 51522}) - -// cooldownTime := time.Duration(core.TernaryFloat64(shaman.HasSetBonus(ItemSetGladiatorsEarthshaker, 4), 6, 8)) -// impSSChance := 0.5 * float64(shaman.Talents.ImprovedStormstrike) - -// shaman.Stormstrike = shaman.RegisterSpell(core.SpellConfig{ -// ActionID: StormstrikeActionID, -// SpellSchool: core.SpellSchoolPhysical, -// ProcMask: core.ProcMaskMeleeMHSpecial, -// Flags: core.SpellFlagMeleeMetrics | core.SpellFlagAPL | core.SpellFlagIncludeTargetBonusDamage, - -// ManaCost: core.ManaCostOptions{ -// BaseCost: 0.08, -// }, -// Cast: core.CastConfig{ -// DefaultCast: core.Cast{ -// GCD: core.GCDDefault, -// }, -// IgnoreHaste: true, -// CD: core.Cooldown{ -// Timer: shaman.NewTimer(), -// Duration: time.Second * cooldownTime, -// }, -// }, - -// ThreatMultiplier: 1, -// DamageMultiplier: core.TernaryFloat64(shaman.HasSetBonus(ItemSetWorldbreakerBattlegear, 2), 1.2, 1), -// CritMultiplier: shaman.DefaultMeleeCritMultiplier(), - -// ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { -// result := spell.CalcOutcome(sim, target, spell.OutcomeMeleeSpecialHit) -// if result.Landed() { -// if impSSChance > 0 && sim.RandomFloat("Improved Stormstrike") < impSSChance { -// shaman.AddMana(sim, 0.2*shaman.BaseMana, manaMetrics) -// } -// ssDebuffAura := ssDebuffAuras.Get(target) -// ssDebuffAura.Activate(sim) -// ssDebuffAura.SetStacks(sim, 4) - -// if skyshatterAura != nil { -// skyshatterAura.Activate(sim) -// } -// if totemOfDuelingAura != nil { -// totemOfDuelingAura.Activate(sim) -// } - -// mhHit(sim, target, spell) - -// if shaman.AutoAttacks.IsDualWielding { -// ohHit(sim, target, spell) -// } - -// shaman.Stormstrike.SpellMetrics[target.UnitIndex].Hits-- -// } -// spell.DealOutcome(sim, result) -// }, -// }) -// } +import ( + "fmt" + "time" + + "github.com/wowsims/sod/sim/core" +) + +var StormstrikeActionID = core.ActionID{SpellID: 17364} + +func (shaman *Shaman) StormstrikeDebuffAura(target *core.Unit, level int32) *core.Aura { + duration := time.Second * 12 + + return target.GetOrRegisterAura(core.Aura{ + Label: fmt.Sprintf("Stormstrike-%s", shaman.Label), + ActionID: StormstrikeActionID, + Duration: duration, + MaxStacks: 2, + OnGain: func(aura *core.Aura, sim *core.Simulation) { + shaman.AttackTables[aura.Unit.UnitIndex].NatureDamageTakenMultiplier *= 1.2 + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + shaman.AttackTables[aura.Unit.UnitIndex].NatureDamageTakenMultiplier /= 1.2 + }, + OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if spell.Unit != &shaman.Unit { + return + } + if spell.SpellSchool != core.SpellSchoolNature { + return + } + if !result.Landed() || result.Damage == 0 { + return + } + aura.RemoveStack(sim) + }, + }) +} + +func (shaman *Shaman) newStormstrikeHitSpell(isMH bool) func(*core.Simulation, *core.Unit, *core.Spell) { + var procMask core.ProcMask + if isMH { + procMask = core.ProcMaskMeleeMHSpecial + } else { + procMask = core.ProcMaskMeleeOHSpecial + } + + return func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + var baseDamage float64 + spell.ProcMask = procMask + if isMH { + baseDamage = spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) + spell.BonusWeaponDamage() + } else { + baseDamage = spell.Unit.OHWeaponDamage(sim, spell.MeleeAttackPower()) + spell.BonusWeaponDamage() + } + + spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialCritOnly) + } +} + +func (shaman *Shaman) registerStormstrikeSpell() { + if shaman.Level < 40 { + return + } + + manaCost := .21 + cooldown := time.Second * 20 + + mhHit := shaman.newStormstrikeHitSpell(true) + ohHit := shaman.newStormstrikeHitSpell(false) + + ssDebuffAuras := shaman.NewEnemyAuraArray(shaman.StormstrikeDebuffAura) + + shaman.Stormstrike = shaman.RegisterSpell(core.SpellConfig{ + ActionID: StormstrikeActionID, + SpellSchool: core.SpellSchoolPhysical, + ProcMask: core.ProcMaskMeleeMHSpecial, + Flags: core.SpellFlagMeleeMetrics | core.SpellFlagAPL | core.SpellFlagIncludeTargetBonusDamage, + + ManaCost: core.ManaCostOptions{ + BaseCost: manaCost, + }, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + IgnoreHaste: true, + CD: core.Cooldown{ + Timer: shaman.NewTimer(), + Duration: cooldown, + }, + }, + + ThreatMultiplier: 1, + DamageMultiplier: 1, + CritMultiplier: shaman.DefaultMeleeCritMultiplier(), + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + result := spell.CalcOutcome(sim, target, spell.OutcomeMeleeSpecialHit) + if result.Landed() { + ssDebuffAura := ssDebuffAuras.Get(target) + ssDebuffAura.Activate(sim) + ssDebuffAura.SetStacks(sim, 4) + + mhHit(sim, target, spell) + + if shaman.AutoAttacks.IsDualWielding { + ohHit(sim, target, spell) + } + + shaman.Stormstrike.SpellMetrics[target.UnitIndex].Hits-- + } + spell.DealOutcome(sim, result) + }, + }) +} diff --git a/sim/shaman/talents.go b/sim/shaman/talents.go index 117bd2f5c2..35739a0cfb 100644 --- a/sim/shaman/talents.go +++ b/sim/shaman/talents.go @@ -16,57 +16,23 @@ import ( // ) func (shaman *Shaman) ApplyTalents() { - // shaman.AddStat(stats.MeleeCrit, core.CritRatingPerCritChance*1*float64(shaman.Talents.ThunderingStrikes)) - // shaman.AddStat(stats.SpellCrit, core.CritRatingPerCritChance*1*float64(shaman.Talents.ThunderingStrikes)) - // shaman.AddStat(stats.Dodge, core.DodgeRatingPerDodgeChance*1*float64(shaman.Talents.Anticipation)) - // shaman.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] *= []float64{1, 1.04, 1.07, 1.1}[shaman.Talents.WeaponMastery] - - // shaman.AddStat(stats.Expertise, 3*core.ExpertisePerQuarterPercentReduction*float64(shaman.Talents.UnleashedRage)) - - // if shaman.Talents.DualWieldSpecialization > 0 && shaman.HasOHWeapon() { - // shaman.AddStat(stats.MeleeHit, core.MeleeHitRatingPerHitChance*2*float64(shaman.Talents.DualWieldSpecialization)) - // } - - // shaman.AddStat(stats.SpellCrit, float64(shaman.Talents.BlessingOfTheEternals)*2*core.CritRatingPerCritChance) - // if shaman.Talents.Toughness > 0 { - // shaman.MultiplyStat(stats.Stamina, 1.0+0.02*float64(shaman.Talents.Toughness)) - // } - // if shaman.Talents.UnrelentingStorm > 0 { - // shaman.AddStatDependency(stats.Intellect, stats.MP5, 0.04*float64(shaman.Talents.UnrelentingStorm)) - // } - // if shaman.Talents.AncestralKnowledge > 0 { - // shaman.MultiplyStat(stats.Intellect, 1.0+0.02*float64(shaman.Talents.AncestralKnowledge)) - // } - // if shaman.Talents.MentalQuickness > 0 { - // shaman.AddStatDependency(stats.AttackPower, stats.SpellPower, 0.1*float64(shaman.Talents.MentalQuickness)) - // } - // if shaman.Talents.MentalDexterity > 0 { - // shaman.AddStatDependency(stats.Intellect, stats.AttackPower, 0.3333*float64(shaman.Talents.MentalDexterity)) - // } - // if shaman.Talents.NaturesBlessing > 0 { - // shaman.AddStatDependency(stats.Intellect, stats.SpellPower, 0.1*float64(shaman.Talents.NaturesBlessing)) - // } - - // if shaman.Talents.SpiritWeapons { - // shaman.PseudoStats.CanParry = true - // shaman.AutoAttacks.MHConfig().ThreatMultiplier *= 0.7 // TODO this looks fishy - // shaman.AutoAttacks.OHConfig().ThreatMultiplier *= 0.7 - // } + shaman.AddStat(stats.MeleeCrit, core.CritRatingPerCritChance*1*float64(shaman.Talents.ThunderingStrikes)) + + shaman.AddStat(stats.Dodge, core.DodgeRatingPerDodgeChance*1*float64(shaman.Talents.Anticipation)) + shaman.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] *= 1 + (.2 * float64(shaman.Talents.WeaponMastery)) + + if shaman.Talents.AncestralKnowledge > 0 { + shaman.MultiplyStat(stats.Intellect, 1.0+0.02*float64(shaman.Talents.AncestralKnowledge)) + } shaman.applyElementalFocus() shaman.applyElementalDevastation() - // shaman.applyFlurry() - // shaman.applyMaelstromWeapon() + shaman.applyFlurry() shaman.registerElementalMasteryCD() - // shaman.registerNaturesSwiftnessCD() - // shaman.registerShamanisticRageCD() + shaman.registerNaturesSwiftnessCD() // shaman.registerManaTideTotemCD() } -// func (shaman *Shaman) spellThreatMultiplier() float64 { -// return []float64{1, 0.9, 0.8, 0.7}[shaman.Talents.ElementalPrecision] -// } - func (shaman *Shaman) applyElementalFocus() { if !shaman.Talents.ElementalFocus { return @@ -92,10 +58,9 @@ func (shaman *Shaman) applyElementalFocus() { shaman.EarthShock, shaman.FlameShock, shaman.FrostShock, + shaman.FireNova, {shaman.LavaBurst}, {shaman.MoltenBlast}, - // TODO: - // Shaman.FireNova, }), func(spell *core.Spell) bool { return spell != nil }, ) }, @@ -161,7 +126,7 @@ func (shaman *Shaman) applyElementalDevastation() { }) } -var eleMasterActionID = core.ActionID{SpellID: 16166} +var ElementalMasteryActionId = core.ActionID{SpellID: 16166} func (shaman *Shaman) registerElementalMasteryCD() { if !shaman.Talents.ElementalMastery { @@ -177,7 +142,7 @@ func (shaman *Shaman) registerElementalMasteryCD() { emAura := shaman.RegisterAura(core.Aura{ Label: "Elemental Mastery", - ActionID: eleMasterActionID, + ActionID: ElementalMasteryActionId, Duration: core.NeverExpires, OnInit: func(aura *core.Aura, sim *core.Simulation) { affectedSpells = core.FilterSlice( @@ -206,7 +171,7 @@ func (shaman *Shaman) registerElementalMasteryCD() { }) eleMastSpell := shaman.RegisterSpell(core.SpellConfig{ - ActionID: eleMasterActionID, + ActionID: ElementalMasteryActionId, Flags: core.SpellFlagNoOnCastComplete, Cast: core.CastConfig{ CD: core.Cooldown{ @@ -225,197 +190,133 @@ func (shaman *Shaman) registerElementalMasteryCD() { }) } -// func (shaman *Shaman) registerNaturesSwiftnessCD() { -// if !shaman.Talents.NaturesSwiftness { -// return -// } -// actionID := core.ActionID{SpellID: 16188} -// cdTimer := shaman.NewTimer() -// cd := time.Minute * 3 - -// nsAura := shaman.RegisterAura(core.Aura{ -// Label: "Natures Swiftness", -// ActionID: actionID, -// Duration: core.NeverExpires, -// OnGain: func(aura *core.Aura, sim *core.Simulation) { -// shaman.ChainLightning.CastTimeMultiplier -= 1 -// shaman.LavaBurst.CastTimeMultiplier -= 1 -// shaman.LightningBolt.CastTimeMultiplier -= 1 -// }, -// OnExpire: func(aura *core.Aura, sim *core.Simulation) { -// shaman.ChainLightning.CastTimeMultiplier += 1 -// shaman.LavaBurst.CastTimeMultiplier += 1 -// shaman.LightningBolt.CastTimeMultiplier += 1 -// }, -// OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { -// if spell != shaman.LightningBolt && spell != shaman.ChainLightning && spell != shaman.LavaBurst { -// return -// } - -// // Remove the buff and put skill on CD -// aura.Deactivate(sim) -// cdTimer.Set(sim.CurrentTime + cd) -// shaman.UpdateMajorCooldowns() -// }, -// }) - -// nsSpell := shaman.RegisterSpell(core.SpellConfig{ -// ActionID: actionID, -// Flags: core.SpellFlagNoOnCastComplete, -// Cast: core.CastConfig{ -// CD: core.Cooldown{ -// Timer: cdTimer, -// Duration: cd, -// }, -// }, -// ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { -// // Don't use NS unless we're casting a full-length lightning bolt, which is -// // the only spell shamans have with a cast longer than GCD. -// return !shaman.HasTemporarySpellCastSpeedIncrease() -// }, -// ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { -// nsAura.Activate(sim) -// }, -// }) - -// shaman.AddMajorCooldown(core.MajorCooldown{ -// Spell: nsSpell, -// Type: core.CooldownTypeDPS, -// }) -// } +func (shaman *Shaman) registerNaturesSwiftnessCD() { + if !shaman.Talents.NaturesSwiftness { + return + } + actionID := core.ActionID{SpellID: 16188} + cdTimer := shaman.NewTimer() + cd := time.Minute * 3 -// func (shaman *Shaman) applyFlurry() { -// if shaman.Talents.Flurry == 0 { -// return -// } + var affectedSpells []*core.Spell -// bonus := 1.0 + 0.06*float64(shaman.Talents.Flurry) + nsAura := shaman.RegisterAura(core.Aura{ + Label: "Natures Swiftness", + ActionID: actionID, + Duration: core.NeverExpires, + OnInit: func(aura *core.Aura, sim *core.Simulation) { + affectedSpells = core.FilterSlice( + core.Flatten([][]*core.Spell{ + shaman.LightningBolt, + shaman.ChainLightning, + shaman.HealingWave, + shaman.LesserHealingWave, + shaman.ChainHeal, + }), func(spell *core.Spell) bool { return spell != nil }, + ) + }, + OnGain: func(aura *core.Aura, sim *core.Simulation) { + core.Each(affectedSpells, func(spell *core.Spell) { spell.CastTimeMultiplier -= 1 }) + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + core.Each(affectedSpells, func(spell *core.Spell) { spell.CastTimeMultiplier += 1 }) + }, + OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { + spellTriggersNS := spell.SpellCode != int32(SpellCode_ShamanLightningBolt) && + spell.SpellCode != int32(SpellCode_ShamanChainLightning) && + spell.SpellCode != int32(SpellCode_HealingWave) && + spell.SpellCode != int32(SpellCode_LesserHealingWave) && + spell.SpellCode != int32(SpellCode_ChainHeal) -// if shaman.HasSetBonus(ItemSetEarthshatterBattlegear, 4) { -// bonus += 0.05 -// } + if spellTriggersNS { + return + } -// inverseBonus := 1 / bonus + // Remove the buff and put skill on CD + aura.Deactivate(sim) + cdTimer.Set(sim.CurrentTime + cd) + shaman.UpdateMajorCooldowns() + }, + }) -// procAura := shaman.RegisterAura(core.Aura{ -// Label: "Flurry Proc", -// ActionID: core.ActionID{SpellID: 16280}, -// Duration: core.NeverExpires, -// MaxStacks: 3, -// OnGain: func(aura *core.Aura, sim *core.Simulation) { -// shaman.MultiplyMeleeSpeed(sim, bonus) -// }, -// OnExpire: func(aura *core.Aura, sim *core.Simulation) { -// shaman.MultiplyMeleeSpeed(sim, inverseBonus) -// }, -// }) + nsSpell := shaman.RegisterSpell(core.SpellConfig{ + ActionID: actionID, + Flags: core.SpellFlagNoOnCastComplete, + Cast: core.CastConfig{ + CD: core.Cooldown{ + Timer: cdTimer, + Duration: cd, + }, + }, + ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { + // Don't use NS unless we're casting a full-length lightning bolt, which is + // the only spell shamans have with a cast longer than GCD. + return !shaman.HasTemporarySpellCastSpeedIncrease() + }, + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { + nsAura.Activate(sim) + }, + }) -// icd := core.Cooldown{ -// Timer: shaman.NewTimer(), -// Duration: time.Millisecond * 500, -// } + shaman.AddMajorCooldown(core.MajorCooldown{ + Spell: nsSpell, + Type: core.CooldownTypeDPS, + }) +} -// shaman.RegisterAura(core.Aura{ -// Label: "Flurry", -// Duration: core.NeverExpires, -// OnReset: func(aura *core.Aura, sim *core.Simulation) { -// aura.Activate(sim) -// }, -// OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { -// if !spell.ProcMask.Matches(core.ProcMaskMelee) { -// return -// } +func (shaman *Shaman) applyFlurry() { + if shaman.Talents.Flurry == 0 { + return + } -// if result.Outcome.Matches(core.OutcomeCrit) { -// procAura.Activate(sim) -// procAura.SetStacks(sim, 3) -// icd.Reset() // the "charge protection" ICD isn't up yet -// return -// } + bonus := 1.0 + 0.06*float64(shaman.Talents.Flurry) -// // Remove a stack. -// if procAura.IsActive() && spell.ProcMask.Matches(core.ProcMaskMeleeWhiteHit) && icd.IsReady(sim) { -// icd.Use(sim) -// procAura.RemoveStack(sim) -// } -// }, -// }) -// } + inverseBonus := 1 / bonus -// func (shaman *Shaman) applyMaelstromWeapon() { -// if shaman.Talents.MaelstromWeapon == 0 { -// return -// } + procAura := shaman.RegisterAura(core.Aura{ + Label: "Flurry Proc", + ActionID: core.ActionID{SpellID: 16280}, + Duration: core.NeverExpires, + MaxStacks: 3, + OnGain: func(aura *core.Aura, sim *core.Simulation) { + shaman.MultiplyMeleeSpeed(sim, bonus) + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + shaman.MultiplyMeleeSpeed(sim, inverseBonus) + }, + }) -// var t10BonusAura *core.Aura -// enhT10Bonus := false -// if shaman.HasSetBonus(ItemSetFrostWitchBattlegear, 4) { -// enhT10Bonus = true - -// statDep := shaman.NewDynamicMultiplyStat(stats.AttackPower, 1.2) -// t10BonusAura = shaman.RegisterAura(core.Aura{ -// Label: "Maelstrom Power", -// ActionID: core.ActionID{SpellID: 70831}, -// Duration: time.Second * 10, -// OnGain: func(aura *core.Aura, sim *core.Simulation) { -// shaman.EnableDynamicStatDep(sim, statDep) -// }, -// OnExpire: func(aura *core.Aura, sim *core.Simulation) { -// shaman.DisableDynamicStatDep(sim, statDep) -// }, -// }) -// } + icd := core.Cooldown{ + Timer: shaman.NewTimer(), + Duration: time.Millisecond * 500, + } -// // TODO: Don't forget to make it so that AA don't reset when casting when MW is active -// // for LB / CL / LvB -// // They can't actually hit while casting, but the AA timer doesnt reset if you cast during the AA timer. - -// // For sim purposes maelstrom weapon only impacts CL / LB -// shaman.MaelstromWeaponAura = shaman.RegisterAura(core.Aura{ -// Label: "MaelstromWeapon Proc", -// ActionID: core.ActionID{SpellID: 53817}, -// Duration: time.Second * 30, -// MaxStacks: 5, -// OnStacksChange: func(aura *core.Aura, sim *core.Simulation, oldStacks int32, newStacks int32) { -// multDiff := 0.2 * float64(newStacks-oldStacks) -// shaman.LightningBolt.CastTimeMultiplier -= multDiff -// shaman.ChainLightning.CastTimeMultiplier -= multDiff - -// if enhT10Bonus && shaman.MaelstromWeaponAura.GetStacks() == 5 { -// if sim.RandomFloat("Maelstrom Power") < 0.15 { -// t10BonusAura.Activate(sim) -// } -// } -// }, -// OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { -// if !spell.Flags.Matches(SpellFlagElectric) { -// return -// } -// shaman.MaelstromWeaponAura.Deactivate(sim) -// }, -// }) + shaman.RegisterAura(core.Aura{ + Label: "Flurry", + Duration: core.NeverExpires, + OnReset: func(aura *core.Aura, sim *core.Simulation) { + aura.Activate(sim) + }, + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if !spell.ProcMask.Matches(core.ProcMaskMelee) { + return + } -// ppmm := shaman.AutoAttacks.NewPPMManager(core.TernaryFloat64(shaman.HasSetBonus(ItemSetWorldbreakerBattlegear, 4), 2.4, 2.0)* -// float64(shaman.Talents.MaelstromWeapon), core.ProcMaskMelee) -// // This aura is hidden, just applies stacks of the proc aura. -// shaman.RegisterAura(core.Aura{ -// Label: "MaelstromWeapon", -// 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 result.Outcome.Matches(core.OutcomeCrit) { + procAura.Activate(sim) + procAura.SetStacks(sim, 3) + icd.Reset() // the "charge protection" ICD isn't up yet + return + } -// if ppmm.Proc(sim, spell.ProcMask, "Maelstrom Weapon") { -// shaman.MaelstromWeaponAura.Activate(sim) -// shaman.MaelstromWeaponAura.AddStack(sim) -// } -// }, -// }) -// } + // Remove a stack. + if procAura.IsActive() && spell.ProcMask.Matches(core.ProcMaskMeleeWhiteHit) && icd.IsReady(sim) { + icd.Use(sim) + procAura.RemoveStack(sim) + } + }, + }) +} // func (shaman *Shaman) registerManaTideTotemCD() { // if !shaman.Talents.ManaTideTotem { diff --git a/ui/balance_druid/presets.ts b/ui/balance_druid/presets.ts index 3d94b5401e..b99ce0087b 100644 --- a/ui/balance_druid/presets.ts +++ b/ui/balance_druid/presets.ts @@ -33,7 +33,7 @@ export const Phase1PresetGear = PresetUtils.makePresetGear('Phase 1', Phase1Gear export const DefaultGear = Phase1PresetGear; -export const APLBalancePhase1 = PresetUtils.makePresetAPLRotation('P1 Preset', Phase1APL); +export const APLBalancePhase1 = PresetUtils.makePresetAPLRotation('Phase 1', Phase1APL); export const DEFAULT_APL = APLBalancePhase1 From 4a60889115293ac4f7eb1700e53d290cf651477c Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Mon, 5 Feb 2024 18:38:37 -0500 Subject: [PATCH 4/6] shaman Phase 1 done ?? --- ui/core/launched_sims.ts | 4 ++-- ui/enhancement_shaman/gear_sets/phase_1.gear.json | 4 ++-- ui/index.html | 12 +++++------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/ui/core/launched_sims.ts b/ui/core/launched_sims.ts index be90464a6d..19a5867b4e 100644 --- a/ui/core/launched_sims.ts +++ b/ui/core/launched_sims.ts @@ -19,8 +19,8 @@ export const simLaunchStatuses: Record = { [Spec.SpecFeralDruid]: LaunchStatus.Alpha, [Spec.SpecFeralTankDruid]: LaunchStatus.Unlaunched, [Spec.SpecRestorationDruid]: LaunchStatus.Unlaunched, - [Spec.SpecElementalShaman]: LaunchStatus.Unlaunched, - [Spec.SpecEnhancementShaman]: LaunchStatus.Unlaunched, + [Spec.SpecElementalShaman]: LaunchStatus.Alpha, + [Spec.SpecEnhancementShaman]: LaunchStatus.Alpha, [Spec.SpecRestorationShaman]: LaunchStatus.Unlaunched, [Spec.SpecHunter]: LaunchStatus.Alpha, [Spec.SpecMage]: LaunchStatus.Unlaunched, diff --git a/ui/enhancement_shaman/gear_sets/phase_1.gear.json b/ui/enhancement_shaman/gear_sets/phase_1.gear.json index 8d6baeca47..f19492f2c6 100644 --- a/ui/enhancement_shaman/gear_sets/phase_1.gear.json +++ b/ui/enhancement_shaman/gear_sets/phase_1.gear.json @@ -14,8 +14,8 @@ {"id":13097}, {"id":211449}, {"id":4381}, - {"id":209436}, - {"id":1454}, + {"id":209436,"enchant":241}, + {"id":1454,"enchant":241}, {"id":209575} ] } diff --git a/ui/index.html b/ui/index.html index 421929c6b5..69576b24af 100644 --- a/ui/index.html +++ b/ui/index.html @@ -187,33 +187,31 @@

Season of Discovery

Shaman - Unlaunched + Alpha