|
|
|
-- 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)
|
|
|
|
|
|
|
|
-- 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')
|
|
|
|
|
|
|
|
-- 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(
|
|
|
|
KillShot:CastableIf(function(self)
|
|
|
|
return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
|
|
|
|
and BestTarget:GetHP() < 20
|
|
|
|
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(
|
|
|
|
KillShot:CastableIf(function(self)
|
|
|
|
return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling()
|
|
|
|
and IsVenomsBiteTalented() and BestTarget:GetHP() < 20
|
|
|
|
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)
|
|
|
|
)
|
|
|
|
|
|
|
|
-- Module Sync
|
|
|
|
BMHunterModule:Sync(function()
|
|
|
|
if Player:IsMounted() then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
DefensiveAPL:Execute()
|
|
|
|
PetAPL:Execute()
|
|
|
|
TranqAPL:Execute() -- This will now respect the random reaction delay
|
|
|
|
|
|
|
|
if Player:IsAffectingCombat() then
|
|
|
|
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)
|