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.
548 lines
29 KiB
548 lines
29 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 mathmax = math.max
|
|
local mathmin = math.min
|
|
local tableinsert = table.insert
|
|
|
|
--- ============================ 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 = {
|
|
I.BeacontotheBeyond:ID(),
|
|
I.DragonfireBombDispenser:ID(),
|
|
}
|
|
|
|
-- 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 = mathmin(SoulFragments + 2, 5)
|
|
LastSoulFragmentAdjustment = S.SoulCarver.LastCastTime
|
|
elseif S.Fracture:IsAvailable() and S.Fracture:TimeSinceLastCast() < Player:GCD() and S.Fracture.LastCastTime ~= LastSoulFragmentAdjustment then
|
|
SoulFragmentsAdjusted = mathmin(SoulFragments + 2 + MetaMod, 5)
|
|
LastSoulFragmentAdjustment = S.Fracture.LastCastTime
|
|
elseif S.Shear:TimeSinceLastCast() < Player:GCD() and S.Fracture.Shear ~= LastSoulFragmentAdjustment then
|
|
SoulFragmentsAdjusted = mathmin(SoulFragments + 1 + MetaMod, 5)
|
|
LastSoulFragmentAdjustment = S.Shear.LastCastTime
|
|
elseif S.SoulSigils:IsAvailable() then
|
|
local SigilLastCastTime = mathmax(S.SigilofFlame.LastCastTime, S.SigilofSilence.LastCastTime, S.SigilofChains.LastCastTime, S.ElysianDecree.LastCastTime)
|
|
local SigilTSLC = mathmin(S.SigilofFlame:TimeSinceLastCast(), S.SigilofSilence:TimeSinceLastCast(), S.SigilofChains:TimeSinceLastCast(), S.ElysianDecree:TimeSinceLastCast())
|
|
if S.ElysianDecree:IsAvailable() and SigilLastCastTime == S.ElysianDecree.LastCastTime and SigilTSLC < Player:GCD() and SigilLastCastTime ~= LastSoulFragmentAdjustment then
|
|
local NewFrags = mathmin(EnemiesCount8yMelee, 3)
|
|
SoulFragmentsAdjusted = mathmin(SoulFragments + NewFrags, 5)
|
|
LastSoulFragmentAdjustment = SigilLastCastTime
|
|
elseif SigilTSLC < Player:GCD() and SigilLastCastTime ~= LastSoulFragmentAdjustment then
|
|
SoulFragmentsAdjusted = mathmin(SoulFragments + 1, 5)
|
|
LastSoulFragmentAdjustment = SigilLastCastTime
|
|
end
|
|
elseif S.Fallout:IsAvailable() and S.ImmolationAura:TimeSinceLastCast() < Player:GCD() and S.ImmolationAura.LastCastTime ~= LastSoulFragmentAdjustment then
|
|
local NewFrags = 0.6 * mathmin(EnemiesCount8yMelee, 5)
|
|
SoulFragmentsAdjusted = mathmin(SoulFragments + NewFrags, 5)
|
|
LastSoulFragmentAdjustment = S.ImmolationAura.LastCastTime
|
|
elseif S.BulkExtraction:IsAvailable() and S.BulkExtraction:TimeSinceLastCast() < Player:GCD() and S.BulkExtraction.LastCastTime ~= LastSoulFragmentAdjustment then
|
|
local NewFrags = mathmin(EnemiesCount8yMelee, 5)
|
|
SoulFragmentsAdjusted = mathmin(SoulFragments + NewFrags, 5)
|
|
LastSoulFragmentAdjustment = S.BulkExtraction.LastCastTime
|
|
end
|
|
else
|
|
-- If we have a soul fragement "snapshot", see if we should invalidate it based on time
|
|
local Prev = Player:PrevGCD(1)
|
|
local FragAbilities = { S.SoulCarver, S.Fracture, S.Shear, S.BulkExtraction }
|
|
if S.SoulSigils:IsAvailable() then
|
|
tableinsert(FragAbilities, S.SigilofFlame)
|
|
tableinsert(FragAbilities, S.SigilofSilence)
|
|
tableinsert(FragAbilities, S.SigilofChains)
|
|
tableinsert(FragAbilities, S.ElysianDecree)
|
|
end
|
|
if S.Fallout:IsAvailable() then
|
|
tableinsert(FragAbilities, S.ImmolationAura)
|
|
end
|
|
for _, FragAbility in pairs(FragAbilities) do
|
|
if Prev == FragAbility:ID() and FragAbility:TimeSinceLastCast() >= Player:GCD() then
|
|
SoulFragmentsAdjusted = 0
|
|
break
|
|
end
|
|
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()
|
|
-- 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 filler 2 (Concentrated)"; end
|
|
else
|
|
if Cast(S.SigilofFlame, Settings.Commons.GCDasOffGCD.SigilOfFlame, nil, not Target:IsInRange(30)) then return "sigil_of_flame filler 2 (Normal)"; end
|
|
end
|
|
end
|
|
-- immolation_aura
|
|
if S.ImmolationAura:IsCastable() then
|
|
if Cast(S.ImmolationAura) then return "immolation_aura filler 4"; end
|
|
end
|
|
-- fracture
|
|
if S.Fracture:IsCastable() then
|
|
if Cast(S.Fracture, nil, nil, not IsInMeleeRange) then return "fracture filler 6"; end
|
|
end
|
|
-- shear
|
|
if S.Shear:IsCastable() then
|
|
if Cast(S.Shear, nil, nil, not IsInMeleeRange) then return "shear filler 8"; end
|
|
end
|
|
-- felblade
|
|
if S.Felblade:IsReady() then
|
|
if Cast(S.Felblade, nil, nil, not Target:IsSpellInRange(S.Felblade)) then return "felblade filler 10"; end
|
|
end
|
|
-- spirit_bomb,if=soul_fragments>=3
|
|
if S.SpiritBomb:IsReady() and (SoulFragments >= 3) then
|
|
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb filler 12"; end
|
|
end
|
|
-- soul_cleave,if=soul_fragments<=1
|
|
if S.SoulCleave:IsReady() and (SoulFragments <= 1) then
|
|
if Cast(S.SoulCleave, nil, nil, not IsInMeleeRange) then return "soul_cleave filler 14"; end
|
|
end
|
|
-- throw_glaive,if=gcd.remains>=0.5*gcd.max
|
|
if S.ThrowGlaive:IsCastable() and (Player:GCDRemains() >= 0.5 * (Player:GCD() + 0.25)) then
|
|
if Cast(S.ThrowGlaive, nil, nil, not Target:IsSpellInRange(S.ThrowGlaive)) then return "throw_glaive filler 16"; end
|
|
end
|
|
end
|
|
|
|
local function Maintenance()
|
|
-- fiery_brand,if=(!ticking&active_dot.fiery_brand=0)|charges>=2|(full_recharge_time<=cast_time+gcd.remains)
|
|
if S.FieryBrand:IsCastable() and ((Target:DebuffDown(S.FieryBrandDebuff) and S.FieryBrandDebuff:AuraActiveCount() == 0) or S.FieryBrand:Charges() >= 2 or (S.FieryBrand:FullRechargeTime() <= S.FieryBrand:CastTime() + Player:GCDRemains())) then
|
|
if Cast(S.FieryBrand, Settings.Vengeance.GCDasOffGCD.FieryBrand, nil, not Target:IsSpellInRange(S.FieryBrand)) then return "fiery_brand maintenance 2"; end
|
|
end
|
|
-- bulk_extraction,if=active_enemies>=(5-soul_fragments)
|
|
if S.BulkExtraction:IsCastable() and (EnemiesCount8yMelee >= (5 - SoulFragments)) then
|
|
if Cast(S.BulkExtraction, Settings.Vengeance.GCDasOffGCD.BulkExtraction, nil, not Target:IsInMeleeRange(8)) then return "bulk_extraction maintenance 4"; 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 6"; end
|
|
end
|
|
-- fracture,target_if=max:dot.fiery_brand.remains,if=active_enemies>1&buff.recrimination.up
|
|
if S.Fracture:IsCastable() and (EnemiesCount8yMelee > 1 and Player:BuffUp(S.RecriminationBuff)) then
|
|
if Everyone.CastTargetIf(S.Fracture, Enemies8yMelee, "max", EvaluateTargetIfFilterFBRemains, EvaluateTargetIfFractureMaintenance, not IsInMeleeRange) then return "fracture maintenance 8"; 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 10"; end
|
|
end
|
|
-- immolation_aura,if=!talent.fallout|soul_fragments<5
|
|
if S.ImmolationAura:IsCastable() and ((not S.Fallout:IsAvailable()) or SoulFragments < 5) then
|
|
if Cast(S.ImmolationAura) then return "immolation_aura maintenance 12"; 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 maintenance 14 (Concentrated)"; end
|
|
else
|
|
if Cast(S.SigilofFlame, Settings.Commons.GCDasOffGCD.SigilOfFlame, nil, not Target:IsInRange(30)) then return "sigil_of_flame maintenance 14 (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 16"; end
|
|
end
|
|
end
|
|
|
|
local function Externals()
|
|
-- invoke_external_buff,name=symbol_of_hope,if=cooldown.fiery_brand.charges=0
|
|
-- invoke_external_buff,name=power_infusion
|
|
-- Note: Not handling external buffs
|
|
end
|
|
|
|
local function Trinkets()
|
|
if Settings.Commons.Enabled.Trinkets then
|
|
-- use_item,name=elementium_pocket_anvil,use_off_gcd=1
|
|
if I.ElementiumPocketAnvil:IsEquippedAndReady() then
|
|
if Cast(I.ElementiumPocketAnvil, nil, Settings.Commons.DisplayStyle.Trinkets, not Target:IsInRange(8)) then return "elementium_pocket_anvil trinkets 2"; end
|
|
end
|
|
-- use_item,name=beacon_to_the_beyond,use_off_gcd=1,if=!talent.first_of_the_illidari|buff.metamorphosis.up|fight_remains<=10|cooldown.metamorphosis.remains>25
|
|
if I.BeacontotheBeyond:IsEquippedAndReady() and ((not S.FirstoftheIllidari:IsAvailable()) or Player:BuffUp(S.MetamorphosisBuff) or FightRemains <= 10 or S.Metamorphosis:CooldownRemains() > 25) then
|
|
if Cast(I.BeacontotheBeyond, nil, Settings.Commons.DisplayStyle.Trinkets, not Target:IsInRange(45)) then return "beacon_to_the_beyond trinkets 4"; end
|
|
end
|
|
-- use_item,name=dragonfire_bomb_dispenser,use_off_gcd=1,if=trinket.beacon_to_the_beyond.cooldown.remains>5|!equipped.beacon_to_the_beyond
|
|
if I.DragonfireBombDispenser:IsEquippedAndReady() and (I.BeacontotheBeyond:CooldownRemains() > 5 or not I.BeacontotheBeyond:IsEquipped()) then
|
|
if Cast(I.DragonfireBombDispenser, nil, Settings.Commons.DisplayStyle.Trinkets, not Target:IsInRange(46)) then return "dragonfire_bomb_dispenser trinkets 6"; end
|
|
end
|
|
end
|
|
-- 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
|
|
end
|
|
|
|
local function SingleTarget()
|
|
-- 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 2"; end
|
|
end
|
|
-- soul_carver
|
|
if S.SoulCarver:IsCastable() then
|
|
if Cast(S.SoulCarver, nil, nil, not IsInMeleeRange) then return "soul_carver single_target 4"; end
|
|
end
|
|
-- the_hunt
|
|
if S.TheHunt:IsCastable() then
|
|
if Cast(S.TheHunt, nil, Settings.Commons.DisplayStyle.Signature, not Target:IsInRange(50)) then return "the_hunt single_target 6"; end
|
|
end
|
|
-- elysian_decree
|
|
if S.ElysianDecree:IsCastable() then
|
|
if Cast(S.ElysianDecree, nil, Settings.Commons.DisplayStyle.Signature, not Target:IsInRange(30)) then return "elysian_decree single_target 8"; end
|
|
end
|
|
-- fracture,if=set_bonus.tier30_4pc&variable.fd&soul_fragments<=4&soul_fragments>=1
|
|
if S.Fracture:IsCastable() and (Player:HasTier(30, 4) and VarFD and SoulFragments <= 4 and SoulFragments >= 1) then
|
|
if Cast(S.Fracture, nil, nil, not IsInMeleeRange) then return "fracture single_target 10"; end
|
|
end
|
|
-- spirit_bomb,if=soul_fragments>=4
|
|
if S.SpiritBomb:IsReady() and (SoulFragments >= 4) then
|
|
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb single_target 12"; 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 14"; end
|
|
end
|
|
-- spirit_bomb,if=variable.fd&soul_fragments>=3
|
|
if S.SpiritBomb:IsReady() and (VarFD and SoulFragments >= 3) then
|
|
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb single_target 16"; 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 single_target 18"; end
|
|
end
|
|
-- soul_cleave,if=soul_fragments<=1
|
|
if S.SoulCleave:IsReady() and (SoulFragments <= 1) then
|
|
if Cast(S.SoulCleave, nil, nil, not IsInMeleeRange) then return "soul_cleave single_target 20"; end
|
|
end
|
|
-- call_action_list,name=filler
|
|
local ShouldReturn = Filler(); if ShouldReturn then return ShouldReturn; end
|
|
end
|
|
|
|
local function SmallAoE()
|
|
-- 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 2"; end
|
|
end
|
|
-- the_hunt
|
|
if S.TheHunt:IsCastable() then
|
|
if Cast(S.TheHunt, nil, Settings.Commons.DisplayStyle.Signature, not Target:IsInRange(50)) then return "the_hunt small_aoe 4"; end
|
|
end
|
|
-- elysian_decree,if=(soul_fragments+variable.incoming_souls)<=2
|
|
if S.ElysianDecree:IsCastable() and (SoulFragments <= 2) then
|
|
if Cast(S.ElysianDecree, nil, Settings.Commons.DisplayStyle.Signature, not Target:IsInRange(30)) then return "elysian_decree small_aoe 6"; end
|
|
end
|
|
-- soul_carver,if=(soul_fragments+variable.incoming_souls)<=3
|
|
if S.SoulCarver:IsCastable() and (SoulFragments <= 3) then
|
|
if Cast(S.SoulCarver, nil, nil, not IsInMeleeRange) then return "soul_carver small_aoe 8"; 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 10"; end
|
|
end
|
|
-- spirit_bomb,if=soul_fragments>=3
|
|
if S.SpiritBomb:IsReady() and (SoulFragments >= 3) then
|
|
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb small_aoe 12"; end
|
|
end
|
|
-- soul_cleave,if=(talent.focused_cleave|(soul_fragments<=2&variable.incoming_souls=0))
|
|
if S.SoulCleave:IsReady() and (S.FocusedCleave:IsAvailable() or SoulFragments <= 2) then
|
|
if Cast(S.SoulCleave, nil, nil, not IsInMeleeRange) then return "soul_cleave small_aoe 14"; end
|
|
end
|
|
-- call_action_list,name=filler
|
|
local ShouldReturn = Filler(); if ShouldReturn then return ShouldReturn; end
|
|
end
|
|
|
|
local function BigAoE()
|
|
-- spirit_bomb,if=(active_enemies<=7&soul_fragments>=4)|(active_enemies>7&soul_fragments>=3)
|
|
if S.SpiritBomb:IsReady() and ((EnemiesCount8yMelee <= 7 and SoulFragments >= 4) or (EnemiesCount8yMelee > 7 and SoulFragments >= 3)) then
|
|
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb big_aoe 2"; 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 4"; end
|
|
end
|
|
-- the_hunt
|
|
if S.TheHunt:IsCastable() then
|
|
if Cast(S.TheHunt, nil, Settings.Commons.DisplayStyle.Signature, not Target:IsInRange(50)) then return "the_hunt big_aoe 6"; end
|
|
end
|
|
-- elysian_decree,if=(soul_fragments+variable.incoming_souls)<=2
|
|
if S.ElysianDecree:IsCastable() and (SoulFragments <= 2) then
|
|
if Cast(S.ElysianDecree, nil, Settings.Commons.DisplayStyle.Signature, not Target:IsInRange(30)) then return "elysian_decree big_aoe 8"; end
|
|
end
|
|
-- soul_carver,if=(soul_fragments+variable.incoming_souls)<=3
|
|
if S.SoulCarver:IsCastable() and (SoulFragments <= 3) then
|
|
if Cast(S.SoulCarver, nil, nil, not IsInMeleeRange) then return "soul_carver big_aoe 10"; end
|
|
end
|
|
-- fracture,if=soul_fragments>=2
|
|
if S.Fracture:IsCastable() and (SoulFragments >= 2) then
|
|
if Cast(S.Fracture, nil, nil, not IsInMeleeRange) then return "fracture big_aoe 12"; end
|
|
end
|
|
-- spirit_bomb,if=soul_fragments>=3
|
|
if S.SpiritBomb:IsReady() and (SoulFragments >= 3) then
|
|
if Cast(S.SpiritBomb, nil, nil, not Target:IsInMeleeRange(8)) then return "spirit_bomb big_aoe 14"; end
|
|
end
|
|
-- soul_cleave,if=soul_fragments<=2&variable.incoming_souls=0
|
|
if S.SoulCleave:IsReady() and (SoulFragments <= 2) then
|
|
if Cast(S.SoulCleave, nil, nil, not IsInMeleeRange) then return "soul_cleave big_aoe 16"; 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_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=incoming_souls,op=reset
|
|
-- variable,name=incoming_souls,op=add,value=2,if=prev_gcd.1.fracture&!buff.metamorphosis.up
|
|
-- variable,name=incoming_souls,op=add,value=3,if=prev_gcd.1.fracture&buff.metamorphosis.up
|
|
-- variable,name=incoming_souls,op=add,value=2,if=talent.soul_sigils&(prev_gcd.2.sigil_of_flame|prev_gcd.2.sigil_of_silence|prev_gcd.2.sigil_of_chains|prev_gcd.2.elysian_decree)
|
|
-- variable,name=incoming_souls,op=add,value=active_enemies>?3,if=talent.elysian_decree&prev_gcd.2.elysian_decree
|
|
-- variable,name=incoming_souls,op=add,value=0.6*active_enemies>?5,if=talent.fallout&prev_gcd.1.immolation_aura
|
|
-- variable,name=incoming_souls,op=add,value=active_enemies>?5,if=talent.bulk_extraction&prev_gcd.1.bulk_extraction
|
|
-- variable,name=incoming_souls,op=add,value=3-(cooldown.soul_carver.duration-ceil(cooldown.soul_carver.remains)),if=talent.soul_carver&cooldown.soul_carver.remains>57
|
|
-- Note: incoming_souls is already included in SoulFragments via SoulFragmentsAdjusted.
|
|
-- variable,name=fd,value=talent.fiery_demise&dot.fiery_brand.ticking
|
|
VarFD = S.FieryDemise:IsAvailable() and Target:DebuffUp(S.FieryBrandDebuff)
|
|
-- auto_attack
|
|
-- 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
|
|
-- demon_spikes,use_off_gcd=1,if=!buff.demon_spikes.up&!cooldown.pause_action.remains
|
|
-- Note: Handled via Defensives()
|
|
-- call_action_list,name=externals
|
|
-- Note: Not handling external buffs.
|
|
-- metamorphosis,use_off_gcd=1,if=talent.first_of_the_illidari&(trinket.beacon_to_the_beyond.cooldown.remains<10|fight_remains<=20|!equipped.beacon_to_the_beyond|fight_remains%%120>5&fight_remains%%120<30)
|
|
-- Note: if not equipped, I.BeacontotheBeyond:CooldownRemains() will return 0, so covered by the <10 check.
|
|
if S.Metamorphosis:IsCastable() and (S.FirstoftheIllidari and (I.BeacontotheBeyond:CooldownRemains() < 10 or FightRemains <= 20 or FightRemains % 120 > 5 and FightRemains % 120 < 30)) then
|
|
if Cast(S.Metamorphosis, nil, Settings.Commons.DisplayStyle.Metamorphosis) then return "metamorphosis main 2"; 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 4"; end
|
|
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 main 6"; end
|
|
end
|
|
end
|
|
-- call_action_list,name=trinkets
|
|
local ShouldReturn = Trinkets(); if ShouldReturn then return ShouldReturn; end
|
|
-- 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);
|
|
|