local Tinkr, Bastion = ... local BloodDKModule = Bastion.Module:New('BloodDeathKnight') local Player = Bastion.UnitManager:Get('player') local Target = Bastion.UnitManager:Get('target') local SpellBook = Bastion.SpellBook:New() -- Spells local DeathStrike = SpellBook:GetSpell(49998) local Marrowrend = SpellBook:GetSpell(195182) local HeartStrike = SpellBook:GetSpell(206930) local BloodBoil = SpellBook:GetSpell(50842) local DeathAndDecay = SpellBook:GetSpell(43265) local DancingRuneWeapon = SpellBook:GetSpell(49028) local Tombstone = SpellBook:GetSpell(219809) local Bonestorm = SpellBook:GetSpell(194844) local AbominationLimb = SpellBook:GetSpell(315443) local DeathsCaress = SpellBook:GetSpell(195292) local RaiseDead = SpellBook:GetSpell(46585) local Consumption = SpellBook:GetSpell(274156) local SoulReaper = SpellBook:GetSpell(343294) local VampiricBlood = SpellBook:GetSpell(55233) local IceboundFortitude = SpellBook:GetSpell(48792) local AntiMagicShell = SpellBook:GetSpell(48707) local MindFreeze = SpellBook:GetSpell(47528) local Asphyxiate = SpellBook:GetSpell(221562) local DeathGrip = SpellBook:GetSpell(49576) local GorefiendGrasp = SpellBook:GetSpell(108199) -- Buffs local BoneShield = SpellBook:GetSpell(195181) local DancingRuneWeaponBuff = SpellBook:GetSpell(81256) local DeathAndDecayBuff = SpellBook:GetSpell(188290) local Coagulopathy = SpellBook:GetSpell(391481) local IcyTalons = SpellBook:GetSpell(194879) -- Debuffs local BloodPlague = SpellBook:GetSpell(55078) -- Helper Functions local function GetEnemiesInRange(range) local count = 0 Bastion.UnitManager:EnumEnemies(function(unit) if unit:GetDistance(Player) <= range and unit:IsAffectingCombat() then count = count + 1 end end) return count end local function GetLowestThreatEnemy() local lowestThreatUnit = nil local lowestThreat = math.huge Bastion.UnitManager:EnumEnemies(function(unit) if unit:IsAffectingCombat() and unit:GetDistance(Player) <= 30 then local _, _, scaledPercent = UnitDetailedThreatSituation(Player:GetOMToken(), unit:GetOMToken()) if scaledPercent and scaledPercent < lowestThreat then lowestThreatUnit = unit lowestThreat = scaledPercent end end end) return lowestThreatUnit end local function GetBoneShieldStacks() return Player:GetAuras():FindMy(BoneShield):GetCount() end local function ShouldUseDeathStrike() local hp = Player:GetHP() local runicPower = Player:GetPower() return hp <= 70 or runicPower > 110 or (hp <= 85 and runicPower >= 45) end local function GetBestDeathAndDecayLocation() return Bastion.UnitManager:FindEnemiesCentroid(10, 30) end local function ShouldUseBonestorm() return GetBoneShieldStacks() > 10 and not Player:GetAuras():FindMy(DancingRuneWeaponBuff):IsUp() and Player:GetAuras():FindMy(DeathAndDecayBuff):IsUp() and Player:GetPower() >= 100 and DancingRuneWeapon:GetCooldownRemaining() > 0 end local function GetAvailableRunes() local count = 0 for i = 1, 6 do local start, duration, runeReady = GetRuneCooldown(i) if runeReady then count = count + 1 end end return count end -- Create APLs local DefaultAPL = Bastion.APL:New('default') local CooldownAPL = Bastion.APL:New('cooldown') local DefensiveAPL = Bastion.APL:New('defensive') local UtilityAPL = Bastion.APL:New('utility') local ThreatAPL = Bastion.APL:New('threat') -- Threat Management APL ThreatAPL:AddSpell( DeathsCaress:CastableIf(function(self) return self:IsKnownAndUsable() and Target:GetDistance(Player) > 10 end):SetTarget(Target) ) ThreatAPL:AddSpell( DeathGrip:CastableIf(function(self) return self:IsKnownAndUsable() and Target:GetDistance(Player) > 10 end):SetTarget(Target) ) ThreatAPL:AddSpell( BloodBoil:CastableIf(function(self) return self:IsKnownAndUsable() and self:GetCharges() > 0 and GetEnemiesInRange(10) > 1 end):SetTarget(Player) ) -- Utility APL UtilityAPL:AddSpell( MindFreeze:CastableIf(function(self) return self:IsKnownAndUsable() and Target:IsCasting() and Target:IsInterruptible() end):SetTarget(Target) ) UtilityAPL:AddSpell( Asphyxiate:CastableIf(function(self) return self:IsKnownAndUsable() and not Target:IsStunned() end):SetTarget(Target) ) UtilityAPL:AddSpell( GorefiendGrasp:CastableIf(function(self) return self:IsKnownAndUsable() and GetEnemiesInRange(15) >= 3 end):SetTarget(Target) ) -- Cooldown APL CooldownAPL:AddSpell( DancingRuneWeapon:CastableIf(function(self) return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(DancingRuneWeaponBuff):IsUp() end):SetTarget(Player) ) CooldownAPL:AddSpell( AbominationLimb:CastableIf(function(self) return self:IsKnownAndUsable() end):SetTarget(Player) ) CooldownAPL:AddSpell( Bonestorm:CastableIf(function(self) return self:IsKnownAndUsable() and ShouldUseBonestorm() end):SetTarget(Player) ) CooldownAPL:AddSpell( RaiseDead:CastableIf(function(self) return self:IsKnownAndUsable() end):SetTarget(Player) ) -- Defensive APL DefensiveAPL:AddSpell( VampiricBlood:CastableIf(function(self) return self:IsKnownAndUsable() and Player:GetHP() <= 60 end):SetTarget(Player) ) DefensiveAPL:AddSpell( IceboundFortitude:CastableIf(function(self) return self:IsKnownAndUsable() and Player:GetHP() <= 40 end):SetTarget(Player) ) DefensiveAPL:AddSpell( AntiMagicShell:CastableIf(function(self) return self:IsKnownAndUsable() and Player:GetHP() <= 70 end):SetTarget(Player) ) -- Default APL DefaultAPL:AddSpell( DeathAndDecay:CastableIf(function(self) return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(DeathAndDecayBuff):IsUp() and GetEnemiesInRange(10) > 0 end):SetTarget(Player):OnCast(function(self) local loc = GetBestDeathAndDecayLocation() if loc then self:Click(loc) end end) ) DefaultAPL:AddSpell( Marrowrend:CastableIf(function(self) local boneShieldCount = GetBoneShieldStacks() return self:IsKnownAndUsable() and (boneShieldCount <= 6 or (boneShieldCount <= 7 and Player:GetAuras():FindMy(BoneShield):GetRemainingTime() < 3)) end):SetTarget(Target) ) DefaultAPL:AddSpell( Tombstone:CastableIf(function(self) return self:IsKnownAndUsable() and GetBoneShieldStacks() >= 6 and not Player:GetAuras():FindMy(DancingRuneWeaponBuff):IsUp() and Player:GetAuras():FindMy(DeathAndDecayBuff):IsUp() and DancingRuneWeapon:GetCooldownRemaining() > 25 end):SetTarget(Player) ) DefaultAPL:AddSpell( BloodBoil:CastableIf(function(self) return self:IsKnownAndUsable() and self:GetCharges() > 1 end):SetTarget(Target) ) DefaultAPL:AddSpell( DeathStrike:CastableIf(function(self) return self:IsKnownAndUsable() and ShouldUseDeathStrike() end):SetTarget(Target) ) DefaultAPL:AddSpell( Consumption:CastableIf(function(self) return self:IsKnownAndUsable() and GetAvailableRunes() >= 2 end):SetTarget(Target) ) DefaultAPL:AddSpell( SoulReaper:CastableIf(function(self) return self:IsKnownAndUsable() and Target:GetHP() <= 35 end):SetTarget(Target) ) DefaultAPL:AddSpell( HeartStrike:CastableIf(function(self) return self:IsKnownAndUsable() end):SetTarget(Target) ) -- Main rotation logic BloodDKModule:Sync(function() -- Check if the player is mounted if IsMounted() or Player:IsMounted() then return -- Exit the function if mounted, pausing the rotation end if not Player:IsAffectingCombat() then if not Target:Exists() or Target:IsDead() then Bastion.UnitManager:EnumEnemies(function(unit) if unit:IsAlive() and unit:GetDistance(Player) <= 30 then Target = unit return true end end) end return end -- Dynamic target selection and threat management local lowestThreatEnemy = GetLowestThreatEnemy() if lowestThreatEnemy then Target = lowestThreatEnemy elseif not Target:Exists() or Target:IsDead() or Target:GetDistance(Player) > 30 then Bastion.UnitManager:EnumEnemies(function(unit) if unit:IsAlive() and unit:GetDistance(Player) <= 30 and unit:IsAffectingCombat() then Target = unit return true end end) end ThreatAPL:Execute() DefensiveAPL:Execute() UtilityAPL:Execute() CooldownAPL:Execute() -- AoE threat management if GetEnemiesInRange(10) > 1 and BloodBoil:IsKnownAndUsable() and BloodBoil:GetCharges() > 0 then BloodBoil:Cast(Player) end -- Single target threat management if Target:Exists() and not Target:GetAuras():FindMy(BloodPlague):IsUp() then if Target:GetDistance(Player) <= 10 then BloodBoil:Cast(Target) elseif DeathsCaress:IsKnownAndUsable() then DeathsCaress:Cast(Target) end end -- Maintain Coagulopathy and Icy Talons if Player:GetAuras():FindMy(Coagulopathy):GetRemainingTime() < 2 or Player:GetAuras():FindMy(IcyTalons):GetRemainingTime() < 2 then DeathStrike:Cast(Target) end DefaultAPL:Execute() end) Bastion:Register(BloodDKModule)