fix: resolve UI rendering crashes, address unit frame aliasing bugs, and implement automatic debuff tracking with lazy-load identity fetches.

This commit is contained in:
Bluewhale1337
2026-08-22 17:18:48 +02:00
parent d0e027ec12
commit b3c2b4a096
7 changed files with 145 additions and 63 deletions
+6
View File
@@ -205,6 +205,12 @@ function HealBot_Action_HealUnit_OnClick(this,button)
end end
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 /) -- Priority 1: Inline Scripts (starts with /)
if string.sub(pattern, 1, 1) == "/" then if string.sub(pattern, 1, 1) == "/" then
local oldTarget = nil local oldTarget = nil
+57 -14
View File
@@ -155,10 +155,21 @@ local HealBot_TrackedHoTs = {
["Interface\\Icons\\Spell_Holy_AshesToAshes"] = true, ["Interface\\Icons\\Spell_Holy_AshesToAshes"] = true,
} }
local HealBot_DebuffTypeMap = nil
function HealBot_OnEvent_UnitAura(this, unit) 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; local DebuffType;
if HealBot_Heals[unit] and unit ~= "target" then if HealBot_Heals[unit] then
if not HealBot_UnitIcons[unit] then if not HealBot_UnitIcons[unit] then
HealBot_UnitIcons[unit] = {} HealBot_UnitIcons[unit] = {}
end end
@@ -168,17 +179,43 @@ function HealBot_OnEvent_UnitAura(this, unit)
local iconCount = 0 local iconCount = 0
local i = 1; local i = 1;
HealBot_UnitDebuff[unit] = nil; HealBot_UnitDebuff[unit] = nil;
while true do while true do
local debuff, tmp, debuff_type = UnitDebuff(unit, i, 1) local debuff, tmp, debuff_type = UnitDebuff(unit, i)
if debuff then if debuff then
if iconCount < 10 then local mapped_type = nil
iconCount = iconCount + 1 if debuff_type then
HealBot_UnitIcons[unit][iconCount] = debuff mapped_type = HealBot_DebuffTypeMap[debuff_type] or debuff_type
end end
if HealBot_CDCInc[UnitClass(unit)] == 1 and debuff_type and HealBot_DebuffWatch[debuff_type] then
HealBot_UnitDebuff[unit] = debuff_type local unitClass = HealBot_Model.units[unit] and HealBot_Model.units[unit].class or UnitClass(unit)
DebuffType = debuff_type; local shouldTrack = false
if HealBot_DebuffPriority[debuff_type] then 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 break
end end
end end
@@ -213,16 +250,22 @@ function HealBot_OnEvent_UnitAura(this, unit)
if HealBot_UnitDebuff[unit] then if HealBot_UnitDebuff[unit] then
if DebuffType and HealBot_Range_Check(unit, 27) == 1 then if DebuffType and HealBot_Range_Check(unit, 27) == 1 then
if HealBot_Config.ShowDebuffWarning == 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, UIErrorsFrame:AddMessage(UnitName(unit) .. " suffers from " .. DebuffType,
HealBot_Config.CDCBarColour[DebuffType].R, r, g, b,
HealBot_Config.CDCBarColour[DebuffType].G,
HealBot_Config.CDCBarColour[DebuffType].B,
1, UIERRORS_HOLD_TIME); 1, UIERRORS_HOLD_TIME);
end end
if HealBot_Config.SoundDebuffWarning == 1 then HealBot_PlaySound(HealBot_Config.SoundDebuffPlay); end if HealBot_Config.SoundDebuffWarning == 1 then HealBot_PlaySound(HealBot_Config.SoundDebuffPlay); end
end end
end end
HealBot_CheckBuffs(unit); -- Check buffs synchronously because tooltip scanning fails in OnUpdate
HealBot_RecalcHeals(unit); HealBot_CheckBuffs(unit)
-- Defer UI updates
HealBot_View_DirtyUnits[unit] = true
end end
end end
+18 -5
View File
@@ -2,6 +2,7 @@
-- Manages WoW Frame events and periodic updates, routing to respective services -- Manages WoW Frame events and periodic updates, routing to respective services
HealBot_View_DirtyUnits = {} HealBot_View_DirtyUnits = {}
HealBot_View_DirtyPower = {}
local HealBot_Timer1, HealsIn_Timer = 0, 0; local HealBot_Timer1, HealsIn_Timer = 0, 0;
HealBot_LastModState = "" HealBot_LastModState = ""
@@ -20,7 +21,7 @@ function HealBot_OnLoad(this)
HealBot_View_DirtyUnits[unitID] = true HealBot_View_DirtyUnits[unitID] = true
end) end)
HealBot_Model:RegisterObserver("UNIT_POWER_CHANGED", function(unitID) HealBot_Model:RegisterObserver("UNIT_POWER_CHANGED", function(unitID)
HealBot_View_DirtyUnits[unitID] = true HealBot_View_DirtyPower[unitID] = true
end) end)
HealBot_Model:RegisterObserver("UNIT_AURA_CHANGED", function(unitID) HealBot_Model:RegisterObserver("UNIT_AURA_CHANGED", function(unitID)
HealBot_View_DirtyUnits[unitID] = true HealBot_View_DirtyUnits[unitID] = true
@@ -74,11 +75,23 @@ function HealBot_OnUpdate(this, arg1)
end end
-- Process Dirty Queue for MVC View -- Process Dirty Queue for MVC View
local unitID, _ = next(HealBot_View_DirtyUnits) local unitsToRefresh = {}
while unitID do for unitID in pairs(HealBot_View_DirtyUnits) do
HealBot_Action_RefreshButtons(unitID) unitsToRefresh[unitID] = true
HealBot_View_DirtyUnits[unitID] = nil 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 end
if HealBot_EquipChangeTimer > 0 then if HealBot_EquipChangeTimer > 0 then
+18 -8
View File
@@ -146,7 +146,7 @@ function HealBot_Model:UpdateUnitIdentity(unit)
if (HealBot_Integrations_SuperWoW_Active or HealBot_Integrations_ClassicAPI_Active) and HealBot_GetUnitGUID then if (HealBot_Integrations_SuperWoW_Active or HealBot_Integrations_ClassicAPI_Active) and HealBot_GetUnitGUID then
local guid = HealBot_GetUnitGUID(unit) local guid = HealBot_GetUnitGUID(unit)
if guid then if guid and guid ~= "0" and guid ~= "0x0000000000000000" then
self.unitGUIDs[unit] = guid self.unitGUIDs[unit] = guid
self.guidUnits[guid] = unit self.guidUnits[guid] = unit
end end
@@ -174,14 +174,16 @@ function HealBot_Model:PreserveStateByGUID()
local oldGUIDs = {} local oldGUIDs = {}
for unit, guid in pairs(self.unitGUIDs) do 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 end
local newUnitForGUID = {} local newUnitForGUID = {}
-- Scan the new roster -- Scan the new roster
for _, unit in ipairs(self.partyMembers) do for _, unit in ipairs(self.partyMembers) do
local guid = HealBot_GetUnitGUID(unit) local guid = HealBot_GetUnitGUID(unit)
if guid then if guid and guid ~= "0" and guid ~= "0x0000000000000000" then
newUnitForGUID[guid] = unit newUnitForGUID[guid] = unit
self.unitGUIDs[unit] = guid self.unitGUIDs[unit] = guid
self.guidUnits[guid] = unit self.guidUnits[guid] = unit
@@ -189,7 +191,7 @@ function HealBot_Model:PreserveStateByGUID()
end end
for _, unit in ipairs(self.raidMembers) do for _, unit in ipairs(self.raidMembers) do
local guid = HealBot_GetUnitGUID(unit) local guid = HealBot_GetUnitGUID(unit)
if guid then if guid and guid ~= "0" and guid ~= "0x0000000000000000" then
newUnitForGUID[guid] = unit newUnitForGUID[guid] = unit
self.unitGUIDs[unit] = guid self.unitGUIDs[unit] = guid
self.guidUnits[guid] = unit self.guidUnits[guid] = unit
@@ -220,10 +222,18 @@ function HealBot_Model:PreserveStateByGUID()
end end
for targetUnit, stateData in pairs(stateSwaps) do for targetUnit, stateData in pairs(stateSwaps) do
self.units[targetUnit] = stateData -- Deep copy to prevent memory aliasing
self.units[targetUnit] = {}
if HealBot_UnitIcons then for k, v in pairs(stateData) do
HealBot_UnitIcons[targetUnit] = iconSwaps[targetUnit] 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 end
if HealBot_MissingBuffs then if HealBot_MissingBuffs then
HealBot_MissingBuffs[targetUnit] = missingBuffSwaps[targetUnit] HealBot_MissingBuffs[targetUnit] = missingBuffSwaps[targetUnit]
+6 -16
View File
@@ -244,22 +244,12 @@ function HealBot_Options_CDC_SetCombo(spell, button, class)
HealBot_Options_KeyCombo_Change() HealBot_Options_KeyCombo_Change()
end end
function HealBot_Options_Debuff_Reset() function HealBot_Options_Debuff_Reset()
local classEN=HealBot_UnitClass("player") HealBot_DebuffWatch = {
if classEN=="PRIEST" or classEN=="DRUID" or classEN=="PALADIN" or classEN=="SHAMAN" then [HEALBOT_DISEASE_en] = true,
local spell = HealBot_Config.CDCLeftText[UnitClass("player")]; [HEALBOT_MAGIC_en] = true,
HealBot_DebuffWatch = {[HEALBOT_DISEASE_en]=false, [HEALBOT_MAGIC_en]=false, [HEALBOT_POISON_en]=false, [HEALBOT_CURSE_en]=false } [HEALBOT_POISON_en] = true,
if spell ~= "None" then [HEALBOT_CURSE_en] = true
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
end end
function HealBot_Colorpick_OnClick(CDCType) function HealBot_Colorpick_OnClick(CDCType)
HealBot_ColourObjWaiting=CDCType; HealBot_ColourObjWaiting=CDCType;
+33 -12
View File
@@ -12,7 +12,6 @@ HealBot_Action_HealGroup = {
"party4", "party4",
}; };
HealBot_Action_HealGroup = {};
HealBot_Action_HealTarget = {}; HealBot_Action_HealTarget = {};
HealBot_Action_HealFocus = {}; HealBot_Action_HealFocus = {};
HealBot_Action_HealButtons = {}; HealBot_Action_HealButtons = {};
@@ -30,18 +29,21 @@ end
function HealBot_HealthColor(unit, hlth, maxhlth) function HealBot_HealthColor(unit, hlth, maxhlth)
if HealBot_UnitDebuff[unit] then if HealBot_UnitDebuff[unit] then
local debuff_type = HealBot_UnitDebuff[unit] local debuff_type = HealBot_UnitDebuff[unit]
local dr = HealBot_Config.CDCBarColour[debuff_type].R local color = HealBot_Config.CDCBarColour[debuff_type]
local dg = HealBot_Config.CDCBarColour[debuff_type].G if color then
local db = HealBot_Config.CDCBarColour[debuff_type].B local dr = color.R
if HealBot_Config.btexture[HealBot_Config.Current_Skin] == 10 then local dg = color.G
dr = dr * 4 local db = color.B
dg = dg * 4 if HealBot_Config.btexture[HealBot_Config.Current_Skin] == 10 then
db = db * 4 dr = dr * 4
if dr > 1 then dr = 1 end dg = dg * 4
if dg > 1 then dg = 1 end db = db * 4
if db > 1 then db = 1 end 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 end
return dr, dg, db, HealBot_Config.Barcola[HealBot_Config.Current_Skin];
end end
local text = UnitName(unit); local text = UnitName(unit);
@@ -102,6 +104,10 @@ function HealBot_Action_EnableButton(button)
local state = HealBot_Model.units[unit] local state = HealBot_Model.units[unit]
if not state then return end if not state then return end
if not state.englishClass then
HealBot_Model:UpdateUnitIdentity(unit)
end
local hlth = state.health local hlth = state.health
local maxhlth = state.maxHealth local maxhlth = state.maxHealth
local name = state.name local name = state.name
@@ -305,6 +311,21 @@ function HealBot_Action_RefreshButtons(unit)
end end
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) function HealBot_Action_PositionButton(button, OsetX, OsetY, bwidth, bheight, checked, header)
local brspace = HealBot_Config.brspace[HealBot_Config.Current_Skin] or 3; local brspace = HealBot_Config.brspace[HealBot_Config.Current_Skin] or 3;
if header then if header then
+7 -8
View File
@@ -49,15 +49,14 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\
### Change Log ### Change Log
**v1.7.0** **v1.7.0**
* **Integrations UI** - Added a new 'Extras' tab in the Options menu to easily toggle external mod integrations. * **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).
* **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.
* **UI Layout** - Widened the Options UI and neatly centered elements to accommodate the new integrations tab. * **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 - Rendering Crash** - Fixed a critical UI issue where unrecognized debuff types would crash the rendering loop, causing unit frames to disappear.
* **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 - 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 Swaps** - Implemented optional SuperWoW GUID-based state preservation to prevent UI tracking bugs when group members are rearranged during combat. * **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** **v1.6.3**
* **Hotfix string splitter** - added falback for string splitter if the the string is empty or nil. * **Hotfix string splitter** - added falback for string splitter if the the string is empty or nil.