diff --git a/HolyPaladin.lua b/HolyPaladin.lua new file mode 100644 index 0000000..219aa7a --- /dev/null +++ b/HolyPaladin.lua @@ -0,0 +1,379 @@ +local Tinkr, Bastion = ... + +local HolyPaladinModule = Bastion.Module:New('HolyPaladin') +local Player = Bastion.UnitManager:Get('player') +local Target = Bastion.UnitManager:Get('target') +local MELEE_RANGE = 5 -- Adjust this value based on your class's melee range + +-- Initialize SpellBook +local SpellBook = Bastion.SpellBook:New() + +-- Power type constant +local HOLY_POWER = Enum.PowerType.HolyPower + +-- Healing threshold variable +local healingThreshold = 90 + +-- Spells +local AutoAttack = SpellBook:GetSpell(6603) +local BeaconOfVirtue = SpellBook:GetSpell(200025) +local BlessingOfSummer = SpellBook:GetSpell(388007) +local SacredWeapon = SpellBook:GetSpell(383395) +local Consecration = SpellBook:GetSpell(26573) +local Judgment = SpellBook:GetSpell(275773) +local HolyPrism = SpellBook:GetSpell(114165) +local WordOfGlory = SpellBook:GetSpell(85673) +local ShieldOfTheRighteous = SpellBook:GetSpell(53600) +local HolyShock = SpellBook:GetSpell(20473) +local HolyLight = SpellBook:GetSpell(82326) +local CrusaderStrike = SpellBook:GetSpell(35395) +local HammerOfWrath = SpellBook:GetSpell(24275) +local FlashOfLight = SpellBook:GetSpell(19750) +local LightOfDawn = SpellBook:GetSpell(85222) +local DivineShield = SpellBook:GetSpell(642) +local LayOnHands = SpellBook:GetSpell(633) +local BlessingOfProtection = SpellBook:GetSpell(1022) +local BlessingOfSacrifice = SpellBook:GetSpell(6940) +local AvengingWrath = SpellBook:GetSpell(31884) + +-- Buffs +local DivineFavorBuff = SpellBook:GetSpell(388039) +local InfusionOfLightBuff = SpellBook:GetSpell(54149) +local ConsecrationBuff = SpellBook:GetSpell(188370) + +-- Custom Units +local Lowest = Bastion.UnitManager:CreateCustomUnit('lowest', function(unit) + local lowest = nil + local lowestHP = math.huge + + Bastion.UnitManager:EnumFriends(function(unit) + if unit:IsDead() or Player:GetDistance(unit) > 40 or not Player:CanSee(unit) then + return false + end + + local hp = unit:GetHP() + if hp < lowestHP then + lowest = unit + lowestHP = hp + end + end) + + return lowest or Player +end) + +local Tank = Bastion.UnitManager:CreateCustomUnit('tank', function(unit) + local tank = nil + + Bastion.UnitManager:EnumFriends(function(unit) + if Player:GetDistance(unit) > 40 or not Player:CanSee(unit) or unit:IsDead() then + return false + end + + if unit:IsTank() then + tank = unit + return true + end + end) + + return tank or Player +end) + +local BestTarget = Bastion.UnitManager:CreateCustomUnit('besttarget', function() + local bestMeleeTarget = nil + local bestRangedTarget = nil + local highestMeleeHealth = 0 + local highestRangedHealth = 0 + + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsAffectingCombat() and unit:GetDistance(Player) <= 40 and Player:CanSee(unit) then + local health = unit:GetHealth() + if unit:GetDistance(Player) <= MELEE_RANGE then + -- Melee target + if health > highestMeleeHealth then + highestMeleeHealth = health + bestMeleeTarget = unit + end + else + -- Ranged target + if health > highestRangedHealth then + highestRangedHealth = health + bestRangedTarget = unit + end + end + end + end) + + -- Return the best melee target if it exists, otherwise return the best ranged target + return bestMeleeTarget or bestRangedTarget or Target +end) + +local ExecuteTarget = Bastion.UnitManager:CreateCustomUnit('executetarget', function() + local target = nil + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsAffectingCombat() and unit:GetDistance(Player) <= 30 and Player:CanSee(unit) + and unit:GetHP() < 20 then + target = unit + return true + end + end) + return target or Bastion.UnitManager:Get('none') +end) + +-- Custom Functions + +local function IsInMeleeRange(unit) + return Player:GetDistance(unit) <= MELEE_RANGE +end + +-- Create APLs +local DefaultAPL = Bastion.APL:New('default') +local HealingAPL = Bastion.APL:New('healing') +local DamageAPL = Bastion.APL:New('damage') +local CooldownAPL = Bastion.APL:New('cooldown') +local DefensiveAPL = Bastion.APL:New('defensive') +local ExecuteAPL = Bastion.APL:New('execute') +local DamagePriorityAPL = Bastion.APL:New('damagepriority') + +-- Utility function to check if healing is needed +local function HealingNeeded() + return Lowest:GetHP() < healingThreshold +end + +-- Utility function to check if emergency healing is needed +local function EmergencyHealingNeeded() + return Lowest:GetHP() < 50 +end + +-- Utility function to check if Beacon of Virtue should be cast +local function ShouldCastBeaconOfVirtue() + local lowHealthCount = 0 + local totalPartyMembers = 0 + Bastion.UnitManager:EnumFriends(function(unit) + if unit:GetDistance(Player) <= 40 and Player:CanSee(unit) then + totalPartyMembers = totalPartyMembers + 1 + if unit:GetHP() < 80 then + lowHealthCount = lowHealthCount + 1 + end + end + end) + return lowHealthCount >= math.min(3, math.ceil(totalPartyMembers * 0.6)) +end + +-- Utility function to check if anyone in the party needs healing +local function AnyoneNeedsHealing() + local needsHealing = false + Bastion.UnitManager:EnumFriends(function(unit) + if unit:GetHP() < healingThreshold then + needsHealing = true + return true + end + end) + return needsHealing +end + +-- Variables for auto-attack tracking +local lastAutoAttackTime = 0 + +-- 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) + +-- Function to check if auto-attacking +local function IsAutoAttacking() + return GetTime() - lastAutoAttackTime < 2 +end + +-- Function to check if we're in an aggressive damage phase +local function AggressiveDamagePhase() + return Player:GetAuras():FindMy(AvengingWrath):IsUp() or + Player:GetPower(HOLY_POWER) >= 4 or + (Player:GetPartyHPAround(40, 90) == 0 and Player:GetHP() > 80) +end + +-- Healing APL +HealingAPL:AddSpell( + BeaconOfVirtue:CastableIf(function(self) + return self:IsKnownAndUsable() and + ShouldCastBeaconOfVirtue() and + Player:GetPower(HOLY_POWER) >= 3 and + not Player:IsCastingOrChanneling() + end):SetTarget(Lowest):OnCast(function() + WordOfGlory:ForceCast(Lowest) + end) +) + +HealingAPL:AddSpell( + HolyPrism:CastableIf(function(self) + return self:IsKnownAndUsable() and HealingNeeded() + end):SetTarget(Lowest) +) + +HealingAPL:AddSpell( + WordOfGlory:CastableIf(function(self) + return self:IsKnownAndUsable() and Player:GetPower(HOLY_POWER) >= 3 and HealingNeeded() + end):SetTarget(Lowest) +) + +HealingAPL:AddSpell( + HolyShock:CastableIf(function(self) + return self:IsKnownAndUsable() and HealingNeeded() + end):SetTarget(Lowest) +) + +HealingAPL:AddSpell( + LightOfDawn:CastableIf(function(self) + return self:IsKnownAndUsable() and Player:GetPower(HOLY_POWER) >= 3 and Player:GetPartyHPAround(15, healingThreshold) >= 3 + end):SetTarget(Player) +) + +HealingAPL:AddSpell( + FlashOfLight:CastableIf(function(self) + return self:IsKnownAndUsable() and (Player:GetAuras():FindMy(InfusionOfLightBuff):IsUp() or EmergencyHealingNeeded()) + end):SetTarget(Lowest) +) + +HealingAPL:AddSpell( + HolyLight:CastableIf(function(self) + return self:IsKnownAndUsable() and Player:GetAuras():FindMy(DivineFavorBuff):IsUp() and HealingNeeded() + end):SetTarget(Lowest) +) + +-- Damage APL +DamageAPL:AddSpell( + AutoAttack:CastableIf(function(self) + return BestTarget:Exists() and not IsAutoAttacking() and Player:CanSee(BestTarget) and Player:IsWithinCombatDistance(BestTarget, 5) + end):SetTarget(BestTarget):OnCast(function() + lastAutoAttackTime = GetTime() + end) +) + +DamageAPL:AddSpell( + Judgment:CastableIf(function(self) + return self:IsKnownAndUsable() and BestTarget:Exists() and Player:IsWithinCombatDistance(BestTarget, 30) and not Player:IsCastingOrChanneling() + end):SetTarget(BestTarget) +) + +DamageAPL:AddSpell( + HolyPrism:CastableIf(function(self) + return self:IsKnownAndUsable() and BestTarget:Exists() and Player:IsWithinCombatDistance(BestTarget, 30) and not Player:IsCastingOrChanneling() + end):SetTarget(BestTarget) +) + +DamageAPL:AddSpell( + CrusaderStrike:CastableIf(function(self) + return self:IsKnownAndUsable() and BestTarget:Exists() and IsInMeleeRange(BestTarget) and not Player:IsCastingOrChanneling() + end):SetTarget(BestTarget) +) + +-- Execute APL +ExecuteAPL:AddSpell( + HammerOfWrath:CastableIf(function(self) + return ExecuteTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + end):SetTarget(ExecuteTarget) +) + +-- Cooldown APL +CooldownAPL:AddSpell( + AvengingWrath:CastableIf(function(self) + return self:IsKnownAndUsable() and Player:GetPartyHPAround(40, 75) >= 3 + end):SetTarget(Player) +) + +CooldownAPL:AddSpell( + BlessingOfSummer:CastableIf(function(self) + return self:IsKnownAndUsable() and BestTarget:Exists() and Player:IsWithinCombatDistance(BestTarget, 30) and not Player:IsCastingOrChanneling() + end):SetTarget(Player) +) + +CooldownAPL:AddSpell( + SacredWeapon:CastableIf(function(self) + return self:IsKnownAndUsable() and BestTarget:Exists() and Player:IsWithinCombatDistance(BestTarget, 30) and not Player:IsCastingOrChanneling() + end):SetTarget(Player) +) + +CooldownAPL:AddSpell( + Consecration:CastableIf(function(self) + return self:IsKnownAndUsable() and + not Player:GetAuras():FindMy(ConsecrationBuff):IsUp() and + BestTarget:Exists() and + Player:IsWithinCombatDistance(BestTarget, 10) and + not Player:IsMoving() and + not Player:IsCastingOrChanneling() + end):SetTarget(Player) +) + +-- Defensive APL +DefensiveAPL:AddSpell( + DivineShield:CastableIf(function(self) + return self:IsKnownAndUsable() and Player:GetHP() < 20 + end):SetTarget(Player) +) + +DefensiveAPL:AddSpell( + LayOnHands:CastableIf(function(self) + return self:IsKnownAndUsable() and Lowest:GetHP() < 10 + end):SetTarget(Lowest) +) + +DefensiveAPL:AddSpell( + BlessingOfProtection:CastableIf(function(self) + return self:IsKnownAndUsable() and Lowest:GetHP() < 30 and not Lowest:IsTank() + end):SetTarget(Lowest) +) + +DefensiveAPL:AddSpell( + BlessingOfSacrifice:CastableIf(function(self) + return self:IsKnownAndUsable() and Tank:GetHP() < 50 and Player:GetHP() > 50 + end):SetTarget(Tank) +) + +-- Damage Priority APL +DamagePriorityAPL:AddSpell( + Judgment:CastableIf(function(self) + return self:IsKnownAndUsable() and BestTarget:Exists() and Player:IsWithinCombatDistance(BestTarget, 30) and not Player:IsCastingOrChanneling() + end):SetTarget(BestTarget) +) + +DamagePriorityAPL:AddSpell( + HolyShock:CastableIf(function(self) + return self:IsKnownAndUsable() and BestTarget:Exists() and Player:IsWithinCombatDistance(BestTarget, 40) and not Player:IsCastingOrChanneling() + end):SetTarget(BestTarget) +) + +DamagePriorityAPL:AddSpell( + CrusaderStrike:CastableIf(function(self) + return self:IsKnownAndUsable() and BestTarget:Exists() and IsInMeleeRange(BestTarget) and not Player:IsCastingOrChanneling() + end):SetTarget(BestTarget) +) + +DamagePriorityAPL:AddSpell( + ShieldOfTheRighteous:CastableIf(function(self) + return self:IsKnownAndUsable() and BestTarget:Exists() and IsInMeleeRange(BestTarget) and not Player:IsCastingOrChanneling() and Player:GetPower(HOLY_POWER) >= 3 and not HealingNeeded() + end):SetTarget(BestTarget) +) + +-- Default APL +DefaultAPL:AddAPL(DefensiveAPL, function() return true end) +DefaultAPL:AddAPL(CooldownAPL, function() return true end) +DefaultAPL:AddAPL(HealingAPL, function() return HealingNeeded() end) +DefaultAPL:AddAPL(ExecuteAPL, function() return true end) +DefaultAPL:AddAPL(DamagePriorityAPL, function() return not HealingNeeded() and AggressiveDamagePhase() end) +DefaultAPL:AddAPL(DamageAPL, function() return not HealingNeeded() end) + +HolyPaladinModule:Sync(function() + if Player:IsAffectingCombat() then + healingThreshold = AggressiveDamagePhase() and 85 or 90 + DefaultAPL:Execute() + else + healingThreshold = 100 + if AnyoneNeedsHealing() then + HealingAPL:Execute() + end + end +end) + +Bastion:Register(HolyPaladinModule) \ No newline at end of file