diff --git a/scripts/restodruid.lua b/scripts/restodruid.lua index 69b0753..83e84d4 100644 --- a/scripts/restodruid.lua +++ b/scripts/restodruid.lua @@ -1,7 +1,7 @@ local Tinkr, Bastion = ... local RestoModule = Bastion.Module:New('resto_druid') - +local Evaluator = Tinkr.Util.Evaluator local Player = Bastion.UnitManager:Get('player') local None = Bastion.UnitManager:Get('none') local Target = Bastion.UnitManager:Get('target') @@ -177,7 +177,7 @@ end) local PurgeTarget = Bastion.UnitManager:CreateCustomUnit('purge', function(unit) local purge = nil - Bastion.UnitManager:EnumEnemies(function(unit) + Bastion.UnitManager:EnumNameplates(function(unit) if unit:IsDead() then return false end @@ -193,6 +193,7 @@ local PurgeTarget = Bastion.UnitManager:CreateCustomUnit('purge', function(unit) if not unit:IsDead() and Player:CanSee(unit) and unit:GetAuras():HasAnyStealableAura() then purge = unit + return true end end) @@ -317,6 +318,48 @@ local SwiftmendUnit = Bastion.UnitManager:CreateCustomUnit('swiftmend', function return lowest end) +local WildGrowthUnit = Bastion.UnitManager:CreateCustomUnit('wildgrowth', function(unit) + local lowest = nil + local lowestHP = math.huge + + Bastion.UnitManager:EnumFriends(function(unit) + if unit:IsDead() then + return false + end + + if not Player:CanSee(unit) then + return false + end + + if Player:GetDistance(unit) > 40 then + return false + end + + if Player:CanSee(unit) and ( + ( + Player:GetAuras():FindMy(SoulOfTheForest):IsUp() and + ( + Player:GetAuras():FindMy(SoulOfTheForest):GetRemainingTime() <= 5 or + unit:GetPartyHPAround(30, 90) >= 2)) or + (unit:GetPartyHPAround(30, 90) >= 3 or unit:GetPartyHPAround(30, 85) >= 2)) + then + local hp = unit:GetHP() + if hp < lowestHP then + lowest = unit + lowestHP = hp + end + end + + end) + + + if lowest == nil then + lowest = None + end + + return lowest +end) + local RestoCommands = Bastion.Command:New('resto') local PLACE_EFFLO = false @@ -385,7 +428,14 @@ DefaultAPL:AddSpell( return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and self:IsInRange(Player) and (Player:GetPartyHPAround(40, 65) >= 2 or Player:GetPartyHPAround(40, 70) >= 3) and (not ConvokeTheSpirits:IsKnownAndUsable() and ConvokeTheSpirits:GetTimeSinceLastCast() > 7) and - WildGrowth:GetTimeSinceLastCast() <= 4 + WildGrowth:GetTimeSinceLastCast() <= 6 + end):SetTarget(Player) +) + +DefaultAPL:AddSpell( + NaturesVigil:CastableIf(function(self) + return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and + self:IsInRange(Player) and Flourish:GetTimeSinceLastCast() <= 5 end):SetTarget(Player) ) @@ -411,22 +461,23 @@ DefaultAPL:AddSpell( DefaultAPL:AddSpell( WildGrowth:CastableIf(function(self) - return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and Player:CanSee(Lowest) and + return WildGrowthUnit:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(WildGrowthUnit) and ( ( Player:GetAuras():FindMy(SoulOfTheForest):IsUp() and ( Player:GetAuras():FindMy(SoulOfTheForest):GetRemainingTime() <= 5 or - Lowest:GetPartyHPAround(30, 90) >= 2)) or - (Lowest:GetPartyHPAround(30, 90) >= 3 or Lowest:GetPartyHPAround(30, 85) >= 2)) and not Player:IsMoving() - end):SetTarget(Lowest) + WildGrowthUnit:GetPartyHPAround(30, 90) >= 2)) or + (WildGrowthUnit:GetPartyHPAround(30, 90) >= 3 or WildGrowthUnit:GetPartyHPAround(30, 85) >= 2)) and + not Player:IsMoving() + end):SetTarget(WildGrowthUnit) ) DefaultAPL:AddSpell( Regrowth:CastableIf(function(self) return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and Player:CanSee(Lowest) and Lowest:GetHP() < 50 and + and Player:CanSee(Lowest) and Lowest:GetHP() < 65 and Player:GetAuras():FindMy(SoulOfTheForest):IsUp() and not Player:IsMoving() end):SetTarget(Lowest) ) @@ -456,6 +507,26 @@ DefaultAPL:AddSpell( end):SetTarget(Lowest) ) +DefaultAPL:AddSpell( + Rejuvenation:CastableIf(function(self) + return RejuvUnit:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(RejuvUnit) and RejuvUnit:GetHP() <= 94 and + not Player:GetAuras():FindMy(SoulOfTheForest):IsUp() + end):SetTarget(RejuvUnit) +) + +DefaultAPL:AddSpell( + Regrowth:CastableIf(function(self) + return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() + and Player:CanSee(Lowest) and + ( + not Player:GetAuras():FindMy(Regrowth):IsUp() and Lowest:GetHP() < 70 or + (Lowest:GetHP() <= 85 and Player:GetAuras():FindMy(ClearCasting):IsUp())) and + not Player:GetAuras():FindMy(SoulOfTheForest):IsUp() and + not Player:IsMoving() + end):SetTarget(Lowest) +) + DefaultAPL:AddSpell( Lifebloom:CastableIf(function(self) return Player:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() @@ -476,24 +547,6 @@ DefaultAPL:AddSpell( end):SetTarget(Tank) ) -DefaultAPL:AddSpell( - Rejuvenation:CastableIf(function(self) - return RejuvUnit:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and Player:CanSee(RejuvUnit) and RejuvUnit:GetHP() <= 94 and - not Player:GetAuras():FindMy(SoulOfTheForest):IsUp() - end):SetTarget(RejuvUnit) -) - -DefaultAPL:AddSpell( - Regrowth:CastableIf(function(self) - return Lowest:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() - and Player:CanSee(Lowest) and - (Lowest:GetHP() < 70 or (Lowest:GetHP() <= 85 and Player:GetAuras():FindMy(ClearCasting):IsUp())) and - not Player:GetAuras():FindMy(Regrowth):IsUp() and not Player:GetAuras():FindMy(SoulOfTheForest):IsUp() and - not Player:IsMoving() - end):SetTarget(Lowest) -) - DefaultAPL:AddSpell( Sunfire:CastableIf(function(self) return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() diff --git a/src/Aura/Aura.lua b/src/Aura/Aura.lua index d81a4ea..1757bfe 100644 --- a/src/Aura/Aura.lua +++ b/src/Aura/Aura.lua @@ -69,6 +69,7 @@ function Aura:New(unit, index, type) castByPlayer = castByPlayer, nameplateShowAll = nameplateShowAll, timeMod = timeMod, + auraInstanceID = nil, index = index, type = type, @@ -94,6 +95,7 @@ function Aura:CreateFromUnitAuraInfo(unitAuraInfo) castByPlayer = unitAuraInfo.isFromPlayerOrPlayerPet, nameplateShowAll = unitAuraInfo.nameplateShowAll, timeMod = unitAuraInfo.timeMod, + auraInstanceID = unitAuraInfo.auraInstanceID, index = nil, type = unitAuraInfo.isHarmful and "HARMFUL" or "HELPFUL", @@ -111,6 +113,11 @@ function Aura:IsUp() return self:IsValid() and (self:GetDuration() == 0 or self:GetRemainingTime() > 0) end +-- Check if the aura is down +function Aura:IsDown() + return not self:IsUp() +end + -- Get the auras index function Aura:GetIndex() return self.aura.index @@ -217,6 +224,11 @@ function Aura:IsDebuff() return self.aura.type == "HARMFUL" end +-- Get aura instance id +function Aura:GetAuraInstanceID() + return self.aura.auraInstanceID +end + -- Check if the aura is dispelable by a spell function Aura:IsDispelableBySpell(spell) if self:GetDispelType() == nil then diff --git a/src/AuraTable/AuraTable.lua b/src/AuraTable/AuraTable.lua index 09ac386..9ec4cf6 100644 --- a/src/AuraTable/AuraTable.lua +++ b/src/AuraTable/AuraTable.lua @@ -4,6 +4,14 @@ local Tinkr, Bastion = ... local AuraTable = {} AuraTable.__index = AuraTable +local function tsize(t) + local keys = {} + for k, v in pairs(t) do + table.insert(keys, k) + end + return keys[#keys] +end + -- Constructor function AuraTable:New(unit) local self = setmetatable({}, AuraTable) @@ -18,42 +26,84 @@ function AuraTable:New(unit) self.playerAppliedDebuffs = {} self.playerAuras = {} self.guid = unit:GetGUID() + self.instanceIDLookup = {} Bastion.EventManager:RegisterWoWEvent('UNIT_AURA', function(unit, auras) local u = Bastion.UnitManager[unit] - if not self.unit:IsUnit(unit) then + if not self.unit:IsUnit(u) then + return + end + + + local isFullUpdate = auras.isFullUpdate + + if isFullUpdate then + self:Update() + print("Full update requested for " .. unit) return end local addedAuras = auras.addedAuras + local removedAuras = auras.removedAuraInstanceIDs + local updatedAuras = auras.updatedAuraInstanceIDs + + -- DevTools_Dump(addedAuras) + if updatedAuras and #updatedAuras > 0 then + for i = 1, #updatedAuras do + local id = updatedAuras[i] + local newAura = C_UnitAuras.GetAuraDataByAuraInstanceID(unit, id); + if newAura then + local aura = Bastion.Aura:CreateFromUnitAuraInfo(newAura) + self:UpdateInstanceID(id, aura) + else + print("Instance ID " .. id .. " not found" .. " for unit " .. unit) + end + end + end + + -- Remove auras + if removedAuras and #removedAuras > 0 then + for i = 1, #removedAuras do + self:RemoveInstanceID(removedAuras[i]) + end + end - if #addedAuras > 0 then + -- Add auras + if addedAuras and #addedAuras > 0 then for i = 1, #addedAuras do local aura = Bastion.Aura:CreateFromUnitAuraInfo(addedAuras[i]) + if aura:IsBuff() then if aura:GetSource():Exists() and aura:GetSource():IsUnit(Bastion.UnitManager['player']) then if not self.playerAppliedBuffs[aura:GetSpell():GetID()] then self.playerAppliedBuffs[aura:GetSpell():GetID()] = {} end - table.insert(self.playerAppliedBuffs[aura:GetSpell():GetID()], aura) + self.playerAppliedBuffs[aura:GetSpell():GetID()][aura:GetAuraInstanceID()] = aura + self.instanceIDLookup[aura:GetAuraInstanceID()] = { 'playerAppliedBuffs', + aura:GetSpell():GetID() } else if not self.buffs[aura:GetSpell():GetID()] then self.buffs[aura:GetSpell():GetID()] = {} end - table.insert(self.buffs[aura:GetSpell():GetID()], aura) + self.buffs[aura:GetSpell():GetID()][aura:GetAuraInstanceID()] = aura + self.instanceIDLookup[aura:GetAuraInstanceID()] = { 'buffs', + aura:GetSpell():GetID() } end else if aura:GetSource():Exists() and aura:GetSource():IsUnit(Bastion.UnitManager['player']) then if not self.playerAppliedDebuffs[aura:GetSpell():GetID()] then self.playerAppliedDebuffs[aura:GetSpell():GetID()] = {} end - table.insert(self.playerAppliedDebuffs[aura:GetSpell():GetID()], aura) + self.playerAppliedDebuffs[aura:GetSpell():GetID()][aura:GetAuraInstanceID()] = aura + self.instanceIDLookup[aura:GetAuraInstanceID()] = { 'playerAppliedDebuffs', + aura:GetSpell():GetID() } else if not self.debuffs[aura:GetSpell():GetID()] then self.debuffs[aura:GetSpell():GetID()] = {} end - table.insert(self.debuffs[aura:GetSpell():GetID()], aura) + self.debuffs[aura:GetSpell():GetID()][aura:GetAuraInstanceID()] = aura + self.instanceIDLookup[aura:GetAuraInstanceID()] = { 'debuffs', aura:GetSpell():GetID() } end end end @@ -63,44 +113,84 @@ function AuraTable:New(unit) return self end +function AuraTable:RemoveInstanceID(instanceID) + if not self.instanceIDLookup[instanceID] then + return + end + + local t, id = unpack(self.instanceIDLookup[instanceID]) + + -- print("Removing aura from table: " .. t .. " " .. id .. " " .. index .. "") + local a = self[t][id][instanceID] + + if a:GetAuraInstanceID() ~= instanceID then + print("Instance ID mismatch: " .. a:GetAuraInstanceID() .. " " .. instanceID) + end + + self[t][id][instanceID] = nil + self.instanceIDLookup[instanceID] = nil +end + +function AuraTable:UpdateInstanceID(instanceID, newAura) + if not self.instanceIDLookup[instanceID] then + return + end + + local t, id = unpack(self.instanceIDLookup[instanceID]) + + -- print("Updating aura in table: " .. t .. " " .. id .. " " .. index .. "") + + self[t][id][instanceID] = newAura +end + -- Get a units buffs function AuraTable:GetUnitBuffs() - for i = 1, 40 do - local aura = Bastion.Aura:New(self.unit, i, 'HELPFUL') + AuraUtil.ForEachAura(self.unit.unit, 'HELPFUL', nil, function(a) + local aura = Bastion.Aura:CreateFromUnitAuraInfo(a) + if aura:IsValid() then if aura:GetSource():Exists() and aura:GetSource():IsUnit(Bastion.UnitManager['player']) then if not self.playerAppliedBuffs[aura:GetSpell():GetID()] then self.playerAppliedBuffs[aura:GetSpell():GetID()] = {} end - table.insert(self.playerAppliedBuffs[aura:GetSpell():GetID()], aura) + self.playerAppliedBuffs[aura:GetSpell():GetID()][aura:GetAuraInstanceID()] = aura + self.instanceIDLookup[aura:GetAuraInstanceID()] = { 'playerAppliedBuffs', + aura:GetSpell():GetID() } else if not self.buffs[aura:GetSpell():GetID()] then self.buffs[aura:GetSpell():GetID()] = {} end - table.insert(self.buffs[aura:GetSpell():GetID()], aura) + self.buffs[aura:GetSpell():GetID()][aura:GetAuraInstanceID()] = aura + self.instanceIDLookup[aura:GetAuraInstanceID()] = { 'buffs', + aura:GetSpell():GetID() } end end - end + end, true) end -- Get a units debuffs function AuraTable:GetUnitDebuffs() - for i = 1, 40 do - local aura = Bastion.Aura:New(self.unit, i, 'HARMFUL') + AuraUtil.ForEachAura(self.unit.unit, 'HARMFUL', nil, function(a) + local aura = Bastion.Aura:CreateFromUnitAuraInfo(a) + if aura:IsValid() then if aura:GetSource():Exists() and aura:GetSource():IsUnit(Bastion.UnitManager['player']) then if not self.playerAppliedDebuffs[aura:GetSpell():GetID()] then self.playerAppliedDebuffs[aura:GetSpell():GetID()] = {} end - table.insert(self.playerAppliedDebuffs[aura:GetSpell():GetID()], aura) + self.playerAppliedDebuffs[aura:GetSpell():GetID()][aura:GetAuraInstanceID()] = aura + self.instanceIDLookup[aura:GetAuraInstanceID()] = { 'playerAppliedDebuffs', + aura:GetSpell():GetID() } else if not self.debuffs[aura:GetSpell():GetID()] then self.debuffs[aura:GetSpell():GetID()] = {} end - table.insert(self.debuffs[aura:GetSpell():GetID()], aura) + self.debuffs[aura:GetSpell():GetID()][aura:GetAuraInstanceID()] = aura + self.instanceIDLookup[aura:GetAuraInstanceID()] = { 'debuffs', + aura:GetSpell():GetID() } end end - end + end, true) end local function merge(t1, t2) @@ -121,50 +211,59 @@ end -- Update auras function AuraTable:Update() self:Clear() - self.lastUpdate = GetTime() + -- self.lastUpdate = GetTime() self:GetUnitBuffs() self:GetUnitDebuffs() - self.auras = merge(self.buffs, self.debuffs) - self.playerAuras = merge(self.playerAppliedBuffs, self.playerAppliedDebuffs) + + -- self.auras = merge(self.buffs, self.debuffs) + -- self.playerAuras = merge(self.playerAppliedBuffs, self.playerAppliedDebuffs) end -- Get a units auras function AuraTable:GetUnitAuras() + if not self.did then + self.did = true + self:Update() + end -- For token units, we need to check if the GUID has changed if self.unit:GetGUID() ~= self.guid then self.guid = self.unit:GetGUID() self:Update() - return self.auras + return merge(self.buffs, self.debuffs) end - -- Cache the auras for the unit so we don't have to query the API every time we want to check if the unit has a specific aura or not - -- If it's less than .4 seconds since the last time we queried the API, return the cached auras - if self.lastUpdate and GetTime() - self.lastUpdate < 0.5 then - return self.auras - end + -- -- Cache the auras for the unit so we don't have to query the API every time we want to check if the unit has a specific aura or not + -- -- If it's less than .4 seconds since the last time we queried the API, return the cached auras + -- if self.lastUpdate and GetTime() - self.lastUpdate < 0.5 then + -- return merge(self.buffs, self.debuffs) + -- end - self:Update() - return self.auras + -- self:Update() + return merge(self.buffs, self.debuffs) end -- Get a units auras function AuraTable:GetMyUnitAuras() + if not self.did then + self.did = true + self:Update() + end -- For token units, we need to check if the GUID has changed if self.unit:GetGUID() ~= self.guid then self.guid = self.unit:GetGUID() self:Update() - return self.playerAuras + return merge(self.playerAppliedBuffs, self.playerAppliedDebuffs) end - -- Cache the auras for the unit so we don't have to query the API every time we want to check if the unit has a specific aura or not - -- If it's less than .4 seconds since the last time we queried the API, return the cached auras - if self.lastUpdate and GetTime() - self.lastUpdate < 0.5 then - return self.playerAuras - end + -- -- Cache the auras for the unit so we don't have to query the API every time we want to check if the unit has a specific aura or not + -- -- If it's less than .4 seconds since the last time we queried the API, return the cached auras + -- if self.lastUpdate and GetTime() - self.lastUpdate < 0.5 then + -- return merge(self.playerAppliedBuffs, self.playerAppliedDebuffs) + -- end - self:Update() - return self.playerAuras + -- self:Update() + return merge(self.playerAppliedBuffs, self.playerAppliedDebuffs) end -- Clear the aura table @@ -175,15 +274,26 @@ function AuraTable:Clear() self.playerAppliedBuffs = {} self.playerAppliedDebuffs = {} self.playerAuras = {} + self.instanceIDLookup = {} end -- Check if the unit has a specific aura function AuraTable:Find(spell) local auras = self:GetUnitAuras() - local aura = auras[spell:GetID()] + local aurasub = auras[spell:GetID()] - if aura then - return aura[1] + if not aurasub then + return Bastion.Aura:New() + end + + for k, a in pairs(aurasub) do + if a ~= nil then + if a:IsUp() then -- Handle expired and non refreshed dropoffs not coming in UNIT_AURA + return a + else + self:RemoveInstanceID(a:GetAuraInstanceID()) + end + end end return Bastion.Aura:New() @@ -191,10 +301,20 @@ end function AuraTable:FindMy(spell) local auras = self:GetMyUnitAuras() - local aura = auras[spell:GetID()] + local aurasub = auras[spell:GetID()] - if aura then - return aura[1] + if not aurasub then + return Bastion.Aura:New() + end + + for k, a in pairs(aurasub) do + if a ~= nil then + if a:IsUp() then -- Handle expired and non refreshed dropoffs not coming in UNIT_AURA + return a + else + self:RemoveInstanceID(a:GetAuraInstanceID()) + end + end end return Bastion.Aura:New() @@ -204,8 +324,12 @@ end function AuraTable:HasAnyStealableAura() for _, auras in pairs(self:GetUnitAuras()) do for _, aura in pairs(auras) do - if aura:GetIsStealable() then - return true + if aura:IsUp() then -- Handle expired and non refreshed dropoffs not coming in UNIT_AURA + if aura:GetIsStealable() then + return true + end + else + self:RemoveInstanceID(aura:GetAuraInstanceID()) end end end @@ -217,8 +341,12 @@ end function AuraTable:HasAnyDispelableAura(spell) for _, auras in pairs(self:GetUnitAuras()) do for _, aura in pairs(auras) do - if aura:IsDebuff() and aura:IsDispelableBySpell(spell) then - return true + if aura:IsUp() then -- Handle expired and non refreshed dropoffs not coming in UNIT_AURA + if aura:IsDebuff() and aura:IsDispelableBySpell(spell) then + return true + end + else + self:RemoveInstanceID(aura:GetAuraInstanceID()) end end end diff --git a/src/EventManager/EventManager.lua b/src/EventManager/EventManager.lua index eade68e..28775bc 100644 --- a/src/EventManager/EventManager.lua +++ b/src/EventManager/EventManager.lua @@ -27,7 +27,6 @@ function EventManager:New() end end) - return self end @@ -36,6 +35,7 @@ function EventManager:RegisterEvent(event, handler) if not self.events[event] then self.events[event] = {} end + table.insert(self.events[event], handler) end @@ -45,6 +45,7 @@ function EventManager:RegisterWoWEvent(event, handler) self.wowEventHandlers[event] = {} self.frame:RegisterEvent(event) end + table.insert(self.wowEventHandlers[event], handler) end diff --git a/src/Spell/Spell.lua b/src/Spell/Spell.lua index 63d85df..7042f14 100644 --- a/src/Spell/Spell.lua +++ b/src/Spell/Spell.lua @@ -102,8 +102,14 @@ function Spell:Cast(unit, condition) -- Check if the mouse was looking self.wasLooking = IsMouselooking() + -- if unit.unit contains 'nameplate' then we need to use Object wrapper to cast + local u = unit.unit + if string.find(u, 'nameplate') then + u = Object(u) + end + -- Cast the spell - CastSpellByName(self:GetName(), unit.unit) + CastSpellByName(self:GetName(), u) Bastion:Debug("Casting", self) diff --git a/src/UnitManager/UnitManager.lua b/src/UnitManager/UnitManager.lua index 39b8f40..9c89b16 100644 --- a/src/UnitManager/UnitManager.lua +++ b/src/UnitManager/UnitManager.lua @@ -214,7 +214,8 @@ end -- Enum enemies (nameplates) function UnitManager:EnumNameplates(cb) - for i = 1, 50 do + local n = GetNumNameplates() + for i = 1, n do local unit = self:Get('nameplate' .. i) if unit:IsValid() then if cb(unit) then