Merge branch 'dev' into main

Signed-off-by: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com>
This commit is contained in:
Bluewhale1337
2026-11-05 10:44:58 +01:00
committed by GitHub
15 changed files with 806 additions and 194 deletions
+50 -13
View File
@@ -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
+25 -32
View File
@@ -209,17 +209,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 = {}
@@ -233,6 +222,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;
@@ -323,13 +315,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
@@ -337,33 +342,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
+34 -4
View File
@@ -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
@@ -185,12 +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
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;
@@ -274,22 +296,30 @@ 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
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,
["RAID_ROSTER_UPDATE"] = function(this)
HealBot_Model:RefreshUnit("player")
for _, unit in ipairs(HealBot_Model.partyMembers) do
HealBot_Model:RefreshUnit(unit)
end
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,
+1 -1
View File
@@ -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)
+35
View File
@@ -294,6 +294,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 = {};
@@ -690,3 +692,36 @@ 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
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
end
end
HealBot_Init_TrackedAuras();
+67 -34
View File
@@ -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()
@@ -145,5 +146,37 @@ 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
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
+1
View File
@@ -208,6 +208,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";
+1
View File
@@ -225,6 +225,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";
+1
View File
@@ -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";
+1
View File
@@ -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 = "회복";
+102 -3
View File
@@ -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
@@ -160,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
@@ -168,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.
+130
View File
@@ -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 or displayMode == 2))
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,103 @@ 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)
elseif displayMode == 2 then
UIDropDownMenu_SetText("Both", 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)
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)
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
+74 -2
View File
@@ -94,9 +94,81 @@
</OnLoad>
<OnClick>
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
</OnClick>
</Scripts>
</CheckButton>
<!-- HoT Timer Display Mode Dropdown -->
<Frame name="HealBot_Options_Integrations_DisplayModeDropdown" inherits="UIDropDownMenuTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="HealBot_Options_Integrations_ShowHoTTimers" relativePoint="BOTTOMLEFT">
<Offset>
<AbsDimension x="-15" y="-22"/>
</Offset>
</Anchor>
</Anchors>
<Layers>
<Layer level="ARTWORK">
<FontString name="HealBot_Options_Integrations_DisplayModeLabel" inherits="GameFontHighlightSmall" justifyH="LEFT" text="Timer Display Mode:">
<Anchors>
<Anchor point="BOTTOMLEFT" relativePoint="TOPLEFT">
<Offset>
<AbsDimension x="20" y="2"/>
</Offset>
</Anchor>
</Anchors>
<Color r="1" g="0.82" b="0"/>
</FontString>
</Layer>
</Layers>
<Scripts>
<OnLoad>
UIDropDownMenu_SetWidth(120, this);
UIDropDownMenu_Initialize(this, HealBot_Options_Integrations_DisplayModeDropdown_Init);
</OnLoad>
</Scripts>
</Frame>
<!-- HoT Timer Position Dropdown -->
<Frame name="HealBot_Options_Integrations_PositionDropdown" inherits="UIDropDownMenuTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="HealBot_Options_Integrations_DisplayModeDropdown" relativePoint="BOTTOMLEFT">
<Offset>
<AbsDimension x="0" y="-22"/>
</Offset>
</Anchor>
</Anchors>
<Layers>
<Layer level="ARTWORK">
<FontString name="HealBot_Options_Integrations_PositionLabel" inherits="GameFontHighlightSmall" justifyH="LEFT" text="Timer List Position:">
<Anchors>
<Anchor point="BOTTOMLEFT" relativePoint="TOPLEFT">
<Offset>
<AbsDimension x="20" y="2"/>
</Offset>
</Anchor>
</Anchors>
<Color r="1" g="0.82" b="0"/>
</FontString>
</Layer>
</Layers>
<Scripts>
<OnLoad>
UIDropDownMenu_SetWidth(120, this);
UIDropDownMenu_Initialize(this, HealBot_Options_Integrations_PositionDropdown_Init);
</OnLoad>
</Scripts>
</Frame>
<Frame setAllPoints="true">
<Layers>
<Layer level="ARTWORK">
@@ -114,9 +186,9 @@
<FontString name="HealBot_Options_Integrations_HoTInfo" inherits="GameFontHighlightSmall" justifyH="LEFT" justifyV="TOP" text="Note: HoT Timers require Nampower to be enabled and active to provide accurate aura times.">
<Size x="340" y="30"/>
<Anchors>
<Anchor point="TOPLEFT" relativeTo="HealBot_Options_Integrations_ShowHoTTimers" relativePoint="BOTTOMLEFT">
<Anchor point="TOPLEFT" relativeTo="HealBot_Options_Integrations_PositionDropdown" relativePoint="BOTTOMLEFT">
<Offset>
<AbsDimension x="20" y="-4"/>
<AbsDimension x="20" y="-6"/>
</Offset>
</Anchor>
</Anchors>
+277 -105
View File
@@ -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
@@ -273,6 +283,10 @@ function HealBot_Action_EnableButton(button)
end
local iconSize = HealBot_Config.biconsize[HealBot_Config.Current_Skin] or 12
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
@@ -280,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]
@@ -290,15 +311,49 @@ 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
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
elseif iconText and iconText:IsVisible() then
iconText:SetText("")
iconText:Hide()
end
else
if icon:IsVisible() then
icon:Hide()
end
if iconText and iconText:IsVisible() then
iconText:Hide()
end
end
end
end
@@ -845,14 +900,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
@@ -1360,67 +1416,132 @@ 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 = {}
HealBot_ActiveHoTUnitMap = {} -- unitID -> { [texture] = timeLeft }
local function HealBot_PlayerHoTs_SortComparator(a, b)
return (a.expires or 0) < (b.expires or 0)
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)
-- 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)
HealBot_PlayerHoTsListFrame:Show()
end
if HealBot_Config.HealBot_Integrations_ShowHoTTimers ~= 1 then
HealBot_PlayerHoTsListFrame:Hide()
return
else
HealBot_PlayerHoTsListFrame:Show()
end
end
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
for spellName, expiration in pairs(auras) do
if HealBot_PlayerHoTs_ValidHoTs[spellName] 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
-- 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
-- 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 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 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
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
-- Sort only the active portion by shifting expired ones to the 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
@@ -1428,11 +1549,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)
@@ -1447,59 +1563,14 @@ function HealBot_Action_UpdatePlayerHoTs()
HealBot_PlayerHoTFrames[i] = frame
end
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_PlayerHoTs_ValidHoTs[hot.spell] 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
@@ -1511,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
+7
View File
@@ -50,6 +50,11 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\
### Change Log
**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.
**v1.7.6**
* **Feature - All-Class Support & Raid Frame Replacement:** Removed legacy restriction that disabled HealBot event listeners and UI frames for classes without native healing spells. HealBot now initializes and functions as a full party/raid frame replacement for all classes (Warrior, Mage, Rogue, Hunter, Warlock).
* **Targeting & Binding Defaults:** Added default `Left` click `"Target"` binding for non-healer classes, recognized `"target"` in spell resolution, and prevented non-healer buttons from disabling on full-health allies.
@@ -110,6 +115,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.