-- Talents: C0PAAAAAAAAAAAAAAAAAAAAAAAMmxGDsALjGaYDAAAAAADAAAAAAgZsNjxMjZYmxMMmZMzYMjZyMMmxMzMmZMDjhZGmlBjZG2A local Tinkr, Bastion = ... local BMHunterModule = Bastion.Module:New('BMHunter') local Player = Bastion.UnitManager:Get('player') local Pet = Bastion.UnitManager:Get('pet') -- Initialize SpellBook local SpellBook = Bastion.SpellBook:New() -- Spells local HuntersMark = SpellBook:GetSpell(257284) local BestialWrath = SpellBook:GetSpell(19574) local BarbedShot = SpellBook:GetSpell(217200) local DireBeast = SpellBook:GetSpell(120679) local KillCommand = SpellBook:GetSpell(34026) local BlackArrow = SpellBook:GetSpell(194599) local KillShot = SpellBook:GetSpell(53351) local CallOfTheWild = SpellBook:GetSpell(359844) local Bloodshed = SpellBook:GetSpell(321530) local CobraShot = SpellBook:GetSpell(193455) local ExplosiveShot = SpellBook:GetSpell(212431) local MultiShot = SpellBook:GetSpell(2643) local AutoShot = SpellBook:GetSpell(75) local CallPet = SpellBook:GetSpell(883) local FeedPet = SpellBook:GetSpell(6991) local CounterShot = SpellBook:GetSpell(147362) local MendPet = SpellBook:GetSpell(136) local Exhilaration = SpellBook:GetSpell(109304) local SurvivalOfTheFittest = SpellBook:GetSpell(264735) local TranquilizingShot = SpellBook:GetSpell(19801) -- Buffs and Debuffs local FrenzyBuff = SpellBook:GetSpell(272790) local BeastCleaveBuff = SpellBook:GetSpell(268877) local BestialWrathBuff = SpellBook:GetSpell(19574) -- Custom Units local BestTarget = Bastion.UnitManager:CreateCustomUnit('besttarget', function() local bestTarget = nil local highestHealth = 0 Bastion.UnitManager:EnumEnemies(function(unit) if unit:IsAffectingCombat() and unit:GetDistance(Player) <= 40 and Player:CanSee(unit) then local health = unit:GetHealth() if health > highestHealth then highestHealth = health bestTarget = unit end end end) return bestTarget or Bastion.UnitManager:Get('target') end) local InterruptTarget = Bastion.UnitManager:CreateCustomUnit('interrupttarget', function() local target = nil Bastion.UnitManager:EnumEnemies(function(unit) if unit:IsAffectingCombat() and unit:GetDistance(Player) <= 40 and Player:CanSee(unit) and unit:IsCasting() and unit:IsInterruptible() then local castPercentage = GetCastPercentage(unit) if castPercentage >= 30 and castPercentage <= 80 then target = unit return true end end end) return target or BestTarget end) local ExecuteTarget = Bastion.UnitManager:CreateCustomUnit('executetarget', function() local target = nil Bastion.UnitManager:EnumEnemies(function(unit) if unit:IsAffectingCombat() and unit:GetDistance(Player) <= 40 and Player:CanSee(unit) and unit:GetHP() < 20 then target = unit return true end end) return target or Bastion.UnitManager:Get('none') end) -- APLs local DefaultAPL = Bastion.APL:New('default') local CooldownAPL = Bastion.APL:New('cooldown') local AoEAPL = Bastion.APL:New('aoe') local PetAPL = Bastion.APL:New('pet') local InterruptAPL = Bastion.APL:New('interrupt') local DefensiveAPL = Bastion.APL:New('defensive') local BossAPL = Bastion.APL:New('boss') local TranqAPL = Bastion.APL:New('tranq') local ExecuteAPL = Bastion.APL:New('execute') -- Helper Functions local function GetBarbedShotCharges() return BarbedShot:GetCharges() end local function GetFrenzyStacks() return Pet:GetAuras():FindMy(FrenzyBuff):GetCount() end local function IsKillerCobraTalented() -- Implement logic to check if Killer Cobra is talented -- This is a placeholder and should be replaced with actual talent checking logic return true end local function IsVenomsBiteTalented() -- Implement logic to check if Venom's Bite is talented -- This is a placeholder and should be replaced with actual talent checking logic return true end local function ShouldUseMultiShot() local beastCleaveBuff = Pet:GetAuras():FindMy(BeastCleaveBuff) return Player:GetEnemies(8) >= 2 and (not beastCleaveBuff:IsUp() or beastCleaveBuff:GetRemainingTime() < 2) end local lastFeedTime = 0 local function ShouldFeedPet() return GetTime() - lastFeedTime > 1800 -- 30 minutes end local lastAutoAttackTime = 0 local function IsAutoAttacking() return GetTime() - lastAutoAttackTime < 2 end local function GetCastPercentage(unit) local spell, _, _, startTimeMS, endTimeMS = UnitCastingInfo(unit:GetOMToken()) if not spell then return 0 end local castTime = (endTimeMS - startTimeMS) / 1000 local elapsed = (GetTime() * 1000 - startTimeMS) / 1000 return elapsed / castTime * 100 end local function ShouldInterrupt(unit) local castPercentage = GetCastPercentage(unit) local randomThreshold = math.random(30, 80) return castPercentage >= randomThreshold end -- Variables to manage the random delay for Tranquilizing Shot local tranqTargetTime = 0 -- Tracks when a valid target was first identified local tranqReactionDelay = 0 -- Stores the random reaction delay -- Function to generate a random reaction delay between 400ms and 2 seconds local function GetRandomReactionDelay() return math.random(400, 2000) / 1000 -- Convert milliseconds to seconds end -- Function to check if the reaction delay has passed local function ShouldUseTranquilizingShot() local currentTime = GetTime() if tranqTargetTime == 0 then return false end return currentTime - tranqTargetTime >= tranqReactionDelay end local TranqTarget = Bastion.UnitManager:CreateCustomUnit('tranqtarget', function() local target = nil Bastion.UnitManager:EnumEnemies(function(unit) if unit:IsAffectingCombat() and unit:GetDistance(Player) <= 40 and Player:CanSee(unit) and unit:GetAuras():HasAnyDispelableAura(TranquilizingShot) then target = unit -- Set the target identification time if it hasn't been set if tranqTargetTime == 0 then tranqTargetTime = GetTime() tranqReactionDelay = GetRandomReactionDelay() end return true end end) -- Reset the target time if no valid target is found if not target then tranqTargetTime = 0 end return target or Bastion.UnitManager:Get('none') end) -- Default APL DefaultAPL:AddSpell( AutoShot:CastableIf(function(self) return BestTarget:Exists() and not IsAutoAttacking() and Player:CanSee(BestTarget) end):SetTarget(BestTarget):OnCast(function() lastAutoAttackTime = GetTime() end) ) DefaultAPL:AddSpell( BarbedShot:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and (GetFrenzyStacks() < 3 or GetBarbedShotCharges() == 2) end):SetTarget(BestTarget) ) DefaultAPL:AddSpell( KillCommand:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() end):SetTarget(BestTarget) ) DefaultAPL:AddSpell( DireBeast:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() end):SetTarget(BestTarget) ) DefaultAPL:AddSpell( CobraShot:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and (Player:GetAuras():FindMy(BestialWrathBuff):IsUp() or Player:GetPower() > 70) end):SetTarget(BestTarget) ) DefaultAPL:AddSpell( ExplosiveShot:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and not Player:GetAuras():FindMy(BestialWrathBuff):IsUp() end):SetTarget(BestTarget) ) -- Cooldown APL CooldownAPL:AddSpell( HuntersMark:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and not BestTarget:GetAuras():FindMy(HuntersMark):IsUp() end):SetTarget(BestTarget) ) CooldownAPL:AddSpell( BestialWrath:CastableIf(function(self) return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() end):SetTarget(Player) ) CooldownAPL:AddSpell( BlackArrow:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() end):SetTarget(BestTarget) ) CooldownAPL:AddSpell( CallOfTheWild:CastableIf(function(self) return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() end):SetTarget(Player) ) CooldownAPL:AddSpell( Bloodshed:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() end):SetTarget(BestTarget) ) -- AoE APL AoEAPL:AddSpell( BarbedShot:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and (GetFrenzyStacks() < 3 or GetBarbedShotCharges() == 2) end):SetTarget(BestTarget) ) AoEAPL:AddSpell( BlackArrow:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() end):SetTarget(BestTarget) ) AoEAPL:AddSpell( MultiShot:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and ShouldUseMultiShot() end):SetTarget(BestTarget) ) AoEAPL:AddSpell( DireBeast:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() end):SetTarget(BestTarget) ) AoEAPL:AddSpell( CallOfTheWild:CastableIf(function(self) return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() end):SetTarget(Player) ) AoEAPL:AddSpell( BestialWrath:CastableIf(function(self) return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() end):SetTarget(Player) ) AoEAPL:AddSpell( KillCommand:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() end):SetTarget(BestTarget) ) AoEAPL:AddSpell( BarbedShot:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and GetBarbedShotCharges() > 0 end):SetTarget(BestTarget) ) AoEAPL:AddSpell( CobraShot:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and Player:GetAuras():FindMy(BestialWrathBuff):IsUp() and IsKillerCobraTalented() end):SetTarget(BestTarget) ) AoEAPL:AddSpell( ExplosiveShot:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() end):SetTarget(BestTarget) ) AoEAPL:AddSpell( CobraShot:CastableIf(function(self) return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() end):SetTarget(BestTarget) ) -- Pet APL PetAPL:AddSpell( CallPet:CastableIf(function(self) return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and not Pet:Exists() end):SetTarget(Player) ) PetAPL:AddSpell( FeedPet:CastableIf(function(self) return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and Pet:Exists() and ShouldFeedPet() end):SetTarget(Pet):OnCast(function() lastFeedTime = GetTime() end) ) PetAPL:AddSpell( MendPet:CastableIf(function(self) return Pet:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and Pet:GetHP() < 90 and not Pet:GetAuras():FindMy(MendPet):IsUp() end):SetTarget(Pet) ) -- Interrupt APL InterruptAPL:AddSpell( CounterShot:CastableIf(function(self) if not BestTarget:Exists() or not self:IsKnownAndUsable() or Player:IsCastingOrChanneling() then return false end if BestTarget:IsCasting() and BestTarget:IsInterruptible() then return ShouldInterrupt(BestTarget) end return false end):SetTarget(BestTarget) ) -- Defensive APL DefensiveAPL:AddSpell( Exhilaration:CastableIf(function(self) return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and Player:GetHP() < 80 end):SetTarget(Player) ) DefensiveAPL:AddSpell( SurvivalOfTheFittest:CastableIf(function(self) return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and Player:GetHP() < 80 end):SetTarget(Player) ) -- Boss APL BossAPL:AddSpell( CallOfTheWild:CastableIf(function(self) return BestTarget:Exists() and BestTarget:IsBoss() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() end):SetTarget(Player) ) ExecuteAPL:AddSpell( KillShot:CastableIf(function(self) return ExecuteTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() end):SetTarget(ExecuteTarget) ) -- Module Sync BMHunterModule:Sync(function() if Player:IsMounted() then return end DefensiveAPL:Execute() PetAPL:Execute() TranqAPL:Execute() if Player:IsAffectingCombat() then ExecuteAPL:Execute() -- Add this line InterruptAPL:Execute() if BestTarget:Exists() and BestTarget:IsBoss() then BossAPL:Execute() end local enemyCount = Player:GetEnemies(8) if enemyCount >= 2 then AoEAPL:Execute() else CooldownAPL:Execute() DefaultAPL:Execute() end end end) Bastion:Register(BMHunterModule) -- Event handler for auto-attack tracking Bastion.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function() local _, subEvent, _, sourceGUID, _, _, _, _, _, _, _, spellID = CombatLogGetCurrentEventInfo() if sourceGUID == Player:GetGUID() and (subEvent == "SWING_DAMAGE" or subEvent == "SWING_MISSED") then lastAutoAttackTime = GetTime() end end)