You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

595 lines
31 KiB

--- ============================ HEADER ============================
--- ======= LOCALIZE =======
-- Addon
local addonName, addonTable = ...
-- HeroDBC
local DBC = HeroDBC.DBC
-- HeroLib
local HL = HeroLib
local Cache = HeroCache
local Unit = HL.Unit
local Player = Unit.Player
local Target = Unit.Target
local Pet = Unit.Pet
local Spell = HL.Spell
local Item = HL.Item
-- HeroRotation
local HR = HeroRotation
local AoEON = HR.AoEON
local CDsON = HR.CDsON
local Cast = HR.Cast
local CastSuggested = HR.CastSuggested
local CastAnnotated = HR.CastAnnotated
-- Num/Bool Helper Functions
local num = HR.Commons.Everyone.num
local bool = HR.Commons.Everyone.bool
-- File locals
local DemonHunter = HR.Commons.DemonHunter
DemonHunter.DGBCDR = 0
DemonHunter.DGBCDRLastUpdate = 0
-- lua
local GetTime = GetTime
local mathmin = math.min
--- ============================ CONTENT ===========================
--- ======= APL LOCALS =======
-- luacheck: max_line_length 9999
-- Define S/I for spell and item arrays
local S = Spell.DemonHunter.Vengeance
local I = Item.DemonHunter.Vengeance
-- Create table to exclude above trinkets from On Use function
local OnUseExcludes = {
}
-- GUI Settings
local Everyone = HR.Commons.Everyone
local Settings = {
General = HR.GUISettings.General,
Commons = HR.GUISettings.APL.DemonHunter.Commons,
Vengeance = HR.GUISettings.APL.DemonHunter.Vengeance
}
-- Rotation Var
local SoulFragments, LastSoulFragmentAdjustment
local SoulFragmentsAdjusted = 0
local IsInMeleeRange, IsInAoERange
local ActiveMitigationNeeded
local IsTanking
local Enemies8yMelee
local EnemiesCount8yMelee
local VarCDTime, VarFD, VarFrailtyReady, VarNextFireCDTime
local BossFightRemains = 11111
local FightRemains = 11111
HL:RegisterForEvent(function()
BossFightRemains = 11111
FightRemains = 11111
end, "PLAYER_REGEN_ENABLED")
-- Soul Fragments function taking into consideration aura lag
local function UpdateSoulFragments()
SoulFragments = Player:BuffStack(S.SoulFragments)
-- Casting Spirit Bomb immediately updates the buff
-- May no longer be needed, as Spirit Bomb instantly removes the buff now
if S.SpiritBomb:TimeSinceLastCast() < Player:GCD() then
SoulFragmentsAdjusted = 0
end
-- Check if we have cast Soul Carver, Fracture, or Shear within the last GCD and haven't "snapshot" yet
if SoulFragmentsAdjusted == 0 then
local MetaMod = (Player:BuffUp(S.MetamorphosisBuff)) and 1 or 0
if S.SoulCarver:IsAvailable() and S.SoulCarver:TimeSinceLastCast() < Player:GCD() and S.SoulCarver.LastCastTime ~= LastSoulFragmentAdjustment then
SoulFragmentsAdjusted = math.min(SoulFragments + 2, 5)
LastSoulFragmentAdjustment = S.SoulCarver.LastCastTime
elseif S.Fracture:IsAvailable() and S.Fracture:TimeSinceLastCast() < Player:GCD() and S.Fracture.LastCastTime ~= LastSoulFragmentAdjustment then
SoulFragmentsAdjusted = math.min(SoulFragments + 2 + MetaMod, 5)
LastSoulFragmentAdjustment = S.Fracture.LastCastTime
elseif S.Shear:TimeSinceLastCast() < Player:GCD() and S.Fracture.Shear ~= LastSoulFragmentAdjustment then
SoulFragmentsAdjusted = math.min(SoulFragments + 1 + MetaMod, 5)
LastSoulFragmentAdjustment = S.Shear.LastCastTime
end
else
-- If we have a soul fragement "snapshot", see if we should invalidate it based on time
local Prev = Player:PrevGCD(1)
if Prev == 207407 and S.SoulCarver:TimeSinceLastCast() >= Player:GCD() then
SoulFragmentsAdjusted = 0
elseif Prev == 263642 and S.Fracture:TimeSinceLastCast() >= Player:GCD() then
SoulFragmentsAdjusted = 0
elseif Prev == 203782 and S.Shear:TimeSinceLastCast() >= Player:GCD() then
SoulFragmentsAdjusted = 0
end
end
-- If we have a higher Soul Fragment "snapshot", use it instead
if SoulFragmentsAdjusted > SoulFragments then
SoulFragments = SoulFragmentsAdjusted
elseif SoulFragmentsAdjusted > 0 then
-- Otherwise, the "snapshot" is invalid, so reset it if it has a value
-- Relevant in cases where we use a generator two GCDs in a row
SoulFragmentsAdjusted = 0
end
end
-- Melee Is In Range w/ Movement Handlers
local function UpdateIsInMeleeRange()
if S.Felblade:TimeSinceLastCast() < Player:GCD()
or S.InfernalStrike:TimeSinceLastCast() < Player:GCD() then
IsInMeleeRange = true
IsInAoERange = true
return
end
IsInMeleeRange = Target:IsInMeleeRange(5)
IsInAoERange = IsInMeleeRange or EnemiesCount8yMelee > 0
end
-- CastTargetIf/CastCycle functions
local function EvaluateTargetIfFilterFBRemains(TargetUnit)
-- target_if=max:dot.fiery_brand.remains
return (TargetUnit:DebuffRemains(S.FieryBrandDebuff))
end
local function EvaluateTargetIfFractureMaintenance(TargetUnit)
-- if=dot.fiery_brand.ticking&buff.recrimination.up
-- Note: RecriminationBuff check is done before CastTargetIf
return (TargetUnit:DebuffUp(S.FieryBrandDebuff))
end
-- Base rotation functions
local function Precombat()
-- flask
-- augmentation
-- food
-- snapshot_stats
-- sigil_of_flame
if (not S.ConcentratedSigils:IsAvailable()) and S.SigilofFlame:IsCastable() then
if Cast(S.SigilofFlame, Settings.Commons.GCDasOffGCD.SigilOfFlame, nil, not Target:IsInRange(30)) then return "sigil_of_flame precombat 2"; end
end
-- immolation_aura
if S.ImmolationAura:IsCastable() then
if Cast(S.ImmolationAura) then return "immolation_aura precombat 4"; end
end
-- Manually added: First attacks
if S.InfernalStrike:IsCastable() and not IsInMeleeRange then
if Cast(S.InfernalStrike, nil, nil, not Target:IsInRange(30)) then return "infernal_strike precombat 6"; end
end
if S.Fracture:IsCastable() and IsInMeleeRange then
if Cast(S.Fracture) then return "fracture precombat 8"; end
end
if S.Shear:IsCastable() and IsInMeleeRange then
if Cast(S.Shear) then return "shear precombat 10"; end
end
end
local function Defensives()
-- Demon Spikes
if S.DemonSpikes:IsCastable() and Player:BuffDown(S.DemonSpikesBuff) and Player:BuffDown(S.MetamorphosisBuff) and (EnemiesCount8yMelee == 1 and Player:BuffDown(S.FieryBrandDebuff) or EnemiesCount8yMelee > 1) then
if S.DemonSpikes:ChargesFractional() > 1.9 then
if Cast(S.DemonSpikes, nil, Settings.Vengeance.DisplayStyle.Defensives) then return "demon_spikes defensives (Capped)"; end
elseif (ActiveMitigationNeeded or Player:HealthPercentage() <= Settings.Vengeance.DemonSpikesHealthThreshold) then
if Cast(S.DemonSpikes, nil, Settings.Vengeance.DisplayStyle.Defensives) then return "demon_spikes defensives (Danger)"; end
end
end
-- Metamorphosis,if=!buff.metamorphosis.up|target.time_to_die<15
if S.Metamorphosis:IsCastable() and Player:HealthPercentage() <= Settings.Vengeance.MetamorphosisHealthThreshold and (Player:BuffDown(S.MetamorphosisBuff) or Target:TimeToDie() < 15) then
if Cast(S.Metamorphosis, nil, Settings.Commons.DisplayStyle.Metamorphosis) then return "metamorphosis defensives"; end
end
-- Fiery Brand
if S.FieryBrand:IsCastable() and (ActiveMitigationNeeded or Player:HealthPercentage() <= Settings.Vengeance.FieryBrandHealthThreshold) then
if Cast(S.FieryBrand, nil, Settings.Vengeance.DisplayStyle.Defensives, not Target:IsSpellInRange(S.FieryBrand)) then return "fiery_brand defensives"; end
end
end
local function Filler()
-- bulk_extraction
if S.BulkExtraction:IsCastable() then
if Cast(S.BulkExtraction, nil, nil, not Target:IsInMeleeRange(8)) then return "bulk_extraction filler 2"; end
end
-- soul_cleave
if S.SoulCleave:IsReady() then
if Cast(S.SoulCleave, nil, nil, not IsInMeleeRange) then return "soul_cleave filler 4"; end
end
-- spirit_bomb
if S.SpiritBomb:IsReady() then
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb filler 6"; end
end
-- felblade
if S.Felblade:IsReady() then
if Cast(S.Felblade, nil, nil, not Target:IsSpellInRange(S.Felblade)) then return "felblade filler 8"; end
end
-- fracture
if S.Fracture:IsCastable() then
if Cast(S.Fracture, nil, nil, not IsInMeleeRange) then return "fracture filler 10"; end
end
-- shear
if S.Shear:IsCastable() then
if Cast(S.Shear, nil, nil, not IsInMeleeRange) then return "shear filler 12"; end
end
-- throw_glaive
if S.ThrowGlaive:IsCastable() then
if Cast(S.ThrowGlaive, nil, nil, not Target:IsSpellInRange(S.ThrowGlaive)) then return "throw_glaive filler 14"; end
end
end
local function Trinkets()
-- use_items
if Settings.Commons.Enabled.Trinkets or Settings.Commons.Enabled.Items then
local ItemToUse, ItemSlot, ItemRange = Player:GetUseableItems(OnUseExcludes)
if ItemToUse then
local DisplayStyle = Settings.Commons.DisplayStyle.Trinkets
if ItemSlot ~= 13 and ItemSlot ~= 14 then DisplayStyle = Settings.Commons.DisplayStyle.Items end
if ((ItemSlot == 13 or ItemSlot == 14) and Settings.Commons.Enabled.Trinkets) or (ItemSlot ~= 13 and ItemSlot ~= 14 and Settings.Commons.Enabled.Items) then
if Cast(ItemToUse, nil, DisplayStyle, not Target:IsInRange(ItemRange)) then return "Generic use_items for " .. ItemToUse:Name(); end
end
end
end
-- use_item,name=dragonfire_bomb_dispenser,use_off_gcd=1
if Settings.Commons.Enabled.Trinkets and I.DragonfireBombDispenser:IsEquippedAndReady() then
if Cast(I.DragonfireBombDispenser, nil, Settings.Commons.DisplayStyle.Trinkets, not Target:IsInRange(46)) then return "dragonfire_bomb_dispenser trinkets 2"; end
end
end
local function Maintenance()
-- invoke_external_buff,name=symbol_of_hope,if=cooldown.fiery_brand.charges=0
-- Note: Not handling external buffs.
-- metamorphosis,if=talent.first_of_the_illidari
if S.Metamorphosis:IsCastable() and (S.FirstoftheIllidari:IsAvailable()) then
if Cast(S.Metamorphosis, nil, Settings.Commons.DisplayStyle.Metamorphosis) then return "metamorphosis maintenance 2"; end
end
-- call_action_list,name=trinkets
local ShouldReturn = Trinkets(); if ShouldReturn then return ShouldReturn; end
-- potion
if Settings.Commons.Enabled.Potions then
local PotionSelected = Everyone.PotionSelected()
if PotionSelected and PotionSelected:IsReady() then
if Cast(PotionSelected, nil, Settings.Commons.DisplayStyle.Potions) then return "potion maintenance 4"; end
end
end
-- fiery_brand,if=charges>=2|(!ticking&((variable.next_fire_cd_time<7)|(variable.next_fire_cd_time>28)))
if S.FieryBrand:IsCastable() and (S.FieryBrand:Charges() >= 2 or (Target:DebuffDown(S.FieryBrandDebuff) and (VarNextFireCDTime < 7 or VarNextFireCDTime > 28))) then
if Cast(S.FieryBrand, Settings.Vengeance.GCDasOffGCD.FieryBrand, nil, not Target:IsSpellInRange(S.FieryBrand)) then return "fiery_brand maintenance 6"; end
end
-- spirit_bomb,if=soul_fragments>=5
if S.SpiritBomb:IsReady() and (SoulFragments >= 5) then
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb maintenance 8"; end
end
-- fracture,target_if=max:dot.fiery_brand.remains,if=dot.fiery_brand.ticking&buff.recrimination.up
if S.Fracture:IsCastable() and (Player:BuffUp(S.RecriminationBuff)) then
if Everyone.CastTargetIf(S.Fracture, Enemies8yMelee, "max", EvaluateTargetIfFilterFBRemains, EvaluateTargetIfFractureMaintenance, not IsInMeleeRange) then return "fracture maintenance 10"; end
end
-- fracture,if=(full_recharge_time<=cast_time+gcd.remains)
if S.Fracture:IsCastable() and (S.Fracture:FullRechargeTime() <= S.Fracture:CastTime() + Player:GCDRemains()) then
if Cast(S.Fracture, nil, nil, not IsInMeleeRange) then return "fracture maintenance 14"; end
end
-- immolation_aura
if S.ImmolationAura:IsCastable() then
if Cast(S.ImmolationAura) then return "immolation_aura maintenance 16"; end
end
-- sigil_of_flame,if=dot.fiery_brand.ticking
if S.SigilofFlame:IsCastable() and (Target:DebuffUp(S.FieryBrandDebuff)) then
if S.ConcentratedSigils:IsAvailable() then
if Cast(S.SigilofFlame, Settings.Commons.GCDasOffGCD.SigilOfFlame, nil, not IsInAoERange) then return "sigil_of_flame maintenance 18 (Concentrated)"; end
else
if Cast(S.SigilofFlame, Settings.Commons.GCDasOffGCD.SigilOfFlame, nil, not Target:IsInRange(30)) then return "sigil_of_flame maintenance 18 (Normal)"; end
end
end
-- metamorphosis,if=talent.demonic&!buff.metamorphosis.up&!cooldown.fel_devastation.up&fury>=50
if S.Metamorphosis:IsCastable() and (S.Demonic:IsAvailable() and Player:BuffDown(S.MetamorphosisBuff) and S.FelDevastation:CooldownDown() and Player:Fury() >= 50) then
if Cast(S.Metamorphosis, nil, Settings.Commons.DisplayStyle.Metamorphosis) then return "metamorphosis maintenance 18"; end
end
end
local function SingleTarget()
-- soul_carver,if=variable.fd&variable.frailty_ready&soul_fragments<=3
if S.SoulCarver:IsCastable() and (VarFD and VarFrailtyReady and SoulFragments <= 3) then
if Cast(S.SoulCarver, nil, nil, not IsInMeleeRange) then return "soul_carver single_target 2"; end
end
-- the_hunt,if=variable.frailty_ready
if S.TheHunt:IsCastable() and (VarFrailtyReady) then
if Cast(S.TheHunt, nil, Settings.Commons.DisplayStyle.Signature, not Target:IsInRange(50)) then return "the_hunt single_target 4"; end
end
-- soul_carver,if=variable.frailty_ready&soul_fragments<=3
if S.SoulCarver:IsCastable() and (VarFrailtyReady and SoulFragments <= 3) then
if Cast(S.SoulCarver, nil, nil, not IsInMeleeRange) then return "soul_carver single_target 6"; end
end
-- fel_devastation,if=variable.frailty_ready&(variable.fd|talent.stoke_the_flames)&!(talent.demonic&buff.metamorphosis.up)
if S.FelDevastation:IsReady() and (VarFrailtyReady and (VarFD or S.StoketheFlames:IsAvailable()) and not (S.Demonic:IsAvailable() and Player:BuffUp(S.MetamorphosisBuff))) then
if Cast(S.FelDevastation, Settings.Vengeance.GCDasOffGCD.FelDevastation, nil, not Target:IsInMeleeRange(20)) then return "fel_devastation single_target 8"; end
end
-- elysian_decree,if=variable.frailty_ready
if S.ElysianDecree:IsCastable() and (VarFrailtyReady) then
if Cast(S.ElysianDecree, nil, Settings.Commons.DisplayStyle.Signature, not Target:IsInRange(30)) then return "elysian_decree single_target 10"; end
end
-- fracture,if=set_bonus.tier30_4pc&variable.fd&(soul_fragments<=3|(buff.metamorphosis.up&soul_fragments<=2))
if S.Fracture:IsCastable() and (Player:HasTier(30, 4) and VarFD and (SoulFragments <= 3 or (Player:BuffUp(S.MetamorphosisBuff) and SoulFragments <= 2))) then
if Cast(S.Fracture, nil, nil, not IsInMeleeRange) then return "fracture single_target 12"; end
end
-- fel_devastation,if=!(talent.demonic&buff.metamorphosis.up)
if S.FelDevastation:IsReady() and (not (S.Demonic:IsAvailable() and Player:BuffUp(S.MetamorphosisBuff))) then
if Cast(S.FelDevastation, Settings.Vengeance.GCDasOffGCD.FelDevastation, nil, not Target:IsInMeleeRange(20)) then return "fel_devastation single_target 14"; end
end
-- sigil_of_flame,if=fury<70
if S.SigilofFlame:IsCastable() and (Player:Fury() < 70) then
if S.ConcentratedSigils:IsAvailable() then
if Cast(S.SigilofFlame, Settings.Commons.GCDasOffGCD.SigilOfFlame, nil, not IsInAoERange) then return "sigil_of_flame single_target 16 (Concentrated)"; end
else
if Cast(S.SigilofFlame, Settings.Commons.GCDasOffGCD.SigilOfFlame, nil, not Target:IsInRange(30)) then return "sigil_of_flame single_target 16 (Normal)"; end
end
end
-- spirit_bomb,if=((variable.fd&soul_fragments>=4)|soul_fragments>=5)
if S.SpiritBomb:IsReady() and ((VarFD and SoulFragments >= 4) or SoulFragments >= 5) then
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb single_target 18"; end
end
-- fracture,if=set_bonus.tier30_4pc&variable.fd
if S.Fracture:IsCastable() and (Player:HasTier(30, 4) and VarFD) then
if Cast(S.Fracture, nil, nil, not IsInMeleeRange) then return "fracture single_target 20"; end
end
-- soul_cleave,if=talent.focused_cleave
if S.SoulCleave:IsReady() and (S.FocusedCleave:IsAvailable()) then
if Cast(S.SoulCleave, nil, nil, not IsInMeleeRange) then return "soul_cleave single_target 22"; end
end
-- spirit_bomb,if=((variable.fd&soul_fragments>=3)|soul_fragments>=4)
if S.SpiritBomb:IsReady() and ((VarFD and SoulFragments >= 3) or SoulFragments >= 4) then
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb single_target 24"; end
end
-- soul_cleave
if S.SoulCleave:IsReady() then
if Cast(S.SoulCleave, nil, nil, not IsInMeleeRange) then return "soul_cleave single_target 26"; end
end
-- fracture
if S.Fracture:IsCastable() then
if Cast(S.Fracture, nil, nil, not IsInMeleeRange) then return "fracture single_target 28"; end
end
-- spirit_bomb,if=((variable.fd&soul_fragments>=2)|soul_fragments>=3)
if S.SpiritBomb:IsReady() and ((VarFD and SoulFragments >= 2) or SoulFragments >= 3) then
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb single_target 30"; end
end
-- sigil_of_flame
if S.SigilofFlame:IsCastable() then
if S.ConcentratedSigils:IsAvailable() then
if Cast(S.SigilofFlame, Settings.Commons.GCDasOffGCD.SigilOfFlame, nil, not IsInAoERange) then return "sigil_of_flame single_target 32 (Concentrated)"; end
else
if Cast(S.SigilofFlame, Settings.Commons.GCDasOffGCD.SigilOfFlame, nil, not Target:IsInRange(30)) then return "sigil_of_flame single_target 32 (Normal)"; end
end
end
-- call_action_list,name=filler
local ShouldReturn = Filler(); if ShouldReturn then return ShouldReturn; end
end
local function SmallAoE()
-- elysian_decree,if=variable.frailty_ready
if S.ElysianDecree:IsCastable() and (VarFrailtyReady) then
if Cast(S.ElysianDecree, nil, Settings.Commons.DisplayStyle.Signature, not Target:IsInRange(30)) then return "elysian_decree small_aoe 2"; end
end
-- fel_devastation,if=variable.frailty_ready&variable.fd&talent.stoke_the_flames&!(talent.demonic&buff.metamorphosis.up)
if S.FelDevastation:IsReady() and (VarFrailtyReady and VarFD and S.StoketheFlames:IsAvailable() and not (S.Demonic:IsAvailable() and Player:BuffUp(S.MetamorphosisBuff))) then
if Cast(S.FelDevastation, Settings.Vengeance.GCDasOffGCD.FelDevastation, nil, not Target:IsInMeleeRange(20)) then return "fel_devastation small_aoe 4"; end
end
-- the_hunt,if=variable.frailty_ready
if S.TheHunt:IsCastable() and (VarFrailtyReady) then
if Cast(S.TheHunt, nil, Settings.Commons.DisplayStyle.Signature, not Target:IsInRange(50)) then return "the_hunt small_aoe 6"; end
end
-- fel_devastation,if=variable.frailty_ready&(variable.fd|talent.stoke_the_flames)&!(talent.demonic&buff.metamorphosis.up)
if S.FelDevastation:IsReady() and (VarFrailtyReady and (VarFD or S.StoketheFlames:IsAvailable()) and not (S.Demonic:IsAvailable() and Player:BuffUp(S.MetamorphosisBuff))) then
if Cast(S.FelDevastation, Settings.Vengeance.GCDasOffGCD.FelDevastation, nil, not Target:IsInMeleeRange(20)) then return "fel_devastation small_aoe 8"; end
end
-- spirit_bomb,if=((variable.fd&soul_fragments>=4)|soul_fragments>=5)
if S.SpiritBomb:IsReady() and ((VarFD and SoulFragments >= 4) or SoulFragments >= 5) then
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb small_aoe 10"; end
end
-- soul_carver,if=variable.frailty_ready&variable.fd&soul_fragments<=3
if S.SoulCarver:IsCastable() and (VarFrailtyReady and VarFD and SoulFragments <= 3) then
if Cast(S.SoulCarver, nil, nil, not IsInMeleeRange) then return "soul_carver small_aoe 12"; end
end
-- fel_devastation,if=!(talent.demonic&buff.metamorphosis.up)
if S.FelDevastation:IsReady() and (not (S.Demonic:IsAvailable() and Player:BuffUp(S.MetamorphosisBuff))) then
if Cast(S.FelDevastation, Settings.Vengeance.GCDasOffGCD.FelDevastation, nil, not Target:IsInMeleeRange(20)) then return "fel_devastation small_aoe 14"; end
end
-- fracture,if=soul_fragments<=3&soul_fragments>=1
if S.Fracture:IsCastable() and (SoulFragments <= 3 and SoulFragments >= 1) then
if Cast(S.Fracture, nil, nil, not IsInMeleeRange) then return "fracture small_aoe 16"; end
end
-- sigil_of_flame
if S.SigilofFlame:IsCastable() then
if S.ConcentratedSigils:IsAvailable() then
if Cast(S.SigilofFlame, Settings.Commons.GCDasOffGCD.SigilOfFlame, nil, not IsInAoERange) then return "sigil_of_flame small_aoe 18 (Concentrated)"; end
else
if Cast(S.SigilofFlame, Settings.Commons.GCDasOffGCD.SigilOfFlame, nil, not Target:IsInRange(30)) then return "sigil_of_flame small_aoe 18 (Normal)"; end
end
end
-- soul_carver,if=variable.frailty_ready&soul_fragments<=3
if S.SoulCarver:IsCastable() and (VarFrailtyReady and SoulFragments <= 3) then
if Cast(S.SoulCarver, nil, nil, not IsInMeleeRange) then return "soul_carver small_aoe 20"; end
end
-- spirit_bomb,if=((variable.fd&soul_fragments>=3)|soul_fragments>=4)
if S.SpiritBomb:IsReady() and ((VarFD and SoulFragments >= 3) or SoulFragments >= 4) then
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb small_aoe 22"; end
end
-- soul_cleave,if=talent.focused_cleave
if S.SoulCleave:IsReady() and (S.FocusedCleave:IsAvailable()) then
if Cast(S.SoulCleave, nil, nil, not IsInMeleeRange) then return "soul_cleave small_aoe 24"; end
end
-- spirit_bomb,if=((variable.fd&soul_fragments>=2)|soul_fragments>=3)
if S.SpiritBomb:IsReady() and ((VarFD and SoulFragments >= 2) or SoulFragments >= 3) then
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb small_aoe 26"; end
end
-- soul_cleave
if S.SoulCleave:IsReady() then
if Cast(S.SoulCleave, nil, nil, not IsInMeleeRange) then return "soul_cleave small_aoe 28"; end
end
-- spirit_bomb,if=((variable.fd&soul_fragments>=1)|soul_fragments>=2)
if S.SpiritBomb:IsReady() and ((VarFD and SoulFragments >= 1) or SoulFragments >= 2) then
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb small_aoe 30"; end
end
-- fracture
if S.Fracture:IsCastable() then
if Cast(S.Fracture, nil, nil, not IsInMeleeRange) then return "fracture small_aoe 32"; end
end
-- call_action_list,name=filler
local ShouldReturn = Filler(); if ShouldReturn then return ShouldReturn; end
end
local function BigAoE()
-- fel_devastation,if=variable.frailty_ready&variable.fd&talent.stoke_the_flames&!(talent.demonic&buff.metamorphosis.up)
if S.FelDevastation:IsReady() and (VarFrailtyReady and VarFD and S.StoketheFlames:IsAvailable() and not (S.Demonic:IsAvailable() and Player:BuffUp(S.MetamorphosisBuff))) then
if Cast(S.FelDevastation, Settings.Vengeance.GCDasOffGCD.FelDevastation, nil, not Target:IsInMeleeRange(20)) then return "fel_devastation big_aoe 2"; end
end
-- elysian_decree,if=variable.frailty_ready
if S.ElysianDecree:IsCastable() and (VarFrailtyReady) then
if Cast(S.ElysianDecree, nil, Settings.Commons.DisplayStyle.Signature, not Target:IsInRange(30)) then return "elysian_decree big_aoe 4"; end
end
-- fel_devastation,if=variable.frailty_ready&(variable.fd|talent.stoke_the_flames)&!(talent.demonic&buff.metamorphosis.up)
if S.FelDevastation:IsReady() and (VarFrailtyReady and (VarFD or S.StoketheFlames:IsAvailable()) and not (S.Demonic:IsAvailable() and Player:BuffUp(S.MetamorphosisBuff))) then
if Cast(S.FelDevastation, Settings.Vengeance.GCDasOffGCD.FelDevastation, nil, not Target:IsInMeleeRange(20)) then return "fel_devastation big_aoe 6"; end
end
-- the_hunt,if=variable.frailty_ready
if S.TheHunt:IsCastable() and (VarFrailtyReady) then
if Cast(S.TheHunt, nil, Settings.Commons.DisplayStyle.Signature, not Target:IsInRange(50)) then return "the_hunt big_aoe 8"; end
end
-- fel_devastation,if=!(talent.demonic&buff.metamorphosis.up)
if S.FelDevastation:IsReady() and (not (S.Demonic:IsAvailable() and Player:BuffUp(S.MetamorphosisBuff))) then
if Cast(S.FelDevastation, Settings.Vengeance.GCDasOffGCD.FelDevastation, nil, not Target:IsInMeleeRange(20)) then return "fel_devastation big_aoe 10"; end
end
-- spirit_bomb,if=((variable.fd&soul_fragments>=4)|soul_fragments>=5)
if S.SpiritBomb:IsReady() and ((VarFD and SoulFragments >= 4) or SoulFragments >= 5) then
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb big_aoe 12"; end
end
-- fracture,if=soul_fragments<=3&soul_fragments>=1
if S.Fracture:IsCastable() and (SoulFragments <= 3 and SoulFragments >= 1) then
if Cast(S.Fracture, nil, nil, not IsInMeleeRange) then return "fracture big_aoe 14"; end
end
-- soul_carver,if=variable.fd&variable.frailty_ready&soul_fragments<=3
if S.SoulCarver:IsCastable() and (VarFD and VarFrailtyReady and SoulFragments <= 3) then
if Cast(S.SoulCarver, nil, nil, not IsInMeleeRange) then return "soul_carver big_aoe 16"; end
end
-- spirit_bomb,if=((variable.fd&soul_fragments>=3)|soul_fragments>=4)
if S.SpiritBomb:IsReady() and ((VarFD and SoulFragments >= 3) or SoulFragments >= 4) then
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb big_aoe 18"; end
end
-- soul_carver,if=soul_fragments<=3
if S.SoulCarver:IsCastable() and (SoulFragments <= 3) then
if Cast(S.SoulCarver, nil, nil, not IsInMeleeRange) then return "soul_carver big_aoe 20"; end
end
-- soul_cleave,if=talent.focused_cleave
if S.SoulCleave:IsReady() and (S.FocusedCleave:IsAvailable()) then
if Cast(S.SoulCleave, nil, nil, not IsInMeleeRange) then return "soul_cleave big_aoe 22"; end
end
-- spirit_bomb,if=((variable.fd&soul_fragments>=2)|soul_fragments>=3)
if S.SpiritBomb:IsReady() and ((VarFD and SoulFragments >= 2) or SoulFragments >= 3) then
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb big_aoe 24"; end
end
-- soul_cleave
if S.SoulCleave:IsReady() then
if Cast(S.SoulCleave, nil, nil, not IsInMeleeRange) then return "soul_cleave big_aoe 26"; end
end
-- fracture,if=soul_fragments<=3
if S.Fracture:IsCastable() and (SoulFragments <= 3) then
if Cast(S.Fracture, nil, nil, not IsInMeleeRange) then return "fracture big_aoe 28"; end
end
-- call_action_list,name=filler
local ShouldReturn = Filler(); if ShouldReturn then return ShouldReturn; end
end
-- APL Main
local function APL()
Enemies8yMelee = Player:GetEnemiesInMeleeRange(8)
if (AoEON()) then
EnemiesCount8yMelee = #Enemies8yMelee
else
EnemiesCount8yMelee = 1
end
UpdateSoulFragments()
UpdateIsInMeleeRange()
ActiveMitigationNeeded = Player:ActiveMitigationNeeded()
IsTanking = Player:IsTankingAoE(8) or Player:IsTanking(Target)
if Everyone.TargetIsValid() or Player:AffectingCombat() then
-- Calculate fight_remains
BossFightRemains = HL.BossFightRemains(nil, true)
FightRemains = BossFightRemains
if FightRemains == 11111 then
FightRemains = HL.FightRemains(Enemies8yMelee, false)
end
end
if Everyone.TargetIsValid() then
-- Precombat
if not Player:AffectingCombat() then
local ShouldReturn = Precombat(); if ShouldReturn then return ShouldReturn; end
end
-- variable,name=next_cd_time,value=cooldown.fel_devastation.remains
VarCDTime = S.FelDevastation:CooldownRemains()
-- variable,name=next_cd_time,op=min,value=cooldown.elysian_decree.remains,if=talent.elysian_decree
if S.ElysianDecree:IsAvailable() then
VarCDTime = mathmin(VarCDTime, S.ElysianDecree:CooldownRemains())
end
-- variable,name=next_cd_time,op=min,value=cooldown.the_hunt.remains,if=talent.the_hunt
if S.TheHunt:IsAvailable() then
VarCDTime = mathmin(VarCDTime, S.TheHunt:CooldownRemains())
end
-- variable,name=next_cd_time,op=min,value=cooldown.soul_carver.remains,if=talent.soul_carver
if S.SoulCarver:IsAvailable() then
VarCDTime = mathmin(VarCDTime, S.SoulCarver:CooldownRemains())
end
-- variable,name=next_fire_cd_time,value=cooldown.fel_devastation.remains
VarNextFireCDTime = S.FelDevastation:CooldownRemains()
-- variable,name=next_fire_cd_time,op=min,value=cooldown.soul_carver.remains,if=talent.soul_carver
if S.SoulCarver:IsAvailable() then
VarNextFireCDTime = mathmin(VarNextFireCDTime, S.SoulCarver:CooldownRemains())
end
-- variable,name=fd,value=talent.fiery_demise&dot.fiery_brand.ticking
VarFD = S.FieryDemise:IsAvailable() and Target:DebuffUp(S.FieryBrandDebuff)
-- variable,name=frailty_ready,value=!talent.soulcrush|debuff.frailty.stack>=2
VarFrailtyReady = (not S.Soulcrush:IsAvailable()) or Target:DebuffStack(S.FrailtyDebuff) >= 2
-- auto_attack
-- use_item,name=elementium_pocket_anvil,use_off_gcd=1
if Settings.Commons.Enabled.Trinkets and I.ElementiumPocketAnvil:IsEquippedAndReady() then
if Cast(I.ElementiumPocketAnvil, nil, Settings.Commons.DisplayStyle.Trinkets, not Target:IsInRange(8)) then return "elementium_pocket_anvil main 1"; end
end
-- disrupt,if=target.debuff.casting.react (Interrupts)
local ShouldReturn = Everyone.Interrupt(10, S.Disrupt, Settings.Commons.OffGCDasOffGCD.Disrupt, false); if ShouldReturn then return ShouldReturn; end
-- Manually added: Defensives
if (IsTanking) then
local ShouldReturn = Defensives(); if ShouldReturn then return ShouldReturn; end
end
-- infernal_strike,use_off_gcd=1
if S.InfernalStrike:IsCastable() and ((not Settings.Vengeance.ConserveInfernalStrike) or S.InfernalStrike:ChargesFractional() > 1.9) and (S.InfernalStrike:TimeSinceLastCast() > 2) then
if Cast(S.InfernalStrike, Settings.Vengeance.OffGCDasOffGCD.InfernalStrike, nil, not Target:IsInRange(30)) then return "infernal_strike main 2"; end
end
-- demon_spikes,use_off_gcd=1,if=!buff.demon_spikes.up&!cooldown.pause_action.remains
-- Note: Handled via Defensives()
-- call_action_list,name=maintenance
local ShouldReturn = Maintenance(); if ShouldReturn then return ShouldReturn; end
-- run_action_list,name=single_target,if=active_enemies=1
if EnemiesCount8yMelee == 1 then
local ShouldReturn = SingleTarget(); if ShouldReturn then return ShouldReturn; end
if CastAnnotated(S.Pool, false, "WAIT") then return "Pool for SingleTarget()"; end
end
-- run_action_list,name=small_aoe,if=active_enemies>1&active_enemies<=5
if EnemiesCount8yMelee > 1 and EnemiesCount8yMelee <= 5 then
local ShouldReturn = SmallAoE(); if ShouldReturn then return ShouldReturn; end
if CastAnnotated(S.Pool, false, "WAIT") then return "Pool for SmallAoE()"; end
end
-- run_action_list,name=big_aoe,if=active_enemies>=6
if EnemiesCount8yMelee >= 6 then
local ShouldReturn = BigAoE(); if ShouldReturn then return ShouldReturn; end
if CastAnnotated(S.Pool, false, "WAIT") then return "Pool for BigAoE()"; end
end
-- If nothing else to do, show the Pool icon
if CastAnnotated(S.Pool, false, "WAIT") then return "Wait/Pool Resources"; end
end
end
local function Init()
S.FieryBrandDebuff:RegisterAuraTracking()
HR.Print("Vengeance DH rotation is currently a work in progress, but has been updated for patch 10.1.5.")
end
HR.SetAPL(581, APL, Init);