From b181ff24d3ff702aa5c21e346ad3e48c87cc2936 Mon Sep 17 00:00:00 2001 From: Patrick-Hogeveen Date: Tue, 13 May 2025 17:53:14 -0400 Subject: [PATCH 01/15] Added mistweaver to alpha --- ui/core/launched_sims.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/core/launched_sims.ts b/ui/core/launched_sims.ts index fc80605fbb..64b34229e6 100644 --- a/ui/core/launched_sims.ts +++ b/ui/core/launched_sims.ts @@ -91,7 +91,7 @@ export const simLaunchStatuses: Record = { }, [Spec.SpecMistweaverMonk]: { phase: Phase.Phase1, - status: LaunchStatus.Unlaunched, + status: LaunchStatus.Alpha, }, [Spec.SpecWindwalkerMonk]: { phase: Phase.Phase1, From 78e9266f3b1d6c1dc62e0d5d9fa2ea4c08640b94 Mon Sep 17 00:00:00 2001 From: Patrick-Hogeveen Date: Sat, 17 May 2025 04:05:25 -0400 Subject: [PATCH 02/15] Adding monk as healer spec in the front end --- ui/core/player.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/core/player.ts b/ui/core/player.ts index 061abc975d..0b86b2a7f5 100644 --- a/ui/core/player.ts +++ b/ui/core/player.ts @@ -401,7 +401,7 @@ export class Player { } canEnableTargetDummies(): boolean { - const healingSpellClasses: Class[] = [Class.ClassDruid, Class.ClassPaladin, Class.ClassPriest, Class.ClassShaman]; + const healingSpellClasses: Class[] = [Class.ClassDruid, Class.ClassPaladin, Class.ClassPriest, Class.ClassShaman, Class.ClassMonk]; return healingSpellClasses.includes(this.getClass()); } From 6aa67fc305cc3c40541aef37bed6218eb8b3dee7 Mon Sep 17 00:00:00 2001 From: Patrick-Hogeveen Date: Sat, 17 May 2025 04:13:32 -0400 Subject: [PATCH 03/15] Renewing mist (wip) + surging mist --- sim/monk/mistweaver/mistweaver.go | 2 + sim/monk/mistweaver/renewing_mist.go | 101 +++++++++++++++++++++++++++ sim/monk/mistweaver/surging_mist.go | 42 +++++++++++ 3 files changed, 145 insertions(+) create mode 100644 sim/monk/mistweaver/renewing_mist.go create mode 100644 sim/monk/mistweaver/surging_mist.go diff --git a/sim/monk/mistweaver/mistweaver.go b/sim/monk/mistweaver/mistweaver.go index 27ae44f644..f1f049f7af 100644 --- a/sim/monk/mistweaver/mistweaver.go +++ b/sim/monk/mistweaver/mistweaver.go @@ -72,6 +72,8 @@ func (mw *MistweaverMonk) Reset(sim *core.Simulation) { func (mw *MistweaverMonk) RegisterSpecializationEffects() { mw.RegisterMastery() + mw.registerRenewingMist() + mw.registerSurgingMist() } func (mw *MistweaverMonk) RegisterMastery() { diff --git a/sim/monk/mistweaver/renewing_mist.go b/sim/monk/mistweaver/renewing_mist.go new file mode 100644 index 0000000000..2844ee35b6 --- /dev/null +++ b/sim/monk/mistweaver/renewing_mist.go @@ -0,0 +1,101 @@ +package mistweaver + +import ( + "time" + + "fmt" + + "github.com/wowsims/mop/sim/core" +) + +func (mw *MistweaverMonk) registerRenewingMist() { + actionID := core.ActionID{SpellID: 115151} + chiMetrics := mw.NewChiMetrics(actionID) + spellCoeff := 0.19665 //Will have to verify this + targets := mw.Env.Raid.GetFirstNPlayersOrPets(int32(mw.Env.Raid.NumTargetDummies)) + maxStacks := 3 + + var renewingMist *core.Spell + renewingMist = mw.RegisterSpell(core.SpellConfig{ + ActionID: actionID, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskSpellHealing, + Flags: core.SpellFlagHelpful | core.SpellFlagAPL, + //ClassSpellMask: monk.MonkSpellRenewingMist, + + ManaCost: core.ManaCostOptions{BaseCostPercent: 5.85}, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + CD: core.Cooldown{ + Timer: mw.NewTimer(), + Duration: time.Second * 8, + }, + }, + DamageMultiplier: 1, + ThreatMultiplier: 1, + CritMultiplier: mw.DefaultCritMultiplier(), + + Hot: core.DotConfig{ + Aura: core.Aura{ + Label: "Renewing Mist", + MaxStacks: 3, + }, + NumberOfTicks: 9, + TickLength: 2 * time.Second, + AffectedByCastSpeed: true, //Not sure + HasteReducesDuration: true, //Not sure + OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, _ bool) { + dot.SnapshotBaseDamage = 0 + mw.CalcScalingSpellDmg(spellCoeff) + dot.SnapshotAttackerMultiplier = dot.Spell.CasterHealingMultiplier() + }, + + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + dot.CalcAndDealPeriodicSnapshotHealing(sim, target, dot.OutcomeTick) + //Has to jump to two more targets after initial cast + + if maxStacks > 1 { + + for _, element := range targets { + hot := renewingMist.Hot(element) + + if !hot.IsActive() { + fmt.Print("Here") + + hot.Apply(sim) + //renewingMist.Cast(sim, target) + maxStacks = maxStacks - 1 + break + } + } + + } + }, + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + + for _, element := range targets { + hot := spell.Hot(element) + + if !hot.IsActive() { + hot.Apply(sim) + break + } + } + + //hot := spell.Hot(target) + //spell.Hot(spell.Unit).Apply(sim) + //if !hot.IsActive() { //Error? + // hot.Apply(sim) + //} + chiGain := int32(1) //core.TernaryInt32(monk.StanceMatches(FierceTiger), 2, 1) + mw.AddChi(sim, spell, chiGain, chiMetrics) + + }, + }) + + mw.RegisterSpell(core.SpellConfig{}) + +} diff --git a/sim/monk/mistweaver/surging_mist.go b/sim/monk/mistweaver/surging_mist.go new file mode 100644 index 0000000000..bc8f1b6bbc --- /dev/null +++ b/sim/monk/mistweaver/surging_mist.go @@ -0,0 +1,42 @@ +package mistweaver + +import ( + "time" + + "github.com/wowsims/mop/sim/core" +) + +func (mw *MistweaverMonk) registerSurgingMist() { + actionID := core.ActionID{SpellID: 116694} + chiMetrics := mw.NewChiMetrics(actionID) + spellCoeff := 1.8 + + mw.RegisterSpell(core.SpellConfig{ + ActionID: actionID, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskSpellHealing, + Flags: core.SpellFlagHelpful | core.SpellFlagAPL, + + ManaCost: core.ManaCostOptions{ + BaseCostPercent: 8.8, + }, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + CastTime: time.Microsecond * 1500, + }, + }, + + DamageMultiplier: 1, + ThreatMultiplier: 1, + CritMultiplier: mw.DefaultCritMultiplier(), + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + baseHealing := 19630 + spellCoeff*spell.HealingPower(target) + spell.CalcAndDealHealing(sim, &mw.Env.Raid.GetFirstTargetDummy().Unit, baseHealing, spell.OutcomeHealingCrit) + chiGain := int32(1) //core.TernaryInt32(monk.StanceMatches(FierceTiger), 2, 1) + mw.AddChi(sim, spell, chiGain, chiMetrics) + }, + }) + +} From 80b9f3ed8f8b89a0f0373e75edad8e18b5cd42d9 Mon Sep 17 00:00:00 2001 From: Patrick-Hogeveen Date: Tue, 27 May 2025 15:58:41 -0400 Subject: [PATCH 04/15] Mistweaver changes --- sim/monk/monk.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sim/monk/monk.go b/sim/monk/monk.go index 107e3c6848..869863cbdb 100644 --- a/sim/monk/monk.go +++ b/sim/monk/monk.go @@ -253,6 +253,9 @@ const ( // Brewmaster MonkSpellGuard + //Mistweaver + MonkSpellRenewingMist + MonkSpellLast MonkSpellsAll = MonkSpellLast<<1 - 1 ) From 2bdec85111cbf5b17cd815908f0f0f13c996158e Mon Sep 17 00:00:00 2001 From: Patrick-Hogeveen Date: Fri, 6 Jun 2025 14:34:40 -0400 Subject: [PATCH 05/15] Working on making monk spells functional --- sim/core/flags.go | 1 + sim/core/spell.go | 2 +- sim/monk/mistweaver/enveloping_mist.go | 86 +++++++++++++++++++++++ sim/monk/mistweaver/mistweaver.go | 5 ++ sim/monk/mistweaver/renewing_mist.go | 67 +++++++++--------- sim/monk/mistweaver/soothing_mist.go | 94 ++++++++++++++++++++++++++ sim/monk/mistweaver/surging_mist.go | 16 +++-- sim/monk/monk.go | 3 + 8 files changed, 235 insertions(+), 39 deletions(-) create mode 100644 sim/monk/mistweaver/enveloping_mist.go create mode 100644 sim/monk/mistweaver/soothing_mist.go diff --git a/sim/core/flags.go b/sim/core/flags.go index e4fd5122ff..acbd892f98 100644 --- a/sim/core/flags.go +++ b/sim/core/flags.go @@ -186,6 +186,7 @@ const ( SpellFlagCannotBeDodged // Ignores dodge in physical hit rolls SpellFlagBinary // Does not do partial resists and could need a different hit roll. SpellFlagChanneled // Spell is channeled + SpellFlagCastWhileChanneling // Spell can be cast while channeling SpellFlagDisease // Spell is categorized as disease SpellFlagHelpful // For healing spells / buffs. SpellFlagMeleeMetrics // Marks a spell as a melee ability for metrics. diff --git a/sim/core/spell.go b/sim/core/spell.go index 1868a2a418..e26bcd7267 100644 --- a/sim/core/spell.go +++ b/sim/core/spell.go @@ -559,7 +559,7 @@ func (spell *Spell) CanCast(sim *Simulation, target *Unit) bool { } // While casting or channeling, no other action is possible - if spell.Unit.Hardcast.Expires > sim.CurrentTime { + if (spell.Unit.Hardcast.Expires > sim.CurrentTime) && !spell.Flags.Matches(SpellFlagCastWhileChanneling) { //if sim.Log != nil { // sim.Log("Cant cast because already casting/channeling") //} diff --git a/sim/monk/mistweaver/enveloping_mist.go b/sim/monk/mistweaver/enveloping_mist.go new file mode 100644 index 0000000000..68486bcd27 --- /dev/null +++ b/sim/monk/mistweaver/enveloping_mist.go @@ -0,0 +1,86 @@ +package mistweaver + +import ( + "fmt" + "time" + + "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/monk" +) + +func (mw *MistweaverMonk) registerEnvelopingMist() { + actionID := core.ActionID{SpellID: 124682} + chiMetrics := mw.NewChiMetrics(actionID) + spellCoeff := 0.45 + + mw.enevelopingMist = mw.RegisterSpell(core.SpellConfig{ + ActionID: actionID, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskSpellHealing, + Flags: core.SpellFlagHelpful | core.SpellFlagAPL, + ClassSpellMask: monk.MonkSpellEnvelopingMist, + + ManaCost: core.ManaCostOptions{BaseCostPercent: 0}, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + CastTime: time.Millisecond * 2000, + }, + }, + DamageMultiplier: 1, + ThreatMultiplier: 1, + CritMultiplier: mw.DefaultCritMultiplier(), + + ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { + return mw.GetChi() >= 3 + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + + fmt.Print("Envelop\n") + + mw.SpendChi(sim, 3, chiMetrics) + spell.RelatedDotSpell.Cast(sim, &mw.Unit) + + }, + }) + + mw.enevelopingMist.RelatedDotSpell = mw.RegisterSpell(core.SpellConfig{ + ActionID: actionID, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskSpellHealing, + Flags: core.SpellFlagHelpful, + //ClassSpellMask: monk.MonkSpellEnvelopingMist, + + DamageMultiplier: 1, + ThreatMultiplier: 1, + CritMultiplier: mw.DefaultCritMultiplier(), + Hot: core.DotConfig{ + Aura: core.Aura{ + Label: "Enveloping Mist", + }, + NumberOfTicks: 6, + TickLength: 1 * time.Second, + AffectedByCastSpeed: true, + HasteReducesDuration: true, + OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, _ bool) { + dot.SnapshotBaseDamage = 0 + mw.CalcScalingSpellDmg(spellCoeff) + 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) { + //Targets only mw currently + fmt.Print("related mist \n") + hot := spell.Hot(&mw.Unit) + + hot.Apply(sim) + + }, + }) +} diff --git a/sim/monk/mistweaver/mistweaver.go b/sim/monk/mistweaver/mistweaver.go index f1f049f7af..6b0f5e6e39 100644 --- a/sim/monk/mistweaver/mistweaver.go +++ b/sim/monk/mistweaver/mistweaver.go @@ -50,6 +50,9 @@ func NewMistweaverMonk(character *core.Character, options *proto.Player) *Mistwe type MistweaverMonk struct { *monk.Monk + + renewingMist *core.Spell + enevelopingMist *core.Spell } func (mw *MistweaverMonk) GetMonk() *monk.Monk { @@ -74,6 +77,8 @@ func (mw *MistweaverMonk) RegisterSpecializationEffects() { mw.RegisterMastery() mw.registerRenewingMist() mw.registerSurgingMist() + mw.registerSoothingMist() + mw.registerEnvelopingMist() } func (mw *MistweaverMonk) RegisterMastery() { diff --git a/sim/monk/mistweaver/renewing_mist.go b/sim/monk/mistweaver/renewing_mist.go index 2844ee35b6..fa09794066 100644 --- a/sim/monk/mistweaver/renewing_mist.go +++ b/sim/monk/mistweaver/renewing_mist.go @@ -1,9 +1,8 @@ package mistweaver import ( - "time" - "fmt" + "time" "github.com/wowsims/mop/sim/core" ) @@ -12,15 +11,34 @@ func (mw *MistweaverMonk) registerRenewingMist() { actionID := core.ActionID{SpellID: 115151} chiMetrics := mw.NewChiMetrics(actionID) spellCoeff := 0.19665 //Will have to verify this - targets := mw.Env.Raid.GetFirstNPlayersOrPets(int32(mw.Env.Raid.NumTargetDummies)) - maxStacks := 3 + //targets := mw.Env.Raid.GetFirstNPlayersOrPets(int32(mw.Env.Raid.NumTargetDummies)) + charges := 3 + + mistHandler := func(sim *core.Simulation, hot *core.Spell) bool { + success := false + for _, player := range sim.Raid.AllUnits { + hot := hot.Hot(player) + + if !hot.IsActive() { + + hot.Apply(sim) + hot.TakeSnapshot(sim, false) + success = true + break + } + } + return success + } + //var renewingMistSpread *core.Spell var renewingMist *core.Spell - renewingMist = mw.RegisterSpell(core.SpellConfig{ + fmt.Print(renewingMist) + + mw.renewingMist = mw.RegisterSpell(core.SpellConfig{ ActionID: actionID, SpellSchool: core.SpellSchoolNature, ProcMask: core.ProcMaskSpellHealing, - Flags: core.SpellFlagHelpful | core.SpellFlagAPL, + Flags: core.SpellFlagHelpful, //ClassSpellMask: monk.MonkSpellRenewingMist, ManaCost: core.ManaCostOptions{BaseCostPercent: 5.85}, @@ -39,8 +57,7 @@ func (mw *MistweaverMonk) registerRenewingMist() { Hot: core.DotConfig{ Aura: core.Aura{ - Label: "Renewing Mist", - MaxStacks: 3, + Label: "Renewing Mist", }, NumberOfTicks: 9, TickLength: 2 * time.Second, @@ -55,19 +72,12 @@ func (mw *MistweaverMonk) registerRenewingMist() { dot.CalcAndDealPeriodicSnapshotHealing(sim, target, dot.OutcomeTick) //Has to jump to two more targets after initial cast - if maxStacks > 1 { - - for _, element := range targets { - hot := renewingMist.Hot(element) - - if !hot.IsActive() { - fmt.Print("Here") + if charges > 1 { + fmt.Print("Checking\n") + success := mistHandler(sim, dot.Spell) - hot.Apply(sim) - //renewingMist.Cast(sim, target) - maxStacks = maxStacks - 1 - break - } + if success { + charges = charges - 1 } } @@ -75,14 +85,13 @@ func (mw *MistweaverMonk) registerRenewingMist() { }, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + fmt.Print("Renew\n") + success := mistHandler(sim, spell) - for _, element := range targets { - hot := spell.Hot(element) - - if !hot.IsActive() { - hot.Apply(sim) - break - } + if success { + charges = 3 + chiGain := int32(1) //core.TernaryInt32(monk.StanceMatches(FierceTiger), 2, 1) + mw.AddChi(sim, spell, chiGain, chiMetrics) } //hot := spell.Hot(target) @@ -90,12 +99,8 @@ func (mw *MistweaverMonk) registerRenewingMist() { //if !hot.IsActive() { //Error? // hot.Apply(sim) //} - chiGain := int32(1) //core.TernaryInt32(monk.StanceMatches(FierceTiger), 2, 1) - mw.AddChi(sim, spell, chiGain, chiMetrics) }, }) - mw.RegisterSpell(core.SpellConfig{}) - } diff --git a/sim/monk/mistweaver/soothing_mist.go b/sim/monk/mistweaver/soothing_mist.go new file mode 100644 index 0000000000..cf0fda7f9b --- /dev/null +++ b/sim/monk/mistweaver/soothing_mist.go @@ -0,0 +1,94 @@ +package mistweaver + +import ( + "fmt" + "time" + + "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/monk" +) + +func (mw *MistweaverMonk) registerSoothingMist() { + actionID := core.ActionID{SpellID: 115175} + chiMetrics := mw.NewChiMetrics(actionID) + spellCoeff := 0.1792 + + surgingMistCastTimeMod := mw.AddDynamicMod(core.SpellModConfig{ + Kind: core.SpellMod_CastTime_Pct, + FloatValue: -1, + ClassMask: monk.MonkSpellSurgingMist, + }) + + envelopingMistCastTimeMod := mw.AddDynamicMod(core.SpellModConfig{ + Kind: core.SpellMod_CastTime_Pct, + FloatValue: -1, + ClassMask: monk.MonkSpellEnvelopingMist, + }) + + var soothingMist *core.Spell + + soothingMist = mw.RegisterSpell(core.SpellConfig{ + ActionID: actionID, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskSpellHealing, + Flags: core.SpellFlagHelpful | core.SpellFlagAPL, + + ManaCost: core.ManaCostOptions{ + BaseCostPercent: 1, + }, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: time.Millisecond * 1000, + }, + }, + + DamageMultiplier: 1, + ThreatMultiplier: 1, + CritMultiplier: mw.DefaultCritMultiplier(), + Hot: core.DotConfig{ + Aura: core.Aura{ + Label: "Soothing Mist", + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + surgingMistCastTimeMod.Deactivate() + envelopingMistCastTimeMod.Deactivate() + }, + }, + NumberOfTicks: 9, + TickLength: 1 * time.Second, + AffectedByCastSpeed: true, //Not sure + HasteReducesDuration: true, //Not sure + OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, _ bool) { + envelopingActive := mw.enevelopingMist.RelatedDotSpell.Hot(target).IsActive() + dot.SnapshotBaseDamage = 0 + mw.CalcScalingSpellDmg(spellCoeff) + multiplier := dot.Spell.CasterHealingMultiplier() + if envelopingActive { + multiplier += +0.3 + } + + dot.SnapshotAttackerMultiplier = multiplier + }, + + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + dot.CalcAndDealPeriodicSnapshotHealing(sim, target, dot.OutcomeTick) + + outcome := sim.Roll(1, 10) + if outcome > 7 { + mw.AddChi(sim, soothingMist, 1, chiMetrics) + } + }, + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + //Currently target mistweaver only, will need to fix this + hot := spell.Hot(&mw.Unit) + hot.Apply(sim) + hot.TickOnce(sim) + expiresAt := hot.ExpiresAt() + mw.AutoAttacks.StopMeleeUntil(sim, expiresAt, false) + surgingMistCastTimeMod.Activate() + envelopingMistCastTimeMod.Activate() + fmt.Print(surgingMistCastTimeMod.IsActive) + }, + }) + +} diff --git a/sim/monk/mistweaver/surging_mist.go b/sim/monk/mistweaver/surging_mist.go index bc8f1b6bbc..e6ea78006b 100644 --- a/sim/monk/mistweaver/surging_mist.go +++ b/sim/monk/mistweaver/surging_mist.go @@ -4,6 +4,7 @@ import ( "time" "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/monk" ) func (mw *MistweaverMonk) registerSurgingMist() { @@ -12,18 +13,18 @@ func (mw *MistweaverMonk) registerSurgingMist() { spellCoeff := 1.8 mw.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - SpellSchool: core.SpellSchoolNature, - ProcMask: core.ProcMaskSpellHealing, - Flags: core.SpellFlagHelpful | core.SpellFlagAPL, - + ActionID: actionID, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskSpellHealing, + Flags: core.SpellFlagHelpful | core.SpellFlagAPL | core.SpellFlagCastWhileChanneling, + ClassSpellMask: monk.MonkSpellSurgingMist, ManaCost: core.ManaCostOptions{ BaseCostPercent: 8.8, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ GCD: core.GCDDefault, - CastTime: time.Microsecond * 1500, + CastTime: time.Millisecond * 1500, }, }, @@ -33,7 +34,8 @@ func (mw *MistweaverMonk) registerSurgingMist() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseHealing := 19630 + spellCoeff*spell.HealingPower(target) - spell.CalcAndDealHealing(sim, &mw.Env.Raid.GetFirstTargetDummy().Unit, baseHealing, spell.OutcomeHealingCrit) + //Hardcoded to heal the player for now + spell.CalcAndDealHealing(sim, &mw.Unit, baseHealing, spell.OutcomeHealingCrit) chiGain := int32(1) //core.TernaryInt32(monk.StanceMatches(FierceTiger), 2, 1) mw.AddChi(sim, spell, chiGain, chiMetrics) }, diff --git a/sim/monk/monk.go b/sim/monk/monk.go index bcad1b61d7..5e66b6df40 100644 --- a/sim/monk/monk.go +++ b/sim/monk/monk.go @@ -328,6 +328,9 @@ const ( //Mistweaver MonkSpellRenewingMist + MonkSpellSoothingMist + MonkSpellSurgingMist + MonkSpellEnvelopingMist MonkSpellLast MonkSpellsAll = MonkSpellLast<<1 - 1 From 590b2ab1ae0aa20d4b7ab22962f0392bec4d134a Mon Sep 17 00:00:00 2001 From: Patrick-Hogeveen Date: Sun, 22 Jun 2025 14:14:11 -0400 Subject: [PATCH 06/15] Added cast while channeling logic --- sim/core/cast.go | 4 ++++ sim/core/spell.go | 2 ++ sim/core/spell_mod.go | 16 ++++++++++++++++ sim/core/unit.go | 4 ++++ sim/monk/mistweaver/mistweaver.go | 13 +++++++++++++ sim/monk/monk.go | 4 ++++ 6 files changed, 43 insertions(+) diff --git a/sim/core/cast.go b/sim/core/cast.go index 46affab4f2..94fb5a7588 100644 --- a/sim/core/cast.go +++ b/sim/core/cast.go @@ -146,6 +146,10 @@ func (spell *Spell) makeCastFunc(config CastConfig) CastSuccessFunc { return spell.castFailureHelper(sim, "casting/channeling %v for %s, curTime = %s", hc.ActionID, hc.Expires-sim.CurrentTime, sim.CurrentTime) } + if dot := spell.Unit.ChanneledDot; spell.Unit.IsChanneling(sim) && !spell.Flags.Matches(SpellFlagCastWhileChanneling) && (spell.Unit.Rotation.interruptChannelIf == nil || !spell.Unit.Rotation.interruptChannelIf.GetBool(sim)) { + return spell.castFailureHelper(sim, "channeling %v for %s, curTime = %s", dot.ActionID, dot.expires-sim.CurrentTime, sim.CurrentTime) + } + if effectiveTime := spell.CurCast.EffectiveTime(); effectiveTime != 0 { // do not add channeled time here as they have variable cast length diff --git a/sim/core/spell.go b/sim/core/spell.go index 47b2f82924..b1f592a420 100644 --- a/sim/core/spell.go +++ b/sim/core/spell.go @@ -575,6 +575,8 @@ func (spell *Spell) CanCast(sim *Simulation, target *Unit) bool { //if sim.Log != nil { // sim.Log("Cant cast because already casting/channeling") //} + fmt.Print("Cant cast because already casting/channeling") + sim.Log("Cant cast because already casting/channeling") return false } diff --git a/sim/core/spell_mod.go b/sim/core/spell_mod.go index 075612bdaf..7f00f2411b 100644 --- a/sim/core/spell_mod.go +++ b/sim/core/spell_mod.go @@ -302,6 +302,9 @@ const ( // Enables casting while moving SpellMod_AllowCastWhileMoving + // Enables casting while channeling + SpellMod_AllowCastWhileChanneling + // Add/subtract bonus spell power // Uses: FloatValue SpellMod_BonusSpellPower_Flat @@ -413,6 +416,11 @@ var spellModMap = map[SpellModType]*SpellModFunctions{ Remove: removeAllowCastWhileMoving, }, + SpellMod_AllowCastWhileChanneling: { + Apply: applyAllowCastWhileChanneling, + Remove: removeAllowCastWhileChanneling, + }, + SpellMod_BonusSpellPower_Flat: { Apply: applyBonusSpellPowerFlat, Remove: removeBonusSpellPowerFlat, @@ -631,6 +639,14 @@ func applyAllowCastWhileMoving(mod *SpellMod, spell *Spell) { spell.Flags |= SpellFlagCanCastWhileMoving } +func applyAllowCastWhileChanneling(mod *SpellMod, spell *Spell) { + spell.Flags |= SpellFlagCastWhileChanneling +} + +func removeAllowCastWhileChanneling(mod *SpellMod, spell *Spell) { + spell.Flags ^= SpellFlagCastWhileChanneling +} + func removeAllowCastWhileMoving(mod *SpellMod, spell *Spell) { spell.Flags ^= SpellFlagCanCastWhileMoving } diff --git a/sim/core/unit.go b/sim/core/unit.go index aa35fdb09a..25d7a57d7c 100644 --- a/sim/core/unit.go +++ b/sim/core/unit.go @@ -436,6 +436,10 @@ func (unit *Unit) InitialCastSpeed() float64 { return unit.initialCastSpeed } +func (unit *Unit) IsChanneling(sim *Simulation) bool { + return unit.ChanneledDot != nil +} + func (unit *Unit) SpellGCD() time.Duration { return max(GCDMin, unit.ApplyCastSpeed(GCDDefault)) } diff --git a/sim/monk/mistweaver/mistweaver.go b/sim/monk/mistweaver/mistweaver.go index 6b0f5e6e39..7367b715c0 100644 --- a/sim/monk/mistweaver/mistweaver.go +++ b/sim/monk/mistweaver/mistweaver.go @@ -51,8 +51,16 @@ func NewMistweaverMonk(character *core.Character, options *proto.Player) *Mistwe type MistweaverMonk struct { *monk.Monk + ManaTeaStackAura *core.Aura + renewingMist *core.Spell enevelopingMist *core.Spell + + JadeSerpentAura *core.Aura + //May move this to monk as both ww and mw use this + outstandingChi int32 + + manaTeaAura *core.Aura } func (mw *MistweaverMonk) GetMonk() *monk.Monk { @@ -70,6 +78,7 @@ func (mw *MistweaverMonk) ApplyTalents() { } func (mw *MistweaverMonk) Reset(sim *core.Simulation) { + mw.outstandingChi = 0 mw.Monk.Reset(sim) } @@ -79,6 +88,10 @@ func (mw *MistweaverMonk) RegisterSpecializationEffects() { mw.registerSurgingMist() mw.registerSoothingMist() mw.registerEnvelopingMist() + mw.registerUplift() + mw.registerRevival() + mw.registerSummonJadeSerpentStatue() + mw.registerManaTea() } func (mw *MistweaverMonk) RegisterMastery() { diff --git a/sim/monk/monk.go b/sim/monk/monk.go index 410271afa4..2b1220dafc 100644 --- a/sim/monk/monk.go +++ b/sim/monk/monk.go @@ -346,6 +346,10 @@ const ( MonkSpellSoothingMist MonkSpellSurgingMist MonkSpellEnvelopingMist + MonkSpellUplift + MonkSpellRevival + MonkSpellSummonJadeSerpentStatue + MonkSpellManaTea MonkSpellLast MonkSpellsAll = MonkSpellLast<<1 - 1 From b173c2db87480ed692627b1aa24b4144446ec8a8 Mon Sep 17 00:00:00 2001 From: Patrick-Hogeveen Date: Sun, 29 Jun 2025 16:51:16 -0400 Subject: [PATCH 07/15] Soothing mist fix + passives --- sim/core/apl.go | 14 +++- sim/core/spell.go | 5 +- sim/monk/blackout_kick.go | 6 ++ sim/monk/jab.go | 4 +- sim/monk/mistweaver/enveloping_mist.go | 2 +- sim/monk/mistweaver/mana_tea.go | 102 ++++++++++++++++++++++++ sim/monk/mistweaver/mistweaver.go | 1 + sim/monk/mistweaver/passives.go | 105 +++++++++++++++++++++++++ sim/monk/mistweaver/renewing_mist.go | 4 +- sim/monk/mistweaver/revival.go | 40 ++++++++++ sim/monk/mistweaver/soothing_mist.go | 35 +++++++-- sim/monk/mistweaver/surging_mist.go | 2 +- sim/monk/mistweaver/uplift.go | 45 +++++++++++ 13 files changed, 349 insertions(+), 16 deletions(-) create mode 100644 sim/monk/mistweaver/mana_tea.go create mode 100644 sim/monk/mistweaver/passives.go create mode 100644 sim/monk/mistweaver/revival.go create mode 100644 sim/monk/mistweaver/uplift.go diff --git a/sim/core/apl.go b/sim/core/apl.go index fa36b70f03..8fceb05c2a 100644 --- a/sim/core/apl.go +++ b/sim/core/apl.go @@ -23,6 +23,8 @@ type APLRotation struct { // If true, can recast channel when interrupted. allowChannelRecastOnInterrupt bool + //Checking for cast-while-channeling spells to allow the APL to not evaluate during channels unless absolutely necessary + allowCastWhileChanneling bool // Used inside of actions/value to determine whether they will occur during the prepull or regular rotation. parsingPrepull bool @@ -265,6 +267,11 @@ func (rot *APLRotation) reset(sim *Simulation) { rot.inLoop = false rot.interruptChannelIf = nil rot.allowChannelRecastOnInterrupt = false + + //rot.allowCastWhileChanneling = slices.ContainsFunc(rot.unit.Spellbook, func(spell *Spell) bool { + // return spell.Flags.Matches(SpellFlagCastWhileChanneling) + //}) + for _, action := range rot.allAPLActions() { action.impl.Reset(sim) } @@ -282,7 +289,12 @@ func (apl *APLRotation) DoNextAction(sim *Simulation) { return } - if apl.unit.ChanneledDot != nil { + //Probably not the best solution, added so apl evaluates if a spell can be cast while channeling during runtime rather than on reset + //apl.allowCastWhileChanneling = slices.ContainsFunc(apl.unit.Spellbook, func(spell *Spell) bool { + // return spell.Flags.Matches(SpellFlagCastWhileChanneling) + //}) + + if apl.unit.ChanneledDot != nil && !apl.allowCastWhileChanneling { return } diff --git a/sim/core/spell.go b/sim/core/spell.go index f08d3d4027..f3de77ca17 100644 --- a/sim/core/spell.go +++ b/sim/core/spell.go @@ -571,12 +571,11 @@ func (spell *Spell) CanCast(sim *Simulation, target *Unit) bool { } // While casting or channeling, no other action is possible - if (spell.Unit.Hardcast.Expires > sim.CurrentTime) && !spell.Flags.Matches(SpellFlagCastWhileChanneling) { + if (spell.Unit.Hardcast.Expires > sim.CurrentTime /*|| spell.Unit.IsChanneling(sim)*/) && !spell.Flags.Matches(SpellFlagCastWhileChanneling) { //if sim.Log != nil { // sim.Log("Cant cast because already casting/channeling") //} - fmt.Print("Cant cast because already casting/channeling") - sim.Log("Cant cast because already casting/channeling") + return false } diff --git a/sim/monk/blackout_kick.go b/sim/monk/blackout_kick.go index 4518b74c8c..e68ba1677e 100644 --- a/sim/monk/blackout_kick.go +++ b/sim/monk/blackout_kick.go @@ -68,6 +68,7 @@ func blackoutKickSpellConfig(monk *Monk, isSEFClone bool, overrides core.SpellCo func (monk *Monk) registerBlackoutKick() { chiMetrics := monk.NewChiMetrics(blackoutKickActionID) chiCost := int32(2) + manaMetrics := monk.NewManaMetrics(blackoutKickActionID) monk.RegisterSpell(blackoutKickSpellConfig(monk, false, core.SpellConfig{ Cast: core.CastConfig{ @@ -92,6 +93,11 @@ func (monk *Monk) registerBlackoutKick() { } else { monk.SpendChi(sim, chiCost, chiMetrics) } + + if monk.MuscleMemoryBlackoutKickAura.IsActive() { + result.Damage += result.Damage * 1.5 + monk.AddMana(sim, monk.MaxMana()*0.04, manaMetrics) + } } spell.DealOutcome(sim, result) diff --git a/sim/monk/jab.go b/sim/monk/jab.go index c436a743c5..75cec0dff3 100644 --- a/sim/monk/jab.go +++ b/sim/monk/jab.go @@ -73,7 +73,8 @@ func (monk *Monk) registerJab() { Refund: 0.8, }, ManaCost: core.ManaCostOptions{ - BaseCostPercent: core.TernaryFloat64(monk.StanceMatches(WiseSerpent), 8, 0), + //This is wrong? But works? + BaseCostPercent: core.TernaryFloat64(monk.StanceMatches(WiseSerpent), 0, 8), }, Cast: core.CastConfig{ @@ -84,6 +85,7 @@ func (monk *Monk) registerJab() { }, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + baseDamage := monk.CalculateMonkStrikeDamage(sim, spell) result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) diff --git a/sim/monk/mistweaver/enveloping_mist.go b/sim/monk/mistweaver/enveloping_mist.go index 68486bcd27..1f0f816838 100644 --- a/sim/monk/mistweaver/enveloping_mist.go +++ b/sim/monk/mistweaver/enveloping_mist.go @@ -17,7 +17,7 @@ func (mw *MistweaverMonk) registerEnvelopingMist() { ActionID: actionID, SpellSchool: core.SpellSchoolNature, ProcMask: core.ProcMaskSpellHealing, - Flags: core.SpellFlagHelpful | core.SpellFlagAPL, + Flags: core.SpellFlagHelpful | core.SpellFlagAPL, // | core.SpellFlagCastWhileChanneling, ClassSpellMask: monk.MonkSpellEnvelopingMist, ManaCost: core.ManaCostOptions{BaseCostPercent: 0}, diff --git a/sim/monk/mistweaver/mana_tea.go b/sim/monk/mistweaver/mana_tea.go new file mode 100644 index 0000000000..ecc6510935 --- /dev/null +++ b/sim/monk/mistweaver/mana_tea.go @@ -0,0 +1,102 @@ +package mistweaver + +import ( + "math" + "time" + + "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/core/stats" + "github.com/wowsims/mop/sim/monk" +) + +func (mw *MistweaverMonk) registerManaTea() { + + buffActionID := core.ActionID{SpellID: 115294} + stackActionID := core.ActionID{SpellID: 123766} + manaMetrics := mw.NewManaMetrics(buffActionID) + manaPerTick := 0.0 + //numerOFTicks := 6 + + mw.Monk.RegisterOnChiSpent(func(sim *core.Simulation, chiSpent int32) { + accumulatedChi := mw.outstandingChi + chiSpent + + for accumulatedChi >= 4 { + + mw.AddBrewStacks(sim, 1) + accumulatedChi -= 4 + } + + mw.outstandingChi = accumulatedChi + + }) + + mw.ManaTeaStackAura = mw.RegisterAura(core.Aura{ + Label: "Mana Tea Stacks" + mw.Label, + ActionID: stackActionID, + Duration: time.Hour, + MaxStacks: 10, + }) + + mw.Monk.RegisterOnNewBrewStacks(func(sim *core.Simulation, stacksToAdd int32) { + mw.ManaTeaStackAura.Activate(sim) + + procChance := mw.GetStat(stats.SpellCritPercent) + + if sim.Proc(math.Mod(procChance, 1), "Mana Tea") { + stacksToAdd += 1 + } + + mw.ManaTeaStackAura.SetStacks(sim, mw.ManaTeaStackAura.GetStacks()+stacksToAdd) + }) + + mw.RegisterSpell(core.SpellConfig{ + ActionID: buffActionID, + Flags: core.SpellFlagAPL | core.SpellFlagNoOnCastComplete | core.SpellFlagHelpful | core.SpellFlagChanneled, + ClassSpellMask: monk.MonkSpellManaTea, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: time.Millisecond * 1000, + }, + }, + + Hot: core.DotConfig{ + SelfOnly: true, + Aura: core.Aura{ + Label: "Mana Tea", + Duration: 3 * time.Second, //Set at activation + }, + NumberOfTicks: 6, + TickLength: 500 * time.Millisecond, + AffectedByCastSpeed: false, //? + OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { + mw.manaTeaAura = dot.Aura + }, + OnTick: func(sim *core.Simulation, target *core.Unit, spell *core.Dot) { + mw.AddMana(sim, manaPerTick, manaMetrics) + + mw.ManaTeaStackAura.SetStacks(sim, mw.ManaTeaStackAura.GetStacks()-1) + + }, + }, + + ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { + + return mw.ManaTeaStackAura.GetStacks() > 0 + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + manaPerTick = mw.MaxMana() * 0.05 //Patched to restore 5% instead of original 4% + + hot := spell.SelfHot() + stacksToUse := min(mw.ManaTeaStackAura.GetStacks(), 6.0) + hot.Duration = time.Duration(stacksToUse) * 500 * time.Millisecond + hot.BaseTickCount = stacksToUse + hot.Activate(sim) + //mw.ManaTeaStackAura.SetStacks(sim, mw.ManaTeaStackAura.GetStacks()-1) + + //spell.SelfHot().Apply(sim) + + }, + }) +} diff --git a/sim/monk/mistweaver/mistweaver.go b/sim/monk/mistweaver/mistweaver.go index f249223477..bf6364ff05 100644 --- a/sim/monk/mistweaver/mistweaver.go +++ b/sim/monk/mistweaver/mistweaver.go @@ -93,6 +93,7 @@ func (mw *MistweaverMonk) RegisterSpecializationEffects() { mw.registerRevival() mw.registerSummonJadeSerpentStatue() mw.registerManaTea() + mw.registerPassives() } func (mw *MistweaverMonk) RegisterMastery() { diff --git a/sim/monk/mistweaver/passives.go b/sim/monk/mistweaver/passives.go new file mode 100644 index 0000000000..654437f733 --- /dev/null +++ b/sim/monk/mistweaver/passives.go @@ -0,0 +1,105 @@ +package mistweaver + +import ( + "fmt" + "time" + + "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/monk" +) + +func (mw *MistweaverMonk) registerPassives() { + mw.registerMuscleMemory() + mw.registerSerpentsZeal() +} + +func (mw *MistweaverMonk) registerSerpentsZeal() { + + dmgDone := 0.0 + + serpentZealHeal := mw.RegisterSpell((core.SpellConfig{ + ActionID: core.ActionID{SpellID: 127722}, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskSpellHealing, + Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + + DamageMultiplier: 0.25, + ThreatMultiplier: 1, + CritMultiplier: mw.DefaultCritMultiplier(), + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + spell.CalcAndDealHealing(sim, target, dmgDone, spell.OutcomeHealing) + }, + })) + mw.SerpentZealAura = mw.RegisterAura(core.Aura{ + Label: "Serpent's Zeal", + ActionID: core.ActionID{SpellID: 127722}, + Duration: time.Second * 30, + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if result == nil || !result.Landed() || result.Damage == 0 || !spell.ProcMask.Matches(core.ProcMaskWhiteHit) { + return + } + + dmgDone = result.Damage + //Should be a smart heal + serpentZealHeal.Cast(sim, &mw.Unit) + + }, + }) + + core.MakeProcTriggerAura(&mw.Unit, core.ProcTrigger{ + Name: "Serpent Zeal: BlackoutKick Trigger", + Callback: core.CallbackOnSpellHitDealt, + ClassSpellMask: monk.MonkSpellBlackoutKick, + Outcome: core.OutcomeLanded, + ProcChance: 1, + + Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + mw.SerpentZealAura.Activate(sim) + }, + }) + +} + +func (mw *MistweaverMonk) registerMuscleMemory() { + registerMuscleMemoryAuraAndTrigger := func(labelSuffix string, spellID int32, triggerSpellMask int64) *core.Aura { + aura := mw.RegisterAura(core.Aura{ + Label: fmt.Sprintf("Muscle Memory %s %s", labelSuffix, mw.Label), + ActionID: core.ActionID{SpellID: 139597}, + Duration: time.Second * 15, + + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if !spell.Matches(triggerSpellMask) || !result.Landed() { + return + } + aura.Deactivate(sim) + }, + }) + + core.MakeProcTriggerAura(&mw.Unit, core.ProcTrigger{ + Name: fmt.Sprintf("Muscle Memory: %s Trigger %s", labelSuffix, mw.Label), + Callback: core.CallbackOnSpellHitDealt, + ClassSpellMask: monk.MonkSpellJab, + Outcome: core.OutcomeLanded, + ProcChance: 1, + + Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + aura.Activate(sim) + }, + }) + + return aura + } + + mw.MuscleMemoryBlackoutKickAura = registerMuscleMemoryAuraAndTrigger( + "Blackout Kick", + 116768, + monk.MonkSpellBlackoutKick, + ) + + mw.MuscleMemoryTigerPalmAura = registerMuscleMemoryAuraAndTrigger( + "Tiger Palm", + 118864, + monk.MonkSpellTigerPalm, + ) +} diff --git a/sim/monk/mistweaver/renewing_mist.go b/sim/monk/mistweaver/renewing_mist.go index fa09794066..6549005195 100644 --- a/sim/monk/mistweaver/renewing_mist.go +++ b/sim/monk/mistweaver/renewing_mist.go @@ -16,7 +16,7 @@ func (mw *MistweaverMonk) registerRenewingMist() { mistHandler := func(sim *core.Simulation, hot *core.Spell) bool { success := false - for _, player := range sim.Raid.AllUnits { + for _, player := range sim.Raid.AllPlayerUnits { hot := hot.Hot(player) if !hot.IsActive() { @@ -72,7 +72,7 @@ func (mw *MistweaverMonk) registerRenewingMist() { dot.CalcAndDealPeriodicSnapshotHealing(sim, target, dot.OutcomeTick) //Has to jump to two more targets after initial cast - if charges > 1 { + if charges > 1 && dot.RemainingTicks() > 1 { fmt.Print("Checking\n") success := mistHandler(sim, dot.Spell) diff --git a/sim/monk/mistweaver/revival.go b/sim/monk/mistweaver/revival.go new file mode 100644 index 0000000000..10e736b3d1 --- /dev/null +++ b/sim/monk/mistweaver/revival.go @@ -0,0 +1,40 @@ +package mistweaver + +import ( + "time" + + "github.com/wowsims/mop/sim/core" +) + +func (mw *MistweaverMonk) registerRevival() { + actionID := core.ActionID{SpellID: 115310} + + spellCoeff := 3.5 + + mw.RegisterSpell(core.SpellConfig{ + ActionID: actionID, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskSpellHealing, + Flags: core.SpellFlagHelpful | core.SpellFlagAPL, + ManaCost: core.ManaCostOptions{BaseCostPercent: 7.7}, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + CD: core.Cooldown{ + Timer: mw.NewTimer(), + Duration: time.Minute * 3, + }, + }, + DamageMultiplier: 1, + ThreatMultiplier: 1, + CritMultiplier: mw.DefaultCritMultiplier(), + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + for _, player := range sim.Raid.AllPlayerUnits { + baseHealing := 0 + spellCoeff*spell.HealingPower(target) + spell.CalcAndDealHealing(sim, player, baseHealing, spell.OutcomeHealingCrit) + //Is it worth it to implement the magical, poison and disease dispel? Does that matter? + } + }, + }) +} diff --git a/sim/monk/mistweaver/soothing_mist.go b/sim/monk/mistweaver/soothing_mist.go index cf0fda7f9b..7712be52f7 100644 --- a/sim/monk/mistweaver/soothing_mist.go +++ b/sim/monk/mistweaver/soothing_mist.go @@ -1,7 +1,6 @@ package mistweaver import ( - "fmt" "time" "github.com/wowsims/mop/sim/core" @@ -12,6 +11,8 @@ func (mw *MistweaverMonk) registerSoothingMist() { actionID := core.ActionID{SpellID: 115175} chiMetrics := mw.NewChiMetrics(actionID) spellCoeff := 0.1792 + manaMetrics := mw.NewManaMetrics(actionID) + manaLoss := 0.0 surgingMistCastTimeMod := mw.AddDynamicMod(core.SpellModConfig{ Kind: core.SpellMod_CastTime_Pct, @@ -19,19 +20,29 @@ func (mw *MistweaverMonk) registerSoothingMist() { ClassMask: monk.MonkSpellSurgingMist, }) + surgingMistChannelMod := mw.AddDynamicMod(core.SpellModConfig{ + Kind: core.SpellMod_AllowCastWhileChanneling, + ClassMask: monk.MonkSpellSurgingMist, + }) + envelopingMistCastTimeMod := mw.AddDynamicMod(core.SpellModConfig{ Kind: core.SpellMod_CastTime_Pct, FloatValue: -1, ClassMask: monk.MonkSpellEnvelopingMist, }) + envelopingMistChannelMod := mw.AddDynamicMod(core.SpellModConfig{ + Kind: core.SpellMod_AllowCastWhileChanneling, + ClassMask: monk.MonkSpellEnvelopingMist, + }) + var soothingMist *core.Spell soothingMist = mw.RegisterSpell(core.SpellConfig{ ActionID: actionID, SpellSchool: core.SpellSchoolNature, ProcMask: core.ProcMaskSpellHealing, - Flags: core.SpellFlagHelpful | core.SpellFlagAPL, + Flags: core.SpellFlagHelpful | core.SpellFlagAPL | core.SpellFlagChanneled, ManaCost: core.ManaCostOptions{ BaseCostPercent: 1, @@ -50,7 +61,16 @@ func (mw *MistweaverMonk) registerSoothingMist() { Label: "Soothing Mist", OnExpire: func(aura *core.Aura, sim *core.Simulation) { surgingMistCastTimeMod.Deactivate() + surgingMistChannelMod.Deactivate() envelopingMistCastTimeMod.Deactivate() + envelopingMistChannelMod.Deactivate() + }, + OnGain: func(aura *core.Aura, sim *core.Simulation) { + surgingMistChannelMod.Activate() + surgingMistCastTimeMod.Activate() + envelopingMistChannelMod.Activate() + envelopingMistCastTimeMod.Activate() + }, }, NumberOfTicks: 9, @@ -70,7 +90,8 @@ func (mw *MistweaverMonk) registerSoothingMist() { OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { dot.CalcAndDealPeriodicSnapshotHealing(sim, target, dot.OutcomeTick) - + mw.SpendMana(sim, manaLoss, manaMetrics) + //Need to take 1% of mana on tick outcome := sim.Roll(1, 10) if outcome > 7 { mw.AddChi(sim, soothingMist, 1, chiMetrics) @@ -80,14 +101,14 @@ func (mw *MistweaverMonk) registerSoothingMist() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { //Currently target mistweaver only, will need to fix this + manaLoss = mw.MaxMana() * 0.01 + hot := spell.Hot(&mw.Unit) hot.Apply(sim) hot.TickOnce(sim) expiresAt := hot.ExpiresAt() - mw.AutoAttacks.StopMeleeUntil(sim, expiresAt, false) - surgingMistCastTimeMod.Activate() - envelopingMistCastTimeMod.Activate() - fmt.Print(surgingMistCastTimeMod.IsActive) + mw.AutoAttacks.StopMeleeUntil(sim, expiresAt) + }, }) diff --git a/sim/monk/mistweaver/surging_mist.go b/sim/monk/mistweaver/surging_mist.go index e6ea78006b..c7877b2f61 100644 --- a/sim/monk/mistweaver/surging_mist.go +++ b/sim/monk/mistweaver/surging_mist.go @@ -16,7 +16,7 @@ func (mw *MistweaverMonk) registerSurgingMist() { ActionID: actionID, SpellSchool: core.SpellSchoolNature, ProcMask: core.ProcMaskSpellHealing, - Flags: core.SpellFlagHelpful | core.SpellFlagAPL | core.SpellFlagCastWhileChanneling, + Flags: core.SpellFlagHelpful | core.SpellFlagAPL, ClassSpellMask: monk.MonkSpellSurgingMist, ManaCost: core.ManaCostOptions{ BaseCostPercent: 8.8, diff --git a/sim/monk/mistweaver/uplift.go b/sim/monk/mistweaver/uplift.go new file mode 100644 index 0000000000..a3a8ead85e --- /dev/null +++ b/sim/monk/mistweaver/uplift.go @@ -0,0 +1,45 @@ +package mistweaver + +import ( + "github.com/wowsims/mop/sim/core" +) + +func (mw *MistweaverMonk) registerUplift() { + actionID := core.ActionID{SpellID: 116670} + chiMetrics := mw.NewChiMetrics(actionID) + spellCoeff := 0.68 + + mw.RegisterSpell(core.SpellConfig{ + ActionID: actionID, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskSpellHealing, + Flags: core.SpellFlagHelpful | core.SpellFlagAPL, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + }, + DamageMultiplier: 1, + ThreatMultiplier: 1, + CritMultiplier: mw.DefaultCritMultiplier(), + ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { + return mw.GetChi() >= 2 + }, + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + success := false + for _, player := range sim.Raid.AllPlayerUnits { + hot := mw.renewingMist.Hot(player) + + if hot.IsActive() { + baseHealing := 0 + spellCoeff*spell.HealingPower(target) + spell.CalcAndDealHealing(sim, player, baseHealing, spell.OutcomeHealingCrit) + success = true + } + } + + if success { + mw.SpendChi(sim, 2, chiMetrics) + } + }, + }) +} From bc16a63ae855e7f38e851cae27f7058199bcf350 Mon Sep 17 00:00:00 2001 From: Patrick-Hogeveen Date: Sun, 29 Jun 2025 16:55:18 -0400 Subject: [PATCH 08/15] channel fix + monk core changes --- sim/core/apl.go | 7 ++++--- sim/core/spell.go | 2 +- sim/monk/monk.go | 5 +++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/sim/core/apl.go b/sim/core/apl.go index 8fceb05c2a..e7a3aa87e3 100644 --- a/sim/core/apl.go +++ b/sim/core/apl.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "slices" "time" "github.com/wowsims/mop/sim/core/proto" @@ -290,9 +291,9 @@ func (apl *APLRotation) DoNextAction(sim *Simulation) { } //Probably not the best solution, added so apl evaluates if a spell can be cast while channeling during runtime rather than on reset - //apl.allowCastWhileChanneling = slices.ContainsFunc(apl.unit.Spellbook, func(spell *Spell) bool { - // return spell.Flags.Matches(SpellFlagCastWhileChanneling) - //}) + apl.allowCastWhileChanneling = slices.ContainsFunc(apl.unit.Spellbook, func(spell *Spell) bool { + return spell.Flags.Matches(SpellFlagCastWhileChanneling) + }) if apl.unit.ChanneledDot != nil && !apl.allowCastWhileChanneling { return diff --git a/sim/core/spell.go b/sim/core/spell.go index f3de77ca17..ab0a48f73c 100644 --- a/sim/core/spell.go +++ b/sim/core/spell.go @@ -571,7 +571,7 @@ func (spell *Spell) CanCast(sim *Simulation, target *Unit) bool { } // While casting or channeling, no other action is possible - if (spell.Unit.Hardcast.Expires > sim.CurrentTime /*|| spell.Unit.IsChanneling(sim)*/) && !spell.Flags.Matches(SpellFlagCastWhileChanneling) { + if (spell.Unit.Hardcast.Expires > sim.CurrentTime || spell.Unit.IsChanneling(sim)) && !spell.Flags.Matches(SpellFlagCastWhileChanneling) { //if sim.Log != nil { // sim.Log("Cant cast because already casting/channeling") //} diff --git a/sim/monk/monk.go b/sim/monk/monk.go index da9a958b1e..ecf04de1ae 100644 --- a/sim/monk/monk.go +++ b/sim/monk/monk.go @@ -70,6 +70,11 @@ type Monk struct { ComboBreakerBlackoutKickAura *core.Aura ComboBreakerTigerPalmAura *core.Aura + MuscleMemoryBlackoutKickAura *core.Aura + MuscleMemoryTigerPalmAura *core.Aura + + SerpentZealAura *core.Aura + ChiSphereAura *core.Aura DampenHarmAura *core.Aura FortifyingBrewAura *core.Aura From 09f417eaf53111752af059ad40af33305b1fa431 Mon Sep 17 00:00:00 2001 From: Patrick-Hogeveen Date: Mon, 30 Jun 2025 16:49:02 -0400 Subject: [PATCH 09/15] Remove debug prints --- sim/monk/mistweaver/enveloping_mist.go | 4 ---- sim/monk/mistweaver/renewing_mist.go | 9 ++------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/sim/monk/mistweaver/enveloping_mist.go b/sim/monk/mistweaver/enveloping_mist.go index 1f0f816838..641f129b3f 100644 --- a/sim/monk/mistweaver/enveloping_mist.go +++ b/sim/monk/mistweaver/enveloping_mist.go @@ -1,7 +1,6 @@ package mistweaver import ( - "fmt" "time" "github.com/wowsims/mop/sim/core" @@ -37,8 +36,6 @@ func (mw *MistweaverMonk) registerEnvelopingMist() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - fmt.Print("Envelop\n") - mw.SpendChi(sim, 3, chiMetrics) spell.RelatedDotSpell.Cast(sim, &mw.Unit) @@ -76,7 +73,6 @@ func (mw *MistweaverMonk) registerEnvelopingMist() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { //Targets only mw currently - fmt.Print("related mist \n") hot := spell.Hot(&mw.Unit) hot.Apply(sim) diff --git a/sim/monk/mistweaver/renewing_mist.go b/sim/monk/mistweaver/renewing_mist.go index 6549005195..5a27cb9c52 100644 --- a/sim/monk/mistweaver/renewing_mist.go +++ b/sim/monk/mistweaver/renewing_mist.go @@ -1,7 +1,6 @@ package mistweaver import ( - "fmt" "time" "github.com/wowsims/mop/sim/core" @@ -30,10 +29,6 @@ func (mw *MistweaverMonk) registerRenewingMist() { return success } - //var renewingMistSpread *core.Spell - var renewingMist *core.Spell - fmt.Print(renewingMist) - mw.renewingMist = mw.RegisterSpell(core.SpellConfig{ ActionID: actionID, SpellSchool: core.SpellSchoolNature, @@ -73,7 +68,7 @@ func (mw *MistweaverMonk) registerRenewingMist() { //Has to jump to two more targets after initial cast if charges > 1 && dot.RemainingTicks() > 1 { - fmt.Print("Checking\n") + success := mistHandler(sim, dot.Spell) if success { @@ -85,7 +80,7 @@ func (mw *MistweaverMonk) registerRenewingMist() { }, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - fmt.Print("Renew\n") + success := mistHandler(sim, spell) if success { From 894f836e11e6d77bc0af60d69ef3cd7f4184be4c Mon Sep 17 00:00:00 2001 From: Patrick-Hogeveen Date: Tue, 1 Jul 2025 15:15:26 -0400 Subject: [PATCH 10/15] Fix muscle memory passive --- sim/monk/blackout_kick.go | 2 +- sim/monk/mistweaver/passives.go | 66 +++++++++++++-------------------- sim/monk/monk.go | 3 +- sim/monk/tiger_palm.go | 6 +++ 4 files changed, 34 insertions(+), 43 deletions(-) diff --git a/sim/monk/blackout_kick.go b/sim/monk/blackout_kick.go index e68ba1677e..51a2ab12fe 100644 --- a/sim/monk/blackout_kick.go +++ b/sim/monk/blackout_kick.go @@ -94,7 +94,7 @@ func (monk *Monk) registerBlackoutKick() { monk.SpendChi(sim, chiCost, chiMetrics) } - if monk.MuscleMemoryBlackoutKickAura.IsActive() { + if monk.MuscleMemoryAura.IsActive() { result.Damage += result.Damage * 1.5 monk.AddMana(sim, monk.MaxMana()*0.04, manaMetrics) } diff --git a/sim/monk/mistweaver/passives.go b/sim/monk/mistweaver/passives.go index 654437f733..735cd0f690 100644 --- a/sim/monk/mistweaver/passives.go +++ b/sim/monk/mistweaver/passives.go @@ -62,44 +62,30 @@ func (mw *MistweaverMonk) registerSerpentsZeal() { } func (mw *MistweaverMonk) registerMuscleMemory() { - registerMuscleMemoryAuraAndTrigger := func(labelSuffix string, spellID int32, triggerSpellMask int64) *core.Aura { - aura := mw.RegisterAura(core.Aura{ - Label: fmt.Sprintf("Muscle Memory %s %s", labelSuffix, mw.Label), - ActionID: core.ActionID{SpellID: 139597}, - Duration: time.Second * 15, - - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if !spell.Matches(triggerSpellMask) || !result.Landed() { - return - } - aura.Deactivate(sim) - }, - }) - - core.MakeProcTriggerAura(&mw.Unit, core.ProcTrigger{ - Name: fmt.Sprintf("Muscle Memory: %s Trigger %s", labelSuffix, mw.Label), - Callback: core.CallbackOnSpellHitDealt, - ClassSpellMask: monk.MonkSpellJab, - Outcome: core.OutcomeLanded, - ProcChance: 1, - - Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - aura.Activate(sim) - }, - }) - - return aura - } - - mw.MuscleMemoryBlackoutKickAura = registerMuscleMemoryAuraAndTrigger( - "Blackout Kick", - 116768, - monk.MonkSpellBlackoutKick, - ) - - mw.MuscleMemoryTigerPalmAura = registerMuscleMemoryAuraAndTrigger( - "Tiger Palm", - 118864, - monk.MonkSpellTigerPalm, - ) + + mw.MuscleMemoryAura = mw.RegisterAura(core.Aura{ + Label: fmt.Sprintf("Muscle Memory %s", mw.Label), + ActionID: core.ActionID{SpellID: 139597}, + Duration: time.Second * 15, + + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if (!spell.Matches(100787) && !spell.Matches(100784)) || !result.Landed() { + return + } + aura.Deactivate(sim) + }, + }) + + core.MakeProcTriggerAura(&mw.Unit, core.ProcTrigger{ + Name: fmt.Sprintf("Muscle Memory: Trigger %s", mw.Label), + Callback: core.CallbackOnSpellHitDealt, + ClassSpellMask: monk.MonkSpellJab, + Outcome: core.OutcomeLanded, + ProcChance: 1, + + Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + mw.MuscleMemoryAura.Activate(sim) + }, + }) + } diff --git a/sim/monk/monk.go b/sim/monk/monk.go index 140702ebf6..bbc4a06227 100644 --- a/sim/monk/monk.go +++ b/sim/monk/monk.go @@ -70,8 +70,7 @@ type Monk struct { ComboBreakerBlackoutKickAura *core.Aura ComboBreakerTigerPalmAura *core.Aura - MuscleMemoryBlackoutKickAura *core.Aura - MuscleMemoryTigerPalmAura *core.Aura + MuscleMemoryAura *core.Aura SerpentZealAura *core.Aura diff --git a/sim/monk/tiger_palm.go b/sim/monk/tiger_palm.go index ccba400acb..f5c7f3aaa7 100644 --- a/sim/monk/tiger_palm.go +++ b/sim/monk/tiger_palm.go @@ -85,6 +85,7 @@ func (monk *Monk) registerTigerPalm() { chiMetrics := monk.NewChiMetrics(tigerPalmActionID) isBrewmaster := monk.Spec == proto.Spec_SpecBrewmasterMonk chiCost := int32(1) + manaMetrics := monk.NewManaMetrics(tigerPalmActionID) tigerPowerBuff := monk.RegisterAura(tigerPowerBuffConfig(monk, false)) @@ -124,6 +125,11 @@ func (monk *Monk) registerTigerPalm() { } else { monk.SpendChi(sim, chiCost, chiMetrics) } + + if monk.MuscleMemoryAura.IsActive() { + result.Damage += result.Damage * 1.5 + monk.AddMana(sim, monk.MaxMana()*0.04, manaMetrics) + } } } From 5563765ab0346b82e6cd15c326d6f9feca6f5449 Mon Sep 17 00:00:00 2001 From: Patrick-Hogeveen Date: Wed, 2 Jul 2025 17:13:38 -0400 Subject: [PATCH 11/15] Vital mists implementation --- sim/monk/mistweaver/passives.go | 54 ++++++++++++++++++++++++++++++++- sim/monk/monk.go | 2 ++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/sim/monk/mistweaver/passives.go b/sim/monk/mistweaver/passives.go index 735cd0f690..d682dba5ff 100644 --- a/sim/monk/mistweaver/passives.go +++ b/sim/monk/mistweaver/passives.go @@ -11,6 +11,58 @@ import ( func (mw *MistweaverMonk) registerPassives() { mw.registerMuscleMemory() mw.registerSerpentsZeal() + mw.registerVitalMists() +} + +func (mw *MistweaverMonk) registerVitalMists() { + vmManaCostMod := mw.AddDynamicMod(core.SpellModConfig{ + ClassMask: monk.MonkSpellSurgingMist, + FloatValue: -0.2, + Kind: core.SpellMod_PowerCost_Pct, + }) + + vmCastTimeMod := mw.AddDynamicMod(core.SpellModConfig{ + ClassMask: monk.MonkSpellSurgingMist, + FloatValue: -0.2, + Kind: core.SpellMod_CastTime_Pct, + }) + + mw.VitalMistsAura = mw.RegisterAura(core.Aura{ + Label: "Vital Mists", + ActionID: core.ActionID{SpellID: 118674}, + Duration: time.Second * 30, + MaxStacks: 5, + OnStacksChange: func(aura *core.Aura, sim *core.Simulation, oldStacks int32, newStacks int32) { + vmCastTimeMod.UpdateFloatValue(float64(newStacks) * -0.2) + vmCastTimeMod.Activate() + vmManaCostMod.UpdateFloatValue(core.TernaryFloat64(newStacks == 5, -2.0, float64(newStacks)*-0.2)) + vmManaCostMod.Activate() + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + vmCastTimeMod.Deactivate() + vmManaCostMod.Deactivate() + }, + OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { + if !spell.Matches(monk.MonkSpellSurgingMist) { + return + } + + mw.VitalMistsAura.Deactivate(sim) + }, + }) + + core.MakeProcTriggerAura(&mw.Unit, core.ProcTrigger{ + Name: "Vital Mists: Tiger Palm Trigger", + Callback: core.CallbackOnSpellHitDealt, + ClassSpellMask: monk.MonkSpellTigerPalm, + Outcome: core.OutcomeLanded, + ProcChance: 1, + + Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + mw.VitalMistsAura.Activate(sim) + mw.VitalMistsAura.AddStack(sim) + }, + }) } func (mw *MistweaverMonk) registerSerpentsZeal() { @@ -69,7 +121,7 @@ func (mw *MistweaverMonk) registerMuscleMemory() { Duration: time.Second * 15, OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if (!spell.Matches(100787) && !spell.Matches(100784)) || !result.Landed() { + if (!spell.Matches(monk.MonkSpellBlackoutKick) && !spell.Matches(monk.MonkSpellTigerPalm)) || !result.Landed() { return } aura.Deactivate(sim) diff --git a/sim/monk/monk.go b/sim/monk/monk.go index bbc4a06227..276059c56f 100644 --- a/sim/monk/monk.go +++ b/sim/monk/monk.go @@ -74,6 +74,8 @@ type Monk struct { SerpentZealAura *core.Aura + VitalMistsAura *core.Aura + ChiSphereAura *core.Aura DampenHarmAura *core.Aura FortifyingBrewAura *core.Aura From 9b5d21a48a8f4dc444db246e0e4dd56155cc40f6 Mon Sep 17 00:00:00 2001 From: Patrick-Hogeveen Date: Wed, 2 Jul 2025 17:26:40 -0400 Subject: [PATCH 12/15] Fix spell_mod function placements in code --- sim/core/spell_mod.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sim/core/spell_mod.go b/sim/core/spell_mod.go index 5fac407d5a..8e01e1a862 100644 --- a/sim/core/spell_mod.go +++ b/sim/core/spell_mod.go @@ -684,6 +684,10 @@ func applyAllowCastWhileMoving(mod *SpellMod, spell *Spell) { spell.Flags |= SpellFlagCanCastWhileMoving } +func removeAllowCastWhileMoving(mod *SpellMod, spell *Spell) { + spell.Flags ^= SpellFlagCanCastWhileMoving +} + func applyAllowCastWhileChanneling(mod *SpellMod, spell *Spell) { spell.Flags |= SpellFlagCastWhileChanneling } @@ -692,10 +696,6 @@ func removeAllowCastWhileChanneling(mod *SpellMod, spell *Spell) { spell.Flags ^= SpellFlagCastWhileChanneling } -func removeAllowCastWhileMoving(mod *SpellMod, spell *Spell) { - spell.Flags ^= SpellFlagCanCastWhileMoving -} - func applyBonusSpellPowerFlat(mod *SpellMod, spell *Spell) { spell.BonusSpellPower += mod.floatValue } From 258aa4cb7c1b5cc78f2584edf3cf7697ceed529e Mon Sep 17 00:00:00 2001 From: Patrick-Hogeveen Date: Fri, 4 Jul 2025 13:49:05 -0400 Subject: [PATCH 13/15] Soothing mist aura consolidation --- sim/monk/mistweaver/soothing_mist.go | 33 ++++++++-------------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/sim/monk/mistweaver/soothing_mist.go b/sim/monk/mistweaver/soothing_mist.go index 7712be52f7..110b1404f9 100644 --- a/sim/monk/mistweaver/soothing_mist.go +++ b/sim/monk/mistweaver/soothing_mist.go @@ -14,26 +14,15 @@ func (mw *MistweaverMonk) registerSoothingMist() { manaMetrics := mw.NewManaMetrics(actionID) manaLoss := 0.0 - surgingMistCastTimeMod := mw.AddDynamicMod(core.SpellModConfig{ + mistCastTimeMod := mw.AddDynamicMod(core.SpellModConfig{ Kind: core.SpellMod_CastTime_Pct, FloatValue: -1, - ClassMask: monk.MonkSpellSurgingMist, + ClassMask: monk.MonkSpellSurgingMist | monk.MonkSpellEnvelopingMist, }) - surgingMistChannelMod := mw.AddDynamicMod(core.SpellModConfig{ + mistChannelMod := mw.AddDynamicMod(core.SpellModConfig{ Kind: core.SpellMod_AllowCastWhileChanneling, - ClassMask: monk.MonkSpellSurgingMist, - }) - - envelopingMistCastTimeMod := mw.AddDynamicMod(core.SpellModConfig{ - Kind: core.SpellMod_CastTime_Pct, - FloatValue: -1, - ClassMask: monk.MonkSpellEnvelopingMist, - }) - - envelopingMistChannelMod := mw.AddDynamicMod(core.SpellModConfig{ - Kind: core.SpellMod_AllowCastWhileChanneling, - ClassMask: monk.MonkSpellEnvelopingMist, + ClassMask: monk.MonkSpellSurgingMist | monk.MonkSpellEnvelopingMist, }) var soothingMist *core.Spell @@ -42,7 +31,7 @@ func (mw *MistweaverMonk) registerSoothingMist() { ActionID: actionID, SpellSchool: core.SpellSchoolNature, ProcMask: core.ProcMaskSpellHealing, - Flags: core.SpellFlagHelpful | core.SpellFlagAPL | core.SpellFlagChanneled, + Flags: core.SpellFlagHelpful | core.SpellFlagAPL | core.SpellFlagChanneled | core.SpellFlagCastWhileChanneling, ManaCost: core.ManaCostOptions{ BaseCostPercent: 1, @@ -60,16 +49,12 @@ func (mw *MistweaverMonk) registerSoothingMist() { Aura: core.Aura{ Label: "Soothing Mist", OnExpire: func(aura *core.Aura, sim *core.Simulation) { - surgingMistCastTimeMod.Deactivate() - surgingMistChannelMod.Deactivate() - envelopingMistCastTimeMod.Deactivate() - envelopingMistChannelMod.Deactivate() + mistCastTimeMod.Deactivate() + mistChannelMod.Deactivate() }, OnGain: func(aura *core.Aura, sim *core.Simulation) { - surgingMistChannelMod.Activate() - surgingMistCastTimeMod.Activate() - envelopingMistChannelMod.Activate() - envelopingMistCastTimeMod.Activate() + mistCastTimeMod.Activate() + mistChannelMod.Activate() }, }, From b4dcd509fb5870677c6197e72ec698d867222457 Mon Sep 17 00:00:00 2001 From: Patrick-Hogeveen Date: Thu, 10 Jul 2025 17:16:09 -0400 Subject: [PATCH 14/15] Changes / fixes based on pr draft comments --- sim/core/apl.go | 10 +--------- sim/core/cast.go | 2 +- sim/core/spell.go | 2 +- sim/core/spell_queueing.go | 2 +- sim/core/unit.go | 2 +- sim/monk/jab.go | 4 +--- sim/monk/mistweaver/enveloping_mist.go | 18 +++++++++++------ sim/monk/mistweaver/mana_tea.go | 7 +++---- sim/monk/mistweaver/mistweaver.go | 4 ++-- sim/monk/mistweaver/passives.go | 2 +- sim/monk/mistweaver/renewing_mist.go | 8 +------- sim/monk/mistweaver/soothing_mist.go | 27 +++++++++++++++----------- sim/monk/mistweaver/surging_mist.go | 10 ++++------ sim/monk/mistweaver/uplift.go | 6 +++--- sim/monk/stances.go | 2 +- 15 files changed, 49 insertions(+), 57 deletions(-) diff --git a/sim/core/apl.go b/sim/core/apl.go index e7a3aa87e3..35e12c6ca0 100644 --- a/sim/core/apl.go +++ b/sim/core/apl.go @@ -2,7 +2,6 @@ package core import ( "fmt" - "slices" "time" "github.com/wowsims/mop/sim/core/proto" @@ -24,8 +23,6 @@ type APLRotation struct { // If true, can recast channel when interrupted. allowChannelRecastOnInterrupt bool - //Checking for cast-while-channeling spells to allow the APL to not evaluate during channels unless absolutely necessary - allowCastWhileChanneling bool // Used inside of actions/value to determine whether they will occur during the prepull or regular rotation. parsingPrepull bool @@ -290,12 +287,7 @@ func (apl *APLRotation) DoNextAction(sim *Simulation) { return } - //Probably not the best solution, added so apl evaluates if a spell can be cast while channeling during runtime rather than on reset - apl.allowCastWhileChanneling = slices.ContainsFunc(apl.unit.Spellbook, func(spell *Spell) bool { - return spell.Flags.Matches(SpellFlagCastWhileChanneling) - }) - - if apl.unit.ChanneledDot != nil && !apl.allowCastWhileChanneling { + if (apl.unit.ChanneledDot != nil) && !apl.unit.ChanneledDot.Spell.Flags.Matches(SpellFlagCastWhileChanneling) { return } diff --git a/sim/core/cast.go b/sim/core/cast.go index d42d0dc31f..698e947f11 100644 --- a/sim/core/cast.go +++ b/sim/core/cast.go @@ -146,7 +146,7 @@ func (spell *Spell) makeCastFunc(config CastConfig) CastSuccessFunc { return spell.castFailureHelper(sim, "casting/channeling %v for %s, curTime = %s", hc.ActionID, hc.Expires-sim.CurrentTime, sim.CurrentTime) } - if dot := spell.Unit.ChanneledDot; spell.Unit.IsChanneling(sim) && !spell.Flags.Matches(SpellFlagCastWhileChanneling) && (spell.Unit.Rotation.interruptChannelIf == nil || !spell.Unit.Rotation.interruptChannelIf.GetBool(sim)) { + if dot := spell.Unit.ChanneledDot; spell.Unit.IsChanneling() && !spell.Flags.Matches(SpellFlagCastWhileChanneling) && (spell.Unit.Rotation.interruptChannelIf == nil || !spell.Unit.Rotation.interruptChannelIf.GetBool(sim)) { return spell.castFailureHelper(sim, "channeling %v for %s, curTime = %s", dot.ActionID, dot.expires-sim.CurrentTime, sim.CurrentTime) } diff --git a/sim/core/spell.go b/sim/core/spell.go index ab0a48f73c..5445e7c4ec 100644 --- a/sim/core/spell.go +++ b/sim/core/spell.go @@ -571,7 +571,7 @@ func (spell *Spell) CanCast(sim *Simulation, target *Unit) bool { } // While casting or channeling, no other action is possible - if (spell.Unit.Hardcast.Expires > sim.CurrentTime || spell.Unit.IsChanneling(sim)) && !spell.Flags.Matches(SpellFlagCastWhileChanneling) { + if (spell.Unit.Hardcast.Expires > sim.CurrentTime || spell.Unit.IsChanneling()) && !spell.Flags.Matches(SpellFlagCastWhileChanneling) { //if sim.Log != nil { // sim.Log("Cant cast because already casting/channeling") //} diff --git a/sim/core/spell_queueing.go b/sim/core/spell_queueing.go index e401f292e5..5047104992 100644 --- a/sim/core/spell_queueing.go +++ b/sim/core/spell_queueing.go @@ -86,7 +86,7 @@ func (spell *Spell) CanQueue(sim *Simulation, target *Unit) bool { } // Apply SQW leniency to any pending hardcasts - if spell.Unit.Hardcast.Expires > sim.CurrentTime+MaxSpellQueueWindow { + if (spell.Unit.Hardcast.Expires > sim.CurrentTime+MaxSpellQueueWindow || spell.Unit.IsChanneling()) && !spell.Flags.Matches(SpellFlagCastWhileChanneling) { return false } diff --git a/sim/core/unit.go b/sim/core/unit.go index cface78b8f..33b5c056dd 100644 --- a/sim/core/unit.go +++ b/sim/core/unit.go @@ -471,7 +471,7 @@ func (unit *Unit) InitialCastSpeed() float64 { return unit.initialCastSpeed } -func (unit *Unit) IsChanneling(sim *Simulation) bool { +func (unit *Unit) IsChanneling() bool { return unit.ChanneledDot != nil } diff --git a/sim/monk/jab.go b/sim/monk/jab.go index 75cec0dff3..8124c21a7a 100644 --- a/sim/monk/jab.go +++ b/sim/monk/jab.go @@ -73,8 +73,7 @@ func (monk *Monk) registerJab() { Refund: 0.8, }, ManaCost: core.ManaCostOptions{ - //This is wrong? But works? - BaseCostPercent: core.TernaryFloat64(monk.StanceMatches(WiseSerpent), 0, 8), + BaseCostPercent: 6, //Lowed from 8 based on patch notes }, Cast: core.CastConfig{ @@ -88,7 +87,6 @@ func (monk *Monk) registerJab() { baseDamage := monk.CalculateMonkStrikeDamage(sim, spell) result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) - if result.Landed() { chiGain := core.TernaryInt32(monk.StanceMatches(FierceTiger), 2, 1) monk.AddChi(sim, spell, chiGain, chiMetrics) diff --git a/sim/monk/mistweaver/enveloping_mist.go b/sim/monk/mistweaver/enveloping_mist.go index 641f129b3f..d993269015 100644 --- a/sim/monk/mistweaver/enveloping_mist.go +++ b/sim/monk/mistweaver/enveloping_mist.go @@ -1,6 +1,7 @@ package mistweaver import ( + "fmt" "time" "github.com/wowsims/mop/sim/core" @@ -12,7 +13,7 @@ func (mw *MistweaverMonk) registerEnvelopingMist() { chiMetrics := mw.NewChiMetrics(actionID) spellCoeff := 0.45 - mw.enevelopingMist = mw.RegisterSpell(core.SpellConfig{ + mw.envelopingMist = mw.RegisterSpell(core.SpellConfig{ ActionID: actionID, SpellSchool: core.SpellSchoolNature, ProcMask: core.ProcMaskSpellHealing, @@ -30,19 +31,19 @@ func (mw *MistweaverMonk) registerEnvelopingMist() { ThreatMultiplier: 1, CritMultiplier: mw.DefaultCritMultiplier(), - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { + ExtraCastCondition: func(_ *core.Simulation, _ *core.Unit) bool { return mw.GetChi() >= 3 }, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { mw.SpendChi(sim, 3, chiMetrics) - spell.RelatedDotSpell.Cast(sim, &mw.Unit) + spell.RelatedDotSpell.Cast(sim, target) }, }) - mw.enevelopingMist.RelatedDotSpell = mw.RegisterSpell(core.SpellConfig{ + mw.envelopingMist.RelatedDotSpell = mw.RegisterSpell(core.SpellConfig{ ActionID: actionID, SpellSchool: core.SpellSchoolNature, ProcMask: core.ProcMaskSpellHealing, @@ -66,14 +67,19 @@ func (mw *MistweaverMonk) registerEnvelopingMist() { }, 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) { - //Targets only mw currently - hot := spell.Hot(&mw.Unit) + hot := spell.Hot(target) + //Will probably have to remove enemy units as options for friendly spells? + if target.Type == core.EnemyUnit { + fmt.Printf("Attemping to cast Enveloping mist on enemy: %v\n", target.Label) + return + } hot.Apply(sim) diff --git a/sim/monk/mistweaver/mana_tea.go b/sim/monk/mistweaver/mana_tea.go index ecc6510935..1878a91da2 100644 --- a/sim/monk/mistweaver/mana_tea.go +++ b/sim/monk/mistweaver/mana_tea.go @@ -1,7 +1,6 @@ package mistweaver import ( - "math" "time" "github.com/wowsims/mop/sim/core" @@ -34,7 +33,7 @@ func (mw *MistweaverMonk) registerManaTea() { Label: "Mana Tea Stacks" + mw.Label, ActionID: stackActionID, Duration: time.Hour, - MaxStacks: 10, + MaxStacks: 20, }) mw.Monk.RegisterOnNewBrewStacks(func(sim *core.Simulation, stacksToAdd int32) { @@ -42,7 +41,7 @@ func (mw *MistweaverMonk) registerManaTea() { procChance := mw.GetStat(stats.SpellCritPercent) - if sim.Proc(math.Mod(procChance, 1), "Mana Tea") { + if sim.Proc(procChance/100, "Mana Tea") { stacksToAdd += 1 } @@ -75,7 +74,7 @@ func (mw *MistweaverMonk) registerManaTea() { OnTick: func(sim *core.Simulation, target *core.Unit, spell *core.Dot) { mw.AddMana(sim, manaPerTick, manaMetrics) - mw.ManaTeaStackAura.SetStacks(sim, mw.ManaTeaStackAura.GetStacks()-1) + mw.ManaTeaStackAura.RemoveStack(sim) }, }, diff --git a/sim/monk/mistweaver/mistweaver.go b/sim/monk/mistweaver/mistweaver.go index bf6364ff05..e24a4a945c 100644 --- a/sim/monk/mistweaver/mistweaver.go +++ b/sim/monk/mistweaver/mistweaver.go @@ -53,8 +53,8 @@ type MistweaverMonk struct { ManaTeaStackAura *core.Aura - renewingMist *core.Spell - enevelopingMist *core.Spell + renewingMist *core.Spell + envelopingMist *core.Spell JadeSerpentAura *core.Aura //May move this to monk as both ww and mw use this diff --git a/sim/monk/mistweaver/passives.go b/sim/monk/mistweaver/passives.go index d682dba5ff..828d18e1ae 100644 --- a/sim/monk/mistweaver/passives.go +++ b/sim/monk/mistweaver/passives.go @@ -121,7 +121,7 @@ func (mw *MistweaverMonk) registerMuscleMemory() { Duration: time.Second * 15, OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if (!spell.Matches(monk.MonkSpellBlackoutKick) && !spell.Matches(monk.MonkSpellTigerPalm)) || !result.Landed() { + if !spell.Matches(monk.MonkSpellBlackoutKick|monk.MonkSpellTigerPalm) || !result.Landed() { return } aura.Deactivate(sim) diff --git a/sim/monk/mistweaver/renewing_mist.go b/sim/monk/mistweaver/renewing_mist.go index 5a27cb9c52..caf46639a1 100644 --- a/sim/monk/mistweaver/renewing_mist.go +++ b/sim/monk/mistweaver/renewing_mist.go @@ -85,16 +85,10 @@ func (mw *MistweaverMonk) registerRenewingMist() { if success { charges = 3 - chiGain := int32(1) //core.TernaryInt32(monk.StanceMatches(FierceTiger), 2, 1) + chiGain := int32(1) mw.AddChi(sim, spell, chiGain, chiMetrics) } - //hot := spell.Hot(target) - //spell.Hot(spell.Unit).Apply(sim) - //if !hot.IsActive() { //Error? - // hot.Apply(sim) - //} - }, }) diff --git a/sim/monk/mistweaver/soothing_mist.go b/sim/monk/mistweaver/soothing_mist.go index 110b1404f9..918ef1abc2 100644 --- a/sim/monk/mistweaver/soothing_mist.go +++ b/sim/monk/mistweaver/soothing_mist.go @@ -1,6 +1,7 @@ package mistweaver import ( + "fmt" "time" "github.com/wowsims/mop/sim/core" @@ -25,9 +26,7 @@ func (mw *MistweaverMonk) registerSoothingMist() { ClassMask: monk.MonkSpellSurgingMist | monk.MonkSpellEnvelopingMist, }) - var soothingMist *core.Spell - - soothingMist = mw.RegisterSpell(core.SpellConfig{ + mw.RegisterSpell(core.SpellConfig{ ActionID: actionID, SpellSchool: core.SpellSchoolNature, ProcMask: core.ProcMaskSpellHealing, @@ -63,23 +62,25 @@ func (mw *MistweaverMonk) registerSoothingMist() { AffectedByCastSpeed: true, //Not sure HasteReducesDuration: true, //Not sure OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, _ bool) { - envelopingActive := mw.enevelopingMist.RelatedDotSpell.Hot(target).IsActive() + dot.SnapshotBaseDamage = 0 + mw.CalcScalingSpellDmg(spellCoeff) multiplier := dot.Spell.CasterHealingMultiplier() - if envelopingActive { - multiplier += +0.3 - } dot.SnapshotAttackerMultiplier = multiplier }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + envelopingActive := mw.envelopingMist.RelatedDotSpell.Hot(target).IsActive() + if envelopingActive { + dot.SnapshotAttackerMultiplier = 1.3 + } else { + dot.SnapshotAttackerMultiplier = 1 + } dot.CalcAndDealPeriodicSnapshotHealing(sim, target, dot.OutcomeTick) mw.SpendMana(sim, manaLoss, manaMetrics) //Need to take 1% of mana on tick - outcome := sim.Roll(1, 10) - if outcome > 7 { - mw.AddChi(sim, soothingMist, 1, chiMetrics) + if sim.Proc(0.3, "Soothing Mist Chi") { + mw.AddChi(sim, dot.Spell, 1, chiMetrics) } }, }, @@ -88,7 +89,11 @@ func (mw *MistweaverMonk) registerSoothingMist() { //Currently target mistweaver only, will need to fix this manaLoss = mw.MaxMana() * 0.01 - hot := spell.Hot(&mw.Unit) + hot := spell.Hot(target) + if target.Type == core.EnemyUnit { + fmt.Printf("Attemping to cast Enveloping mist on enemy: %v\n", target.Label) + return + } hot.Apply(sim) hot.TickOnce(sim) expiresAt := hot.ExpiresAt() diff --git a/sim/monk/mistweaver/surging_mist.go b/sim/monk/mistweaver/surging_mist.go index c7877b2f61..d3f96e167d 100644 --- a/sim/monk/mistweaver/surging_mist.go +++ b/sim/monk/mistweaver/surging_mist.go @@ -10,7 +10,6 @@ import ( func (mw *MistweaverMonk) registerSurgingMist() { actionID := core.ActionID{SpellID: 116694} chiMetrics := mw.NewChiMetrics(actionID) - spellCoeff := 1.8 mw.RegisterSpell(core.SpellConfig{ ActionID: actionID, @@ -19,7 +18,7 @@ func (mw *MistweaverMonk) registerSurgingMist() { Flags: core.SpellFlagHelpful | core.SpellFlagAPL, ClassSpellMask: monk.MonkSpellSurgingMist, ManaCost: core.ManaCostOptions{ - BaseCostPercent: 8.8, + BaseCostPercent: 7.65, //Changed based on patch notes }, Cast: core.CastConfig{ DefaultCast: core.Cast{ @@ -31,12 +30,11 @@ func (mw *MistweaverMonk) registerSurgingMist() { DamageMultiplier: 1, ThreatMultiplier: 1, CritMultiplier: mw.DefaultCritMultiplier(), + BonusCoefficient: 1.8, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseHealing := 19630 + spellCoeff*spell.HealingPower(target) - //Hardcoded to heal the player for now - spell.CalcAndDealHealing(sim, &mw.Unit, baseHealing, spell.OutcomeHealingCrit) - chiGain := int32(1) //core.TernaryInt32(monk.StanceMatches(FierceTiger), 2, 1) + spell.CalcAndDealHealing(sim, target, 17242, spell.OutcomeHealingCrit) + chiGain := int32(1) mw.AddChi(sim, spell, chiGain, chiMetrics) }, }) diff --git a/sim/monk/mistweaver/uplift.go b/sim/monk/mistweaver/uplift.go index a3a8ead85e..7e3e036117 100644 --- a/sim/monk/mistweaver/uplift.go +++ b/sim/monk/mistweaver/uplift.go @@ -7,7 +7,6 @@ import ( func (mw *MistweaverMonk) registerUplift() { actionID := core.ActionID{SpellID: 116670} chiMetrics := mw.NewChiMetrics(actionID) - spellCoeff := 0.68 mw.RegisterSpell(core.SpellConfig{ ActionID: actionID, @@ -22,6 +21,7 @@ func (mw *MistweaverMonk) registerUplift() { DamageMultiplier: 1, ThreatMultiplier: 1, CritMultiplier: mw.DefaultCritMultiplier(), + BonusCoefficient: 0.68, ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { return mw.GetChi() >= 2 }, @@ -31,8 +31,8 @@ func (mw *MistweaverMonk) registerUplift() { hot := mw.renewingMist.Hot(player) if hot.IsActive() { - baseHealing := 0 + spellCoeff*spell.HealingPower(target) - spell.CalcAndDealHealing(sim, player, baseHealing, spell.OutcomeHealingCrit) + + spell.CalcAndDealHealing(sim, player, 0, spell.OutcomeHealingCrit) success = true } } diff --git a/sim/monk/stances.go b/sim/monk/stances.go index d53abb6f02..21c71f131a 100644 --- a/sim/monk/stances.go +++ b/sim/monk/stances.go @@ -111,7 +111,7 @@ func (monk *Monk) registerStanceOfTheWiseSerpent(stanceCD *core.Timer) { ProcMask: core.ProcMaskSpellHealing, Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, - DamageMultiplier: 0.25, + DamageMultiplier: 0.42, //Changed from 25% to 42% based on patch notes ThreatMultiplier: 1, CritMultiplier: monk.DefaultCritMultiplier(), From eb1e58c28a0e0e53ab407146ed4a897e94ed90a7 Mon Sep 17 00:00:00 2001 From: Patrick-Hogeveen Date: Mon, 14 Jul 2025 16:03:47 -0400 Subject: [PATCH 15/15] Mistweaver aura updates --- sim/monk/monk.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sim/monk/monk.go b/sim/monk/monk.go index 276059c56f..639b33d109 100644 --- a/sim/monk/monk.go +++ b/sim/monk/monk.go @@ -70,11 +70,11 @@ type Monk struct { ComboBreakerBlackoutKickAura *core.Aura ComboBreakerTigerPalmAura *core.Aura + // Mistweaver MuscleMemoryAura *core.Aura - - SerpentZealAura *core.Aura - - VitalMistsAura *core.Aura + SerpentZealAura *core.Aura + VitalMistsAura *core.Aura + ThunderFocusTea *core.Aura ChiSphereAura *core.Aura DampenHarmAura *core.Aura