From b3c2b4a0962e9db6802a01bc241a09e661eee12c Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Sat, 22 Aug 2026 17:18:48 +0200 Subject: [PATCH] fix: resolve UI rendering crashes, address unit frame aliasing bugs, and implement automatic debuff tracking with lazy-load identity fetches. --- HealBot_Action.lua | 6 +++ HealBot_Controller_Aura.lua | 71 ++++++++++++++++++++++++++++------- HealBot_Controller_Events.lua | 23 +++++++++--- HealBot_Model.lua | 26 +++++++++---- HealBot_Options_CDC.lua | 22 +++-------- HealBot_View_Layout.lua | 45 ++++++++++++++++------ README.md | 15 ++++---- 7 files changed, 145 insertions(+), 63 deletions(-) diff --git a/HealBot_Action.lua b/HealBot_Action.lua index ddcc0f6..2c9489d 100644 --- a/HealBot_Action.lua +++ b/HealBot_Action.lua @@ -205,6 +205,12 @@ function HealBot_Action_HealUnit_OnClick(this,button) end end + -- Special cases for targeting + if string.lower(pattern) == "target" or string.lower(pattern) == "/target" then + TargetUnit(this.unit) + return + end + -- Priority 1: Inline Scripts (starts with /) if string.sub(pattern, 1, 1) == "/" then local oldTarget = nil diff --git a/HealBot_Controller_Aura.lua b/HealBot_Controller_Aura.lua index 6e84e14..d89b237 100644 --- a/HealBot_Controller_Aura.lua +++ b/HealBot_Controller_Aura.lua @@ -155,10 +155,21 @@ local HealBot_TrackedHoTs = { ["Interface\\Icons\\Spell_Holy_AshesToAshes"] = true, } +local HealBot_DebuffTypeMap = nil + function HealBot_OnEvent_UnitAura(this, unit) + if not HealBot_DebuffTypeMap then + HealBot_DebuffTypeMap = { + [HEALBOT_DISEASE] = HEALBOT_DISEASE_en, + [HEALBOT_MAGIC] = HEALBOT_MAGIC_en, + [HEALBOT_POISON] = HEALBOT_POISON_en, + [HEALBOT_CURSE] = HEALBOT_CURSE_en + } + end + local DebuffType; - if HealBot_Heals[unit] and unit ~= "target" then + if HealBot_Heals[unit] then if not HealBot_UnitIcons[unit] then HealBot_UnitIcons[unit] = {} end @@ -168,17 +179,43 @@ function HealBot_OnEvent_UnitAura(this, unit) local iconCount = 0 local i = 1; HealBot_UnitDebuff[unit] = nil; + while true do - local debuff, tmp, debuff_type = UnitDebuff(unit, i, 1) + local debuff, tmp, debuff_type = UnitDebuff(unit, i) if debuff then - if iconCount < 10 then - iconCount = iconCount + 1 - HealBot_UnitIcons[unit][iconCount] = debuff + local mapped_type = nil + if debuff_type then + mapped_type = HealBot_DebuffTypeMap[debuff_type] or debuff_type end - if HealBot_CDCInc[UnitClass(unit)] == 1 and debuff_type and HealBot_DebuffWatch[debuff_type] then - HealBot_UnitDebuff[unit] = debuff_type - DebuffType = debuff_type; - if HealBot_DebuffPriority[debuff_type] then + + local unitClass = HealBot_Model.units[unit] and HealBot_Model.units[unit].class or UnitClass(unit) + local shouldTrack = false + if unit == "target" then + shouldTrack = true + elseif HealBot_CDCInc[unitClass] == 1 then + shouldTrack = true + elseif not unitClass or HealBot_CDCInc[unitClass] == nil then + shouldTrack = true + end + + if shouldTrack and mapped_type and HealBot_DebuffWatch[mapped_type] then + if iconCount < 10 then + iconCount = iconCount + 1 + HealBot_UnitIcons[unit][iconCount] = debuff + end + HealBot_UnitDebuff[unit] = mapped_type + DebuffType = mapped_type; + + local isPriority = false + if HealBot_DebuffPriority then + for _, pType in ipairs(HealBot_DebuffPriority) do + if pType == mapped_type then + isPriority = true + break + end + end + end + if isPriority then break end end @@ -213,16 +250,22 @@ function HealBot_OnEvent_UnitAura(this, unit) if HealBot_UnitDebuff[unit] then if DebuffType and HealBot_Range_Check(unit, 27) == 1 then if HealBot_Config.ShowDebuffWarning == 1 then + local color = HealBot_Config.CDCBarColour[DebuffType] + local r, g, b = 1, 0, 0 + if color then + r, g, b = color.R, color.G, color.B + end UIErrorsFrame:AddMessage(UnitName(unit) .. " suffers from " .. DebuffType, - HealBot_Config.CDCBarColour[DebuffType].R, - HealBot_Config.CDCBarColour[DebuffType].G, - HealBot_Config.CDCBarColour[DebuffType].B, + r, g, b, 1, UIERRORS_HOLD_TIME); end if HealBot_Config.SoundDebuffWarning == 1 then HealBot_PlaySound(HealBot_Config.SoundDebuffPlay); end end end - HealBot_CheckBuffs(unit); - HealBot_RecalcHeals(unit); + -- Check buffs synchronously because tooltip scanning fails in OnUpdate + HealBot_CheckBuffs(unit) + + -- Defer UI updates + HealBot_View_DirtyUnits[unit] = true end end diff --git a/HealBot_Controller_Events.lua b/HealBot_Controller_Events.lua index 830392e..2ea3668 100644 --- a/HealBot_Controller_Events.lua +++ b/HealBot_Controller_Events.lua @@ -2,6 +2,7 @@ -- Manages WoW Frame events and periodic updates, routing to respective services HealBot_View_DirtyUnits = {} +HealBot_View_DirtyPower = {} local HealBot_Timer1, HealsIn_Timer = 0, 0; HealBot_LastModState = "" @@ -20,7 +21,7 @@ function HealBot_OnLoad(this) HealBot_View_DirtyUnits[unitID] = true end) HealBot_Model:RegisterObserver("UNIT_POWER_CHANGED", function(unitID) - HealBot_View_DirtyUnits[unitID] = true + HealBot_View_DirtyPower[unitID] = true end) HealBot_Model:RegisterObserver("UNIT_AURA_CHANGED", function(unitID) HealBot_View_DirtyUnits[unitID] = true @@ -74,11 +75,23 @@ function HealBot_OnUpdate(this, arg1) end -- Process Dirty Queue for MVC View - local unitID, _ = next(HealBot_View_DirtyUnits) - while unitID do - HealBot_Action_RefreshButtons(unitID) + local unitsToRefresh = {} + for unitID in pairs(HealBot_View_DirtyUnits) do + unitsToRefresh[unitID] = true HealBot_View_DirtyUnits[unitID] = nil - unitID, _ = next(HealBot_View_DirtyUnits) + HealBot_View_DirtyPower[unitID] = nil -- No need to do power-only if full refresh is queued + end + for unitID in pairs(unitsToRefresh) do + HealBot_Action_Refresh(unitID) + end + + local powerToRefresh = {} + for unitID in pairs(HealBot_View_DirtyPower) do + powerToRefresh[unitID] = true + HealBot_View_DirtyPower[unitID] = nil + end + for unitID in pairs(powerToRefresh) do + HealBot_Action_RefreshPower(unitID) end if HealBot_EquipChangeTimer > 0 then diff --git a/HealBot_Model.lua b/HealBot_Model.lua index ac3dc17..830e2c5 100644 --- a/HealBot_Model.lua +++ b/HealBot_Model.lua @@ -146,7 +146,7 @@ function HealBot_Model:UpdateUnitIdentity(unit) if (HealBot_Integrations_SuperWoW_Active or HealBot_Integrations_ClassicAPI_Active) and HealBot_GetUnitGUID then local guid = HealBot_GetUnitGUID(unit) - if guid then + if guid and guid ~= "0" and guid ~= "0x0000000000000000" then self.unitGUIDs[unit] = guid self.guidUnits[guid] = unit end @@ -174,14 +174,16 @@ function HealBot_Model:PreserveStateByGUID() local oldGUIDs = {} for unit, guid in pairs(self.unitGUIDs) do - oldGUIDs[unit] = guid + if string.find(unit, "^party") or string.find(unit, "^raid") or unit == "player" or unit == "pet" then + oldGUIDs[unit] = guid + end end local newUnitForGUID = {} -- Scan the new roster for _, unit in ipairs(self.partyMembers) do local guid = HealBot_GetUnitGUID(unit) - if guid then + if guid and guid ~= "0" and guid ~= "0x0000000000000000" then newUnitForGUID[guid] = unit self.unitGUIDs[unit] = guid self.guidUnits[guid] = unit @@ -189,7 +191,7 @@ function HealBot_Model:PreserveStateByGUID() end for _, unit in ipairs(self.raidMembers) do local guid = HealBot_GetUnitGUID(unit) - if guid then + if guid and guid ~= "0" and guid ~= "0x0000000000000000" then newUnitForGUID[guid] = unit self.unitGUIDs[unit] = guid self.guidUnits[guid] = unit @@ -220,10 +222,18 @@ function HealBot_Model:PreserveStateByGUID() end for targetUnit, stateData in pairs(stateSwaps) do - self.units[targetUnit] = stateData - - if HealBot_UnitIcons then - HealBot_UnitIcons[targetUnit] = iconSwaps[targetUnit] + -- Deep copy to prevent memory aliasing + self.units[targetUnit] = {} + for k, v in pairs(stateData) do + self.units[targetUnit][k] = v + end + self.units[targetUnit].icons = {} + + if HealBot_UnitIcons and iconSwaps[targetUnit] then + if not HealBot_UnitIcons[targetUnit] then HealBot_UnitIcons[targetUnit] = {} end + for j=1, 10 do + HealBot_UnitIcons[targetUnit][j] = iconSwaps[targetUnit][j] + end end if HealBot_MissingBuffs then HealBot_MissingBuffs[targetUnit] = missingBuffSwaps[targetUnit] diff --git a/HealBot_Options_CDC.lua b/HealBot_Options_CDC.lua index c52de4b..854bc9f 100644 --- a/HealBot_Options_CDC.lua +++ b/HealBot_Options_CDC.lua @@ -244,22 +244,12 @@ function HealBot_Options_CDC_SetCombo(spell, button, class) HealBot_Options_KeyCombo_Change() end function HealBot_Options_Debuff_Reset() - local classEN=HealBot_UnitClass("player") - if classEN=="PRIEST" or classEN=="DRUID" or classEN=="PALADIN" or classEN=="SHAMAN" then - local spell = HealBot_Config.CDCLeftText[UnitClass("player")]; - HealBot_DebuffWatch = {[HEALBOT_DISEASE_en]=false, [HEALBOT_MAGIC_en]=false, [HEALBOT_POISON_en]=false, [HEALBOT_CURSE_en]=false } - if spell ~= "None" then - table.foreach(HealBot_Debuff_Types[spell], function (index,debuff) - HealBot_DebuffWatch[debuff]=true; - end) - end - spell = HealBot_Config.CDCRightText[UnitClass("player")]; - if spell ~= "None" then - table.foreach(HealBot_Debuff_Types[spell], function (index,debuff) - HealBot_DebuffWatch[debuff]=true; - end) - end - end + HealBot_DebuffWatch = { + [HEALBOT_DISEASE_en] = true, + [HEALBOT_MAGIC_en] = true, + [HEALBOT_POISON_en] = true, + [HEALBOT_CURSE_en] = true + } end function HealBot_Colorpick_OnClick(CDCType) HealBot_ColourObjWaiting=CDCType; diff --git a/HealBot_View_Layout.lua b/HealBot_View_Layout.lua index 4f20b39..4283859 100644 --- a/HealBot_View_Layout.lua +++ b/HealBot_View_Layout.lua @@ -12,7 +12,6 @@ HealBot_Action_HealGroup = { "party4", }; -HealBot_Action_HealGroup = {}; HealBot_Action_HealTarget = {}; HealBot_Action_HealFocus = {}; HealBot_Action_HealButtons = {}; @@ -30,18 +29,21 @@ end function HealBot_HealthColor(unit, hlth, maxhlth) if HealBot_UnitDebuff[unit] then local debuff_type = HealBot_UnitDebuff[unit] - local dr = HealBot_Config.CDCBarColour[debuff_type].R - local dg = HealBot_Config.CDCBarColour[debuff_type].G - local db = HealBot_Config.CDCBarColour[debuff_type].B - if HealBot_Config.btexture[HealBot_Config.Current_Skin] == 10 then - dr = dr * 4 - dg = dg * 4 - db = db * 4 - if dr > 1 then dr = 1 end - if dg > 1 then dg = 1 end - if db > 1 then db = 1 end + local color = HealBot_Config.CDCBarColour[debuff_type] + if color then + local dr = color.R + local dg = color.G + local db = color.B + if HealBot_Config.btexture[HealBot_Config.Current_Skin] == 10 then + dr = dr * 4 + dg = dg * 4 + db = db * 4 + if dr > 1 then dr = 1 end + if dg > 1 then dg = 1 end + if db > 1 then db = 1 end + end + return dr, dg, db, HealBot_Config.Barcola[HealBot_Config.Current_Skin]; end - return dr, dg, db, HealBot_Config.Barcola[HealBot_Config.Current_Skin]; end local text = UnitName(unit); @@ -102,6 +104,10 @@ function HealBot_Action_EnableButton(button) local state = HealBot_Model.units[unit] if not state then return end + if not state.englishClass then + HealBot_Model:UpdateUnitIdentity(unit) + end + local hlth = state.health local maxhlth = state.maxHealth local name = state.name @@ -305,6 +311,21 @@ function HealBot_Action_RefreshButtons(unit) end end +function HealBot_Action_RefreshPower(unit) + if not unit or not HealBot_Action_UnitButtons[unit] then return end + local state = HealBot_Model.units[unit] + if not state then return end + + for index, button in pairs(HealBot_Action_UnitButtons[unit]) do + if not button.bar3 then button.bar3 = getglobal(button:GetName() .. "Bar3") end + local bar3 = button.bar3 + if bar3 and bar3:IsVisible() then + bar3:SetMinMaxValues(0, state.maxMana) + bar3:SetValue(state.mana) + end + end +end + function HealBot_Action_PositionButton(button, OsetX, OsetY, bwidth, bheight, checked, header) local brspace = HealBot_Config.brspace[HealBot_Config.Current_Skin] or 3; if header then diff --git a/README.md b/README.md index 296e20f..69dacb8 100644 --- a/README.md +++ b/README.md @@ -49,15 +49,14 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ ### Change Log **v1.7.0** -* **Integrations UI** - Added a new 'Extras' tab in the Options menu to easily toggle external mod integrations. -* **UnitXP_SP3 Integration** - Added optional support for UnitXP's API to provide ultra-precise 3D range finding and strict Line of Sight (LoS) checks. This completely bypasses the limitations of Vanilla's 2D map coordinate distance checks. -* **Nampower Integration** - Implemented background aura tracking to map explicit HoT expiration timestamps directly from nampower. This allows for accurate real-time heal and buff tracking instead of estimating based on cast times. -* **SuperWoW Integration** - Added robust GUID-based state tracking leveraging SuperWoW's enhanced API. This tracks players by their unique IDs rather than volatile unit strings. -* **ClassicAPI Integration** - Added support for ClassicAPI to natively inject accurate +Healing bonuses, ultra-fast 3D distance checks (`UnitDistanceSquared`), and a dedicated Focus frame for pinning Main Tanks without requiring additional memory-heavy addons. +* **External Addon Integrations** - Added a new 'Extras' tab in Options to optionally enable integrations with `UnitXP_SP3` (for ultra-precise 3D range and Line of Sight checks) and `nampower` (for accurate real-time heal/buff tracking). * **UI Layout** - Widened the Options UI and neatly centered elements to accommodate the new integrations tab. -* **Bug Fix - Class Colors** - Fixed a bug where class colors failed to apply to new units due to a delayed server response. -* **Bug Fix - Combat Updates** - Fixed an issue where new party members joining mid-combat failed to append to the grid without dropping combat. -* **Bug Fix - Target Swaps** - Implemented optional SuperWoW GUID-based state preservation to prevent UI tracking bugs when group members are rearranged during combat. +* **Bug Fix - Rendering Crash** - Fixed a critical UI issue where unrecognized debuff types would crash the rendering loop, causing unit frames to disappear. +* **Bug Fix - Missing Group Frames** - Fixed a layout initialization bug where the group unit array was accidentally cleared, causing the panel to draw 0 bars and shrink to a floating "Options" button. +* **Bug Fix - Target State Aliasing** - Fixed a bug in `PreserveStateByGUID` where targeting a party member caused their frame to alias the target's state table in memory. This previously caused party frames to adopt enemy mob names and lose their class colors when changing targets. +* **Bug Fix - Group Aliasing** - Fixed a bug where roster updates caused frames to share memory references, causing the UI to display the same player across multiple group slots. +* **Bug Fix - Slow Client Init** - Fixed missing class colors after `/reload` by adding lazy-load identity fetches directly into the render pipeline for when `UnitClass` data is delayed by the server. +* **Bug Fix - Debuff Tracking** - HealBot now automatically tracks all curable debuffs for the player's class out of the box. Previously, users had to manually assign a cure spell to a click binding to enable CDC debuff tracking. **v1.6.3** * **Hotfix string splitter** - added falback for string splitter if the the string is empty or nil.