From 8d39ae75c28e7a14c2a25f7f5cf8b78b1b5f7105 Mon Sep 17 00:00:00 2001 From: Jrc13245 Date: Fri, 26 Dec 2025 16:42:58 -0500 Subject: [PATCH] add error catcher and fix libdebuff accesses --- Compatibility/pfUI.lua | 3 ++- Conditionals.lua | 36 ++++++++++++++++++++++++------------ Core.lua | 30 ++++++++++++++++++++++++------ 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/Compatibility/pfUI.lua b/Compatibility/pfUI.lua index 895dc7e..5bf6b40 100644 --- a/Compatibility/pfUI.lua +++ b/Compatibility/pfUI.lua @@ -154,7 +154,8 @@ function Extension.HookPfUILibdebuff() pflib.AddEffect = function(self, unit, unitlevel, effect, duration, caster) -- RANK CHECKING: Preserve higher rank's remaining time if lower rank was cast -- NOTE: 'unit' is a unit NAME (e.g., "Expert Training Dummy"), not a unit ID - if caster == "player" and CleveRoids.libdebuff and duration and duration > 0 then + -- Defensive: verify libdebuff is a table, not a function + if caster == "player" and type(CleveRoids.libdebuff) == "table" and duration and duration > 0 then -- Try to find the GUID for this unit name local unitGUID = nil diff --git a/Conditionals.lua b/Conditionals.lua index 13d72ad..3a8b633 100644 --- a/Conditionals.lua +++ b/Conditionals.lua @@ -688,16 +688,19 @@ end -- Helper: Get debuff time-left (seconds) from CleveRoids.libdebuff only local function _get_debuff_timeleft(unitToken, auraName) + -- Defensive: verify libdebuff is a table before accessing properties + local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil + -- SuperWoW path: GUID-based lookup -- SuperWoW debuff slots: 1-16 are regular debuffs, 17-48 overflow to buff slots 1-32 - if CleveRoids.hasSuperwow then + if CleveRoids.hasSuperwow and lib then local _, guid = UnitExists(unitToken) - if guid and CleveRoids.libdebuff and CleveRoids.libdebuff.objects[guid] then + if guid and lib.objects and lib.objects[guid] then -- Check 1-48: debuff slots 1-16 + overflow debuffs in buff slots 1-32 -- NOTE: Slots 1-16 are dense (break on nil), slots 17-48 are sparse (continue on nil) -- Overflow debuffs in buff slots are mixed with regular buffs, so we can't break early for i = 1, 48 do - local effect, _, _, _, _, duration, timeleft = CleveRoids.libdebuff:UnitDebuff(unitToken, i) + local effect, _, _, _, _, duration, timeleft = lib:UnitDebuff(unitToken, i) -- Only break for slots 1-16 (regular debuffs are dense) -- For overflow slots 17-48, nil means "regular buff filtered out", not "end of list" if not effect and i <= 16 then break end @@ -709,9 +712,9 @@ local function _get_debuff_timeleft(unitToken, auraName) end -- Non-SuperWoW fallback - if CleveRoids.libdebuff and CleveRoids.libdebuff.UnitDebuff then + if lib and lib.UnitDebuff then for idx = 1, 48 do - local effect, _, _, _, _, duration, timeleft = CleveRoids.libdebuff:UnitDebuff(unitToken, idx) + local effect, _, _, _, _, duration, timeleft = lib:UnitDebuff(unitToken, idx) -- Only break for slots 1-16 (regular debuffs are dense) -- For overflow slots 17-48, nil means "regular buff filtered out", not "end of list" if not effect and idx <= 16 then break end @@ -1796,7 +1799,9 @@ function CleveRoids.ValidateUnitDebuff(unit, args) -- For non-player units, check tracking table directly -- SIMPLE: Did the player cast this spell? Is the timer still valid? - if unit ~= "player" and CleveRoids.libdebuff then + -- Defensive: verify libdebuff is a table before accessing properties + local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil + if unit ~= "player" and lib and lib.objects then local _, guid = UnitExists(unit) if not guid then return false end @@ -1810,10 +1815,10 @@ function CleveRoids.ValidateUnitDebuff(unit, args) -- Check tracking table for ANY rank of this spell: Did player cast this? Is timer valid? if matchingSpellIDs and table.getn(matchingSpellIDs) > 0 then for _, spellID in ipairs(matchingSpellIDs) do - local rec = CleveRoids.libdebuff.objects[guid] and CleveRoids.libdebuff.objects[guid][spellID] + local rec = lib.objects[guid] and lib.objects[guid][spellID] -- For shared debuffs (Sunder, Faerie Fire, etc.), accept any caster (including nil) -- For personal debuffs (Rip, Rupture, etc.), only accept player casts - local isSharedDebuff = CleveRoids.libdebuff:IsPersonalDebuff(spellID) == false + local isSharedDebuff = lib.IsPersonalDebuff and lib:IsPersonalDebuff(spellID) == false if rec and rec.duration and rec.start and (isSharedDebuff or rec.caster == "player") then local timeRemaining = rec.duration + rec.start - GetTime() if timeRemaining > 0 then @@ -1857,7 +1862,9 @@ function CleveRoids.ValidateUnitDebuff(unit, args) ) end -- Remove expired debuff to prevent repeated "expired" messages - CleveRoids.libdebuff.objects[guid][spellID] = nil + if lib.objects[guid] then + lib.objects[guid][spellID] = nil + end end end end @@ -2041,7 +2048,8 @@ function CleveRoids.ValidateUnitDebuff(unit, args) return cmp[args.operator](tl or 0, args.amount) end - if CleveRoids.libdebuff and CleveRoids.libdebuff.UnitDebuff then + -- Defensive: verify libdebuff is a table before accessing properties + if type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff.UnitDebuff then local atl = nil local caster = nil @@ -2722,7 +2730,10 @@ CleveRoids.CCSpellMechanics = { -- Check if BuffLib is available with full mechanic support function CleveRoids.HasBuffLib() - return BuffLib and BuffLib.GetUnitDebuffsByMechanic and BuffLib.SpellData and BuffLib.SpellData.GetMechanic + -- Defensive: ensure BuffLib.SpellData is a table, not a function + return BuffLib and BuffLib.GetUnitDebuffsByMechanic + and type(BuffLib.SpellData) == "table" + and BuffLib.SpellData.GetMechanic end -- Get mechanic for a spell ID (uses BuffLib if available, otherwise built-in table) @@ -2730,7 +2741,8 @@ function CleveRoids.GetSpellMechanic(spellID) if not spellID or spellID <= 0 then return 0 end -- Try BuffLib first (has complete DBC data) - if BuffLib and BuffLib.SpellData and BuffLib.SpellData.GetMechanic then + -- Defensive: verify SpellData is a table before indexing + if BuffLib and type(BuffLib.SpellData) == "table" and BuffLib.SpellData.GetMechanic then local mechanic = BuffLib.SpellData:GetMechanic(spellID) if mechanic and mechanic > 0 then return mechanic diff --git a/Core.lua b/Core.lua index a70b740..c63f205 100644 --- a/Core.lua +++ b/Core.lua @@ -3,8 +3,24 @@ License: MIT License ]] --- Setup to wrap our stuff in a table so we don't pollute the global environment +-- DEBUG: Error catcher for "attempt to index a function value" +-- Remove this block once the error is identified local _G = _G or getfenv(0) +local originalErrorHandler = geterrorhandler and geterrorhandler() +if seterrorhandler then + seterrorhandler(function(msg) + if msg and string.find(msg, "index a function value") then + local trace = debugstack and debugstack(2, 20, 0) or "no stack available" + DEFAULT_CHAT_FRAME:AddMessage("|cFFFF0000[SCRM DEBUG] index function error:|r " .. tostring(msg)) + DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00Stack:|r " .. tostring(trace)) + end + if originalErrorHandler then + return originalErrorHandler(msg) + end + end) +end + +-- Setup to wrap our stuff in a table so we don't pollute the global environment local CleveRoids = _G.CleveRoids or {} _G.CleveRoids = CleveRoids CleveRoids.lastItemIndexTime = 0 @@ -3995,11 +4011,13 @@ function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time -- Only process if this is the player's melee swing if caster == CleveRoids.playerGuid and CleveRoids.playerClass == "PALADIN" then -- Refresh judgements on the target - if target and CleveRoids.libdebuff then + -- Defensive: verify libdebuff is a table before accessing properties + local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil + if target and lib and lib.objects then local normalizedTarget = CleveRoids.NormalizeGUID(target) - if normalizedTarget and CleveRoids.libdebuff.objects[normalizedTarget] then + if normalizedTarget and lib.objects[normalizedTarget] then -- Refresh all active Judgements on the target (pfUI-style: by name, not just ID) - for spellID, rec in pairs(CleveRoids.libdebuff.objects[normalizedTarget]) do + for spellID, rec in pairs(lib.objects[normalizedTarget]) do if rec.start and rec.duration then local spellName = SpellInfo(spellID) if spellName then @@ -4007,7 +4025,7 @@ function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "") -- Check if this is a judgement by name (pfUI approach) - if CleveRoids.libdebuff.judgementNames and CleveRoids.libdebuff.judgementNames[baseName] then + if lib.judgementNames and lib.judgementNames[baseName] then -- Only refresh if the Judgement is still active and was cast by player local remaining = rec.duration + rec.start - GetTime() if remaining > 0 and rec.caster == "player" then @@ -4023,7 +4041,7 @@ function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time -- Also sync to pfUI if it's loaded if pfUI and pfUI.api and pfUI.api.libdebuff then - local targetName = CleveRoids.libdebuff.guidToName[normalizedTarget] or UnitName("target") + local targetName = (lib.guidToName and lib.guidToName[normalizedTarget]) or UnitName("target") local targetLevel = UnitLevel("target") or 0 if targetName then