From aea1f734caddef00e866643a341138398577f412 Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:55:30 +0200 Subject: [PATCH 1/6] fix: resolve raid class coloring, identity cache, and player range detection issues --- HealBot.lua | 63 +++++++++++++++++++++++++++-------- HealBot_Controller_Events.lua | 16 ++++++++- HealBot_Controller_Range.lua | 2 +- HealBot_Model.lua | 35 ++++++++++++++++++- HealBot_View_Layout.lua | 21 +++++++++--- README.md | 2 ++ 6 files changed, 118 insertions(+), 21 deletions(-) diff --git a/HealBot.lua b/HealBot.lua index 93e6548..86524a1 100644 --- a/HealBot.lua +++ b/HealBot.lua @@ -203,22 +203,59 @@ end -- Spell parsing and casting functions moved to HealBot_Controller_Spells.lua +HealBot_ClassMap = nil + +function HealBot_InitClassMap() + HealBot_ClassMap = { + ["DRUID"] = "DRUID", ["Druid"] = "DRUID", + ["HUNTER"] = "HUNTER", ["Hunter"] = "HUNTER", + ["MAGE"] = "MAGE", ["Mage"] = "MAGE", + ["PALADIN"] = "PALADIN", ["Paladin"] = "PALADIN", + ["PRIEST"] = "PRIEST", ["Priest"] = "PRIEST", + ["ROGUE"] = "ROGUE", ["Rogue"] = "ROGUE", + ["SHAMAN"] = "SHAMAN", ["Shaman"] = "SHAMAN", + ["WARLOCK"] = "WARLOCK", ["Warlock"] = "WARLOCK", + ["WARRIOR"] = "WARRIOR", ["Warrior"] = "WARRIOR", + } + if HEALBOT_DRUID then HealBot_ClassMap[HEALBOT_DRUID] = "DRUID" end + if HEALBOT_HUNTER then HealBot_ClassMap[HEALBOT_HUNTER] = "HUNTER" end + if HEALBOT_MAGE then HealBot_ClassMap[HEALBOT_MAGE] = "MAGE" end + if HEALBOT_PALADIN then HealBot_ClassMap[HEALBOT_PALADIN] = "PALADIN" end + if HEALBOT_PRIEST then HealBot_ClassMap[HEALBOT_PRIEST] = "PRIEST" end + if HEALBOT_ROGUE then HealBot_ClassMap[HEALBOT_ROGUE] = "ROGUE" end + if HEALBOT_SHAMAN then HealBot_ClassMap[HEALBOT_SHAMAN] = "SHAMAN" end + if HEALBOT_WARLOCK then HealBot_ClassMap[HEALBOT_WARLOCK] = "WARLOCK" end + if HEALBOT_WARRIOR then HealBot_ClassMap[HEALBOT_WARRIOR] = "WARRIOR" end +end + -- HealBot_UnitClass: Returns English class string for a unit. function HealBot_UnitClass(unit) - local locClass, enClass = UnitClass(unit) - if enClass then return string.upper(enClass) end - - -- Fallback for Vanilla 1.12 where enClass is nil - if locClass == HEALBOT_DRUID then return "DRUID" - elseif locClass == HEALBOT_HUNTER then return "HUNTER" - elseif locClass == HEALBOT_MAGE then return "MAGE" - elseif locClass == HEALBOT_PALADIN then return "PALADIN" - elseif locClass == HEALBOT_PRIEST then return "PRIEST" - elseif locClass == HEALBOT_ROGUE then return "ROGUE" - elseif locClass == HEALBOT_SHAMAN then return "SHAMAN" - elseif locClass == HEALBOT_WARLOCK then return "WARLOCK" - elseif locClass == HEALBOT_WARRIOR then return "WARRIOR" + if not unit then return nil end + if not HealBot_ClassMap then HealBot_InitClassMap() end + + -- 1. If player self, query "player" directly + if unit == "player" or UnitIsUnit(unit, "player") then + local locClass, enClass = UnitClass("player") + if enClass and enClass ~= "" then return string.upper(enClass) end + if locClass and HealBot_ClassMap[locClass] then return HealBot_ClassMap[locClass] end + if locClass then return string.upper(locClass) end end + + -- 2. If raid member, query GetRaidRosterInfo (server authoritative English class token) + local _, _, raidIdx = string.find(unit, "^raid(%d+)$") + if raidIdx and GetNumRaidMembers() > 0 then + local _, _, _, _, class, fileName = GetRaidRosterInfo(tonumber(raidIdx)) + if fileName and fileName ~= "" then + return string.upper(fileName) + elseif class and HealBot_ClassMap[class] then + return HealBot_ClassMap[class] + end + end + + -- 3. General fallback via UnitClass API + local locClass, enClass = UnitClass(unit) + if enClass and enClass ~= "" then return string.upper(enClass) end + if locClass and HealBot_ClassMap[locClass] then return HealBot_ClassMap[locClass] end return locClass end diff --git a/HealBot_Controller_Events.lua b/HealBot_Controller_Events.lua index 5682a44..bad14f4 100644 --- a/HealBot_Controller_Events.lua +++ b/HealBot_Controller_Events.lua @@ -172,7 +172,19 @@ function HealBot_OnUpdate(this, arg1) for unitID in pairs(powerToRefresh) do HealBot_Action_RefreshPower(unitID) end - + -- Periodic resolution of units with missing class identity + if HealBot_Model and HealBot_Model.unresolvedUnits and next(HealBot_Model.unresolvedUnits) then + HealBot_UnresolvedTimer = (HealBot_UnresolvedTimer or 0) + arg1 + if HealBot_UnresolvedTimer >= 0.5 then + HealBot_UnresolvedTimer = 0 + for u in pairs(HealBot_Model.unresolvedUnits) do + if HealBot_Model:UpdateUnitIdentity(u) then + HealBot_View_DirtyUnits[u] = true + end + end + end + end + if HealBot_EquipChangeTimer > 0 then HealBot_EquipChangeTimer = HealBot_EquipChangeTimer - arg1 if HealBot_EquipChangeTimer <= 0 then @@ -274,6 +286,7 @@ local HealBot_EventHandlers = { HealBot_OnEvent_PlayerTargetChanged(this) end, ["PARTY_MEMBERS_CHANGED"] = function(this) + HealBot_Model:RefreshUnit("player") for _, unit in ipairs(HealBot_Model.partyMembers) do HealBot_Model:RefreshUnit(unit) end @@ -284,6 +297,7 @@ local HealBot_EventHandlers = { HealBot_OnEvent_PartyMembersChanged(this) end, ["RAID_ROSTER_UPDATE"] = function(this) + HealBot_Model:RefreshUnit("player") for _, unit in ipairs(HealBot_Model.partyMembers) do HealBot_Model:RefreshUnit(unit) end diff --git a/HealBot_Controller_Range.lua b/HealBot_Controller_Range.lua index 86c12af..ae03b1a 100644 --- a/HealBot_Controller_Range.lua +++ b/HealBot_Controller_Range.lua @@ -33,7 +33,7 @@ function HealBot_Range_Check(unit, range) return _rangeCache[range][unit] end - if ( unit == "player" or string.find(unit, "^Test") ) then + if ( unit and (unit == "player" or UnitIsUnit(unit, "player") or string.find(unit, "^Test")) ) then return_val = 1; elseif HealBot_Integrations_ClassicAPI_Active and UnitDistanceSquared and UnitInLineOfSight then local inSight = UnitInLineOfSight("player", unit) diff --git a/HealBot_Model.lua b/HealBot_Model.lua index 7ebfc74..8b66dad 100644 --- a/HealBot_Model.lua +++ b/HealBot_Model.lua @@ -47,7 +47,10 @@ HealBot_Model = { -- Integration maps unitGUIDs = {}, -- unit -> GUID - guidUnits = {} -- GUID -> unit + guidUnits = {}, -- GUID -> unit + + -- Delayed resolution queue + unresolvedUnits = {} } -------------------------------------------------------------------------------- @@ -87,6 +90,7 @@ end -- HealBot_Model:Initialize: Pre-allocates the unit state dictionary. function HealBot_Model:Initialize() + for k in pairs(self.unresolvedUnits) do self.unresolvedUnits[k] = nil end self:InitUnitState("player") self:InitUnitState("target") self:InitUnitState("pet") @@ -149,7 +153,36 @@ function HealBot_Model:UpdateUnitIdentity(unit) local oldName = self.units[unit].name local oldEnglishClass = self.units[unit].englishClass local name = UnitName(unit) + + -- If UnitName is not ready, attempt raid roster fallback for raid units + if not name or name == "Unknown" then + local _, _, raidIdx = string.find(unit, "^raid(%d+)$") + if raidIdx and GetNumRaidMembers() > 0 then + local rName = GetRaidRosterInfo(tonumber(raidIdx)) + if rName and rName ~= "" and rName ~= "Unknown" then + name = rName + end + end + end + + -- If name is still nil, retain oldName if unit exists + if not name and oldName and UnitExists(unit) then + name = oldName + end + local englishClass = HealBot_UnitClass(unit) + + -- If name is identical, do NOT overwrite an existing valid englishClass with nil + if oldName and name == oldName and not englishClass and oldEnglishClass then + englishClass = oldEnglishClass + end + + -- Track unresolved units (unit exists with a name, but missing englishClass) + if name and not englishClass then + self.unresolvedUnits[unit] = true + else + self.unresolvedUnits[unit] = nil + end if oldName ~= name or oldEnglishClass ~= englishClass then self.units[unit].name = name diff --git a/HealBot_View_Layout.lua b/HealBot_View_Layout.lua index d6785c0..cd1e040 100644 --- a/HealBot_View_Layout.lua +++ b/HealBot_View_Layout.lua @@ -69,10 +69,20 @@ function HealBot_HealthColor(unit, hlth, maxhlth) end local colorMode = HealBot_Config.bcolormode[HealBot_Config.Current_Skin] or 1 + local engClass = nil + if colorMode == 2 and not string.find(unit, "pet") then + engClass = HealBot_Model and HealBot_Model.units[unit] and HealBot_Model.units[unit].englishClass + if not engClass then + engClass = HealBot_UnitClass(unit) + if engClass and HealBot_Model and HealBot_Model.units[unit] then + HealBot_Model.units[unit].englishClass = engClass + end + end + end + if colorMode == 2 and string.find(unit, "pet") then r, g, b = 0.4, 0.9, 0.6 -- Mint color - elseif colorMode == 2 and HealBot_Model and HealBot_Model.units[unit] and HealBot_Model.units[unit].englishClass and RAID_CLASS_COLORS and RAID_CLASS_COLORS[HealBot_Model.units[unit].englishClass] then - local engClass = HealBot_Model.units[unit].englishClass + elseif colorMode == 2 and engClass and RAID_CLASS_COLORS and RAID_CLASS_COLORS[engClass] then r = RAID_CLASS_COLORS[engClass].r g = RAID_CLASS_COLORS[engClass].g b = RAID_CLASS_COLORS[engClass].b @@ -845,14 +855,15 @@ function HealBot_Action_PartyChanged() if GetNumRaidMembers() > 0 then for j = 1, 40 do local unit = "raid" .. j; - local Class = UnitClass(unit); - local ProcessUnit = 0; - local PossibleEmerg = 1; local name, rank, subgroup, level, class, fileName, zone, online, isDead = GetRaidRosterInfo(j); if not name then name = "not known" end if not class then class = "not known" end if not subgroup then subgroup = "not known" end + local Class = UnitClass(unit) or class; + local ProcessUnit = 0; + local PossibleEmerg = 1; + if HealBot_EmergInc[Class] == 1 then ProcessUnit = 1; end diff --git a/README.md b/README.md index 148453d..bafac05 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,8 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ * **Feature - Shapeshift Spell Queue** - Built a native spell queue system for Druid auto-unshifting. Bypasses the "Cannot cast while shapeshifted" error and server lag when AutoUnshift is enabled, firing the heal seamlessly the exact millisecond the form is dropped. Now completely unified to use the standard casting API for maximum reliability across all client setups. * **Bug Fix - CDC Config List** - Fixed an uninitialized dropdown list array that prevented the CDC class monitor configuration from saving correctly. * **Bug Fix - Color Picker Accuracy** - Removed an undocumented multiplier hack on default textures that was artificially brightening debuff bar colors and overriding the user's selected colors from the color picker. +* **Bug Fix - Raid Self-Range & Transparency** - Fixed player health bar appearing transparent in raid instances by checking `UnitIsUnit(unit, "player")` in range detection, preventing false out-of-range failures when player is represented by a `raidX` token. +* **Bug Fix - Raid Class Coloring & Identity Cache** - Fixed missing and delayed raid class colors by querying authoritative server data (`GetRaidRosterInfo`) for raid members, preventing model identity cache wipes, adding a throttled resolution retry in `OnUpdate`, and providing on-demand class color fallbacks in `HealBot_HealthColor`. **v1.6.5** * **Performance Update - Mana Tracking** - Added a fast-path redraw pipeline for unit power changes. This optimization prevents full frame redraws when players naturally regenerate or consume mana, resolving severe FPS drops in 40-man raids while tracking mana. From 3ed13b97f4bc7efc39b07caf75b877c019854c6e Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:57:39 +0200 Subject: [PATCH 2/6] fix: bump addon version to 1.7.6 in TOC file --- HealBotBlue.toc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HealBotBlue.toc b/HealBotBlue.toc index 2a890a3..16457b5 100644 --- a/HealBotBlue.toc +++ b/HealBotBlue.toc @@ -1,6 +1,6 @@ ## Interface: 11200 ## Title: HealBotBlue -## Version: 1.7.5 +## Version: 1.7.6 ## Author: Bluewhale ## Notes: Raidframe replacement for healing, decursing, resource tracking and buffing with optional support for SuperWow, ClassicAPI, UnitXP_SP3 and Nampower. ## SavedVariablesPerCharacter: HealBot_Config From 71b92fe1a6c8cb096afdaea12e26e8370a5501a3 Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Thu, 15 Oct 2026 14:19:10 +0200 Subject: [PATCH 3/6] refactor: unify aura and hoT tracking into a single master dictionary with localized support --- HealBot_Controller_Aura.lua | 57 ++++++++++++++++--------------------- HealBot_Data.lua | 31 ++++++++++++++++++++ HealBot_Localization.de.lua | 1 + HealBot_Localization.en.lua | 1 + HealBot_Localization.fr.lua | 1 + HealBot_Localization.kr.lua | 1 + HealBot_View_Layout.lua | 12 +++----- README.md | 3 ++ 8 files changed, 67 insertions(+), 40 deletions(-) diff --git a/HealBot_Controller_Aura.lua b/HealBot_Controller_Aura.lua index 6b398fe..2e45fc7 100644 --- a/HealBot_Controller_Aura.lua +++ b/HealBot_Controller_Aura.lua @@ -166,17 +166,6 @@ function HealBot_CheckBuffs(unit) end end -local HealBot_TrackedHoTs = { - ["Interface\\Icons\\Spell_Holy_Renew"] = true, - ["Interface\\Icons\\Spell_Nature_Rejuvenation"] = true, - ["Interface\\Icons\\Spell_Nature_ResistNature"] = true, - ["Interface\\Icons\\Spell_Holy_PowerWordShield"] = true, - ["Interface\\Icons\\Spell_Holy_SealOfProtection"] = true, - ["Interface\\Icons\\Spell_Holy_Excorcism"] = true, - ["Interface\\Icons\\btnholyscriptures"] = true, - ["Interface\\Icons\\Spell_Holy_AshesToAshes"] = true, -} - local HealBot_DebuffTypeMap = nil local HealBot_AuraWarningPlayed = {} @@ -190,6 +179,9 @@ function HealBot_OnEvent_UnitAura(this, unit) [HEALBOT_POISON] = HEALBOT_POISON_en, [HEALBOT_CURSE] = HEALBOT_CURSE_en } + if HealBot_Init_TrackedAuras then + HealBot_Init_TrackedAuras() + end end local DebuffType; @@ -280,13 +272,26 @@ function HealBot_OnEvent_UnitAura(this, unit) local buff = UnitBuff(unit, b) if not buff then break end activeBuffs[buff] = true - if HealBot_TrackedHoTs[buff] and iconCount < 10 then + if HealBot_TrackedAura_Textures and HealBot_TrackedAura_Textures[buff] and iconCount < 10 then iconCount = iconCount + 1 HealBot_UnitIcons[unit][iconCount] = buff end b = b + 1 end + local d = 1 + local activeDebuffs = HealBot_GetTable() + while true do + local debuff = UnitDebuff(unit, d) + if not debuff then break end + activeDebuffs[debuff] = true + if HealBot_TrackedAura_Textures and HealBot_TrackedAura_Textures[debuff] and iconCount < 10 then + iconCount = iconCount + 1 + HealBot_UnitIcons[unit][iconCount] = debuff + end + d = d + 1 + end + if HealBot_Integrations_Nampower_Active and HealBot_Nampower_Auras then local guid = HealBot_GetUnitGUID and HealBot_GetUnitGUID(unit) if guid then @@ -294,33 +299,21 @@ function HealBot_OnEvent_UnitAura(this, unit) HealBot_Nampower_NameCache[guid] = UnitName(unit) if HealBot_Nampower_Auras[guid] then - local validHoTs = { - ["Renew"] = "Interface\\Icons\\Spell_Holy_Renew", - ["Rejuvenation"] = "Interface\\Icons\\Spell_Nature_Rejuvenation", - ["Regrowth"] = "Interface\\Icons\\Spell_Nature_ResistNature", - ["Power Word: Shield"] = "Interface\\Icons\\Spell_Holy_PowerWordShield", - } for spellName, _ in pairs(HealBot_Nampower_Auras[guid]) do - local tex = validHoTs[spellName] - if tex and not activeBuffs[tex] then - HealBot_Nampower_Auras[guid][spellName] = nil + local auraInfo = HealBot_TrackedAura_Spells and HealBot_TrackedAura_Spells[spellName] + if auraInfo then + local tex = auraInfo.texture + local isActive = auraInfo.isDebuff and activeDebuffs[tex] or activeBuffs[tex] + if not isActive then + HealBot_Nampower_Auras[guid][spellName] = nil + end end end end end end HealBot_ReleaseTable(activeBuffs) - - local d = 1 - while true do - local debuff = UnitDebuff(unit, d) - if not debuff then break end - if HealBot_TrackedHoTs[debuff] and iconCount < 10 then - iconCount = iconCount + 1 - HealBot_UnitIcons[unit][iconCount] = debuff - end - d = d + 1 - end + HealBot_ReleaseTable(activeDebuffs) if HealBot_UnitDebuff[unit] then if DebuffType and HealBot_Range_Check(unit, 40) == 1 then diff --git a/HealBot_Data.lua b/HealBot_Data.lua index 362c495..3ab8840 100644 --- a/HealBot_Data.lua +++ b/HealBot_Data.lua @@ -603,3 +603,34 @@ function HealBot_ReleaseTable(t) end end +HealBot_TrackedAuras = { + ["Renew"] = { texture = "Interface\\Icons\\Spell_Holy_Renew", isDebuff = false, showTimer = true, locKey = "HEALBOT_RENEW" }, + ["Rejuvenation"] = { texture = "Interface\\Icons\\Spell_Nature_Rejuvenation", isDebuff = false, showTimer = true, locKey = "HEALBOT_REJUVENATION" }, + ["Regrowth"] = { texture = "Interface\\Icons\\Spell_Nature_ResistNature", isDebuff = false, showTimer = true, locKey = "HEALBOT_REGROWTH" }, + ["Power Word: Shield"] = { texture = "Interface\\Icons\\Spell_Holy_PowerWordShield", isDebuff = false, showTimer = true, locKey = "HEALBOT_POWER_WORD_SHIELD" }, + ["Fear Ward"] = { texture = "Interface\\Icons\\Spell_Holy_Excorcism", isDebuff = false, showTimer = true, locKey = "HEALBOT_FEAR_WARD" }, + ["Abolish Disease"] = { texture = "Interface\\Icons\\Spell_Nature_NullifyDisease",isDebuff = false, showTimer = true, locKey = "HEALBOT_ABOLISH_DISEASE" }, + ["Abolish Poison"] = { texture = "Interface\\Icons\\Spell_Nature_NullifyPoison", isDebuff = false, showTimer = true, locKey = "HEALBOT_ABOLISH_POISON" }, + ["Weakened Soul"] = { texture = "Interface\\Icons\\Spell_Holy_AshesToAshes", isDebuff = true, showTimer = true, locKey = "HEALBOT_WEAKENED_SOUL" }, + ["Blessing of Protection"] = { texture = "Interface\\Icons\\Spell_Holy_SealOfProtection", isDebuff = false, showTimer = false }, + ["Holy Scriptures"] = { texture = "Interface\\Icons\\btnholyscriptures", isDebuff = false, showTimer = false }, +}; + +HealBot_TrackedAura_Textures = {}; +HealBot_TrackedAura_Spells = {}; + +function HealBot_Init_TrackedAuras() + for spell, info in pairs(HealBot_TrackedAuras) do + HealBot_TrackedAura_Textures[info.texture] = info + HealBot_TrackedAura_Spells[spell] = info + if info.locKey then + local locName = getglobal(info.locKey) + if locName and locName ~= "" then + HealBot_TrackedAura_Spells[locName] = info + end + end + end +end +HealBot_Init_TrackedAuras(); + + diff --git a/HealBot_Localization.de.lua b/HealBot_Localization.de.lua index cc64ac7..e708384 100644 --- a/HealBot_Localization.de.lua +++ b/HealBot_Localization.de.lua @@ -154,6 +154,7 @@ HEALBOT_BUFF_REGROWTH = "Interface\\Icons\\Spell_Nature_ResistNature" HEALBOT_BUFF_RENEW = "Interface\\Icons\\Spell_Holy_Renew"; HEALBOT_BUFF_FEAR_WARD = "Interface\\Icons\\Spell_Holy_Excorcism"; HEALBOT_DEBUFF_WEAKENED_SOUL = "Interface\\Icons\\Spell_Holy_AshesToAshes"; +HEALBOT_WEAKENED_SOUL = "Geschw\195\164chte Seele"; HEALBOT_DEBUFF_RECENTLY_BANDAGED = "Interface\\Icons\\INV_Misc_Bandage_08"; diff --git a/HealBot_Localization.en.lua b/HealBot_Localization.en.lua index 0147ab8..ae6b7cf 100644 --- a/HealBot_Localization.en.lua +++ b/HealBot_Localization.en.lua @@ -171,6 +171,7 @@ HEALBOT_BUFF_REGROWTH = "Interface\\Icons\\Spell_Nature_ResistNature" HEALBOT_BUFF_RENEW = "Interface\\Icons\\Spell_Holy_Renew"; HEALBOT_BUFF_FEAR_WARD = "Interface\\Icons\\Spell_Holy_Excorcism"; HEALBOT_DEBUFF_WEAKENED_SOUL = "Interface\\Icons\\Spell_Holy_AshesToAshes"; +HEALBOT_WEAKENED_SOUL = "Weakened Soul"; HEALBOT_DEBUFF_RECENTLY_BANDAGED = "Interface\\Icons\\INV_Misc_Bandage_08"; diff --git a/HealBot_Localization.fr.lua b/HealBot_Localization.fr.lua index 7c6f693..b9e2a9a 100644 --- a/HealBot_Localization.fr.lua +++ b/HealBot_Localization.fr.lua @@ -72,6 +72,7 @@ HEALBOT_LESSER_HEAL = "Soins inf\195\169rieurs"; HEALBOT_LESSER_HEALING_WAVE = "Vague de soins mineurs"; HEALBOT_MEND_PET = "Soigner un Familier"; HEALBOT_POWER_WORD_SHIELD = "Mot de pouvoir : Bouclier"; +HEALBOT_WEAKENED_SOUL = "\195\130me affaiblie"; HEALBOT_REGROWTH = "R\195\169tablissement"; HEALBOT_RENEW = "R\195\169novation"; HEALBOT_REJUVENATION = "R\195\169cup\195\169ration"; diff --git a/HealBot_Localization.kr.lua b/HealBot_Localization.kr.lua index 0b7e0ea..0eda4b1 100644 --- a/HealBot_Localization.kr.lua +++ b/HealBot_Localization.kr.lua @@ -54,6 +54,7 @@ if (GetLocale() == "koKR") then HEALBOT_LESSER_HEALING_WAVE = "하급 치유의 물결"; HEALBOT_MEND_PET = "동물 치료"; HEALBOT_POWER_WORD_SHIELD = "신의 권능: 보호막"; + HEALBOT_WEAKENED_SOUL = "신체 취약"; HEALBOT_REGROWTH = "재생"; HEALBOT_RENEW = "소생"; HEALBOT_REJUVENATION = "회복"; diff --git a/HealBot_View_Layout.lua b/HealBot_View_Layout.lua index cd1e040..e5bf629 100644 --- a/HealBot_View_Layout.lua +++ b/HealBot_View_Layout.lua @@ -1372,12 +1372,7 @@ end local HealBot_PlayerHoTsListFrame = nil local HealBot_PlayerHoTFrames = {} -local HealBot_PlayerHoTs_ValidHoTs = { - ["Renew"] = "Interface\\Icons\\Spell_Holy_Renew", - ["Rejuvenation"] = "Interface\\Icons\\Spell_Nature_Rejuvenation", - ["Regrowth"] = "Interface\\Icons\\Spell_Nature_ResistNature", - ["Power Word: Shield"] = "Interface\\Icons\\Spell_Holy_PowerWordShield", -} + local HealBot_PlayerHoTs_ListCache = {} @@ -1409,7 +1404,8 @@ function HealBot_Action_UpdatePlayerHoTs() if HealBot_Integrations_Nampower_Active and HealBot_Nampower_Auras then for targetName, auras in pairs(HealBot_Nampower_Auras) do for spellName, expiration in pairs(auras) do - if HealBot_PlayerHoTs_ValidHoTs[spellName] then + local auraInfo = HealBot_TrackedAura_Spells and HealBot_TrackedAura_Spells[spellName] + if auraInfo and auraInfo.showTimer then if expiration > currentTime then numHoTs = numHoTs + 1 if not HealBot_PlayerHoTs_ListCache[numHoTs] then @@ -1504,7 +1500,7 @@ function HealBot_Action_UpdatePlayerHoTs() end local spellId = HealBot_GetSpellId and HealBot_GetSpellId(hot.spell) or nil - local tex = HealBot_PlayerHoTs_ValidHoTs[hot.spell] or "Interface\\Icons\\INV_Misc_QuestionMark" + local tex = (HealBot_TrackedAura_Spells and HealBot_TrackedAura_Spells[hot.spell] and HealBot_TrackedAura_Spells[hot.spell].texture) or "Interface\\Icons\\INV_Misc_QuestionMark" if spellId then tex = GetSpellTexture(spellId, "BOOKTYPE_SPELL") or tex end diff --git a/README.md b/README.md index bafac05..53fa0d1 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,9 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ ### Change Log +**v1.7.6** +* **Refactor & Feature - Unified Aura & HoT Tracking** - Unified bar icons and HoT timers into a single master dictionary (`HealBot_TrackedAuras`) in `HealBot_Data.lua`. Added Weakened Soul, Fear Ward, Abolish Disease, and Abolish Poison to active timer bars with localized client support and debuff-aware pruning. + **v1.7.5** * **Feature - Test Bars** - Implemented a "Test Bars" button and slider in the Skins options tab, allowing you to spawn and adjust a dummy raid group of up to 40 members without being in a real raid. This allows for easy layout and appearance customization out of combat. * **UI - Skins Options & Custom Health Gradients** - Added configurable Max HP and Min HP colour pickers with descriptive labels for custom gradient support. Fixed colour picker button click interceptors and hooked ColorPickerFrame callbacks. Integrated global font selector across raid frame text, headers, Options button, and preview bars. Reorganized Skins tab layout to anchor Test Bars cleanly in Column 2. From 7b8c0251fc063d0266ea802596c98f5635518ae3 Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Wed, 21 Oct 2026 22:33:01 +0200 Subject: [PATCH 4/6] fix: dropdown syntax + converted to named click handlers --- HealBot_Controller_Events.lua | 22 ++++++ HealBot_Data.lua | 4 + HealBot_Integrations.lua | 3 + HealBot_Options_Integrations.lua | 119 ++++++++++++++++++++++++++++ HealBot_Options_Integrations.xml | 76 +++++++++++++++++- HealBot_View_Layout.lua | 130 ++++++++++++++++++++++++++----- README.md | 1 + 7 files changed, 334 insertions(+), 21 deletions(-) diff --git a/HealBot_Controller_Events.lua b/HealBot_Controller_Events.lua index bad14f4..b26d44e 100644 --- a/HealBot_Controller_Events.lua +++ b/HealBot_Controller_Events.lua @@ -197,6 +197,28 @@ function HealBot_OnUpdate(this, arg1) if HealBot_Config.HealBot_Integrations_ShowHoTTimers == 1 then if not HealBot_HoT_Update_Timer or GetTime() >= HealBot_HoT_Update_Timer then HealBot_HoT_Update_Timer = GetTime() + 0.25 + local displayMode = HealBot_Config.HealBot_Integrations_HoTTimerDisplayMode or 0 + if displayMode == 1 then + -- Icon Overlay mode: dirty-flag units with active Nampower auras + if HealBot_Integrations_Nampower_Active and HealBot_Nampower_Auras then + local now = GetTime() + for guid, auras in pairs(HealBot_Nampower_Auras) do + local hasActive = false + for _, exp in pairs(auras) do + if exp > now then hasActive = true; break end + end + if hasActive then + local unitID = (HealBot_Model and HealBot_Model:GetUnitByGUID(guid)) + if not unitID and HealBot_PlayerGUID and guid == HealBot_PlayerGUID then + unitID = "player" + end + if unitID and not string.find(unitID, "pet") then + HealBot_View_DirtyUnits[unitID] = true + end + end + end + end + end if HealBot_Action_UpdatePlayerHoTs then HealBot_Action_UpdatePlayerHoTs() end diff --git a/HealBot_Data.lua b/HealBot_Data.lua index 3ab8840..0184c6a 100644 --- a/HealBot_Data.lua +++ b/HealBot_Data.lua @@ -269,6 +269,8 @@ HealBot_ConfigDefaults = { HealBot_Integrations_SuperWoW = 0, HealBot_Integrations_ClassicAPI = 0, HealBot_Integrations_ShowHoTTimers = 0, + HealBot_Integrations_HoTTimerPosition = "BELOW", + HealBot_Integrations_HoTTimerDisplayMode = 0, }; HealBot_Config = {}; @@ -621,11 +623,13 @@ HealBot_TrackedAura_Spells = {}; function HealBot_Init_TrackedAuras() for spell, info in pairs(HealBot_TrackedAuras) do + info.spell = spell HealBot_TrackedAura_Textures[info.texture] = info HealBot_TrackedAura_Spells[spell] = info if info.locKey then local locName = getglobal(info.locKey) if locName and locName ~= "" then + info.locName = locName HealBot_TrackedAura_Spells[locName] = info end end diff --git a/HealBot_Integrations.lua b/HealBot_Integrations.lua index 65a34a7..f6e0884 100644 --- a/HealBot_Integrations.lua +++ b/HealBot_Integrations.lua @@ -145,5 +145,8 @@ end HealBot_Options_Integrations_ShowHoTTimers:SetChecked(0) HealBot_Config.HealBot_Integrations_ShowHoTTimers = 0 end + if HealBot_Options_Integrations_UpdateDropdownStates then + HealBot_Options_Integrations_UpdateDropdownStates() + end end end diff --git a/HealBot_Options_Integrations.lua b/HealBot_Options_Integrations.lua index d9d754b..ffd541c 100644 --- a/HealBot_Options_Integrations.lua +++ b/HealBot_Options_Integrations.lua @@ -1,3 +1,34 @@ +-- HealBot_Options_Integrations.lua + +local function HealBot_Options_SetDropDownState(frame, enabled) + if not frame then return end + local button = getglobal(frame:GetName() .. "Button") + local text = getglobal(frame:GetName() .. "Text") + if button then + if enabled then + button:Enable() + else + button:Disable() + end + end + if text then + if enabled then + text:SetTextColor(1, 1, 1) + else + text:SetTextColor(0.5, 0.5, 0.5) + end + end +end + +-- Update enabled/disabled state of dropdowns based on current config +function HealBot_Options_Integrations_UpdateDropdownStates() + local timersEnabled = HealBot_Config.HealBot_Integrations_ShowHoTTimers == 1 + local displayMode = HealBot_Config.HealBot_Integrations_HoTTimerDisplayMode or 0 + + HealBot_Options_SetDropDownState(HealBot_Options_Integrations_DisplayModeDropdown, timersEnabled) + HealBot_Options_SetDropDownState(HealBot_Options_Integrations_PositionDropdown, timersEnabled and (displayMode == 0)) +end + -- HealBot_Options_Integrations_OnShow: UI handler for OnShow options panel. function HealBot_Options_Integrations_OnShow(this) HealBot_Options_Integrations_UnitXP:SetChecked(HealBot_Config.HealBot_Integrations_UnitXP); @@ -5,4 +36,92 @@ function HealBot_Options_Integrations_OnShow(this) HealBot_Options_Integrations_SuperWoW:SetChecked(HealBot_Config.HealBot_Integrations_SuperWoW); HealBot_Options_Integrations_ClassicAPI:SetChecked(HealBot_Config.HealBot_Integrations_ClassicAPI); HealBot_Options_Integrations_ShowHoTTimers:SetChecked(HealBot_Config.HealBot_Integrations_ShowHoTTimers); + + -- Set display mode dropdown text + local displayMode = HealBot_Config.HealBot_Integrations_HoTTimerDisplayMode or 0 + if displayMode == 1 then + UIDropDownMenu_SetText("Icon Overlay", HealBot_Options_Integrations_DisplayModeDropdown) + else + UIDropDownMenu_SetText("List", HealBot_Options_Integrations_DisplayModeDropdown) + end + + -- Set position dropdown text + local pos = HealBot_Config.HealBot_Integrations_HoTTimerPosition or "BELOW" + local posLabels = { BELOW = "Below", ABOVE = "Above", LEFT = "Left", RIGHT = "Right" } + UIDropDownMenu_SetText(posLabels[pos] or "Below", HealBot_Options_Integrations_PositionDropdown) + + -- Update enabled/disabled state + HealBot_Options_Integrations_UpdateDropdownStates() +end + +-- Display Mode dropdown click handler (relying on global this) +function HealBot_Options_Integrations_DisplayModeDropdown_OnClick() + UIDropDownMenu_SetSelectedID(HealBot_Options_Integrations_DisplayModeDropdown, this:GetID()) + HealBot_Config.HealBot_Integrations_HoTTimerDisplayMode = this.value + UIDropDownMenu_SetText(this:GetText(), HealBot_Options_Integrations_DisplayModeDropdown) + HealBot_Options_Integrations_UpdateDropdownStates() + if HealBot_Action_UnitButtons then + for u in pairs(HealBot_Action_UnitButtons) do + HealBot_View_DirtyUnits[u] = true + end + end + if HealBot_Action_UpdatePlayerHoTs then + HealBot_Action_UpdatePlayerHoTs() + end +end + +-- Display Mode dropdown init +function HealBot_Options_Integrations_DisplayModeDropdown_Init() + local mode = HealBot_Config.HealBot_Integrations_HoTTimerDisplayMode or 0 + local info + + info = {} + info.text = "List" + info.value = 0 + info.func = HealBot_Options_Integrations_DisplayModeDropdown_OnClick + if mode == 0 then + info.checked = 1 + end + UIDropDownMenu_AddButton(info) + + info = {} + info.text = "Icon Overlay" + info.value = 1 + info.func = HealBot_Options_Integrations_DisplayModeDropdown_OnClick + if mode == 1 then + info.checked = 1 + end + UIDropDownMenu_AddButton(info) +end + +-- Position dropdown click handler (relying on global this) +function HealBot_Options_Integrations_PositionDropdown_OnClick() + UIDropDownMenu_SetSelectedID(HealBot_Options_Integrations_PositionDropdown, this:GetID()) + HealBot_Config.HealBot_Integrations_HoTTimerPosition = this.value + UIDropDownMenu_SetText(this:GetText(), HealBot_Options_Integrations_PositionDropdown) + if HealBot_Action_UpdatePlayerHoTs then + HealBot_Action_UpdatePlayerHoTs() + end +end + +-- Position dropdown init +function HealBot_Options_Integrations_PositionDropdown_Init() + local positions = { + { value = "BELOW", label = "Below" }, + { value = "ABOVE", label = "Above" }, + { value = "LEFT", label = "Left" }, + { value = "RIGHT", label = "Right" }, + } + local currentPos = HealBot_Config.HealBot_Integrations_HoTTimerPosition or "BELOW" + + for _, p in ipairs(positions) do + local info = {} + info.text = p.label + info.value = p.value + info.func = HealBot_Options_Integrations_PositionDropdown_OnClick + if currentPos == p.value then + info.checked = 1 + end + UIDropDownMenu_AddButton(info) + end end diff --git a/HealBot_Options_Integrations.xml b/HealBot_Options_Integrations.xml index bec1dde..47795a9 100644 --- a/HealBot_Options_Integrations.xml +++ b/HealBot_Options_Integrations.xml @@ -94,9 +94,81 @@ HealBot_Config.HealBot_Integrations_ShowHoTTimers = this:GetChecked() and 1 or 0; + HealBot_Options_Integrations_UpdateDropdownStates(); + if HealBot_Action_UnitButtons then + for u in pairs(HealBot_Action_UnitButtons) do + HealBot_View_DirtyUnits[u] = true; + end + end + if HealBot_Action_UpdatePlayerHoTs then + HealBot_Action_UpdatePlayerHoTs(); + end + + + + + + + + + + + + + + + + + + + + + + + + + + + UIDropDownMenu_SetWidth(120, this); + UIDropDownMenu_Initialize(this, HealBot_Options_Integrations_DisplayModeDropdown_Init); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UIDropDownMenu_SetWidth(120, this); + UIDropDownMenu_Initialize(this, HealBot_Options_Integrations_PositionDropdown_Init); + + + + @@ -114,9 +186,9 @@ - + - + diff --git a/HealBot_View_Layout.lua b/HealBot_View_Layout.lua index e5bf629..8e06ccc 100644 --- a/HealBot_View_Layout.lua +++ b/HealBot_View_Layout.lua @@ -283,6 +283,10 @@ function HealBot_Action_EnableButton(button) end local iconSize = HealBot_Config.biconsize[HealBot_Config.Current_Skin] or 12 + local isPet = string.find(unit, "pet") ~= nil + local overlayMode = (HealBot_Config.HealBot_Integrations_ShowHoTTimers == 1) and ((HealBot_Config.HealBot_Integrations_HoTTimerDisplayMode or 0) == 1) and not isPet + local unitGUID = overlayMode and ((HealBot_GetUnitGUID and HealBot_GetUnitGUID(unit)) or (unit == "player" and HealBot_PlayerGUID)) + local currentTime = overlayMode and GetTime() for i = 1, 10 do local icon = button["icon"..i] if not icon then @@ -305,10 +309,54 @@ function HealBot_Action_EnableButton(button) if not icon:IsVisible() then icon:Show() end + + -- Icon overlay timer text + local iconText = button["iconText"..i] + if not iconText then + iconText = button:CreateFontString(nil, "OVERLAY") + iconText:SetFont("Fonts\\ARIALN.TTF", 10, "OUTLINE") + iconText:SetPoint("CENTER", icon, "CENTER", 0, 0) + iconText:SetTextColor(1, 1, 1, 1) + button["iconText"..i] = iconText + end + if overlayMode and unitGUID and HealBot_Nampower_Auras and HealBot_Nampower_Auras[unitGUID] then + local texturePath = HealBot_UnitIcons[unit][i] + local auraInfo = HealBot_TrackedAura_Textures and HealBot_TrackedAura_Textures[texturePath] + if auraInfo and auraInfo.showTimer then + local expiration = (auraInfo.locName and HealBot_Nampower_Auras[unitGUID][auraInfo.locName]) + or (auraInfo.spell and HealBot_Nampower_Auras[unitGUID][auraInfo.spell]) + if not expiration and HealBot_TrackedAura_Spells then + for spellName, spInfo in pairs(HealBot_TrackedAura_Spells) do + if spInfo.texture == texturePath and spInfo.showTimer then + expiration = HealBot_Nampower_Auras[unitGUID][spellName] + if expiration then break end + end + end + end + if expiration and expiration > currentTime then + local timeLeft = math.floor(expiration - currentTime) + iconText:SetText(timeLeft) + iconText:Show() + else + iconText:SetText("") + iconText:Hide() + end + else + iconText:SetText("") + iconText:Hide() + end + else + iconText:SetText("") + iconText:Hide() + end else if icon:IsVisible() then icon:Hide() end + local iconText = button["iconText"..i] + if iconText and iconText:IsVisible() then + iconText:Hide() + end end end end @@ -1380,22 +1428,65 @@ local function HealBot_PlayerHoTs_SortComparator(a, b) return (a.expires or 0) < (b.expires or 0) end +-- Reposition the HoT timer list frame based on config +local function HealBot_HoTTimerList_Reanchor() + if not HealBot_PlayerHoTsListFrame then return end + local pos = HealBot_Config.HealBot_Integrations_HoTTimerPosition or "BELOW" + HealBot_PlayerHoTsListFrame:ClearAllPoints() + if pos == "ABOVE" then + HealBot_PlayerHoTsListFrame:SetPoint("BOTTOMLEFT", HealBot_Action, "TOPLEFT", 0, 2) + elseif pos == "RIGHT" then + HealBot_PlayerHoTsListFrame:SetPoint("TOPLEFT", HealBot_Action, "TOPRIGHT", 2, 0) + elseif pos == "LEFT" then + HealBot_PlayerHoTsListFrame:SetPoint("TOPRIGHT", HealBot_Action, "TOPLEFT", -2, 0) + else -- BELOW (default) + HealBot_PlayerHoTsListFrame:SetPoint("TOPLEFT", HealBot_Action, "BOTTOMLEFT", 0, -2) + end +end + +-- Reposition child HoT frames for stacking direction +local function HealBot_HoTTimerList_StackChild(frame, index) + local pos = HealBot_Config.HealBot_Integrations_HoTTimerPosition or "BELOW" + frame:ClearAllPoints() + if pos == "ABOVE" then + if index == 1 then + frame:SetPoint("BOTTOMLEFT", HealBot_PlayerHoTsListFrame, "BOTTOMLEFT", 0, 0) + else + frame:SetPoint("BOTTOMLEFT", HealBot_PlayerHoTFrames[index-1], "TOPLEFT", 0, 2) + end + else + if index == 1 then + frame:SetPoint("TOPLEFT", HealBot_PlayerHoTsListFrame, "TOPLEFT", 0, 0) + else + frame:SetPoint("TOPLEFT", HealBot_PlayerHoTFrames[index-1], "BOTTOMLEFT", 0, -2) + end + end +end + function HealBot_Action_UpdatePlayerHoTs() if not HealBot_PlayerHoTsListFrame then HealBot_PlayerHoTsListFrame = CreateFrame("Frame", "HealBot_PlayerHoTsListFrame", HealBot_Action) HealBot_PlayerHoTsListFrame:SetWidth(128) HealBot_PlayerHoTsListFrame:SetHeight(1) - HealBot_PlayerHoTsListFrame:SetPoint("TOPLEFT", HealBot_Action, "BOTTOMLEFT", 0, -2) HealBot_PlayerHoTsListFrame:Show() end - + + HealBot_HoTTimerList_Reanchor() + if HealBot_Config.HealBot_Integrations_ShowHoTTimers ~= 1 then HealBot_PlayerHoTsListFrame:Hide() return - else - HealBot_PlayerHoTsListFrame:Show() end + -- If display mode is Icon Overlay, hide the list and return + local displayMode = HealBot_Config.HealBot_Integrations_HoTTimerDisplayMode or 0 + if displayMode == 1 then + HealBot_PlayerHoTsListFrame:Hide() + return + end + + HealBot_PlayerHoTsListFrame:Show() + local currentTime = GetTime() -- Clear cache instead of reallocating @@ -1403,17 +1494,20 @@ function HealBot_Action_UpdatePlayerHoTs() if HealBot_Integrations_Nampower_Active and HealBot_Nampower_Auras then for targetName, auras in pairs(HealBot_Nampower_Auras) do - for spellName, expiration in pairs(auras) do - local auraInfo = HealBot_TrackedAura_Spells and HealBot_TrackedAura_Spells[spellName] - if auraInfo and auraInfo.showTimer then - if expiration > currentTime then - numHoTs = numHoTs + 1 - if not HealBot_PlayerHoTs_ListCache[numHoTs] then - HealBot_PlayerHoTs_ListCache[numHoTs] = {} + local unitID = HealBot_Model:GetUnitByGUID(targetName) or HealBot_Model:GetUnitIDByName(targetName) + if not (unitID and string.find(unitID, "pet")) then + for spellName, expiration in pairs(auras) do + local auraInfo = HealBot_TrackedAura_Spells and HealBot_TrackedAura_Spells[spellName] + if auraInfo and auraInfo.showTimer then + if expiration > currentTime then + numHoTs = numHoTs + 1 + if not HealBot_PlayerHoTs_ListCache[numHoTs] then + HealBot_PlayerHoTs_ListCache[numHoTs] = {} + end + HealBot_PlayerHoTs_ListCache[numHoTs].name = targetName + HealBot_PlayerHoTs_ListCache[numHoTs].spell = spellName + HealBot_PlayerHoTs_ListCache[numHoTs].expires = expiration end - HealBot_PlayerHoTs_ListCache[numHoTs].name = targetName - HealBot_PlayerHoTs_ListCache[numHoTs].spell = spellName - HealBot_PlayerHoTs_ListCache[numHoTs].expires = expiration end end end @@ -1435,11 +1529,6 @@ function HealBot_Action_UpdatePlayerHoTs() frame = CreateFrame("Frame", "HealBot_PlayerHoT" .. i, HealBot_PlayerHoTsListFrame) frame:SetWidth(128) frame:SetHeight(16) - if i == 1 then - frame:SetPoint("TOPLEFT", HealBot_PlayerHoTsListFrame, "TOPLEFT", 0, 0) - else - frame:SetPoint("TOPLEFT", HealBot_PlayerHoTFrames[i-1], "BOTTOMLEFT", 0, -2) - end local icon = frame:CreateTexture(nil, "ARTWORK") icon:SetWidth(14) @@ -1454,6 +1543,9 @@ function HealBot_Action_UpdatePlayerHoTs() HealBot_PlayerHoTFrames[i] = frame end + + -- Reposition child for stacking direction + HealBot_HoTTimerList_StackChild(frame, i) local hot = HealBot_PlayerHoTs_ListCache[i] local timeLeft = math.floor(hot.expires - currentTime) diff --git a/README.md b/README.md index 53fa0d1..4578ab4 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ ### Change Log **v1.7.6** +* **Feature - HoT Timer Position & Icon Overlay Display Modes** - Added configurable HoT Timer list position dropdown (`Below`, `Above`, `Left`, `Right`) relative to raid frames. Added alternative `Icon Overlay` display mode rendering countdown numbers directly on unit bar HoT icons. Excluded pets from timer lists and overlay numbers. Optimized expiration lookup with O(1) cached texture-to-spell mappings. * **Refactor & Feature - Unified Aura & HoT Tracking** - Unified bar icons and HoT timers into a single master dictionary (`HealBot_TrackedAuras`) in `HealBot_Data.lua`. Added Weakened Soul, Fear Ward, Abolish Disease, and Abolish Poison to active timer bars with localized client support and debuff-aware pruning. **v1.7.5** From 5a77b2298dcb2bdeed73a08d5d88c6d985418c58 Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Mon, 2 Nov 2026 17:13:37 +0100 Subject: [PATCH 5/6] refactor: improve Nampower aura tracking and GUID management integration --- HealBot_Controller_Events.lua | 44 ++-- HealBot_Integrations.lua | 98 ++++++--- HealBot_Model.lua | 70 ++++++- HealBot_Options_Integrations.lua | 13 +- HealBot_View_Layout.lua | 349 +++++++++++++++++++------------ README.md | 1 + 6 files changed, 375 insertions(+), 200 deletions(-) diff --git a/HealBot_Controller_Events.lua b/HealBot_Controller_Events.lua index b26d44e..be708d6 100644 --- a/HealBot_Controller_Events.lua +++ b/HealBot_Controller_Events.lua @@ -197,34 +197,22 @@ function HealBot_OnUpdate(this, arg1) if HealBot_Config.HealBot_Integrations_ShowHoTTimers == 1 then if not HealBot_HoT_Update_Timer or GetTime() >= HealBot_HoT_Update_Timer then HealBot_HoT_Update_Timer = GetTime() + 0.25 - local displayMode = HealBot_Config.HealBot_Integrations_HoTTimerDisplayMode or 0 - if displayMode == 1 then - -- Icon Overlay mode: dirty-flag units with active Nampower auras - if HealBot_Integrations_Nampower_Active and HealBot_Nampower_Auras then - local now = GetTime() - for guid, auras in pairs(HealBot_Nampower_Auras) do - local hasActive = false - for _, exp in pairs(auras) do - if exp > now then hasActive = true; break end - end - if hasActive then - local unitID = (HealBot_Model and HealBot_Model:GetUnitByGUID(guid)) - if not unitID and HealBot_PlayerGUID and guid == HealBot_PlayerGUID then - unitID = "player" - end - if unitID and not string.find(unitID, "pet") then - HealBot_View_DirtyUnits[unitID] = true - end - end - end - end - end - if HealBot_Action_UpdatePlayerHoTs then + if HealBot_UpdateActiveHoTTimers then + HealBot_UpdateActiveHoTTimers() + elseif HealBot_Action_UpdatePlayerHoTs then HealBot_Action_UpdatePlayerHoTs() end end - elseif HealBot_PlayerHoTsListFrame and HealBot_PlayerHoTsListFrame:IsVisible() then - HealBot_PlayerHoTsListFrame:Hide() + else + if (HealBot_PlayerHoTsListFrame and HealBot_PlayerHoTsListFrame:IsVisible()) or HealBot_Overlays_Active then + if HealBot_PlayerHoTsListFrame and HealBot_PlayerHoTsListFrame:IsVisible() then + HealBot_PlayerHoTsListFrame:Hide() + end + if HealBot_ClearHoTOverlays then + HealBot_ClearHoTOverlays() + end + HealBot_Overlays_Active = false + end end HealBot_Timer1 = HealBot_Timer1 + arg1; @@ -315,6 +303,9 @@ local HealBot_EventHandlers = { for _, unit in ipairs(HealBot_Model.raidMembers) do HealBot_Model:RefreshUnit(unit) end + if HealBot_Model and HealBot_Model.PopulateAllGUIDs then + HealBot_Model:PopulateAllGUIDs() + end HealBot_Model:NotifyObservers("ROSTER_CHANGED") HealBot_OnEvent_PartyMembersChanged(this) end, @@ -326,6 +317,9 @@ local HealBot_EventHandlers = { for _, unit in ipairs(HealBot_Model.raidMembers) do HealBot_Model:RefreshUnit(unit) end + if HealBot_Model and HealBot_Model.PopulateAllGUIDs then + HealBot_Model:PopulateAllGUIDs() + end HealBot_Model:NotifyObservers("ROSTER_CHANGED") HealBot_OnEvent_PartyMembersChanged(this) end, diff --git a/HealBot_Integrations.lua b/HealBot_Integrations.lua index f6e0884..a8ad1a0 100644 --- a/HealBot_Integrations.lua +++ b/HealBot_Integrations.lua @@ -40,11 +40,15 @@ function HealBot_Integrations_Toggle() if not HealBot_Integrations_Nampower_Active then return end if event == "AURA_CAST_ON_SELF" or event == "AURA_CAST_ON_OTHER" then 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 + if not caster or not target or not duration or duration <= 0 then return end -- The player is ALWAYS the caster in these events for Nampower if not HealBot_PlayerGUID then HealBot_PlayerGUID = caster + if HealBot_Model and HealBot_Model.unitGUIDs then + HealBot_Model.unitGUIDs["player"] = caster + HealBot_Model.guidUnits[caster] = "player" + end end local spellName = GetSpellRecField(spellID, "name") @@ -57,9 +61,31 @@ function HealBot_Integrations_Toggle() 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) + if not HealBot_Nampower_NameCache then HealBot_Nampower_NameCache = HealBot_GetTable() end + if event == "AURA_CAST_ON_SELF" then + HealBot_Nampower_NameCache[target] = UnitName("player") + HealBot_OnEvent_UnitAura(nil, "player") + else + local tName = nil + if UnitExists("target") then + local curTargetGuid = HealBot_GetUnitGUID and HealBot_GetUnitGUID("target") + if curTargetGuid and (curTargetGuid == target or string.lower(curTargetGuid) == string.lower(target)) then + tName = UnitName("target") + end + end + local unitID = HealBot_Model and HealBot_Model:GetUnitByGUID(target) + if not unitID and HealBot_PlayerGUID and (target == HealBot_PlayerGUID or string.lower(target) == string.lower(HealBot_PlayerGUID)) then + unitID = "player" + end + if not tName and unitID then + tName = UnitName(unitID) or (HealBot_Model and HealBot_Model.units[unitID] and HealBot_Model.units[unitID].name) + end + if tName then + HealBot_Nampower_NameCache[target] = tName + end + if unitID then + HealBot_OnEvent_UnitAura(nil, unitID) + end end end end) @@ -75,40 +101,11 @@ function HealBot_Integrations_Toggle() 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:GetUnitByGUID(targetName) and not HealBot_Model:GetUnitIDByName(targetName) then - HealBot_ReleaseTable(auras) - 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_ReleaseTable(auras) - 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 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"); @@ -135,6 +132,10 @@ end HealBot_AddDebug("ClassicAPI Integration: DISABLED"); end + if HealBot_Model and HealBot_Model.PopulateAllGUIDs then + HealBot_Model:PopulateAllGUIDs() + end + if HealBot_Options_Integrations_ShowHoTTimers then if HealBot_Integrations_Nampower_Active or HealBot_Integrations_SuperWoW_Active or HealBot_Integrations_ClassicAPI_Active then HealBot_Options_Integrations_ShowHoTTimers:Enable() @@ -150,3 +151,32 @@ end 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, or is the player + local isPlayer = (HealBot_PlayerGUID and string.lower(targetName) == string.lower(HealBot_PlayerGUID)) + or (UnitName("player") and string.lower(targetName) == string.lower(UnitName("player"))) + if not isPlayer and not HealBot_Model:GetUnitByGUID(targetName) and not HealBot_Model:GetUnitIDByName(targetName) then + HealBot_ReleaseTable(auras) + 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_ReleaseTable(auras) + HealBot_Nampower_Auras[targetName] = nil + end + end + end +end diff --git a/HealBot_Model.lua b/HealBot_Model.lua index 8b66dad..4b025f8 100644 --- a/HealBot_Model.lua +++ b/HealBot_Model.lua @@ -193,7 +193,9 @@ function HealBot_Model:UpdateUnitIdentity(unit) local guid = HealBot_GetUnitGUID(unit) if guid and guid ~= "0" and guid ~= "0x0000000000000000" then self.unitGUIDs[unit] = guid - self.guidUnits[guid] = unit + if unit ~= "target" then + self.guidUnits[guid] = unit + end end end return true -- Identity changed @@ -201,10 +203,74 @@ function HealBot_Model:UpdateUnitIdentity(unit) return false end +-- HealBot_Model:PopulateAllGUIDs: Refreshes unit-to-GUID and GUID-to-unit mappings for all current roster units. +function HealBot_Model:PopulateAllGUIDs() + if not (HealBot_Integrations_SuperWoW_Active or HealBot_Integrations_ClassicAPI_Active or HealBot_Integrations_Nampower_Active) or not HealBot_GetUnitGUID then return end + + local playerGuid = HealBot_GetUnitGUID("player") + if playerGuid and playerGuid ~= "0" and playerGuid ~= "0x0000000000000000" then + self.unitGUIDs["player"] = playerGuid + self.guidUnits[playerGuid] = "player" + HealBot_PlayerGUID = playerGuid + end + + for _, unit in ipairs(self.partyMembers) do + local guid = HealBot_GetUnitGUID(unit) + if guid and guid ~= "0" and guid ~= "0x0000000000000000" then + self.unitGUIDs[unit] = guid + self.guidUnits[guid] = unit + end + end + + for _, unit in ipairs(self.raidMembers) do + local guid = HealBot_GetUnitGUID(unit) + if guid and guid ~= "0" and guid ~= "0x0000000000000000" then + self.unitGUIDs[unit] = guid + self.guidUnits[guid] = unit + end + end +end + -- HealBot_Model:GetUnitByGUID: Internal utility: HealBot_Model:GetUnitByGUID function HealBot_Model:GetUnitByGUID(guid) if not guid then return nil end - return self.guidUnits[guid] + local unit = self.guidUnits[guid] + if unit and UnitExists(unit) then return unit end + + local lowerGuid = string.lower(guid) + if HealBot_PlayerGUID and string.lower(HealBot_PlayerGUID) == lowerGuid then + self.guidUnits[guid] = "player" + return "player" + end + if HealBot_GetUnitGUID then + local pGuid = HealBot_GetUnitGUID("player") + if pGuid and string.lower(pGuid) == lowerGuid then + self.guidUnits[guid] = "player" + HealBot_PlayerGUID = pGuid + return "player" + end + for i = 1, 40 do + local u = "raid" .. i + if UnitExists(u) then + local uGuid = HealBot_GetUnitGUID(u) + if uGuid and string.lower(uGuid) == lowerGuid then + self.guidUnits[guid] = u + return u + end + end + end + for i = 1, 4 do + local u = "party" .. i + if UnitExists(u) then + local uGuid = HealBot_GetUnitGUID(u) + if uGuid and string.lower(uGuid) == lowerGuid then + self.guidUnits[guid] = u + return u + end + end + end + end + return nil end -- HealBot_Model:GetUnitIDByName: Looks up a group unit token by player name. diff --git a/HealBot_Options_Integrations.lua b/HealBot_Options_Integrations.lua index ffd541c..8af3dda 100644 --- a/HealBot_Options_Integrations.lua +++ b/HealBot_Options_Integrations.lua @@ -26,7 +26,7 @@ function HealBot_Options_Integrations_UpdateDropdownStates() local displayMode = HealBot_Config.HealBot_Integrations_HoTTimerDisplayMode or 0 HealBot_Options_SetDropDownState(HealBot_Options_Integrations_DisplayModeDropdown, timersEnabled) - HealBot_Options_SetDropDownState(HealBot_Options_Integrations_PositionDropdown, timersEnabled and (displayMode == 0)) + HealBot_Options_SetDropDownState(HealBot_Options_Integrations_PositionDropdown, timersEnabled and (displayMode == 0 or displayMode == 2)) end -- HealBot_Options_Integrations_OnShow: UI handler for OnShow options panel. @@ -41,6 +41,8 @@ function HealBot_Options_Integrations_OnShow(this) local displayMode = HealBot_Config.HealBot_Integrations_HoTTimerDisplayMode or 0 if displayMode == 1 then UIDropDownMenu_SetText("Icon Overlay", HealBot_Options_Integrations_DisplayModeDropdown) + elseif displayMode == 2 then + UIDropDownMenu_SetText("Both", HealBot_Options_Integrations_DisplayModeDropdown) else UIDropDownMenu_SetText("List", HealBot_Options_Integrations_DisplayModeDropdown) end @@ -92,6 +94,15 @@ function HealBot_Options_Integrations_DisplayModeDropdown_Init() info.checked = 1 end UIDropDownMenu_AddButton(info) + + info = {} + info.text = "Both" + info.value = 2 + info.func = HealBot_Options_Integrations_DisplayModeDropdown_OnClick + if mode == 2 then + info.checked = 1 + end + UIDropDownMenu_AddButton(info) end -- Position dropdown click handler (relying on global this) diff --git a/HealBot_View_Layout.lua b/HealBot_View_Layout.lua index 8e06ccc..29e0bc1 100644 --- a/HealBot_View_Layout.lua +++ b/HealBot_View_Layout.lua @@ -283,10 +283,10 @@ function HealBot_Action_EnableButton(button) end local iconSize = HealBot_Config.biconsize[HealBot_Config.Current_Skin] or 12 - local isPet = string.find(unit, "pet") ~= nil - local overlayMode = (HealBot_Config.HealBot_Integrations_ShowHoTTimers == 1) and ((HealBot_Config.HealBot_Integrations_HoTTimerDisplayMode or 0) == 1) and not isPet - local unitGUID = overlayMode and ((HealBot_GetUnitGUID and HealBot_GetUnitGUID(unit)) or (unit == "player" and HealBot_PlayerGUID)) - local currentTime = overlayMode and GetTime() + local dMode = HealBot_Config.HealBot_Integrations_HoTTimerDisplayMode or 0 + local overlayMode = (HealBot_Config.HealBot_Integrations_ShowHoTTimers == 1) and (dMode == 1 or dMode == 2) + local unitMap = overlayMode and HealBot_ActiveHoTUnitMap and HealBot_ActiveHoTUnitMap[unit] + for i = 1, 10 do local icon = button["icon"..i] if not icon then @@ -294,6 +294,13 @@ function HealBot_Action_EnableButton(button) button["icon"..i] = icon end if icon then + if button["iconText"..i] then + button["iconText"..i]:Hide() + button["iconText"..i] = nil + end + + local iconText = bar["iconText"..i] + if HealBot_UnitIcons and HealBot_UnitIcons[unit] and HealBot_UnitIcons[unit][i] then local currentTexture = icon:GetTexture() local newTexture = HealBot_UnitIcons[unit][i] @@ -304,48 +311,39 @@ function HealBot_Action_EnableButton(button) if icon:GetWidth() ~= iconSize then icon:SetWidth(iconSize) icon:SetHeight(iconSize) + if iconText then + iconText:SetFont("Fonts\\ARIALN.TTF", math.min(11, math.max(8, iconSize - 2)), "OUTLINE") + end end if not icon:IsVisible() then icon:Show() end - - -- Icon overlay timer text - local iconText = button["iconText"..i] - if not iconText then - iconText = button:CreateFontString(nil, "OVERLAY") - iconText:SetFont("Fonts\\ARIALN.TTF", 10, "OUTLINE") - iconText:SetPoint("CENTER", icon, "CENTER", 0, 0) - iconText:SetTextColor(1, 1, 1, 1) - button["iconText"..i] = iconText - end - if overlayMode and unitGUID and HealBot_Nampower_Auras and HealBot_Nampower_Auras[unitGUID] then - local texturePath = HealBot_UnitIcons[unit][i] - local auraInfo = HealBot_TrackedAura_Textures and HealBot_TrackedAura_Textures[texturePath] - if auraInfo and auraInfo.showTimer then - local expiration = (auraInfo.locName and HealBot_Nampower_Auras[unitGUID][auraInfo.locName]) - or (auraInfo.spell and HealBot_Nampower_Auras[unitGUID][auraInfo.spell]) - if not expiration and HealBot_TrackedAura_Spells then - for spellName, spInfo in pairs(HealBot_TrackedAura_Spells) do - if spInfo.texture == texturePath and spInfo.showTimer then - expiration = HealBot_Nampower_Auras[unitGUID][spellName] - if expiration then break end - end - end - end - if expiration and expiration > currentTime then - local timeLeft = math.floor(expiration - currentTime) - iconText:SetText(timeLeft) - iconText:Show() - else - iconText:SetText("") - iconText:Hide() + + if overlayMode then + if not iconText then + local overlayFrame = bar.overlayFrame + if not overlayFrame then + overlayFrame = CreateFrame("Frame", nil, bar) + overlayFrame:SetAllPoints(bar) + overlayFrame:SetFrameLevel(bar:GetFrameLevel() + 5) + bar.overlayFrame = overlayFrame end + iconText = overlayFrame:CreateFontString(nil, "OVERLAY") + iconText:SetFont("Fonts\\ARIALN.TTF", math.min(11, math.max(8, iconSize - 2)), "OUTLINE") + iconText:SetPoint("CENTER", icon, "CENTER", 0, 0) + iconText:SetTextColor(1, 1, 1, 1) + bar["iconText"..i] = iconText + end + local timeLeft = unitMap and (unitMap[newTexture] or (newTexture and unitMap[string.lower(newTexture)])) + if timeLeft then + iconText:SetText(timeLeft) + iconText:Show() else iconText:SetText("") iconText:Hide() end - else + elseif iconText and iconText:IsVisible() then iconText:SetText("") iconText:Hide() end @@ -353,7 +351,6 @@ function HealBot_Action_EnableButton(button) if icon:IsVisible() then icon:Hide() end - local iconText = button["iconText"..i] if iconText and iconText:IsVisible() then iconText:Hide() end @@ -1419,10 +1416,8 @@ end local HealBot_PlayerHoTsListFrame = nil local HealBot_PlayerHoTFrames = {} - - - local HealBot_PlayerHoTs_ListCache = {} +HealBot_ActiveHoTUnitMap = {} -- unitID -> { [texture] = timeLeft } local function HealBot_PlayerHoTs_SortComparator(a, b) return (a.expires or 0) < (b.expires or 0) @@ -1463,65 +1458,90 @@ local function HealBot_HoTTimerList_StackChild(frame, index) end end -function HealBot_Action_UpdatePlayerHoTs() - if not HealBot_PlayerHoTsListFrame then - HealBot_PlayerHoTsListFrame = CreateFrame("Frame", "HealBot_PlayerHoTsListFrame", HealBot_Action) - HealBot_PlayerHoTsListFrame:SetWidth(128) - HealBot_PlayerHoTsListFrame:SetHeight(1) - HealBot_PlayerHoTsListFrame:Show() +-- Clear all icon overlay timer texts +function HealBot_ClearHoTOverlays() + if not HealBot_Action_UnitButtons then return end + for unit, btnList in pairs(HealBot_Action_UnitButtons) do + for _, button in pairs(btnList) do + local bar = button.bar or getglobal(button:GetName() .. "Bar") + if bar then + for i = 1, 10 do + local iconText = bar["iconText"..i] + if iconText and iconText:IsVisible() then + iconText:SetText("") + iconText:Hide() + end + end + end + end end +end - HealBot_HoTTimerList_Reanchor() +-- Render countdown numbers directly on unit bar icons +function HealBot_RenderHoTOverlays() + if not HealBot_Action_UnitButtons then return end + local iconSize = HealBot_Config.biconsize[HealBot_Config.Current_Skin] or 12 + local fontSize = math.min(11, math.max(8, iconSize - 2)) - if HealBot_Config.HealBot_Integrations_ShowHoTTimers ~= 1 then - HealBot_PlayerHoTsListFrame:Hide() - return - end - - -- If display mode is Icon Overlay, hide the list and return - local displayMode = HealBot_Config.HealBot_Integrations_HoTTimerDisplayMode or 0 - if displayMode == 1 then - HealBot_PlayerHoTsListFrame:Hide() - return - end - - HealBot_PlayerHoTsListFrame:Show() - - local currentTime = GetTime() - - -- Clear cache instead of reallocating - local numHoTs = 0 - - if HealBot_Integrations_Nampower_Active and HealBot_Nampower_Auras then - for targetName, auras in pairs(HealBot_Nampower_Auras) do - local unitID = HealBot_Model:GetUnitByGUID(targetName) or HealBot_Model:GetUnitIDByName(targetName) - if not (unitID and string.find(unitID, "pet")) then - for spellName, expiration in pairs(auras) do - local auraInfo = HealBot_TrackedAura_Spells and HealBot_TrackedAura_Spells[spellName] - if auraInfo and auraInfo.showTimer then - if expiration > currentTime then - numHoTs = numHoTs + 1 - if not HealBot_PlayerHoTs_ListCache[numHoTs] then - HealBot_PlayerHoTs_ListCache[numHoTs] = {} + for unit, btnList in pairs(HealBot_Action_UnitButtons) do + local unitMap = HealBot_ActiveHoTUnitMap[unit] + for _, button in pairs(btnList) do + local bar = HealBot_Action_HealthBar(button) + if bar and bar:IsVisible() then + for i = 1, 10 do + local icon = button["icon"..i] or getglobal(button:GetName() .. "BarIcon" .. i) + local iconText = bar["iconText"..i] + if icon and icon:IsVisible() then + local tex = icon:GetTexture() + local timeLeft = unitMap and (unitMap[tex] or (tex and unitMap[string.lower(tex)])) + if timeLeft then + if not iconText then + local overlayFrame = bar.overlayFrame + if not overlayFrame then + overlayFrame = CreateFrame("Frame", nil, bar) + overlayFrame:SetAllPoints(bar) + overlayFrame:SetFrameLevel(bar:GetFrameLevel() + 5) + bar.overlayFrame = overlayFrame + end + iconText = overlayFrame:CreateFontString(nil, "OVERLAY") + iconText:SetFont("Fonts\\ARIALN.TTF", fontSize, "OUTLINE") + iconText:SetPoint("CENTER", icon, "CENTER", 0, 0) + iconText:SetTextColor(1, 1, 1, 1) + bar["iconText"..i] = iconText end - HealBot_PlayerHoTs_ListCache[numHoTs].name = targetName - HealBot_PlayerHoTs_ListCache[numHoTs].spell = spellName - HealBot_PlayerHoTs_ListCache[numHoTs].expires = expiration + iconText:SetText(timeLeft) + iconText:Show() + else + if iconText and iconText:IsVisible() then + iconText:SetText("") + iconText:Hide() + end + end + else + if iconText and iconText:IsVisible() then + iconText:Hide() end end end end end end - - -- Blank out remaining cache entries so sort doesn't read them - for i = numHoTs + 1, table.getn(HealBot_PlayerHoTs_ListCache) do - HealBot_PlayerHoTs_ListCache[i].expires = 9999999999 +end + +-- Render HoT timer list +local function HealBot_RenderHoTList(numHoTs, currentTime) + if not HealBot_PlayerHoTsListFrame then + HealBot_PlayerHoTsListFrame = CreateFrame("Frame", "HealBot_PlayerHoTsListFrame", HealBot_Action) + HealBot_PlayerHoTsListFrame:SetWidth(128) + HealBot_PlayerHoTsListFrame:SetHeight(1) end - - -- Sort only the active portion by shifting expired ones to the end + + HealBot_HoTTimerList_Reanchor() + HealBot_PlayerHoTsListFrame:Show() + + -- Sort active HoTs by expiration table.sort(HealBot_PlayerHoTs_ListCache, HealBot_PlayerHoTs_SortComparator) - + for i = 1, math.max(numHoTs, table.getn(HealBot_PlayerHoTFrames)) do local frame = HealBot_PlayerHoTFrames[i] if i <= numHoTs then @@ -1544,61 +1564,13 @@ function HealBot_Action_UpdatePlayerHoTs() HealBot_PlayerHoTFrames[i] = frame end - -- Reposition child for stacking direction HealBot_HoTTimerList_StackChild(frame, i) local hot = HealBot_PlayerHoTs_ListCache[i] - local timeLeft = math.floor(hot.expires - currentTime) + local timeLeft = hot.timeLeft or math.floor(hot.expires - currentTime) - -- Resolve GUID to Name - if not HealBot_Nampower_NameCache then HealBot_Nampower_NameCache = HealBot_GetTable() end - - local dispName = HealBot_Nampower_NameCache[hot.name] - - if not dispName then - dispName = hot.name - local resolved = false - - local unitID = HealBot_Model:GetUnitByGUID(hot.name) or HealBot_Model:GetUnitIDByName(hot.name) - if unitID then - local n = UnitName(unitID) - if n then - dispName = n - resolved = true - end - elseif string.sub(hot.name, 1, 2) == "0x" or string.sub(hot.name, 1, 2) == "0X" then - if HealBot_PlayerGUID and string.lower(HealBot_PlayerGUID) == string.lower(hot.name) then - dispName = UnitName("player") or "Player" - resolved = true - elseif HealBot_GetUnitGUID then - if string.lower(HealBot_GetUnitGUID("player") or "") == string.lower(hot.name) then - dispName = UnitName("player") or "Player" - resolved = true - elseif string.lower(HealBot_GetUnitGUID("target") or "") == string.lower(hot.name) then - dispName = UnitName("target") or "Target" - resolved = true - elseif string.lower(HealBot_GetUnitGUID("mouseover") or "") == string.lower(hot.name) then - dispName = UnitName("mouseover") or "Mouseover" - resolved = true - end - end - end - - if resolved then - HealBot_Nampower_NameCache[hot.name] = dispName - elseif string.sub(hot.name, 1, 2) == "0x" then - dispName = "Unknown" - end - end - - local spellId = HealBot_GetSpellId and HealBot_GetSpellId(hot.spell) or nil - local tex = (HealBot_TrackedAura_Spells and HealBot_TrackedAura_Spells[hot.spell] and HealBot_TrackedAura_Spells[hot.spell].texture) or "Interface\\Icons\\INV_Misc_QuestionMark" - if spellId then - tex = GetSpellTexture(spellId, "BOOKTYPE_SPELL") or tex - end - - frame.icon:SetTexture(tex) - frame.text:SetText(dispName .. ": " .. timeLeft .. "s") + frame.icon:SetTexture(hot.texture) + frame.text:SetText(hot.name .. ": " .. timeLeft .. "s") frame.text:SetTextColor(0.8, 1, 0.8) frame:Show() else @@ -1610,3 +1582,104 @@ function HealBot_Action_UpdatePlayerHoTs() HealBot_PlayerHoTsListFrame:SetHeight(math.max(1, numHoTs * 18)) end + +-- Unified HoT Timer Pipeline +function HealBot_UpdateActiveHoTTimers() + if HealBot_Config.HealBot_Integrations_ShowHoTTimers ~= 1 then + if HealBot_PlayerHoTsListFrame and HealBot_PlayerHoTsListFrame:IsVisible() then + HealBot_PlayerHoTsListFrame:Hide() + end + HealBot_ClearHoTOverlays() + return + end + + local currentTime = GetTime() + local numHoTs = 0 + + -- Clear unit map (recycle) + for _, sub in pairs(HealBot_ActiveHoTUnitMap) do + for k in pairs(sub) do sub[k] = nil end + end + + if HealBot_Integrations_Nampower_Active and HealBot_Nampower_Auras then + for targetKey, auras in pairs(HealBot_Nampower_Auras) do + local unitID = HealBot_Model and HealBot_Model:GetUnitByGUID(targetKey) + if not unitID and HealBot_PlayerGUID and (targetKey == HealBot_PlayerGUID or string.lower(targetKey) == string.lower(HealBot_PlayerGUID)) then + unitID = "player" + end + if not unitID and UnitName("player") and (targetKey == UnitName("player") or string.lower(targetKey) == string.lower(UnitName("player"))) then + unitID = "player" + end + local displayName = nil + if unitID and unitID ~= "target" then + if HealBot_Model and HealBot_Model.units[unitID] and HealBot_Model.units[unitID].name then + displayName = HealBot_Model.units[unitID].name + end + if not displayName then + displayName = UnitName(unitID) + end + end + if not displayName and HealBot_Nampower_NameCache then + displayName = HealBot_Nampower_NameCache[targetKey] + end + if displayName and HealBot_Nampower_NameCache then + HealBot_Nampower_NameCache[targetKey] = displayName + end + + -- Exclude pets + if not (unitID and string.find(unitID, "pet")) then + for spellName, expiration in pairs(auras) do + local auraInfo = HealBot_TrackedAura_Spells and HealBot_TrackedAura_Spells[spellName] + if auraInfo and auraInfo.showTimer and expiration > currentTime then + local timeLeft = math.floor(expiration - currentTime) + local tex = auraInfo.texture or "Interface\\Icons\\INV_Misc_QuestionMark" + + numHoTs = numHoTs + 1 + if not HealBot_PlayerHoTs_ListCache[numHoTs] then + HealBot_PlayerHoTs_ListCache[numHoTs] = {} + end + local hot = HealBot_PlayerHoTs_ListCache[numHoTs] + hot.unit = unitID + hot.name = displayName or targetKey + hot.spell = spellName + hot.texture = tex + hot.expires = expiration + hot.timeLeft = timeLeft + + if unitID then + if not HealBot_ActiveHoTUnitMap[unitID] then + HealBot_ActiveHoTUnitMap[unitID] = {} + end + HealBot_ActiveHoTUnitMap[unitID][tex] = timeLeft + HealBot_ActiveHoTUnitMap[unitID][string.lower(tex)] = timeLeft + end + end + end + end + end + end + + -- Blank out remaining cache entries so sort doesn't read them + for i = numHoTs + 1, table.getn(HealBot_PlayerHoTs_ListCache) do + HealBot_PlayerHoTs_ListCache[i].expires = 9999999999 + end + + local displayMode = HealBot_Config.HealBot_Integrations_HoTTimerDisplayMode or 0 + if displayMode == 0 then + -- List Mode + HealBot_ClearHoTOverlays() + HealBot_RenderHoTList(numHoTs, currentTime) + elseif displayMode == 1 then + -- Overlay Mode + if HealBot_PlayerHoTsListFrame and HealBot_PlayerHoTsListFrame:IsVisible() then + HealBot_PlayerHoTsListFrame:Hide() + end + HealBot_RenderHoTOverlays() + else + -- Both Mode (2) + HealBot_RenderHoTList(numHoTs, currentTime) + HealBot_RenderHoTOverlays() + end +end + +HealBot_Action_UpdatePlayerHoTs = HealBot_UpdateActiveHoTTimers diff --git a/README.md b/README.md index 4578ab4..f608048 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ **v1.7.6** * **Feature - HoT Timer Position & Icon Overlay Display Modes** - Added configurable HoT Timer list position dropdown (`Below`, `Above`, `Left`, `Right`) relative to raid frames. Added alternative `Icon Overlay` display mode rendering countdown numbers directly on unit bar HoT icons. Excluded pets from timer lists and overlay numbers. Optimized expiration lookup with O(1) cached texture-to-spell mappings. +* **Bug Fix & Feature - HoT Timer Resolution & "Both" Display Mode** - Resolved issue where HoT timer lists displayed raw GUID strings instead of player names by adding eager roster GUID synchronization on group updates and direct raid/party fallback scanning. Added "Both" display mode option (`List`, `Icon Overlay`, `Both`) allowing simultaneous list timers and on-bar icon countdown overlays. Added case-insensitive texture fallback mapping to ensure overlay numbers render consistently across all tracked auras. * **Refactor & Feature - Unified Aura & HoT Tracking** - Unified bar icons and HoT timers into a single master dictionary (`HealBot_TrackedAuras`) in `HealBot_Data.lua`. Added Weakened Soul, Fear Ward, Abolish Disease, and Abolish Poison to active timer bars with localized client support and debuff-aware pruning. **v1.7.5** From de9454e85f6b42f48e450158c46a77cad7e6bc54 Mon Sep 17 00:00:00 2001 From: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com> Date: Thu, 5 Nov 2026 10:13:55 +0100 Subject: [PATCH 6/6] docs: update version to v1.7.7 in readme changelog --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f608048..1bf21d3 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\ ### Change Log -**v1.7.6** +**v1.7.7** * **Feature - HoT Timer Position & Icon Overlay Display Modes** - Added configurable HoT Timer list position dropdown (`Below`, `Above`, `Left`, `Right`) relative to raid frames. Added alternative `Icon Overlay` display mode rendering countdown numbers directly on unit bar HoT icons. Excluded pets from timer lists and overlay numbers. Optimized expiration lookup with O(1) cached texture-to-spell mappings. * **Bug Fix & Feature - HoT Timer Resolution & "Both" Display Mode** - Resolved issue where HoT timer lists displayed raw GUID strings instead of player names by adding eager roster GUID synchronization on group updates and direct raid/party fallback scanning. Added "Both" display mode option (`List`, `Icon Overlay`, `Both`) allowing simultaneous list timers and on-bar icon countdown overlays. Added case-insensitive texture fallback mapping to ensure overlay numbers render consistently across all tracked auras. * **Refactor & Feature - Unified Aura & HoT Tracking** - Unified bar icons and HoT timers into a single master dictionary (`HealBot_TrackedAuras`) in `HealBot_Data.lua`. Added Weakened Soul, Fear Ward, Abolish Disease, and Abolish Poison to active timer bars with localized client support and debuff-aware pruning.