-- HealBot_View_HoT.lua -- HoT overlays, duration timers, and side list rendering local HealBot_PlayerHoTsListFrame = nil local HealBot_PlayerHoTFrames = {} local HealBot_PlayerHoTs_ListCache = {} local HealBot_ActiveHoTUnitMap = {} 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 -- 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 -- 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)) 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 cleanTex = tex and string.lower(string.gsub(tex, "/", "\\")) local shortTex = cleanTex and string.match(cleanTex, "([^\\]+)$") local timeLeft = unitMap and (unitMap[tex] or (cleanTex and unitMap[cleanTex]) or (shortTex and unitMap[shortTex])) 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 if bar.overlayFrame then bar.overlayFrame:Show() end 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 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 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 if not frame then frame = CreateFrame("Frame", "HealBot_PlayerHoT" .. i, HealBot_PlayerHoTsListFrame) frame:SetWidth(128) frame:SetHeight(16) local icon = frame:CreateTexture(nil, "ARTWORK") icon:SetWidth(14) icon:SetHeight(14) icon:SetPoint("LEFT", frame, "LEFT", 2, 0) frame.icon = icon local text = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") text:SetPoint("LEFT", icon, "RIGHT", 4, 0) text:SetJustifyH("LEFT") frame.text = text HealBot_PlayerHoTFrames[i] = frame end HealBot_HoTTimerList_StackChild(frame, i) local hot = HealBot_PlayerHoTs_ListCache[i] local timeLeft = hot.timeLeft or math.floor(hot.expires - currentTime) frame.icon:SetTexture(hot.texture) frame.text:SetText(hot.name .. ": " .. timeLeft .. "s") frame.text:SetTextColor(0.8, 1, 0.8) frame:Show() else if frame then frame:Hide() end end end 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 lowerKey = string.lower(targetKey) local unitID = HealBot_Model and (HealBot_Model:GetUnitByGUID(targetKey) or HealBot_Model:GetUnitByGUID(lowerKey)) if not unitID then local isPlayer = (HealBot_PlayerGUID and (targetKey == HealBot_PlayerGUID or lowerKey == string.lower(HealBot_PlayerGUID))) or (UnitName("player") and (targetKey == UnitName("player") or lowerKey == string.lower(UnitName("player")))) or (HealBot_GetUnitGUID and (targetKey == HealBot_GetUnitGUID("player") or lowerKey == string.lower(HealBot_GetUnitGUID("player") or ""))) if isPlayer then unitID = "player" end end if not unitID and HealBot_Model and HealBot_Model.GetUnitIDByName then unitID = HealBot_Model:GetUnitIDByName(targetKey) end -- Multi-tier player name resolution 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] or HealBot_Nampower_NameCache[lowerKey] end if not displayName and UnitExists("target") and HealBot_GetUnitGUID and (targetKey == HealBot_GetUnitGUID("target") or lowerKey == string.lower(HealBot_GetUnitGUID("target") or "")) then displayName = UnitName("target") end if not displayName and UnitExists("mouseover") and HealBot_GetUnitGUID and (targetKey == HealBot_GetUnitGUID("mouseover") or lowerKey == string.lower(HealBot_GetUnitGUID("mouseover") or "")) then displayName = UnitName("mouseover") end if not displayName then local uName = UnitName(targetKey) if uName and uName ~= targetKey and uName ~= "Unknown" then displayName = uName end end if displayName and HealBot_Nampower_NameCache then HealBot_Nampower_NameCache[targetKey] = displayName HealBot_Nampower_NameCache[lowerKey] = 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 not auraInfo and HealBot_TrackedAuras and HealBot_TrackedAuras[spellName] then auraInfo = HealBot_TrackedAuras[spellName] end 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 "Unknown" 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 local cleanTex = string.lower(string.gsub(tex, "/", "\\")) local shortTex = string.match(cleanTex, "([^\\]+)$") HealBot_ActiveHoTUnitMap[unitID][tex] = timeLeft HealBot_ActiveHoTUnitMap[unitID][cleanTex] = timeLeft if shortTex then HealBot_ActiveHoTUnitMap[unitID][shortTex] = timeLeft end -- If player in raid, map under raid token button as well if unitID == "player" and HealBot_Action_UnitButtons then for btnUnit in pairs(HealBot_Action_UnitButtons) do if btnUnit ~= "player" and not string.find(btnUnit, "^Test") and UnitIsUnit(btnUnit, "player") then if not HealBot_ActiveHoTUnitMap[btnUnit] then HealBot_ActiveHoTUnitMap[btnUnit] = {} end HealBot_ActiveHoTUnitMap[btnUnit][tex] = timeLeft HealBot_ActiveHoTUnitMap[btnUnit][cleanTex] = timeLeft if shortTex then HealBot_ActiveHoTUnitMap[btnUnit][shortTex] = timeLeft end end end end 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