--========================================================-- -- Scorpio UnitFrame Aura API -- -- -- -- Author : kurapica125@outlook.com -- -- Create Date : 2021/09/07 -- --========================================================-- --========================================================-- Scorpio "Scorpio.Secure.UnitFrame.AuraAPI" "1.0.0" --========================================================-- namespace "Scorpio.Secure.UnitFrame" import "System.Reactive" import "System.Toolset" __Final__() interface "UnitAuraPredicate" (function(_ENV) local obUnitAura = Wow.FromUnitEvent("UNIT_AURA"):Next() local scanResult = setmetatable({}, { __index = function(self, key) local ret = {} rawset(self, key, ret) return ret end }) local recycle = Recycle() local singleSpellID = setmetatable({}, getmetatable(scanResult)) local singleSpellName = setmetatable({}, getmetatable(scanResult)) local auraFilter = { HELPFUL = 1, HARMFUL = 2, PLAYER = 3, RAID = 4, CANCELABLE = 5, NOT_CANCELABLE = 6, INCLUDE_NAME_PLATE_ONLY = 7, MAW = 8 } local refreshAura, scanForUnit if _G.UnitAuraSlots then function refreshAura(cache, unit, filter, auraIdx, continuationToken, ...) local singleSpellIDMap = singleSpellID[filter] local singleSpellNameMap= singleSpellName[filter] for i = 1, select("#", ...) do local slot = select(i, ...) local name, icon, count, dtype, duration, expires, caster, isStealable, nameplateShowPersonal, spellId = UnitAuraBySlot(unit, slot) if singleSpellIDMap[spellId] then cache[spellId] = auraIdx end if singleSpellNameMap[name] then cache[name] = auraIdx end auraIdx = auraIdx + 1 end return continuationToken and refreshAura(cache, unit, filter, auraIdx, UnitAuraSlots(unit, filter, 16, continuationToken)) end function scanForUnit(cache, unit, filter) return refreshAura(cache, unit, filter, 1, UnitAuraSlots(unit, filter, 16)) end else function scanForUnit(cache, unit, filter) local singleSpellIDMap = singleSpellID[filter] local singleSpellNameMap= singleSpellName[filter] local auraIdx = 1 local name, icon, count, dtype, duration, expires, caster, isStealable, nameplateShowPersonal, spellId = UnitAura(unit, auraIdx, filter) while name do if singleSpellIDMap[spellId] then cache[spellId] = auraIdx end if singleSpellNameMap[name] then cache[name] = auraIdx end auraIdx = auraIdx + 1 name, icon, count, dtype, duration, expires, caster, isStealable, nameplateShowPersonal, spellId = UnitAura(unit, auraIdx, filter) end end end local function getUnitCache(unit, filter) local guid = UnitGUID(unit) local now = GetTime() local fcache = scanResult[filter] local cache = fcache[guid] if not cache then cache = recycle() fcache[guid] = cache cache[0] = now scanForUnit(cache, unit, filter) elseif cache[0] < now then wipe(cache) cache[0] = now scanForUnit(cache, unit, filter) end return cache end local function passFilter(filter) return XList(filter:gmatch("[%w_]+")):ToList():Sort(function(a, b) return auraFilter[a] < auraFilter[b] end):Join("|") end -- Clear Caches Wow.FromEvent("PLAYER_REGEN_ENABLED"):Next():Subscribe(function() for filter, fcache in pairs(scanResult) do for guid, cache in pairs(fcache) do recycle(wipe(cache)) end wipe(fcache) end end) -- Used for full scan __Static__() __Arguments__{} function PredicateUnitAura() return obUnitAura end -- Predicate for one spell id __Static__() __Arguments__{ String, Number } __Observable__() function PredicateUnitAura(filter, id) filter = passFilter(filter) singleSpellID[filter][id] = true return Operator(obUnitAura, function(observer, unit) local cache = getUnitCache(unit, filter) local auraID = cache[id] return auraID and observer:OnNext(unit, filter, auraID) end) end -- Predicate for one spell name __Static__() __Arguments__{ String } __Observable__() function PredicateUnitAura(filter, name) filter = passFilter(filter) singleSpellName[filter][name] = true return Operator(obUnitAura, function(observer, unit) local cache = getUnitCache(unit, filter) local auraID = cache[name] return auraID and observer:OnNext(unit, filter, auraID) end) end __Arguments__{ String, struct { Number }} __Static__() __Observable__() function PredicateUnitAura(filter, ids) filter = passFilter(filter) local clone = recycle() local len = #ids for i = 1, len do singleSpellID[filter][ids[i]] = true clone[i] = ids[i] end return Operator(obUnitAura, function(observer, unit) local cache = getUnitCache(unit, filter) local auraIDs = recycle() local index = 1 for i = 1, len do local auraID = cache[clone[i]] if auraID then auraIDs[index] = auraID index = index + 1 end end if index > 1 then observer:OnNext(unit, filter, unpack(auraIDs)) end recycle(wipe(auraIDs)) end) end __Arguments__{ String, struct { String }} __Static__() __Observable__() function PredicateUnitAura(filter, names) filter = passFilter(filter) local clone = recycle() local len = #names for i = 1, len do singleSpellName[filter][names[i]] = true clone[i] = names[i] end return Operator(obUnitAura, function(observer, unit) local cache = getUnitCache(unit, filter) local auraIDs = recycle() local index = 1 for i = 1, len do local auraID = cache[clone[i]] if auraID then auraIDs[index] = auraID index = index + 1 end end if index > 1 then observer:OnNext(unit, filter, unpack(auraIDs)) end recycle(wipe(auraIDs)) end) end end) __Static__() Wow.UnitAura = UnitAuraPredicate.PredicateUnitAura ------------------------------------------------------------ -- Wow Classic -- ------------------------------------------------------------ if Scorpio.IsRetail or Scorpio.IsBCC then return end --- Try Get LibClassicDurations pcall(LoadAddOn, "LibClassicDurations") local ok, LibClassicDurations = pcall(_G.LibStub, "LibClassicDurations") if not (ok and LibClassicDurations) then return end LibClassicDurations:Register("Scorpio") -- tell library it's being used and should start working _Parent.UnitAura = LibClassicDurations.UnitAuraWithBuffs LibClassicDurations.RegisterCallback("Scorpio", "UNIT_BUFF", function(event, unit) return FireSystemEvent("UNIT_AURA", unit) end)