--- ============================ 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 )