--- ============================ 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_timeexecute_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_timeexecute_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= 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= 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_time1 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 if S.RapidFire:IsCastable() and (S.SurgingShots:IsAvailable()) 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_timeexecute_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_timeexecute_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=execute_time&(buff.precise_shots.down|buff.trueshot.up|full_recharge_time=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.durationtrinket.1.cooldown.duration%2|trinket.2.has_use_buff&trinket.2.cooldown.remains>variable.sync_remains-15&trinket.2.cooldown.remains-5fight_remains)|variable.trinket_2_stronger&(trinket.2.cooldown.remains&(trinket.2.cooldown.remains-5=20|trinket.2.cooldown.remains-5>=variable.sync_remains&(variable.sync_remains>trinket.1.cooldown.duration%2|trinket.1.cooldown.durationfight_remains)))|trinket.2.cooldown.ready&variable.sync_remains>20&variable.sync_remains20|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-5fight_remains)|variable.trinket_1_stronger&(trinket.1.cooldown.remains&(trinket.1.cooldown.remains-5=20|trinket.1.cooldown.remains-5>=variable.sync_remains&(variable.sync_remains>trinket.2.cooldown.duration%2|trinket.2.cooldown.durationfight_remains)))|trinket.1.cooldown.ready&variable.sync_remains>20&variable.sync_remains20|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.0.") end HR.SetAPL(254, APL, Init)