diff --git a/sim/common/vanilla/item_sets/dungeon_set_1.go b/sim/common/vanilla/item_sets/dungeon_set_1.go index 0e120ce93..45f2ad860 100644 --- a/sim/common/vanilla/item_sets/dungeon_set_1.go +++ b/sim/common/vanilla/item_sets/dungeon_set_1.go @@ -342,7 +342,7 @@ var ItemSetBattlegearOfValor = core.NewItemSet(core.ItemSet{ ProcMask: core.ProcMaskMelee, PPM: 1, Handler: func(sim *core.Simulation, spell *core.Spell, _ *core.SpellResult) { - c.GainHealth(sim, sim.Roll(88, 132), healthMetrics) + c.GainHealth(sim, sim.Roll(88, 133), healthMetrics) }, }) }, diff --git a/sim/core/buffs.go b/sim/core/buffs.go index 63223c35f..790009bfd 100644 --- a/sim/core/buffs.go +++ b/sim/core/buffs.go @@ -361,7 +361,7 @@ func applyBuffEffects(agent Agent, playerFaction proto.Faction, raidBuffs *proto } if raidBuffs.BattleShout != proto.TristateEffect_TristateEffectMissing { - MakePermanent(BattleShoutAura(&character.Unit, GetTristateValueInt32(raidBuffs.BattleShout, 0, 5), 0)) + MakePermanent(BattleShoutAura(&character.Unit, GetTristateValueInt32(raidBuffs.BattleShout, 0, 5), 0, false)) // Do we implement 3pc wrath for the other sims? } if individualBuffs.BlessingOfMight != proto.TristateEffect_TristateEffectMissing && isAlliance { @@ -1398,11 +1398,11 @@ var BattleShoutSpellId = [BattleShoutRanks + 1]int32{0, 6673, 5242, 6192, 11549, var BattleShoutBaseAP = [BattleShoutRanks + 1]float64{0, 20, 40, 57, 93, 138, 193, 232} var BattleShoutLevel = [BattleShoutRanks + 1]int{0, 1, 12, 22, 32, 42, 52, 60} -func BattleShoutAura(unit *Unit, impBattleShout int32, boomingVoicePts int32) *Aura { +func BattleShoutAura(unit *Unit, impBattleShout int32, boomingVoicePts int32, has3pcWrath bool) *Aura { rank := TernaryInt32(IncludeAQ, 7, 6) spellId := BattleShoutSpellId[rank] baseAP := BattleShoutBaseAP[rank] - + return unit.GetOrRegisterAura(Aura{ Label: "Battle Shout", ActionID: ActionID{SpellID: spellId}, @@ -1410,12 +1410,12 @@ func BattleShoutAura(unit *Unit, impBattleShout int32, boomingVoicePts int32) *A BuildPhase: CharacterBuildPhaseBuffs, OnGain: func(aura *Aura, sim *Simulation) { aura.Unit.AddStatsDynamic(sim, stats.Stats{ - stats.AttackPower: math.Floor(baseAP * (1 + 0.05*float64(impBattleShout))), + stats.AttackPower: math.Floor(baseAP * (1 + 0.05*float64(impBattleShout)) + TernaryFloat64(has3pcWrath, 30, 0)), }) }, OnExpire: func(aura *Aura, sim *Simulation) { aura.Unit.AddStatsDynamic(sim, stats.Stats{ - stats.AttackPower: -1 * math.Floor(baseAP*(1+0.05*float64(impBattleShout))), + stats.AttackPower: -1 * math.Floor(baseAP*(1+0.05*float64(impBattleShout)) + TernaryFloat64(has3pcWrath, 30, 0)), }) }, }) diff --git a/sim/warrior/deep_wounds.go b/sim/warrior/deep_wounds.go index b0a9b4742..aef069f07 100644 --- a/sim/warrior/deep_wounds.go +++ b/sim/warrior/deep_wounds.go @@ -37,12 +37,14 @@ func (warrior *Warrior) applyDeepWounds() { TickLength: time.Second * 3, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + attackTable := warrior.AttackTables[target.UnitIndex][proto.CastType_CastTypeMainHand] + dot.SnapshotAttackerMultiplier = dot.Spell.AttackerDamageMultiplier(attackTable) // Double dips on attackers mods dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) }, }, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - spell.Dot(target).ApplyOrRefresh(sim) + spell.Dot(target).Apply(sim) //Resets the tick counter with Apply vs ApplyorRefresh spell.CalcAndDealOutcome(sim, target, spell.OutcomeAlwaysHitNoHitCounter) }, }) @@ -69,8 +71,6 @@ func (warrior *Warrior) applyDeepWounds() { func (warrior *Warrior) procDeepWounds(sim *core.Simulation, target *core.Unit, isOh bool) { dot := warrior.DeepWounds.Dot(target) - outstandingDamage := core.TernaryFloat64(dot.IsActive(), dot.SnapshotBaseDamage*float64(dot.NumberOfTicks-dot.TickCount), 0) - var awd float64 if isOh { attackTableOh := warrior.AttackTables[target.UnitIndex][proto.CastType_CastTypeOffHand] @@ -82,9 +82,9 @@ func (warrior *Warrior) procDeepWounds(sim *core.Simulation, target *core.Unit, awd = warrior.AutoAttacks.MH().CalculateAverageWeaponDamage(dot.Spell.MeleeAttackPower()) * adm } - newDamage := awd * 0.2 * float64(warrior.Talents.DeepWounds) + newDamage := awd * 0.2 * float64(warrior.Talents.DeepWounds) // 60% of average attackers damage - dot.SnapshotBaseDamage = (outstandingDamage + newDamage) / float64(dot.NumberOfTicks) + dot.SnapshotBaseDamage = newDamage / 4.0 // spread over 4 ticks of the dot dot.SnapshotAttackerMultiplier = 1 warrior.DeepWounds.Cast(sim, target) diff --git a/sim/warrior/dps_warrior/TestP1DPSWarrior.results b/sim/warrior/dps_warrior/TestP1DPSWarrior.results index 69b097bf9..e0906b436 100644 --- a/sim/warrior/dps_warrior/TestP1DPSWarrior.results +++ b/sim/warrior/dps_warrior/TestP1DPSWarrior.results @@ -50,8 +50,8 @@ character_stats_results: { stat_weights_results: { key: "TestP1DPSWarrior-Phase1-StatWeights-Default" value: { - weights: 0.44532 - weights: 0.13025 + weights: 0.38423 + weights: 0.08848 weights: 0 weights: 0 weights: 0 @@ -67,9 +67,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.39162 + weights: 0.31173 weights: 0 - weights: 2.74696 + weights: 1.91476 weights: 0 weights: 0 weights: 0 @@ -96,101 +96,143 @@ stat_weights_results: { weights: 0 } } +dps_results: { + key: "TestP1DPSWarrior-Phase1-AllItems-BattlegearofHeroism" + value: { + dps: 237.73911 + tps: 231.66781 + } +} +dps_results: { + key: "TestP1DPSWarrior-Phase1-AllItems-BattlegearofMight" + value: { + dps: 222.95766 + tps: 218.52321 + } +} +dps_results: { + key: "TestP1DPSWarrior-Phase1-AllItems-BattlegearofWrath" + value: { + dps: 219.245 + tps: 216.30574 + } +} +dps_results: { + key: "TestP1DPSWarrior-Phase1-AllItems-Conqueror'sBattlegear" + value: { + dps: 244.90003 + tps: 237.86693 + } +} +dps_results: { + key: "TestP1DPSWarrior-Phase1-AllItems-Dreadnaught'sBattlegear" + value: { + dps: 228.08017 + tps: 223.44701 + } +} +dps_results: { + key: "TestP1DPSWarrior-Phase1-AllItems-Vindicator'sBattlegear" + value: { + dps: 236.32283 + tps: 231.13999 + } +} dps_results: { key: "TestP1DPSWarrior-Phase1-Average-Default" value: { - dps: 251.86628 - tps: 220.05239 + dps: 225.0288 + tps: 220.3409 } } dps_results: { key: "TestP1DPSWarrior-Phase1-Settings-Human-p0.bis-DPS-p1-FullBuffs-P1-Consumes-LongMultiTarget" value: { - dps: 35.95075 - tps: 121.17562 + dps: 33.71639 + tps: 123.6996 } } dps_results: { key: "TestP1DPSWarrior-Phase1-Settings-Human-p0.bis-DPS-p1-FullBuffs-P1-Consumes-LongSingleTarget" value: { - dps: 32.91426 - tps: 33.524 + dps: 30.6799 + tps: 36.04798 } } dps_results: { key: "TestP1DPSWarrior-Phase1-Settings-Human-p0.bis-DPS-p1-FullBuffs-P1-Consumes-ShortSingleTarget" value: { - dps: 88.77662 - tps: 81.49139 + dps: 78.71826 + tps: 81.12737 } } dps_results: { key: "TestP1DPSWarrior-Phase1-Settings-Human-p0.bis-DPS-p1-NoBuffs-P1-Consumes-LongMultiTarget" value: { - dps: 17.18813 - tps: 106.16552 + dps: 16.31096 + tps: 109.77525 } } dps_results: { key: "TestP1DPSWarrior-Phase1-Settings-Human-p0.bis-DPS-p1-NoBuffs-P1-Consumes-LongSingleTarget" value: { - dps: 14.89313 - tps: 19.1071 + dps: 14.01596 + tps: 22.71683 } } dps_results: { key: "TestP1DPSWarrior-Phase1-Settings-Human-p0.bis-DPS-p1-NoBuffs-P1-Consumes-ShortSingleTarget" value: { - dps: 42.96501 - tps: 44.8421 + dps: 38.74616 + tps: 49.14968 } } dps_results: { key: "TestP1DPSWarrior-Phase1-Settings-Orc-p0.bis-DPS-p1-FullBuffs-P1-Consumes-LongMultiTarget" value: { - dps: 37.75094 - tps: 122.61577 + dps: 35.24971 + tps: 124.92625 } } dps_results: { key: "TestP1DPSWarrior-Phase1-Settings-Orc-p0.bis-DPS-p1-FullBuffs-P1-Consumes-LongSingleTarget" value: { - dps: 34.71445 - tps: 34.96415 + dps: 32.21322 + tps: 37.27463 } } dps_results: { key: "TestP1DPSWarrior-Phase1-Settings-Orc-p0.bis-DPS-p1-FullBuffs-P1-Consumes-ShortSingleTarget" value: { - dps: 91.92593 - tps: 84.01084 + dps: 81.08886 + tps: 83.02385 } } dps_results: { key: "TestP1DPSWarrior-Phase1-Settings-Orc-p0.bis-DPS-p1-NoBuffs-P1-Consumes-LongMultiTarget" value: { - dps: 18.37064 - tps: 107.11153 + dps: 17.31727 + tps: 110.5803 } } dps_results: { key: "TestP1DPSWarrior-Phase1-Settings-Orc-p0.bis-DPS-p1-NoBuffs-P1-Consumes-LongSingleTarget" value: { - dps: 16.07564 - tps: 20.0531 + dps: 15.02227 + tps: 23.52187 } } dps_results: { key: "TestP1DPSWarrior-Phase1-Settings-Orc-p0.bis-DPS-p1-NoBuffs-P1-Consumes-ShortSingleTarget" value: { - dps: 46.51015 - tps: 47.67821 + dps: 41.44703 + tps: 51.31038 } } dps_results: { key: "TestP1DPSWarrior-Phase1-SwitchInFrontOfTarget-Default" value: { - dps: 198.04954 - tps: 174.84326 + dps: 180.2038 + tps: 179.18853 } } diff --git a/sim/warrior/execute.go b/sim/warrior/execute.go index 9fc9045c9..7d5a5177a 100644 --- a/sim/warrior/execute.go +++ b/sim/warrior/execute.go @@ -2,32 +2,13 @@ package warrior import ( "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" ) func (warrior *Warrior) registerExecuteSpell() { - hasSuddenDeathRune := warrior.HasRune(proto.WarriorRune_RuneSuddenDeath) - flatDamage := map[int32]float64{ - 25: 125, - 40: 325, - 50: 450, - 60: 600, - }[warrior.Level] - - convertedRageDamage := map[int32]float64{ - 25: 3, - 40: 9, - 50: 12, - 60: 15, - }[warrior.Level] - - spellID := map[int32]int32{ - 25: 5308, - 40: 20660, - 50: 20661, - 60: 20662, - }[warrior.Level] + flatDamage := 600.0 + convertedRageDamage := 15.0 + spellID := int32(20662) var rageMetrics *core.ResourceMetrics warrior.Execute = warrior.RegisterSpell(BattleStance|BerserkerStance, core.SpellConfig{ @@ -48,7 +29,7 @@ func (warrior *Warrior) registerExecuteSpell() { }, }, ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return sim.IsExecutePhase20() || (hasSuddenDeathRune && warrior.SuddenDeathAura.IsActive()) + return sim.IsExecutePhase20() }, CritDamageBonus: warrior.impale(), diff --git a/sim/warrior/hamstring.go b/sim/warrior/hamstring.go index fdd8e2898..81408ad25 100644 --- a/sim/warrior/hamstring.go +++ b/sim/warrior/hamstring.go @@ -5,26 +5,9 @@ import ( ) func (warrior *Warrior) registerHamstringSpell() { - damage := map[int32]float64{ - 25: 5, - 40: 18, - 50: 18, - 60: 45, - }[warrior.Level] - - spellID := map[int32]int32{ - 25: 1715, - 40: 7372, - 50: 7372, - 60: 27584, - }[warrior.Level] - - spell_level := map[int32]int32{ - 25: 8, - 40: 32, - 50: 32, - 60: 54, - }[warrior.Level] + damage := 45.0 + spellID := int32(27584) + spell_level := 54.0 warrior.Hamstring = warrior.RegisterSpell(BattleStance|BerserkerStance, core.SpellConfig{ ActionID: core.ActionID{SpellID: spellID}, diff --git a/sim/warrior/heroic_strike_cleave.go b/sim/warrior/heroic_strike_cleave.go index 1d23cb9b4..dfae1f524 100644 --- a/sim/warrior/heroic_strike_cleave.go +++ b/sim/warrior/heroic_strike_cleave.go @@ -5,27 +5,10 @@ import ( ) func (warrior *Warrior) registerHeroicStrikeSpell(realismICD *core.Cooldown) { - flatDamageBonus := map[int32]float64{ - 25: 44, - 40: 80, - 50: 111, - 60: core.TernaryFloat64(core.IncludeAQ, 157, 138), - }[warrior.Level] - - spellID := map[int32]int32{ - 25: 1608, - 40: 11565, - 50: 11566, - 60: core.TernaryInt32(core.IncludeAQ, 25286, 11567), - }[warrior.Level] - + flatDamageBonus := core.TernaryFloat64(core.IncludeAQ, 157, 138) + spellID := core.TernaryInt32(core.IncludeAQ, 25286, 11567) // No known equation - threat := map[int32]float64{ - 25: 68, //guess - 40: 103, //guess - 50: 120, - 60: core.TernaryFloat64(core.IncludeAQ, 173, 145), - }[warrior.Level] + threat := core.TernaryFloat64(core.IncludeAQ, 173, 145) warrior.HeroicStrike = warrior.RegisterSpell(AnyStance, core.SpellConfig{ ActionID: core.ActionID{SpellID: spellID}, @@ -64,26 +47,9 @@ func (warrior *Warrior) registerHeroicStrikeSpell(realismICD *core.Cooldown) { } func (warrior *Warrior) registerCleaveSpell(realismICD *core.Cooldown) { - flatDamageBonus := map[int32]float64{ - 25: 5, - 40: 18, - 50: 32, - 60: 50, - }[warrior.Level] - - spellID := map[int32]int32{ - 25: 845, - 40: 11608, - 50: 11609, - 60: 20569, - }[warrior.Level] - - threat := map[int32]float64{ - 25: 20, //guess - 40: 60, //guess - 50: 80, - 60: 100, - }[warrior.Level] + flatDamageBonus := 50.0 + spellID := int32(20569) + threat := 100.0 flatDamageBonus *= []float64{1, 1.4, 1.8, 2.2}[warrior.Talents.ImprovedCleave] diff --git a/sim/warrior/_item_sets_pve.go b/sim/warrior/item_sets_pve.go similarity index 70% rename from sim/warrior/_item_sets_pve.go rename to sim/warrior/item_sets_pve.go index b6395f28c..c980e5fc7 100644 --- a/sim/warrior/_item_sets_pve.go +++ b/sim/warrior/item_sets_pve.go @@ -3,53 +3,208 @@ package warrior import ( "slices" "time" - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" "github.com/wowsims/classic/sim/core/stats" ) /////////////////////////////////////////////////////////////////////////// -// SoD Phase 4 Item Sets +// Phase 1 Item Sets - Molten Core +/////////////////////////////////////////////////////////////////////////// + +var ItemSetBattleGearOfMight = core.NewItemSet(core.ItemSet{ + Name: "Battlegear of Might", + Bonuses: map[int32]core.ApplyEffect{ + // Increases the block value of your shield by 30. + 3: func(agent core.Agent) { + character := agent.GetCharacter() + character.AddStat(stats.BlockValue, 30) + }, + // Gives you a 20% chance to generate an additional Rage point whenever damage is dealt to you. + 5: func(agent core.Agent) { + warrior := agent.(WarriorAgent).GetWarrior() + actionID := core.ActionID{SpellID: 21838} + rageMetrics := warrior.NewRageMetrics(actionID) + + core.MakeProcTriggerAura(&warrior.Unit, core.ProcTrigger{ + ActionID: actionID, + Name: "Battlegear of Might - Rage", + Callback: core.CallbackOnSpellHitTaken, + ProcMask: core.ProcMaskMelee, + ProcChance: 0.20, + Handler: func(sim *core.Simulation, spell *core.Spell, _ *core.SpellResult) { + warrior.AddRage(sim, 1, rageMetrics) + }, + }) + }, + // Increases the threat generated by Sunder Armor by 15%. + 8: func(agent core.Agent) { + warrior := agent.(WarriorAgent).GetWarrior() + warrior.RegisterAura(core.Aura{ + Label: "Enhanced Sunder Armor", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + warrior.SunderArmor.ThreatMultiplier *= 1.15 + warrior.SunderArmor.FlatThreatBonus *= 1.15 + }, + }) + }, + }, +}) + +/////////////////////////////////////////////////////////////////////////// +// Phase 2 Item Sets - Dire Maul +/////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// Phase 3 Item Sets - BWL +/////////////////////////////////////////////////////////////////////////// + +var ItemSetBattleGearOfWrath = core.NewItemSet(core.ItemSet{ + Name: "Battlegear of Wrath", + Bonuses: map[int32]core.ApplyEffect{ + // Increases the attack power granted by Battle Shout by 30. + 3: func(agent core.Agent) { + // Managed in shouts.go + }, + // 20% chance after using an offensive ability requiring rage that your next offensive ability requires 5 less rage to use. + 5: func(agent core.Agent) { + warrior := agent.(WarriorAgent).GetWarrior() + + var affectedSpells []*core.Spell + + warriorsWrathAura := warrior.RegisterAura(core.Aura{ + ActionID: core.ActionID{SpellID: 21887}, + Label: "Warrior's Wrath", + Duration: time.Second * 10, + OnInit: func(aura *core.Aura, sim *core.Simulation) { + for _, spell := range warrior.Spellbook { + if spell.Cost != nil && spell.Cost.CostType() == core.CostTypeRage && !spell.Flags.Matches(core.SpellFlagHelpful) { + affectedSpells = append(affectedSpells, spell) + } + } + }, + OnGain: func(aura *core.Aura, sim *core.Simulation) { + for _, spell := range affectedSpells { + spell.Cost.FlatModifier -= 5 + } + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + for _, spell := range affectedSpells { + spell.Cost.FlatModifier += 5 + } + }, + OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { + if slices.Contains(affectedSpells, spell) { + aura.Deactivate(sim) + } + }, + }) + + core.MakePermanent(warrior.RegisterAura(core.Aura{ + Label: "Warrior's Wrath Trigger", + OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { + if slices.Contains(affectedSpells, spell) { + if sim.Proc(0.2, "Warrior's Wrath Trigger"){ + warriorsWrathAura.Activate(sim) + } + } + }, + })) + }, + // 4% chance to parry the next attack after a block. + 8: func(agent core.Agent) { + warrior := agent.(WarriorAgent).GetWarrior() + actionID := core.ActionID{SpellID: 23548} + + parryAura := warrior.RegisterAura(core.Aura{ + ActionID: actionID, + Label: "Parry", + Duration: time.Second * 10, + OnGain: func(aura *core.Aura, sim *core.Simulation) { + warrior.AddStatDynamic(sim, stats.Parry, 100*core.ParryRatingPerParryChance) + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + warrior.AddStatDynamic(sim, stats.Parry, -100*core.ParryRatingPerParryChance) + }, + OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + aura.Deactivate(sim) + }, + }) + + core.MakePermanent(warrior.RegisterAura(core.Aura{ + Label: "Parry Trigger Aura", + OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if result.DidBlock() && sim.Proc(0.04, "Parry Proc") { + parryAura.Activate(sim) + } + }, + })) + + }, + }, +}) + +var ItemSetVindicatorsBattlegear = core.NewItemSet(core.ItemSet{ + Name: "Vindicator's Battlegear", + Bonuses: map[int32]core.ApplyEffect{ + // Increases your chance to block attacks with a shield by 2%. + 2: func(agent core.Agent) { + warrior := agent.(WarriorAgent).GetWarrior() + warrior.AddStat(stats.Block, 2) + }, + // Decreases the cooldown of Intimidating Shout by 15 sec. + 3: func(agent core.Agent) { + // No implementation of Intimidating Shout in sim + }, + // Decrease the rage cost of Whirlwind by 3. + 5: func(agent core.Agent) { + warrior := agent.(WarriorAgent).GetWarrior() + warrior.RegisterAura(core.Aura{ + Label: "Improved Whirlwind", + OnInit: func(aura *core.Aura, sim *core.Simulation) { + warrior.Whirlwind.Cost.FlatModifier -= 3 + }, + }) + }, + }, +}) + +/////////////////////////////////////////////////////////////////////////// +// Phase 4 Item Sets - AQ /////////////////////////////////////////////////////////////////////////// -var ItemSetBattlegearOfValor = core.NewItemSet(core.ItemSet{ +var ItemSetBattlegearOfHeroism = core.NewItemSet(core.ItemSet{ Name: "Battlegear of Heroism", Bonuses: map[int32]core.ApplyEffect{ - // +40 Attack Power. + // +8 All Resistances. 2: func(agent core.Agent) { c := agent.GetCharacter() - c.AddStats(stats.Stats{ - stats.AttackPower: 40, - stats.RangedAttackPower: 40, - }) + c.AddResistances(8) }, - // Chance on melee attack to heal you for 88 to 132 and energize you for 10 Rage + // Chance on melee attack to heal you for 88 to 133 4: func(agent core.Agent) { c := agent.GetCharacter() - actionID := core.ActionID{SpellID: 450587} - healthMetrics := c.NewHealthMetrics(core.ActionID{SpellID: 450589}) - rageMetrics := c.NewRageMetrics(core.ActionID{SpellID: 450589}) + actionID := core.ActionID{SpellID: 27419} + healthMetrics := c.NewHealthMetrics(core.ActionID{SpellID: 27419}) core.MakeProcTriggerAura(&c.Unit, core.ProcTrigger{ ActionID: actionID, - Name: "S03 - Warrior Armor Heal Trigger - Battlegear of Valor", + Name: "Warrior's Resolve", Callback: core.CallbackOnSpellHitDealt, Outcome: core.OutcomeLanded, ProcMask: core.ProcMaskMelee, PPM: 1, Handler: func(sim *core.Simulation, spell *core.Spell, _ *core.SpellResult) { - c.GainHealth(sim, sim.Roll(88, 132), healthMetrics) - if c.HasRageBar() { - c.AddRage(sim, 10, rageMetrics) - } + c.GainHealth(sim, sim.Roll(88, 133), healthMetrics) }, }) }, - // +8 All Resistances. + // +40 Attack Power. 6: func(agent core.Agent) { c := agent.GetCharacter() - c.AddResistances(8) + c.AddStats(stats.Stats{ + stats.AttackPower: 40, + stats.RangedAttackPower: 40, + }) }, // +200 Armor. 8: func(agent core.Agent) { @@ -59,6 +214,78 @@ var ItemSetBattlegearOfValor = core.NewItemSet(core.ItemSet{ }, }) +var ItemSetConquerorsBattleGear = core.NewItemSet(core.ItemSet{ + Name: "Conqueror's Battlegear", + Bonuses: map[int32]core.ApplyEffect{ + // Decreases the rage cost of all Warrior shouts by 35%. + 3: func(agent core.Agent) { + warrior := agent.(WarriorAgent).GetWarrior() + core.MakePermanent(warrior.RegisterAura(core.Aura{ + Label: "Conqueror Shout Bonus", + OnGain: func(aura *core.Aura, sim *core.Simulation) { + warrior.BattleShout.Cost.Multiplier -= 35 + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + warrior.BattleShout.Cost.Multiplier += 35 + }, + })) + }, + // Increase the Slow effect and damage of Thunder Clap by 50%. + 5: func(agent core.Agent) { + //Implemented in Thunderclap.go + }, + }, +}) + +/////////////////////////////////////////////////////////////////////////// +// Phase 5 Item Sets - Naxx +/////////////////////////////////////////////////////////////////////////// + +var ItemSetDreadnaughtsBattlegear = core.NewItemSet(core.ItemSet{ + Name: "Dreadnaught's Battlegear", + Bonuses: map[int32]core.ApplyEffect{ + // Increases the damage done by your Revenge ability by 75. + 2: func(agent core.Agent) { + // Implemented in revenge.go + }, + // Improves your chance to hit with Taunt and Challenging Shout by 5%. + 4: func(agent core.Agent) { + // No taunt spells implemented in classic sim + }, + // Improves your chance to hit with Sunder Armor, Heroic Strike, Revenge, and Shield Slam by 5%. + 6: func(agent core.Agent) { + warrior := agent.(WarriorAgent).GetWarrior() + core.MakePermanent(warrior.RegisterAura(core.Aura{ + Label: "Increased Hit Chance", + OnGain: func(aura *core.Aura, sim *core.Simulation) { + warrior.SunderArmor.BonusHitRating += 5 + warrior.HeroicStrike.BonusHitRating += 5 + warrior.Revenge.BonusHitRating += 5 + if warrior.Talents.ShieldSlam { + warrior.ShieldSlam.BonusHitRating += 5 + } + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + warrior.SunderArmor.BonusHitRating -= 5 + warrior.HeroicStrike.BonusHitRating -= 5 + warrior.Revenge.BonusHitRating -= 5 + if warrior.Talents.ShieldSlam { + warrior.ShieldSlam.BonusHitRating -= 5 + } + }, + })) + }, + // When your health drops below 20%, for the next 5 seconds healing spells cast on you help you to Cheat Death, increasing healing done by up to 160. + 8: func(agent core.Agent) { + // Current healing model is flat HPS with variance in cadence, doent appear callable to adjust. + }, + }, +}) + +/////////////////////////////////////////////////////////////////////////// +// SoD Phase 4 Item Sets +/////////////////////////////////////////////////////////////////////////// +/* var ItemSetUnstoppableMight = core.NewItemSet(core.ItemSet{ Name: "Unstoppable Might", Bonuses: map[int32]core.ApplyEffect{ @@ -217,37 +444,6 @@ var ItemSetUnstoppableMight = core.NewItemSet(core.ItemSet{ }, }) -var ItemSetImmoveableMight = core.NewItemSet(core.ItemSet{ - Name: "Immoveable Might", - Bonuses: map[int32]core.ApplyEffect{ - // Increases the block value of your shield by 30. - 2: func(agent core.Agent) { - character := agent.GetCharacter() - character.AddStat(stats.BlockValue, 30) - }, - // You gain 1 extra Rage every time you take any damage or deal auto attack damage. - 4: func(agent core.Agent) { - warrior := agent.(WarriorAgent).GetWarrior() - warrior.AddDamageDealtRageBonus(1) - warrior.AddDamageTakenRageBonus(1) - }, - // Increases all threat you generate in Defensive Stance by an additional 10% and increases all damage you deal in Gladiator Stance by 4%. - 6: func(agent core.Agent) { - warrior := agent.(WarriorAgent).GetWarrior() - core.MakePermanent(warrior.RegisterAura(core.Aura{ - Label: "S03 - Item - T1 - Warrior - Tank 6P Bonus", - OnGain: func(aura *core.Aura, sim *core.Simulation) { - warrior.defensiveStanceThreatMultiplier *= 1.10 - warrior.gladiatorStanceDamageMultiplier *= 1.04 - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - warrior.defensiveStanceThreatMultiplier /= 1.10 - warrior.gladiatorStanceDamageMultiplier /= 1.04 - }, - })) - }, - }, -}) /////////////////////////////////////////////////////////////////////////// // SoD Phase 5 Item Sets @@ -570,3 +766,4 @@ var ItemSetBattlegearOfUnyieldingStrength = core.NewItemSet(core.ItemSet{ }, }, }) +*/ \ No newline at end of file diff --git a/sim/warrior/items.go b/sim/warrior/items.go index 5e6e93be7..d6b9992a9 100644 --- a/sim/warrior/items.go +++ b/sim/warrior/items.go @@ -9,10 +9,9 @@ import ( const ( DiamondFlask = 20130 - // Exsanguinar = 216497 - // SuzerainDefender = 224280 - // GrileksCharmOFMight = 231286 - // RageOfMugamba = 231350 + GrileksCharmOfMight = 19951 + RageOfMugamba = 19577 + LifegivingGem = 19341 ) func init() { @@ -50,75 +49,11 @@ func init() { }) }) - /* core.NewItemEffect(Exsanguinar, func(agent core.Agent) { - character := agent.GetCharacter() - actionId := core.ActionID{SpellID: 436332} - - spell := character.RegisterSpell(core.SpellConfig{ - ActionID: actionId, - SpellSchool: core.SpellSchoolPhysical | core.SpellSchoolShadow, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagOffensiveEquipment, - - Cast: core.CastConfig{ - CD: core.Cooldown{ - Timer: character.NewTimer(), - Duration: time.Minute * 3, - }, - }, - - DamageMultiplier: 1, - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Exsanguination", - }, - TickLength: 2 * time.Second, - NumberOfTicks: 15, - - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, 5, isRollover) - }, - - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - for _, aoeTarget := range sim.Encounter.TargetUnits { - // Has no DefenseType, also haven't seen a miss in logs. - result := spell.CalcAndDealDamage(sim, aoeTarget, 65, spell.OutcomeAlwaysHit) - if result.Landed() { - spell.Dot(aoeTarget).Apply(sim) - } - } - }, - }) - - character.AddMajorCooldown(core.MajorCooldown{ - Spell: spell, - Priority: core.CooldownPriorityLow, - Type: core.CooldownTypeDPS, - }) - }) - - core.NewItemEffect(GrileksCharmOFMight, func(agent core.Agent) { + core.NewItemEffect(GrileksCharmOfMight, func(agent core.Agent) { warrior := agent.(WarriorAgent).GetWarrior() - actionID := core.ActionID{ItemID: GrileksCharmOFMight} + actionID := core.ActionID{ItemID: GrileksCharmOfMight} rageMetrics := warrior.NewRageMetrics(actionID) - aura := warrior.RegisterAura(core.Aura{ - ActionID: actionID, - Label: "Gri'lek's Guard", - Duration: time.Second * 20, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - warrior.AddStatDynamic(sim, stats.BlockValue, 200) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - warrior.AddStatDynamic(sim, stats.BlockValue, -200) - }, - }) spell := warrior.Character.RegisterSpell(core.SpellConfig{ ActionID: actionID, @@ -129,13 +64,12 @@ func init() { Cast: core.CastConfig{ CD: core.Cooldown{ Timer: warrior.NewTimer(), - Duration: time.Minute * 2, + Duration: time.Minute * 3, }, }, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { warrior.AddRage(sim, 30, rageMetrics) - aura.Activate(sim) }, }) @@ -147,78 +81,55 @@ func init() { core.NewItemEffect(RageOfMugamba, func(agent core.Agent) { warrior := agent.(WarriorAgent).GetWarrior() - if !warrior.Talents.ShieldSlam { - return - } warrior.RegisterAura(core.Aura{ - Label: "Reduced Shield Slam Cost (Rage of Mugamba)", + Label: "Reduces the cost of your Hamstring ability by 2 rage points.", OnInit: func(aura *core.Aura, sim *core.Simulation) { - warrior.ShieldSlam.Cost.FlatModifier -= 5 + warrior.Hamstring.Cost.FlatModifier -= 2 }, }) }) - core.NewItemEffect(SuzerainDefender, func(agent core.Agent) { - character := agent.GetCharacter() - actionID := core.ActionID{ItemID: SuzerainDefender} - - // Store a reference in case the unit switches targets since we don't have a great way to do this right now - fightingDragonkin := false - rageOfSuzerain := character.RegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 469025}, - Label: "Rage of the Suzerain", - Duration: time.Second * 10, + core.NewItemEffect(LifegivingGem, func(agent core.Agent) { + warrior := agent.(WarriorAgent).GetWarrior() + actionID := core.ActionID{ItemID: LifegivingGem} + healthMetrics := warrior.NewHealthMetrics(actionID) + + var bonusHealth float64 + lifegivingGemAura := warrior.RegisterAura(core.Aura{ + Label: "Gift of Life", + ActionID: core.ActionID{SpellID: 23725}, + Duration: time.Second * 20, OnGain: func(aura *core.Aura, sim *core.Simulation) { - if aura.Unit.CurrentTarget.MobType == proto.MobType_MobTypeDragonkin { - aura.Unit.PseudoStats.DamageDealtMultiplier *= 1.30 - fightingDragonkin = true - } + bonusHealth = warrior.MaxHealth() * 0.15 + warrior.AddStatsDynamic(sim, stats.Stats{stats.Health: bonusHealth}) + warrior.GainHealth(sim, bonusHealth, healthMetrics) }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { - if fightingDragonkin { - aura.Unit.PseudoStats.DamageDealtMultiplier /= 1.30 - fightingDragonkin = false - } + warrior.AddStatsDynamic(sim, stats.Stats{stats.Health: -bonusHealth}) }, }) - - defenseOfDragonflights := character.RegisterAura(core.Aura{ + + lifegivingGemSpell := warrior.RegisterSpell(AnyStance, core.SpellConfig{ ActionID: actionID, - Label: "Defense of the Dragonflights", - Duration: time.Second * 5, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier.MultiplyMagicSchools(0.50) - rageOfSuzerain.Activate(sim) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.PseudoStats.SchoolDamageTakenMultiplier.MultiplyMagicSchools(2) - }, - }) - - spell := character.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - SpellSchool: core.SpellSchoolPhysical, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagOffensiveEquipment, - + Cast: core.CastConfig{ CD: core.Cooldown{ - Timer: character.NewTimer(), - Duration: time.Minute * 1, + Timer: warrior.NewTimer(), + Duration: time.Minute * 5, }, }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - defenseOfDragonflights.Activate(sim) + + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + lifegivingGemAura.Activate(sim) }, }) - - character.AddMajorCooldown(core.MajorCooldown{ - Type: core.CooldownTypeDPS, - Spell: spell, + + warrior.AddMajorCooldown(core.MajorCooldown{ + Spell: lifegivingGemSpell.Spell, + Type: core.CooldownTypeSurvival, }) - }) */ + }) core.AddEffectsToTest = true } diff --git a/sim/warrior/mortal_strike.go b/sim/warrior/mortal_strike.go index ff28faa1b..ef1926ad9 100644 --- a/sim/warrior/mortal_strike.go +++ b/sim/warrior/mortal_strike.go @@ -11,17 +11,8 @@ func (warrior *Warrior) registerMortalStrikeSpell(cdTimer *core.Timer) { return } - bonusDamage := map[int32]float64{ - 40: 85, - 50: 110, - 60: 160, - }[warrior.Level] - - spellID := map[int32]int32{ - 40: 12294, - 50: 21551, - 60: 21553, - }[warrior.Level] + bonusDamage := 160.0 + spellID := int32(21553) warrior.MortalStrike = warrior.RegisterSpell(AnyStance, core.SpellConfig{ SpellCode: SpellCode_WarriorMortalStrike, diff --git a/sim/warrior/overpower.go b/sim/warrior/overpower.go index 024003188..6d6849a8f 100644 --- a/sim/warrior/overpower.go +++ b/sim/warrior/overpower.go @@ -4,25 +4,11 @@ import ( "time" "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" ) func (warrior *Warrior) registerOverpowerSpell(cdTimer *core.Timer) { - hasTasteForBloodRune := warrior.HasRune(proto.WarriorRune_RuneTasteForBlood) - - bonusDamage := map[int32]float64{ - 25: 5, - 40: 15, - 50: 25, - 60: 35, - }[warrior.Level] - - spellID := map[int32]int32{ - 25: 7384, - 40: 7887, - 50: 11584, - 60: 11585, - }[warrior.Level] + bonusDamage := 35.0 + spellID := int32(11585) warrior.RegisterAura(core.Aura{ Label: "Overpower Trigger", @@ -66,7 +52,7 @@ func (warrior *Warrior) registerOverpowerSpell(cdTimer *core.Timer) { }, }, ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return warrior.OverpowerAura.IsActive() || (hasTasteForBloodRune && warrior.TasteForBloodAura.IsActive()) + return warrior.OverpowerAura.IsActive() }, BonusCritRating: 25 * core.CritRatingPerCritChance * float64(warrior.Talents.ImprovedOverpower), diff --git a/sim/warrior/quick_strike.go b/sim/warrior/quick_strike.go deleted file mode 100644 index 2b38b44fb..000000000 --- a/sim/warrior/quick_strike.go +++ /dev/null @@ -1,49 +0,0 @@ -package warrior - -import ( - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -func (warrior *Warrior) registerQuickStrike() { - if !warrior.HasRune(proto.WarriorRune_RuneQuickStrike) { - return - } - - warrior.QuickStrike = warrior.RegisterSpell(AnyStance, core.SpellConfig{ - ActionID: core.ActionID{SpellID: 429765}, - SpellSchool: core.SpellSchoolPhysical, - DefenseType: core.DefenseTypeMelee, - ProcMask: core.ProcMaskMeleeMHSpecial, - Flags: core.SpellFlagMeleeMetrics | core.SpellFlagAPL | SpellFlagOffensive, - - RageCost: core.RageCostOptions{ - Cost: 20 - float64(warrior.Talents.ImprovedHeroicStrike), - Refund: 0.8, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return warrior.MainHand().HandType == proto.HandType_HandTypeTwoHand - }, - - CritDamageBonus: warrior.impale(), - - DamageMultiplier: 1, - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := sim.Roll(0.25, 0.35) * spell.MeleeAttackPower() - - result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) - - if !result.Landed() { - spell.IssueRefund(sim) - } - }, - }) -} diff --git a/sim/warrior/raging_blow.go b/sim/warrior/raging_blow.go deleted file mode 100644 index c6a121942..000000000 --- a/sim/warrior/raging_blow.go +++ /dev/null @@ -1,66 +0,0 @@ -package warrior - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -// A ferocious strike that deals 100% weapon damage, but can only be used while Enrage, Berserker Rage, or Bloodrage is active. -// Raging blow cooldown is reduced by 1 second when you use another melee ability while enraged. -func (warrior *Warrior) registerRagingBlow() { - if !warrior.HasRune(proto.WarriorRune_RuneRagingBlow) { - return - } - - warrior.RegisterAura(core.Aura{ - Label: "Raging Blow CDR", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - if spell.ProcMask.Matches(core.ProcMaskMeleeSpecial) && warrior.IsEnraged() && !warrior.RagingBlow.CD.IsReady(sim) && spell.SpellCode != SpellCode_WarriorRagingBlow { - warrior.RagingBlow.CD.Timer.Set(time.Duration(*warrior.RagingBlow.CD.Timer) - time.Second*1) - } - }, - }) - - warrior.RagingBlow = warrior.RegisterSpell(AnyStance, core.SpellConfig{ - SpellCode: SpellCode_WarriorRagingBlow, - ActionID: core.ActionID{SpellID: 402911}, - SpellSchool: core.SpellSchoolPhysical, - DefenseType: core.DefenseTypeMelee, - ProcMask: core.ProcMaskMeleeMHSpecial, - Flags: core.SpellFlagMeleeMetrics | core.SpellFlagAPL | SpellFlagOffensive, - - RageCost: core.RageCostOptions{ - Cost: 0, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - IgnoreHaste: true, - CD: core.Cooldown{ - Timer: warrior.NewTimer(), - Duration: time.Second * 8, - }, - }, - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return warrior.IsEnraged() - }, - - CritDamageBonus: warrior.impale(), - - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := spell.Unit.MHNormalizedWeaponDamage(sim, spell.MeleeAttackPower()) - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) - }, - }) -} diff --git a/sim/warrior/rampage.go b/sim/warrior/rampage.go deleted file mode 100644 index c3f9d4f29..000000000 --- a/sim/warrior/rampage.go +++ /dev/null @@ -1,56 +0,0 @@ -package warrior - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" - "github.com/wowsims/classic/sim/core/stats" -) - -// Go on a rampage, increasing your attack power by 10% for 30 sec. This ability can only be used while Enraged. -func (warrior *Warrior) registerRampage() { - if !warrior.HasRune(proto.WarriorRune_RuneRampage) { - return - } - - actionID := core.ActionID{SpellID: int32(proto.WarriorRune_RuneRampage)} - statDep := warrior.NewDynamicMultiplyStat(stats.AttackPower, 1.10) - - warrior.RampageAura = warrior.RegisterAura(core.Aura{ - Label: "Rampage", - ActionID: actionID, - Duration: time.Second * 30, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - warrior.EnableDynamicStatDep(sim, statDep) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - warrior.DisableDynamicStatDep(sim, statDep) - }, - }) - - warrior.Rampage = warrior.RegisterSpell(AnyStance, core.SpellConfig{ - ActionID: actionID, - Flags: core.SpellFlagAPL | core.SpellFlagCastTimeNoGCD, - - Cast: core.CastConfig{ - CD: core.Cooldown{ - Timer: warrior.NewTimer(), - Duration: time.Minute * 2, - }, - }, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return warrior.IsEnraged() - }, - - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { - warrior.RampageAura.Activate(sim) - }, - }) - - warrior.AddMajorCooldown(core.MajorCooldown{ - Type: core.CooldownTypeDPS, - Spell: warrior.Rampage.Spell, - }) -} diff --git a/sim/warrior/rend.go b/sim/warrior/rend.go index e11636971..b39ddce7e 100644 --- a/sim/warrior/rend.go +++ b/sim/warrior/rend.go @@ -4,15 +4,9 @@ import ( "time" "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" ) -// Blood Frenzy -// Rend can now be used in Berserker stance, Rend's damage is increased by 100%, -// and Rend deals additional damage equal to 3% of your Attack Power each time it deals damage. - func (warrior *Warrior) registerRendSpell() { - hasBloodFrenzyRune := warrior.HasRune(proto.WarriorRune_RuneBloodFrenzy) rend := map[int32]struct { ticks int32 @@ -26,9 +20,6 @@ func (warrior *Warrior) registerRendSpell() { }[warrior.Level] baseDamage := rend.damage - if hasBloodFrenzyRune { - baseDamage *= 2 - } damageMultiplier := []float64{1, 1.15, 1.25, 1.35}[warrior.Talents.ImprovedRend] @@ -60,12 +51,7 @@ func (warrior *Warrior) registerRendSpell() { NumberOfTicks: rend.ticks, TickLength: time.Second * 3, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - damage := baseDamage - if hasBloodFrenzyRune { - damage += .03 * dot.Spell.MeleeAttackPower() - } - - dot.Snapshot(target, damage, isRollover) + dot.Snapshot(target, baseDamage, isRollover) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) diff --git a/sim/warrior/revenge.go b/sim/warrior/revenge.go index 1209a83ca..4fecaa8ed 100644 --- a/sim/warrior/revenge.go +++ b/sim/warrior/revenge.go @@ -13,18 +13,11 @@ var RevengeBaseDamage = [RevengeRanks + 1][]float64{{0, 0}, {12, 14}, {18, 22}, var RevengeLevel = [RevengeRanks + 1]int{0, 14, 24, 34, 44, 54, 60} func (warrior *Warrior) registerRevengeSpell(cdTimer *core.Timer) { - rank := []int{ - 25: 2, - 40: 3, - 50: 4, - 60: core.TernaryInt(core.IncludeAQ, 6, 5), - }[warrior.Level] - actionID := core.ActionID{SpellID: RevengeSpellId[rank]} - basedamageLow := RevengeBaseDamage[rank][0] - basedamageHigh := RevengeBaseDamage[rank][1] - // Added in SoD phase 5 - apCoeff := 0.15 - cooldown := time.Second * 5 + actionID := core.ActionID{SpellID: core.TernaryInt32(core.IncludeAQ, 25288, 11601)} + has2pcDreadnaught := warrior.HasSetBonus(ItemSetDreadnaughtsBattlegear, 2) + basedamageLow := core.TernaryFloat64(core.IncludeAQ, 81, 64) + core.TernaryFloat64(has2pcDreadnaught, 75, 0) + basedamageHigh := core.TernaryFloat64(core.IncludeAQ, 99, 78) + core.TernaryFloat64(has2pcDreadnaught, 75, 0) + revengeLevel := core.TernaryFloat64(core.IncludeAQ, 60.0, 54.0) warrior.revengeProcAura = warrior.RegisterAura(core.Aura{ Label: "Revenge", @@ -64,7 +57,7 @@ func (warrior *Warrior) registerRevengeSpell(cdTimer *core.Timer) { IgnoreHaste: true, CD: core.Cooldown{ Timer: cdTimer, - Duration: cooldown, + Duration: time.Second * 5, }, }, ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { @@ -75,11 +68,11 @@ func (warrior *Warrior) registerRevengeSpell(cdTimer *core.Timer) { DamageMultiplier: 1, ThreatMultiplier: 2.25, - FlatThreatBonus: 2.25 * 2 * float64(RevengeLevel[rank]), + FlatThreatBonus: 2.25 * 2 * revengeLevel, BonusCoefficient: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := sim.Roll(basedamageLow, basedamageHigh) + apCoeff*spell.MeleeAttackPower() + baseDamage := sim.Roll(basedamageLow, basedamageHigh) result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) if !result.Landed() { diff --git a/sim/warrior/runes.go b/sim/warrior/runes.go deleted file mode 100644 index 33d8b65f4..000000000 --- a/sim/warrior/runes.go +++ /dev/null @@ -1,517 +0,0 @@ -package warrior - -import ( - "slices" - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" - "github.com/wowsims/classic/sim/core/stats" -) - -func (warrior *Warrior) ApplyRunes() { - // Head - warrior.applyVigilance() - warrior.applyEndlessRage() - warrior.applyShieldMastery() - warrior.applyTasteForBlood() - - // Cloak - warrior.applySuddenDeath() - warrior.applyFreshMeat() - warrior.registerShockwaveSpell() - - // Chest - warrior.applyFlagellation() - warrior.registerRagingBlow() - warrior.applyBloodFrenzy() - - // Bracers - warrior.registerRampage() - warrior.applySwordAndBoard() - warrior.applyWreckingCrew() - - // Gloves - warrior.applySingleMindedFury() - warrior.registerQuickStrike() - - // Waist - warrior.applyFocusedRage() - warrior.applyBloodSurge() - - // Pants - warrior.applyFrenziedAssault() - warrior.applyConsumedByRage() - // Furious Thunder implemented in thunder_clap.go - - // Boots - // Gladiator implemented on stances.go -} - -func (warrior *Warrior) applyVigilance() { - if !warrior.HasRune(proto.WarriorRune_RuneVigilance) { - return - } - - warrior.PseudoStats.ThreatMultiplier *= 1.1 -} - -func (warrior *Warrior) applyEndlessRage() { - if !warrior.HasRune(proto.WarriorRune_RuneEndlessRage) { - return - } - - warrior.AddDamageDealtRageMultiplier(1.25) -} - -func (warrior *Warrior) applyShieldMastery() { - if !warrior.HasRune(proto.WarriorRune_RuneShieldMastery) { - return - } - - buffAura := warrior.RegisterAura(core.Aura{ - Label: "Shield Mastery Buff", - Duration: core.NeverExpires, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - warrior.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] *= 1.1 - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - warrior.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] /= 1.1 - }, - }) - - // Use a hidden aura to periodically verify that the player is still using a 2H weapon mid-sim, for example if Item Swapping - warrior.RegisterAura(core.Aura{ - Label: "Shield Mastery Dummy", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - core.StartPeriodicAction(sim, core.PeriodicActionOptions{ - Period: time.Second * 2, - TickImmediately: true, - OnAction: func(sim *core.Simulation) { - if !warrior.PseudoStats.CanBlock { - buffAura.Deactivate(sim) - return - } - - buffAura.Activate(sim) - }, - }) - }, - }) -} - -// You gain Rage from Physical damage taken as if you were wearing no armor. -func (warrior *Warrior) applyFlagellation() { - if !warrior.HasRune(proto.WarriorRune_RuneFlagellation) { - return - } - - // TODO: Rage gain from hits -} - -func (warrior *Warrior) applyBloodFrenzy() { - if !warrior.HasRune(proto.WarriorRune_RuneBloodFrenzy) { - return - } - - core.MakePermanent(warrior.RegisterAura(core.Aura{ - Label: "Blood Frenzy Dummy", - OnInit: func(aura *core.Aura, sim *core.Simulation) { - warrior.Rend.StanceMask |= BerserkerStance - }, - })) -} - -func (warrior *Warrior) applyFrenziedAssault() { - if !warrior.HasRune(proto.WarriorRune_RuneFrenziedAssault) { - return - } - - actionID := core.ActionID{SpellID: 431046} - rageMetrics := warrior.NewRageMetrics(actionID) - - buffAura := warrior.RegisterAura(core.Aura{ - ActionID: actionID, - Label: "Frenzied Assault", - Duration: core.NeverExpires, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - warrior.MultiplyMeleeSpeed(sim, 1.3) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - warrior.MultiplyMeleeSpeed(sim, 1/1.3) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.ProcMask.Matches(core.ProcMaskWhiteHit) && result.Landed() { - warrior.AddRage(sim, core.Ternary(result.DidCrit(), 4.0, 2.0), rageMetrics) - } - }, - }) - - // Use a dummy aura to periodically verify that the player is still using a 2H weapon mid-sim, for example if Item Swapping - warrior.RegisterAura(core.Aura{ - Label: "Frenzied Assault Dummy", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - core.StartPeriodicAction(sim, core.PeriodicActionOptions{ - Period: time.Second * 2, - TickImmediately: true, - OnAction: func(sim *core.Simulation) { - if warrior.MainHand().HandType != proto.HandType_HandTypeTwoHand { - buffAura.Deactivate(sim) - return - } - - if !buffAura.IsActive() { - buffAura.Activate(sim) - } - }, - }) - }, - }) -} - -// Enrages you (activating abilities which require being Enraged) for 12 sec after you exceed 60 Rage. -// In addition, Whirlwind also strikes with off-hand melee weapons while you are Enraged -func (warrior *Warrior) applyConsumedByRage() { - if !warrior.HasRune(proto.WarriorRune_RuneConsumedByRage) { - return - } - - warrior.ConsumedByRageAura = warrior.GetOrRegisterAura(core.Aura{ - Label: "Enrage (Consumed by Rage)", - ActionID: core.ActionID{SpellID: 425415}, - Duration: time.Second * 12, - }) - - warrior.ConsumedByRageAura.NewExclusiveEffect("Enrage", true, core.ExclusiveEffect{Priority: 0}) - - warrior.RegisterAura(core.Aura{ - Label: "Consumed By Rage Trigger", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnRageChange: func(aura *core.Aura, sim *core.Simulation, metrics *core.ResourceMetrics) { - // Refunding rage should not enable CBR - if warrior.CurrentRage() < 60 || metrics.ActionID.OtherID == proto.OtherAction_OtherActionRefund { - return - } - - warrior.ConsumedByRageAura.Activate(sim) - }, - }) -} - -func (warrior *Warrior) applyFocusedRage() { - if !warrior.HasRune(proto.WarriorRune_RuneFocusedRage) { - return - } - - warrior.OnSpellRegistered(func(spell *core.Spell) { - if spell.Flags.Matches(SpellFlagOffensive) && spell.Cost != nil { - spell.Cost.FlatModifier -= 3 - } - }) -} - -func (warrior *Warrior) applyBloodSurge() { - if !warrior.HasRune(proto.WarriorRune_RuneBloodSurge) { - return - } - - warrior.BloodSurgeAura = warrior.RegisterAura(core.Aura{ - Label: "Blood Surge Proc", - ActionID: core.ActionID{SpellID: 413399}, - Duration: time.Second * 15, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - warrior.Slam.CastTimeMultiplier -= 1 - warrior.Slam.Cost.Multiplier -= 100 - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - warrior.Slam.CastTimeMultiplier += 1 - warrior.Slam.Cost.Multiplier += 100 - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - // removed even if slam doesn't land - if spell.SpellCode == SpellCode_WarriorSlamMH { - aura.Deactivate(sim) - } - }, - }) - - affectedSpells := make(map[*core.Spell]bool) - - core.MakePermanent(warrior.RegisterAura(core.Aura{ - Label: "Blood Surge Trigger", - OnInit: func(aura *core.Aura, sim *core.Simulation) { - if warrior.Slam == nil { - aura.Deactivate(sim) - return - } - - affectedSpells[warrior.HeroicStrike.Spell] = true - affectedSpells[warrior.Whirlwind.Spell] = true - - if warrior.Bloodthirst != nil { - affectedSpells[warrior.Bloodthirst.Spell] = true - } - - if warrior.HasRune(proto.WarriorRune_RuneQuickStrike) { - affectedSpells[warrior.QuickStrike.Spell] = true - } - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if result.Landed() && affectedSpells[spell] && sim.Proc(0.3, "Blood Surge") { - warrior.BloodSurgeAura.Activate(sim) - } - }, - })) -} - -func (warrior *Warrior) applyTasteForBlood() { - if !warrior.HasRune(proto.WarriorRune_RuneTasteForBlood) { - return - } - - warrior.TasteForBloodAura = warrior.RegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 426969}, - Label: "Taste for Blood", - Duration: time.Second * 9, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - if spell.SpellCode == SpellCode_WarriorOverpower { - aura.Deactivate(sim) - } - }, - }) - - icd := core.Cooldown{ - Timer: warrior.NewTimer(), - Duration: time.Millisecond * 5800, - } - - core.MakePermanent(warrior.RegisterAura(core.Aura{ - Label: "Taste for Blood Trigger", - OnPeriodicDamageDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.SpellCode == SpellCode_WarriorRend && icd.IsReady(sim) { - icd.Use(sim) - warrior.TasteForBloodAura.Activate(sim) - } - }, - })) -} - -// Your melee hits have a 10% chance to grant Sudden Death. Sudden Death allows one use of Execute regardless of the target's health state. -// When Execute is enabled by Sudden Death, you will retain 10 rage after using Execute. -func (warrior *Warrior) applySuddenDeath() { - if !warrior.HasRune(proto.WarriorRune_RuneSuddenDeath) { - return - } - - rageMetrics := warrior.NewRageMetrics(core.ActionID{SpellID: int32(proto.WarriorRune_RuneSuddenDeath)}) - - minRageKept := 10.0 - procChance := 0.10 - - warrior.SuddenDeathAura = warrior.RegisterAura(core.Aura{ - Label: "Sudden Death", - ActionID: core.ActionID{SpellID: 440114}, - Duration: time.Second * 10, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if result.Landed() && spell.SpellCode == SpellCode_WarriorExecute { // removed only when landed - warrior.AddRage(sim, minRageKept, rageMetrics) - aura.Deactivate(sim) - } - }, - }) - - warrior.RegisterAura(core.Aura{ - Label: "Sudden Death Trigger", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if !result.Landed() || !spell.ProcMask.Matches(core.ProcMaskMelee) { - return - } - - if sim.Proc(procChance, "Sudden Death") { - warrior.SuddenDeathAura.Activate(sim) - } - }, - }) -} - -// Damaging a target with Bloodthirst has a 100% chance the first time and a 10% chance each subsequent time to -// Enrage you (activating abilities which requiring being Enraged), and cause you to deal 10% increased Physical damage for 12 sec. -func (warrior *Warrior) applyFreshMeat() { - if !warrior.HasRune(proto.WarriorRune_RuneFreshMeat) { - return - } - - warrior.FreshMeatEnrageAura = warrior.RegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 14201}, - Label: "Enrage (Fresh Meat)", - Duration: time.Second * 12, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - warrior.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] *= 1.1 - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - warrior.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] /= 1.1 - }, - }) - - var damagedUnits map[int32]bool - affectedSpellCodes := []int32{SpellCode_WarriorBloodthirst, SpellCode_WarriorMortalStrike, SpellCode_WarriorShieldSlam} - - warrior.RegisterAura(core.Aura{ - Label: "Fresh Meat Trigger", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - damagedUnits = make(map[int32]bool) - aura.Activate(sim) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if !slices.Contains(affectedSpellCodes, spell.SpellCode) { - return - } - - procChance := 0.10 - if !damagedUnits[result.Target.UnitIndex] { - procChance = 1.00 - damagedUnits[result.Target.UnitIndex] = true - } - - if sim.Proc(procChance, "Fresh Meat") { - warrior.FreshMeatEnrageAura.Activate(sim) - } - }, - }) -} - -// Your melee critical hits Enrage you (activating abilities which require being Enraged), and increase Mortal Strike, Bloodthirst, and Shield Slam damage by 10% for 12 sec. -func (warrior *Warrior) applyWreckingCrew() { - if !warrior.HasRune(proto.WarriorRune_RuneWreckingCrew) { - return - } - - var affectedSpells []*WarriorSpell - warrior.WreckingCrewEnrageAura = warrior.RegisterAura(core.Aura{ - Label: "Enrage (Wrecking Crew)", - ActionID: core.ActionID{SpellID: 427066}, - Duration: time.Second * 6, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - affectedSpells = core.FilterSlice( - []*WarriorSpell{warrior.MortalStrike, warrior.Bloodthirst, warrior.ShieldSlam}, - func(spell *WarriorSpell) bool { return spell != nil }, - ) - }, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - for _, spell := range affectedSpells { - spell.DamageMultiplier *= 1.1 - } - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - for _, spell := range affectedSpells { - spell.DamageMultiplier /= 1.1 - } - }, - }) - - warrior.RegisterAura(core.Aura{ - Label: "Wrecking Crew Trigger", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.ProcMask.Matches(core.ProcMaskMelee) && result.Outcome.Matches(core.OutcomeCrit) { - warrior.WreckingCrewEnrageAura.Activate(sim) - } - }, - }) -} - -func (warrior *Warrior) applySwordAndBoard() { - if !warrior.HasRune(proto.WarriorRune_RuneSwordAndBoard) || !warrior.Talents.ShieldSlam { - return - } - - sabAura := warrior.GetOrRegisterAura(core.Aura{ - Label: "Sword And Board", - ActionID: core.ActionID{SpellID: int32(proto.WarriorRune_RuneSwordAndBoard)}, - Duration: 5 * time.Second, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - warrior.ShieldSlam.Cost.Multiplier -= 100 - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - warrior.ShieldSlam.Cost.Multiplier += 100 - }, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - if spell.SpellCode == SpellCode_WarriorShieldSlam { - aura.Deactivate(sim) - } - }, - }) - - core.MakePermanent(warrior.GetOrRegisterAura(core.Aura{ - Label: "Sword And Board Trigger", - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if !result.Landed() { - return - } - - if spell.SpellCode != SpellCode_WarriorRevenge && spell.SpellCode != SpellCode_WarriorDevastate { - return - } - - if sim.Proc(0.3, "Sword And Board") { - sabAura.Activate(sim) - warrior.ShieldSlam.CD.Reset() - } - }, - })) -} - -// While dual-wielding, your movement speed is increased by 10% and you gain 2% attack speed each time your melee auto-attack strikes the same target as your previous auto-attack, stacking up to 5 times. -// Lasts 10 sec or until your auto-attack strikes a different target. -func (warrior *Warrior) applySingleMindedFury() { - if !warrior.HasRune(proto.WarriorRune_RuneSingleMindedFury) { - return - } - - buffAura := warrior.RegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 461470}, - Label: "Single-Minded Fury Attack Speed", - Duration: time.Second * 10, - MaxStacks: 5, - OnStacksChange: func(aura *core.Aura, sim *core.Simulation, oldStacks int32, newStacks int32) { - warrior.MultiplyAttackSpeed(sim, 1/(1+.02*float64(oldStacks))) - warrior.MultiplyAttackSpeed(sim, 1+.02*float64(newStacks)) - }, - }) - - warrior.RegisterAura(core.Aura{ - Label: "Single-Minded Fury Trigger", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - warrior.lastMeleeAutoTarget = nil - aura.Activate(sim) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if !warrior.AutoAttacks.IsDualWielding || !result.Landed() || !spell.ProcMask.Matches(core.ProcMaskMeleeWhiteHit) { - return - } - - if warrior.lastMeleeAutoTarget == nil || warrior.lastMeleeAutoTarget != result.Target { - warrior.lastMeleeAutoTarget = result.Target - buffAura.Deactivate(sim) - return - } - - buffAura.Activate(sim) - buffAura.AddStack(sim) - }, - }) -} diff --git a/sim/warrior/shield_slam.go b/sim/warrior/shield_slam.go index fe82ea003..7d7ac0571 100644 --- a/sim/warrior/shield_slam.go +++ b/sim/warrior/shield_slam.go @@ -4,7 +4,6 @@ import ( "time" "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/stats" ) func (warrior *Warrior) registerShieldSlamSpell() { @@ -12,24 +11,16 @@ func (warrior *Warrior) registerShieldSlamSpell() { return } - rank := map[int32]struct { - spellID int32 - damageLow float64 - damageHigh float64 - threat float64 - }{ - 40: {spellID: 23922, damageLow: 225, damageHigh: 235, threat: 178}, - 50: {spellID: 23923, damageLow: 264, damageHigh: 276, threat: 203}, - 60: {spellID: 23925, damageLow: 342, damageHigh: 358, threat: 254}, - }[warrior.Level] + spellID := int32(23925) + damageLow := 342.0 + damageHigh := 358.0 + threat := 254.0 apCoef := 0.15 - defendersResolveAura := core.DefendersResolveAttackPower(warrior.GetCharacter()) - warrior.ShieldSlam = warrior.RegisterSpell(AnyStance, core.SpellConfig{ SpellCode: SpellCode_WarriorShieldSlam, - ActionID: core.ActionID{SpellID: rank.spellID}, + ActionID: core.ActionID{SpellID: spellID}, SpellSchool: core.SpellSchoolPhysical, DefenseType: core.DefenseTypeMelee, ProcMask: core.ProcMaskMeleeMHSpecial, // TODO really? @@ -56,22 +47,15 @@ func (warrior *Warrior) registerShieldSlamSpell() { CritDamageBonus: warrior.impale(), DamageMultiplier: 1, - ThreatMultiplier: 2, - FlatThreatBonus: rank.threat * 2, + ThreatMultiplier: 1, + FlatThreatBonus: threat * 2, BonusCoefficient: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - damage := sim.Roll(rank.damageLow, rank.damageHigh) + warrior.BlockValue()*2 + apCoef*spell.MeleeAttackPower() + damage := sim.Roll(damageLow, damageHigh) + warrior.BlockValue()*2 + apCoef*spell.MeleeAttackPower() result := spell.CalcAndDealDamage(sim, target, damage, spell.OutcomeMeleeSpecialHitAndCrit) - if result.Landed() { - if stacks := int32(warrior.GetStat(stats.Defense)); stacks > 0 { - defendersResolveAura.Activate(sim) - if defendersResolveAura.GetStacks() != stacks { - defendersResolveAura.SetStacks(sim, stacks) - } - } - } else { + if !result.Landed() { spell.IssueRefund(sim) } }, diff --git a/sim/warrior/shockwave.go b/sim/warrior/shockwave.go deleted file mode 100644 index 60af30940..000000000 --- a/sim/warrior/shockwave.go +++ /dev/null @@ -1,50 +0,0 @@ -package warrior - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -func (warrior *Warrior) registerShockwaveSpell() { - if !warrior.HasRune(proto.WarriorRune_RuneShockwave) { - return - } - - apCoef := 0.50 - - warrior.Shockwave = warrior.RegisterSpell(DefensiveStance, core.SpellConfig{ - ActionID: core.ActionID{SpellID: int32(proto.WarriorRune_RuneShockwave)}, - SpellSchool: core.SpellSchoolPhysical, - DefenseType: core.DefenseTypeRanged, - ProcMask: core.ProcMaskRangedSpecial, - Flags: core.SpellFlagMeleeMetrics | core.SpellFlagAPL | SpellFlagOffensive, - - RageCost: core.RageCostOptions{ - Cost: 15, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - CD: core.Cooldown{ - Timer: warrior.NewTimer(), - Duration: 20 * time.Second, - }, - }, - - CritDamageBonus: warrior.impale(), - - DamageMultiplier: 1, - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := apCoef * spell.MeleeAttackPower() - for _, aoeTarget := range sim.Encounter.TargetUnits { - // Shockwave can miss and be blocked, but it can't be dodged or parried - spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMeleeSpecialNoDodgeParry) - } - }, - }) -} diff --git a/sim/warrior/shouts.go b/sim/warrior/shouts.go index a356f4c20..fb3cb27d4 100644 --- a/sim/warrior/shouts.go +++ b/sim/warrior/shouts.go @@ -40,9 +40,10 @@ func (warrior *Warrior) newShoutSpellConfig(actionID core.ActionID, rank int32, func (warrior *Warrior) registerBattleShout() { rank := core.TernaryInt32(core.IncludeAQ, 7, 6) actionId := core.BattleShoutSpellId[rank] + has3pcWrath := warrior.HasSetBonus(ItemSetBattleGearOfWrath, 3) warrior.BattleShout = warrior.newShoutSpellConfig(core.ActionID{SpellID: actionId}, rank, warrior.NewPartyAuraArray(func(unit *core.Unit) *core.Aura { - return core.BattleShoutAura(unit, warrior.Talents.ImprovedBattleShout, warrior.Talents.BoomingVoice) + return core.BattleShoutAura(unit, warrior.Talents.ImprovedBattleShout, warrior.Talents.BoomingVoice, has3pcWrath) })) } diff --git a/sim/warrior/slam.go b/sim/warrior/slam.go index 5ec5c623e..5809437de 100644 --- a/sim/warrior/slam.go +++ b/sim/warrior/slam.go @@ -4,45 +4,12 @@ import ( "time" "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" ) func (warrior *Warrior) registerSlamSpell() { - if warrior.Level < 30 { - return - } - - hasBloodSurgeRune := warrior.HasRune(proto.WarriorRune_RuneBloodSurge) - - var castTime time.Duration - var cooldown core.Cooldown - if warrior.HasRune(proto.WarriorRune_RunePreciseTiming) { - castTime = 0 - cooldown = core.Cooldown{ - Timer: warrior.NewTimer(), - Duration: 6 * time.Second, - } - } else { - castTime = time.Millisecond*1500 - time.Millisecond*100*time.Duration(warrior.Talents.ImprovedSlam) - } - - requiredLevel := map[int32]int{ - 40: 38, - 50: 46, - 60: 54, - }[warrior.Level] - - spellID := map[int32]int32{ - 40: 8820, - 50: 11604, - 60: 11605, - }[warrior.Level] - - warrior.SlamMH = warrior.newSlamHitSpell(true) - canHitOffhand := hasBloodSurgeRune && warrior.AutoAttacks.IsDualWielding - if canHitOffhand { - warrior.SlamOH = warrior.newSlamHitSpell(false) - } + requiredLevel := 54 + spellID := int32(11605) + flatDamageBonus := 87.0 warrior.Slam = warrior.RegisterSpell(AnyStance, core.SpellConfig{ SpellCode: SpellCode_WarriorSlam, @@ -61,9 +28,8 @@ func (warrior *Warrior) registerSlamSpell() { Cast: core.CastConfig{ DefaultCast: core.Cast{ GCD: core.GCDDefault, - CastTime: castTime, + CastTime: time.Millisecond*1500 - time.Millisecond*100*time.Duration(warrior.Talents.ImprovedSlam), }, - CD: cooldown, ModifyCast: func(sim *core.Simulation, spell *core.Spell, cast *core.Cast) { if spell.CastTime() > 0 { warrior.AutoAttacks.StopMeleeUntil(sim, sim.CurrentTime+cast.CastTime, true) @@ -71,66 +37,20 @@ func (warrior *Warrior) registerSlamSpell() { }, }, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - doOffhandHit := canHitOffhand && warrior.BloodSurgeAura.IsActive() - warrior.SlamMH.Cast(sim, target) - if doOffhandHit { - warrior.SlamOH.Cast(sim, target) - } - }, - }) -} - -func (warrior *Warrior) newSlamHitSpell(isMH bool) *WarriorSpell { - spellID := map[int32]int32{ - 40: 8820, - 50: 11604, - 60: 11605, - }[warrior.Level] - - requiredLevel := map[int32]float64{ - 40: 38, - 50: 46, - 60: 54, - }[warrior.Level] - - flatDamageBonus := map[int32]float64{ - 40: 43, - 50: 68, - 60: 87, - }[warrior.Level] - - procMask := core.ProcMaskMeleeMHSpecial - flags := core.SpellFlagMeleeMetrics | core.SpellFlagNoOnCastComplete - damageFunc := warrior.MHWeaponDamage - if !isMH { - flatDamageBonus /= 2 - procMask = core.ProcMaskMeleeOHSpecial - flags |= core.SpellFlagPassiveSpell - damageFunc = warrior.OHWeaponDamage - } - - return warrior.RegisterSpell(AnyStance, core.SpellConfig{ - SpellCode: core.Ternary(isMH, SpellCode_WarriorSlamMH, SpellCode_WarriorSlamOH), - ActionID: core.ActionID{SpellID: spellID}.WithTag(int32(core.Ternary(isMH, 1, 2))), - SpellSchool: core.SpellSchoolPhysical, - DefenseType: core.DefenseTypeMelee, - ProcMask: procMask, - Flags: flags, - CritDamageBonus: warrior.impale(), - FlatThreatBonus: 1 * requiredLevel, DamageMultiplier: 1, ThreatMultiplier: 1, + FlatThreatBonus: 140, // Should this be 54 or the old 140 value from before SoD? BonusCoefficient: 1, + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := flatDamageBonus + damageFunc(sim, spell.MeleeAttackPower()) - result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) + baseDamage := flatDamageBonus + spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) - if isMH && !result.Landed() { - warrior.Slam.IssueRefund(sim) + result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) + if !result.Landed() { + spell.IssueRefund(sim) } }, }) diff --git a/sim/warrior/stances.go b/sim/warrior/stances.go index 6da7e21fd..9837f11a5 100644 --- a/sim/warrior/stances.go +++ b/sim/warrior/stances.go @@ -4,7 +4,6 @@ import ( "time" "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" "github.com/wowsims/classic/sim/core/stats" ) @@ -14,16 +13,15 @@ const ( BattleStance Stance = 1 << iota DefensiveStance BerserkerStance - GladiatorStance - AnyStance = BattleStance | DefensiveStance | BerserkerStance | GladiatorStance + AnyStance = BattleStance | DefensiveStance | BerserkerStance ) func (stance Stance) Matches(other Stance) bool { return (stance & other) != 0 } -var StanceCodes = []int32{SpellCode_WarriorStanceBattle, SpellCode_WarriorStanceDefensive, SpellCode_WarriorStanceBerserker, SpellCode_WarriorStanceGladiator} +var StanceCodes = []int32{SpellCode_WarriorStanceBattle, SpellCode_WarriorStanceDefensive, SpellCode_WarriorStanceBerserker} const stanceEffectCategory = "Stance" @@ -36,7 +34,6 @@ func (warrior *Warrior) makeStanceSpell(stance Stance, aura *core.Aura, stanceCD BattleStance: SpellCode_WarriorStanceBattle, DefensiveStance: SpellCode_WarriorStanceDefensive, BerserkerStance: SpellCode_WarriorStanceBerserker, - GladiatorStance: SpellCode_WarriorStanceGladiator, }[stance] actionID := aura.ActionID maxRetainedRage := 5 * float64(warrior.Talents.TacticalMastery) @@ -141,112 +138,13 @@ func (warrior *Warrior) registerBerserkerStanceAura() { }) } -// An aggressive stance that increases damage while you are wearing a shield by 10% and increases block chance by 10%, but reduces armor by 30% and threat generated by 30%. -// In addition, you gain 50% increased Rage when your auto-attack damages an enemy not targeting you. -// While wearing a shield in Gladiator Stance, you may use all abilities that are restricted to other stances. -func (warrior *Warrior) registerGladiatorStanceAura() { - if !warrior.HasRune(proto.WarriorRune_RuneGladiatorStance) { - return - } - - warrior.gladiatorStanceDamageMultiplier = 1.1 - isTanking := warrior.IsTanking() - - gladStanceDamageAura := warrior.RegisterAura(core.Aura{ - Label: "Gladiator Stance Damage Bonus", - Duration: core.NeverExpires, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.PseudoStats.DamageDealtMultiplier *= warrior.gladiatorStanceDamageMultiplier - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.PseudoStats.DamageDealtMultiplier /= warrior.gladiatorStanceDamageMultiplier - }, - }) - - // Use a periodic action aura to verify that the player is still using a shield for the damage bonus and stance override. - // This is needed if Item Swapping. - var gladStanceValidationPA *core.PendingAction - var gladStanceStanceOverrideEE *core.ExclusiveEffect - gladStanceValidationAura := warrior.RegisterAura(core.Aura{ - Label: "Gladiator Stance Shield Validation", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - gladStanceValidationPA = nil - }, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - gladStanceValidationPA = core.StartPeriodicAction(sim, core.PeriodicActionOptions{ - Period: time.Second * 2, - TickImmediately: true, - OnAction: func(sim *core.Simulation) { - if warrior.GladiatorStanceAura.IsActive() && warrior.PseudoStats.CanBlock { - if !gladStanceDamageAura.IsActive() { - gladStanceDamageAura.Activate(sim) - } - if !gladStanceStanceOverrideEE.IsActive() { - gladStanceStanceOverrideEE.Activate(sim) - } - } else { - gladStanceDamageAura.Deactivate(sim) - gladStanceStanceOverrideEE.Deactivate(sim) - return - } - }, - }) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - if gladStanceValidationPA != nil { - gladStanceValidationPA.Cancel(sim) - } - if gladStanceDamageAura.IsActive() { - gladStanceDamageAura.Deactivate(sim) - } - if !gladStanceStanceOverrideEE.IsActive() { - gladStanceStanceOverrideEE.Activate(sim) - } - }, - }) - - warrior.GladiatorStanceAura = warrior.RegisterAura(core.Aura{ - Label: "Gladiator Stance", - ActionID: core.ActionID{SpellID: int32(proto.WarriorRune_RuneGladiatorStance)}, - Duration: core.NeverExpires, - }) - warrior.GladiatorStanceAura.NewExclusiveEffect(stanceEffectCategory, true, core.ExclusiveEffect{ - OnGain: func(ee *core.ExclusiveEffect, sim *core.Simulation) { - ee.Aura.Unit.AddStatDynamic(sim, stats.Block, 10*core.BlockRatingPerBlockChance) - ee.Aura.Unit.PseudoStats.ArmorMultiplier *= 0.7 - ee.Aura.Unit.PseudoStats.ThreatMultiplier *= 0.7 - if !isTanking { - warrior.AddDamageDealtRageMultiplier(1.5) - } - - gladStanceValidationAura.Activate(sim) - }, - OnExpire: func(ee *core.ExclusiveEffect, sim *core.Simulation) { - ee.Aura.Unit.AddStatDynamic(sim, stats.Block, -10*core.BlockRatingPerBlockChance) - ee.Aura.Unit.PseudoStats.ArmorMultiplier /= 0.7 - ee.Aura.Unit.PseudoStats.ThreatMultiplier /= 0.7 - if !isTanking { - warrior.AddDamageDealtRageMultiplier(1 / 1.5) - } - - gladStanceValidationAura.Deactivate(sim) - }, - }) - gladStanceStanceOverrideEE = warrior.newStanceOverrideExclusiveEffect(AnyStance, warrior.GladiatorStanceAura) -} - func (warrior *Warrior) registerStances() { warrior.Stances = make([]*WarriorSpell, 0) stanceCD := warrior.NewTimer() warrior.registerBattleStanceAura() warrior.registerDefensiveStanceAura() warrior.registerBerserkerStanceAura() - warrior.registerGladiatorStanceAura() warrior.BattleStance = warrior.makeStanceSpell(BattleStance, warrior.BattleStanceAura, stanceCD) warrior.DefensiveStance = warrior.makeStanceSpell(DefensiveStance, warrior.DefensiveStanceAura, stanceCD) warrior.BerserkerStance = warrior.makeStanceSpell(BerserkerStance, warrior.BerserkerStanceAura, stanceCD) - if warrior.HasRune(proto.WarriorRune_RuneGladiatorStance) { - warrior.GladiatorStance = warrior.makeStanceSpell(GladiatorStance, warrior.GladiatorStanceAura, stanceCD) - } } diff --git a/sim/warrior/sunder_armor.go b/sim/warrior/sunder_armor.go index 14d2a2ab5..12888718a 100644 --- a/sim/warrior/sunder_armor.go +++ b/sim/warrior/sunder_armor.go @@ -2,64 +2,19 @@ package warrior import ( "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" ) -func (warrior *Warrior) registerSunderArmorSpell() *WarriorSpell { +func (warrior *Warrior) registerSunderArmorSpell() { warrior.SunderArmorAuras = warrior.NewEnemyAuraArray(core.SunderArmorAura) - spellID := map[int32]int32{ - 25: 7405, - 40: 8380, - 50: 11596, - 60: 11597, - }[warrior.Level] + spellID := int32(11597) - spell_level := map[int32]int32{ - 25: 22, - 40: 34, - 50: 46, - 60: 58, - }[warrior.Level] + spell_level := 58 - var effectiveStacks int32 var canApplySunder bool - if warrior.HasRune(proto.WarriorRune_RuneDevastate) { - warrior.Devastate = warrior.RegisterSpell(DefensiveStance, core.SpellConfig{ - SpellCode: SpellCode_WarriorDevastate, - ActionID: core.ActionID{SpellID: int32(proto.WarriorRune_RuneDevastate)}, - SpellSchool: core.SpellSchoolPhysical, - DefenseType: core.DefenseTypeMelee, - ProcMask: core.ProcMaskMeleeMHSpecial, // TODO check whether this can actually proc stuff or not - Flags: core.SpellFlagMeleeMetrics | SpellFlagOffensive, - CritDamageBonus: warrior.impale(), - DamageMultiplier: 1.5, - ThreatMultiplier: 1, - BonusCoefficient: 1, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return warrior.PseudoStats.CanBlock - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - threatMultiplier := 1.0 - if warrior.Stance == DefensiveStance { - threatMultiplier = 1.50 - } - spell.ThreatMultiplier *= threatMultiplier - - weapon := warrior.AutoAttacks.MH() - baseDamage := weapon.CalculateAverageWeaponDamage(spell.MeleeAttackPower()) / weapon.SwingSpeed - multiplier := 1 + 0.1*float64(effectiveStacks) - spell.CalcAndDealDamage(sim, target, baseDamage*multiplier, spell.OutcomeMeleeSpecialCritOnly) - spell.ThreatMultiplier /= threatMultiplier - }, - }) - } - - return warrior.RegisterSpell(AnyStance, core.SpellConfig{ + warrior.SunderArmor = warrior.RegisterSpell(AnyStance, core.SpellConfig{ ActionID: core.ActionID{SpellID: spellID}, SpellSchool: core.SpellSchoolPhysical, ProcMask: core.ProcMaskMeleeMHSpecial, @@ -78,16 +33,13 @@ func (warrior *Warrior) registerSunderArmorSpell() *WarriorSpell { ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { sa := warrior.SunderArmorAuras.Get(target) if sa.IsActive() { - effectiveStacks = sa.GetStacks() canApplySunder = true } else if sa.ExclusiveEffects[0].Category.AnyActive() { - effectiveStacks = sa.MaxStacks canApplySunder = false } else { - effectiveStacks = 0 canApplySunder = true } - return canApplySunder || warrior.Devastate != nil + return canApplySunder }, ThreatMultiplier: 1, @@ -97,16 +49,11 @@ func (warrior *Warrior) registerSunderArmorSpell() *WarriorSpell { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { result := spell.CalcAndDealOutcome(sim, target, spell.OutcomeMeleeWeaponSpecialNoCrit) // Cannot be blocked - if !result.Landed() { spell.IssueRefund(sim) return } - if warrior.Devastate != nil { - warrior.Devastate.Cast(sim, target) - } - if canApplySunder { sa := warrior.SunderArmorAuras.Get(target) sa.Activate(sim) diff --git a/sim/warrior/sweeping_strikes.go b/sim/warrior/sweeping_strikes.go index f1ecdd391..f3a68fd33 100644 --- a/sim/warrior/sweeping_strikes.go +++ b/sim/warrior/sweeping_strikes.go @@ -62,7 +62,7 @@ func (warrior *Warrior) registerSweepingStrikesCD() { var spellToUse *WarriorSpell - if spell.SpellCode == SpellCode_WarriorWhirlwindMH || (spell.SpellCode == SpellCode_WarriorExecute && !sim.IsExecutePhase20()) { + if spell.SpellCode == SpellCode_WarriorWhirlwind || (spell.SpellCode == SpellCode_WarriorExecute && !sim.IsExecutePhase20()) { spellToUse = hitSpecialNormalized } else { curDmg = result.Damage diff --git a/sim/warrior/tank_warrior/TestP1TankWarrior.results b/sim/warrior/tank_warrior/TestP1TankWarrior.results index 14fe50081..a0ada6a6b 100644 --- a/sim/warrior/tank_warrior/TestP1TankWarrior.results +++ b/sim/warrior/tank_warrior/TestP1TankWarrior.results @@ -50,7 +50,7 @@ character_stats_results: { stat_weights_results: { key: "TestP1TankWarrior-Phase1-StatWeights-Default" value: { - weights: 0.23674 + weights: 0.18451 weights: 0 weights: 0 weights: 0 @@ -67,7 +67,7 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.14335 + weights: 0.10765 weights: 0 weights: 0 weights: 0 @@ -96,101 +96,143 @@ stat_weights_results: { weights: 0 } } +dps_results: { + key: "TestP1TankWarrior-Phase1-AllItems-BattlegearofHeroism" + value: { + dps: 189.98621 + tps: 349.32605 + } +} +dps_results: { + key: "TestP1TankWarrior-Phase1-AllItems-BattlegearofMight" + value: { + dps: 177.17269 + tps: 327.56392 + } +} +dps_results: { + key: "TestP1TankWarrior-Phase1-AllItems-BattlegearofWrath" + value: { + dps: 174.45046 + tps: 324.94163 + } +} +dps_results: { + key: "TestP1TankWarrior-Phase1-AllItems-Conqueror'sBattlegear" + value: { + dps: 194.91338 + tps: 357.01103 + } +} +dps_results: { + key: "TestP1TankWarrior-Phase1-AllItems-Dreadnaught'sBattlegear" + value: { + dps: 181.27821 + tps: 335.07122 + } +} +dps_results: { + key: "TestP1TankWarrior-Phase1-AllItems-Vindicator'sBattlegear" + value: { + dps: 188.78846 + tps: 348.35385 + } +} dps_results: { key: "TestP1TankWarrior-Phase1-Average-Default" value: { - dps: 194.79525 - tps: 318.60109 + dps: 179.24733 + tps: 330.87664 } } dps_results: { key: "TestP1TankWarrior-Phase1-Settings-Human-p0.bis-Protection-p1-FullBuffs-P1-Consumes-LongMultiTarget" value: { - dps: 26.09262 - tps: 151.45021 + dps: 25.75113 + tps: 159.21102 } } dps_results: { key: "TestP1TankWarrior-Phase1-Settings-Human-p0.bis-Protection-p1-FullBuffs-P1-Consumes-LongSingleTarget" value: { - dps: 22.95352 - tps: 44.87155 + dps: 22.61203 + tps: 52.63236 } } dps_results: { key: "TestP1TankWarrior-Phase1-Settings-Human-p0.bis-Protection-p1-FullBuffs-P1-Consumes-ShortSingleTarget" value: { - dps: 33.04408 - tps: 64.7106 + dps: 32.2164 + tps: 74.5088 } } dps_results: { key: "TestP1TankWarrior-Phase1-Settings-Human-p0.bis-Protection-p1-NoBuffs-P1-Consumes-LongMultiTarget" value: { - dps: 11.63948 - tps: 129.84277 + dps: 11.61465 + tps: 138.07698 } } dps_results: { key: "TestP1TankWarrior-Phase1-Settings-Human-p0.bis-Protection-p1-NoBuffs-P1-Consumes-LongSingleTarget" value: { - dps: 9.27833 - tps: 24.42714 + dps: 9.2535 + tps: 32.66135 } } dps_results: { key: "TestP1TankWarrior-Phase1-Settings-Human-p0.bis-Protection-p1-NoBuffs-P1-Consumes-ShortSingleTarget" value: { - dps: 11.78536 - tps: 32.92882 + dps: 11.75958 + tps: 43.92587 } } dps_results: { key: "TestP1TankWarrior-Phase1-Settings-Orc-p0.bis-Protection-p1-FullBuffs-P1-Consumes-LongMultiTarget" value: { - dps: 27.57978 - tps: 148.67352 + dps: 27.10262 + tps: 156.2315 } } dps_results: { key: "TestP1TankWarrior-Phase1-Settings-Orc-p0.bis-Protection-p1-FullBuffs-P1-Consumes-LongSingleTarget" value: { - dps: 24.44069 - tps: 46.84486 + dps: 23.96352 + tps: 54.40283 } } dps_results: { key: "TestP1TankWarrior-Phase1-Settings-Orc-p0.bis-Protection-p1-FullBuffs-P1-Consumes-ShortSingleTarget" value: { - dps: 35.40788 - tps: 66.99448 + dps: 34.34516 + tps: 76.4413 } } dps_results: { key: "TestP1TankWarrior-Phase1-Settings-Orc-p0.bis-Protection-p1-NoBuffs-P1-Consumes-LongMultiTarget" value: { - dps: 12.34613 - tps: 125.89922 + dps: 12.3163 + tps: 134.12596 } } dps_results: { key: "TestP1TankWarrior-Phase1-Settings-Orc-p0.bis-Protection-p1-NoBuffs-P1-Consumes-LongSingleTarget" value: { - dps: 9.98498 - tps: 25.23359 + dps: 9.95515 + tps: 33.46033 } } dps_results: { key: "TestP1TankWarrior-Phase1-Settings-Orc-p0.bis-Protection-p1-NoBuffs-P1-Consumes-ShortSingleTarget" value: { - dps: 12.82153 - tps: 33.22788 + dps: 12.7905 + tps: 44.21708 } } dps_results: { key: "TestP1TankWarrior-Phase1-SwitchInFrontOfTarget-Default" value: { - dps: 164.70533 - tps: 271.42344 + dps: 151.74785 + tps: 284.17308 } } diff --git a/sim/warrior/thunder_clap.go b/sim/warrior/thunder_clap.go index 93ec384b0..83e6117f5 100644 --- a/sim/warrior/thunder_clap.go +++ b/sim/warrior/thunder_clap.go @@ -4,45 +4,24 @@ import ( "time" "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" ) -// Thunder Clap now increases the time between attacks by an additional 6%, can be used in any stance, deals 100% increased damage, and deals 50% increased threat. func (warrior *Warrior) registerThunderClapSpell() { - hasFuriousThunder := warrior.HasRune(proto.WarriorRune_RuneFuriousThunder) - - info := map[int32]struct { - spellID int32 - baseDamage float64 - duration time.Duration - }{ - 25: {spellID: 8198, baseDamage: 23, duration: time.Second * 14}, - 40: {spellID: 8205, baseDamage: 55, duration: time.Second * 22}, - 50: {spellID: 11580, baseDamage: 82, duration: time.Second * 26}, - 60: {spellID: 11581, baseDamage: 103, duration: time.Second * 30}, - }[warrior.Level] - - damageMultiplier := 1.0 - threatMultiplier := 2.5 - apCoef := 0.05 - attackSpeedReduction := int32(10) + spellID := int32(11581) + baseDamage := 103.0 + duration := time.Second * 30 + has5pcConq := warrior.HasSetBonus(ItemSetConquerorsBattleGear, 5) + attackSpeedReduction := core.TernaryInt32(has5pcConq, 15, 10) stanceMask := BattleStance - if hasFuriousThunder { - damageMultiplier *= 2 - threatMultiplier *= 1.5 - attackSpeedReduction += 6 - stanceMask = AnyStance - } - warrior.ThunderClapAuras = warrior.NewEnemyAuraArray(func(target *core.Unit) *core.Aura { - return core.ThunderClapAura(target, info.spellID, info.duration, attackSpeedReduction) + return core.ThunderClapAura(target, spellID, duration, attackSpeedReduction) }) results := make([]*core.SpellResult, min(4, warrior.Env.GetNumTargets())) warrior.ThunderClap = warrior.RegisterSpell(stanceMask, core.SpellConfig{ - ActionID: core.ActionID{SpellID: info.spellID}, + ActionID: core.ActionID{SpellID: spellID}, SpellSchool: core.SpellSchoolPhysical, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskSpellDamage, @@ -64,12 +43,12 @@ func (warrior *Warrior) registerThunderClapSpell() { CritDamageBonus: warrior.impale(), - DamageMultiplier: damageMultiplier, - ThreatMultiplier: threatMultiplier, + DamageMultiplier: core.TernaryFloat64(has5pcConq, 1.5, 1), + ThreatMultiplier: 2.5, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { for idx := range results { - results[idx] = spell.CalcDamage(sim, target, info.baseDamage+apCoef*spell.MeleeAttackPower(), spell.OutcomeMagicHitAndCrit) + results[idx] = spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) target = sim.Environment.NextTargetUnit(target) } diff --git a/sim/warrior/warrior.go b/sim/warrior/warrior.go index 4c6fc62be..0873d8a9a 100644 --- a/sim/warrior/warrior.go +++ b/sim/warrior/warrior.go @@ -18,24 +18,17 @@ const ( SpellCode_WarriorBloodthirst SpellCode_WarriorDeepWounds - SpellCode_WarriorDevastate SpellCode_WarriorExecute SpellCode_WarriorMortalStrike SpellCode_WarriorOverpower - SpellCode_WarriorRagingBlow SpellCode_WarriorRend SpellCode_WarriorRevenge SpellCode_WarriorShieldSlam SpellCode_WarriorSlam - SpellCode_WarriorSlamMH - SpellCode_WarriorSlamOH SpellCode_WarriorStanceBattle SpellCode_WarriorStanceBerserker - SpellCode_WarriorStanceGladiator SpellCode_WarriorStanceDefensive SpellCode_WarriorWhirlwind - SpellCode_WarriorWhirlwindMH - SpellCode_WarriorWhirlwindOH ) var TalentTreeSizes = [3]int{18, 17, 17} @@ -64,19 +57,12 @@ type Warrior struct { revengeProcAura *core.Aura OverpowerAura *core.Aura - BloodSurgeAura *core.Aura - RampageAura *core.Aura - SuddenDeathAura *core.Aura - TasteForBloodAura *core.Aura lastMeleeAutoTarget *core.Unit // Enrage Auras BerserkerRageAura *core.Aura BloodrageAura *core.Aura - ConsumedByRageAura *core.Aura EnrageAura *core.Aura - FreshMeatEnrageAura *core.Aura - WreckingCrewEnrageAura *core.Aura // Reaction time values reactionTime time.Duration @@ -92,7 +78,6 @@ type Warrior struct { BattleStance *WarriorSpell DefensiveStance *WarriorSpell BerserkerStance *WarriorSpell - GladiatorStance *WarriorSpell Bloodrage *WarriorSpell BerserkerRage *WarriorSpell @@ -107,24 +92,16 @@ type Warrior struct { ShieldBlock *WarriorSpell ShieldSlam *WarriorSpell Slam *WarriorSpell - SlamMH *WarriorSpell - SlamOH *WarriorSpell SunderArmor *WarriorSpell Devastate *WarriorSpell ThunderClap *WarriorSpell Whirlwind *WarriorSpell - WhirlwindMH *WarriorSpell - WhirlwindOH *WarriorSpell DeepWounds *WarriorSpell ConcussionBlow *WarriorSpell - RagingBlow *WarriorSpell Hamstring *WarriorSpell - Rampage *WarriorSpell - Shockwave *WarriorSpell HeroicStrike *WarriorSpell HeroicStrikeQueue *WarriorSpell - QuickStrike *WarriorSpell Cleave *WarriorSpell CleaveQueue *WarriorSpell curQueueAura *core.Aura @@ -133,10 +110,8 @@ type Warrior struct { BattleStanceAura *core.Aura DefensiveStanceAura *core.Aura BerserkerStanceAura *core.Aura - GladiatorStanceAura *core.Aura defensiveStanceThreatMultiplier float64 - gladiatorStanceDamageMultiplier float64 ShieldBlockAura *core.Aura @@ -187,46 +162,6 @@ func (warrior *Warrior) RegisterSpell(stanceMask Stance, config core.SpellConfig return ws } -func (warrior *Warrior) newStanceOverrideExclusiveEffect(stance Stance, aura *core.Aura) *core.ExclusiveEffect { - return aura.NewExclusiveEffect("stance-override", false, core.ExclusiveEffect{ - Priority: core.TernaryFloat64(stance == AnyStance, 2, 1), - OnGain: func(ee *core.ExclusiveEffect, sim *core.Simulation) { - if stance.Matches(BattleStance) { - for _, spell := range warrior.BattleStanceSpells { - spell.stanceOverride = true - } - } - if stance.Matches(DefensiveStance) { - for _, spell := range warrior.DefensiveStanceSpells { - spell.stanceOverride = true - } - } - if stance.Matches(BerserkerStance) { - for _, spell := range warrior.BerserkerStanceSpells { - spell.stanceOverride = true - } - } - }, - OnExpire: func(ee *core.ExclusiveEffect, sim *core.Simulation) { - if stance.Matches(BattleStance) { - for _, spell := range warrior.BattleStanceSpells { - spell.stanceOverride = false - } - } - if stance.Matches(DefensiveStance) { - for _, spell := range warrior.DefensiveStanceSpells { - spell.stanceOverride = false - } - } - if stance.Matches(BerserkerStance) { - for _, spell := range warrior.BerserkerStanceSpells { - spell.stanceOverride = false - } - } - }, - }) -} - func (warrior *Warrior) Initialize() { primaryTimer := warrior.NewTimer() overpowerRevengeTimer := warrior.NewTimer() @@ -244,6 +179,7 @@ func (warrior *Warrior) Initialize() { warrior.registerRevengeSpell(overpowerRevengeTimer) warrior.registerShieldSlamSpell() warrior.registerSlamSpell() + warrior.registerSunderArmorSpell() warrior.registerThunderClapSpell() warrior.registerWhirlwindSpell() warrior.registerRendSpell() @@ -258,7 +194,7 @@ func (warrior *Warrior) Initialize() { warrior.registerHeroicStrikeSpell(queuedRealismICD) warrior.registerCleaveSpell(queuedRealismICD) - warrior.SunderArmor = warrior.registerSunderArmorSpell() + warrior.registerBloodrageCD() warrior.RegisterRecklessnessCD() @@ -279,15 +215,8 @@ func (warrior *Warrior) Reset(sim *core.Simulation) { case proto.WarriorStance_WarriorStanceBerserker: warrior.Stance = BerserkerStance warrior.BerserkerStanceAura.Activate(sim) - case proto.WarriorStance_WarriorStanceGladiator: - warrior.Stance = GladiatorStance - warrior.GladiatorStanceAura.Activate(sim) default: - // Fallback to checking for Glad Stance rune or checking talent tree - if warrior.GladiatorStanceAura != nil { - warrior.Stance = GladiatorStance - warrior.GladiatorStanceAura.Activate(sim) - } else if warrior.PrimaryTalentTree == ArmsTree { + if warrior.PrimaryTalentTree == ArmsTree { warrior.Stance = BattleStance warrior.BattleStanceAura.Activate(sim) } else if warrior.PrimaryTalentTree == FuryTree { @@ -326,19 +255,6 @@ type WarriorAgent interface { GetWarrior() *Warrior } -func (warrior *Warrior) HasRune(rune proto.WarriorRune) bool { - return false // warrior.HasRuneById(int32(rune)) -} - -func (warrior *Warrior) IsEnraged() bool { - return warrior.BloodrageAura.IsActive() || - warrior.BerserkerRageAura.IsActive() || - (warrior.EnrageAura != nil && warrior.EnrageAura.IsActive()) || - (warrior.ConsumedByRageAura != nil && warrior.ConsumedByRageAura.IsActive()) || - (warrior.FreshMeatEnrageAura != nil && warrior.FreshMeatEnrageAura.IsActive()) || - (warrior.WreckingCrewEnrageAura != nil && warrior.WreckingCrewEnrageAura.IsActive()) -} - type WarriorSpell struct { *core.Spell StanceMask Stance diff --git a/sim/warrior/whirlwind.go b/sim/warrior/whirlwind.go index 6ca42c183..965fc0c86 100644 --- a/sim/warrior/whirlwind.go +++ b/sim/warrior/whirlwind.go @@ -4,21 +4,10 @@ import ( "time" "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" ) func (warrior *Warrior) registerWhirlwindSpell() { - if warrior.Level < 36 { - return - } - - hasConsumedByRageRune := warrior.HasRune(proto.WarriorRune_RuneConsumedByRage) - - warrior.WhirlwindMH = warrior.newWhirlwindHitSpell(true) - canHitOffhand := hasConsumedByRageRune && warrior.AutoAttacks.IsDualWielding - if canHitOffhand { - warrior.WhirlwindOH = warrior.newWhirlwindHitSpell(false) - } + results := make([]*core.SpellResult, min(4, warrior.Env.GetNumTargets())) warrior.Whirlwind = warrior.RegisterSpell(BerserkerStance, core.SpellConfig{ SpellCode: SpellCode_WarriorWhirlwind, @@ -41,34 +30,6 @@ func (warrior *Warrior) registerWhirlwindSpell() { Duration: time.Second * 10, }, }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, _ *core.Spell) { - for _, aoeTarget := range sim.Encounter.TargetUnits { - warrior.WhirlwindMH.Cast(sim, aoeTarget) - if canHitOffhand && warrior.IsEnraged() { - warrior.WhirlwindOH.Cast(sim, aoeTarget) - } - } - }, - }) -} - -func (warrior *Warrior) newWhirlwindHitSpell(isMH bool) *WarriorSpell { - procMask := core.ProcMaskMeleeSpecial - damageFunc := warrior.MHNormalizedWeaponDamage - if !isMH { - procMask = core.ProcMaskMeleeOHSpecial - damageFunc = warrior.OHNormalizedWeaponDamage - } - - return warrior.RegisterSpell(AnyStance, core.SpellConfig{ - SpellCode: core.Ternary(isMH, SpellCode_WarriorWhirlwindMH, SpellCode_WarriorWhirlwindOH), - ActionID: core.ActionID{SpellID: 1680}.WithTag(int32(core.Ternary(isMH, 1, 2))), - SpellSchool: core.SpellSchoolPhysical, - DefenseType: core.DefenseTypeMelee, - ProcMask: procMask, - Flags: core.SpellFlagMeleeMetrics | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, - CritDamageBonus: warrior.impale(), DamageMultiplier: 1, @@ -76,8 +37,15 @@ func (warrior *Warrior) newWhirlwindHitSpell(isMH bool) *WarriorSpell { BonusCoefficient: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := damageFunc(sim, spell.MeleeAttackPower()) - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) + for idx := range results { + baseDamage := spell.Unit.MHNormalizedWeaponDamage(sim, spell.MeleeAttackPower()) + results[idx] = spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) + target = sim.Environment.NextTargetUnit(target) + } + + for _, result := range results { + spell.DealDamage(sim, result) + } }, }) -} +} \ No newline at end of file diff --git a/ui/warrior/presets.ts b/ui/warrior/presets.ts index 2ff9827d0..e31cf3ea4 100644 --- a/ui/warrior/presets.ts +++ b/ui/warrior/presets.ts @@ -106,18 +106,15 @@ export const DefaultRaidBuffs = RaidBuffs.create({ leaderOfThePack: true, powerWordFortitude: TristateEffect.TristateEffectImproved, strengthOfEarthTotem: TristateEffect.TristateEffectImproved, - stoneskinTotem: TristateEffect.TristateEffectRegular, }); export const DefaultIndividualBuffs = IndividualBuffs.create({ blessingOfKings: true, blessingOfMight: TristateEffect.TristateEffectImproved, fengusFerocity: true, - moldarsMoxie: true, rallyingCryOfTheDragonslayer: true, saygesFortune: SaygesFortune.SaygesDamage, songflowerSerenade: true, - spiritOfZandalar: true, warchiefsBlessing: true, });