Blood Death Knight Rotation Improvements

Changes:
- Improved dynamic target selection for better threat management
- Added AoE threat management logic
- Implemented smarter use of defensive cooldowns
- Optimized ability usage based on current combat situation

New Features:
- Added GetAvailableRunes() function for more accurate rune management
- Implemented GetLowestThreatEnemy() function for improved tanking
- Added ShouldUseBonestorm() function for optimal Bonestorm usage

Optimizations:
- Improved Bone Shield management logic
- Enhanced Death Strike usage based on health and Runic Power
- Optimized Death and Decay placement using GetBestDeathAndDecayLocation()

Bug Fixes:
- Fixed issues with target selection and spell casting
- Resolved errors related to unit indexing in spell casts

Miscellaneous:
- Added mount detection to pause rotation while mounted
- Improved code structure and readability
- Enhanced comments for better code understanding

This update significantly improves the Blood Death Knight rotation,
focusing on better threat management, defensive play, and overall
performance in dungeon environments.
main
Emlembow 5 months ago
parent dd09c31dfb
commit deb58bc7a7
  1. 309
      BloodDK.lua

@ -14,43 +14,87 @@ local DancingRuneWeapon = SpellBook:GetSpell(49028)
local Tombstone = SpellBook:GetSpell(219809) local Tombstone = SpellBook:GetSpell(219809)
local Bonestorm = SpellBook:GetSpell(194844) local Bonestorm = SpellBook:GetSpell(194844)
local AbominationLimb = SpellBook:GetSpell(315443) local AbominationLimb = SpellBook:GetSpell(315443)
local EmpowerRuneWeapon = SpellBook:GetSpell(47568)
local DeathsCaress = SpellBook:GetSpell(195292) local DeathsCaress = SpellBook:GetSpell(195292)
local RaiseDead = SpellBook:GetSpell(46585) local RaiseDead = SpellBook:GetSpell(46585)
local BlindingSleet = SpellBook:GetSpell(207167) 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 AntiMagicShell = SpellBook:GetSpell(48707)
local GorefiendGrasp = SpellBook:GetSpell(108199)
local MindFreeze = SpellBook:GetSpell(47528) local MindFreeze = SpellBook:GetSpell(47528)
local Asphyxiate = SpellBook:GetSpell(221562) local Asphyxiate = SpellBook:GetSpell(221562)
local RaiseAlly = SpellBook:GetSpell(61999) local DeathGrip = SpellBook:GetSpell(49576)
local AntiMagicZone = SpellBook:GetSpell(51052) local GorefiendGrasp = SpellBook:GetSpell(108199)
local VampiricBlood = SpellBook:GetSpell(55233)
-- Buffs -- Buffs
local BoneShield = SpellBook:GetSpell(195181) local BoneShield = SpellBook:GetSpell(195181)
local DancingRuneWeaponBuff = SpellBook:GetSpell(81256) local DancingRuneWeaponBuff = SpellBook:GetSpell(81256)
local DeathAndDecayBuff = SpellBook:GetSpell(188290) local DeathAndDecayBuff = SpellBook:GetSpell(188290)
local Coagulopathy = SpellBook:GetSpell(391481)
local IcyTalons = SpellBook:GetSpell(194879)
-- Debuffs
local BloodPlague = SpellBook:GetSpell(55078)
-- Helper Functions -- Helper Functions
local function GetEnemiesInRange(range) local function GetEnemiesInRange(range)
local count = 0 local count = 0
Bastion.UnitManager:EnumEnemies(function(unit) Bastion.UnitManager:EnumEnemies(function(unit)
if unit:GetDistance(Player) <= range then if unit:GetDistance(Player) <= range and unit:IsAffectingCombat() then
count = count + 1 count = count + 1
end end
end) end)
return count return count
end end
local function HasEnoughRunicPower(cost) local function GetLowestThreatEnemy()
return Player:GetPower() >= cost 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 end
local function ShouldUseDeathStrike() local function ShouldUseDeathStrike()
local hp = Player:GetHP() local hp = Player:GetHP()
local runicPower = Player:GetPower() local runicPower = Player:GetPower()
return hp <= 70 or runicPower > 110 or (hp <= 85 and runicPower >= 45)
return hp <= 70 or runicPower > 110 or (hp <= 85 and HasEnoughRunicPower(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 end
-- Create APLs -- Create APLs
@ -58,20 +102,28 @@ local DefaultAPL = Bastion.APL:New('default')
local CooldownAPL = Bastion.APL:New('cooldown') local CooldownAPL = Bastion.APL:New('cooldown')
local DefensiveAPL = Bastion.APL:New('defensive') local DefensiveAPL = Bastion.APL:New('defensive')
local UtilityAPL = Bastion.APL:New('utility') local UtilityAPL = Bastion.APL:New('utility')
local ThreatAPL = Bastion.APL:New('threat')
-- Utility APL -- Threat Management APL
UtilityAPL:AddSpell( ThreatAPL:AddSpell(
BlindingSleet:CastableIf(function(self) DeathsCaress:CastableIf(function(self)
return self:IsKnownAndUsable() and GetEnemiesInRange(12) >= 3 return self:IsKnownAndUsable() and Target:GetDistance(Player) > 10
end):SetTarget(Player) end):SetTarget(Target)
) )
UtilityAPL:AddSpell( ThreatAPL:AddSpell(
GorefiendGrasp:CastableIf(function(self) DeathGrip:CastableIf(function(self)
return self:IsKnownAndUsable() and GetEnemiesInRange(15) >= 3 return self:IsKnownAndUsable() and Target:GetDistance(Player) > 10
end):SetTarget(Target) 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( UtilityAPL:AddSpell(
MindFreeze:CastableIf(function(self) MindFreeze:CastableIf(function(self)
return self:IsKnownAndUsable() and Target:IsCasting() and Target:IsInterruptible() return self:IsKnownAndUsable() and Target:IsCasting() and Target:IsInterruptible()
@ -84,6 +136,12 @@ UtilityAPL:AddSpell(
end):SetTarget(Target) end):SetTarget(Target)
) )
UtilityAPL:AddSpell(
GorefiendGrasp:CastableIf(function(self)
return self:IsKnownAndUsable() and GetEnemiesInRange(15) >= 3
end):SetTarget(Target)
)
-- Cooldown APL -- Cooldown APL
CooldownAPL:AddSpell( CooldownAPL:AddSpell(
DancingRuneWeapon:CastableIf(function(self) DancingRuneWeapon:CastableIf(function(self)
@ -98,40 +156,51 @@ CooldownAPL:AddSpell(
) )
CooldownAPL:AddSpell( CooldownAPL:AddSpell(
EmpowerRuneWeapon:CastableIf(function(self) Bonestorm:CastableIf(function(self)
return self:IsKnownAndUsable() and ShouldUseBonestorm()
end):SetTarget(Player)
)
CooldownAPL:AddSpell(
RaiseDead:CastableIf(function(self)
return self:IsKnownAndUsable() return self:IsKnownAndUsable()
end):SetTarget(Player) end):SetTarget(Player)
) )
-- Defensive APL -- Defensive APL
DefensiveAPL:AddSpell( DefensiveAPL:AddSpell(
AntiMagicShell:CastableIf(function(self) VampiricBlood:CastableIf(function(self)
return self:IsKnownAndUsable() and Player:GetHP() <= 70 return self:IsKnownAndUsable() and Player:GetHP() <= 60
end):SetTarget(Player) end):SetTarget(Player)
) )
DefensiveAPL:AddSpell( DefensiveAPL:AddSpell(
AntiMagicZone:CastableIf(function(self) IceboundFortitude:CastableIf(function(self)
return self:IsKnownAndUsable() and Player:GetHP() <= 50 return self:IsKnownAndUsable() and Player:GetHP() <= 40
end):SetTarget(Player) end):SetTarget(Player)
) )
DefensiveAPL:AddSpell( DefensiveAPL:AddSpell(
VampiricBlood:CastableIf(function(self) AntiMagicShell:CastableIf(function(self)
return self:IsKnownAndUsable() and Player:GetHP() <= 60 return self:IsKnownAndUsable() and Player:GetHP() <= 70
end):SetTarget(Player) end):SetTarget(Player)
) )
-- Default APL -- Default APL
DefaultAPL:AddSpell( DefaultAPL:AddSpell(
DeathStrike:CastableIf(function(self) DeathAndDecay:CastableIf(function(self)
return self:IsKnownAndUsable() and ShouldUseDeathStrike() return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(DeathAndDecayBuff):IsUp() and GetEnemiesInRange(10) > 0
end):SetTarget(Target) end):SetTarget(Player):OnCast(function(self)
local loc = GetBestDeathAndDecayLocation()
if loc then
self:Click(loc)
end
end)
) )
DefaultAPL:AddSpell( DefaultAPL:AddSpell(
Marrowrend:CastableIf(function(self) Marrowrend:CastableIf(function(self)
local boneShieldCount = Player:GetAuras():FindMy(BoneShield):GetCount() local boneShieldCount = GetBoneShieldStacks()
return self:IsKnownAndUsable() and return self:IsKnownAndUsable() and
(boneShieldCount <= 6 or (boneShieldCount <= 6 or
(boneShieldCount <= 7 and Player:GetAuras():FindMy(BoneShield):GetRemainingTime() < 3)) (boneShieldCount <= 7 and Player:GetAuras():FindMy(BoneShield):GetRemainingTime() < 3))
@ -140,37 +209,34 @@ DefaultAPL:AddSpell(
DefaultAPL:AddSpell( DefaultAPL:AddSpell(
Tombstone:CastableIf(function(self) Tombstone:CastableIf(function(self)
return self:IsKnownAndUsable() and Player:GetAuras():FindMy(BoneShield):GetCount() > 6 return self:IsKnownAndUsable() and GetBoneShieldStacks() >= 6
and not Player:GetAuras():FindMy(DancingRuneWeaponBuff):IsUp() and not Player:GetAuras():FindMy(DancingRuneWeaponBuff):IsUp()
and Player:GetAuras():FindMy(DeathAndDecayBuff):IsUp() and Player:GetAuras():FindMy(DeathAndDecayBuff):IsUp()
and DancingRuneWeapon:GetCooldownRemaining() > 0 and DancingRuneWeapon:GetCooldownRemaining() > 25
end):SetTarget(Player) end):SetTarget(Player)
) )
DefaultAPL:AddSpell( DefaultAPL:AddSpell(
Bonestorm:CastableIf(function(self) BloodBoil:CastableIf(function(self)
return self:IsKnownAndUsable() and Player:GetAuras():FindMy(BoneShield):GetCount() > 11 return self:IsKnownAndUsable() and self:GetCharges() > 1
and not Player:GetAuras():FindMy(DancingRuneWeaponBuff):IsUp() end):SetTarget(Target)
and Player:GetAuras():FindMy(DeathAndDecayBuff):IsUp()
and HasEnoughRunicPower(100)
and DancingRuneWeapon:GetCooldownRemaining() > 0
end):SetTarget(Player)
) )
DefaultAPL:AddSpell( DefaultAPL:AddSpell(
DeathAndDecay:CastableIf(function(self) DeathStrike:CastableIf(function(self)
return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(DeathAndDecayBuff):IsUp() return self:IsKnownAndUsable() and ShouldUseDeathStrike()
end):SetTarget(Player):OnCast(function(self) end):SetTarget(Target)
local loc = Bastion.UnitManager:FindEnemiesCentroid(10, 30)
if loc then
self:Click(loc)
end
end)
) )
DefaultAPL:AddSpell( DefaultAPL:AddSpell(
BloodBoil:CastableIf(function(self) Consumption:CastableIf(function(self)
return self:IsKnownAndUsable() and self:GetCharges() > 1 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) end):SetTarget(Target)
) )
@ -180,113 +246,62 @@ DefaultAPL:AddSpell(
end):SetTarget(Target) end):SetTarget(Target)
) )
-- Opener sequence -- Main rotation logic
local OpenerSequence = Bastion.Sequencer:New({ BloodDKModule:Sync(function()
function(self) -- Check if the player is mounted
if DeathAndDecay:IsKnownAndUsable() then if IsMounted() or Player:IsMounted() then
DeathAndDecay:Cast(Player):OnCast(function(self) return -- Exit the function if mounted, pausing the rotation
local loc = Bastion.UnitManager:FindEnemiesCentroid(10, 30) end
if loc then
self:Click(loc) 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) end)
return true
end
return false
end,
function(self)
if DeathsCaress:IsKnownAndUsable() then
DeathsCaress:Cast(Target)
return true
end end
return false
end,
function(self)
if RaiseDead:IsKnownAndUsable() then
RaiseDead:Cast(Player)
return true
end
return false
end,
function(self)
if DancingRuneWeapon:IsKnownAndUsable() then
DancingRuneWeapon:Cast(Player)
return true
end
return false
end,
function(self)
if BloodBoil:IsKnownAndUsable() then
BloodBoil:Cast(Target)
return true
end
return false
end,
function(self)
if Tombstone:IsKnownAndUsable() then
Tombstone:Cast(Player)
return true
end
return false
end,
function(self)
if HeartStrike:IsKnownAndUsable() then
HeartStrike:Cast(Target)
return true
end
return false
end,
function(self)
if DeathStrike:IsKnownAndUsable() and ShouldUseDeathStrike() then
DeathStrike:Cast(Target)
return true
end
return false
end,
function(self)
if HeartStrike:IsKnownAndUsable() then
HeartStrike:Cast(Target)
return true
end
return false
end,
function(self)
if BloodBoil:IsKnownAndUsable() then
BloodBoil:Cast(Target)
return true
end
return false
end,
function(self)
if DeathStrike:IsKnownAndUsable() and ShouldUseDeathStrike() then
DeathStrike:Cast(Target)
return true
end
return false
end,
function(self)
if Marrowrend:IsKnownAndUsable() then
Marrowrend:Cast(Target)
return true
end
return false
end,
}, function()
return not Player:IsAffectingCombat()
end)
DefaultAPL:AddSequence(OpenerSequence, function()
return not Player:IsAffectingCombat()
end)
BloodDKModule:Sync(function()
if not Player:IsAffectingCombat() then
return return
end 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() DefensiveAPL:Execute()
UtilityAPL:Execute() UtilityAPL:Execute()
CooldownAPL: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() DefaultAPL:Execute()
end) end)

Loading…
Cancel
Save