diff --git a/assets/favicon_io/android-chrome-192x192.png b/assets/favicon_io/android-chrome-192x192.png old mode 100755 new mode 100644 diff --git a/assets/favicon_io/android-chrome-512x512.png b/assets/favicon_io/android-chrome-512x512.png old mode 100755 new mode 100644 diff --git a/assets/favicon_io/apple-touch-icon.png b/assets/favicon_io/apple-touch-icon.png old mode 100755 new mode 100644 diff --git a/assets/favicon_io/favicon-16x16.png b/assets/favicon_io/favicon-16x16.png old mode 100755 new mode 100644 diff --git a/assets/favicon_io/favicon-32x32.png b/assets/favicon_io/favicon-32x32.png old mode 100755 new mode 100644 diff --git a/assets/favicon_io/favicon.ico b/assets/favicon_io/favicon.ico old mode 100755 new mode 100644 diff --git a/assets/favicon_io/site.webmanifest b/assets/favicon_io/site.webmanifest old mode 100755 new mode 100644 diff --git a/pre-commit b/pre-commit old mode 100755 new mode 100644 diff --git a/sim/common/vanilla/item_effects.go b/sim/common/vanilla/item_effects.go index c187084f58..31a77dd863 100644 --- a/sim/common/vanilla/item_effects.go +++ b/sim/common/vanilla/item_effects.go @@ -139,7 +139,11 @@ const ( KalimdorsRevenge = 233621 JomGabbar = 233627 // 23570 NeretzekBloodDrinker = 233647 + RazorbrambleShoulderpads = 233804 + RazorbrambleCowl = 233808 + RazorbrambleLeathers = 233813 Speedstone = 233990 + LodestoneofRetaliation = 233992 ManslayerOfTheQiraji = 234067 EyeOfMoam = 234080 // 21473 DarkmoonCardHeroism = 234176 // 19287 @@ -2371,13 +2375,28 @@ func init() { Name: "Fiery Aura Proc", Callback: core.CallbackOnSpellHitTaken, Outcome: core.OutcomeLanded, - ProcMask: core.ProcMaskMelee, // TODO: Unsure if this means melee attacks or all attacks + ProcMask: core.ProcMaskMelee, Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { procSpell.Cast(sim, spell.Unit) }, }) }) + // https://www.wowhead.com/classic/item=228293/essence-of-the-pure-flame + // Equip: When struck in combat inflicts 100 Nature damage to the attacker. Causes twice as much threat as damage dealt. + core.NewItemEffect(RazorbrambleLeathers, func(agent core.Agent) { + DamageShieldWithThreatMod(agent.GetCharacter(), 1213813, 100, 2, "Damage Shield Razorbramble Leathers") + }) + core.NewItemEffect(RazorbrambleShoulderpads, func(agent core.Agent) { + DamageShieldWithThreatMod(agent.GetCharacter(), 1213816, 80, 2, "Damage Shield Razorbramble Shoulderpads") + }) + core.NewItemEffect(RazorbrambleCowl, func(agent core.Agent) { + DamageShieldWithThreatMod(agent.GetCharacter(), 1213813, 100, 2, "Damage Shield Razorbramble Cowl") + }) + core.NewItemEffect(LodestoneofRetaliation, func(agent core.Agent) { + DamageShieldWithThreatMod(agent.GetCharacter(), 1213816, 80, 2, "Damage Shield Lodestone of Retaliation") + }) + // https://www.wowhead.com/classic/item=234080/eye-of-moam // Use: Increases damage done by magical spells and effects by up to 150, and decreases the magical resistances of your spell targets by 100 for 30 sec. (3 Min Cooldown) core.NewSimpleStatOffensiveTrinketEffect(EyeOfMoam, stats.Stats{stats.SpellDamage: 150, stats.SpellPenetration: 100}, time.Second*30, time.Minute*3) @@ -3356,6 +3375,34 @@ func manslayerOfTheQirajiAura(character *core.Character) *core.Aura { }) } +func DamageShieldWithThreatMod(character *core.Character, spellID int32, damage float64, threatMod float64, procName string) { + + procSpell := character.GetOrRegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: spellID}, + SpellSchool: core.SpellSchoolNature, + DefenseType: core.DefenseTypeMagic, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + + DamageMultiplier: 1, + ThreatMultiplier: threatMod, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + spell.CalcAndDealDamage(sim, target, damage, spell.OutcomeAlwaysHit) + }, + }) + + core.MakeProcTriggerAura(&character.Unit, core.ProcTrigger{ + Name: procName, + Callback: core.CallbackOnSpellHitTaken, + Outcome: core.OutcomeLanded, + ProcMask: core.ProcMaskMelee, + Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + procSpell.Cast(sim, spell.Unit) + }, + }) +} + func neretzekBloodDrinkerEffect(character *core.Character) *core.Spell { // PPM based on old ppm from Armamaments discord actionID := core.ActionID{SpellID: 1214208} healthMetrics := character.NewHealthMetrics(actionID) diff --git a/sim/druid/_frenzied_regeneration.go b/sim/druid/_frenzied_regeneration.go deleted file mode 100644 index a8cd05099c..0000000000 --- a/sim/druid/_frenzied_regeneration.go +++ /dev/null @@ -1,66 +0,0 @@ -package druid - -import ( - "time" - - "github.com/wowsims/sod/sim/core" -) - -func (druid *Druid) registerFrenziedRegenerationCD() { - actionID := core.ActionID{SpellID: 22842} - healthMetrics := druid.NewHealthMetrics(actionID) - rageMetrics := druid.NewRageMetrics(actionID) - - cdTimer := druid.NewTimer() - cd := time.Minute * 3 - healingMulti := 1.0 - - druid.FrenziedRegenerationAura = druid.RegisterAura(core.Aura{ - Label: "Frenzied Regeneration", - ActionID: actionID, - Duration: time.Second * 10, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - druid.PseudoStats.HealingTakenMultiplier *= healingMulti - }, - - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - druid.PseudoStats.HealingTakenMultiplier /= healingMulti - }, - }) - - druid.FrenziedRegeneration = druid.RegisterSpell(Bear, core.SpellConfig{ - ActionID: actionID, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - CD: core.Cooldown{ - Timer: cdTimer, - Duration: cd, - }, - IgnoreHaste: true, - }, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - core.StartPeriodicAction(sim, core.PeriodicActionOptions{ - NumTicks: 10, - Period: time.Second * 1, - OnAction: func(sim *core.Simulation) { - rageDumped := min(druid.CurrentRage(), 10.0) - healthGained := rageDumped * 0.3 / 100 * druid.MaxHealth() * druid.PseudoStats.HealingTakenMultiplier - - if druid.FrenziedRegenerationAura.IsActive() { - druid.SpendRage(sim, rageDumped, rageMetrics) - druid.GainHealth(sim, healthGained, healthMetrics) - } - }, - }) - - druid.FrenziedRegenerationAura.Activate(sim) - }, - }) - - druid.AddMajorCooldown(core.MajorCooldown{ - Spell: druid.FrenziedRegeneration.Spell, - Type: core.CooldownTypeSurvival, - }) -} diff --git a/sim/druid/_lacerate.go b/sim/druid/_lacerate.go deleted file mode 100644 index 60861b1cdf..0000000000 --- a/sim/druid/_lacerate.go +++ /dev/null @@ -1,106 +0,0 @@ -package druid - -import ( - "time" - - "github.com/wowsims/sod/sim/core" -) - -func (druid *Druid) registerLacerateSpell() { - tickDamage := 320.0 / 5 - initialDamage := 88.0 - if druid.Ranged().ID == 27744 { // Idol of Ursoc - tickDamage += 8 - initialDamage += 8 - } - - initialDamageMul := 1 * - core.TernaryFloat64(druid.HasSetBonus(ItemSetLasherweaveBattlegear, 2), 1.2, 1) * - core.TernaryFloat64(druid.HasSetBonus(ItemSetDreamwalkerBattlegear, 2), 1.05, 1) - - tickDamageMul := 1 * - core.TernaryFloat64(druid.HasSetBonus(ItemSetLasherweaveBattlegear, 2), 1.2, 1) * - core.TernaryFloat64(druid.HasSetBonus(ItemSetMalfurionsBattlegear, 2), 1.05, 1) - - druid.Lacerate = druid.RegisterSpell(Bear, core.SpellConfig{ - ActionID: core.ActionID{SpellID: 48568}, - SpellSchool: core.SpellSchoolPhysical, - DefenseType: core.DefenseTypeMelee, - ProcMask: core.ProcMaskMeleeMHSpecial, - Flags: SpellFlagOmen | core.SpellFlagMeleeMetrics | core.SpellFlagAPL, - - RageCost: core.RageCostOptions{ - Cost: 15 - float64(druid.Talents.ShreddingAttacks), - Refund: 0.8, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - IgnoreHaste: true, - }, - - DamageMultiplier: initialDamageMul, - ThreatMultiplier: 0.5, - // FlatThreatBonus: 515.5, // Handled below - - Dot: core.DotConfig{ - Aura: druid.applyRendAndTear(core.Aura{ - Label: "Lacerate", - MaxStacks: 5, - Duration: time.Second * 15, - }), - NumberOfTicks: 5, - TickLength: time.Second * 3, - - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.SnapshotBaseDamage = tickDamage + 0.01*dot.Spell.MeleeAttackPower() - dot.SnapshotBaseDamage *= float64(dot.Aura.GetStacks()) - - if !isRollover { - attackTable := dot.Spell.Unit.AttackTables[target.UnitIndex][dot.Spell.CastType] - dot.Spell.DamageMultiplier = tickDamageMul - dot.SnapshotCritChance = dot.Spell.PhysicalCritChance(attackTable) - dot.SnapshotAttackerMultiplier = dot.Spell.AttackerDamageMultiplier(attackTable) - } - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - if druid.Talents.PrimalGore { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) - } else { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.Spell.OutcomeAlwaysHit) - } - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := initialDamage + 0.01*spell.MeleeAttackPower() - if druid.BleedCategories.Get(target).AnyActive() { - baseDamage *= 1.3 - } - - // Hack so that FlatThreatBonus only applies to the initial portion. - spell.FlatThreatBonus = 515.5 - spell.DamageMultiplier = initialDamageMul - result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) - spell.FlatThreatBonus = 0 - - if result.Landed() { - dot := spell.Dot(target) - if dot.IsActive() { - dot.Refresh(sim) - dot.AddStack(sim) - dot.TakeSnapshot(sim, true) - } else { - dot.Apply(sim) - dot.SetStacks(sim, 1) - dot.TakeSnapshot(sim, true) - } - } else { - spell.IssueRefund(sim) - } - - spell.DealDamage(sim, result) - }, - }) -} diff --git a/sim/druid/_maul.go b/sim/druid/_maul.go deleted file mode 100644 index a2223cdab9..0000000000 --- a/sim/druid/_maul.go +++ /dev/null @@ -1,103 +0,0 @@ -package druid - -import ( - "github.com/wowsims/sod/sim/core" -) - -func (druid *Druid) registerMaulSpell() { - flatBaseDamage := 578.0 - rageCost := 15 - float64(druid.Talents.Ferocity) - - switch druid.Ranged().ID { - case IdolOfBrutality: - rageCost -= 3 - } - - druid.Maul = druid.RegisterSpell(Bear, core.SpellConfig{ - ActionID: core.ActionID{SpellID: 48480}, - SpellSchool: core.SpellSchoolPhysical, - DefenseType: core.DefenseTypeMelee, - ProcMask: core.ProcMaskMeleeMHSpecial | core.ProcMaskMeleeMHAuto, - Flags: SpellFlagOmen | core.SpellFlagMeleeMetrics | core.SpellFlagIncludeTargetBonusDamage | core.SpellFlagNoOnCastComplete, - - RageCost: core.RageCostOptions{ - Cost: rageCost, - Refund: 0.8, - }, - - DamageMultiplier: 1 + 0.1*float64(druid.Talents.SavageFury), - ThreatMultiplier: 1, - FlatThreatBonus: 424, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - // Need to specially deactivate CC here in case maul is cast simultaneously with another spell. - if druid.ClearcastingAura != nil { - druid.ClearcastingAura.Deactivate(sim) - } - - modifier := 1.0 - if druid.BleedCategories.Get(target).AnyActive() { - modifier += .3 - } - if druid.AssumeBleedActive || druid.Rip.Dot(target).IsActive() || druid.Rake.Dot(target).IsActive() || druid.Lacerate.Dot(target).IsActive() { - modifier *= 1.0 + (0.04 * float64(druid.Talents.RendAndTear)) - } - - baseDamage := flatBaseDamage + - spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) + - spell.BonusWeaponDamage() - baseDamage *= modifier - - result := spell.CalcAndDealDamage(sim, curTarget, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) - - if !result.Landed() { - spell.IssueRefund(sim) - } - - druid.MaulQueueAura.Deactivate(sim) - }, - }) - - druid.MaulQueueAura = druid.RegisterAura(core.Aura{ - Label: "Maul Queue Aura", - ActionID: druid.Maul.ActionID, - Duration: core.NeverExpires, - }) - - druid.MaulQueueSpell = druid.RegisterSpell(Bear, core.SpellConfig{ - ActionID: druid.Maul.WithTag(1), - SpellSchool: core.SpellSchoolPhysical, - ProcMask: core.ProcMaskMeleeMHSpecial, - Flags: core.SpellFlagMeleeMetrics | core.SpellFlagAPL, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return !druid.MaulQueueAura.IsActive() && - druid.CurrentRage() >= druid.Maul.Cost.GetCurrentCost() && - !druid.IsCasting(sim) - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - druid.MaulQueueAura.Activate(sim) - }, - }) -} - -func (druid *Druid) QueueMaul(sim *core.Simulation) { - if druid.MaulQueueSpell.CanCast(sim, druid.CurrentTarget) { - druid.MaulQueueSpell.Cast(sim, druid.CurrentTarget) - } -} - -// Returns true if the regular melee swing should be used, false otherwise. -func (druid *Druid) MaulReplaceMH(sim *core.Simulation, mhSwingSpell *core.Spell) *core.Spell { - if !druid.MaulQueueAura.IsActive() { - return mhSwingSpell - } - - if !druid.Maul.Spell.CanCast(sim, druid.CurrentTarget) { - druid.MaulQueueAura.Deactivate(sim) - return mhSwingSpell - } - - return druid.Maul.Spell -} diff --git a/sim/druid/_tank/tank_test.go b/sim/druid/_tank/tank_test.go deleted file mode 100644 index ea01094776..0000000000 --- a/sim/druid/_tank/tank_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package tank - -import ( - "testing" - - _ "github.com/wowsims/sod/sim/common" - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" -) - -func init() { - RegisterFeralTankDruid() -} - -func TestFeralTank(t *testing.T) { - core.RunTestSuite(t, t.Name(), core.FullCharacterTestSuiteGenerator(core.CharacterSuiteConfig{ - Class: proto.Class_ClassDruid, - Race: proto.Race_RaceTauren, - - GearSet: core.GetGearSet("../../../ui/feral_tank_druid/gear_sets", "p1"), - Talents: StandardTalents, - Consumes: FullConsumes, - SpecOptions: core.SpecOptionsCombo{Label: "Default", SpecOptions: PlayerOptionsDefault}, - Rotation: core.GetAplRotation("../../../ui/feral_tank_druid/apls", "default"), - - IsTank: true, - InFrontOfTarget: true, - - ItemFilter: core.ItemFilter{ - WeaponTypes: []proto.WeaponType{ - proto.WeaponType_WeaponTypeDagger, - proto.WeaponType_WeaponTypeMace, - proto.WeaponType_WeaponTypeOffHand, - proto.WeaponType_WeaponTypeStaff, - }, - ArmorType: proto.ArmorType_ArmorTypeLeather, - RangedWeaponTypes: []proto.RangedWeaponType{ - proto.RangedWeaponType_RangedWeaponTypeIdol, - }, - }, - })) -} - -var StandardTalents = "-503232132322010353120300313511-20350001" - -var PlayerOptionsDefault = &proto.Player_FeralTankDruid{ - FeralTankDruid: &proto.FeralTankDruid{ - Options: &proto.FeralTankDruid_Options{ - InnervateTarget: &proto.UnitReference{}, // no Innervate - StartingRage: 20, - }, - }, -} - -var FullConsumes = &proto.Consumes{ - BattleElixir: proto.BattleElixir_GurusElixir, - GuardianElixir: proto.GuardianElixir_GiftOfArthas, - Food: proto.Food_FoodBlackenedDragonfin, - DefaultPotion: proto.Potions_IndestructiblePotion, - DefaultConjured: proto.Conjured_ConjuredHealthstone, - ThermalSapper: true, - FillerExplosive: proto.Explosive_ExplosiveSaroniteBomb, -} diff --git a/sim/druid/berserk.go b/sim/druid/berserk.go index c5b7192454..438f42e041 100644 --- a/sim/druid/berserk.go +++ b/sim/druid/berserk.go @@ -11,6 +11,7 @@ func (druid *Druid) applyBerserk() { if !druid.HasRune(proto.DruidRune_RuneBeltBerserk) { return } + hasMangle := druid.HasRune(proto.DruidRune_RuneHandsMangle) actionId := core.ActionID{SpellID: 417141} var affectedSpells []*DruidSpell @@ -64,6 +65,9 @@ func (druid *Druid) applyBerserk() { IgnoreHaste: true, }, ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { + if hasMangle { + druid.MangleBear.CD.Reset() + } druid.BerserkAura.Activate(sim) }, }) diff --git a/sim/druid/druid.go b/sim/druid/druid.go index e8638ffaa6..7fc06a8599 100644 --- a/sim/druid/druid.go +++ b/sim/druid/druid.go @@ -25,6 +25,10 @@ const ( SpellCode_DruidInsectSwarm SpellCode_DruidMangleCat SpellCode_DruidMangleBear + SpellCode_DruidSwipeCat + SpellCode_DruidLacerate + SpellCode_DruidSwipeBear + SpellCode_DruidMaul SpellCode_DruidMoonfire SpellCode_DruidRake SpellCode_DruidRip @@ -66,6 +70,7 @@ type Druid struct { Innervate *DruidSpell InsectSwarm []*DruidSpell Lacerate *DruidSpell + LacerateBleed *DruidSpell Languish *DruidSpell MangleBear *DruidSpell MangleCat *DruidSpell @@ -90,6 +95,8 @@ type Druid struct { SwipeCat *DruidSpell TigersFury *DruidSpell Typhoon *DruidSpell + curQueuedAutoSpell *DruidSpell + MaulQueue *DruidSpell Wrath []*DruidSpell BearForm *DruidSpell @@ -100,6 +107,7 @@ type Druid struct { BearFormAura *core.Aura BerserkAura *core.Aura CatFormAura *core.Aura + curQueueAura *core.Aura ClearcastingAura *core.Aura DemoralizingRoarAuras core.AuraArray DreamstateManaRegenAura *core.Aura @@ -109,6 +117,7 @@ type Druid struct { ImprovedFaerieFireAuras core.AuraArray FrenziedRegenerationAura *core.Aura FurorAura *core.Aura + PrimalFuryAura *core.Aura FuryOfStormrageAura *core.Aura InsectSwarmAuras core.AuraArray MaulQueueAura *core.Aura @@ -121,6 +130,8 @@ type Druid struct { SolarEclipseProcAura *core.Aura LunarEclipseProcAura *core.Aura WildStrikesBuffAura *core.Aura + Tank2PieceAqAura *core.Aura + Tank2PieceAqProcAura *core.Aura BleedCategories core.ExclusiveCategoryArray SavageRoarDurationTable [6]time.Duration @@ -128,6 +139,8 @@ type Druid struct { // Extra data used for various calculations and overrides AllowRakeRipDoTCrits bool // From T1 Feral 4p bonus FerociousBiteExcessEnergyOverride bool // When true, disables the excess energy consumption of Ferocious bite + FuryOfStormrageCritRatingBonus float64 + CenarionRageThreatBonus float64 // Sunfire/Moonfire modifiers applied when in Moonkin form MoonfireDotMultiplier float64 ShredPositionOverride bool @@ -161,9 +174,18 @@ func (druid *Druid) AddRaidBuffs(raidBuffs *proto.RaidBuffs) { } } -// func (druid *Druid) TryMaul(sim *core.Simulation, mhSwingSpell *core.Spell) *core.Spell { -// return druid.MaulReplaceMH(sim, mhSwingSpell) -// } +func (druid *Druid) TryMaul(sim *core.Simulation, mhSwingSpell *core.Spell) *core.Spell { + if !druid.curQueueAura.IsActive() { + return mhSwingSpell + } + + if !druid.curQueuedAutoSpell.CanCast(sim, druid.CurrentTarget) { + druid.curQueueAura.Deactivate(sim) + return mhSwingSpell + } + + return druid.curQueuedAutoSpell.Spell +} func (druid *Druid) RegisterSpell(formMask DruidForm, config core.SpellConfig) *DruidSpell { prev := config.ExtraCastCondition @@ -229,20 +251,18 @@ func (druid *Druid) RegisterFeralCatSpells() { } // TODO: Classic feral tank -func (druid *Druid) RegisterFeralTankSpells() { +func (druid *Druid) RegisterFeralTankSpells(realismICD *core.Cooldown) { // druid.registerBarkskinCD() // druid.registerBerserkCD() - // druid.registerBearFormSpell() + druid.registerBearFormSpell() // druid.registerDemoralizingRoarSpell() - // druid.registerEnrageSpell() - // druid.registerFrenziedRegenerationCD() - // druid.registerMangleBearSpell() - // druid.registerMaulSpell() - // druid.registerLacerateSpell() - // druid.registerRakeSpell() - // druid.registerRipSpell() + druid.registerEnrageSpell() + druid.registerFrenziedRegenerationCD() + druid.registerRakeSpell() + druid.registerRipSpell() + druid.registerMaulSpell(realismICD) // druid.registerSurvivalInstinctsCD() - // druid.registerSwipeBearSpell() + druid.registerSwipeBearSpell() } func (druid *Druid) Reset(_ *core.Simulation) { diff --git a/sim/druid/_enrage.go b/sim/druid/enrage.go similarity index 68% rename from sim/druid/_enrage.go rename to sim/druid/enrage.go index 0288d4ce60..def1e988b7 100644 --- a/sim/druid/_enrage.go +++ b/sim/druid/enrage.go @@ -11,30 +11,22 @@ func (druid *Druid) registerEnrageSpell() { actionID := core.ActionID{SpellID: 5229} rageMetrics := druid.NewRageMetrics(actionID) - instantRage := []float64{20, 24, 27, 30}[druid.Talents.Intensity] - - dmgBonus := 0.05 * float64(druid.Talents.KingOfTheJungle) - - t10_4p := druid.HasSetBonus(ItemSetLasherweaveBattlegear, 4) + instantRage := []float64{20, 25, 30}[druid.Talents.ImprovedEnrage] + initarmor := druid.BaseEquipStats()[stats.Armor] + hasCenarionRage4Piece := druid.HasSetBonus(ItemSetCenarionRage, 4) druid.EnrageAura = druid.RegisterAura(core.Aura{ Label: "Enrage Aura", ActionID: actionID, Duration: 10 * time.Second, OnGain: func(aura *core.Aura, sim *core.Simulation) { - druid.PseudoStats.DamageDealtMultiplier *= 1.0 + dmgBonus - if !t10_4p { - druid.ApplyDynamicEquipScaling(sim, stats.Armor, 0.84) - } else { - druid.PseudoStats.DamageTakenMultiplier *= 0.88 + if !hasCenarionRage4Piece { + druid.AddStatDynamic(sim, stats.Armor, float64(0.16*initarmor)*-1) } }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { - druid.PseudoStats.DamageDealtMultiplier /= 1.0 + dmgBonus - if !t10_4p { - druid.RemoveDynamicEquipScaling(sim, stats.Armor, 0.84) - } else { - druid.PseudoStats.DamageTakenMultiplier /= 0.88 + if !hasCenarionRage4Piece { + druid.AddStatDynamic(sim, stats.Armor, float64(0.16*initarmor)) } }, }) diff --git a/sim/druid/forms.go b/sim/druid/forms.go index 58ff81b466..a44c049fe7 100644 --- a/sim/druid/forms.go +++ b/sim/druid/forms.go @@ -62,15 +62,15 @@ func (druid *Druid) GetCatWeapon(level int32) core.Weapon { return claws } -// Func (druid *Druid) GetBearWeapon() core.Weapon { -// return core.Weapon{ -// BaseDamageMin: 109, -// BaseDamageMax: 165, -// SwingSpeed: 2.5, -// NormalizedSwingSpeed: 2.5, -// AttackPowerPerDPS: core.DefaultAttackPowerPerDPS, -// } -// } +func (druid *Druid) GetBearWeapon() core.Weapon { + return core.Weapon{ + BaseDamageMin: 109, + BaseDamageMax: 165, + SwingSpeed: 2.5, + NormalizedSwingSpeed: 2.5, + AttackPowerPerDPS: core.DefaultAttackPowerPerDPS, + } +} // TODO: Class bonus stats for both cat and bear. func (druid *Druid) GetFormShiftStats() stats.Stats { @@ -263,144 +263,137 @@ func (druid *Druid) registerCatFormSpell() { }) } -// func (druid *Druid) registerBearFormSpell() { -// actionID := core.ActionID{SpellID: 9634} -// healthMetrics := druid.NewHealthMetrics(actionID) - -// statBonus := druid.GetFormShiftStats().Add(stats.Stats{ -// stats.AttackPower: 3 * float64(druid.Level), -// }) - -// stamDep := druid.NewDynamicMultiplyStat(stats.Stamina, 1.25) - -// var potpDep *stats.StatDependency -// if druid.Talents.ProtectorOfThePack > 0 { -// potpDep = druid.NewDynamicMultiplyStat(stats.AttackPower, 1.0+0.02*float64(druid.Talents.ProtectorOfThePack)) -// } - -// var hotwDep *stats.StatDependency -// if druid.Talents.HeartOfTheWild > 0 { -// hotwDep = druid.NewDynamicMultiplyStat(stats.Stamina, 1.0+0.02*float64(druid.Talents.HeartOfTheWild)) -// } - -// potpdtm := 1 - 0.04*float64(druid.Talents.ProtectorOfThePack) - -// clawWeapon := druid.GetBearWeapon() -// predBonus := stats.Stats{} - -// druid.BearFormAura = druid.RegisterAura(core.Aura{ -// Label: "Bear Form", -// ActionID: actionID, -// Duration: core.NeverExpires, -// BuildPhase: core.Ternary(druid.StartingForm.Matches(Bear), core.CharacterBuildPhaseBase, core.CharacterBuildPhaseNone), -// OnGain: func(aura *core.Aura, sim *core.Simulation) { -// if !druid.Env.MeasuringStats && druid.form != Humanoid { -// druid.CancelShapeshift(sim) -// } -// druid.form = Bear -// druid.SetCurrentPowerBar(core.RageBar) - -// druid.AutoAttacks.SetMH(clawWeapon) - -// druid.PseudoStats.ThreatMultiplier *= 2.1021 -// druid.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] *= 1.0 + 0.02*float64(druid.Talents.MasterShapeshifter) -// druid.PseudoStats.DamageTakenMultiplier *= potpdtm -// Switch to using AddStat as PseudoStat is being removed -// druid.PseudoStats.BaseDodge += 0.02 * float64(druid.Talents.FeralSwiftness+druid.Talents.NaturalReaction) - -// predBonus = druid.GetDynamicPredStrikeStats() -// druid.AddStatsDynamic(sim, predBonus) -// druid.AddStatsDynamic(sim, statBonus) -// druid.ApplyDynamicEquipScaling(sim, stats.Armor, druid.BearArmorMultiplier()) -// if potpDep != nil { -// druid.EnableDynamicStatDep(sim, potpDep) -// } - -// // Preserve fraction of max health when shifting -// healthFrac := druid.CurrentHealth() / druid.MaxHealth() -// druid.EnableDynamicStatDep(sim, stamDep) -// if hotwDep != nil { -// druid.EnableDynamicStatDep(sim, hotwDep) -// } -// druid.GainHealth(sim, healthFrac*druid.MaxHealth()-druid.CurrentHealth(), healthMetrics) - -// if !druid.Env.MeasuringStats { -// druid.AutoAttacks.SetReplaceMHSwing(druid.ReplaceBearMHFunc) -// druid.AutoAttacks.EnableAutoSwing(sim) - -// druid.manageCooldownsEnabled() -// druid.UpdateManaRegenRates() -// } -// }, -// OnExpire: func(aura *core.Aura, sim *core.Simulation) { -// druid.form = Humanoid -// druid.AutoAttacks.SetMH(druid.WeaponFromMainHand()) - -// druid.PseudoStats.ThreatMultiplier /= 2.1021 -// druid.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] /= 1.0 + 0.02*float64(druid.Talents.MasterShapeshifter) -// druid.PseudoStats.DamageTakenMultiplier /= potpdtm -// Switch to using AddStat as PseudoStat is being removed -// druid.PseudoStats.BaseDodge -= 0.02 * float64(druid.Talents.FeralSwiftness+druid.Talents.NaturalReaction) - -// druid.AddStatsDynamic(sim, predBonus.Invert()) -// druid.AddStatsDynamic(sim, statBonus.Invert()) -// druid.RemoveDynamicEquipScaling(sim, stats.Armor, druid.BearArmorMultiplier()) -// if potpDep != nil { -// druid.DisableDynamicStatDep(sim, potpDep) -// } - -// healthFrac := druid.CurrentHealth() / druid.MaxHealth() -// druid.DisableDynamicStatDep(sim, stamDep) -// if hotwDep != nil { -// druid.DisableDynamicStatDep(sim, hotwDep) -// } -// druid.RemoveHealth(sim, druid.CurrentHealth()-healthFrac*druid.MaxHealth()) - -// if !druid.Env.MeasuringStats { -// druid.AutoAttacks.SetReplaceMHSwing(nil) -// druid.AutoAttacks.EnableAutoSwing(sim) - -// druid.manageCooldownsEnabled() -// druid.UpdateManaRegenRates() -// druid.EnrageAura.Deactivate(sim) -// druid.MaulQueueAura.Deactivate(sim) -// } -// }, -// }) - -// rageMetrics := druid.NewRageMetrics(actionID) - -// furorProcChance := []float64{0, 0.2, 0.4, 0.6, 0.8, 1}[druid.Talents.Furor] - -// druid.BearForm = druid.RegisterSpell(Any, core.SpellConfig{ -// ActionID: actionID, -// Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagAPL, - -// ManaCost: core.ManaCostOptions{ -// BaseCost: 0.55, -// Multiplier: (1 - 0.2*float64(druid.Talents.KingOfTheJungle)) * (1 - 0.1*float64(druid.Talents.NaturalShapeshifter)), -// }, -// Cast: core.CastConfig{ -// DefaultCast: core.Cast{ -// GCD: core.GCDDefault, -// }, -// IgnoreHaste: true, -// }, - -// ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { -// rageDelta := 0 - druid.CurrentRage() -// if sim.Proc(furorProcChance, "Furor") { -// rageDelta += 10 -// } -// if rageDelta > 0 { -// druid.AddRage(sim, rageDelta, rageMetrics) -// } else if rageDelta < 0 { -// druid.SpendRage(sim, -rageDelta, rageMetrics) -// } -// druid.BearFormAura.Activate(sim) -// }, -// }) -// } +func (druid *Druid) registerBearFormSpell() { + actionID := core.ActionID{SpellID: core.TernaryInt32(druid.Level < 40, 5487, 9634)} + feralSkillCoefficient := float64((druid.PseudoStats.FeralCombatSkill + 300) / 5) + + statBonus := druid.GetFormShiftStats().Add(stats.Stats{ + stats.AttackPower: 3 * feralSkillCoefficient, + stats.Health: core.TernaryFloat64(druid.Level < 40, (18*feralSkillCoefficient)-160, (32*feralSkillCoefficient)-680), + }) + + hasWolfheadBonus := false + if head := druid.Equipment.Head(); head != nil && (head.ID == WolfsheadHelm || head.Enchant.EffectID == sod.WolfsheadTrophy) { + hasWolfheadBonus = true + } + + feralApDep := druid.NewDynamicStatDependency(stats.FeralAttackPower, stats.AttackPower, 1) + + var hotwDep *stats.StatDependency + if druid.Talents.HeartOfTheWild > 0 { + hotwDep = druid.NewDynamicMultiplyStat(stats.Stamina, 1.0+0.04*float64(druid.Talents.HeartOfTheWild)) + } + + sotfdtm := 1.0 + if druid.HasRune(proto.DruidRune_RuneChestSurvivalOfTheFittest) { + sotfdtm = 0.81 + } + + clawWeapon := druid.GetBearWeapon() + predBonus := stats.Stats{} + + druid.BearFormAura = druid.RegisterAura(core.Aura{ + Label: "Bear Form", + ActionID: actionID, + Duration: core.NeverExpires, + BuildPhase: core.Ternary(druid.StartingForm.Matches(Bear), core.CharacterBuildPhaseBase, core.CharacterBuildPhaseNone), + OnGain: func(aura *core.Aura, sim *core.Simulation) { + if !druid.Env.MeasuringStats && druid.form != Humanoid { + druid.CancelShapeshift(sim) + } + druid.form = Bear + druid.SetCurrentPowerBar(core.RageBar) + druid.PrimalFuryAura.Activate(sim) + + druid.AutoAttacks.SetMH(clawWeapon) + + druid.PseudoStats.ThreatMultiplier += druid.CenarionRageThreatBonus + druid.PseudoStats.ThreatMultiplier += .3 + .03*float64(druid.Talents.FeralInstinct) + druid.PseudoStats.DamageTakenMultiplier *= sotfdtm + + predBonus = druid.GetDynamicPredStrikeStats() + druid.AddStatsDynamic(sim, predBonus) + druid.AddStatsDynamic(sim, statBonus) + druid.ApplyDynamicEquipScaling(sim, stats.Armor, core.TernaryFloat64(druid.Level < 40, 1.8, 4.6)) + druid.ApplyDynamicEquipScaling(sim, stats.Armor, 1+.02*float64(druid.Talents.ThickHide)) + + druid.EnableDynamicStatDep(sim, feralApDep) + if hotwDep != nil { + druid.EnableDynamicStatDep(sim, hotwDep) + } + + if !druid.Env.MeasuringStats { + druid.AutoAttacks.SetReplaceMHSwing(druid.ReplaceBearMHFunc) + druid.AutoAttacks.EnableAutoSwing(sim) + + druid.manageCooldownsEnabled() + druid.UpdateManaRegenRates() + } + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + druid.form = Humanoid + druid.SetCurrentPowerBar(core.ManaBar) + druid.PrimalFuryAura.Deactivate(sim) + + druid.AutoAttacks.SetMH(druid.WeaponFromMainHand()) + + druid.PseudoStats.ThreatMultiplier -= druid.CenarionRageThreatBonus + druid.PseudoStats.ThreatMultiplier -= .3 + .03*float64(druid.Talents.FeralInstinct) + druid.PseudoStats.DamageTakenMultiplier /= sotfdtm + + druid.AddStatsDynamic(sim, predBonus.Invert()) + druid.AddStatsDynamic(sim, statBonus.Invert()) + druid.RemoveDynamicEquipScaling(sim, stats.Armor, core.TernaryFloat64(druid.Level < 40, 1.8, 4.6)) + druid.RemoveDynamicEquipScaling(sim, stats.Armor, 1+.02*float64(druid.Talents.ThickHide)) + + if hotwDep != nil { + druid.DisableDynamicStatDep(sim, hotwDep) + } + + if !druid.Env.MeasuringStats { + druid.AutoAttacks.SetReplaceMHSwing(nil) + druid.AutoAttacks.EnableAutoSwing(sim) + + druid.manageCooldownsEnabled() + druid.UpdateManaRegenRates() + druid.EnrageAura.Deactivate(sim) + } + }, + }) + + rageMetrics := druid.NewRageMetrics(actionID) + + furorProcChance := []float64{0, 0.2, 0.4, 0.6, 0.8, 1}[druid.Talents.Furor] + + druid.BearForm = druid.RegisterSpell(Any, core.SpellConfig{ + ActionID: actionID, + Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagAPL, + + ManaCost: core.ManaCostOptions{ + BaseCost: 0.55, + Multiplier: 100 - 10*druid.Talents.NaturalShapeshifter, + }, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + IgnoreHaste: true, + }, + + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + rageDelta := core.TernaryFloat64(hasWolfheadBonus, 5, 0) - druid.CurrentRage() + if sim.Proc(furorProcChance, "Furor") { + rageDelta += 10 + } + if rageDelta > 0 { + druid.AddRage(sim, rageDelta, rageMetrics) + } else if rageDelta < 0 { + druid.SpendRage(sim, -rageDelta, rageMetrics) + } + druid.BearFormAura.Activate(sim) + }, + }) +} func (druid *Druid) manageCooldownsEnabled() { // Disable cooldowns not usable in form and/or delay others diff --git a/sim/druid/frenzied_regeneration.go b/sim/druid/frenzied_regeneration.go new file mode 100644 index 0000000000..8f4d976394 --- /dev/null +++ b/sim/druid/frenzied_regeneration.go @@ -0,0 +1,105 @@ +package druid + +import ( + "time" + + "github.com/wowsims/sod/sim/core" + "github.com/wowsims/sod/sim/core/proto" + "github.com/wowsims/sod/sim/core/stats" +) + +type FrenziedRegenerationRankInfo struct { + id int32 + level int32 + rageConversion float64 +} + +var frenziedRegenerationSpells = []FrenziedRegenerationRankInfo{ + { + id: 22842, + level: 36, + rageConversion: 10.0, + }, + { + id: 22895, + level: 46, + rageConversion: 15.0, + }, + { + + id: 22896, + level: 56, + rageConversion: 20.0, + }, +} + +func (druid *Druid) registerFrenziedRegenerationCD() { + // Add highest available rank for level. + for rank := len(frenziedRegenerationSpells) - 1; rank >= 0; rank-- { + if druid.Level >= frenziedRegenerationSpells[rank].level { + config := druid.newFrenziedRegenSpellConfig(frenziedRegenerationSpells[rank]) + druid.FrenziedRegeneration = druid.RegisterSpell(Bear, config) + healingMulti := 1.0 + + druid.FrenziedRegenerationAura = druid.RegisterAura(core.Aura{ + Label: "Frenzied Regeneration", + ActionID: druid.FrenziedRegeneration.ActionID, + Duration: time.Second * 10, + OnGain: func(aura *core.Aura, sim *core.Simulation) { + druid.PseudoStats.HealingTakenMultiplier *= healingMulti + }, + + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + druid.PseudoStats.HealingTakenMultiplier /= healingMulti + }, + }) + break + } + } + + druid.AddMajorCooldown(core.MajorCooldown{ + Spell: druid.FrenziedRegeneration.Spell, + Type: core.CooldownTypeSurvival, + }) +} + +func (druid *Druid) newFrenziedRegenSpellConfig(frenziedRegenRank FrenziedRegenerationRankInfo) core.SpellConfig { + actionID := core.ActionID{SpellID: frenziedRegenRank.id} + healthMetrics := druid.NewHealthMetrics(actionID) + rageMetrics := druid.NewRageMetrics(actionID) + hasImprovedFrenziedRegen := druid.HasRune(proto.DruidRune_RuneBracersImpFrenziedRegen) + + cdTimer := druid.NewTimer() + cd := time.Minute * 3 + + return core.SpellConfig{ + ActionID: actionID, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + CD: core.Cooldown{ + Timer: cdTimer, + Duration: cd, + }, + IgnoreHaste: true, + }, + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + core.StartPeriodicAction(sim, core.PeriodicActionOptions{ + NumTicks: 10, + Period: time.Second * 1, + OnAction: func(sim *core.Simulation) { + rageDumped := min(druid.CurrentRage(), 10.0) + healthGained := core.TernaryFloat64(hasImprovedFrenziedRegen, druid.GetStat(stats.Health)*.1, rageDumped*frenziedRegenRank.rageConversion*druid.PseudoStats.HealingTakenMultiplier) + + if druid.FrenziedRegenerationAura.IsActive() { + druid.SpendRage(sim, rageDumped, rageMetrics) + druid.GainHealth(sim, healthGained, healthMetrics) + } + }, + }) + + druid.FrenziedRegenerationAura.Activate(sim) + }, + } +} diff --git a/sim/druid/item_sets_pve.go b/sim/druid/item_sets_pve.go index 6c158b685f..a81c5a4455 100644 --- a/sim/druid/item_sets_pve.go +++ b/sim/druid/item_sets_pve.go @@ -1,132 +1 @@ package druid - -import ( - "time" - - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" - "github.com/wowsims/sod/sim/core/stats" -) - -/////////////////////////////////////////////////////////////////////////// -// SoD Phase 3 Item Sets -/////////////////////////////////////////////////////////////////////////// - -var ItemSetLostWorshippersArmor = core.NewItemSet(core.ItemSet{ - Name: "Lost Worshipper's Armor", - Bonuses: map[int32]core.ApplyEffect{ - 2: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.MeleeHit, 1) - c.AddStat(stats.SpellHit, 1) - }, - 3: func(agent core.Agent) { - c := agent.GetCharacter() - c.OnSpellRegistered(func(spell *core.Spell) { - if spell.SpellCode == SpellCode_DruidWrath || spell.SpellCode == SpellCode_DruidStarfire { - spell.BonusCritRating += 3 * core.CritRatingPerCritChance - } - }) - }, - }, -}) - -var ItemSetCoagulateBloodguardsLeathers = core.NewItemSet(core.ItemSet{ - Name: "Coagulate Bloodguard's Leathers", - Bonuses: map[int32]core.ApplyEffect{ - 2: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Strength, 10) - }, - 3: func(agent core.Agent) { - druid := agent.(DruidAgent).GetDruid() - - // Power Shredder - procAura := druid.GetOrRegisterAura(core.Aura{ - Label: "Power Shredder Proc", - ActionID: core.ActionID{SpellID: 449925}, - Duration: time.Second * 10, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - druid.CatForm.Cost.Multiplier -= 30 - //druid.BearForm.CostMultiplier -= 0.3 - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - druid.CatForm.Cost.Multiplier += 30 - //druid.BearForm.CostMultiplier += 0.3 - }, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - if spell == druid.CatForm.Spell /* || spell == druid.BearForm.Spell */ { - aura.Deactivate(sim) - } - }, - }) - - core.MakeProcTriggerAura(&druid.Unit, core.ProcTrigger{ - Name: "Power Shredder", - Callback: core.CallbackOnCastComplete, - Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.SpellCode == SpellCode_DruidShred { - procAura.Activate(sim) - } - }, - }) - - // Precise Claws should be implemented in the bear form spells when those get added back - // Adds 2% hit while in bear/dire bear forms - }, - }, -}) - -var ItemSetExiledProphetsRaiment = core.NewItemSet(core.ItemSet{ - Name: "Exiled Prophet's Raiment", - Bonuses: map[int32]core.ApplyEffect{ - 2: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.MP5, 4) - }, - 3: func(agent core.Agent) { - druid := agent.(DruidAgent).GetDruid() - // TODO: Not tested because Druid doesn't have healing spells implemented at the moment - if druid.HasRune(proto.DruidRune_RuneFeetDreamstate) { - core.MakeProcTriggerAura(&druid.Unit, core.ProcTrigger{ - Name: "Exiled Dreamer", - Callback: core.CallbackOnHealDealt, - ProcMask: core.ProcMaskSpellHealing, - Outcome: core.OutcomeCrit, - ProcChance: 0.5, - Handler: func(sim *core.Simulation, _ *core.Spell, _ *core.SpellResult) { - druid.DreamstateManaRegenAura.Activate(sim) - }, - }) - } - }, - }, -}) - -var ItemSetEmeraldWatcherVestments = core.NewItemSet(core.ItemSet{ - Name: "Emerald Watcher Vestments", - Bonuses: map[int32]core.ApplyEffect{ - 3: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Stamina, 10) - }, - 6: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.SpellPower, 12) - }, - }, -}) - -var ItemSetEmeraldDreamkeeperGarb = core.NewItemSet(core.ItemSet{ - Name: "Emerald Dreamkeeper Garb", - Bonuses: map[int32]core.ApplyEffect{ - 3: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.Stamina, 10) - }, - 6: func(agent core.Agent) { - c := agent.GetCharacter() - c.AddStat(stats.HealingPower, 22) - }, - }, -}) diff --git a/sim/druid/item_sets_pve_phase_4.go b/sim/druid/item_sets_pve_phase_4.go index 391754e021..a134be59c1 100644 --- a/sim/druid/item_sets_pve_phase_4.go +++ b/sim/druid/item_sets_pve_phase_4.go @@ -4,6 +4,7 @@ import ( "github.com/wowsims/sod/sim/core" "github.com/wowsims/sod/sim/core/proto" "github.com/wowsims/sod/sim/core/stats" + "time" ) var ItemSetFeralheartRaiment = core.NewItemSet(core.ItemSet{ @@ -225,7 +226,8 @@ var ItemSetCenarionRage = core.NewItemSet(core.ItemSet{ }, // Reduces the cooldown of Enrage by 30 sec and it no longer reduces your armor. 4: func(agent core.Agent) { - // TODO: Enrage + druid := agent.(DruidAgent).GetDruid() + druid.Enrage.CD.Duration -= time.Second * 30 }, 6: func(agent core.Agent) { druid := agent.(DruidAgent).GetDruid() @@ -236,17 +238,7 @@ var ItemSetCenarionRage = core.NewItemSet(core.ItemSet{ // Bear Form and Dire Bear Form increase all threat you generate by an additional 20%, and Cower now removes all your threat against the target but has a 20 sec longer cooldown. func (druid *Druid) applyT1Guardian6PBonus() { - label := "S03 - Item - T1 - Druid - Guardian 6P Bonus" - if druid.HasAura(label) { - return - } - - druid.RegisterAura(core.Aura{ - Label: label, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - // TODO - }, - }) + druid.CenarionRageThreatBonus = .2 } var ItemSetCenarionBounty = core.NewItemSet(core.ItemSet{ diff --git a/sim/druid/item_sets_pve_phase_5.go b/sim/druid/item_sets_pve_phase_5.go index 2778120b04..83010e3dd3 100644 --- a/sim/druid/item_sets_pve_phase_5.go +++ b/sim/druid/item_sets_pve_phase_5.go @@ -5,7 +5,6 @@ import ( "time" "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" "github.com/wowsims/sod/sim/core/stats" ) @@ -237,55 +236,76 @@ var ItemSetFuryOfStormrage = core.NewItemSet(core.ItemSet{ // Swipe(Bear) also causes your Maul to hit 1 additional target for the next 6 sec. func (druid *Druid) applyT2Guardian2PBonus() { - label := "S03 - Item - T2 - Druid - Guardian 2P Bonus" - if druid.HasAura(label) { + if druid.Env.GetNumTargets() == 1 { return } - druid.RegisterAura(core.Aura{ - Label: label, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - // TODO + var curDmg float64 + + cleaveHit := druid.RegisterSpell(Bear, core.SpellConfig{ + ActionID: core.ActionID{SpellID: 467217}, + SpellSchool: core.SpellSchoolPhysical, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagMeleeMetrics | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + + DamageMultiplier: 1, + ThreatMultiplier: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + spell.CalcAndDealDamage(sim, target, curDmg, spell.OutcomeAlwaysHit) }, }) + + cleaveAura := druid.RegisterAura(core.Aura{ + Label: "2P Cleave Buff", + Duration: time.Second * 6, + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if result.Landed() && (spell.SpellCode == SpellCode_DruidMaul) { + curDmg = result.Damage / result.ResistanceMultiplier + cleaveHit.Cast(sim, druid.Env.NextTargetUnit(result.Target)) + cleaveHit.SpellMetrics[result.Target.UnitIndex].Casts-- + } + }, + }) + + core.MakePermanent(druid.RegisterAura(core.Aura{ + Label: "S03 - Item - T2 - Druid - Guardian 2P Bonus", + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if result.Landed() && spell.SpellCode == SpellCode_DruidSwipeBear { + cleaveAura.Activate(sim) + } + }, + })) } // Your Mangle(Bear), Swipe(Bear), Maul, and Lacerate abilities gain 5% increased critical strike chance against targets afflicted by your Lacerate. func (druid *Druid) applyT2Guardian4PBonus() { - if !druid.HasRune(proto.DruidRune_RuneLegsLacerate) { - return - } - - label := "S03 - Item - T2 - Druid - Guardian 4P Bonus" - if druid.HasAura(label) { - return - } - - druid.RegisterAura(core.Aura{ - Label: label, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - // TODO - }, - }) + druid.FuryOfStormrageCritRatingBonus = 5 * core.SpellCritRatingPerCritChance } // Your Swipe now spreads your Lacerate from your primary target to other targets it strikes. func (druid *Druid) applyT2Guardian6PBonus() { - if !druid.HasRune(proto.DruidRune_RuneLegsLacerate) { - return - } - - label := "S03 - Item - T2 - Druid - Guardian 6P Bonus" - if druid.HasAura(label) { - return - } - - druid.RegisterAura(core.Aura{ - Label: label, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - // TODO - }, - }) + // if druid.Env.GetNumTargets() == 1 { + // return + // } + + // targetCount := core.TernaryInt32(druid.HasRune(proto.DruidRune_RuneCloakImprovedSwipe), 10, 3) + // numHits := min(targetCount, druid.Env.GetNumTargets()) + // results := make([]*core.SpellResult, numHits) + + // core.MakePermanent(druid.RegisterAura(core.Aura{ + // Label: "S03 - Item - T2 - Druid - Guardian 6P Bonus", + // OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + // if spell.SpellCode == SpellCode_DruidSwipeBear { + // // TODO: Troubleshoot - Saeyon + // for _, cleaveMob := range results { + // druid.LacerateBleed.Cast(sim, cleaveMob.Target) + // druid.LacerateBleed.Dot(cleaveMob.Target).SetStacks(sim, druid.LacerateBleed.Dot(result.Target).GetStacks()) + // // Bleed duration needs to match the original DOT duration + // } + // } + // }, + // })) } var ItemSetBountyOfStormrage = core.NewItemSet(core.ItemSet{ diff --git a/sim/druid/item_sets_pve_phase_6.go b/sim/druid/item_sets_pve_phase_6.go index b0564762ca..91cde04e04 100644 --- a/sim/druid/item_sets_pve_phase_6.go +++ b/sim/druid/item_sets_pve_phase_6.go @@ -138,7 +138,7 @@ func (druid *Druid) applyTAQFeral4PBonus() { ActionID: core.ActionID{SpellID: 1213174}, // Tracking in APL Label: label, OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if !result.Outcome.Matches(core.OutcomeCrit) || !(spell == druid.Shred.Spell || spell == druid.MangleCat.Spell || spell == druid.FerociousBite.Spell) { + if !result.Outcome.Matches(core.OutcomeCrit) || !(spell == druid.Shred.Spell || spell == druid.MangleCat.Spell || spell == druid.MangleBear.Spell || spell == druid.FerociousBite.Spell) { return } @@ -183,15 +183,33 @@ var ItemSetGenesisFury = core.NewItemSet(core.ItemSet{ // Each time you Dodge while in Dire Bear Form, you gain 10% increased damage on your next Mangle or Swipe, stacking up to 5 times. func (druid *Druid) applyTAQGuardian2PBonus() { - label := "S03 - Item - TAQ - Druid - Guardian 2P Bonus" - if druid.HasAura(label) { - return - } + druid.Tank2PieceAqProcAura = druid.RegisterAura(core.Aura{ + Label: "Guardian 2P Bonus Proc", + ActionID: core.ActionID{SpellID: 1213188}, + Duration: time.Second * 10, + MaxStacks: 5, + OnStacksChange: func(aura *core.Aura, sim *core.Simulation, oldStacks, newStacks int32) { + druid.MangleBear.DamageMultiplierAdditive += 0.1 * float64(newStacks-oldStacks) + druid.SwipeBear.DamageMultiplierAdditive += 0.1 * float64(newStacks-oldStacks) + }, + }) - druid.RegisterAura(core.Aura{ - Label: label, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - // TODO + druid.Tank2PieceAqAura = druid.RegisterAura(core.Aura{ + Label: "S03 - Item - TAQ - Druid - Guardian 2P Bonus", + Duration: core.NeverExpires, + OnReset: func(aura *core.Aura, sim *core.Simulation) { + aura.Activate(sim) + }, + OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if druid.form == Bear && spell.ProcMask.Matches(core.ProcMaskMelee) && result.Outcome.Matches(core.OutcomeDodge) { + druid.Tank2PieceAqProcAura.Activate(sim) + druid.Tank2PieceAqProcAura.AddStack(sim) + } + }, + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if spell.SpellCode == SpellCode_DruidMangleBear || spell.SpellCode == SpellCode_DruidSwipeBear { + druid.Tank2PieceAqProcAura.SetStacks(sim, 0) + } }, }) } @@ -201,17 +219,10 @@ func (druid *Druid) applyTAQGuardian4PBonus() { if !druid.HasRune(proto.DruidRune_RuneHandsMangle) { return } - - label := "S03 - Item - TAQ - Druid - Guardian 4P Bonus" - if druid.HasAura(label) { - return - } - - druid.RegisterAura(core.Aura{ - Label: label, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - // TODO - }, + druid.OnSpellRegistered(func(spell *core.Spell) { + if spell.SpellCode == SpellCode_DruidMangleBear { + spell.CD.Duration -= 1500 * time.Millisecond + } }) } diff --git a/sim/druid/items.go b/sim/druid/items.go index 3308239edc..c1036373d2 100644 --- a/sim/druid/items.go +++ b/sim/druid/items.go @@ -15,6 +15,7 @@ const ( IdolOfFerocity = 22397 IdolOfTheMoon = 23197 IdolOfBrutality = 23198 + IdolOfCruelty = 232424 IdolMindExpandingMushroom = 209576 Catnip = 213407 IdolOfWrath = 216490 diff --git a/sim/druid/lacerate.go b/sim/druid/lacerate.go new file mode 100644 index 0000000000..1bbd389c8b --- /dev/null +++ b/sim/druid/lacerate.go @@ -0,0 +1,131 @@ +package druid + +import ( + "time" + + "github.com/wowsims/sod/sim/core" + "github.com/wowsims/sod/sim/core/proto" +) + +func (druid *Druid) registerLacerateSpell() { + if !druid.HasRune(proto.DruidRune_RuneLegsLacerate) { + return + } + initialDamageMul := 0.0 + hasGore := druid.HasRune(proto.DruidRune_RuneHelmGore) + + switch druid.Ranged().ID { + case IdolOfCruelty: + initialDamageMul += .07 + } + rageMetrics := druid.NewRageMetrics(core.ActionID{SpellID: 431446}) + + druid.Lacerate = druid.RegisterSpell(Bear, core.SpellConfig{ + ActionID: core.ActionID{SpellID: 414644}, + SpellSchool: core.SpellSchoolPhysical, + SpellCode: SpellCode_DruidLacerate, + DefenseType: core.DefenseTypeMelee, + ProcMask: core.ProcMaskMeleeMHSpecial, + Flags: SpellFlagOmen | core.SpellFlagMeleeMetrics | core.SpellFlagAPL, + + RageCost: core.RageCostOptions{ + Cost: 10, + Refund: 0.8, + }, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + IgnoreHaste: true, + }, + + DamageMultiplier: 1, + ThreatMultiplier: 3.33, + // TODO: Berserk 3 target lacerate cleave - Saeyon + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + baseDamage := spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) * (.2*float64(druid.LacerateBleed.Dot(target).GetStacks()) + initialDamageMul) + berserking := druid.BerserkAura.IsActive() + + dotBonusCrit := 0.0 + if druid.LacerateBleed.Dot(target).GetStacks() > 0 { + dotBonusCrit = druid.FuryOfStormrageCritRatingBonus + } + + spell.BonusCritRating += dotBonusCrit + spell.Cost.FlatModifier -= core.TernaryInt32(berserking, 10, 0) + result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) + spell.Cost.FlatModifier += core.TernaryInt32(berserking, 10, 0) + spell.BonusCritRating -= dotBonusCrit + + if result.Landed() { + druid.LacerateBleed.Cast(sim, target) + + if hasGore && sim.Proc(0.15, "Gore") { + druid.AddRage(sim, 10.0, rageMetrics) + druid.MangleBear.CD.Reset() + } + } else { + spell.IssueRefund(sim) + } + }, + }) +} + +func (druid *Druid) registerLacerateBleedSpell() { + if !druid.HasRune(proto.DruidRune_RuneLegsLacerate) { + return + } + tickDamage := 29.8312 + + switch druid.Ranged().ID { + case IdolOfCruelty: + tickDamage += 7.0 + } + + druid.LacerateBleed = druid.RegisterSpell(Bear, core.SpellConfig{ + ActionID: core.ActionID{SpellID: 414647}, + SpellSchool: core.SpellSchoolPhysical, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagMeleeMetrics | core.SpellFlagNoOnCastComplete, + + DamageMultiplier: 1, + ThreatMultiplier: 3.33, + + Dot: core.DotConfig{ + Aura: core.Aura{ + Label: "Lacerate", + MaxStacks: 5, + Duration: time.Second * 15, + }, + NumberOfTicks: 5, + TickLength: time.Second * 3, + + OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { + dot.SnapshotBaseDamage = tickDamage + dot.SnapshotBaseDamage *= float64(dot.Aura.GetStacks()) + + if !isRollover { + attackTable := dot.Spell.Unit.AttackTables[target.UnitIndex][dot.Spell.CastType] + dot.SnapshotAttackerMultiplier = dot.Spell.AttackerDamageMultiplier(attackTable, true) + } + }, + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.Spell.OutcomeAlwaysHit) + }, + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + dot := spell.Dot(target) + if dot.IsActive() { + dot.Refresh(sim) + dot.AddStack(sim) + dot.TakeSnapshot(sim, true) + } else { + dot.Apply(sim) + dot.SetStacks(sim, 1) + dot.TakeSnapshot(sim, true) + } + }, + }) +} diff --git a/sim/druid/mangle.go b/sim/druid/mangle.go index 37a09e09ac..2bfba54322 100644 --- a/sim/druid/mangle.go +++ b/sim/druid/mangle.go @@ -5,27 +5,38 @@ import ( "github.com/wowsims/sod/sim/core" "github.com/wowsims/sod/sim/core/proto" + "github.com/wowsims/sod/sim/core/stats" ) -/* TODO: Bear mangle func (druid *Druid) registerMangleBearSpell() { - if !druid.Talents.Mangle { + if !druid.HasRune(proto.DruidRune_RuneHandsMangle) { return } + idolMultiplier := 1.0 + rageCostReduction := float64(druid.Talents.Ferocity) + hasLacerate := druid.HasRune(proto.DruidRune_RuneLegsLacerate) + + switch druid.Ranged().ID { + case IdolOfUrsinPower: + idolMultiplier += .03 + case IdolOfBrutality: + rageCostReduction += 3 + } + mangleAuras := druid.NewEnemyAuraArray(core.MangleAura) - durReduction := (0.5) * float64(druid.Talents.ImprovedMangle) + apProcAura := core.DefendersResolveAttackPower(druid.GetCharacter()) druid.MangleBear = druid.RegisterSpell(Bear, core.SpellConfig{ - SpellCode: SpellCode_DruidMangleBear - ActionID: core.ActionID{SpellID: 48564}, + ActionID: core.ActionID{SpellID: 407995}, + SpellCode: SpellCode_DruidMangleBear, SpellSchool: core.SpellSchoolPhysical, DefenseType: core.DefenseTypeMelee, ProcMask: core.ProcMaskMeleeMHSpecial, - Flags: SpellFlagOmen | core.SpellFlagMeleeMetrics | core.SpellFlagIncludeTargetBonusDamage | core.SpellFlagAPL, + Flags: SpellFlagOmen | core.SpellFlagMeleeMetrics | core.SpellFlagAPL, RageCost: core.RageCostOptions{ - Cost: 20 - float64(druid.Talents.Ferocity), + Cost: 15 - rageCostReduction, Refund: 0.8, }, Cast: core.CastConfig{ @@ -35,22 +46,34 @@ func (druid *Druid) registerMangleBearSpell() { IgnoreHaste: true, CD: core.Cooldown{ Timer: druid.NewTimer(), - Duration: time.Duration(float64(time.Second) * (6 - durReduction)), + Duration: time.Second * 6, }, }, + // TODO: Berserk 3 target mangle cleave - Saeyon - DamageMultiplier: (1 + 0.1*float64(druid.Talents.SavageFury)) * 1.15 - ThreatMultiplier: core.TernaryFloat64(druid.HasSetBonus(ItemSetThunderheartHarness, 2), 1.15, 1), + DamageMultiplier: 1.6 * idolMultiplier, + DamageMultiplierAdditive: 1 + 0.1*float64(druid.Talents.SavageFury), + ThreatMultiplier: 1.5, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := 299/1.15 + - spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) + - spell.BonusWeaponDamage() + baseDamage := spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) + dotBonusCrit := 0.0 + if hasLacerate && druid.LacerateBleed.Dot(target).GetStacks() > 0 { + dotBonusCrit = druid.FuryOfStormrageCritRatingBonus + } + spell.BonusCritRating += dotBonusCrit result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) + spell.BonusCritRating -= dotBonusCrit if result.Landed() { mangleAuras.Get(target).Activate(sim) + if stacks := int32(druid.GetStat(stats.Defense)); stacks > 0 { + apProcAura.Activate(sim) + if apProcAura.GetStacks() != stacks { + apProcAura.SetStacks(sim, stacks) + } + } } else { spell.IssueRefund(sim) } @@ -62,8 +85,7 @@ func (druid *Druid) registerMangleBearSpell() { RelatedAuras: []core.AuraArray{mangleAuras}, }) - } -*/ +} func (druid *Druid) registerMangleCatSpell() { if !druid.HasRune(proto.DruidRune_RuneHandsMangle) { @@ -95,9 +117,10 @@ func (druid *Druid) registerMangleCatSpell() { IgnoreHaste: true, }, - DamageMultiplier: (1 + 0.1*float64(druid.Talents.SavageFury)) * weaponMulti, - ThreatMultiplier: 1, - BonusCoefficient: 1, + DamageMultiplier: 1 * weaponMulti, + DamageMultiplierAdditive: 1 + 0.1*float64(druid.Talents.SavageFury), + ThreatMultiplier: 1, + BonusCoefficient: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) diff --git a/sim/druid/maul.go b/sim/druid/maul.go new file mode 100644 index 0000000000..c1f6ce01e8 --- /dev/null +++ b/sim/druid/maul.go @@ -0,0 +1,169 @@ +package druid + +import ( + "github.com/wowsims/sod/sim/core" + "github.com/wowsims/sod/sim/core/proto" +) + +type MaulRankInfo struct { + id int32 + level int32 + damage float64 +} + +var maulSpells = []MaulRankInfo{ + { + id: 6807, + level: 10, + damage: 18.0, + }, + { + id: 6808, + level: 18, + damage: 27.0, + }, + { + id: 6809, + level: 26, + damage: 37.0, + }, + { + id: 8972, + level: 34, + damage: 49.0, + }, + { + id: 9745, + level: 42, + damage: 71.0, + }, + { + id: 9880, + level: 50, + damage: 101.0, + }, + { + id: 9881, + level: 58, + damage: 128.0, + }, +} + +func (druid *Druid) registerMaulSpell(realismICD *core.Cooldown) { + // Add highest available rank for level. + for rank := len(maulSpells) - 1; rank >= 0; rank-- { + if druid.Level >= maulSpells[rank].level { + config := druid.newMaulSpellConfig(maulSpells[rank]) + druid.Maul = druid.RegisterSpell(Bear, config) + break + } + } + + druid.MaulQueue = druid.makeQueueSpellsAndAura(druid.Maul, realismICD) +} + +func (druid *Druid) newMaulSpellConfig(maulRank MaulRankInfo) core.SpellConfig { + flatBaseDamage := maulRank.damage + rageCost := 15 - float64(druid.Talents.Ferocity) + hasLacerate := druid.HasRune(proto.DruidRune_RuneLegsLacerate) + hasGore := druid.HasRune(proto.DruidRune_RuneHelmGore) + + switch druid.Ranged().ID { + case IdolOfBrutality: + rageCost -= 3 + } + rageMetrics := druid.NewRageMetrics(core.ActionID{SpellID: maulRank.id}) + + return core.SpellConfig{ + ActionID: core.ActionID{SpellID: maulRank.id}, + SpellSchool: core.SpellSchoolPhysical, + SpellCode: SpellCode_DruidMaul, + DefenseType: core.DefenseTypeMelee, + ProcMask: core.ProcMaskMeleeMHSpecial | core.ProcMaskMeleeMHAuto, + Flags: SpellFlagOmen | core.SpellFlagMeleeMetrics | core.SpellFlagNoOnCastComplete, + + RageCost: core.RageCostOptions{ + Cost: rageCost, + Refund: 0.8, + }, + + DamageMultiplierAdditive: 1 + .1*float64(druid.Talents.SavageFury), + ThreatMultiplier: 1.75, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + // Need to specially deactivate CC here in case maul is cast simultaneously with another spell. + if druid.ClearcastingAura != nil { + druid.ClearcastingAura.Deactivate(sim) + } + + baseDamage := flatBaseDamage + spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) + + dotBonusCrit := 0.0 + if hasLacerate && druid.LacerateBleed.Dot(target).GetStacks() > 0 { + dotBonusCrit = druid.FuryOfStormrageCritRatingBonus + } + + spell.BonusCritRating += dotBonusCrit + result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) + spell.BonusCritRating -= dotBonusCrit + + if !result.Landed() { + spell.IssueRefund(sim) + } + + if hasGore && sim.Proc(0.15, "Gore") { + druid.AddRage(sim, 10.0, rageMetrics) + druid.MangleBear.CD.Reset() + } + + if druid.curQueueAura != nil { + druid.curQueueAura.Deactivate(sim) + } + }, + } +} + +func (druid *Druid) makeQueueSpellsAndAura(srcSpell *DruidSpell, realismICD *core.Cooldown) *DruidSpell { + queueAura := druid.RegisterAura(core.Aura{ + Label: "Maul Queue Aura-" + srcSpell.ActionID.String(), + ActionID: srcSpell.ActionID.WithTag(1), + Duration: core.NeverExpires, + OnGain: func(aura *core.Aura, sim *core.Simulation) { + if druid.curQueueAura != nil { + druid.curQueueAura.Deactivate(sim) + } + druid.curQueueAura = aura + druid.curQueuedAutoSpell = srcSpell + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + druid.curQueueAura = nil + druid.curQueuedAutoSpell = nil + }, + }) + + queueSpell := druid.RegisterSpell(Bear, core.SpellConfig{ + ActionID: srcSpell.ActionID.WithTag(1), + Flags: core.SpellFlagMeleeMetrics | core.SpellFlagAPL | core.SpellFlagCastTimeNoGCD, + + ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { + return druid.curQueueAura == nil && + druid.CurrentRage() >= srcSpell.DefaultCast.Cost && + !druid.IsCasting(sim) && + realismICD.IsReady(sim) + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + if realismICD.IsReady(sim) { + realismICD.Use(sim) + sim.AddPendingAction(&core.PendingAction{ + NextActionAt: sim.CurrentTime + realismICD.Duration, + OnAction: func(sim *core.Simulation) { + queueAura.Activate(sim) + }, + }) + } + }, + }) + + return queueSpell +} diff --git a/sim/druid/runes.go b/sim/druid/runes.go index 3e7560e4ac..c1ff9dfbbc 100644 --- a/sim/druid/runes.go +++ b/sim/druid/runes.go @@ -28,8 +28,8 @@ func (druid *Druid) ApplyRunes() { druid.applyElunesFires() // Hands - druid.applyMangle() druid.registerSunfireSpell() + druid.applyMangle() // Belt druid.applyBerserk() @@ -38,6 +38,8 @@ func (druid *Druid) ApplyRunes() { // Legs druid.applyStarsurge() druid.applySavageRoar() + druid.registerLacerateBleedSpell() + druid.registerLacerateSpell() // Feet druid.applyDreamstate() @@ -335,7 +337,7 @@ func (druid *Druid) tryElunesFiresRipExtension(sim *core.Simulation, unit *core. } func (druid *Druid) applyMangle() { - //druid.registerMangleBearSpell() + druid.registerMangleBearSpell() druid.registerMangleCatSpell() } diff --git a/sim/druid/swipe.go b/sim/druid/swipe.go index c00a9538ca..c979a1f22a 100644 --- a/sim/druid/swipe.go +++ b/sim/druid/swipe.go @@ -5,52 +5,87 @@ import ( "github.com/wowsims/sod/sim/core" "github.com/wowsims/sod/sim/core/proto" + "github.com/wowsims/sod/sim/core/stats" ) -const SwipeRanks = 5 +type SwipeRankInfo struct { + id int32 + level int32 + damage float64 +} + +var swipeSpells = []SwipeRankInfo{ + { + id: 779, + level: 16, + damage: 18.0, + }, + { + id: 780, + level: 24, + damage: 25.0, + }, + { + id: 769, + level: 34, + damage: 36.0, + }, + { + id: 9754, + level: 44, + damage: 60.0, + }, + { + id: 9908, + level: 54, + damage: 83.0, + }, +} -var SwipeSpellId = [SwipeRanks + 1]int32{0, 779, 780, 769, 9754, 9908} -var SwipeBaseDamage = [SwipeRanks + 1]float64{0, 18, 25, 36, 60, 83} -var SwipeLevel = [SwipeRanks + 1]int{0, 16, 24, 34, 44, 54} +func (druid *Druid) registerSwipeBearSpell() { + // Add highest available rank for level. + for rank := len(swipeSpells) - 1; rank >= 0; rank-- { + if druid.Level >= swipeSpells[rank].level { + config := druid.newSwipeBearSpellConfig(swipeSpells[rank]) + druid.SwipeBear = druid.RegisterSpell(Bear, config) + break + } + } +} // See https://www.wowhead.com/classic/spell=436895/s03-tuning-and-overrides-passive-druid // Modifies Threat +101%: -const SwipeThreatMultiplier = 2.0 +const SwipeThreatMultiplier = 3.5 -func (druid *Druid) registerSwipeBearSpell() { +func (druid *Druid) newSwipeBearSpellConfig(swipeRank SwipeRankInfo) core.SpellConfig { hasImprovedSwipeRune := druid.HasRune(proto.DruidRune_RuneCloakImprovedSwipe) + baseMultiplier := 1.0 - rank := map[int32]int{ - 25: 2, - 40: 3, - 50: 4, - 60: 6, - }[druid.Level] - - level := SwipeLevel[rank] - spellID := SwipeSpellId[rank] - baseDamage := SwipeBaseDamage[rank] + baseDamage := swipeRank.damage + .1*druid.GetStat(stats.AttackPower) rageCost := 20 - float64(druid.Talents.Ferocity) targetCount := core.TernaryInt32(hasImprovedSwipeRune, 10, 3) numHits := min(targetCount, druid.Env.GetNumTargets()) results := make([]*core.SpellResult, numHits) + hasGore := druid.HasRune(proto.DruidRune_RuneHelmGore) + hasLacerate := druid.HasRune(proto.DruidRune_RuneLegsLacerate) switch druid.Ranged().ID { case IdolOfBrutality: rageCost -= 3 + case IdolOfUrsinPower: + baseMultiplier += .03 } + rageMetrics := druid.NewRageMetrics(core.ActionID{SpellID: swipeRank.id}) - druid.SwipeBear = druid.RegisterSpell(Bear, core.SpellConfig{ - ActionID: core.ActionID{SpellID: spellID}, + return core.SpellConfig{ + ActionID: core.ActionID{SpellID: swipeRank.id}, SpellSchool: core.SpellSchoolPhysical, + SpellCode: SpellCode_DruidSwipeBear, DefenseType: core.DefenseTypeMelee, ProcMask: core.ProcMaskMeleeMHSpecial, Flags: SpellFlagOmen | core.SpellFlagMeleeMetrics | core.SpellFlagAPL, - Rank: rank, - RequiredLevel: level, - RageCost: core.RageCostOptions{ Cost: 20 - float64(druid.Talents.Ferocity), }, @@ -62,20 +97,34 @@ func (druid *Druid) registerSwipeBearSpell() { IgnoreHaste: true, }, - DamageMultiplier: 1 + 0.1*float64(druid.Talents.SavageFury), - ThreatMultiplier: SwipeThreatMultiplier, + BaseDamageMultiplierAdditive: baseMultiplier, + DamageMultiplierAdditive: 1 + 0.1*float64(druid.Talents.SavageFury), + ThreatMultiplier: SwipeThreatMultiplier, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { for idx := range results { + dotBonusCrit := 0.0 + if hasLacerate && druid.LacerateBleed.Dot(target).GetStacks() > 0 { + dotBonusCrit = druid.FuryOfStormrageCritRatingBonus + } + + spell.BonusCritRating += dotBonusCrit results[idx] = spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) + spell.BonusCritRating -= dotBonusCrit + target = sim.Environment.NextTargetUnit(target) } for _, result := range results { spell.DealDamage(sim, result) } + + if hasGore && sim.Proc(0.15, "Gore") { + druid.AddRage(sim, 10.0, rageMetrics) + druid.MangleBear.CD.Reset() + } }, - }) + } } func (druid *Druid) registerSwipeCatSpell() { diff --git a/sim/druid/talents.go b/sim/druid/talents.go index 5853a85eda..03a0db98c0 100644 --- a/sim/druid/talents.go +++ b/sim/druid/talents.go @@ -22,8 +22,7 @@ func (druid *Druid) ApplyTalents() { // Feral druid.applyBloodFrenzy() - - druid.ApplyEquipScaling(stats.Armor, druid.ThickHideMultiplier()) + druid.applyPrimalFury() if druid.Talents.HeartOfTheWild > 0 { bonus := 0.04 * float64(druid.Talents.HeartOfTheWild) @@ -36,21 +35,6 @@ func (druid *Druid) ApplyTalents() { druid.PseudoStats.SpiritRegenRateCasting += .05 * float64(druid.Talents.Reflection) } -func (druid *Druid) ThickHideMultiplier() float64 { - thickHideMulti := 1.0 - - if druid.Talents.ThickHide > 0 { - thickHideMulti += 0.04 + 0.03*float64(druid.Talents.ThickHide-1) - } - - return thickHideMulti -} - -func (druid *Druid) BearArmorMultiplier() float64 { - sotfMulti := 1.0 + 0.33/3.0 - return 4.7 * sotfMulti -} - func (druid *Druid) applyNaturesGrace() { if !druid.Talents.NaturesGrace { return @@ -173,42 +157,28 @@ func (druid *Druid) applyNaturesGrace() { // }) // } -// TODO: Classic bear -// func (druid *Druid) applyPrimalFury() { -// if druid.Talents.PrimalFury == 0 { -// return -// } +func (druid *Druid) applyPrimalFury() { + if druid.Talents.PrimalFury == 0 { + return + } -// procChance := []float64{0, 0.5, 1}[druid.Talents.PrimalFury] -// actionID := core.ActionID{SpellID: 37117} -// rageMetrics := druid.NewRageMetrics(actionID) -// cpMetrics := druid.NewComboPointMetrics(actionID) + procChance := []float64{0, 0.5, 1}[druid.Talents.PrimalFury] + actionID := core.ActionID{SpellID: 16959} + rageMetrics := druid.NewRageMetrics(actionID) -// druid.RegisterAura(core.Aura{ -// Label: "Primal Fury", -// 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 druid.InForm(Bear) { -// if result.Outcome.Matches(core.OutcomeCrit) { -// if sim.Proc(procChance, "Primal Fury") { -// druid.AddRage(sim, 5, rageMetrics) -// } -// } -// } else if druid.InForm(Cat) { -// if druid.IsMangle(spell) || druid.Shred.IsEqual(spell) || druid.Rake.IsEqual(spell) { -// if result.Outcome.Matches(core.OutcomeCrit) { -// if sim.Proc(procChance, "Primal Fury") { -// druid.AddComboPoints(sim, 1, cpMetrics) -// } -// } -// } -// } -// }, -// }) -// } + druid.PrimalFuryAura = druid.RegisterAura(core.Aura{ + Label: "Primal Fury", + 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.Outcome.Matches(core.OutcomeCrit) && sim.Proc(procChance, "Primal Fury") { + druid.AddRage(sim, 5, rageMetrics) + } + }, + }) +} func (druid *Druid) applyBloodFrenzy() { if druid.Talents.BloodFrenzy == 0 { diff --git a/sim/druid/_tank/tank.go b/sim/druid/tank/tank.go similarity index 83% rename from sim/druid/_tank/tank.go rename to sim/druid/tank/tank.go index 69b82ded74..26ecad2a72 100644 --- a/sim/druid/_tank/tank.go +++ b/sim/druid/tank/tank.go @@ -38,8 +38,9 @@ func NewFeralTankDruid(character *core.Character, options *proto.Player) *FeralT } bear.EnableRageBar(core.RageBarOptions{ - StartingRage: bear.Options.StartingRage, - RageMultiplier: 1, + StartingRage: bear.Options.StartingRage, + DamageDealtMultiplier: 1, + DamageTakenMultiplier: 1, }) bear.EnableAutoAttacks(bear, core.AutoAttackOptions{ @@ -57,6 +58,9 @@ func NewFeralTankDruid(character *core.Character, options *proto.Player) *FeralT } } + bear.PseudoStats.FeralCombatEnabled = true + bear.PseudoStats.InFrontOfTarget = true + return bear } @@ -72,12 +76,16 @@ func (bear *FeralTankDruid) GetDruid() *druid.Druid { func (bear *FeralTankDruid) Initialize() { bear.Druid.Initialize() - bear.RegisterFeralTankSpells() + queuedRealismICD := &core.Cooldown{ + Timer: bear.NewTimer(), + Duration: core.SpellBatchWindow * 10, + } + bear.RegisterFeralTankSpells(queuedRealismICD) } func (bear *FeralTankDruid) Reset(sim *core.Simulation) { bear.Druid.Reset(sim) - bear.Druid.ClearForm(sim) + bear.Druid.CancelShapeshift(sim) bear.BearFormAura.Activate(sim) bear.Druid.PseudoStats.Stunned = false } diff --git a/sim/druid/tank/tank_test.go b/sim/druid/tank/tank_test.go new file mode 100644 index 0000000000..806c1cfd56 --- /dev/null +++ b/sim/druid/tank/tank_test.go @@ -0,0 +1,68 @@ +package tank + +import ( + "testing" + + _ "github.com/wowsims/sod/sim/common" + "github.com/wowsims/sod/sim/core" + "github.com/wowsims/sod/sim/core/proto" +) + +func init() { + RegisterFeralTankDruid() +} + +func TestFeralTank(t *testing.T) { + core.RunTestSuite(t, t.Name(), core.FullCharacterTestSuiteGenerator([]core.CharacterSuiteConfig{ + { + Class: proto.Class_ClassDruid, + Level: 60, + Phase: 5, + Race: proto.Race_RaceTauren, + + GearSet: core.GetGearSet("../../../ui/feral_tank_druid/gear_sets", "phase_5"), + Talents: StandardTalents, + Buffs: core.FullBuffsPhase4, + Consumes: FullConsumes, + SpecOptions: core.SpecOptionsCombo{Label: "Default", SpecOptions: PlayerOptionsDefault}, + Rotation: core.GetAplRotation("../../../ui/feral_tank_druid/apls", "phase_5"), + + ItemFilter: core.ItemFilter{ + WeaponTypes: []proto.WeaponType{ + proto.WeaponType_WeaponTypeDagger, + proto.WeaponType_WeaponTypeMace, + proto.WeaponType_WeaponTypeOffHand, + proto.WeaponType_WeaponTypeStaff, + }, + ArmorType: proto.ArmorType_ArmorTypeLeather, + RangedWeaponTypes: []proto.RangedWeaponType{ + proto.RangedWeaponType_RangedWeaponTypeIdol, + }, + }, + }, + })) +} + +var StandardTalents = "500005001-5050321303022151-05002" + +var PlayerOptionsDefault = &proto.Player_FeralTankDruid{ + FeralTankDruid: &proto.FeralTankDruid{ + Options: &proto.FeralTankDruid_Options{ + InnervateTarget: &proto.UnitReference{}, // no Innervate + StartingRage: 20, + }, + }, +} + +var FullConsumes = core.ConsumesCombo{ + Label: "Full Consumes", + Consumes: &proto.Consumes{ + AgilityElixir: proto.AgilityElixir_ElixirOfTheMongoose, + AttackPowerBuff: proto.AttackPowerBuff_JujuMight, + DragonBreathChili: true, + Flask: proto.Flask_FlaskOfTheTitans, + Food: proto.Food_FoodDirgesKickChimaerokChops, + MainHandImbue: proto.WeaponImbue_WildStrikes, + StrengthBuff: proto.StrengthBuff_JujuPower, + }, +} diff --git a/sim/register_all.go b/sim/register_all.go index d8f97b2942..e3560eb226 100644 --- a/sim/register_all.go +++ b/sim/register_all.go @@ -11,8 +11,8 @@ import ( "github.com/wowsims/sod/sim/shaman/warden" "github.com/wowsims/sod/sim/druid/feral" + feralTank "github.com/wowsims/sod/sim/druid/tank" // restoDruid "github.com/wowsims/sod/sim/druid/restoration" - // feralTank "github.com/wowsims/sod/sim/druid/tank" _ "github.com/wowsims/sod/sim/encounters" "github.com/wowsims/sod/sim/hunter" "github.com/wowsims/sod/sim/mage" @@ -40,7 +40,7 @@ func RegisterAll() { balance.RegisterBalanceDruid() feral.RegisterFeralDruid() - // feralTank.RegisterFeralTankDruid() + feralTank.RegisterFeralTankDruid() // restoDruid.RegisterRestorationDruid() elemental.RegisterElementalShaman() enhancement.RegisterEnhancementShaman() diff --git a/sim/rogue/evasion.go b/sim/rogue/evasion.go index 5e523c9a80..de2303f740 100644 --- a/sim/rogue/evasion.go +++ b/sim/rogue/evasion.go @@ -4,14 +4,14 @@ import ( "time" "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/stats" "github.com/wowsims/sod/sim/core/proto" + "github.com/wowsims/sod/sim/core/stats" ) func (rogue *Rogue) RegisterEvasionSpell() { //Used to double evasion due it ignoring the dynamic -50% dodge suppresion aura from JAFW hasJAFW := rogue.HasRune(proto.RogueRune_RuneJustAFleshWound) - + rogue.EvasionAura = rogue.RegisterAura(core.Aura{ Label: "Evasion", ActionID: core.ActionID{SpellID: 5277}, diff --git a/tools/database/gen_db/main.go b/tools/database/gen_db/main.go index cee2610bfc..ca5315d731 100644 --- a/tools/database/gen_db/main.go +++ b/tools/database/gen_db/main.go @@ -432,12 +432,11 @@ func GetAllRotationSpellIds() map[string][]int32 { Level: 60, Equipment: &proto.EquipmentSpec{}, }, &proto.Player_BalanceDruid{BalanceDruid: &proto.BalanceDruid{Options: &proto.BalanceDruid_Options{}}}), nil, nil, nil)}, - // TODO: Druid Tank Sim - // {Name: "druid tank", Raid: core.SinglePlayerRaidProto(core.WithSpec(&proto.Player{ - // Class: proto.Class_ClassDruid, - // Level: 60, - // Equipment: &proto.EquipmentSpec{}, - // }, &proto.Player_FeralTankDruid{FeralTankDruid: &proto.FeralTankDruid{Options: &proto.FeralTankDruid_Options{}}}), nil, nil, nil)}, + {Name: "druid tank", Raid: core.SinglePlayerRaidProto(core.WithSpec(&proto.Player{ + Class: proto.Class_ClassDruid, + Level: 60, + Equipment: &proto.EquipmentSpec{}, + }, &proto.Player_FeralTankDruid{FeralTankDruid: &proto.FeralTankDruid{Options: &proto.FeralTankDruid_Options{}}}), nil, nil, nil)}, {Name: "elemental", Raid: core.SinglePlayerRaidProto(core.WithSpec(&proto.Player{ Class: proto.Class_ClassShaman, Level: 60, diff --git a/tools/scrape_enchant_descriptions.sh b/tools/scrape_enchant_descriptions.sh old mode 100755 new mode 100644 diff --git a/ui/core/launched_sims.ts b/ui/core/launched_sims.ts index eb0658f7e1..6f0e0209db 100644 --- a/ui/core/launched_sims.ts +++ b/ui/core/launched_sims.ts @@ -33,8 +33,8 @@ export const simLaunchStatuses: Record = { status: LaunchStatus.Alpha, }, [Spec.SpecFeralTankDruid]: { - phase: Phase.Phase1, - status: LaunchStatus.Unlaunched, + phase: Phase.Phase5, + status: LaunchStatus.Alpha, }, [Spec.SpecRestorationDruid]: { phase: Phase.Phase1, diff --git a/ui/feral_tank_druid/apls/default.apl.json b/ui/feral_tank_druid/apls/default.apl.json deleted file mode 100644 index 6c3a5b13e9..0000000000 --- a/ui/feral_tank_druid/apls/default.apl.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "type": "TypeAPL", - "prepullActions": [ - {"action":{"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}},"doAtValue":{"const":{"val":"-1s"}}} - ], - "priorityList": [ - {"action":{"autocastOtherCooldowns":{}}}, - {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpEq","lhs":{"auraNumStacks":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":48568}}},"rhs":{"const":{"val":"5"}}}},{"cmp":{"op":"OpLe","lhs":{"dotRemainingTime":{"spellId":{"spellId":48568}}},"rhs":{"const":{"val":"1.5s"}}}}]}},"castSpell":{"spellId":{"spellId":48568}}}}, - {"action":{"castSpell":{"spellId":{"spellId":48564}}}}, - {"action":{"condition":{"and":{"vals":[{"gcdIsReady":{}},{"not":{"val":{"spellIsReady":{"spellId":{"spellId":48564}}}}},{"cmp":{"op":"OpLe","lhs":{"spellTimeToReady":{"spellId":{"spellId":48564}}},"rhs":{"const":{"val":"1.2s"}}}}]}},"wait":{"duration":{"spellTimeToReady":{"spellId":{"spellId":48564}}}}}}, - {"action":{"condition":{"auraShouldRefresh":{"auraId":{"spellId":48560},"maxOverlap":{"const":{"val":"1.5s"}}}},"castSpell":{"spellId":{"spellId":48560}}}}, - {"action":{"castSpell":{"spellId":{"spellId":16857}}}}, - {"action":{"condition":{"or":{"vals":[{"cmp":{"op":"OpLt","lhs":{"auraNumStacks":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":48568}}},"rhs":{"const":{"val":"5"}}}},{"cmp":{"op":"OpLe","lhs":{"dotRemainingTime":{"spellId":{"spellId":48568}}},"rhs":{"const":{"val":"8s"}}}}]}},"castSpell":{"spellId":{"spellId":48568}}}}, - {"action":{"condition":{"cmp":{"op":"OpGe","lhs":{"currentRage":{}},"rhs":{"const":{"val":"40"}}}},"castSpell":{"spellId":{"spellId":48562}}}}, - {"action":{"condition":{"cmp":{"op":"OpGe","lhs":{"currentRage":{}},"rhs":{"const":{"val":"25"}}}},"castSpell":{"spellId":{"spellId":48480,"tag":1}}}} - ] -} \ No newline at end of file diff --git a/ui/feral_tank_druid/apls/phase_5.apl.json b/ui/feral_tank_druid/apls/phase_5.apl.json new file mode 100644 index 0000000000..ba0ddc8fb5 --- /dev/null +++ b/ui/feral_tank_druid/apls/phase_5.apl.json @@ -0,0 +1,11 @@ +{ + "type": "TypeAPL", + "prepullActions": [ + {"action":{"castSpell":{"spellId":{"spellId":9634}}},"doAtValue":{"const":{"val":"-1s"}}} + ], + "priorityList": [ + {"action":{"condition":{"spellIsReady":{"spellId":{"spellId":407995}}},"castSpell":{"spellId":{"spellId":407995}}}}, + {"action":{"condition":{"cmp":{"op":"OpEq","lhs":{"const":{"val":"1"}},"rhs":{"const":{"val":"1"}}}},"castSpell":{"spellId":{"spellId":414644}}}}, + {"action":{"condition":{"cmp":{"op":"OpEq","lhs":{"const":{"val":"1"}},"rhs":{"const":{"val":"1"}}}},"castSpell":{"spellId":{"spellId":9881,"tag":1,"rank":7}}}} + ] +} \ No newline at end of file diff --git a/ui/feral_tank_druid/gear_sets/blank.gear.json b/ui/feral_tank_druid/gear_sets/blank.gear.json deleted file mode 100644 index 4bcab0861e..0000000000 --- a/ui/feral_tank_druid/gear_sets/blank.gear.json +++ /dev/null @@ -1 +0,0 @@ -{"items": []} \ No newline at end of file diff --git a/ui/feral_tank_druid/gear_sets/phase_5.gear.json b/ui/feral_tank_druid/gear_sets/phase_5.gear.json new file mode 100644 index 0000000000..a4f78a2006 --- /dev/null +++ b/ui/feral_tank_druid/gear_sets/phase_5.gear.json @@ -0,0 +1,21 @@ +{ + "items": [ + {"id":231257,"enchant":7124,"rune":417145}, + {"id":231803}, + {"id":231259,"enchant":2606}, + {"id":230842,"enchant":849,"rune":439510}, + {"id":231254,"enchant":1891,"rune":407977}, + {"id":231261,"enchant":1885,"rune":414719}, + {"id":232100,"enchant":927,"rune":407995}, + {"id":232096,"rune":417141}, + {"id":232098,"enchant":7615,"rune":407988}, + {"id":232101,"enchant":1887,"rune":417046}, + {"id":230734,"rune":453622}, + {"id":228261,"rune":442896}, + {"id":231779}, + {"id":230282}, + {"id":224282,"enchant":1900}, + {}, + {"id":220606} + ] +} diff --git a/ui/feral_tank_druid/index.ts b/ui/feral_tank_druid/index.ts index 88dba4f795..70f80d9581 100644 --- a/ui/feral_tank_druid/index.ts +++ b/ui/feral_tank_druid/index.ts @@ -7,7 +7,6 @@ import { FeralTankDruidSimUI } from './sim.js'; const sim = new Sim(); const player = new Player(Spec.SpecFeralTankDruid, sim); -player.enableHealing(); sim.raid.setPlayer(TypedEvent.nextEventID(), 0, player); diff --git a/ui/feral_tank_druid/presets.ts b/ui/feral_tank_druid/presets.ts index 7f8093ae44..de78932794 100644 --- a/ui/feral_tank_druid/presets.ts +++ b/ui/feral_tank_druid/presets.ts @@ -1,8 +1,22 @@ import { Phase } from '../core/constants/other.js'; import { + AgilityElixir, + AttackPowerBuff, Consumes, + Debuffs, + EnchantedSigil, Flask, Food, + IndividualBuffs, + Potions, + Profession, + RaidBuffs, + SaygesFortune, + Spec, + StrengthBuff, + TristateEffect, + WeaponImbue, + ZanzaBuff, UnitReference } from '../core/proto/common.js'; import { SavedTalents } from '../core/proto/ui.js'; @@ -10,13 +24,14 @@ import { SavedTalents } from '../core/proto/ui.js'; import { FeralTankDruid_Options as DruidOptions, FeralTankDruid_Rotation as DruidRotation, + FeralTankDruid_Rotation, } from '../core/proto/druid.js'; import * as PresetUtils from '../core/preset_utils.js'; -import BlankGear from './gear_sets/blank.gear.json'; +import Phase5Gear from './gear_sets/phase_5.gear.json'; -import DefaultApl from './apls/default.apl.json'; +import Phase5APL from './apls/phase_5.apl.json'; // Preset options for this spec. // Eventually we will import these values for the raid sim too, so its good to @@ -26,45 +41,48 @@ import DefaultApl from './apls/default.apl.json'; // Gear Presets /////////////////////////////////////////////////////////////////////////// -export const GearBlank = PresetUtils.makePresetGear('Blank', BlankGear); +export const P5Gear = PresetUtils.makePresetGear('Phase 5', Phase5Gear); export const GearPresets = { - [Phase.Phase1]: [ - GearBlank, - ], - [Phase.Phase2]: [ - ] + [Phase.Phase1]: [P5Gear], + [Phase.Phase2]: [P5Gear], + [Phase.Phase3]: [P5Gear], + [Phase.Phase4]: [P5Gear], + [Phase.Phase5]: [P5Gear] }; // TODO: Add Phase 2 preset and pull from map -export const DefaultGear = GearPresets[Phase.Phase1][0]; +export const DefaultGear = GearPresets[Phase.Phase5][0]; /////////////////////////////////////////////////////////////////////////// // APL Presets /////////////////////////////////////////////////////////////////////////// -export const DefaultRotation = DruidRotation.create({ - maulRageThreshold: 25, - maintainDemoralizingRoar: true, - lacerateTime: 8.0, -}); - -export const DefaultAPL = PresetUtils.makePresetAPLRotation('Default', DefaultApl); +export const APLPhase5 = PresetUtils.makePresetAPLRotation('Phase 5', Phase5APL, { customCondition: player => player.getLevel() === 60 }); export const APLPresets = { - [Phase.Phase1]: [ - DefaultAPL, - ], - [Phase.Phase2]: [ - ] + [Phase.Phase1]: [APLPhase5], + [Phase.Phase2]: [APLPhase5], + [Phase.Phase3]: [APLPhase5], + [Phase.Phase4]: [APLPhase5], + [Phase.Phase5]: [APLPhase5] }; -// TODO: Add Phase 2 preset and pull from map export const DefaultAPLs: Record = { 25: APLPresets[Phase.Phase1][0], - 40: APLPresets[Phase.Phase1][0], + 40: APLPresets[Phase.Phase2][0], + 50: APLPresets[Phase.Phase3][0], + 60: APLPresets[Phase.Phase5][0] }; +export const DefaultRotation = FeralTankDruid_Rotation.create({ + maulRageThreshold: 25, + maintainDemoralizingRoar: true, + lacerateTime: 8.0, +}); + +export const SIMPLE_ROTATION_DEFAULT = PresetUtils.makePresetSimpleRotation('Default', Spec.SpecFeralTankDruid, DefaultRotation); + /////////////////////////////////////////////////////////////////////////// // Talent Presets /////////////////////////////////////////////////////////////////////////// @@ -75,20 +93,20 @@ export const DefaultAPLs: Record = { export const StandardTalents = { name: 'Standard', data: SavedTalents.create({ - talentsString: '-503232132322010353120300313511-20350001', + talentsString: '500005001-5050321303022151-05002', }), }; export const TalentPresets = { - [Phase.Phase1]: [ - StandardTalents, - ], - [Phase.Phase2]: [ - ] + [Phase.Phase1]: [StandardTalents], + [Phase.Phase2]: [StandardTalents], + [Phase.Phase3]: [StandardTalents], + [Phase.Phase4]: [StandardTalents], + [Phase.Phase5]: [StandardTalents], }; // TODO: Add Phase 2 preset and pull from map -export const DefaultTalents = TalentPresets[Phase.Phase1][0]; +export const DefaultTalents = TalentPresets[Phase.Phase5][0]; /////////////////////////////////////////////////////////////////////////// // Options @@ -100,6 +118,57 @@ export const DefaultOptions = DruidOptions.create({ }); export const DefaultConsumes = Consumes.create({ - flask: Flask.FlaskUnknown, - food: Food.FoodUnknown, + agilityElixir: AgilityElixir.ElixirOfTheMongoose, + attackPowerBuff: AttackPowerBuff.JujuMight, + defaultPotion: Potions.GreaterStoneshieldPotion, + dragonBreathChili: true, + enchantedSigil: EnchantedSigil.FlowingWatersSigil, + flask: Flask.FlaskOfTheTitans, + food: Food.FoodDirgesKickChimaerokChops, + mainHandImbue: WeaponImbue.ElementalSharpeningStone, + miscConsumes: { + catnip: true, + jujuEmber: true, + }, + strengthBuff: StrengthBuff.JujuPower, + zanzaBuff: ZanzaBuff.ROIDS, }); + +export const DefaultRaidBuffs = RaidBuffs.create({ + arcaneBrilliance: true, + aspectOfTheLion: true, + battleShout: TristateEffect.TristateEffectImproved, + divineSpirit: true, + giftOfTheWild: TristateEffect.TristateEffectImproved, + graceOfAirTotem: TristateEffect.TristateEffectImproved, + leaderOfThePack: true, + manaSpringTotem: TristateEffect.TristateEffectRegular, + strengthOfEarthTotem: TristateEffect.TristateEffectImproved, +}); + +export const DefaultIndividualBuffs = IndividualBuffs.create({ + blessingOfKings: true, + blessingOfMight: TristateEffect.TristateEffectImproved, + blessingOfWisdom: TristateEffect.TristateEffectImproved, + fengusFerocity: true, + mightOfStormwind: true, + rallyingCryOfTheDragonslayer: true, + saygesFortune: SaygesFortune.SaygesDamage, + songflowerSerenade: true, + spiritOfZandalar: true, + valorOfAzeroth: true, + warchiefsBlessing: true, +}); + +export const DefaultDebuffs = Debuffs.create({ + curseOfRecklessness: true, + exposeArmor: TristateEffect.TristateEffectImproved, + faerieFire: true, + homunculi: 70, // 70% average uptime default + sunderArmor: true, +}); + +export const OtherDefaults = { + profession1: Profession.Enchanting, + profession2: Profession.Alchemy, +}; \ No newline at end of file diff --git a/ui/feral_tank_druid/sim.ts b/ui/feral_tank_druid/sim.ts index 0450c93c19..44e75ed3e6 100644 --- a/ui/feral_tank_druid/sim.ts +++ b/ui/feral_tank_druid/sim.ts @@ -2,8 +2,9 @@ import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs'; import * as OtherInputs from '../core/components/other_inputs.js'; import { Phase } from '../core/constants/other.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; +import { APLRotation_Type as APLRotationType } from '../core/proto/apl.js'; import { Player } from '../core/player.js'; -import { Class, Debuffs, Faction, IndividualBuffs, PartyBuffs, PseudoStat, Race, RaidBuffs, Spec, Stat, TristateEffect } from '../core/proto/common.js'; +import { Class, Faction, PartyBuffs, PseudoStat, Race, Spec, Stat, WeaponImbue } from '../core/proto/common.js'; import { Stats } from '../core/proto_utils/stats.js'; import { getSpecIcon, specNames } from '../core/proto_utils/utils.js'; import * as DruidInputs from './inputs.js'; @@ -14,6 +15,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecFeralTankDruid, { cssScheme: 'druid', // List any known bugs / issues here and they'll be shown on the site. knownIssues: [], + warnings: [], // All stats for which EP should be calculated. epStats: [ @@ -53,7 +55,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecFeralTankDruid, { Stat.StatSpellCrit, Stat.StatShadowResistance, ], - displayPseudoStats: [], + displayPseudoStats: [PseudoStat.PseudoStatMainHandDps], defaults: { // Default equipped gear. @@ -80,30 +82,18 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecFeralTankDruid, { // Default consumes settings. consumes: Presets.DefaultConsumes, // Default rotation settings. + rotationType: APLRotationType.TypeSimple, simpleRotation: Presets.DefaultRotation, // Default talents. talents: Presets.DefaultTalents.data, // Default spec-specific settings. specOptions: Presets.DefaultOptions, + other: Presets.OtherDefaults, // Default raid/party buffs settings. - raidBuffs: RaidBuffs.create({ - powerWordFortitude: TristateEffect.TristateEffectImproved, - shadowProtection: true, - giftOfTheWild: TristateEffect.TristateEffectImproved, - thorns: TristateEffect.TristateEffectImproved, - strengthOfEarthTotem: TristateEffect.TristateEffectRegular, - battleShout: TristateEffect.TristateEffectImproved, - moonkinAura: true, - }), + raidBuffs: Presets.DefaultRaidBuffs, partyBuffs: PartyBuffs.create({}), - individualBuffs: IndividualBuffs.create({ - blessingOfKings: true, - blessingOfMight: TristateEffect.TristateEffectImproved, - }), - debuffs: Debuffs.create({ - faerieFire: true, - exposeArmor: TristateEffect.TristateEffectImproved, - }), + individualBuffs: Presets.DefaultIndividualBuffs, + debuffs: Presets.DefaultDebuffs, }, // IconInputs to include in the 'Player' section on the settings tab. @@ -112,7 +102,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecFeralTankDruid, { rotationInputs: DruidInputs.FeralTankDruidRotationConfig, // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [BuffDebuffInputs.SpellCritBuff, BuffDebuffInputs.SpellISBDebuff], - excludeBuffDebuffInputs: [], + excludeBuffDebuffInputs: [WeaponImbue.ElementalSharpeningStone, WeaponImbue.DenseSharpeningStone, WeaponImbue.WildStrikes], // Inputs to include in the 'Other' section on the settings tab. otherInputs: { inputs: [ diff --git a/ui/index.html b/ui/index.html index 4490ae9775..5cadd01874 100644 --- a/ui/index.html +++ b/ui/index.html @@ -134,14 +134,13 @@

Season of Discovery

  • - - +