diff --git a/HealBot_Controller_Events.lua b/HealBot_Controller_Events.lua index 5d76946..830392e 100644 --- a/HealBot_Controller_Events.lua +++ b/HealBot_Controller_Events.lua @@ -388,6 +388,11 @@ function HealBot_OnEvent_PlayerTargetChanged(this) end function HealBot_OnEvent_PartyMembersChanged(this) + HealBot_Model:PreserveStateByGUID() + HealBot_Integrations_PruneNampower() + if HealBot_IsFighting then + HealBot_Action_PartyChanged() + end HealBot_Delay_RecalcParty = 1; end diff --git a/HealBot_Controller_Range.lua b/HealBot_Controller_Range.lua index d24a191..2a95219 100644 --- a/HealBot_Controller_Range.lua +++ b/HealBot_Controller_Range.lua @@ -15,9 +15,12 @@ function HealBot_Range_Check(unit, range) if ( unit == "player" ) then return_val = 1; elseif HealBot_Integrations_UnitXP_Active then - local dist = UnitXP("distanceBetween", "player", unit, "Gaussian") - if dist and dist <= range then - return_val = 1; + local inSight = UnitXP("inSight", "player", unit) + if inSight then + local dist = UnitXP("distanceBetween", "player", unit, "Gaussian") + if dist and dist <= range then + return_val = 1; + end end elseif ( UnitIsVisible(unit) == 1 ) then local tx, ty = GetPlayerMapPosition(unit) diff --git a/HealBot_Data.lua b/HealBot_Data.lua index c5ea038..7737148 100644 --- a/HealBot_Data.lua +++ b/HealBot_Data.lua @@ -257,6 +257,7 @@ HealBot_ConfigDefaults = { HideParty = 0, HealBot_Integrations_UnitXP = 0, HealBot_Integrations_Nampower = 0, + HealBot_Integrations_SuperWoW = 0, }; HealBot_Config = {}; diff --git a/HealBot_Integrations.lua b/HealBot_Integrations.lua index 028bcce..d1926a9 100644 --- a/HealBot_Integrations.lua +++ b/HealBot_Integrations.lua @@ -15,9 +15,83 @@ function HealBot_Integrations_Toggle() if HealBot_Config.HealBot_Integrations_Nampower == 1 then HealBot_Integrations_Nampower_Active = true; HealBot_AddDebug("nampower Integration: ENABLED"); - -- Add Nampower specific initialization here later + if not HealBot_NampowerFrame then + HealBot_Nampower_Auras = {} + HealBot_NampowerFrame = CreateFrame("Frame") + HealBot_NampowerFrame:RegisterEvent("AURA_CAST_ON_SELF") + HealBot_NampowerFrame:RegisterEvent("AURA_CAST_ON_OTHER") + HealBot_NampowerFrame:SetScript("OnEvent", function() + if not HealBot_Integrations_Nampower_Active then return end + local spellID, caster, target, _, _, _, _, duration = arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 + if not caster or not target or duration <= 0 then return end + + local spellName = GetSpellRecField(spellID, "name") + if not spellName then return end + + if not HealBot_Nampower_Auras[target] then + HealBot_Nampower_Auras[target] = {} + end + + local expirationTime = GetTime() + (duration / 1000) + HealBot_Nampower_Auras[target][spellName] = expirationTime + + local unitID = HealBot_Model:GetUnitIDByName(target) + if unitID then + HealBot_OnEvent_UnitAura(nil, unitID) + end + end) + end else HealBot_Integrations_Nampower_Active = false; HealBot_AddDebug("nampower Integration: DISABLED"); + if HealBot_Nampower_Auras then + for k in pairs(HealBot_Nampower_Auras) do + HealBot_Nampower_Auras[k] = nil + end + end + end + + + +function HealBot_Integrations_PruneNampower() + if not HealBot_Integrations_Nampower_Active or not HealBot_Nampower_Auras then return end + + local currentTime = GetTime() + for targetName, auras in pairs(HealBot_Nampower_Auras) do + -- Check if target is still in the raid/party + if not HealBot_Model:GetUnitIDByName(targetName) then + HealBot_Nampower_Auras[targetName] = nil + else + -- Prune expired auras + local hasAuras = false + for spellName, expiration in pairs(auras) do + if currentTime > expiration then + auras[spellName] = nil + else + hasAuras = true + end + end + if not hasAuras then + HealBot_Nampower_Auras[targetName] = nil + end + end + end +end + + -- SuperWoW + if HealBot_Config.HealBot_Integrations_SuperWoW == 1 and UnitExists ~= nil then + -- We can check if GetUnitGUID exists as a quick SuperWoW test, or UnitExists returns 2 args + -- Actually SuperWoW adds SpellInfo, or we can just rely on GetUnitGUID if it exists in the environment + if GetUnitGUID or (type(UnitExists) == "function") then + -- If the client actually supports GUID tracking via SuperWoW (or a similar mod) + HealBot_Integrations_SuperWoW_Active = true; + HealBot_AddDebug("SuperWoW Integration: ENABLED"); + else + HealBot_Integrations_SuperWoW_Active = false; + HealBot_AddDebug("SuperWoW Integration: DISABLED (Mod Not Found)"); + end + else + HealBot_Integrations_SuperWoW_Active = false; + HealBot_AddDebug("SuperWoW Integration: DISABLED"); end end diff --git a/HealBot_Model.lua b/HealBot_Model.lua index 7fca64a..259a52b 100644 --- a/HealBot_Model.lua +++ b/HealBot_Model.lua @@ -35,7 +35,11 @@ HealBot_Model = { partyMembers = {}, raidMembers = {}, playerPet = nil, - target = nil + target = nil, + + -- Integration maps + unitGUIDs = {}, -- unit -> GUID + guidUnits = {} -- GUID -> unit } -------------------------------------------------------------------------------- @@ -131,18 +135,105 @@ function HealBot_Model:UpdateUnitIdentity(unit) if not self.units[unit] then return false end local oldName = self.units[unit].name + local oldEnglishClass = self.units[unit].englishClass local name = UnitName(unit) local _, englishClass = UnitClass(unit) - if oldName ~= name then + if oldName ~= name or oldEnglishClass ~= englishClass then self.units[unit].name = name self.units[unit].englishClass = englishClass self.units[unit].class = UnitClass(unit) + + if HealBot_Integrations_SuperWoW_Active and GetUnitGUID then + local guid = GetUnitGUID(unit) + if guid then + self.unitGUIDs[unit] = guid + self.guidUnits[guid] = unit + end + end return true -- Identity changed end return false end +function HealBot_Model:GetUnitByGUID(guid) + if not guid then return nil end + return self.guidUnits[guid] +end + +function HealBot_Model:GetUnitIDByName(name) + if not name then return nil end + for unit, data in pairs(self.units) do + if data.name == name then return unit end + end + return nil +end + +function HealBot_Model:PreserveStateByGUID() + if not HealBot_Integrations_SuperWoW_Active or not GetUnitGUID then return end + + local oldGUIDs = {} + for unit, guid in pairs(self.unitGUIDs) do + oldGUIDs[unit] = guid + end + + local newUnitForGUID = {} + -- Scan the new roster + for _, unit in ipairs(self.partyMembers) do + local guid = GetUnitGUID(unit) + if guid then + newUnitForGUID[guid] = unit + self.unitGUIDs[unit] = guid + self.guidUnits[guid] = unit + end + end + for _, unit in ipairs(self.raidMembers) do + local guid = GetUnitGUID(unit) + if guid then + newUnitForGUID[guid] = unit + self.unitGUIDs[unit] = guid + self.guidUnits[guid] = unit + end + end + + local stateSwaps = {} + local iconSwaps = {} + local missingBuffSwaps = {} + local debuffSwaps = {} + + for oldUnit, guid in pairs(oldGUIDs) do + local newUnit = newUnitForGUID[guid] + if newUnit and newUnit ~= oldUnit then + stateSwaps[newUnit] = self.units[oldUnit] + + -- Preserve external global tables if they exist + if HealBot_UnitIcons and HealBot_UnitIcons[oldUnit] then + iconSwaps[newUnit] = HealBot_UnitIcons[oldUnit] + end + if HealBot_MissingBuffs and HealBot_MissingBuffs[oldUnit] ~= nil then + missingBuffSwaps[newUnit] = HealBot_MissingBuffs[oldUnit] + end + if HealBot_UnitDebuff and HealBot_UnitDebuff[oldUnit] ~= nil then + debuffSwaps[newUnit] = HealBot_UnitDebuff[oldUnit] + end + end + end + + for targetUnit, stateData in pairs(stateSwaps) do + self.units[targetUnit] = stateData + + if HealBot_UnitIcons then + HealBot_UnitIcons[targetUnit] = iconSwaps[targetUnit] + end + if HealBot_MissingBuffs then + HealBot_MissingBuffs[targetUnit] = missingBuffSwaps[targetUnit] + end + if HealBot_UnitDebuff then + HealBot_UnitDebuff[targetUnit] = debuffSwaps[targetUnit] + end + end +end + -- Updates health/maxHealth values. Returns true if changed. function HealBot_Model:UpdateUnitHealth(unit) if not self.units[unit] then return false end diff --git a/HealBot_Options_Integrations.lua b/HealBot_Options_Integrations.lua index f93f5de..31f23b2 100644 --- a/HealBot_Options_Integrations.lua +++ b/HealBot_Options_Integrations.lua @@ -1,4 +1,5 @@ function HealBot_Options_Integrations_OnShow(this) HealBot_Options_Integrations_UnitXP:SetChecked(HealBot_Config.HealBot_Integrations_UnitXP); HealBot_Options_Integrations_Nampower:SetChecked(HealBot_Config.HealBot_Integrations_Nampower); + HealBot_Options_Integrations_SuperWoW:SetChecked(HealBot_Config.HealBot_Integrations_SuperWoW); end diff --git a/HealBot_Options_Integrations.xml b/HealBot_Options_Integrations.xml index d4e3154..a990483 100644 --- a/HealBot_Options_Integrations.xml +++ b/HealBot_Options_Integrations.xml @@ -42,6 +42,25 @@ + + + + + + + + + + + getglobal(this:GetName().."Text"):SetText("Enable SuperWoW Integration (GUID Tracking)"); + + + HealBot_Config.HealBot_Integrations_SuperWoW = this:GetChecked() and 1 or 0; + HealBot_Integrations_Toggle(); + + + + diff --git a/README.md b/README.md index f30f68a..c4393b9 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,7 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ * **Highly Customizable Skins:** Fully configure dimensions (width, height), row spacing, column layouts, custom textures, opacity, class-colored frames, and outline of fonts. ### To be implemented -- **GUID-based frame mapping:** Migrate internal state to use lightweight GUID tracking to resolve bugs when targets switch or duplicate names appear (e.g., enemy mobs, warlock pets). -- **Mod Integration Optimization:** Add conditional SuperWoW/UnitXP SP3 integration to reduce GC spikes and improve frame rendering accuracy. +- **Strategy Pattern / Code Tree Splits:** Migrate heavy mod integrations (like Nampower aura tracking and SuperWoW UI rendering) into isolated code trees (e.g., `HealBot_Model_SuperWoW.lua`). This will allow dynamic overwriting of global functions at startup, eliminating the CPU overhead of running `if integration then` branch checks inside high-frequency update loops on Vanilla clients. ### Known issues @@ -50,8 +49,14 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ ### Change Log **v1.7.0** -* **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). +* **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. * **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. **v1.6.3** * **Hotfix string splitter** - added falback for string splitter if the the string is empty or nil.