diff --git a/BloodDK.lua b/BloodDK.lua index c23b216..2e5f067 100644 --- a/BloodDK.lua +++ b/BloodDK.lua @@ -14,43 +14,87 @@ local DancingRuneWeapon = SpellBook:GetSpell(49028) local Tombstone = SpellBook:GetSpell(219809) local Bonestorm = SpellBook:GetSpell(194844) local AbominationLimb = SpellBook:GetSpell(315443) -local EmpowerRuneWeapon = SpellBook:GetSpell(47568) local DeathsCaress = SpellBook:GetSpell(195292) 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 GorefiendGrasp = SpellBook:GetSpell(108199) local MindFreeze = SpellBook:GetSpell(47528) local Asphyxiate = SpellBook:GetSpell(221562) -local RaiseAlly = SpellBook:GetSpell(61999) -local AntiMagicZone = SpellBook:GetSpell(51052) -local VampiricBlood = SpellBook:GetSpell(55233) +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 then + if unit:GetDistance(Player) <= range and unit:IsAffectingCombat() then count = count + 1 end end) return count end -local function HasEnoughRunicPower(cost) - return Player:GetPower() >= cost +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 HasEnoughRunicPower(45)) + 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 @@ -58,20 +102,28 @@ 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') --- Utility APL -UtilityAPL:AddSpell( - BlindingSleet:CastableIf(function(self) - return self:IsKnownAndUsable() and GetEnemiesInRange(12) >= 3 - end):SetTarget(Player) +-- Threat Management APL +ThreatAPL:AddSpell( + DeathsCaress:CastableIf(function(self) + return self:IsKnownAndUsable() and Target:GetDistance(Player) > 10 + end):SetTarget(Target) ) -UtilityAPL:AddSpell( - GorefiendGrasp:CastableIf(function(self) - return self:IsKnownAndUsable() and GetEnemiesInRange(15) >= 3 +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() @@ -84,6 +136,12 @@ UtilityAPL:AddSpell( 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) @@ -98,40 +156,51 @@ 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() end):SetTarget(Player) ) -- Defensive APL DefensiveAPL:AddSpell( - AntiMagicShell:CastableIf(function(self) - return self:IsKnownAndUsable() and Player:GetHP() <= 70 + VampiricBlood:CastableIf(function(self) + return self:IsKnownAndUsable() and Player:GetHP() <= 60 end):SetTarget(Player) ) DefensiveAPL:AddSpell( - AntiMagicZone:CastableIf(function(self) - return self:IsKnownAndUsable() and Player:GetHP() <= 50 + IceboundFortitude:CastableIf(function(self) + return self:IsKnownAndUsable() and Player:GetHP() <= 40 end):SetTarget(Player) ) DefensiveAPL:AddSpell( - VampiricBlood:CastableIf(function(self) - return self:IsKnownAndUsable() and Player:GetHP() <= 60 + AntiMagicShell:CastableIf(function(self) + return self:IsKnownAndUsable() and Player:GetHP() <= 70 end):SetTarget(Player) ) -- Default APL DefaultAPL:AddSpell( - DeathStrike:CastableIf(function(self) - return self:IsKnownAndUsable() and ShouldUseDeathStrike() - end):SetTarget(Target) + 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 = Player:GetAuras():FindMy(BoneShield):GetCount() + local boneShieldCount = GetBoneShieldStacks() return self:IsKnownAndUsable() and (boneShieldCount <= 6 or (boneShieldCount <= 7 and Player:GetAuras():FindMy(BoneShield):GetRemainingTime() < 3)) @@ -140,37 +209,34 @@ DefaultAPL:AddSpell( DefaultAPL:AddSpell( 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 Player:GetAuras():FindMy(DeathAndDecayBuff):IsUp() - and DancingRuneWeapon:GetCooldownRemaining() > 0 + and DancingRuneWeapon:GetCooldownRemaining() > 25 end):SetTarget(Player) ) DefaultAPL:AddSpell( - Bonestorm:CastableIf(function(self) - return self:IsKnownAndUsable() and Player:GetAuras():FindMy(BoneShield):GetCount() > 11 - and not Player:GetAuras():FindMy(DancingRuneWeaponBuff):IsUp() - and Player:GetAuras():FindMy(DeathAndDecayBuff):IsUp() - and HasEnoughRunicPower(100) - and DancingRuneWeapon:GetCooldownRemaining() > 0 - end):SetTarget(Player) + BloodBoil:CastableIf(function(self) + return self:IsKnownAndUsable() and self:GetCharges() > 1 + end):SetTarget(Target) ) DefaultAPL:AddSpell( - DeathAndDecay:CastableIf(function(self) - return self:IsKnownAndUsable() and not Player:GetAuras():FindMy(DeathAndDecayBuff):IsUp() - end):SetTarget(Player):OnCast(function(self) - local loc = Bastion.UnitManager:FindEnemiesCentroid(10, 30) - if loc then - self:Click(loc) - end - end) + DeathStrike:CastableIf(function(self) + return self:IsKnownAndUsable() and ShouldUseDeathStrike() + end):SetTarget(Target) ) DefaultAPL:AddSpell( - BloodBoil:CastableIf(function(self) - return self:IsKnownAndUsable() and self:GetCharges() > 1 + 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) ) @@ -180,113 +246,62 @@ DefaultAPL:AddSpell( end):SetTarget(Target) ) --- Opener sequence -local OpenerSequence = Bastion.Sequencer:New({ - function(self) - if DeathAndDecay:IsKnownAndUsable() then - DeathAndDecay:Cast(Player):OnCast(function(self) - local loc = Bastion.UnitManager:FindEnemiesCentroid(10, 30) - if loc then - self:Click(loc) +-- 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) - return true - end - return false - end, - function(self) - if DeathsCaress:IsKnownAndUsable() then - DeathsCaress:Cast(Target) - return true 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 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)