diff --git a/sim/shaman/bloodlust.go b/sim/shaman/bloodlust.go index 3e63f43d90..73dcebdf2d 100644 --- a/sim/shaman/bloodlust.go +++ b/sim/shaman/bloodlust.go @@ -12,9 +12,6 @@ func (shaman *Shaman) BloodlustActionID() core.ActionID { } func (shaman *Shaman) registerBloodlustCD() { - if !shaman.SelfBuffs.Bloodlust && !shaman.IsUsingAPL { - return - } actionID := shaman.BloodlustActionID() blAuras := []*core.Aura{} @@ -24,7 +21,7 @@ func (shaman *Shaman) registerBloodlustCD() { } } - bloodlustSpell := shaman.RegisterSpell(core.SpellConfig{ + shaman.RegisterSpell(core.SpellConfig{ ActionID: actionID, Flags: core.SpellFlagAPL, @@ -72,12 +69,4 @@ func (shaman *Shaman) registerBloodlustCD() { } }, }) - - if !shaman.IsUsingAPL { - shaman.AddMajorCooldown(core.MajorCooldown{ - Spell: bloodlustSpell, - Priority: core.CooldownPriorityBloodlust, - Type: core.CooldownTypeDPS, - }) - } } diff --git a/sim/shaman/elemental/elemental.go b/sim/shaman/elemental/elemental.go index 3e734f02d6..aa4a5e1bfd 100644 --- a/sim/shaman/elemental/elemental.go +++ b/sim/shaman/elemental/elemental.go @@ -32,37 +32,16 @@ func NewElementalShaman(character *core.Character, options *proto.Player) *Eleme Shield: eleShamOptions.Options.Shield, } - if eleShamOptions.Rotation.Bloodlust != proto.ElementalShaman_Rotation_UnsetBloodlust { - selfBuffs.Bloodlust = eleShamOptions.Rotation.Bloodlust == proto.ElementalShaman_Rotation_UseBloodlust - } - totems := &proto.ShamanTotems{} if eleShamOptions.Options.Totems != nil { totems = eleShamOptions.Options.Totems totems.UseFireMcd = true // Control fire totems as MCD. } - var rotation Rotation - - switch eleShamOptions.Rotation.Type { - case proto.ElementalShaman_Rotation_Adaptive: - rotation = NewAdaptiveRotation(eleShamOptions.Rotation) - case proto.ElementalShaman_Rotation_Manual: - rotation = NewManualRotation(eleShamOptions.Rotation) - default: - rotation = NewAdaptiveRotation(eleShamOptions.Rotation) - } - - inRange := eleShamOptions.Rotation.InThunderstormRange - if eleShamOptions.Options.ThunderstormRange != proto.ElementalShaman_Options_UnsetTSRange { - inRange = eleShamOptions.Options.ThunderstormRange == proto.ElementalShaman_Options_TSInRange - } + inRange := eleShamOptions.Options.ThunderstormRange == proto.ElementalShaman_Options_TSInRange ele := &ElementalShaman{ - Shaman: shaman.NewShaman(character, options.TalentsString, totems, selfBuffs, inRange), - rotation: rotation, - has4pT6: character.HasSetBonus(shaman.ItemSetSkyshatterRegalia, 4), + Shaman: shaman.NewShaman(character, options.TalentsString, totems, selfBuffs, inRange), } - ele.EnableResumeAfterManaWait(ele.tryUseGCD) if mh := ele.GetMHWeapon(); mh != nil { ele.ApplyFlametongueImbueToItem(mh, false) @@ -90,10 +69,6 @@ func NewElementalShaman(character *core.Character, options *proto.Player) *Eleme type ElementalShaman struct { *shaman.Shaman - - rotation Rotation - - has4pT6 bool } func (eleShaman *ElementalShaman) GetShaman() *shaman.Shaman { @@ -102,5 +77,4 @@ func (eleShaman *ElementalShaman) GetShaman() *shaman.Shaman { func (eleShaman *ElementalShaman) Reset(sim *core.Simulation) { eleShaman.Shaman.Reset(sim) - eleShaman.rotation.Reset(eleShaman, sim) } diff --git a/sim/shaman/elemental/elemental_test.go b/sim/shaman/elemental/elemental_test.go index fe06b3efc2..5a20559d19 100644 --- a/sim/shaman/elemental/elemental_test.go +++ b/sim/shaman/elemental/elemental_test.go @@ -113,9 +113,8 @@ var PlayerOptionsAdaptive = &proto.Player_ElementalShaman{ Options: &proto.ElementalShaman_Options{ Shield: proto.ShamanShield_WaterShield, Bloodlust: true, - Totems: BasicTotems, + Totems: FireElementalBasicTotems, }, - Rotation: &proto.ElementalShaman_Rotation{}, }, } @@ -126,7 +125,6 @@ var PlayerOptionsAdaptiveFireElemental = &proto.Player_ElementalShaman{ Bloodlust: true, Totems: FireElementalBasicTotems, }, - Rotation: &proto.ElementalShaman_Rotation{}, }, } diff --git a/sim/shaman/elemental/rotation.go b/sim/shaman/elemental/rotation.go deleted file mode 100644 index 3847fdbd7e..0000000000 --- a/sim/shaman/elemental/rotation.go +++ /dev/null @@ -1,311 +0,0 @@ -package elemental - -import ( - "time" - - "github.com/wowsims/wotlk/sim/core" - "github.com/wowsims/wotlk/sim/core/proto" -) - -// func (eleShaman *ElementalShaman) GetPresimOptions(_ proto.Player) *core.PresimOptions { -// return eleShaman.rotation.GetPresimOptions() -// } - -func (eleShaman *ElementalShaman) OnGCDReady(sim *core.Simulation) { - if eleShaman.IsUsingAPL { - return - } - - eleShaman.tryUseGCD(sim) -} - -func (eleShaman *ElementalShaman) tryUseGCD(sim *core.Simulation) { - if eleShaman.TryDropTotems(sim) { - return - } - - eleShaman.rotation.DoAction(eleShaman, sim) -} - -// Picks which attacks / abilities the Shaman does. -type Rotation interface { - // GetPresimOptions() *core.PresimOptions - - // Returns the action this rotation would like to take next. - DoAction(*ElementalShaman, *core.Simulation) - - // Returns this rotation to its initial state. Called before each Sim iteration. - Reset(*ElementalShaman, *core.Simulation) -} - -// ################################################################ -// -// ADAPTIVE -// -// ################################################################ -type AdaptiveRotation struct { - fnmm float64 - clmm float64 - lvbFSWait time.Duration -} - -func (rotation *AdaptiveRotation) DoAction(eleShaman *ElementalShaman, sim *core.Simulation) { - target := eleShaman.CurrentTarget - - shouldTS := false - cmp := eleShaman.CurrentManaPercent() - percent := sim.GetRemainingDurationPercent() - 0.1 - if eleShaman.Env.GetNumTargets() > 1 { - percent = 0.9 // single target we need less mana. - } - if cmp < percent { - shouldTS = true - } - if shouldTS && eleShaman.Thunderstorm.IsReady(sim) { - eleShaman.Thunderstorm.Cast(sim, target) - return - } - - fsTime := eleShaman.FlameShock.CurDot().RemainingDuration(sim) - lvTime := eleShaman.LavaBurst.CD.TimeToReady(sim) - lvCastTime := eleShaman.ApplyCastSpeed(eleShaman.LavaBurst.DefaultCast.CastTime) - if fsTime <= 0 && eleShaman.FlameShock.IsReady(sim) { - if !eleShaman.FlameShock.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.FlameShock.CurCast.Cost) - } - return - } else if fsTime > lvCastTime { - if lvTime <= 0 { - if !eleShaman.LavaBurst.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.LavaBurst.CurCast.Cost) - } - return - } else if lvTime <= rotation.lvbFSWait && fsTime > lvCastTime+lvTime { - // If we have enough time to wait lvbFSWait and still have FS up, we should just wait to cast LvB. - eleShaman.WaitUntil(sim, sim.CurrentTime+lvTime) - return - } - } - - fsCD := eleShaman.FlameShock.CD.TimeToReady(sim) - if fsCD > fsTime { - fsTime = fsCD - } - // If FS will be needed and is ready in < lvbFSWait time, delay. - if fsTime <= rotation.lvbFSWait { - eleShaman.WaitUntil(sim, sim.CurrentTime+fsTime) - return - } - - if cmp > rotation.clmm && eleShaman.ChainLightning.IsReady(sim) { - clTime := eleShaman.ApplyCastSpeed(eleShaman.ChainLightning.DefaultCast.CastTime) - // Only CL if cast time > 1 second than CL or there is more than 1 target. - if clTime > core.GCDMin || eleShaman.Env.GetNumTargets() > 1 { - if !eleShaman.ChainLightning.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.ChainLightning.CurCast.Cost) - } - return - } - } else if cmp > rotation.fnmm && eleShaman.FireNova.IsReady(sim) { - if !eleShaman.FireNova.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.FireNova.CurCast.Cost) - } - return - } - - if !eleShaman.LightningBolt.Cast(sim, target) { - if sim.Log != nil { - eleShaman.Log(sim, "Failed to cast LB, cost: %0.1f, current mana: %0.1f\n") - } - eleShaman.WaitForMana(sim, eleShaman.LightningBolt.CurCast.Cost) - } -} - -func (rotation *AdaptiveRotation) Reset(_ *ElementalShaman, sim *core.Simulation) { - rotation.fnmm = 1.0 - rotation.clmm = 1.0 - if len(sim.Encounter.Targets) > 4 { - // 5+ targets FN is better - rotation.fnmm = 0.33 - // Allow CL as long as you have decent mana (leaving most mana for FN) - rotation.clmm = 0.5 - } else if len(sim.Encounter.Targets) == 4 { - // 4 targets, enable both similar prio, prob looking at real AoE now (short fight) - rotation.clmm = 0.33 - rotation.fnmm = 0.33 - } else if len(sim.Encounter.Targets) == 3 { - // 3 targets, enable both, but prio CL (more efficient) - // Still trying to be very mana efficient as 3 targets - // is still often a "boss fight" and could be long. - rotation.clmm = 0.33 - rotation.fnmm = 0.66 - } else if len(sim.Encounter.Targets) == 2 { - // enable CL with 2 - rotation.clmm = 0.33 - } -} - -// func (rotation *AdaptiveRotation) GetPresimOptions() *core.PresimOptions { -// return &core.PresimOptions{ -// SetPresimPlayerOptions: func(player *proto.Player) { -// }, - -// OnPresimResult: func(presimResult proto.UnitMetrics, iterations int32, duration time.Duration) bool { -// return true -// }, -// } -// } - -func NewAdaptiveRotation(options *proto.ElementalShaman_Rotation) *AdaptiveRotation { - if options.LvbFsWaitMs == 0 { - options.LvbFsWaitMs = 175 - } - return &AdaptiveRotation{ - lvbFSWait: time.Duration(options.LvbFsWaitMs) * time.Millisecond, - } -} - -// ################################################################ -// -// MANUAL -// -// ################################################################ -type ManualRotation struct { - options *proto.ElementalShaman_Rotation -} - -func (rotation *ManualRotation) DoAction(eleShaman *ElementalShaman, sim *core.Simulation) { - target := eleShaman.CurrentTarget - lvbFSWait := time.Duration(rotation.options.LvbFsWaitMs) * time.Millisecond - shouldTS := false - cmp := eleShaman.CurrentManaPercent() - - // TODO: expose these percents to let user tweak - percent := sim.GetRemainingDurationPercent() - 0.1 - if eleShaman.Env.GetNumTargets() > 1 { - percent = 0.9 - } - if cmp < percent { - shouldTS = true - } - if shouldTS && rotation.options.UseThunderstorm && eleShaman.Thunderstorm.IsReady(sim) { - eleShaman.Thunderstorm.Cast(sim, target) - return - } - - fsRemain := eleShaman.FlameShock.CurDot().RemainingDuration(sim) - needFS := fsRemain <= 0 - // Only overwrite if lvb is ready right now. - if !needFS && rotation.options.OverwriteFlameshock && eleShaman.LavaBurst.CD.TimeToReady(sim) <= core.GCDDefault { - lvbTime := max(eleShaman.ApplyCastSpeed(eleShaman.LavaBurst.DefaultCast.CastTime), core.GCDMin) - if fsRemain < lvbTime { - needFS = true - } - } - - allowLvB := true - if rotation.options.AlwaysCritLvb { - lvbTime := max(eleShaman.ApplyCastSpeed(eleShaman.LavaBurst.DefaultCast.CastTime), core.GCDMin) - if fsRemain <= lvbTime { - allowLvB = false - } - } - - shouldCL := rotation.options.UseChainLightning && cmp > (rotation.options.ClMinManaPer/100) && eleShaman.ChainLightning.IsReady(sim) - clTime := eleShaman.ApplyCastSpeed(eleShaman.ChainLightning.DefaultCast.CastTime) - - // Never cast CL if single target and cast time <= 1 second - // This is effecively a waste of haste (that is probably temporary.) - if clTime <= time.Second && eleShaman.Env.GetNumTargets() == 1 { - shouldCL = false - } - lvbCD := eleShaman.LavaBurst.CD.TimeToReady(sim) - if shouldCL && rotation.options.UseClOnlyGap { - shouldCL = false - clCast := max(eleShaman.ApplyCastSpeed(eleShaman.ChainLightning.DefaultCast.CastTime), core.GCDMin) - // If LvB CD < CL cast time, we should use CL to pass the time until then. - // Or if FS is about to expire and we didn't cast LvB. - if fsRemain <= clCast || (lvbCD <= clCast) { - shouldCL = true - } - } - - fsCD := eleShaman.FlameShock.CD.TimeToReady(sim) - if fsCD > fsRemain { - fsRemain = fsCD - } - - // If FS will be needed and is ready in < lvbFSWait time, delay. - if fsRemain <= lvbFSWait && fsRemain > 0 { - eleShaman.WaitUntil(sim, sim.CurrentTime+fsRemain) - return - } - - // If LvB will be ready in < lvbFSWait time, delay - if lvbCD <= lvbFSWait && lvbCD > 0 { - eleShaman.WaitUntil(sim, sim.CurrentTime+lvbCD) - return - } - - if needFS && eleShaman.FlameShock.IsReady(sim) { - if !eleShaman.FlameShock.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.FlameShock.CurCast.Cost) - } - return - } else if allowLvB && eleShaman.LavaBurst.IsReady(sim) { - if !eleShaman.LavaBurst.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.LavaBurst.CurCast.Cost) - } - return - } else if shouldCL { - if !eleShaman.ChainLightning.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.ChainLightning.CurCast.Cost) - } - return - } else if rotation.options.UseFireNova && cmp > (rotation.options.FnMinManaPer/100) && eleShaman.FireNova.IsReady(sim) { - if !eleShaman.FireNova.Cast(sim, target) { - eleShaman.WaitForMana(sim, eleShaman.FireNova.CurCast.Cost) - } - return - } - - if !eleShaman.LightningBolt.Cast(sim, target) { - if sim.Log != nil { - eleShaman.Log(sim, "Failed to cast LB, cost: %0.1f, current mana: %0.1f\n") - } - eleShaman.WaitForMana(sim, eleShaman.LightningBolt.CurCast.Cost) - } -} - -func (rotation *ManualRotation) Reset(_ *ElementalShaman, _ *core.Simulation) { -} - -// func (rotation *ManualRotation) GetPresimOptions() *core.PresimOptions { -// return &core.PresimOptions{ -// SetPresimPlayerOptions: func(player *proto.Player) { -// }, - -// OnPresimResult: func(presimResult proto.UnitMetrics, iterations int32, duration time.Duration) bool { -// return true -// }, -// } -// } - -func NewManualRotation(options *proto.ElementalShaman_Rotation) *ManualRotation { - return &ManualRotation{ - options: options, - } -} - -// A single action that an Agent can take. -type AgentAction interface { - GetActionID() core.ActionID - - // TODO: Maybe change this to 'ResourceCost' - // Amount of mana required to perform the action. - GetManaCost() float64 - - // Do the action. Returns whether the action was successful. An unsuccessful - // action indicates that the prerequisites, like resource cost, were not met. - Cast(sim *core.Simulation) bool -} diff --git a/sim/shaman/enhancement/enhancement.go b/sim/shaman/enhancement/enhancement.go index 00d3ef34e8..e1653492f2 100644 --- a/sim/shaman/enhancement/enhancement.go +++ b/sim/shaman/enhancement/enhancement.go @@ -3,7 +3,6 @@ package enhancement import ( "time" - "github.com/wowsims/wotlk/sim/common" "github.com/wowsims/wotlk/sim/core" "github.com/wowsims/wotlk/sim/core/proto" "github.com/wowsims/wotlk/sim/shaman" @@ -36,11 +35,6 @@ func NewEnhancementShaman(character *core.Character, options *proto.Player) *Enh ImbueOH: enhOptions.Options.ImbueOh, } - // Override with new rotation option bloodlust. - if enhOptions.Rotation.Bloodlust != proto.EnhancementShaman_Rotation_UnsetBloodlust { - selfBuffs.Bloodlust = enhOptions.Rotation.Bloodlust == proto.EnhancementShaman_Rotation_UseBloodlust - } - totems := &proto.ShamanTotems{} if enhOptions.Options.Totems != nil { totems = enhOptions.Options.Totems @@ -50,9 +44,6 @@ func NewEnhancementShaman(character *core.Character, options *proto.Player) *Enh Shaman: shaman.NewShaman(character, options.TalentsString, totems, selfBuffs, true), } - enh.EnableResumeAfterManaWait(enh.OnGCDReady) - enh.rotation = NewPriorityRotation(enh, enhOptions.Rotation) - // Enable Auto Attacks for this spec enh.EnableAutoAttacks(enh, core.AutoAttackOptions{ MainHand: enh.WeaponFromMainHand(enh.DefaultMeleeCritMultiplier()), @@ -64,12 +55,6 @@ func NewEnhancementShaman(character *core.Character, options *proto.Player) *Enh enh.ApplyFlametongueImbue(enh.getImbueProcMask(proto.ShamanImbue_FlametongueWeapon), false) enh.ApplyFlametongueImbue(enh.getImbueProcMask(proto.ShamanImbue_FlametongueWeaponDownrank), true) - if enhOptions.Rotation.LightningboltWeave { - enh.maelstromWeaponMinStack = enhOptions.Rotation.MaelstromweaponMinStack - } else { - enh.maelstromWeaponMinStack = 5 - } - if !enh.HasMHWeapon() { enh.SelfBuffs.ImbueMH = proto.ShamanImbue_NoImbue } @@ -83,8 +68,6 @@ func NewEnhancementShaman(character *core.Character, options *proto.Player) *Enh SpiritWolf2: enh.NewSpiritWolf(2), } - enh.ShamanisticRageManaThreshold = enhOptions.Rotation.ShamanisticRageManaThreshold - return enh } @@ -102,13 +85,8 @@ func (enh *EnhancementShaman) getImbueProcMask(imbue proto.ShamanImbue) core.Pro type EnhancementShaman struct { *shaman.Shaman - rotation Rotation - maelstromWeaponMinStack int32 - // for weaving Lava Burst or Lightning Bolt previousSwingAt time.Duration - - scheduler common.GCDScheduler } func (enh *EnhancementShaman) GetShaman() *shaman.Shaman { @@ -132,13 +110,6 @@ func (enh *EnhancementShaman) Initialize() { enh.ApplySyncType(proto.ShamanSyncType_Auto) }) } - enh.DelayDPSCooldowns(3 * time.Second) - - if !enh.IsUsingAPL { - enh.RegisterPrepullAction(-time.Second, func(sim *core.Simulation) { - enh.ItemSwap.SwapItems(sim, []proto.ItemSlot{proto.ItemSlot_ItemSlotMainHand, proto.ItemSlot_ItemSlotOffHand}) - }) - } } func (enh *EnhancementShaman) Reset(sim *core.Simulation) { @@ -183,58 +154,3 @@ func (enh *EnhancementShaman) ApplySyncType(syncType proto.ShamanSyncType) { enh.AutoAttacks.SetReplaceMHSwing(nil) } } - -func (enh *EnhancementShaman) CastLightningBoltWeave(sim *core.Simulation, reactionTime time.Duration) bool { - previousAttack := sim.CurrentTime - enh.previousSwingAt - reactionTime = core.TernaryDuration(previousAttack < reactionTime, reactionTime-previousAttack, 0) - - //calculate cast times for weaving - lbCastTime := enh.ApplyCastSpeed(enh.LightningBolt.DefaultCast.CastTime-(time.Millisecond*time.Duration(500*enh.MaelstromWeaponAura.GetStacks()))) + reactionTime - //calculate swing times for weaving - timeUntilSwing := enh.AutoAttacks.NextAttackAt() - sim.CurrentTime - - if lbCastTime < timeUntilSwing { - if reactionTime > 0 { - reactionTime += sim.CurrentTime - - enh.HardcastWaitUntil(sim, reactionTime, func(_ *core.Simulation, _ *core.Unit) { - enh.GCD.Reset() - enh.LightningBolt.Cast(sim, enh.CurrentTarget) - }) - - enh.WaitUntil(sim, reactionTime) - return true - } - return enh.LightningBolt.Cast(sim, enh.CurrentTarget) - } - - return false -} - -func (enh *EnhancementShaman) CastLavaBurstWeave(sim *core.Simulation, reactionTime time.Duration) bool { - previousAttack := sim.CurrentTime - enh.previousSwingAt - reactionTime = core.TernaryDuration(previousAttack < reactionTime, reactionTime-previousAttack, 0) - - //calculate cast times for weaving - lvbCastTime := enh.ApplyCastSpeed(enh.LavaBurst.DefaultCast.CastTime) + reactionTime - //calculate swing times for weaving - timeUntilSwing := enh.AutoAttacks.NextAttackAt() - sim.CurrentTime - - if lvbCastTime < timeUntilSwing { - if reactionTime > 0 { - reactionTime += sim.CurrentTime - - enh.HardcastWaitUntil(sim, reactionTime, func(_ *core.Simulation, _ *core.Unit) { - enh.GCD.Reset() - enh.LavaBurst.Cast(sim, enh.CurrentTarget) - }) - - enh.WaitUntil(sim, reactionTime) - return true - } - - return enh.LavaBurst.Cast(sim, enh.CurrentTarget) - } - - return false -} diff --git a/sim/shaman/enhancement/enhancement_test.go b/sim/shaman/enhancement/enhancement_test.go index 33d9f3dd8b..14da520aef 100644 --- a/sim/shaman/enhancement/enhancement_test.go +++ b/sim/shaman/enhancement/enhancement_test.go @@ -87,15 +87,13 @@ var StandardGlyphs = &proto.Glyphs{ var PlayerOptionsWFWF = &proto.Player_EnhancementShaman{ EnhancementShaman: &proto.EnhancementShaman{ - Options: enhShamWFWF, - Rotation: &proto.EnhancementShaman_Rotation{}, + Options: enhShamWFWF, }, } var PlayerOptionsFTFT = &proto.Player_EnhancementShaman{ EnhancementShaman: &proto.EnhancementShaman{ - Options: enhShamFTFT, - Rotation: &proto.EnhancementShaman_Rotation{}, + Options: enhShamFTFT, }, } diff --git a/sim/shaman/enhancement/priority_rotation.go b/sim/shaman/enhancement/priority_rotation.go deleted file mode 100644 index 8a1900dfba..0000000000 --- a/sim/shaman/enhancement/priority_rotation.go +++ /dev/null @@ -1,411 +0,0 @@ -package enhancement - -import ( - "time" - - "github.com/wowsims/wotlk/sim/core" - "github.com/wowsims/wotlk/sim/core/proto" -) - -// Default Priority Order -const ( - LightningBolt = iota - StormstrikeApplyDebuff - WeaveLavaBurst - WeaveLightningBolt - MagmaTotem - Stormstrike - FlameShock - EarthShock - FrostShock - FireNova - DelayedWeave - LightningShield - DropAllTotems - LavaLash - NumberSpells // Used to get the max number of spells in the prio list, keep at bottoom -) - -type PriorityRotation struct { - options *proto.EnhancementShaman_Rotation - spellPriority []Spell -} - -type Cast func(sim *core.Simulation, target *core.Unit) bool -type Condition func(sim *core.Simulation, target *core.Unit) bool -type ReadyAt func() time.Duration - -// Holds all the spell info we need to make decisions -type Spell struct { - readyAt ReadyAt - cast Cast - // Must pass this check to cast or use readyAt, a special condition to be met - condition Condition -} - -func NewPriorityRotation(enh *EnhancementShaman, options *proto.EnhancementShaman_Rotation) *PriorityRotation { - rotation := &PriorityRotation{ - options: options, - } - - rotation.buildPriorityRotation(enh) - - return rotation -} - -func (rotation *PriorityRotation) buildPriorityRotation(enh *EnhancementShaman) { - stormstrikeApplyDebuff := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return !enh.StormstrikeDebuffAura(target).IsActive() - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.Stormstrike.IsReady(sim) && enh.Stormstrike.Cast(sim, target) - }, - readyAt: func() time.Duration { - return enh.Stormstrike.ReadyAt() - }, - } - - instantLightningBolt := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return enh.MaelstromWeaponAura.GetStacks() == 5 - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.LightningBolt.Cast(sim, enh.CurrentTarget) - }, - readyAt: func() time.Duration { - return 0 - }, - } - - chainLightning := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return enh.MaelstromWeaponAura.GetStacks() == 5 && enh.ChainLightning.IsReady(sim) - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.ChainLightning.Cast(sim, enh.CurrentTarget) - }, - readyAt: func() time.Duration { - return 0 - }, - } - - stormstrike := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - //Checking if we learned the spell, ie untalented - return enh.Stormstrike != nil - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - //TODO add in SS delay so we don't lose flametongues, if Last attack = current time - return enh.Stormstrike.IsReady(sim) && enh.Stormstrike.Cast(sim, target) - }, - readyAt: func() time.Duration { - return enh.Stormstrike.ReadyAt() - }, - } - - weaveLightningBolt := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return rotation.options.LightningboltWeave && enh.MaelstromWeaponAura.GetStacks() >= rotation.options.MaelstromweaponMinStack && enh.CurrentMana() >= enh.LightningBolt.DefaultCast.Cost - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - reactionTime := time.Millisecond * time.Duration(rotation.options.AutoWeaveDelay) - return enh.CastLightningBoltWeave(sim, reactionTime) - }, - readyAt: func() time.Duration { - return 0 - }, - } - - weaveLavaBurst := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - if enh.CurrentMana() < enh.LavaBurst.CurCast.Cost { - return false - } - - return rotation.options.LavaburstWeave && enh.MaelstromWeaponAura.GetStacks() >= rotation.options.MaelstromweaponMinStack - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - reactionTime := time.Millisecond * time.Duration(rotation.options.AutoWeaveDelay) - return rotation.options.LavaburstWeave && enh.LavaBurst.IsReady(sim) && enh.CastLavaBurstWeave(sim, reactionTime) - }, - readyAt: func() time.Duration { - return 0 - }, - } - - flameShock := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - fsDot := enh.FlameShock.Dot(target) - return rotation.options.WeaveFlameShock && fsDot.RemainingDuration(sim) <= time.Duration(rotation.options.FlameShockClipTicks)*fsDot.TickLength - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.FlameShock.IsReady(sim) && enh.FlameShock.Cast(sim, target) - }, - readyAt: func() time.Duration { - return enh.FlameShock.ReadyAt() - }, - } - - earthShock := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return rotation.options.PrimaryShock == proto.EnhancementShaman_Rotation_Earth - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.EarthShock.IsReady(sim) && enh.EarthShock.Cast(sim, target) - }, - readyAt: func() time.Duration { - return enh.EarthShock.ReadyAt() - }, - } - - frostShock := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return rotation.options.PrimaryShock == proto.EnhancementShaman_Rotation_Frost - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.FrostShock.IsReady(sim) && enh.FrostShock.Cast(sim, target) - }, - readyAt: func() time.Duration { - return enh.EarthShock.ReadyAt() - }, - } - - lightningShield := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return enh.LightningShield != nil && !enh.LightningShieldAura.IsActive() - }, - cast: func(sim *core.Simulation, _ *core.Unit) bool { - return enh.LightningShield.Cast(sim, nil) - }, - readyAt: func() time.Duration { - return 0 - }, - } - - fireNova := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - if enh.Totems.Fire == proto.FireTotem_NoFireTotem { - return false - } - - return enh.NextTotemDrops[2] > sim.CurrentTime && enh.CurrentMana() > rotation.options.FirenovaManaThreshold - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.FireNova.IsReady(sim) && enh.FireNova.Cast(sim, target) - }, - readyAt: func() time.Duration { - return enh.FireNova.ReadyAt() - }, - } - - lavaLash := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - //Checking if we learned the spell, ie untalented - return enh.LavaLash != nil && enh.AutoAttacks.IsDualWielding - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - //TODO add in LL delay so we don't lose flametongues, if Last attack = current time - return enh.LavaLash.IsReady(sim) && enh.LavaLash.Cast(sim, target) - }, - readyAt: func() time.Duration { - return enh.LavaLash.ReadyAt() - }, - } - - magmaTotem := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - //TODO : rework totems to make them easier to work with. - return enh.Totems.Fire == proto.FireTotem_MagmaTotem && enh.NextTotemDrops[2] <= sim.CurrentTime - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - return enh.MagmaTotem.Cast(sim, target) - }, - readyAt: func() time.Duration { - return 0 - }, - } - - dropAllTotems := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - return true - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - // TODO : totems need to be dropped all at once. - return enh.TryDropTotems(sim) - }, - readyAt: func() time.Duration { - return 0 - }, - } - - delayedWeave := Spell{ - condition: func(sim *core.Simulation, target *core.Unit) bool { - if enh.CurrentMana() < enh.LightningBolt.DefaultCast.Cost { - return false - } - - return rotation.options.LightningboltWeave && rotation.options.DelayGcdWeave > 0 && enh.MaelstromWeaponAura.GetStacks() >= rotation.options.MaelstromweaponMinStack - }, - cast: func(sim *core.Simulation, target *core.Unit) bool { - timeUntilSwing := enh.AutoAttacks.NextAttackAt() - sim.CurrentTime - if timeUntilSwing <= time.Millisecond*time.Duration(rotation.options.DelayGcdWeave) && timeUntilSwing != 0 { - delay := enh.AutoAttacks.NextAttackAt() + time.Millisecond*100 - if delay < sim.CurrentTime { - return false - } - - enh.HardcastWaitUntil(sim, delay, func(_ *core.Simulation, _ *core.Unit) { - enh.GCD.Reset() - enh.CastLightningBoltWeave(sim, 0) - }) - - enh.WaitUntil(sim, delay) - return true - } - - return false - }, - readyAt: func() time.Duration { - return 0 - }, - } - - //Normal Priority Rotation - var spellPriority []Spell - if rotation.options.RotationType == proto.EnhancementShaman_Rotation_Priority { - spellPriority = make([]Spell, NumberSpells) - spellPriority[StormstrikeApplyDebuff] = stormstrikeApplyDebuff - spellPriority[LightningBolt] = instantLightningBolt - spellPriority[Stormstrike] = stormstrike - spellPriority[FlameShock] = flameShock - spellPriority[EarthShock] = earthShock - spellPriority[LightningShield] = lightningShield - spellPriority[FireNova] = fireNova - spellPriority[LavaLash] = lavaLash - spellPriority[WeaveLightningBolt] = weaveLightningBolt - spellPriority[FrostShock] = frostShock - spellPriority[WeaveLavaBurst] = weaveLavaBurst - spellPriority[MagmaTotem] = magmaTotem - spellPriority[DropAllTotems] = dropAllTotems - spellPriority[DelayedWeave] = delayedWeave - } - - //Custom Priority Rotation - if rotation.options.RotationType == proto.EnhancementShaman_Rotation_Custom && rotation.options.CustomRotation != nil { - spellPriority := make([]Spell, 0, len(rotation.options.CustomRotation.Spells)) - - // Turn weaving off, will enable them if they have been added. - rotation.options.LightningboltWeave = false - rotation.options.LavaburstWeave = false - rotation.options.WeaveFlameShock = false - rotation.options.PrimaryShock = proto.EnhancementShaman_Rotation_None - for _, customSpellProto := range rotation.options.CustomRotation.Spells { - switch customSpellProto.Spell { - case int32(proto.EnhancementShaman_Rotation_StormstrikeDebuffMissing): - spellPriority = append(spellPriority, stormstrikeApplyDebuff) - case int32(proto.EnhancementShaman_Rotation_LightningBolt): - spellPriority = append(spellPriority, instantLightningBolt) - case int32(proto.EnhancementShaman_Rotation_ChainLightning): - spellPriority = append(spellPriority, chainLightning) - case int32(proto.EnhancementShaman_Rotation_LightningBoltWeave): - rotation.options.LightningboltWeave = true - spellPriority = append(spellPriority, weaveLightningBolt) - case int32(proto.EnhancementShaman_Rotation_LightningBoltDelayedWeave): - rotation.options.LightningboltWeave = true - spellPriority = append(spellPriority, delayedWeave) - case int32(proto.EnhancementShaman_Rotation_Stormstrike): - spellPriority = append(spellPriority, stormstrike) - case int32(proto.EnhancementShaman_Rotation_FlameShock): - rotation.options.WeaveFlameShock = true - spellPriority = append(spellPriority, flameShock) - case int32(proto.EnhancementShaman_Rotation_FireNova): - spellPriority = append(spellPriority, fireNova) - case int32(proto.EnhancementShaman_Rotation_LavaLash): - spellPriority = append(spellPriority, lavaLash) - case int32(proto.EnhancementShaman_Rotation_EarthShock): - if rotation.options.PrimaryShock == proto.EnhancementShaman_Rotation_None { - rotation.options.PrimaryShock = proto.EnhancementShaman_Rotation_Earth - spellPriority = append(spellPriority, earthShock) - } - case int32(proto.EnhancementShaman_Rotation_LightningShield): - spellPriority = append(spellPriority, lightningShield) - case int32(proto.EnhancementShaman_Rotation_FrostShock): - if rotation.options.PrimaryShock == proto.EnhancementShaman_Rotation_None { - rotation.options.PrimaryShock = proto.EnhancementShaman_Rotation_Frost - spellPriority = append(spellPriority, frostShock) - } - case int32(proto.EnhancementShaman_Rotation_LavaBurst): - rotation.options.LavaburstWeave = true - spellPriority = append(spellPriority, weaveLavaBurst) - case int32(proto.EnhancementShaman_Rotation_MagmaTotem): - spellPriority = append(spellPriority, magmaTotem) - } - } - - rotation.spellPriority = spellPriority - return - } - - rotation.spellPriority = spellPriority -} - -func (rotation *PriorityRotation) DoAction(enh *EnhancementShaman, sim *core.Simulation) { - target := enh.CurrentTarget - - cheapestSpell := enh.LightningBolt.CurCast.Cost - if enh.LavaLash != nil { - cheapestSpell = enh.LavaLash.CurCast.Cost - } - - // Incase the array is empty - if len(rotation.spellPriority) == 0 { - enh.DoNothing() - } - - //Mana guard, our cheapest spell. - if enh.CurrentMana() < cheapestSpell { - // Lets top off lightning shield stacks before waiting for mana. - if enh.LightningShield != nil && enh.LightningShieldAura.GetStacks() < 3 { - enh.LightningShield.Cast(sim, nil) - } - enh.WaitForMana(sim, cheapestSpell) - return - } - - // We could choose to not wait for auto attacks if we don't have any MW stacks, - // this would reduce the amount of DoAction calls by a little bit; might not be a issue though. - upcomingCD := enh.AutoAttacks.NextAttackAt() - var cast Cast - for _, spell := range rotation.spellPriority { - if !spell.condition(sim, target) { - continue - } - - if spell.cast(sim, target) { - return - } - - readyAt := spell.readyAt() - if readyAt > sim.CurrentTime && (upcomingCD > readyAt || upcomingCD < sim.CurrentTime) { - upcomingCD = readyAt - cast = spell.cast - } - } - - //Lets wait on a upcoming CD or AutoAttack - enh.WaitUntil(sim, upcomingCD) - - //Incase the next auto is our best CD then there are no spells to cast. - if cast != nil { - //We have a upcoming CD and we know what to cast lets just do that. - enh.HardcastWaitUntil(sim, upcomingCD, func(sim *core.Simulation, target *core.Unit) { - enh.GCD.Reset() - cast(sim, target) - }) - } -} - -func (rotation *PriorityRotation) Reset(_ *EnhancementShaman, _ *core.Simulation) { - -} diff --git a/sim/shaman/enhancement/rotation.go b/sim/shaman/enhancement/rotation.go deleted file mode 100644 index 53476c86d5..0000000000 --- a/sim/shaman/enhancement/rotation.go +++ /dev/null @@ -1,31 +0,0 @@ -package enhancement - -import ( - "github.com/wowsims/wotlk/sim/core" -) - -func (enh *EnhancementShaman) OnAutoAttack(sim *core.Simulation, _ *core.Spell) { - enh.previousSwingAt = sim.CurrentTime -} - -func (enh *EnhancementShaman) OnGCDReady(sim *core.Simulation) { - // TODO move this into the rotation, also this uses waitForMana if it was unable to cast the totem - // that will need to be pulled out so we are not waiting for a magma totem mana cost - enh.rotation.DoAction(enh, sim) -} - -type Rotation interface { - DoAction(*EnhancementShaman, *core.Simulation) - Reset(*EnhancementShaman, *core.Simulation) -} - -// CUSTOM ROTATION (advanced) (also WIP). -// -// TODO: figure out how to do this (probably too complicated to copy hunters) -type AgentAction interface { - GetActionID() core.ActionID - - GetManaCost() float64 - - Cast(sim *core.Simulation) bool -} diff --git a/sim/shaman/fire_elemental_pet.go b/sim/shaman/fire_elemental_pet.go index 255277e8df..879a5e56dc 100644 --- a/sim/shaman/fire_elemental_pet.go +++ b/sim/shaman/fire_elemental_pet.go @@ -99,7 +99,6 @@ func (fireElemental *FireElemental) ExecuteCustomRotation(sim *core.Simulation) fireNovaCasts := fireElemental.FireNova.SpellMetrics[0].Casts if fireBlastCasts == maxFireBlastCasts && fireNovaCasts == maxFireNovaCasts { - fireElemental.DoNothing() return } diff --git a/sim/shaman/fire_elemental_totem.go b/sim/shaman/fire_elemental_totem.go index fe82987ad5..cebf9fed61 100644 --- a/sim/shaman/fire_elemental_totem.go +++ b/sim/shaman/fire_elemental_totem.go @@ -10,7 +10,7 @@ import ( const fireTotemDuration time.Duration = time.Second * 120 func (shaman *Shaman) registerFireElementalTotem() { - if !shaman.Totems.UseFireElemental && !shaman.IsUsingAPL { + if !shaman.Totems.UseFireElemental { return } @@ -55,50 +55,13 @@ func (shaman *Shaman) registerFireElementalTotem() { shaman.FireElemental.EnableWithTimeout(sim, shaman.FireElemental, fireTotemDuration) - //TODO handle more then one swap if the fight is greater then 5 mins, for now will just do the one. - if !shaman.IsUsingAPL && shaman.FireElementalTotem.SpellMetrics[target.Index].Casts == 1 { - shaman.ItemSwap.SwapItems(sim, []proto.ItemSlot{proto.ItemSlot_ItemSlotMainHand, proto.ItemSlot_ItemSlotOffHand}) - } - // Add a dummy aura to show in metrics fireElementalAura.Activate(sim) }, }) - //Enh has 1.5seconds GCD also, so just going to wait the normal 1.5 instead of using the dynamic Spell GCD - var castWindow = 1550 * time.Millisecond - - enhTier10Aura := shaman.GetAura("Maelstrom Power") - shaman.AddMajorCooldown(core.MajorCooldown{ Spell: shaman.FireElementalTotem, Type: core.CooldownTypeUnknown, - ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { - success := false - if enhTier10Aura != nil && shaman.Totems.EnhTierTenBonus && shaman.fireElementalSnapShot != nil { - if enhTier10Aura.IsActive() { - success = shaman.fireElementalSnapShot.CanSnapShot(sim, castWindow) - } else if sim.CurrentTime+fireTotemDuration > sim.Encounter.Duration { - success = true - } - } else if sim.CurrentTime > 1*time.Second && shaman.fireElementalSnapShot == nil { - success = true - } else if sim.Encounter.Duration <= 120*time.Second && sim.CurrentTime >= 10*time.Second { - success = true - } else if sim.Encounter.Duration > 120*time.Second && sim.CurrentTime >= 20*time.Second { - success = true - } else if shaman.fireElementalSnapShot != nil { - success = shaman.fireElementalSnapShot.CanSnapShot(sim, castWindow) - } - - if success && shaman.fireElementalSnapShot != nil { - shaman.castFireElemental = true - shaman.fireElementalSnapShot.ActivateMajorCooldowns(sim) - shaman.fireElementalSnapShot.ResetProcTrackers() - shaman.castFireElemental = false - } - - return success - }, }) } diff --git a/sim/shaman/fire_totems.go b/sim/shaman/fire_totems.go index 43f88bd94e..689a92328b 100644 --- a/sim/shaman/fire_totems.go +++ b/sim/shaman/fire_totems.go @@ -4,23 +4,9 @@ import ( "time" "github.com/wowsims/wotlk/sim/core" - "github.com/wowsims/wotlk/sim/core/proto" ) func (shaman *Shaman) registerSearingTotemSpell() { - var extraCastCondition core.CanCastCondition - if !shaman.IsUsingAPL && shaman.Totems.Fire == proto.FireTotem_SearingTotem && shaman.Totems.UseFireMcd { - extraCastCondition = func(sim *core.Simulation, target *core.Unit) bool { - if shaman.Totems.Fire != proto.FireTotem_SearingTotem { - return false - } - if shaman.SearingTotem.Dot(shaman.CurrentTarget).IsActive() || shaman.FireElemental.IsEnabled() || shaman.FireElementalTotem.IsReady(sim) { - return false - } - return true - } - } - shaman.SearingTotem = shaman.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 58704}, SpellSchool: core.SpellSchoolFire, @@ -38,7 +24,6 @@ func (shaman *Shaman) registerSearingTotemSpell() { GCD: time.Second, }, }, - ExtraCastCondition: extraCastCondition, BonusHitRating: float64(shaman.Talents.ElementalPrecision) * core.SpellHitRatingPerHitChance, DamageMultiplier: 1 + float64(shaman.Talents.CallOfFlame)*0.05, @@ -66,37 +51,13 @@ func (shaman *Shaman) registerSearingTotemSpell() { shaman.MagmaTotem.AOEDot().Cancel(sim) shaman.FireElemental.Disable(sim) spell.Dot(sim.GetTargetUnit(0)).Apply(sim) - if !shaman.Totems.UseFireMcd { - // +1 needed because of rounding issues with totem tick time. - shaman.NextTotemDrops[FireTotem] = sim.CurrentTime + time.Second*60 + 1 - } + // +1 needed because of rounding issues with totem tick time. + shaman.NextTotemDrops[FireTotem] = sim.CurrentTime + time.Second*60 + 1 }, }) - - if extraCastCondition == nil { - return - } - shaman.AddMajorCooldown(core.MajorCooldown{ - Spell: shaman.SearingTotem, - Priority: core.CooldownPriorityDefault, // TODO needs to be altered due to snap shotting. - Type: core.CooldownTypeDPS, - }) } func (shaman *Shaman) registerMagmaTotemSpell() { - var extraCastCondition core.CanCastCondition - if !shaman.IsUsingAPL && shaman.Totems.Fire == proto.FireTotem_MagmaTotem && shaman.Totems.UseFireMcd { - extraCastCondition = func(sim *core.Simulation, target *core.Unit) bool { - if shaman.Totems.Fire != proto.FireTotem_MagmaTotem { - return false - } - if shaman.MagmaTotem.AOEDot().IsActive() || shaman.FireElemental.IsEnabled() || shaman.FireElementalTotem.IsReady(sim) { - return false - } - return true - } - } - shaman.MagmaTotem = shaman.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 58734}, SpellSchool: core.SpellSchoolFire, @@ -114,7 +75,6 @@ func (shaman *Shaman) registerMagmaTotemSpell() { GCD: time.Second, }, }, - ExtraCastCondition: extraCastCondition, BonusHitRating: float64(shaman.Talents.ElementalPrecision) * core.SpellHitRatingPerHitChance, DamageMultiplier: 1 + float64(shaman.Talents.CallOfFlame)*0.05, @@ -141,19 +101,8 @@ func (shaman *Shaman) registerMagmaTotemSpell() { shaman.SearingTotem.Dot(shaman.CurrentTarget).Cancel(sim) shaman.FireElemental.Disable(sim) spell.AOEDot().Apply(sim) - if !shaman.Totems.UseFireMcd { - // +1 needed because of rounding issues with totem tick time. - shaman.NextTotemDrops[FireTotem] = sim.CurrentTime + time.Second*20 + 1 - } + // +1 needed because of rounding issues with totem tick time. + shaman.NextTotemDrops[FireTotem] = sim.CurrentTime + time.Second*20 + 1 }, }) - - if extraCastCondition == nil { - return - } - shaman.AddMajorCooldown(core.MajorCooldown{ - Spell: shaman.MagmaTotem, - Priority: core.CooldownPriorityDefault, // TODO needs to be altered due to snap shotting. - Type: core.CooldownTypeDPS, - }) } diff --git a/sim/shaman/restoration/restoration.go b/sim/shaman/restoration/restoration.go index 880f4c0d29..a4a2d0b706 100644 --- a/sim/shaman/restoration/restoration.go +++ b/sim/shaman/restoration/restoration.go @@ -1,8 +1,6 @@ package restoration import ( - "time" - "github.com/wowsims/wotlk/sim/core" "github.com/wowsims/wotlk/sim/core/proto" "github.com/wowsims/wotlk/sim/shaman" @@ -33,26 +31,15 @@ func NewRestorationShaman(character *core.Character, options *proto.Player) *Res Shield: restoShamOptions.Options.Shield, } - if restoShamOptions.Rotation.Bloodlust != proto.RestorationShaman_Rotation_UnsetBloodlust { - selfBuffs.Bloodlust = restoShamOptions.Rotation.Bloodlust == proto.RestorationShaman_Rotation_UseBloodlust - } - totems := &proto.ShamanTotems{} if restoShamOptions.Options.Totems != nil { totems = restoShamOptions.Options.Totems } resto := &RestorationShaman{ - Shaman: shaman.NewShaman(character, options.TalentsString, totems, selfBuffs, false), - rotation: restoShamOptions.Rotation, + Shaman: shaman.NewShaman(character, options.TalentsString, totems, selfBuffs, false), } - // can only use earth shield if specc'd - resto.rotation.UseEarthShield = resto.rotation.UseEarthShield && resto.Talents.EarthShield - resto.earthShieldPPM = restoShamOptions.Options.EarthShieldPPM - - resto.EnableResumeAfterManaWait(resto.tryUseGCD) - if resto.HasMHWeapon() { resto.ApplyEarthlivingImbueToItem(resto.GetMHWeapon()) } @@ -65,10 +52,6 @@ func NewRestorationShaman(character *core.Character, options *proto.Player) *Res type RestorationShaman struct { *shaman.Shaman - - rotation *proto.RestorationShaman_Rotation - earthShieldPPM int32 - lastEarthShieldProc time.Duration } func (resto *RestorationShaman) GetShaman() *shaman.Shaman { @@ -77,7 +60,6 @@ func (resto *RestorationShaman) GetShaman() *shaman.Shaman { func (resto *RestorationShaman) Reset(sim *core.Simulation) { resto.Shaman.Reset(sim) - resto.lastEarthShieldProc = -core.NeverExpires } func (resto *RestorationShaman) GetMainTarget() *core.Unit { // TODO: make this just grab first player that isn't self. diff --git a/sim/shaman/restoration/rotation.go b/sim/shaman/restoration/rotation.go deleted file mode 100644 index 08a0c52d54..0000000000 --- a/sim/shaman/restoration/rotation.go +++ /dev/null @@ -1,58 +0,0 @@ -package restoration - -import ( - "time" - - "github.com/wowsims/wotlk/sim/core" - "github.com/wowsims/wotlk/sim/core/proto" -) - -func (resto *RestorationShaman) OnGCDReady(sim *core.Simulation) { - resto.tryUseGCD(sim) -} - -func (resto *RestorationShaman) tryUseGCD(sim *core.Simulation) { - - // TODO: This could actually just be made as a PA that runs and triggers the shield instead of part of the rotation here. - es := resto.EarthShield.Hot(resto.CurrentTarget) - if es.IsActive() && resto.earthShieldPPM > 0 { - procTime := time.Duration(60.0 / float64(resto.earthShieldPPM) * float64(time.Second)) - lastProc := resto.lastEarthShieldProc - if lastProc+procTime < sim.CurrentTime { - es.OnSpellHitTaken(es.Aura, sim, nil, &core.SpellResult{Outcome: core.OutcomeHit, Target: resto.CurrentTarget}) - resto.lastEarthShieldProc = sim.CurrentTime - } - } - - if resto.TryDropTotems(sim) { - return - } - - var spell *core.Spell - switch resto.rotation.PrimaryHeal { - case proto.ShamanHealSpell_AutoHeal: - if len(resto.Party.Players) > 3 { - spell = resto.ChainHeal - } else { - // TODO: lots of things to consider here... - spell = resto.LesserHealingWave - } - case proto.ShamanHealSpell_LesserHealingWave: - spell = resto.LesserHealingWave - case proto.ShamanHealSpell_HealingWave: - panic("healing wave not implemented yet") - spell = resto.HealingWave - case proto.ShamanHealSpell_ChainHeal: - spell = resto.ChainHeal - } - - if resto.rotation.UseEarthShield && !es.IsActive() { - spell = resto.EarthShield - } else if resto.rotation.UseRiptide && !resto.Riptide.Hot(resto.CurrentTarget).IsActive() && resto.Riptide.IsReady(sim) { - spell = resto.Riptide - } - - if !spell.Cast(sim, resto.CurrentTarget) { - resto.WaitForMana(sim, spell.CurCast.Cost) - } -} diff --git a/sim/shaman/shaman.go b/sim/shaman/shaman.go index 79f21578ba..a895f3acc3 100644 --- a/sim/shaman/shaman.go +++ b/sim/shaman/shaman.go @@ -80,8 +80,6 @@ type Shaman struct { thunderstormInRange bool // flag if thunderstorm will be in range. - ShamanisticRageManaThreshold float64 //% of mana to use sham. rage at - Talents *proto.ShamanTalents SelfBuffs SelfBuffs @@ -116,10 +114,8 @@ type Shaman struct { FeralSpirit *core.Spell SpiritWolves *SpiritWolves - castFireElemental bool - FireElemental *FireElemental - FireElementalTotem *core.Spell - fireElementalSnapShot *core.SnapshotManager + FireElemental *FireElemental + FireElementalTotem *core.Spell MagmaTotem *core.Spell ManaSpringTotem *core.Spell @@ -227,8 +223,6 @@ func (shaman *Shaman) AddPartyBuffs(partyBuffs *proto.PartyBuffs) { } func (shaman *Shaman) Initialize() { - enableSnapshot := shaman.Totems.BonusSpellpower == 0 - shaman.registerChainLightningSpell() shaman.registerFeralSpirit() shaman.registerFireElementalTotem() @@ -256,19 +250,6 @@ func (shaman *Shaman) Initialize() { shaman.registerCallOfTheElements() shaman.registerBloodlustCD() - - if shaman.Totems.UseFireElemental && enableSnapshot { - shaman.fireElementalSnapShot = core.NewSnapshotManager(shaman.GetCharacter()) - shaman.setupProcTrackers() - } - - // Healing stream totem applies a HoT (aura) and so needs to be handled as a pre-pull action - // instead of during init/reset. - if !shaman.IsUsingAPL && shaman.Totems.Water == proto.WaterTotem_HealingStreamTotem { - shaman.RegisterPrepullAction(0, func(sim *core.Simulation) { - shaman.HealingStreamTotem.Cast(sim, &shaman.Unit) - }) - } } func (shaman *Shaman) RegisterHealingSpells() { @@ -301,11 +282,6 @@ func (shaman *Shaman) RegisterHealingSpells() { } func (shaman *Shaman) Reset(sim *core.Simulation) { - if shaman.Totems.UseFireElemental { - shaman.setupFireElementalCooldowns() - shaman.castFireElemental = false - } - // Check to see if we are casting a totem to set its expire time. for i := range shaman.NextTotemDrops { shaman.NextTotemDrops[i] = core.NeverExpires @@ -344,111 +320,6 @@ func (shaman *Shaman) Reset(sim *core.Simulation) { shaman.FlameShock.CD.Reset() } -func (shaman *Shaman) setupProcTrackers() { - snapshotManager := shaman.fireElementalSnapShot - - snapshotManager.AddProc(40212, "Potion of Wild Magic", true) - snapshotManager.AddProc(33697, "Blood Fury", true) - snapshotManager.AddProc(59620, "Berserking MH Proc", false) - snapshotManager.AddProc(59620, "Berserking OH Proc", false) - - //AP Ring Procs - snapshotManager.AddProc(44308, "Signet of Edward the Odd Proc", false) - snapshotManager.AddProc(50401, "Ashen Band of Unmatched Vengeance Proc", false) - snapshotManager.AddProc(50402, "Ashen Band of Endless Vengeance Proc", false) - snapshotManager.AddProc(52571, "Ashen Band of Unmatched Might Proc", false) - snapshotManager.AddProc(52572, "Ashen Band of Endless Might Proc", false) - - //SP Trinket Procs - snapshotManager.AddProc(40255, "Dying Curse Proc", false) - snapshotManager.AddProc(40682, "Sundial of the Exiled Proc", false) - snapshotManager.AddProc(37660, "Forge Ember Proc", false) - snapshotManager.AddProc(45518, "Flare of the Heavens Proc", false) - snapshotManager.AddProc(54572, "Charred Twilight Scale Proc", false) - snapshotManager.AddProc(54588, "Charred Twilight Scale H Proc", false) - snapshotManager.AddProc(47213, "Abyssal Rune Proc", false) - snapshotManager.AddProc(45490, "Pandora's Plea Proc", false) - snapshotManager.AddProc(50348, "Dislodged Foreign Object H", false) - snapshotManager.AddProc(50353, "Dislodged Foreign Object", false) - snapshotManager.AddProc(50360, "Phylactery of the Nameless Lich Proc", false) - snapshotManager.AddProc(50365, "Phylactery of the Nameless Lich H Proc", false) - snapshotManager.AddProc(50345, "Muradin's Spyglass H Proc", false) - snapshotManager.AddProc(50340, "Muradin's Spyglass Proc", false) - - // SP Ring Procs - snapshotManager.AddProc(50398, "Ashen Band of Endless Destruction", false) - - //AP Trinket Procs - snapshotManager.AddProc(40684, "Mirror of Truth Proc", false) - snapshotManager.AddProc(45522, "Blood of the Old God Proc", false) - snapshotManager.AddProc(40767, "Sonic Booster Proc", false) - snapshotManager.AddProc(44914, "Anvil of Titans Proc", false) - snapshotManager.AddProc(45286, "Pyrite Infuser Proc", false) - snapshotManager.AddProc(47214, "Banner of Victory Proc", false) - snapshotManager.AddProc(49074, "Coren's Chromium Coaster Proc", false) - snapshotManager.AddProc(50342, "Whispering Fanged Skull Proc", false) - snapshotManager.AddProc(50343, "Whispering Fanged Skull H Proc", false) - snapshotManager.AddProc(54569, "Sharpened Twilight Scale Proc", false) - snapshotManager.AddProc(54590, "Sharpened Twilight Scale H Proc", false) - snapshotManager.AddProc(47115, "Deaths Verdict Agility Proc", false) - snapshotManager.AddProc(47131, "Deaths Verdict H Agility Proc", false) - snapshotManager.AddProc(47303, "Deaths Choice Agility Proc", false) - snapshotManager.AddProc(47464, "Deaths Choice H Agility Proc", false) - snapshotManager.AddProc(71492, "Deathbringer's Will Strength Proc", false) - snapshotManager.AddProc(71561, "Deathbringer's Will H Strength Proc", false) - snapshotManager.AddProc(71492, "Deathbringer's Will Agility Proc", false) - snapshotManager.AddProc(71561, "Deathbringer's Will H Agility Proc", false) - snapshotManager.AddProc(71492, "Deathbringer's Will AP Proc", false) - snapshotManager.AddProc(71561, "Deathbringer's Will H AP Proc", false) - - // Tier Bonus - snapshotManager.AddProc(70831, "Maelstrom Power", false) -} - -func (shaman *Shaman) setupFireElementalCooldowns() { - if shaman.fireElementalSnapShot == nil { - return - } - - shaman.fireElementalSnapShot.ClearMajorCooldowns() - - // blood fury (orc) - shaman.fireElementalCooldownSync(core.ActionID{SpellID: 33697}, false) - - // potion of Wild Magic - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 40212}, true) - - //active sp trinkets - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 37873}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 45148}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 48724}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 50357}, false) - - // active ap trinkets - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 35937}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 36871}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 37166}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 37556}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 37557}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 38080}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 38081}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 38761}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 39257}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 45263}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 46086}, false) - shaman.fireElementalCooldownSync(core.ActionID{ItemID: 47734}, false) -} - -func (shaman *Shaman) fireElementalCooldownSync(actionID core.ActionID, isPotion bool) { - if majorCd := shaman.Character.GetMajorCooldown(actionID); majorCd != nil { - majorCd.ShouldActivate = func(sim *core.Simulation, character *core.Character) bool { - return shaman.castFireElemental || (shaman.FireElementalTotem.CD.TimeToReady(sim) > majorCd.Spell.CD.Duration && !isPotion) || shaman.FireElementalTotem.CD.ReadyAt() > shaman.Env.Encounter.Duration - } - - shaman.fireElementalSnapShot.AddMajorCooldown(majorCd) - } -} - func (shaman *Shaman) ElementalCritMultiplier(secondary float64) float64 { critBonus := 0.2*float64(shaman.Talents.ElementalFury) + secondary return shaman.SpellCritMultiplier(1, critBonus) diff --git a/sim/shaman/shamanistic_rage.go b/sim/shaman/shamanistic_rage.go index 42fcde55ef..d62b95fe91 100644 --- a/sim/shaman/shamanistic_rage.go +++ b/sim/shaman/shamanistic_rage.go @@ -12,10 +12,7 @@ func (shaman *Shaman) registerShamanisticRageCD() { return } - t10Bonus := false - if shaman.HasSetBonus(ItemSetFrostWitchBattlegear, 2) { - t10Bonus = true - } + t10Bonus := shaman.HasSetBonus(ItemSetFrostWitchBattlegear, 2) actionID := core.ActionID{SpellID: 30823} ppmm := shaman.AutoAttacks.NewPPMManager(15, core.ProcMaskMelee) @@ -66,22 +63,11 @@ func (shaman *Shaman) registerShamanisticRageCD() { }, }) - if shaman.IsUsingAPL { - shaman.AddMajorCooldown(core.MajorCooldown{ - Spell: spell, - Type: core.CooldownTypeMana, - ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { - return character.CurrentManaPercent() <= 0.2 - }, - }) - } else { - shaman.AddMajorCooldown(core.MajorCooldown{ - Spell: spell, - Type: core.CooldownTypeMana, - ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { - manaReserve := shaman.ShamanisticRageManaThreshold / 100 * shaman.MaxMana() - return character.CurrentMana() <= manaReserve - }, - }) - } + 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/totems.go b/sim/shaman/totems.go index fdf73dd247..701228ee2a 100644 --- a/sim/shaman/totems.go +++ b/sim/shaman/totems.go @@ -187,47 +187,6 @@ func (shaman *Shaman) registerCallOfTheElements() { }) } -func (shaman *Shaman) NextTotemAt(_ *core.Simulation) time.Duration { - return min(shaman.NextTotemDrops[0], shaman.NextTotemDrops[1], shaman.NextTotemDrops[2], shaman.NextTotemDrops[3]) -} - -// TryDropTotems will check to see if totems need to be re-cast. -// -// Returns whether we tried to cast a totem, regardless of whether it succeeded. -func (shaman *Shaman) TryDropTotems(sim *core.Simulation) bool { - var spell *core.Spell - - casted := false - for totemTypeIdx, totemExpiration := range shaman.NextTotemDrops { - spell = nil - nextDrop := shaman.NextTotemDropType[totemTypeIdx] - if sim.CurrentTime >= totemExpiration { - switch totemTypeIdx { - case AirTotem: - spell = shaman.getAirTotemSpell(proto.AirTotem(nextDrop)) - case EarthTotem: - spell = shaman.getEarthTotemSpell(proto.EarthTotem(nextDrop)) - case FireTotem: - spell = shaman.getFireTotemSpell(proto.FireTotem(nextDrop)) - case WaterTotem: - spell = shaman.getWaterTotemSpell(proto.WaterTotem(nextDrop)) - } - } - if spell != nil { - if success := spell.Cast(sim, shaman.CurrentTarget); !success { - shaman.WaitForMana(sim, spell.CurCast.Cost) - return true - } - casted = true - } - } - - if casted { - shaman.WaitUntil(sim, sim.CurrentTime+time.Second) - } - return casted -} - func (shaman *Shaman) getAirTotemSpell(totemType proto.AirTotem) *core.Spell { switch totemType { case proto.AirTotem_WrathOfAirTotem: diff --git a/ui/elemental_shaman/apls/advanced.apl.json b/ui/elemental_shaman/apls/advanced.apl.json index 3240d5bc13..73b7d2f0b4 100644 --- a/ui/elemental_shaman/apls/advanced.apl.json +++ b/ui/elemental_shaman/apls/advanced.apl.json @@ -1,5 +1,4 @@ { - "enabled": true, "type": "TypeAPL", "prepullActions": [ {"action":{"castSpell":{"spellId":{"spellId":59159}}},"doAtValue":{"const":{"val":"-5"}}},