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**