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.
299 lines
17 KiB
299 lines
17 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
|
|
-- Num/Bool Helper Functions
|
|
local num = HR.Commons.Everyone.num
|
|
local bool = HR.Commons.Everyone.bool
|
|
|
|
--- ============================ CONTENT ===========================
|
|
--- ======= APL LOCALS =======
|
|
-- luacheck: max_line_length 9999
|
|
|
|
-- Define S/I for spell and item arrays
|
|
local S = Spell.Paladin.Protection
|
|
local I = Item.Paladin.Protection
|
|
|
|
-- Create table to exclude above trinkets from On Use function
|
|
local OnUseExcludes = {
|
|
}
|
|
|
|
-- Interrupts List
|
|
local StunInterrupts = {
|
|
{S.HammerofJustice, "Cast Hammer of Justice (Interrupt)", function () return true; end},
|
|
}
|
|
|
|
-- Rotation Var
|
|
local ActiveMitigationNeeded
|
|
local IsTanking
|
|
local Enemies8y, Enemies30y
|
|
local EnemiesCount8y, EnemiesCount30y
|
|
|
|
-- GUI Settings
|
|
local Everyone = HR.Commons.Everyone
|
|
local Settings = {
|
|
General = HR.GUISettings.General,
|
|
Commons = HR.GUISettings.APL.Paladin.Commons,
|
|
Protection = HR.GUISettings.APL.Paladin.Protection
|
|
}
|
|
|
|
local function EvaluateTargetIfFilterJudgment(TargetUnit)
|
|
return TargetUnit:DebuffRemains(S.JudgmentDebuff)
|
|
end
|
|
|
|
local function MissingAura()
|
|
return (Player:BuffDown(S.RetributionAura) and Player:BuffDown(S.DevotionAura) and Player:BuffDown(S.ConcentrationAura) and Player:BuffDown(S.CrusaderAura))
|
|
end
|
|
|
|
local function Precombat()
|
|
-- flask
|
|
-- food
|
|
-- augmentation
|
|
-- snapshot_stats
|
|
-- Manually added: devotion_aura
|
|
if S.DevotionAura:IsCastable() and (MissingAura()) then
|
|
if Cast(S.DevotionAura) then return "devotion_aura precombat 2"; end
|
|
end
|
|
-- lights_judgment
|
|
if CDsON() and S.LightsJudgment:IsCastable() then
|
|
if Cast(S.LightsJudgment, Settings.Commons.OffGCDasOffGCD.Racials, nil, not Target:IsSpellInRange(S.LightsJudgment)) then return "lights_judgment precombat 4"; end
|
|
end
|
|
-- arcane_torrent
|
|
if CDsON() and S.ArcaneTorrent:IsCastable() then
|
|
if Cast(S.ArcaneTorrent, Settings.Commons.OffGCDasOffGCD.Racials, nil, not Target:IsInRange(8)) then return "arcane_torrent precombat 6"; end
|
|
end
|
|
-- consecration
|
|
if S.Consecration:IsCastable() and Target:IsInMeleeRange(8) then
|
|
if Cast(S.Consecration) then return "consecration precombat 8"; end
|
|
end
|
|
-- variable,name=trinket_1_sync,op=setif,value=1,value_else=0.5,condition=trinket.1.has_use_buff&((talent.moment_of_glory.enabled&trinket.1.cooldown.duration%%cooldown.moment_of_glory.duration=0)|(!talent.moment_of_glory.enabled&trinket.1.cooldown.duration%%cooldown.avenging_wrath.duration=0))
|
|
-- variable,name=trinket_2_sync,op=setif,value=1,value_else=0.5,condition=trinket.2.has_use_buff&((talent.moment_of_glory.enabled&trinket.2.cooldown.duration%%cooldown.moment_of_glory.duration=0)|(!talent.moment_of_glory.enabled&trinket.2.cooldown.duration%%cooldown.avenging_wrath.duration=0))
|
|
-- variable,name=trinket_priority,op=setif,value=2,value_else=1,condition=!trinket.1.has_use_buff&trinket.2.has_use_buff|trinket.2.has_use_buff&((trinket.2.cooldown.duration%trinket.2.proc.any_dps.duration)*(1.5+trinket.2.has_buff.strength)*(variable.trinket_2_sync))>((trinket.1.cooldown.duration%trinket.1.proc.any_dps.duration)*(1.5+trinket.1.has_buff.strength)*(variable.trinket_1_sync))
|
|
-- variable,name=trinket_1_buffs,value=trinket.1.has_buff.strength|trinket.1.has_buff.mastery|trinket.1.has_buff.versatility|trinket.1.has_buff.haste|trinket.1.has_buff.crit
|
|
-- variable,name=trinket_2_buffs,value=trinket.2.has_buff.strength|trinket.2.has_buff.mastery|trinket.2.has_buff.versatility|trinket.2.has_buff.haste|trinket.2.has_buff.crit
|
|
-- Note: Unable to handle some trinket conditionals, such as cooldown.duration.
|
|
-- Manually added: avengers_shield
|
|
if S.AvengersShield:IsCastable() then
|
|
if Cast(S.AvengersShield, nil, nil, not Target:IsSpellInRange(S.AvengersShield)) then return "avengers_shield precombat 10"; end
|
|
end
|
|
-- Manually added: judgment
|
|
if S.Judgment:IsReady() then
|
|
if Cast(S.Judgment, nil, nil, not Target:IsSpellInRange(S.Judgment)) then return "judgment precombat 12"; end
|
|
end
|
|
end
|
|
|
|
local function Defensives()
|
|
if Player:HealthPercentage() <= Settings.Protection.LoHHP and S.LayonHands:IsCastable() then
|
|
if Cast(S.LayonHands, nil, Settings.Protection.DisplayStyle.Defensives) then return "lay_on_hands defensive 2"; end
|
|
end
|
|
if S.GuardianofAncientKings:IsCastable() and (Player:HealthPercentage() <= Settings.Protection.GoAKHP and Player:BuffDown(S.ArdentDefenderBuff)) then
|
|
if Cast(S.GuardianofAncientKings, nil, Settings.Protection.DisplayStyle.Defensives) then return "guardian_of_ancient_kings defensive 4"; end
|
|
end
|
|
if S.ArdentDefender:IsCastable() and (Player:HealthPercentage() <= Settings.Protection.ArdentDefenderHP and Player:BuffDown(S.GuardianofAncientKingsBuff)) then
|
|
if Cast(S.ArdentDefender, nil, Settings.Protection.DisplayStyle.Defensives) then return "ardent_defender defensive 6"; end
|
|
end
|
|
if S.WordofGlory:IsReady() and (Player:HealthPercentage() <= Settings.Protection.PrioSelfWordofGloryHP and not Player:HealingAbsorbed()) then
|
|
-- cast word of glory on us if it's a) free or b) probably not going to drop sotr
|
|
if (Player:BuffRemains(S.ShieldoftheRighteousBuff) >= 5 or Player:BuffUp(S.DivinePurposeBuff) or Player:BuffUp(S.ShiningLightFreeBuff)) then
|
|
if Cast(S.WordofGlory) then return "word_of_glory defensive 8"; end
|
|
else
|
|
-- cast it anyway but run the fuck away
|
|
if HR.CastAnnotated(S.WordofGlory, false, "KITE") then return "word_of_glory defensive 10"; end
|
|
end
|
|
end
|
|
if S.ShieldoftheRighteous:IsReady() and (Player:BuffRefreshable(S.ShieldoftheRighteousBuff) and (ActiveMitigationNeeded or Player:HealthPercentage() <= Settings.Protection.SotRHP)) then
|
|
if Cast(S.ShieldoftheRighteous, nil, Settings.Protection.DisplayStyle.ShieldOfTheRighteous) then return "shield_of_the_righteous defensive 14"; end
|
|
end
|
|
end
|
|
|
|
local function Cooldowns()
|
|
-- avenging_wrath
|
|
if S.AvengingWrath:IsCastable() then
|
|
if Cast(S.AvengingWrath, Settings.Protection.OffGCDasOffGCD.AvengingWrath) then return "avenging_wrath cooldowns 2"; end
|
|
end
|
|
-- sentinel
|
|
-- Note: Protection Paladin APL has back-end code to replace AW with Sentinel when talented.
|
|
if S.Sentinel:IsCastable() then
|
|
if Cast(S.Sentinel, Settings.Protection.OffGCDasOffGCD.Sentinel) then return "sentinel cooldowns 3"; end
|
|
end
|
|
-- potion,if=buff.avenging_wrath.up
|
|
if Settings.Commons.Enabled.Potions and (Player:BuffUp(S.AvengingWrathBuff)) then
|
|
local PotionSelected = Everyone.PotionSelected()
|
|
if PotionSelected and PotionSelected:IsReady() then
|
|
if Cast(PotionSelected, nil, Settings.Commons.DisplayStyle.Potions) then return "potion cooldowns 4"; end
|
|
end
|
|
end
|
|
-- moment_of_glory,if=(buff.avenging_wrath.remains<15|(time>10|(cooldown.avenging_wrath.remains>15))&(cooldown.avengers_shield.remains&cooldown.judgment.remains&cooldown.hammer_of_wrath.remains))
|
|
if S.MomentofGlory:IsCastable() and (Player:BuffRemains(S.AvengingWrathBuff) < 15 or (HL.CombatTime() > 10 or (S.AvengingWrath:CooldownRemains() > 15)) and (S.AvengersShield:CooldownDown() and S.Judgment:CooldownDown() and S.HammerofWrath:CooldownDown())) then
|
|
if Cast(S.MomentofGlory, Settings.Protection.OffGCDasOffGCD.MomentOfGlory) then return "moment_of_glory cooldowns 6"; end
|
|
end
|
|
-- holy_avenger,if=buff.avenging_wrath.up|cooldown.avenging_wrath.remains>60
|
|
-- Note: Appears to have been removed in 10.0.7
|
|
-- bastion_of_light,if=buff.avenging_wrath.up
|
|
if S.BastionofLight:IsCastable() and (Player:BuffUp(S.AvengingWrathBuff)) then
|
|
if Cast(S.BastionofLight, Settings.Protection.OffGCDasOffGCD.BastionOfLight) then return "bastion_of_light cooldowns 10"; end
|
|
end
|
|
end
|
|
|
|
local function Trinkets()
|
|
-- use_item,slot=trinket1,if=(buff.moment_of_glory.up|!talent.moment_of_glory_enabled&buff.avenging_wrath.up)&(!trinket.2.has_cooldown|trinket.2.cooldown.remains|variable.trinket_priority=1)|trinket.1.proc.any_dps.duration>=fight_remains
|
|
-- use_item,slot=trinket2,if=(buff.moment_of_glory.up|!talent.moment_of_glory_enabled&buff.avenging_wrath.up)&(!trinket.1.has_cooldown|trinket.1.cooldown.remains|variable.trinket_priority=2)|trinket.2.proc.any_dps.duration>=fight_remains
|
|
-- use_item,slot=trinket1,if=!variable.trinket_1_buffs&(trinket.2.cooldown.remains|!variable.trinket_2_buffs|(cooldown.moment_of_glory.remains>20|(!talent.moment_of_glory.enabled&cooldown.avenging_wrath.remains>20)))
|
|
-- use_item,slot=trinket2,if=!variable.trinket_2_buffs&(trinket.1.cooldown.remains|!variable.trinket_1_buffs|(cooldown.moment_of_glory.remains>20|(!talent.moment_of_glory.enabled&cooldown.avenging_wrath.remains>20)))
|
|
-- Note: Unable to handle some trinket conditionals, such as cooldown.duration. Using a generic fallback instead.
|
|
-- use_items,if=(buff.moment_of_glory.up|!talent.moment_of_glory.enabled&buff.avenging_wrath.up)|(cooldown.moment_of_glory.remains>20|!talent.moment_of_glory.enabled&cooldown.avenging_wrath.remains>20)
|
|
if ((Player:BuffUp(S.MomentofGloryBuff) or (not S.MomentofGlory:IsAvailable()) and Player:BuffUp(S.AvengingWrathBuff)) or (S.MomentofGlory:CooldownRemains() > 20 or (not S.MomentofGlory:IsAvailable()) and S.AvengingWrath:CooldownRemains() > 20)) 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 Standard()
|
|
-- shield_of_the_righteous,if=(!talent.righteous_protector.enabled|cooldown.righteous_protector_icd.remains=0)&(buff.bastion_of_light.up|buff.divine_purpose.up|holy_power>2)
|
|
-- TODO: Find a way to track RighteousProtector ICD.
|
|
if S.ShieldoftheRighteous:IsReady() and (Player:BuffUp(S.BastionofLightBuff) or Player:BuffUp(S.DivinePurposeBuff) or Player:HolyPower() > 2) then
|
|
if Cast(S.ShieldoftheRighteous, nil, Settings.Protection.DisplayStyle.ShieldOfTheRighteous) then return "shield_of_the_righteous standard 2"; end
|
|
end
|
|
-- avengers_shield,if=buff.moment_of_glory.up|!talent.moment_of_glory.enabled
|
|
if S.AvengersShield:IsCastable() and (Player:BuffUp(S.MomentofGloryBuff) or not S.MomentofGlory:IsAvailable()) then
|
|
if Cast(S.AvengersShield, nil, nil, not Target:IsSpellInRange(S.AvengersShield)) then return "avengers_shield standard 4"; end
|
|
end
|
|
-- hammer_of_wrath,if=buff.avenging_wrath.up
|
|
if S.HammerofWrath:IsReady() and (Player:BuffUp(S.AvengingWrathBuff)) then
|
|
if Cast(S.HammerofWrath, Settings.Commons.GCDasOffGCD.HammerOfWrath, nil, not Target:IsSpellInRange(S.HammerofWrath)) then return "hammer_of_wrath standard 6"; end
|
|
end
|
|
-- judgment,target_if=min:debuff.judgment.remains,if=charges=2|!talent.crusaders_judgment.enabled
|
|
if S.Judgment:IsReady() and (S.Judgment:Charges() == 2 or not S.CrusadersJudgment:IsAvailable()) then
|
|
if Everyone.CastTargetIf(S.Judgment, Enemies30y, "min", EvaluateTargetIfFilterJudgment, nil, not Target:IsSpellInRange(S.Judgment)) then return "judgment standard 8"; end
|
|
end
|
|
-- divine_toll,if=time>20|((buff.avenging_wrath.up|!talent.avenging_wrath.enabled)&(buff.moment_of_glory.up|!talent.moment_of_glory.enabled))
|
|
if CDsON() and S.DivineToll:IsReady() and (HL.CombatTime() > 20 or ((Player:BuffUp(S.AvengingWrathBuff) or not S.AvengingWrath:IsAvailable()) and (Player:BuffUp(S.MomentofGloryBuff) or not S.MomentofGlory:IsAvailable()))) then
|
|
if Cast(S.DivineToll, nil, Settings.Commons.DisplayStyle.Signature, not Target:IsInRange(30)) then return "divine_toll standard 10"; end
|
|
end
|
|
-- avengers_shield
|
|
if S.AvengersShield:IsCastable() then
|
|
if Cast(S.AvengersShield, nil, nil, not Target:IsSpellInRange(S.AvengersShield)) then return "avengers_shield standard 12"; end
|
|
end
|
|
-- hammer_of_wrath
|
|
if S.HammerofWrath:IsReady() then
|
|
if Cast(S.HammerofWrath, Settings.Commons.GCDasOffGCD.HammerOfWrath, not Target:IsSpellInRange(S.HammerofWrath)) then return "hammer_of_wrath standard 14"; end
|
|
end
|
|
-- judgment,target_if=min:debuff.judgment.remains
|
|
if S.Judgment:IsReady() then
|
|
if Everyone.CastTargetIf(S.Judgment, Enemies30y, "min", EvaluateTargetIfFilterJudgment, nil, not Target:IsSpellInRange(S.Judgment)) then return "judgment standard 16"; end
|
|
end
|
|
-- consecration,if=!consecration.up
|
|
if S.Consecration:IsCastable() and (Player:BuffDown(S.ConsecrationBuff)) then
|
|
if Cast(S.Consecration) then return "consecration standard 18"; end
|
|
end
|
|
-- eye_of_tyr,if=talent.inmost_light.enabled&raid_event.adds.in>=45
|
|
if CDsON() and S.EyeofTyr:IsCastable() and (S.InmostLight:IsAvailable()) then
|
|
if Cast(S.EyeofTyr, Settings.Protection.GCDasOffGCD.EyeOfTyr, nil, not Target:IsInMeleeRange(8)) then return "eye_of_tyr standard 20"; end
|
|
end
|
|
-- blessed_hammer
|
|
if S.BlessedHammer:IsCastable() then
|
|
if Cast(S.BlessedHammer, nil, nil, not Target:IsInMeleeRange(5)) then return "blessed_hammer standard 22"; end
|
|
end
|
|
-- hammer_of_the_righteous
|
|
if S.HammeroftheRighteous:IsCastable() then
|
|
if Cast(S.HammeroftheRighteous, nil, nil, not Target:IsInMeleeRange(5)) then return "hammer_of_the_righteous standard 24"; end
|
|
end
|
|
-- crusader_strike
|
|
if S.CrusaderStrike:IsCastable() then
|
|
if Cast(S.CrusaderStrike, nil, nil, not Target:IsInMeleeRange(5)) then return "crusader_strike standard 26"; end
|
|
end
|
|
-- eye_of_tyr,if=!talent.inmost_light.enabled&raid_event.adds.in>=60
|
|
if CDsON() and S.EyeofTyr:IsCastable() and (not S.InmostLight:IsAvailable()) then
|
|
if Cast(S.EyeofTyr, Settings.Protection.GCDasOffGCD.EyeOfTyr, nil, not Target:IsInMeleeRange(8)) then return "eye_of_tyr standard 27"; end
|
|
end
|
|
-- word_of_glory,if=buff.shining_light_free.up
|
|
if S.WordofGlory:IsReady() and (Player:BuffUp(S.ShiningLightFreeBuff)) then
|
|
-- Is our health ok? Are we in a party with a wounded party member? Heal them instead.
|
|
if Player:HealthPercentage() > 90 and Player:IsInParty() and not Player:IsInRaid() then
|
|
for _, Char in pairs(Unit.Party) do
|
|
if Char:Exists() and Char:IsInRange(40) and Char:HealthPercentage() <= 80 then
|
|
if HR.CastAnnotated(S.WordofGlory, false, Char:Name()) then return "word_of_glory standard party 28"; end
|
|
end
|
|
end
|
|
-- Nobody in the party needs it. We might as well heal ourselves for the extra block chance.
|
|
if Cast(S.WordofGlory, Settings.Protection.GCDasOffGCD.WordOfGlory) then return "word_of_glory standard self 30"; end
|
|
else
|
|
-- We're either solo, in a raid, or injured. Heal ourselves.
|
|
if Cast(S.WordofGlory, Settings.Protection.GCDasOffGCD.WordOfGlory) then return "word_of_glory standard self 32"; end
|
|
end
|
|
end
|
|
-- consecration
|
|
if S.Consecration:IsCastable() then
|
|
if Cast(S.Consecration) then return "consecration standard 34"; end
|
|
end
|
|
end
|
|
|
|
-- APL Main
|
|
local function APL()
|
|
Enemies8y = Player:GetEnemiesInMeleeRange(8)
|
|
Enemies30y = Player:GetEnemiesInRange(30)
|
|
if (AoEON()) then
|
|
EnemiesCount8y = #Enemies8y
|
|
EnemiesCount30y = #Enemies30y
|
|
else
|
|
EnemiesCount8y = 1
|
|
EnemiesCount30y = 1
|
|
end
|
|
|
|
ActiveMitigationNeeded = Player:ActiveMitigationNeeded()
|
|
IsTanking = Player:IsTankingAoE(8) or Player:IsTanking(Target)
|
|
|
|
if Everyone.TargetIsValid() then
|
|
-- Precombat
|
|
if not Player:AffectingCombat() then
|
|
local ShouldReturn = Precombat(); if ShouldReturn then return ShouldReturn; end
|
|
end
|
|
-- auto_attack
|
|
-- Interrupts
|
|
local ShouldReturn = Everyone.Interrupt(5, S.Rebuke, Settings.Commons.OffGCDasOffGCD.Rebuke, StunInterrupts); if ShouldReturn then return ShouldReturn; end
|
|
-- Manually added: Defensives!
|
|
if IsTanking then
|
|
local ShouldReturn = Defensives(); if ShouldReturn then return ShouldReturn; end
|
|
end
|
|
-- call_action_list,name=cooldowns
|
|
if CDsON() then
|
|
local ShouldReturn = Cooldowns(); if ShouldReturn then return ShouldReturn; end
|
|
end
|
|
-- call_action_list,name=trinkets
|
|
if Settings.Commons.Enabled.Trinkets or Settings.Commons.Enabled.Items then
|
|
local ShouldReturn = Trinkets(); if ShouldReturn then return ShouldReturn; end
|
|
end
|
|
-- call_action_list,name=standard
|
|
local ShouldReturn = Standard(); if ShouldReturn then return ShouldReturn; end
|
|
-- Manually added: Pool, if nothing else to do
|
|
if HR.CastAnnotated(S.Pool, false, "WAIT") then return "Wait/Pool Resources"; end
|
|
end
|
|
end
|
|
|
|
local function Init()
|
|
HR.Print("Protection Paladin rotation is currently a work in progress, but has been updated for patch 10.1.5.")
|
|
end
|
|
|
|
HR.SetAPL(66, APL, Init)
|
|
|