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.
513 lines
33 KiB
513 lines
33 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 MultiSpell = HL.MultiSpell
|
|
local Item = HL.Item
|
|
-- HeroRotation
|
|
local HR = HeroRotation
|
|
local Cast = HR.Cast
|
|
local CDsON = HR.CDsON
|
|
local AoEON = HR.AoEON
|
|
-- Num/Bool Helper Functions
|
|
local num = HR.Commons.Everyone.num
|
|
local bool = HR.Commons.Everyone.bool
|
|
-- lua
|
|
local GetTime = GetTime
|
|
-- File Locals
|
|
local Hunter = HR.Commons.Hunter
|
|
|
|
--- ============================ CONTENT ===========================
|
|
--- ======= APL LOCALS =======
|
|
-- luacheck: max_line_length 9999
|
|
|
|
-- Define S/I for spell and item arrays
|
|
local S = Spell.Hunter.Marksmanship
|
|
local I = Item.Hunter.Marksmanship
|
|
|
|
-- Define array of summon_pet spells
|
|
local SummonPetSpells = { S.SummonPet, S.SummonPet2, S.SummonPet3, S.SummonPet4, S.SummonPet5 }
|
|
|
|
-- Create table to exclude above trinkets from On Use function
|
|
local OnUseExcludes = {
|
|
I.AlgetharPuzzleBox:ID(),
|
|
}
|
|
|
|
-- Trinket Item Objects
|
|
local equip = Player:GetEquipment()
|
|
local trinket1 = (equip[13]) and Item(equip[13]) or Item(0)
|
|
local trinket2 = (equip[14]) and Item(equip[14]) or Item(0)
|
|
|
|
-- Rotation Var
|
|
local SteadyShotTracker = { LastCast = 0, Count = 0 }
|
|
local VarTrueshotReady
|
|
local BossFightRemains = 11111
|
|
local FightRemains = 11111
|
|
|
|
-- Enemy Range Variables
|
|
local Enemies40y
|
|
local Enemies10ySplash
|
|
local EnemiesCount10ySplash
|
|
local TargetInRange40y
|
|
|
|
-- GUI Settings
|
|
local Everyone = HR.Commons.Everyone;
|
|
local Settings = {
|
|
General = HR.GUISettings.General,
|
|
Commons = HR.GUISettings.APL.Hunter.Commons,
|
|
Commons2 = HR.GUISettings.APL.Hunter.Commons2,
|
|
Marksmanship = HR.GUISettings.APL.Hunter.Marksmanship
|
|
};
|
|
|
|
-- Variables
|
|
local VarCAExecute = Target:HealthPercentage() > 70 and S.CarefulAim:IsAvailable()
|
|
|
|
-- Interrupts
|
|
local StunInterrupts = {
|
|
{ S.Intimidation, "Cast Intimidation (Interrupt)", function () return true; end },
|
|
};
|
|
|
|
HL:RegisterForEvent(function()
|
|
equip = Player:GetEquipment()
|
|
trinket1 = (equip[13]) and Item(equip[13]) or Item(0)
|
|
trinket2 = (equip[14]) and Item(equip[14]) or Item(0)
|
|
end, "PLAYER_EQUIPMENT_CHANGED")
|
|
|
|
HL:RegisterForEvent(function()
|
|
SteadyShotTracker = { LastCast = 0, Count = 0 }
|
|
BossFightRemains = 11111
|
|
FightRemains = 11111
|
|
end, "PLAYER_REGEN_ENABLED")
|
|
|
|
HL:RegisterForEvent(function()
|
|
S.SerpentSting:RegisterInFlight()
|
|
S.SteadyShot:RegisterInFlight()
|
|
S.AimedShot:RegisterInFlight()
|
|
end, "LEARNED_SPELL_IN_TAB")
|
|
S.SerpentSting:RegisterInFlight()
|
|
S.SteadyShot:RegisterInFlight()
|
|
S.AimedShot:RegisterInFlight()
|
|
|
|
-- TODO(mrdmnd) - if you're casting (aimed or rapid fire) with volley up, you actually only have trick shots for next
|
|
-- aimed shot if volley buff is still up at the end of the cast. also conceivably build in buffer here.
|
|
-- test Player:BuffRemains(S.VolleyBuff) against S.Trueshot:ExecuteTime() for more accuracy
|
|
local function TrickShotsBuffCheck()
|
|
return (Player:BuffUp(S.TrickShotsBuff) and not Player:IsCasting(S.AimedShot) and not Player:IsChanneling(S.RapidFire)) or Player:BuffUp(S.VolleyBuff)
|
|
end
|
|
|
|
-- Update our SteadyFocus count
|
|
local function SteadyFocusUpdate()
|
|
-- The LastCast < GetTime - CastTime check is to try to not double count a single cast
|
|
if (SteadyShotTracker.Count == 0 or SteadyShotTracker.Count == 1) and Player:IsCasting(S.SteadyShot) and SteadyShotTracker.LastCast < GetTime() - S.SteadyShot:CastTime() then
|
|
SteadyShotTracker.LastCast = GetTime()
|
|
SteadyShotTracker.Count = SteadyShotTracker.Count + 1
|
|
end
|
|
-- Reset the counter if we cast anything that's not SteadyShot
|
|
if not (Player:IsCasting(S.SteadyShot) or Player:PrevGCDP(1, S.SteadyShot)) then SteadyShotTracker.Count = 0 end
|
|
-- Reset the counter if the last time we had the buff is newer than the last time we cast SteadyShot
|
|
if S.SteadyFocusBuff.LastAppliedOnPlayerTime > SteadyShotTracker.LastCast then SteadyShotTracker.Count = 0 end
|
|
end
|
|
|
|
local function EvaluateTargetIfFilterSerpentRemains(TargetUnit)
|
|
-- target_if=min:remains
|
|
return (TargetUnit:DebuffRemains(S.SerpentStingDebuff))
|
|
end
|
|
|
|
local function EvaluateTargetIfFilterAimedShot(TargetUnit)
|
|
-- target_if=min:dot.serpent_sting.remains+action.serpent_sting.in_flight_to_target*99
|
|
return (TargetUnit:DebuffRemains(S.SerpentStingDebuff) + num(S.SerpentSting:InFlight()) * 99)
|
|
end
|
|
|
|
local function EvaluateTargetIfFilterLatentPoison(TargetUnit)
|
|
-- target_if=max:debuff.latent_poison.stack
|
|
return (TargetUnit:DebuffStack(S.LatentPoisonDebuff))
|
|
end
|
|
|
|
local function EvaluateTargetIfSerpentSting(TargetUnit)
|
|
-- if=refreshable&!talent.serpentstalkers_trickery&buff.trueshot.down
|
|
return (TargetUnit:DebuffRefreshable(S.SerpentStingDebuff) and (not S.SerpentstalkersTrickery:IsAvailable()))
|
|
end
|
|
|
|
local function EvaluateTargetIfSerpentSting2(TargetUnit)
|
|
-- if=refreshable&talent.hydras_bite&!talent.serpentstalkers_trickery
|
|
return (TargetUnit:DebuffRefreshable(S.SerpentStingDebuff) and S.HydrasBite:IsAvailable() and not S.SerpentstalkersTrickery:IsAvailable())
|
|
end
|
|
|
|
local function EvaluateTargetIfSerpentSting3(TargetUnit)
|
|
-- if=refreshable&talent.poison_injection&!talent.serpentstalkers_trickery
|
|
return (TargetUnit:DebuffRefreshable(S.SerpentStingDebuff) and S.PoisonInjection:IsAvailable() and not S.SerpentstalkersTrickery:IsAvailable())
|
|
end
|
|
|
|
local function EvaluateTargetIfAimedShot(TargetUnit)
|
|
-- if=talent.serpentstalkers_trickery&(buff.precise_shots.down|(buff.trueshot.up|full_recharge_time<gcd+cast_time)&(!talent.chimaera_shot|active_enemies<2|ca_active)|buff.trick_shots.remains>execute_time&active_enemies>1)
|
|
return (S.SerpentstalkersTrickery:IsAvailable() and (Player:BuffDown(S.PreciseShotsBuff) or (Player:BuffUp(S.TrueshotBuff) or S.AimedShot:FullRechargeTime() < Player:GCD() + S.AimedShot:CastTime()) and ((not S.ChimaeraShot:IsAvailable()) or EnemiesCount10ySplash < 2 or TargetUnit:HealthPercentage() > 70) or Player:BuffRemains(S.TrickShotsBuff) > S.AimedShot:ExecuteTime() and EnemiesCount10ySplash > 1))
|
|
end
|
|
|
|
local function EvaluateTargetIfAimedShot2(TargetUnit)
|
|
-- if=(buff.precise_shots.down|(buff.trueshot.up|full_recharge_time<gcd+cast_time)&(!talent.chimaera_shot|active_enemies<2))|buff.trick_shots.remains>execute_time&active_enemies>1
|
|
return ((Player:BuffDown(S.PreciseShotsBuff) or (Player:BuffUp(S.TrueshotBuff) or S.AimedShot:FullRechargeTime() < Player:GCD() + S.AimedShot:CastTime()) and ((not S.ChimaeraShot:IsAvailable()) or EnemiesCount10ySplash < 2)) or Player:BuffRemains(S.TrickShotsBuff) > S.AimedShot:ExecuteTime() and EnemiesCount10ySplash > 1)
|
|
end
|
|
|
|
local function EvaluateTargetIfAimedShot3(TargetUnit)
|
|
-- if=talent.serpentstalkers_trickery&(buff.trick_shots.remains>=execute_time&(buff.precise_shots.down|buff.trueshot.up|full_recharge_time<cast_time+gcd))
|
|
return (S.SerpentstalkersTrickery:IsAvailable() and (Player:BuffRemains(S.TrickShotsBuff) >= S.AimedShot:ExecuteTime() and (Player:BuffDown(S.PreciseShotsBuff) or Player:BuffUp(S.TrueshotBuff) or S.AimedShot:FullRechargeTime() < S.AimedShot:CastTime() + Player:GCD())))
|
|
end
|
|
|
|
local function EvaluateTargetIfAimedShot4(TargetUnit)
|
|
-- if=(buff.trick_shots.remains>=execute_time&(buff.precise_shots.down|buff.trueshot.up|full_recharge_time<cast_time+gcd))
|
|
return (Player:BuffRemains(S.TrickShotsBuff) >= S.AimedShot:ExecuteTime() and (Player:BuffDown(S.PreciseShotsBuff) or Player:BuffUp(S.TrueshotBuff) or S.AimedShot:FullRechargeTime() < S.AimedShot:CastTime() + Player:GCD()))
|
|
end
|
|
|
|
local function Precombat()
|
|
-- flask
|
|
-- augmentation
|
|
-- food
|
|
-- summon_pet,if=!talent.lone_wolf
|
|
if S.SummonPet:IsCastable() and (not S.LoneWolf:IsAvailable()) then
|
|
if Cast(SummonPetSpells[Settings.Commons2.SummonPetSlot], Settings.Commons2.GCDasOffGCD.SummonPet) then return "Summon Pet"; end
|
|
end
|
|
-- snapshot_stats
|
|
-- salvo,precast_time=10
|
|
if S.Salvo:IsCastable() then
|
|
if Cast(S.Salvo, Settings.Marksmanship.OffGCDasOffGCD.Salvo) then return "salvo precombat 3"; end
|
|
end
|
|
-- use_item,name=algethar_puzzle_box
|
|
if Settings.Commons.Enabled.Trinkets and I.AlgetharPuzzleBox:IsEquippedAndReady() then
|
|
if Cast(I.AlgetharPuzzleBox, nil, Settings.Commons.DisplayStyle.Trinkets) then return "algethar_puzzle_box precombat 4"; end
|
|
end
|
|
-- aimed_shot,if=active_enemies<3&(!talent.volley|active_enemies<2)
|
|
if S.AimedShot:IsReady() and (not Player:IsCasting(S.AimedShot)) and (EnemiesCount10ySplash < 3 and ((not S.Volley:IsAvailable()) or EnemiesCount10ySplash < 2)) then
|
|
if Cast(S.AimedShot, nil, nil, not TargetInRange40y) then return "aimed_shot precombat 6"; end
|
|
end
|
|
-- wailing_arrow,if=active_enemies>2|!talent.steady_focus
|
|
if S.WailingArrow:IsReady() and (not Player:IsCasting(S.WailingArrow)) and (EnemiesCount10ySplash > 2 or not S.SteadyFocus:IsAvailable()) then
|
|
if Cast(S.WailingArrow, nil, nil, not TargetInRange40y) then return "wailing_arrow precombat 8"; end
|
|
end
|
|
-- steady_shot,if=active_enemies>2|talent.volley&active_enemies=2
|
|
if S.SteadyShot:IsCastable() and (EnemiesCount10ySplash > 2 or S.Volley:IsAvailable() and EnemiesCount10ySplash == 2) then
|
|
if Cast(S.SteadyShot, nil, nil, not TargetInRange40y) then return "steady_shot precombat 10"; end
|
|
end
|
|
end
|
|
|
|
local function Cds()
|
|
-- invoke_external_buff,name=power_infusion,if=buff.trueshot.remains>12
|
|
-- Note: Not handling external buffs.
|
|
-- berserking,if=buff.trueshot.up|fight_remains<13
|
|
if S.Berserking:IsReady() and (Player:BuffUp(S.TrueshotBuff) or FightRemains < 13) then
|
|
if Cast(S.Berserking, Settings.Commons.OffGCDasOffGCD.Racials) then return "berserking cds 2"; end
|
|
end
|
|
-- blood_fury,if=buff.trueshot.up|cooldown.trueshot.remains>30|fight_remains<16
|
|
if S.BloodFury:IsReady() and (Player:BuffUp(S.TrueshotBuff) or S.Trueshot:CooldownRemains() > 30 or FightRemains < 16) then
|
|
if Cast(S.BloodFury, Settings.Commons.OffGCDasOffGCD.Racials) then return "blood_fury cds 4"; end
|
|
end
|
|
-- ancestral_call,if=buff.trueshot.up|cooldown.trueshot.remains>30|fight_remains<16
|
|
if S.AncestralCall:IsReady() and (Player:BuffUp(S.TrueshotBuff) or S.Trueshot:CooldownRemains() > 30 or FightRemains < 16) then
|
|
if Cast(S.AncestralCall, Settings.Commons.OffGCDasOffGCD.Racials) then return "ancestral_call cds 6"; end
|
|
end
|
|
-- fireblood,if=buff.trueshot.up|cooldown.trueshot.remains>30|fight_remains<9
|
|
if S.Fireblood:IsReady() and (Player:BuffUp(S.TrueshotBuff) or S.Trueshot:CooldownRemains() > 30 or FightRemains < 9) then
|
|
if Cast(S.Fireblood, Settings.Commons.OffGCDasOffGCD.Racials) then return "fireblood cds 8"; end
|
|
end
|
|
-- lights_judgment,if=buff.trueshot.down
|
|
if S.LightsJudgment:IsReady() and (Player:BuffDown(S.TrueshotBuff)) then
|
|
if Cast(S.LightsJudgment, Settings.Commons.OffGCDasOffGCD.Racials, nil, not Target:IsSpellInRange(S.LightsJudgment)) then return "lights_judgment cds 10"; end
|
|
end
|
|
-- potion,if=buff.trueshot.up&(buff.bloodlust.up|target.health.pct<20)|fight_remains<26
|
|
if Settings.Commons.Enabled.Potions and (Player:BuffUp(S.TrueshotBuff) and (Player:BloodlustUp() or Target:HealthPercentage() < 20) or FightRemains < 26) then
|
|
local PotionSelected = Everyone.PotionSelected()
|
|
if PotionSelected and PotionSelected:IsReady() then
|
|
if Cast(PotionSelected, nil, Settings.Commons.DisplayStyle.Potions) then return "potion cds 12"; end
|
|
end
|
|
end
|
|
-- salvo,if=active_enemies>2|cooldown.volley.remains<10
|
|
if S.Salvo:IsCastable() and (EnemiesCount10ySplash > 2 or S.Volley:CooldownRemains() < 10) then
|
|
if Cast(S.Salvo, Settings.Marksmanship.OffGCDasOffGCD.Salvo) then return "salvo cds 14"; end
|
|
end
|
|
end
|
|
|
|
local function St()
|
|
-- steady_shot,if=talent.steady_focus&(steady_focus_count&buff.steady_focus.remains<5|buff.steady_focus.down&!buff.trueshot.up)
|
|
if S.SteadyShot:IsCastable() and (S.SteadyFocus:IsAvailable() and (SteadyShotTracker.Count == 1 and Player:BuffRemains(S.SteadyFocusBuff) < 5 or Player:BuffDown(S.SteadyFocusBuff) and Player:BuffDown(S.TrueshotBuff) and SteadyShotTracker.Count ~= 2)) then
|
|
if Cast(S.SteadyShot, nil, nil, not TargetInRange40y) then return "steady_shot st 2"; end
|
|
end
|
|
-- aimed_shot,if=buff.trueshot.up&full_recharge_time<gcd+cast_time&talent.legacy_of_the_windrunners&talent.windrunners_guidance
|
|
if S.AimedShot:IsReady() and (Player:BuffUp(S.TrueshotBuff) and S.AimedShot:FullRechargeTime() < Player:GCD() + S.AimedShot:CastTime() and S.LegacyoftheWindrunners:IsAvailable() and S.WindrunnersGuidance:IsAvailable()) then
|
|
if Cast(S.AimedShot, nil, nil, not TargetInRange40y) then return "aimed_shot st 4"; end
|
|
end
|
|
-- kill_shot,if=buff.trueshot.down
|
|
if S.KillShot:IsReady() and (Player:BuffDown(S.TrueshotBuff)) then
|
|
if Cast(S.KillShot, nil, nil, not TargetInRange40y) then return "kill_shot st 6"; end
|
|
end
|
|
-- volley,if=buff.salvo.up
|
|
if S.Volley:IsReady() and (Player:BuffUp(S.SalvoBuff)) then
|
|
if Cast(S.Volley, Settings.Marksmanship.GCDasOffGCD.Volley, nil, not TargetInRange40y) then return "volley st 8"; end
|
|
end
|
|
-- steel_trap,if=buff.trueshot.down
|
|
if S.SteelTrap:IsCastable() and (Player:BuffDown(S.TrueshotBuff)) then
|
|
if Cast(S.SteelTrap, Settings.Commons2.GCDasOffGCD.SteelTrap, nil, not Target:IsInRange(40)) then return "steel_trap st 10"; end
|
|
end
|
|
-- serpent_sting,target_if=min:dot.serpent_sting.remains,if=refreshable&!talent.serpentstalkers_trickery&buff.trueshot.down
|
|
if S.SerpentSting:IsReady() and (Player:BuffDown(S.TrueshotBuff)) then
|
|
if Everyone.CastTargetIf(S.SerpentSting, Enemies40y, "min", EvaluateTargetIfFilterSerpentRemains, EvaluateTargetIfSerpentSting, not TargetInRange40y) then return "serpent_sting st 12"; end
|
|
end
|
|
-- explosive_shot
|
|
if S.ExplosiveShot:IsReady() then
|
|
if Cast(S.ExplosiveShot, Settings.Commons2.GCDasOffGCD.ExplosiveShot, nil, not TargetInRange40y) then return "explosive_shot st 14"; end
|
|
end
|
|
-- stampede
|
|
if S.Stampede:IsCastable() then
|
|
if Cast(S.Stampede, nil, nil, not Target:IsInRange(30)) then return "stampede st 16"; end
|
|
end
|
|
-- death_chakram
|
|
if S.DeathChakram:IsReady() then
|
|
if Cast(S.DeathChakram, nil, Settings.Commons.DisplayStyle.Signature, not TargetInRange40y) then return "dark_chakram st 18"; end
|
|
end
|
|
-- wailing_arrow,if=active_enemies>1
|
|
if S.WailingArrow:IsReady() and (EnemiesCount10ySplash > 1) then
|
|
if Cast(S.WailingArrow, Settings.Marksmanship.GCDasOffGCD.WailingArrow, nil, not TargetInRange40y) then return "wailing_arrow st 20"; end
|
|
end
|
|
-- rapid_fire,if=talent.surging_shots|action.aimed_shot.full_recharge_time>action.aimed_shot.cast_time+cast_time
|
|
if S.RapidFire:IsCastable() and (S.SurgingShots:IsAvailable() or S.AimedShot:FullRechargeTime() > S.AimedShot:CastTime() + S.RapidFire:CastTime()) then
|
|
if Cast(S.RapidFire, nil, nil, not TargetInRange40y) then return "rapid_fire st 22"; end
|
|
end
|
|
-- kill_shot
|
|
if S.KillShot:IsReady() then
|
|
if Cast(S.KillShot, nil, nil, not TargetInRange40y) then return "kill_shot st 24"; end
|
|
end
|
|
-- trueshot,if=variable.trueshot_ready&(buff.trueshot.down|buff.trueshot.remains<5)
|
|
if S.Trueshot:IsReady() and CDsON() and (VarTrueshotReady and (Player:BuffDown(S.TrueshotBuff) or Player:BuffRemains(S.TrueshotBuff) < 5)) then
|
|
if Cast(S.Trueshot, Settings.Marksmanship.OffGCDasOffGCD.Trueshot) then return "trueshot st 26"; end
|
|
end
|
|
-- multishot,if=buff.salvo.up&!talent.volley
|
|
if S.MultiShot:IsReady() and (Player:BuffUp(S.SalvoBuff) and not S.Volley:IsAvailable()) then
|
|
if Cast(S.MultiShot, nil, nil, not TargetInRange40y) then return "multishot st 28"; end
|
|
end
|
|
-- aimed_shot,target_if=min:dot.serpent_sting.remains+action.serpent_sting.in_flight_to_target*99,if=talent.serpentstalkers_trickery&(buff.precise_shots.down|(buff.trueshot.up|full_recharge_time<gcd+cast_time)&(!talent.chimaera_shot|active_enemies<2|ca_active)|buff.trick_shots.remains>execute_time&active_enemies>1)
|
|
if S.AimedShot:IsReady() then
|
|
if Everyone.CastTargetIf(S.AimedShot, Enemies40y, "min", EvaluateTargetIfFilterAimedShot, EvaluateTargetIfAimedShot, not TargetInRange40y) then return "aimed_shot st 30"; end
|
|
end
|
|
-- aimed_shot,target_if=max:debuff.latent_poison.stack,if=(buff.precise_shots.down|(buff.trueshot.up|full_recharge_time<gcd+cast_time)&(!talent.chimaera_shot|active_enemies<2))|buff.trick_shots.remains>execute_time&active_enemies>1
|
|
if S.AimedShot:IsReady() then
|
|
if Everyone.CastTargetIf(S.AimedShot, Enemies40y, "max", EvaluateTargetIfFilterLatentPoison, EvaluateTargetIfAimedShot2, not TargetInRange40y) then return "aimed_shot st 32"; end
|
|
end
|
|
-- volley
|
|
if S.Volley:IsReady() then
|
|
if Cast(S.Volley, Settings.Marksmanship.GCDasOffGCD.Volley) then return "volley trickshots 36"; end
|
|
end
|
|
-- rapid_fire
|
|
if S.RapidFire:IsCastable() then
|
|
if Cast(S.RapidFire, nil, nil, not TargetInRange40y) then return "rapid_fire st 38"; end
|
|
end
|
|
-- wailing_arrow,if=buff.trueshot.down
|
|
if S.WailingArrow:IsReady() and (Player:BuffDown(S.TrueshotBuff)) then
|
|
if Cast(S.WailingArrow, Settings.Marksmanship.GCDasOffGCD.WailingArrow, nil, not TargetInRange40y) then return "wailing_arrow st 40"; end
|
|
end
|
|
-- kill_command,if=buff.trueshot.down
|
|
if S.KillCommand:IsCastable() and (Player:BuffDown(S.TrueshotBuff)) then
|
|
if Cast(S.KillCommand, nil, nil, not Target:IsInRange(50)) then return "kill_command st 42"; end
|
|
end
|
|
-- chimaera_shot,if=buff.precise_shots.up|focus>cost+action.aimed_shot.cost
|
|
if S.ChimaeraShot:IsReady() and (Player:BuffUp(S.PreciseShotsBuff) or Player:FocusP() > S.ChimaeraShot:Cost() + S.AimedShot:Cost()) then
|
|
if Cast(S.ChimaeraShot, nil, nil, not TargetInRange40y) then return "chimaera_shot st 44"; end
|
|
end
|
|
-- arcane_shot,if=buff.precise_shots.up|focus>cost+action.aimed_shot.cost
|
|
if S.ArcaneShot:IsReady() and (Player:BuffUp(S.PreciseShotsBuff) or Player:FocusP() > S.ArcaneShot:Cost() + S.AimedShot:Cost()) then
|
|
if Cast(S.ArcaneShot, nil, nil, not TargetInRange40y) then return "arcane_shot st 46"; end
|
|
end
|
|
-- bag_of_tricks,if=buff.trueshot.down
|
|
if S.BagofTricks:IsReady() then
|
|
if Cast(S.BagofTricks, Settings.Commons.OffGCDasOffGCD.Racials, nil, not Target:IsSpellInRange(S.BagofTricks)) then return "bag_of_tricks st 48"; end
|
|
end
|
|
-- steady_shot
|
|
if S.SteadyShot:IsCastable() then
|
|
if Cast(S.SteadyShot, nil, nil, not TargetInRange40y) then return "steady_shot st 50"; end
|
|
end
|
|
end
|
|
|
|
local function Trickshots()
|
|
-- steady_shot,if=talent.steady_focus&steady_focus_count&buff.steady_focus.remains<8
|
|
if S.SteadyShot:IsCastable() and (S.SteadyFocus:IsAvailable() and SteadyShotTracker.Count == 1 and Player:BuffRemains(S.SteadyFocusBuff) < 8) then
|
|
if Cast(S.SteadyShot, nil, nil, not TargetInRange40y) then return "steady_shot trickshots 2"; end
|
|
end
|
|
-- kill_shot,if=buff.razor_fragments.up
|
|
if S.KillShot:IsReady() and (Player:BuffUp(S.RazorFragmentsBuff)) then
|
|
if Cast(S.KillShot, nil, nil, not TargetInRange40y) then return "kill_shot trickshots 4"; end
|
|
end
|
|
-- explosive_shot
|
|
if S.ExplosiveShot:IsReady() then
|
|
if Cast(S.ExplosiveShot, Settings.Commons2.GCDasOffGCD.ExplosiveShot, nil, not TargetInRange40y) then return "explosive_shot trickshots 8"; end
|
|
end
|
|
-- death_chakram
|
|
if S.DeathChakram:IsReady() then
|
|
if Cast(S.DeathChakram, nil, Settings.Commons.DisplayStyle.Signature, not TargetInRange40y) then return "death_chakram trickshots 10"; end
|
|
end
|
|
-- stampede
|
|
if S.Stampede:IsReady() then
|
|
if Cast(S.Stampede, nil, nil, not Target:IsInRange(30)) then return "stampede trickshots 12"; end
|
|
end
|
|
-- wailing_arrow
|
|
if S.WailingArrow:IsReady() then
|
|
if Cast(S.WailingArrow, Settings.Marksmanship.GCDasOffGCD.WailingArrow, nil, not TargetInRange40y) then return "wailing_arrow trickshots 14"; end
|
|
end
|
|
-- serpent_sting,target_if=min:dot.serpent_sting.remains,if=refreshable&talent.hydras_bite&!talent.serpentstalkers_trickery
|
|
if S.SerpentSting:IsReady() then
|
|
if Everyone.CastTargetIf(S.SerpentSting, Enemies40y, "min", EvaluateTargetIfFilterSerpentRemains, EvaluateTargetIfSerpentSting2, not TargetInRange40y) then return "serpent_sting trickshots 16"; end
|
|
end
|
|
-- barrage,if=active_enemies>7
|
|
if S.Barrage:IsReady() and (EnemiesCount10ySplash > 7) then
|
|
if Cast(S.Barrage, nil, nil, not TargetInRange40y) then return "barrage trickshots 18"; end
|
|
end
|
|
-- volley
|
|
if S.Volley:IsReady() then
|
|
if Cast(S.Volley, Settings.Marksmanship.GCDasOffGCD.Volley) then return "volley trickshots 20"; end
|
|
end
|
|
-- trueshot
|
|
if S.Trueshot:IsReady() and CDsON() then
|
|
if Cast(S.Trueshot, Settings.Marksmanship.OffGCDasOffGCD.Trueshot, nil, not TargetInRange40y) then return "trueshot trickshots 22"; end
|
|
end
|
|
-- rapid_fire,if=buff.trick_shots.remains>=execute_time&talent.surging_shots
|
|
if S.RapidFire:IsCastable() and (Player:BuffRemains(S.TrickShotsBuff) >= S.RapidFire:ExecuteTime() and S.SurgingShots:IsAvailable()) then
|
|
if Cast(S.RapidFire, nil, nil, not TargetInRange40y) then return "rapid_fire trickshots 24"; end
|
|
end
|
|
-- aimed_shot,target_if=min:dot.serpent_sting.remains+action.serpent_sting.in_flight_to_target*99,if=talent.serpentstalkers_trickery&(buff.trick_shots.remains>=execute_time&(buff.precise_shots.down|buff.trueshot.up|full_recharge_time<cast_time+gcd))
|
|
if S.AimedShot:IsReady() then
|
|
if Everyone.CastTargetIf(S.AimedShot, Enemies40y, "min", EvaluateTargetIfFilterAimedShot, EvaluateTargetIfAimedShot3, not TargetInRange40y) then return "aimed_shot trickshots 26"; end
|
|
end
|
|
-- aimed_shot,target_if=max:debuff.latent_poison.stack,if=(buff.trick_shots.remains>=execute_time&(buff.precise_shots.down|buff.trueshot.up|full_recharge_time<cast_time+gcd))
|
|
if S.AimedShot:IsReady() then
|
|
if Everyone.CastTargetIf(S.AimedShot, Enemies40y, "max", EvaluateTargetIfFilterLatentPoison, EvaluateTargetIfAimedShot4, not TargetInRange40y) then return "aimed_shot trickshots 28"; end
|
|
end
|
|
-- rapid_fire,if=buff.trick_shots.remains>=execute_time
|
|
if S.RapidFire:IsCastable() and (Player:BuffRemains(S.TrickShotsBuff) >= S.RapidFire:ExecuteTime()) then
|
|
if Cast(S.RapidFire, nil, nil, not TargetInRange40y) then return "rapid_fire trickshots 30"; end
|
|
end
|
|
-- chimaera_shot,if=buff.trick_shots.up&buff.precise_shots.up&focus>cost+action.aimed_shot.cost&active_enemies<4
|
|
if S.ChimaeraShot:IsReady() and (Player:BuffUp(S.TrickShotsBuff) and Player:BuffUp(S.PreciseShotsBuff) and Player:FocusP() > S.ChimaeraShot:Cost() + S.AimedShot:Cost() and EnemiesCount10ySplash < 4) then
|
|
if Cast(S.ChimaeraShot, nil, nil, not TargetInRange40y) then return "chimaera_shot trickshots 32"; end
|
|
end
|
|
-- multishot,if=buff.trick_shots.down|(buff.precise_shots.up|buff.bulletstorm.stack=10)&focus>cost+action.aimed_shot.cost
|
|
if S.MultiShot:IsReady() and ((not TrickShotsBuffCheck()) or (Player:BuffUp(S.PreciseShotsBuff) or Player:BuffStack(S.BulletstormBuff) == 10) and Player:FocusP() > S.MultiShot:Cost() + S.AimedShot:Cost()) then
|
|
if Cast(S.MultiShot, nil, nil, not TargetInRange40y) then return "multishot trickshots 34"; end
|
|
end
|
|
-- serpent_sting,target_if=min:dot.serpent_sting.remains,if=refreshable&talent.poison_injection&!talent.serpentstalkers_trickery
|
|
if S.SerpentSting:IsReady() then
|
|
if Everyone.CastTargetIf(S.SerpentSting, Enemies40y, "min", EvaluateTargetIfFilterSerpentRemains, EvaluateTargetIfSerpentSting3, not TargetInRange40y) then return "serpent_sting trickshots 36"; end
|
|
end
|
|
-- steel_trap,if=buff.trueshot.down
|
|
if S.SteelTrap:IsCastable() and (Player:BuffDown(S.TrueshotBuff)) then
|
|
if Cast(S.SteelTrap, Settings.Commons2.GCDasOffGCD.SteelTrap, nil, not Target:IsInRange(40)) then return "steel_trap trickshots 38"; end
|
|
end
|
|
-- kill_shot,if=focus>cost+action.aimed_shot.cost
|
|
if S.KillShot:IsReady() and (Player:FocusP() > S.KillShot:Cost() + S.AimedShot:Cost()) then
|
|
if Cast(S.KillShot, nil, nil, not TargetInRange40y) then return "kill_shot trickshots 40"; end
|
|
end
|
|
-- multishot,if=focus>cost+action.aimed_shot.cost
|
|
if S.MultiShot:IsReady() and (Player:FocusP() > S.MultiShot:Cost() + S.AimedShot:Cost()) then
|
|
if Cast(S.MultiShot, nil, nil, not TargetInRange40y) then return "multishot trickshots 42"; end
|
|
end
|
|
-- bag_of_tricks,if=buff.trueshot.down
|
|
if S.BagofTricks:IsReady() and (Player:BuffDown(S.Trueshot)) then
|
|
if Cast(S.BagofTricks, Settings.Commons.OffGCDasOffGCD.Racials, nil, not Target:IsSpellInRange(S.BagofTricks)) then return "bag_of_tricks trickshots 44"; end
|
|
end
|
|
-- steady_shot
|
|
if S.SteadyShot:IsCastable() then
|
|
if Cast(S.SteadyShot, nil, nil, not TargetInRange40y) then return "steady_shot trickshots 46"; end
|
|
end
|
|
end
|
|
|
|
local function Trinkets()
|
|
-- variable,name=sync_ready,value=variable.trueshot_ready
|
|
-- variable,name=sync_active,value=buff.trueshot.up
|
|
-- variable,name=sync_remains,value=cooldown.trueshot.remains
|
|
-- variable,name=trinket_1_stronger,value=!trinket.2.has_cooldown|trinket.1.has_use_buff&(!trinket.2.has_use_buff|trinket.2.cooldown.duration<trinket.1.cooldown.duration|trinket.2.cast_time<trinket.1.cast_time|trinket.2.cast_time=trinket.1.cast_time&trinket.2.cooldown.duration=trinket.1.cooldown.duration)|!trinket.1.has_use_buff&(!trinket.2.has_use_buff&(trinket.2.cooldown.duration<trinket.1.cooldown.duration|trinket.2.cast_time<trinket.1.cast_time|trinket.2.cast_time=trinket.1.cast_time&trinket.2.cooldown.duration=trinket.1.cooldown.duration))
|
|
-- variable,name=trinket_2_stronger,value=!trinket.1.has_cooldown|trinket.2.has_use_buff&(!trinket.1.has_use_buff|trinket.1.cooldown.duration<trinket.2.cooldown.duration|trinket.1.cast_time<trinket.2.cast_time|trinket.1.cast_time=trinket.2.cast_time&trinket.1.cooldown.duration=trinket.2.cooldown.duration)|!trinket.2.has_use_buff&(!trinket.1.has_use_buff&(trinket.1.cooldown.duration<trinket.2.cooldown.duration|trinket.1.cast_time<trinket.2.cast_time|trinket.1.cast_time=trinket.2.cast_time&trinket.1.cooldown.duration=trinket.2.cooldown.duration))
|
|
-- use_item,use_off_gcd=1,slot=trinket1,if=trinket.1.has_use_buff&(variable.sync_ready&(variable.trinket_1_stronger|trinket.2.cooldown.remains)|!variable.sync_ready&(variable.trinket_1_stronger&(variable.sync_remains>trinket.1.cooldown.duration%2|trinket.2.has_use_buff&trinket.2.cooldown.remains>variable.sync_remains-15&trinket.2.cooldown.remains-5<variable.sync_remains&variable.sync_remains+40>fight_remains)|variable.trinket_2_stronger&(trinket.2.cooldown.remains&(trinket.2.cooldown.remains-5<variable.sync_remains&variable.sync_remains>=20|trinket.2.cooldown.remains-5>=variable.sync_remains&(variable.sync_remains>trinket.1.cooldown.duration%2|trinket.1.cooldown.duration<fight_remains&(variable.sync_remains+trinket.1.cooldown.duration>fight_remains)))|trinket.2.cooldown.ready&variable.sync_remains>20&variable.sync_remains<trinket.2.cooldown.duration%2)))|!trinket.1.has_use_buff&(trinket.1.cast_time=0|!variable.sync_active)&((!trinket.2.has_use_buff&(variable.trinket_1_stronger|trinket.2.cooldown.remains)|trinket.2.has_use_buff&(variable.sync_remains>20|trinket.2.cooldown.remains>20)))|target.time_to_die<25&(variable.trinket_1_stronger|trinket.2.cooldown.remains)
|
|
-- use_item,use_off_gcd=1,slot=trinket2,if=trinket.2.has_use_buff&(variable.sync_ready&(variable.trinket_2_stronger|trinket.1.cooldown.remains)|!variable.sync_ready&(variable.trinket_2_stronger&(variable.sync_remains>trinket.2.cooldown.duration%2|trinket.1.has_use_buff&trinket.1.cooldown.remains>variable.sync_remains-15&trinket.1.cooldown.remains-5<variable.sync_remains&variable.sync_remains+40>fight_remains)|variable.trinket_1_stronger&(trinket.1.cooldown.remains&(trinket.1.cooldown.remains-5<variable.sync_remains&variable.sync_remains>=20|trinket.1.cooldown.remains-5>=variable.sync_remains&(variable.sync_remains>trinket.2.cooldown.duration%2|trinket.2.cooldown.duration<fight_remains&(variable.sync_remains+trinket.2.cooldown.duration>fight_remains)))|trinket.1.cooldown.ready&variable.sync_remains>20&variable.sync_remains<trinket.1.cooldown.duration%2)))|!trinket.2.has_use_buff&(trinket.2.cast_time=0|!variable.sync_active)&((!trinket.1.has_use_buff&(variable.trinket_2_stronger|trinket.1.cooldown.remains)|trinket.1.has_use_buff&(variable.sync_remains>20|trinket.1.cooldown.remains>20)))|target.time_to_die<25&(variable.trinket_2_stronger|trinket.1.cooldown.remains)
|
|
-- Note: Currently can't track trinket CD length. Using the old trinket suggestion as a fallback.
|
|
-- use_item,name=algethar_puzzle_box,if=cooldown.trueshot.remains<2|fight_remains<22
|
|
if I.AlgetharPuzzleBox:IsEquippedAndReady() and (S.Trueshot:CooldownRemains() < 2 or FightRemains < 22) then
|
|
if Cast(I.AlgetharPuzzleBox, nil, Settings.Commons.DisplayStyle.Trinkets) then return "algethar_puzzle_box trinkets 2"; end
|
|
end
|
|
-- use_items,slots=trinket1,if=!trinket.1.has_use_buff|buff.trueshot.up
|
|
local Trinket1ToUse, _, Trinket1Range = Player:GetUseableItems(OnUseExcludes)
|
|
if Trinket1ToUse and ((not Trinket1ToUse:HasUseBuff()) or Player:BuffUp(S.TrueshotBuff)) then
|
|
if Cast(Trinket1ToUse, nil, Settings.Commons.DisplayStyle.Trinkets, not Target:IsInRange(Trinket1Range)) then return "Generic use_items for " .. Trinket1ToUse:Name(); end
|
|
end
|
|
-- use_items,slots=trinket2,if=!trinket.2.has_use_buff|buff.trueshot.up
|
|
local Trinket2ToUse, _, Trinket2Range = Player:GetUseableItems(OnUseExcludes)
|
|
if Trinket2ToUse and ((not Trinket2ToUse:HasUseBuff()) or Player:BuffUp(S.TrueshotBuff)) then
|
|
if Cast(Trinket2ToUse, nil, Settings.Commons.DisplayStyle.Trinkets, not Target:IsInRange(Trinket2Range)) then return "Generic use_items for " .. Trinket2ToUse:Name(); end
|
|
end
|
|
end
|
|
|
|
--- ======= ACTION LISTS =======
|
|
local function APL()
|
|
TargetInRange40y = Target:IsSpellInRange(S.AimedShot) -- Ranged abilities; Distance varies by Mastery
|
|
Enemies40y = Player:GetEnemiesInRange(S.AimedShot.MaximumRange)
|
|
Enemies10ySplash = Target:GetEnemiesInSplashRange(10)
|
|
if AoEON() then
|
|
EnemiesCount10ySplash = Target:GetEnemiesInSplashRangeCount(10)
|
|
else
|
|
EnemiesCount10ySplash = 1
|
|
end
|
|
|
|
if Everyone.TargetIsValid() or Player:AffectingCombat() then
|
|
-- Calculate fight_remains
|
|
BossFightRemains = HL.BossFightRemains(nil, true)
|
|
FightRemains = BossFightRemains
|
|
if FightRemains == 11111 then
|
|
FightRemains = HL.FightRemains(Enemies10ySplash, false)
|
|
end
|
|
end
|
|
|
|
if Everyone.TargetIsValid() then
|
|
SteadyFocusUpdate()
|
|
-- call precombat
|
|
if not Player:AffectingCombat() then
|
|
local ShouldReturn = Precombat(); if ShouldReturn then return ShouldReturn; end
|
|
end
|
|
-- Self heal, if below setting value
|
|
if S.Exhilaration:IsReady() and Player:HealthPercentage() <= Settings.Commons2.ExhilarationHP then
|
|
if Cast(S.Exhilaration, Settings.Commons2.GCDasOffGCD.Exhilaration) then return "exhilaration"; end
|
|
end
|
|
-- Interrupts
|
|
local ShouldReturn = Everyone.Interrupt(40, S.CounterShot, Settings.Commons2.OffGCDasOffGCD.CounterShot, StunInterrupts); if ShouldReturn then return ShouldReturn; end
|
|
-- auto_shot
|
|
-- variable,name=trueshot_ready,value=cooldown.trueshot.ready&(!raid_event.adds.exists&(!talent.bullseye|fight_remains>cooldown.trueshot.duration_guess+buff.trueshot.duration%2|buff.bullseye.stack=buff.bullseye.max_stack)&(!trinket.1.has_use_buff|trinket.1.cooldown.remains>30|trinket.1.cooldown.ready)&(!trinket.2.has_use_buff|trinket.2.cooldown.remains>30|trinket.2.cooldown.ready)|raid_event.adds.exists&(!raid_event.adds.up&(raid_event.adds.duration+raid_event.adds.in<25|raid_event.adds.in>60)|raid_event.adds.up&raid_event.adds.remains>10)|active_enemies>1|fight_remains<25)
|
|
VarTrueshotReady = S.Trueshot:CooldownUp()
|
|
-- call_action_list,name=cds
|
|
if (CDsON()) then
|
|
local ShouldReturn = Cds(); if ShouldReturn then return ShouldReturn; end
|
|
end
|
|
-- call_action_list,name=trinkets
|
|
if Settings.Commons.Enabled.Trinkets then
|
|
local ShouldReturn = Trinkets(); if ShouldReturn then return ShouldReturn; end
|
|
end
|
|
-- call_action_list,name=st,if=active_enemies<3|!talent.trick_shots
|
|
if (EnemiesCount10ySplash < 3 or not S.TrickShots:IsAvailable()) then
|
|
local ShouldReturn = St(); if ShouldReturn then return ShouldReturn; end
|
|
end
|
|
-- call_action_list,name=trickshots,if=active_enemies>2
|
|
if (EnemiesCount10ySplash > 2) then
|
|
local ShouldReturn = Trickshots(); if ShouldReturn then return ShouldReturn; end
|
|
end
|
|
-- Pool Focus if nothing else to do
|
|
if HR.CastAnnotated(S.PoolFocus, false, "WAIT") then return "Pooling Focus"; end
|
|
end
|
|
end
|
|
|
|
local function Init()
|
|
HR.Print("Marksmanship Hunter rotation is currently a work in progress, but has been updated for patch 10.1.5.")
|
|
end
|
|
|
|
HR.SetAPL(254, APL, Init)
|
|
|