From 70d691eb67abd6df53a131e1c7d0e0165faa58fe Mon Sep 17 00:00:00 2001 From: Emlembow <36314674+Emlembow@users.noreply.github.com> Date: Wed, 18 Sep 2024 21:20:42 -0700 Subject: [PATCH] I give up, this is good enough --- RestoShaman.lua | 376 +++++++++++++++++++----------------------------- 1 file changed, 145 insertions(+), 231 deletions(-) diff --git a/RestoShaman.lua b/RestoShaman.lua index 83edb3c..825afac 100644 --- a/RestoShaman.lua +++ b/RestoShaman.lua @@ -3,52 +3,40 @@ local Tinkr, Bastion = ... local RestoShamanModule = Bastion.Module:New('RestoShaman') local Player = Bastion.UnitManager:Get('player') local Target = Bastion.UnitManager:Get('target') - --- Initialize SpellBook local SpellBook = Bastion.SpellBook:New() -- Spells local WaterShield = SpellBook:GetSpell(52127) local EarthShield = SpellBook:GetSpell(974) -local HealingRain = SpellBook:GetSpell(73920) local Riptide = SpellBook:GetSpell(61295) -local CloudburstTotem = SpellBook:GetSpell(157153) -local HealingStreamTotem = SpellBook:GetSpell(5394) -local PrimordialWave = SpellBook:GetSpell(375982) -local UnleashLife = SpellBook:GetSpell(73685) -local Downpour = SpellBook:GetSpell(207778) -local ChainHeal = SpellBook:GetSpell(1064) -local HealingWave = SpellBook:GetSpell(77472) local HealingSurge = SpellBook:GetSpell(8004) -local Wellspring = SpellBook:GetSpell(197995) +local HealingWave = SpellBook:GetSpell(77472) +local ChainHeal = SpellBook:GetSpell(1064) +local HealingRain = SpellBook:GetSpell(73920) +local Downpour = SpellBook:GetSpell(207778) +local HealingStreamTotem = SpellBook:GetSpell(5394) +local CloudburstTotem = SpellBook:GetSpell(157153) local SpiritLinkTotem = SpellBook:GetSpell(98008) -local HealingTideTotem = SpellBook:GetSpell(108280) local AncestralGuidance = SpellBook:GetSpell(108281) -local ManaTideTotem = SpellBook:GetSpell(16191) local Ascendance = SpellBook:GetSpell(114052) -local NaturesSwiftness = SpellBook:GetSpell(378081) -local EarthenWallTotem = SpellBook:GetSpell(198838) -local PurifySpirit = SpellBook:GetSpell(77130) -local LightningBolt = SpellBook:GetSpell(403) -local ChainLightning = SpellBook:GetSpell(421) -local FlameShock = SpellBook:GetSpell(188389) +local Wellspring = SpellBook:GetSpell(197995) +local UnleashLife = SpellBook:GetSpell(73685) +local PrimordialWave = SpellBook:GetSpell(375982) local LavaBurst = SpellBook:GetSpell(51505) +local FlameShock = SpellBook:GetSpell(188389) +local WindShear = SpellBook:GetSpell(57994) +local LightningBolt = SpellBook:GetSpell(403) -- Buffs local TidalWaves = SpellBook:GetSpell(53390) -local HighTide = SpellBook:GetSpell(288675) - --- Totem timers -local cloudburstTotemEnd = 0 -local healingStreamTotemEnd = 0 -local earthenWallTotemEnd = 0 -local spiritLinkTotemEnd = 0 -local healingTideTotemEnd = 0 +local WaterShieldBuff = SpellBook:GetSpell(52127) +local EarthShieldBuff = SpellBook:GetSpell(383648) +local UnleashLifeBuff = SpellBook:GetSpell(73685) -- Custom Units -local Lowest = Bastion.UnitManager:CreateCustomUnit('lowest', function(unit) +local Lowest = Bastion.UnitManager:CreateCustomUnit('lowest', function() local lowest = nil - local lowestHP = math.huge + local lowestHP = 100 Bastion.UnitManager:EnumFriends(function(unit) if unit:IsDead() or Player:GetDistance(unit) > 40 or not Player:CanSee(unit) then @@ -65,285 +53,211 @@ local Lowest = Bastion.UnitManager:CreateCustomUnit('lowest', function(unit) return lowest or Player end) --- APLs -local PrePullAPL = Bastion.APL:New('prepull') -local DefaultAPL = Bastion.APL:New('default') -local CooldownAPL = Bastion.APL:New('cooldown') -local DamageAPL = Bastion.APL:New('damage') - --- Debug function -local function Debug(message) - -- print("[RestoShaman Debug]: " .. message) -end - --- Helper Functions -local function IsTotemActive(totemEnd) - return GetTime() < totemEnd -end - -local function UpdateTotemTimer(totemEnd, duration) - return GetTime() + duration -end - -local function ShouldUseCloudburstTotem() - return not IsTotemActive(cloudburstTotemEnd) and (Player:GetEnemies(10) >= 3 or Player:GetPartyHPAround(30, 80) >= 3) -end - -local function ShouldUseHealingStreamTotem() - return not IsTotemActive(healingStreamTotemEnd) and not CloudburstTotem:IsKnownAndUsable() -end - -local function ShouldUseEarthenWallTotem() - return not IsTotemActive(earthenWallTotemEnd) and Player:GetPartyHPAround(40, 80) >= 3 -end - -local function ShouldUseSpiritLinkTotem() - return not IsTotemActive(spiritLinkTotemEnd) and Player:GetPartyHPAround(40, 60) >= 3 -end - -local function ShouldUseHealingTideTotem() - return not IsTotemActive(healingTideTotemEnd) and Player:GetPartyHPAround(40, 70) >= 3 -end - -local function GetRiptideCount() - local count = 0 - Bastion.UnitManager:EnumFriends(function(unit) - if unit:GetAuras():FindMy(Riptide):IsUp() then - count = count + 1 +local BestTarget = Bastion.UnitManager:CreateCustomUnit('besttarget', function() + local bestTarget = nil + local highestHP = 0 + + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsAffectingCombat() and Player:IsWithinCombatDistance(unit, 40) and Player:CanSee(unit) and Player:IsFacing(unit) then + local hp = unit:GetHP() + if hp > highestHP then + bestTarget = unit + highestHP = hp + end end end) - return count -end -local function ShouldUseAscendance() - return Player:GetPartyHPAround(40, 70) >= 3 and CloudburstTotem:GetTimeSinceLastCast() < 3 -end + return bestTarget or Target +end) -local function GetTanks() - local tanks = {} - Bastion.UnitManager:EnumFriends(function(unit) - if unit:IsTank() and not unit:IsDead() and Player:CanSee(unit) and Player:GetDistance(unit) <= 40 then - table.insert(tanks, unit) - end - end) - Debug("Found " .. #tanks .. " tanks") - return tanks -end +-- APLs +local DefaultAPL = Bastion.APL:New('default') +local CooldownAPL = Bastion.APL:New('cooldown') +local DefensiveAPL = Bastion.APL:New('defensive') +local DpsAPL = Bastion.APL:New('dps') +local InterruptAPL = Bastion.APL:New('interrupt') -local function ApplyEarthShield() - local tanks = GetTanks() - for _, tank in ipairs(tanks) do - if not tank:GetAuras():FindMy(EarthShield):IsUp() then - Debug("Applying Earth Shield to " .. tank:GetName()) - return EarthShield:Cast(tank) - end - end - Debug("No tanks need Earth Shield") +-- Helper Functions +local function GetPlayerManaPercent() + return (UnitPower("player", Enum.PowerType.Mana) / UnitPowerMax("player", Enum.PowerType.Mana)) * 100 end -local function NeedsHealing(unit, threshold) - return unit:GetHP() < threshold +local function NeedsUrgentHealing() + return Lowest:GetHP() < 70 or Player:GetPartyHPAround(30, 80) >= 3 end --- Pre-Pull APL -PrePullAPL:AddSpell( - WaterShield:CastableIf(function(self) - return Player:Exists() and self:IsKnownAndUsable() and not Player:GetAuras():FindMy(WaterShield):IsUp() +-- Modify the EarthShield spell in the DefaultAPL +DefaultAPL:AddSpell( + EarthShield:CastableIf(function(self) + local playerShield = Player:GetAuras():FindMy(EarthShieldBuff) + return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and + (not playerShield:IsUp() or playerShield:GetRemainingTime() < 30) end):SetTarget(Player) ) -- Default APL -DefaultAPL:AddSpell( - CloudburstTotem:CastableIf(function(self) - return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and ShouldUseCloudburstTotem() - end):SetTarget(Player):OnCast(function() - cloudburstTotemEnd = UpdateTotemTimer(cloudburstTotemEnd, 15) - end) -) - -DefaultAPL:AddSpell( - HealingStreamTotem:CastableIf(function(self) - return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and ShouldUseHealingStreamTotem() - end):SetTarget(Player):OnCast(function() - healingStreamTotemEnd = UpdateTotemTimer(healingStreamTotemEnd, 15) - end) -) - DefaultAPL:AddSpell( Riptide:CastableIf(function(self) return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and NeedsHealing(Lowest, 90) - end):SetTarget(Lowest) -) - -DefaultAPL:AddSpell( - PrimordialWave:CastableIf(function(self) - return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and NeedsHealing(Lowest, 85) + and Lowest:GetHP() < 95 and not Lowest:GetAuras():FindMy(TidalWaves):IsUp() end):SetTarget(Lowest) ) DefaultAPL:AddSpell( UnleashLife:CastableIf(function(self) return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and NeedsHealing(Lowest, 80) + and Lowest:GetHP() < 80 end):SetTarget(Lowest) ) DefaultAPL:AddSpell( - HealingRain:CastableIf(function(self) - return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and Player:GetPartyHPAround(30, 85) >= 6 and not Player:IsMoving() - end):SetTarget(Player) -) - -DefaultAPL:AddSpell( - Downpour:CastableIf(function(self) - return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and Player:GetPartyHPAround(30, 80) >= 3 - end):SetTarget(Player) -) - -DefaultAPL:AddSpell( - ChainHeal:CastableIf(function(self) + HealingSurge:CastableIf(function(self) return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and (Player:GetAuras():FindMy(HighTide):IsUp() or Player:GetAuras():FindMy(NaturesSwiftness):IsUp()) - and not Player:IsMoving() and NeedsHealing(Lowest, 75) + and Lowest:GetHP() < 70 and Player:GetAuras():FindMy(TidalWaves):IsUp() end):SetTarget(Lowest) ) DefaultAPL:AddSpell( HealingWave:CastableIf(function(self) return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and Player:GetAuras():FindMy(TidalWaves):IsUp() and not Player:IsMoving() - and NeedsHealing(Lowest, 85) - end):SetTarget(Lowest) -) - -DefaultAPL:AddSpell( - HealingSurge:CastableIf(function(self) - return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and NeedsHealing(Lowest, 70) + and Lowest:GetHP() < 85 end):SetTarget(Lowest) ) DefaultAPL:AddSpell( - PurifySpirit:CastableIf(function(self) - return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and Lowest:GetAuras():HasAnyDispelableAura(PurifySpirit) + ChainHeal:CastableIf(function(self) + return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:GetPartyHPAround(40, 85) >= 3 end):SetTarget(Lowest) ) -- Cooldown APL CooldownAPL:AddSpell( - SpiritLinkTotem:CastableIf(function(self) - return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and ShouldUseSpiritLinkTotem() - end):SetTarget(Player):OnCast(function() - spiritLinkTotemEnd = UpdateTotemTimer(spiritLinkTotemEnd, 6) - end) -) - -CooldownAPL:AddSpell( - HealingTideTotem:CastableIf(function(self) - return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and ShouldUseHealingTideTotem() - end):SetTarget(Player):OnCast(function() - healingTideTotemEnd = UpdateTotemTimer(healingTideTotemEnd, 10) - end) -) - -CooldownAPL:AddSpell( - AncestralGuidance:CastableIf(function(self) - return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and Player:GetPartyHPAround(40, 75) >= 3 + CloudburstTotem:CastableIf(function(self) + return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:GetPartyHPAround(40, 80) >= 3 end):SetTarget(Player) ) CooldownAPL:AddSpell( - ManaTideTotem:CastableIf(function(self) - return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and Player:GetPP() <= 80 + HealingStreamTotem:CastableIf(function(self) + return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and not CloudburstTotem:IsKnownAndUsable() and Player:GetPartyHPAround(40, 90) >= 2 end):SetTarget(Player) ) CooldownAPL:AddSpell( - Ascendance:CastableIf(function(self) - return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and ShouldUseAscendance() + SpiritLinkTotem:CastableIf(function(self) + return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:GetPartyHPAround(30, 70) >= 3 end):SetTarget(Player) ) CooldownAPL:AddSpell( - NaturesSwiftness:CastableIf(function(self) - return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and NeedsHealing(Lowest, 50) - end):SetTarget(Lowest) -) - -CooldownAPL:AddSpell( - EarthenWallTotem:CastableIf(function(self) - return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and ShouldUseEarthenWallTotem() - end):SetTarget(Player):OnCast(function() - earthenWallTotemEnd = UpdateTotemTimer(earthenWallTotemEnd, 15) - end) + AncestralGuidance:CastableIf(function(self) + return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:GetPartyHPAround(40, 75) >= 3 + end):SetTarget(Player) ) CooldownAPL:AddSpell( - Wellspring:CastableIf(function(self) - return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and Player:GetPartyHPAround(30, 85) >= 3 + Ascendance:CastableIf(function(self) + return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:GetPartyHPAround(40, 60) >= 3 end):SetTarget(Player) ) --- Damage APL -DamageAPL:AddSpell( +-- DPS APL +DpsAPL:AddSpell( FlameShock:CastableIf(function(self) - return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and not Target:GetAuras():FindMy(FlameShock):IsUp() - end):SetTarget(Target) + return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and not BestTarget:GetAuras():FindMy(FlameShock):IsUp() + end):SetTarget(BestTarget) ) -DamageAPL:AddSpell( +DpsAPL:AddSpell( LavaBurst:CastableIf(function(self) - return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and Target:GetAuras():FindMy(FlameShock):IsUp() - end):SetTarget(Target) + return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and BestTarget:GetAuras():FindMy(FlameShock):IsUp() + end):SetTarget(BestTarget) ) -DamageAPL:AddSpell( - ChainLightning:CastableIf(function(self) - return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and Player:GetEnemies(10) > 2 - end):SetTarget(Target) +DpsAPL:AddSpell( + LightningBolt:CastableIf(function(self) + return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + end):SetTarget(BestTarget) ) -DamageAPL:AddSpell( - LightningBolt:CastableIf(function(self) - return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - end):SetTarget(Target) +-- Interrupt APL +InterruptAPL:AddSpell( + WindShear:CastableIf(function(self) + return BestTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and BestTarget:IsCasting() and BestTarget:IsInterruptible() + and BestTarget:GetChannelOrCastPercentComplete() >= 50 + end):SetTarget(BestTarget) ) --- Module Sync +-- Modify the out of combat Earth Shield logic in the Module Sync function RestoShamanModule:Sync(function() + if Player:IsMounted() then + return + end + + -- Out of combat healing and buffs if not Player:IsAffectingCombat() then - -- Reset totem timers when leaving combat - cloudburstTotemEnd = 0 - healingStreamTotemEnd = 0 - earthenWallTotemEnd = 0 - spiritLinkTotemEnd = 0 - healingTideTotemEnd = 0 - - if not Player:GetAuras():FindMy(WaterShield):IsUp() then + if not Player:GetAuras():FindMy(WaterShieldBuff):IsUp() then WaterShield:Cast(Player) + elseif not Player:GetAuras():FindMy(EarthShieldBuff):IsUp() then + EarthShield:Cast(Player) + elseif Lowest:GetHP() < 90 then + DefaultAPL:Execute() end - ApplyEarthShield() - elseif Player:IsAffectingCombat() then - ApplyEarthShield() + return + end + + -- In combat + InterruptAPL:Execute() + + if NeedsUrgentHealing() then CooldownAPL:Execute() DefaultAPL:Execute() + else + if GetPlayerManaPercent() > 80 and BestTarget:Exists() then + DpsAPL:Execute() + end + + if Riptide:GetCharges() >= 2 then + Bastion.UnitManager:EnumFriends(function(unit) + if unit:GetHP() < 95 and not unit:GetAuras():FindMy(TidalWaves):IsUp() then + Riptide:Cast(unit) + return true -- Stop enumerating after casting + end + end) + end - -- Use damage abilities when healing isn't needed - if Player:GetPP() > 80 and Player:GetPartyHPAround(40, 95) == 0 then - DamageAPL:Execute() + if HealingWave:IsKnownAndUsable() and Lowest:GetHP() < 90 then + HealingWave:Cast(Lowest) + end + end + + -- AoE Healing + if Player:GetPartyHPAround(30, 85) >= 4 then + if HealingRain:IsKnownAndUsable() then + local loc = Bastion.UnitManager:FindFriendsCentroid(10, 40) + if loc then + HealingRain:Cast(Player):OnCast(function(self) + self:Click(loc) + end) + end + elseif Downpour:IsKnownAndUsable() then + local loc = Bastion.UnitManager:FindFriendsCentroid(10, 40) + if loc then + Downpour:Cast(Player):OnCast(function(self) + self:Click(loc) + end) + end + elseif Wellspring:IsKnownAndUsable() then + Wellspring:Cast(Player) end end end)