diff --git a/proto/warlock.proto b/proto/warlock.proto index 25a07585a9..4a5a48ac28 100644 --- a/proto/warlock.proto +++ b/proto/warlock.proto @@ -178,6 +178,7 @@ message Warlock { bool detonate_seed = 6; SpecSpell spec_spell = 7; Type type = 8; + bool use_infernal = 9; } Rotation rotation = 1; diff --git a/sim/warlock/chaos_bolt.go b/sim/warlock/chaos_bolt.go index b0bc2ffd24..e21fbdec3b 100644 --- a/sim/warlock/chaos_bolt.go +++ b/sim/warlock/chaos_bolt.go @@ -39,7 +39,7 @@ func (warlock *Warlock) registerChaosBoltSpell() { }, BonusCritRating: 0 + - warlock.masterDemonologistFireCrit() + + warlock.masterDemonologistFireCrit + core.TernaryFloat64(warlock.Talents.Devastation, 1, 0)*5*core.CritRatingPerCritChance, DamageMultiplierAdditive: 1 + warlock.GrandFirestoneBonus() + diff --git a/sim/warlock/conflagrate.go b/sim/warlock/conflagrate.go index e924eb8d61..e2186cfbe5 100644 --- a/sim/warlock/conflagrate.go +++ b/sim/warlock/conflagrate.go @@ -47,7 +47,7 @@ func (warlock *Warlock) registerConflagrateSpell() { }, BonusCritRating: 0 + - warlock.masterDemonologistFireCrit() + + warlock.masterDemonologistFireCrit + core.TernaryFloat64(warlock.Talents.Devastation, 5*core.CritRatingPerCritChance, 0) + 5*float64(warlock.Talents.FireAndBrimstone)*core.CritRatingPerCritChance, DamageMultiplierAdditive: 1 + diff --git a/sim/warlock/corruption.go b/sim/warlock/corruption.go index d254fef8c3..57e42d4455 100644 --- a/sim/warlock/corruption.go +++ b/sim/warlock/corruption.go @@ -29,7 +29,7 @@ func (warlock *Warlock) registerCorruptionSpell() { }, BonusCritRating: 0 + - warlock.masterDemonologistShadowCrit() + + warlock.masterDemonologistShadowCrit + 3*float64(warlock.Talents.Malediction)*core.CritRatingPerCritChance + core.TernaryFloat64(warlock.HasSetBonus(ItemSetDarkCovensRegalia, 2), 5*core.CritRatingPerCritChance, 0), DamageMultiplierAdditive: 1 + diff --git a/sim/warlock/demonic_empowerment.go b/sim/warlock/demonic_empowerment.go index 4c49a09e0d..bb98d69f50 100644 --- a/sim/warlock/demonic_empowerment.go +++ b/sim/warlock/demonic_empowerment.go @@ -52,9 +52,7 @@ func (warlock *Warlock) registerDemonicEmpowermentSpell() { } if warlock.Options.Summon != proto.Warlock_Options_NoSummon { - warlock.Pet.DemonicEmpowermentAura = warlock.Pet.RegisterAura( - petAura, - ) + warlock.Pet.DemonicEmpowermentAura = warlock.Pet.RegisterAura(petAura) } warlock.DemonicEmpowerment = warlock.RegisterSpell(core.SpellConfig{ diff --git a/sim/warlock/haunt.go b/sim/warlock/haunt.go index f48715e0e9..dd26d9e3cc 100644 --- a/sim/warlock/haunt.go +++ b/sim/warlock/haunt.go @@ -51,7 +51,7 @@ func (warlock *Warlock) registerHauntSpell() { }, BonusCritRating: 0 + - warlock.masterDemonologistShadowCrit(), + warlock.masterDemonologistShadowCrit, DamageMultiplierAdditive: 1 + warlock.GrandFirestoneBonus() + 0.03*float64(warlock.Talents.ShadowMastery), diff --git a/sim/warlock/immolate.go b/sim/warlock/immolate.go index 626321081b..102bfb9b43 100644 --- a/sim/warlock/immolate.go +++ b/sim/warlock/immolate.go @@ -33,7 +33,7 @@ func (warlock *Warlock) registerImmolateSpell() { }, BonusCritRating: 0 + - warlock.masterDemonologistFireCrit() + + warlock.masterDemonologistFireCrit + core.TernaryFloat64(warlock.Talents.Devastation, 5*core.CritRatingPerCritChance, 0), DamageMultiplierAdditive: 1 + warlock.GrandFirestoneBonus() + diff --git a/sim/warlock/incinerate.go b/sim/warlock/incinerate.go index 3a42382977..e5a77b1ca5 100644 --- a/sim/warlock/incinerate.go +++ b/sim/warlock/incinerate.go @@ -32,7 +32,7 @@ func (warlock *Warlock) registerIncinerateSpell() { }, BonusCritRating: 0 + - warlock.masterDemonologistFireCrit() + + warlock.masterDemonologistFireCrit + core.TernaryFloat64(warlock.Talents.Devastation, 5*core.CritRatingPerCritChance, 0) + core.TernaryFloat64(warlock.HasSetBonus(ItemSetDeathbringerGarb, 4), 5*core.CritRatingPerCritChance, 0) + core.TernaryFloat64(warlock.HasSetBonus(ItemSetDarkCovensRegalia, 2), 5*core.CritRatingPerCritChance, 0), diff --git a/sim/warlock/inferno.go b/sim/warlock/inferno.go new file mode 100644 index 0000000000..939c646f26 --- /dev/null +++ b/sim/warlock/inferno.go @@ -0,0 +1,204 @@ +package warlock + +import ( + "math" + "time" + + "github.com/wowsims/wotlk/sim/core" + "github.com/wowsims/wotlk/sim/core/proto" + "github.com/wowsims/wotlk/sim/core/stats" +) + +func (warlock *Warlock) registerInfernoSpell() { + if !warlock.Rotation.UseInfernal { + return + } + + summonInfernalAura := warlock.RegisterAura(core.Aura{ + Label: "Summon Infernal", + ActionID: core.ActionID{SpellID: 1122}, + Duration: time.Second * 60, + }) + + baseCost := 0.8 * warlock.BaseMana + warlock.Inferno = warlock.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: 1122}, + SpellSchool: core.SpellSchoolFire, + ProcMask: core.ProcMaskEmpty, + ResourceType: stats.Mana, + BaseCost: baseCost, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + CastTime: time.Millisecond * 1500, + Cost: baseCost, + GCD: core.GCDDefault, + }, + CD: core.Cooldown{ + Timer: warlock.NewTimer(), + Duration: time.Second * time.Duration(600), + }, + }, + + DamageMultiplier: 1, + ThreatMultiplier: 1, + CritMultiplier: warlock.SpellCritMultiplier(1, 0), + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + // TODO: add fire spell damage + baseDmg := (200 + 1*spell.SpellPower()) * sim.Encounter.AOECapMultiplier() + + for _, aoeTarget := range sim.Encounter.Targets { + spell.CalcAndDealDamage(sim, &aoeTarget.Unit, baseDmg, spell.OutcomeMagicHitAndCrit) + } + + warlock.Pet.Disable(sim) + warlock.Infernal.EnableWithTimeout(sim, warlock.Infernal, time.Second*60) + + // fake aura to show duration + summonInfernalAura.Activate(sim) + }, + }) + + warlock.AddMajorCooldown(core.MajorCooldown{ + Spell: warlock.Inferno, + Type: core.CooldownTypeDPS, + ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { + return sim.GetRemainingDuration() <= 61*time.Second + }, + }) +} + +type InfernalPet struct { + core.Pet + owner *Warlock + immolationAura *core.Spell +} + +func (warlock *Warlock) NewInfernal() *InfernalPet { + statInheritance := + func(ownerStats stats.Stats) stats.Stats { + ownerHitChance := math.Floor(ownerStats[stats.SpellHit] / core.SpellHitRatingPerHitChance) + + // TODO: account for fire spell damage + return stats.Stats{ + stats.Stamina: ownerStats[stats.Stamina] * 0.75, + stats.Intellect: ownerStats[stats.Intellect] * 0.3, + stats.Armor: ownerStats[stats.Armor] * 0.35, + stats.AttackPower: ownerStats[stats.SpellPower] * 0.57, + stats.SpellPower: ownerStats[stats.SpellPower] * 0.15, + stats.SpellPenetration: ownerStats[stats.SpellPenetration], + stats.MeleeHit: ownerHitChance * core.MeleeHitRatingPerHitChance, + stats.SpellHit: ownerHitChance * core.SpellHitRatingPerHitChance, + stats.Expertise: (ownerStats[stats.SpellHit] / core.SpellHitRatingPerHitChance) * + PetExpertiseScale * core.ExpertisePerQuarterPercentReduction, + } + } + + infernal := &InfernalPet{ + Pet: core.NewPet( + "Infernal", + &warlock.Character, + stats.Stats{ + stats.Strength: 331, + stats.Agility: 113, + stats.Stamina: 361, + stats.Intellect: 65, + stats.Spirit: 109, + stats.Mana: 0, + stats.MeleeCrit: 3.192 * core.CritRatingPerCritChance, + }, + statInheritance, + false, + false, + ), + owner: warlock, + } + + infernal.AddStatDependency(stats.Strength, stats.AttackPower, 2) + infernal.AddStat(stats.AttackPower, -20) + + // infernal is classified as a warrior class, so we assume it gets the + // same agi crit coefficient + infernal.AddStatDependency(stats.Agility, stats.MeleeCrit, core.CritRatingPerCritChance*1/62.5) + + // command doesn't apply to infernal + if warlock.Race == proto.Race_RaceOrc { + infernal.PseudoStats.DamageDealtMultiplier /= 1.05 + } + + infernal.EnableAutoAttacks(infernal, core.AutoAttackOptions{ + MainHand: core.Weapon{ + BaseDamageMin: 330, + BaseDamageMax: 494.9, + SwingSpeed: 2, + SwingDuration: time.Second * 2, + CritMultiplier: 2, + }, + AutoSwingMelee: true, + }) + infernal.AutoAttacks.MHConfig.DamageMultiplier *= 3.2 + + core.ApplyPetConsumeEffects(&infernal.Character, warlock.Consumes) + + warlock.AddPet(infernal) + + return infernal +} + +func (infernal *InfernalPet) GetPet() *core.Pet { + return &infernal.Pet +} + +func (infernal *InfernalPet) Initialize() { + felarmor_coef := core.TernaryFloat64(infernal.owner.Options.Armor == proto.Warlock_Options_FelArmor, + 0.3*(1+float64(infernal.owner.Talents.DemonicAegis)*0.1), 0) + + immolationAuraDot := core.NewDot(core.Dot{ + Aura: infernal.RegisterAura(core.Aura{ + Label: "Immolation", + ActionID: core.ActionID{SpellID: 19483}, + }), + NumberOfTicks: 31, + TickLength: time.Second * 2, + AffectedByCastSpeed: false, + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + // TODO: use highest SP amount of all schools + // base formula is 25 + (lvl-50)*0.5 * Warlock_SP*0.2 + // note this scales with the warlocks SP, NOT with the pets + + // we remove all the spirit based sp since immolation aura doesn't benefit from it, see + // JamminL/wotlk-classic-bugs#329 + coef := core.TernaryFloat64(infernal.owner.GlyphOfLifeTapAura.IsActive(), 0.2, 0) + felarmor_coef + + warlockSP := infernal.owner.Unit.GetStat(stats.SpellPower) - infernal.owner.Unit.GetStat(stats.Spirit)*coef + baseDmg := (40 + warlockSP*0.2) * sim.Encounter.AOECapMultiplier() + + for _, aoeTarget := range sim.Encounter.Targets { + dot.Spell.CalcAndDealDamage(sim, &aoeTarget.Unit, baseDmg, dot.Spell.OutcomeMagicHit) + } + }, + }) + + infernal.immolationAura = infernal.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: 20153}, + SpellSchool: core.SpellSchoolFire, + ProcMask: core.ProcMaskEmpty, + ResourceType: stats.Mana, + + DamageMultiplier: 1, + ThreatMultiplier: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + immolationAuraDot.Apply(sim) + }, + }) + immolationAuraDot.Spell = infernal.immolationAura +} + +func (infernal *InfernalPet) Reset(sim *core.Simulation) { +} + +func (infernal *InfernalPet) OnGCDReady(sim *core.Simulation) { + infernal.immolationAura.Cast(sim, nil) + infernal.DoNothing() +} diff --git a/sim/warlock/items.go b/sim/warlock/items.go index ced6878f0a..5f11d83bf2 100644 --- a/sim/warlock/items.go +++ b/sim/warlock/items.go @@ -117,6 +117,7 @@ var ItemSetGuldansRegalia = core.NewItemSet(core.ItemSet{ Name: "Gul'dan's Regalia", Bonuses: map[int32]core.ApplyEffect{ 2: func(agent core.Agent) { + // TODO: probably doesn't apply to infernal warlock := agent.(WarlockAgent).GetWarlock() pet := warlock.Pets[0].GetCharacter() pet.AddStats(stats.Stats{ diff --git a/sim/warlock/pet.go b/sim/warlock/pet.go index ecabfd65e1..c95a9a4c7e 100644 --- a/sim/warlock/pet.go +++ b/sim/warlock/pet.go @@ -12,8 +12,6 @@ import ( type WarlockPet struct { core.Pet - config PetConfig - owner *Warlock primaryAbility *core.Spell @@ -39,22 +37,112 @@ func (warlock *Warlock) NewWarlockPet() *WarlockPet { } } - petConfig := PetConfigs[summonChoice] + var cfg struct { + Name string + PowerModifier float64 + Stats stats.Stats + AutoAttacks core.AutoAttackOptions + } + + switch summonChoice { + // TODO: revisit base damage once blizzard fixes JamminL/wotlk-classic-bugs#328 + case proto.Warlock_Options_Felguard: + cfg.Name = "Felguard" + cfg.PowerModifier = 0.77 // GetUnitPowerModifier("pet") + cfg.Stats = stats.Stats{ + stats.Strength: 314, + stats.Agility: 90, + stats.Stamina: 328, + stats.Intellect: 150, + stats.Spirit: 209, + stats.Mana: 1559, + stats.MeleeCrit: 3.2685 * core.CritRatingPerCritChance, + stats.SpellCrit: 3.3355 * core.CritRatingPerCritChance, + } + cfg.AutoAttacks = core.AutoAttackOptions{ + MainHand: core.Weapon{ + BaseDamageMin: 88.8, + BaseDamageMax: 133.3, + SwingSpeed: 2, + SwingDuration: time.Second * 2, + CritMultiplier: 2, + }, + AutoSwingMelee: true, + } + case proto.Warlock_Options_Imp: + cfg.Name = "Imp" + cfg.PowerModifier = 0.33 // GetUnitPowerModifier("pet") + cfg.Stats = stats.Stats{ + stats.Strength: 297, + stats.Agility: 79, + stats.Stamina: 118, + stats.Intellect: 369, + stats.Spirit: 367, + stats.Mana: 1174, + stats.MeleeCrit: 3.454 * core.CritRatingPerCritChance, + stats.SpellCrit: 0.9075 * core.CritRatingPerCritChance, + } + case proto.Warlock_Options_Succubus: + cfg.Name = "Succubus" + cfg.PowerModifier = 0.77 // GetUnitPowerModifier("pet") + cfg.Stats = stats.Stats{ + stats.Strength: 314, + stats.Agility: 90, + stats.Stamina: 328, + stats.Intellect: 150, + stats.Spirit: 209, + stats.Mana: 1559, + stats.MeleeCrit: 3.2685 * core.CritRatingPerCritChance, + stats.SpellCrit: 3.3355 * core.CritRatingPerCritChance, + } + cfg.AutoAttacks = core.AutoAttackOptions{ + MainHand: core.Weapon{ + BaseDamageMin: 98, + BaseDamageMax: 147, + SwingSpeed: 2, + SwingDuration: time.Second * 2, + CritMultiplier: 2, + }, + AutoSwingMelee: true, + } + case proto.Warlock_Options_Felhunter: + cfg.Name = "Felhunter" + cfg.PowerModifier = 0.77 // GetUnitPowerModifier("pet") + cfg.Stats = stats.Stats{ + stats.Strength: 314, + stats.Agility: 90, + stats.Stamina: 328, + stats.Intellect: 150, + stats.Spirit: 209, + stats.Mana: 1559, + stats.MeleeCrit: 3.2685 * core.CritRatingPerCritChance, + stats.SpellCrit: 3.3355 * core.CritRatingPerCritChance, + } + cfg.AutoAttacks = core.AutoAttackOptions{ + MainHand: core.Weapon{ + BaseDamageMin: 88.8, + BaseDamageMax: 133.3, + SwingSpeed: 2, + SwingDuration: time.Second * 2, + CritMultiplier: 2, + }, + AutoSwingMelee: true, + } + } wp := &WarlockPet{ Pet: core.NewPet( - petConfig.Name, + cfg.Name, &warlock.Character, - petConfig.Stats, + cfg.Stats, warlock.makeStatInheritance(), true, false, ), - config: petConfig, - owner: warlock, + owner: warlock, } - wp.EnableManaBarWithModifier(petConfig.PowerModifier) + wp.EnableManaBarWithModifier(cfg.PowerModifier) wp.EnableResumeAfterManaWait(wp.OnGCDReady) wp.AddStatDependency(stats.Strength, stats.AttackPower, 2) @@ -79,47 +167,11 @@ func (warlock *Warlock) NewWarlockPet() *WarlockPet { wp.PseudoStats.DamageDealtMultiplier *= 1.0 + 0.04*float64(warlock.Talents.UnholyPower) - if petConfig.Melee { - switch summonChoice { - // TODO: revisit base damage once blizzard fixes JamminL/wotlk-classic-bugs#328 - case proto.Warlock_Options_Felguard: - wp.EnableAutoAttacks(wp, core.AutoAttackOptions{ - MainHand: core.Weapon{ - BaseDamageMin: 88.8, - BaseDamageMax: 133.3, - SwingSpeed: 2, - SwingDuration: time.Second * 2, - CritMultiplier: 2, - }, - AutoSwingMelee: true, - }) - case proto.Warlock_Options_Succubus: - wp.EnableAutoAttacks(wp, core.AutoAttackOptions{ - MainHand: core.Weapon{ - BaseDamageMin: 98, - BaseDamageMax: 147, - SwingSpeed: 2, - SwingDuration: time.Second * 2, - CritMultiplier: 2, - }, - AutoSwingMelee: true, - }) - case proto.Warlock_Options_Felhunter: - wp.EnableAutoAttacks(wp, core.AutoAttackOptions{ - MainHand: core.Weapon{ - BaseDamageMin: 88.8, - BaseDamageMax: 133.3, - SwingSpeed: 2, - SwingDuration: time.Second * 2, - CritMultiplier: 2, - }, - AutoSwingMelee: true, - }) - } + if summonChoice != proto.Warlock_Options_Imp { // imps generally don't meele + wp.EnableAutoAttacks(wp, cfg.AutoAttacks) } - // wp.AutoAttacks.MHEffect.DamageMultiplier *= petConfig.DamageMultiplier - switch summonChoice { - case proto.Warlock_Options_Felguard: + + if summonChoice == proto.Warlock_Options_Felguard { if wp.owner.HasMajorGlyph(proto.WarlockMajorGlyph_GlyphOfFelguard) { wp.MultiplyStat(stats.AttackPower, 1.2) } @@ -166,6 +218,53 @@ func (warlock *Warlock) NewWarlockPet() *WarlockPet { wp.MultiplyStat(stats.Stamina, bonus) } + if warlock.Talents.MasterDemonologist > 0 { + val := 1.0 + 0.01*float64(warlock.Talents.MasterDemonologist) + md := core.Aura{ + Label: "Master Demonologist", + ActionID: core.ActionID{SpellID: 35706}, // many different spells associated with this talent + Duration: core.NeverExpires, + OnGain: func(aura *core.Aura, _ *core.Simulation) { + switch warlock.Options.Summon { + case proto.Warlock_Options_Imp: + aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexFire] *= val + case proto.Warlock_Options_Succubus: + aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexShadow] *= val + case proto.Warlock_Options_Felguard: + aura.Unit.PseudoStats.DamageDealtMultiplier *= val + } + }, + OnExpire: func(aura *core.Aura, _ *core.Simulation) { + switch warlock.Options.Summon { + case proto.Warlock_Options_Imp: + aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexFire] /= val + case proto.Warlock_Options_Succubus: + aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexShadow] /= val + case proto.Warlock_Options_Felguard: + aura.Unit.PseudoStats.DamageDealtMultiplier /= val + } + }, + } + + mdLockAura := warlock.RegisterAura(md) + mdPetAura := wp.RegisterAura(md) + + wp.OnPetEnable = func(sim *core.Simulation) { + val := float64(warlock.Talents.MasterDemonologist) * core.CritRatingPerCritChance + mdLockAura.Activate(sim) + mdPetAura.Activate(sim) + warlock.masterDemonologistFireCrit = core.TernaryFloat64(warlock.Options.Summon == proto.Warlock_Options_Imp, val, 0) + warlock.masterDemonologistShadowCrit = core.TernaryFloat64(warlock.Options.Summon == proto.Warlock_Options_Succubus, val, 0) + } + + wp.OnPetDisable = func(sim *core.Simulation) { + mdLockAura.Deactivate(sim) + mdPetAura.Deactivate(sim) + warlock.masterDemonologistFireCrit = 0.0 + warlock.masterDemonologistShadowCrit = 0.0 + } + } + core.ApplyPetConsumeEffects(&wp.Character, warlock.Consumes) warlock.AddPet(wp) @@ -178,33 +277,27 @@ func (wp *WarlockPet) GetPet() *core.Pet { } func (wp *WarlockPet) Initialize() { - wp.primaryAbility = wp.NewPetAbility(wp.config.PrimaryAbility, true) - wp.secondaryAbility = wp.NewPetAbility(wp.config.SecondaryAbility, false) + switch wp.owner.Options.Summon { + case proto.Warlock_Options_Felguard: + wp.primaryAbility = wp.NewPetAbility(Cleave, true) + wp.secondaryAbility = wp.NewPetAbility(Intercept, false) + case proto.Warlock_Options_Succubus: + wp.primaryAbility = wp.NewPetAbility(LashOfPain, true) + case proto.Warlock_Options_Felhunter: + wp.primaryAbility = wp.NewPetAbility(ShadowBite, true) + case proto.Warlock_Options_Imp: + wp.primaryAbility = wp.NewPetAbility(Firebolt, true) + } } func (wp *WarlockPet) Reset(sim *core.Simulation) { - } func (wp *WarlockPet) OnGCDReady(sim *core.Simulation) { target := wp.CurrentTarget - if wp.config.RandomSelection { - if sim.RandomFloat("Warlock Pet Ability") < 0.5 { - if !wp.TryCast(sim, target, wp.primaryAbility) { - wp.TryCast(sim, target, wp.secondaryAbility) - } - } else { - if !wp.TryCast(sim, target, wp.secondaryAbility) { - wp.TryCast(sim, target, wp.primaryAbility) - } - } - return - } if !wp.TryCast(sim, target, wp.primaryAbility) { - if wp.secondaryAbility != nil { - wp.TryCast(sim, target, wp.secondaryAbility) - } else if !wp.primaryAbility.IsReady(sim) { + if !wp.primaryAbility.IsReady(sim) { wp.WaitUntil(sim, wp.primaryAbility.CD.ReadyAt()) } else { wp.WaitForMana(sim, wp.primaryAbility.CurCast.Cost) @@ -240,85 +333,3 @@ func (warlock *Warlock) makeStatInheritance() core.PetStatInheritance { } } } - -type PetConfig struct { - Name string - // DamageMultiplier float64 - Melee bool - Stats stats.Stats - PowerModifier float64 - - // Randomly select between abilities instead of using a prio. - RandomSelection bool - - PrimaryAbility PetAbilityType - SecondaryAbility PetAbilityType -} - -var PetConfigs = map[proto.Warlock_Options_Summon]PetConfig{ - proto.Warlock_Options_Felguard: { - Name: "Felguard", - Melee: true, - PrimaryAbility: Cleave, - SecondaryAbility: Intercept, - PowerModifier: 0.77, // GetUnitPowerModifier("pet") - Stats: stats.Stats{ - stats.Strength: 314, - stats.Agility: 90, - stats.Stamina: 328, - stats.Intellect: 150, - stats.Spirit: 209, - stats.Mana: 1559, - stats.MeleeCrit: 3.2685 * core.CritRatingPerCritChance, - stats.SpellCrit: 3.3355 * core.CritRatingPerCritChance, - }, - }, - proto.Warlock_Options_Imp: { - Name: "Imp", - PowerModifier: 0.33, // GetUnitPowerModifier("pet") - Melee: false, - PrimaryAbility: Firebolt, - Stats: stats.Stats{ - stats.Strength: 297, - stats.Agility: 79, - stats.Stamina: 118, - stats.Intellect: 369, - stats.Spirit: 367, - stats.Mana: 1174, - stats.MeleeCrit: 3.454 * core.CritRatingPerCritChance, - stats.SpellCrit: 0.9075 * core.CritRatingPerCritChance, - }, - }, - proto.Warlock_Options_Succubus: { - Name: "Succubus", - PowerModifier: 0.77, // GetUnitPowerModifier("pet") - Melee: true, - PrimaryAbility: LashOfPain, - Stats: stats.Stats{ - stats.Strength: 314, - stats.Agility: 90, - stats.Stamina: 328, - stats.Intellect: 150, - stats.Spirit: 209, - stats.Mana: 1559, - stats.MeleeCrit: 3.2685 * core.CritRatingPerCritChance, - stats.SpellCrit: 3.3355 * core.CritRatingPerCritChance, - }, - }, - proto.Warlock_Options_Felhunter: { - Name: "Felhunter", - PowerModifier: 0.77, // GetUnitPowerModifier("pet") - Melee: true, - PrimaryAbility: ShadowBite, - Stats: stats.Stats{ - stats.Strength: 314, - stats.Agility: 90, - stats.Stamina: 328, - stats.Intellect: 150, - stats.Spirit: 209, - stats.Mana: 1559, - stats.MeleeCrit: 3.2685 * core.CritRatingPerCritChance, - stats.SpellCrit: 3.3355 * core.CritRatingPerCritChance, - }, - }, -} diff --git a/sim/warlock/pet_abilities.go b/sim/warlock/pet_abilities.go index 0592f7c780..610f4e8ddc 100644 --- a/sim/warlock/pet_abilities.go +++ b/sim/warlock/pet_abilities.go @@ -19,6 +19,7 @@ const ( Firebolt ) +// TODO: this seems pointless // Returns whether the ability was successfully cast. func (wp *WarlockPet) TryCast(sim *core.Simulation, target *core.Unit, spell *core.Spell) bool { if wp.CurrentMana() < spell.DefaultCast.Cost { @@ -72,7 +73,7 @@ func (wp *WarlockPet) newFirebolt() *core.Spell { }, }, - BonusCritRating: wp.owner.masterDemonologistFireCrit(), + BonusCritRating: wp.owner.masterDemonologistFireCrit, DamageMultiplier: (1 + 0.1*float64(wp.owner.Talents.ImprovedImp)) * (1 + 0.2*core.TernaryFloat64(wp.owner.HasMajorGlyph(proto.WarlockMajorGlyph_GlyphOfImp), 1, 0)), CritMultiplier: 2, @@ -147,7 +148,7 @@ func (wp *WarlockPet) newLashOfPain() *core.Spell { }, }, - BonusCritRating: wp.owner.masterDemonologistShadowCrit(), + BonusCritRating: wp.owner.masterDemonologistShadowCrit, DamageMultiplier: 1, CritMultiplier: 1.5, ThreatMultiplier: 1, diff --git a/sim/warlock/rotations.go b/sim/warlock/rotations.go index 0d7f7ed897..02c6815f58 100644 --- a/sim/warlock/rotations.go +++ b/sim/warlock/rotations.go @@ -446,7 +446,9 @@ func (warlock *Warlock) tryUseGCD(sim *core.Simulation) { // Small CDs (Cast on CD) // ------------------------------------------ if warlock.Talents.DemonicEmpowerment && warlock.DemonicEmpowerment.IsReady(sim) && warlock.Options.Summon != proto.Warlock_Options_NoSummon { - warlock.DemonicEmpowerment.Cast(sim, target) + if !warlock.Rotation.UseInfernal || warlock.Inferno.IsReady(sim) { + warlock.DemonicEmpowerment.Cast(sim, target) + } } if warlock.Talents.Metamorphosis && warlock.MetamorphosisAura.IsActive() && warlock.ImmolationAura.IsReady(sim) && warlock.ImmolationAura.Cast(sim, target) { diff --git a/sim/warlock/seed.go b/sim/warlock/seed.go index d0efed0ab6..e3accda869 100644 --- a/sim/warlock/seed.go +++ b/sim/warlock/seed.go @@ -31,7 +31,7 @@ func (warlock *Warlock) makeSeed(targetIdx int, numTargets int) { ProcMask: core.ProcMaskSpellDamage, BonusCritRating: 0 + - warlock.masterDemonologistShadowCrit() + + warlock.masterDemonologistShadowCrit + float64(warlock.Talents.ImprovedCorruption)*core.CritRatingPerCritChance, DamageMultiplierAdditive: 1 + warlock.GrandFirestoneBonus() + diff --git a/sim/warlock/shadowbolt.go b/sim/warlock/shadowbolt.go index 5d8443fe5d..930f75f6fd 100644 --- a/sim/warlock/shadowbolt.go +++ b/sim/warlock/shadowbolt.go @@ -42,7 +42,7 @@ func (warlock *Warlock) registerShadowBoltSpell() { }, BonusCritRating: 0 + - warlock.masterDemonologistShadowCrit() + + warlock.masterDemonologistShadowCrit + core.TernaryFloat64(warlock.Talents.Devastation, 5*core.CritRatingPerCritChance, 0) + core.TernaryFloat64(warlock.HasSetBonus(ItemSetDeathbringerGarb, 4), 5*core.CritRatingPerCritChance, 0) + core.TernaryFloat64(warlock.HasSetBonus(ItemSetDarkCovensRegalia, 2), 5*core.CritRatingPerCritChance, 0), diff --git a/sim/warlock/shadowburn.go b/sim/warlock/shadowburn.go index ec0672e6d9..7acc134cda 100644 --- a/sim/warlock/shadowburn.go +++ b/sim/warlock/shadowburn.go @@ -45,7 +45,7 @@ func (warlock *Warlock) registerShadowBurnSpell() { }, BonusCritRating: 0 + - warlock.masterDemonologistShadowCrit() + + warlock.masterDemonologistShadowCrit + core.TernaryFloat64(warlock.Talents.Devastation, 5*core.CritRatingPerCritChance, 0), DamageMultiplierAdditive: 1 + warlock.GrandFirestoneBonus() + diff --git a/sim/warlock/soul_fire.go b/sim/warlock/soul_fire.go index 86af08236a..377951bf35 100644 --- a/sim/warlock/soul_fire.go +++ b/sim/warlock/soul_fire.go @@ -30,7 +30,7 @@ func (warlock *Warlock) registerSoulFireSpell() { }, BonusCritRating: 0 + - warlock.masterDemonologistFireCrit() + + warlock.masterDemonologistFireCrit + core.TernaryFloat64(warlock.Talents.Devastation, 5*core.CritRatingPerCritChance, 0) + core.TernaryFloat64(warlock.HasSetBonus(ItemSetDarkCovensRegalia, 2), 5*core.CritRatingPerCritChance, 0), DamageMultiplierAdditive: 1 + diff --git a/sim/warlock/talents.go b/sim/warlock/talents.go index 2b8f518b8b..7efe695735 100644 --- a/sim/warlock/talents.go +++ b/sim/warlock/talents.go @@ -47,22 +47,6 @@ func (warlock *Warlock) ApplyTalents() { warlock.MultiplyStat(stats.Health, bonus) } - if warlock.Options.Summon != proto.Warlock_Options_NoSummon { - if warlock.Talents.MasterDemonologist > 0 { - switch warlock.Options.Summon { - case proto.Warlock_Options_Imp: - warlock.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexFire] *= 1.0 + 0.01*float64(warlock.Talents.MasterDemonologist) - warlock.Pet.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexFire] *= 1.0 + 0.01*float64(warlock.Talents.MasterDemonologist) - case proto.Warlock_Options_Succubus: - warlock.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexShadow] *= 1.0 + 0.01*float64(warlock.Talents.MasterDemonologist) - warlock.Pet.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexShadow] *= 1.0 + 0.01*float64(warlock.Talents.MasterDemonologist) - case proto.Warlock_Options_Felguard: - warlock.PseudoStats.DamageDealtMultiplier *= 1.0 + 0.01*float64(warlock.Talents.MasterDemonologist) - warlock.Pet.PseudoStats.DamageDealtMultiplier *= 1.0 + 0.01*float64(warlock.Talents.MasterDemonologist) - } - } - } - // Demonic Tactics, applies even without pet out if warlock.Talents.DemonicTactics > 0 { warlock.AddStats(stats.Stats{ @@ -116,13 +100,6 @@ func (warlock *Warlock) ApplyTalents() { } } -func (warlock *Warlock) masterDemonologistFireCrit() float64 { - return core.TernaryFloat64(warlock.Options.Summon == proto.Warlock_Options_Imp, float64(warlock.Talents.MasterDemonologist)*core.CritRatingPerCritChance, 0) -} -func (warlock *Warlock) masterDemonologistShadowCrit() float64 { - return core.TernaryFloat64(warlock.Options.Summon == proto.Warlock_Options_Succubus, float64(warlock.Talents.MasterDemonologist)*core.CritRatingPerCritChance, 0) -} - func (warlock *Warlock) applyDeathsEmbrace() { multiplier := 1.0 + 0.04*float64(warlock.Talents.DeathsEmbrace) diff --git a/sim/warlock/unstable_affliction.go b/sim/warlock/unstable_affliction.go index e60ec35c39..5c1943d6bf 100644 --- a/sim/warlock/unstable_affliction.go +++ b/sim/warlock/unstable_affliction.go @@ -31,7 +31,7 @@ func (warlock *Warlock) registerUnstableAfflictionSpell() { }, BonusCritRating: 0 + - warlock.masterDemonologistShadowCrit() + + warlock.masterDemonologistShadowCrit + 3*core.CritRatingPerCritChance*float64(warlock.Talents.Malediction), DamageMultiplierAdditive: 1 + warlock.GrandSpellstoneBonus() + diff --git a/sim/warlock/warlock.go b/sim/warlock/warlock.go index 4d3390cb07..4892092772 100644 --- a/sim/warlock/warlock.go +++ b/sim/warlock/warlock.go @@ -69,6 +69,9 @@ type Warlock struct { GlyphOfLifeTapAura *core.Aura FakeSpiritsoftheDamnedAura *core.Aura + Infernal *InfernalPet + Inferno *core.Spell + // Rotation related memory CorruptionRolloverPower float64 DrainSoulRolloverPower float64 @@ -77,7 +80,9 @@ type Warlock struct { PreviousTime time.Duration SpellsRotation []SpellRotation - petStmBonusSP float64 + petStmBonusSP float64 + masterDemonologistFireCrit float64 + masterDemonologistShadowCrit float64 // set bonus cache T7TwoSetBonus bool @@ -142,6 +147,7 @@ func (warlock *Warlock) Initialize() { } warlock.registerDarkPactSpell() warlock.registerShadowBurnSpell() + warlock.registerInfernoSpell() warlock.defineRotation() } @@ -192,6 +198,10 @@ func NewWarlock(character core.Character, options *proto.Player) *Warlock { warlock.Pet = warlock.NewWarlockPet() } + if warlock.Rotation.UseInfernal { + warlock.Infernal = warlock.NewInfernal() + } + warlock.applyWeaponImbue() return warlock diff --git a/ui/core/proto_utils/action_id.ts b/ui/core/proto_utils/action_id.ts index 827c9ab092..f19e9e0160 100644 --- a/ui/core/proto_utils/action_id.ts +++ b/ui/core/proto_utils/action_id.ts @@ -509,6 +509,7 @@ const petNameToIcon: Record = { 'Dragonhawk': 'https://wow.zamimg.com/images/wow/icons/medium/ability_hunter_pet_dragonhawk.jpg', 'Felguard': 'https://wow.zamimg.com/images/wow/icons/large/spell_shadow_summonfelguard.jpg', 'Felhunter': 'https://wow.zamimg.com/images/wow/icons/large/spell_shadow_summonfelhunter.jpg', + 'Infernal': 'https://wow.zamimg.com/images/wow/icons/large/spell_shadow_summoninfernal.jpg', 'Gargoyle': 'https://wow.zamimg.com/images/wow/icons/large/ability_hunter_pet_bat.jpg', 'Ghoul': 'https://wow.zamimg.com/images/wow/icons/large/spell_shadow_raisedead.jpg', 'Army of the Dead': 'https://wow.zamimg.com/images/wow/icons/large/spell_deathknight_armyofthedead.jpg', diff --git a/ui/warlock/inputs.ts b/ui/warlock/inputs.ts index e6631484b0..996147afd6 100644 --- a/ui/warlock/inputs.ts +++ b/ui/warlock/inputs.ts @@ -291,6 +291,12 @@ export const WarlockRotationConfig = { player.setRotation(eventID, newRotation); }, }, + + InputHelpers.makeRotationBooleanInput({ + fieldName: 'useInfernal', + label: 'Summon Infernal', + labelTooltip: 'Casts Inferno 60s before the fight is over', + }), InputHelpers.makeRotationBooleanInput({ fieldName: 'detonateSeed', label: 'Detonate Seed on Cast', diff --git a/ui/warlock/presets.ts b/ui/warlock/presets.ts index ef18e0b62f..9d5f5faf56 100644 --- a/ui/warlock/presets.ts +++ b/ui/warlock/presets.ts @@ -86,6 +86,7 @@ export const AfflictionRotation = WarlockRotation.create({ specSpell: SpecSpell.Haunt, curse: Curse.Agony, corruption: true, + useInfernal: false, detonateSeed: true, }); @@ -95,6 +96,7 @@ export const DemonologyRotation = WarlockRotation.create({ specSpell: SpecSpell.NoSpecSpell, curse: Curse.Doom, corruption: true, + useInfernal: false, detonateSeed: true, }); @@ -104,6 +106,7 @@ export const DestructionRotation = WarlockRotation.create({ specSpell: SpecSpell.ChaosBolt, curse: Curse.Doom, corruption: false, + useInfernal: false, detonateSeed: true, });