diff --git a/HealBot.lua b/HealBot.lua
index 00befc2..93e6548 100644
--- a/HealBot.lua
+++ b/HealBot.lua
@@ -103,6 +103,21 @@ function HealBot_SlashCmd(cmd)
HealBot_AddDebug( "Channel active" );
return;
end
+ if (cmd=="debug" or cmd=="dbg") then
+ if not HealBot_Config then return; end
+ if HealBot_Config.DebugMode == 1 then
+ HealBot_Config.DebugMode = 0;
+ if DEFAULT_CHAT_FRAME then
+ DEFAULT_CHAT_FRAME:AddMessage("|cFFFFD100HealBot|r: Debug mode |cFFFF0000DISABLED|r.");
+ end
+ else
+ HealBot_Config.DebugMode = 1;
+ if DEFAULT_CHAT_FRAME then
+ DEFAULT_CHAT_FRAME:AddMessage("|cFFFFD100HealBot|r: Debug mode |cFF00FF00ENABLED|r.");
+ end
+ end
+ return;
+ end
end
-- HealBot_SendAddonMessage moved to HealBot_Controller_Comms.lua
@@ -222,27 +237,42 @@ end
-- SpiBonus and GetBonus functions moved to HealBot_Controller_Spells.lua
+local HealBot_RaidUnitTokens = {}
+for i=1, 40 do HealBot_RaidUnitTokens[i] = "raid"..i end
+
+HealBot_NameToUnitID = {}
+
+function HealBot_UpdateUnitIDCache()
+ HealBot_NameToUnitID = {}
+ local name = UnitName("player")
+ if name then HealBot_NameToUnitID[name] = "player" end
+
+ for i=1, 4 do
+ local unit = "party"..i
+ name = UnitName(unit)
+ if name then HealBot_NameToUnitID[name] = unit end
+ unit = "partypet"..i
+ name = UnitName(unit)
+ if name then HealBot_NameToUnitID[name] = unit end
+ end
+
+ local petName = UnitName("pet")
+ if petName then HealBot_NameToUnitID[petName] = "pet" end
+
+ for i=1, 40 do
+ local unit = "raid"..i
+ name = UnitName(unit)
+ if name then HealBot_NameToUnitID[name] = unit end
+ unit = "raidpet"..i
+ name = UnitName(unit)
+ if name then HealBot_NameToUnitID[name] = unit end
+ end
+end
+
-- HealBot_FindUnitID: Finds a unit token for a given player name.
function HealBot_FindUnitID(unitname)
- local text;
- for _,unit in ipairs(HealBot_Action_HealGroup) do
- text = UnitName(unit);
- if text then
- if text==unitname then
- return unit;
- end
- end
- end
- for i=1,40 do
- local unit = "raid"..i;
- text = UnitName(unit);
- if text then
- if text==unitname then
- return unit;
- end
- end
- end
- return nil;
+ if not unitname then return nil end
+ return HealBot_NameToUnitID[unitname]
end
-- HealBot_PlaySound: Plays an audio alert based on ID.
diff --git a/HealBotBlue.toc b/HealBotBlue.toc
index 30b9bda..4a508a7 100644
--- a/HealBotBlue.toc
+++ b/HealBotBlue.toc
@@ -1,6 +1,6 @@
## Interface: 11200
## Title: HealBotBlue
-## Version: 1.7.3
+## Version: 1.7.4
## 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
diff --git a/HealBot_Controller_Aura.lua b/HealBot_Controller_Aura.lua
index 597adee..5efcad9 100644
--- a/HealBot_Controller_Aura.lua
+++ b/HealBot_Controller_Aura.lua
@@ -107,13 +107,25 @@ function HealBot_CheckBuffs(unit)
hasBuff[k] = nil
end
local i = 1
+ if not HealBot_TextureToName then HealBot_TextureToName = {} end
while true do
local buffTexture = UnitBuff(unit, i)
if not buffTexture then break end
- HealBot_ScanTooltip:SetOwner(UIParent, "ANCHOR_NONE")
- HealBot_ScanTooltip:ClearLines()
- HealBot_ScanTooltip:SetUnitBuff(unit, i)
- local buffName = HealBot_ScanTooltipTextLeft1:GetText()
+
+ local buffName = HealBot_TextureToName[buffTexture]
+ if buffName == nil then
+ HealBot_ScanTooltip:SetOwner(UIParent, "ANCHOR_NONE")
+ HealBot_ScanTooltip:ClearLines()
+ HealBot_ScanTooltip:SetUnitBuff(unit, i)
+ local textObj = HealBot_ScanTooltipTextLeft1
+ if textObj then
+ buffName = textObj:GetText()
+ end
+ if buffName then
+ HealBot_TextureToName[buffTexture] = buffName
+ end
+ end
+
if buffName then
hasBuff[buffName] = true
end
@@ -260,9 +272,11 @@ function HealBot_OnEvent_UnitAura(this, unit)
end
local b = 1
+ local activeBuffs = HealBot_GetTable()
while true do
local buff = UnitBuff(unit, b)
if not buff then break end
+ activeBuffs[buff] = true
if HealBot_TrackedHoTs[buff] and iconCount < 10 then
iconCount = iconCount + 1
HealBot_UnitIcons[unit][iconCount] = buff
@@ -270,6 +284,30 @@ function HealBot_OnEvent_UnitAura(this, unit)
b = b + 1
end
+ if HealBot_Integrations_Nampower_Active and HealBot_Nampower_Auras then
+ local guid = HealBot_GetUnitGUID and HealBot_GetUnitGUID(unit)
+ if guid then
+ if not HealBot_Nampower_NameCache then HealBot_Nampower_NameCache = HealBot_GetTable() end
+ 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
+ end
+ end
+ end
+ end
+ end
+ HealBot_ReleaseTable(activeBuffs)
+
local d = 1
while true do
local debuff = UnitDebuff(unit, d)
diff --git a/HealBot_Controller_Comms.lua b/HealBot_Controller_Comms.lua
index 47033dd..2267662 100644
--- a/HealBot_Controller_Comms.lua
+++ b/HealBot_Controller_Comms.lua
@@ -30,8 +30,15 @@ function HealBot_AddChat(msg)
end
end
--- HealBot_AddDebug: Prints debug messages to internal chat channel.
+-- HealBot_AddDebug: Prints debug messages to chat or internal debug channel.
function HealBot_AddDebug(msg)
+ if HealBot_Config and HealBot_Config.DebugMode == 1 then
+ if DEFAULT_CHAT_FRAME then
+ DEFAULT_CHAT_FRAME:AddMessage("|cFFFFD100[HB Debug]|r " .. msg);
+ end
+ return;
+ end
+
local chanid = HealBot_Get_DebugChan();
if chanid and HealBot_SpamCnt < 3 then
HealBot_SpamCnt = HealBot_SpamCnt + 1;
@@ -136,9 +143,17 @@ function HealBot_OnEvent_AddonMsg(this, addon_id, inc_msg, dist_target, sender_i
-- Clear reusable args array
for i=1, 10 do HealBot_Comms_Args[i] = nil end
local argCount = 0
- for word in string.gfind(inc_msg, "[^/]+") do
+ local start_pos = 1
+ local next_pos = string.find(inc_msg, "/", start_pos, true)
+ while next_pos do
argCount = argCount + 1
- HealBot_Comms_Args[argCount] = word
+ HealBot_Comms_Args[argCount] = string.sub(inc_msg, start_pos, next_pos - 1)
+ start_pos = next_pos + 1
+ next_pos = string.find(inc_msg, "/", start_pos, true)
+ end
+ if start_pos <= string.len(inc_msg) then
+ argCount = argCount + 1
+ HealBot_Comms_Args[argCount] = string.sub(inc_msg, start_pos)
end
local cmd = HealBot_Comms_Args[1]
diff --git a/HealBot_Controller_Events.lua b/HealBot_Controller_Events.lua
index ab5521e..5682a44 100644
--- a/HealBot_Controller_Events.lua
+++ b/HealBot_Controller_Events.lua
@@ -173,13 +173,6 @@ function HealBot_OnUpdate(this, arg1)
HealBot_Action_RefreshPower(unitID)
end
- local powerID, _ = next(HealBot_View_DirtyPower)
- while powerID do
- HealBot_Action_RefreshPower(powerID)
- HealBot_View_DirtyPower[powerID] = nil
- powerID, _ = next(HealBot_View_DirtyPower)
- end
-
if HealBot_EquipChangeTimer > 0 then
HealBot_EquipChangeTimer = HealBot_EquipChangeTimer - arg1
if HealBot_EquipChangeTimer <= 0 then
@@ -189,6 +182,16 @@ function HealBot_OnUpdate(this, arg1)
HealBot_RecalcSpells();
end
end
+ 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
+ HealBot_Action_UpdatePlayerHoTs()
+ end
+ end
+ elseif HealBot_PlayerHoTsListFrame and HealBot_PlayerHoTsListFrame:IsVisible() then
+ HealBot_PlayerHoTsListFrame:Hide()
+ end
HealBot_Timer1 = HealBot_Timer1 + arg1;
if HealBot_Timer1 >= 2.5 then
@@ -505,6 +508,7 @@ end
function HealBot_OnEvent_PartyMembersChanged(this)
HealBot_Model:PreserveStateByGUID()
HealBot_Integrations_PruneNampower()
+ if HealBot_UpdateUnitIDCache then HealBot_UpdateUnitIDCache() end
if HealBot_IsFighting then
HealBot_Action_PartyChanged()
end
@@ -564,12 +568,14 @@ end
function HealBot_OnEvent_SpellsChanged(this, arg1)
if arg1 then return; end
HealBot_AddDebug("HB: SpellsChanged");
+ if HealBot_CacheSpellIDs then HealBot_CacheSpellIDs() end
HealBot_SpellsInitFlag = 2;
end
-- HealBot_OnEvent_TalentsChanged: Internal utility: HealBot_OnEvent_TalentsChanged
function HealBot_OnEvent_TalentsChanged(this, arg1)
HealBot_AddDebug("HB: TalentsChanged");
+ if HealBot_UpdateTalentRanks then HealBot_UpdateTalentRanks() end
end
-- HealBot_OnEvent_PlayerEnteringWorld: Internal utility: HealBot_OnEvent_PlayerEnteringWorld
@@ -579,6 +585,7 @@ function HealBot_OnEvent_PlayerEnteringWorld(this)
if HealBot_ApplyRefreshHook then
HealBot_ApplyRefreshHook()
end
+ if HealBot_UpdateUnitIDCache then HealBot_UpdateUnitIDCache() end
end
-- HealBot_OnEvent_SpellcastStart: Internal utility: HealBot_OnEvent_SpellcastStart
diff --git a/HealBot_Controller_Range.lua b/HealBot_Controller_Range.lua
index f37c7b2..823a053 100644
--- a/HealBot_Controller_Range.lua
+++ b/HealBot_Controller_Range.lua
@@ -8,12 +8,31 @@ function HealBot_ResetRangeScale()
_scale = 0
end
+local _rangeCache = {}
+local _lastFrameTime = 0
+
-- HealBot_Range_Check: Checks if unit is in range via map distance or API.
function HealBot_Range_Check(unit, range)
+ local currentTime = GetTime()
+ if _lastFrameTime ~= currentTime then
+ for r, cacheTable in pairs(_rangeCache) do
+ for u in pairs(cacheTable) do
+ cacheTable[u] = nil
+ end
+ end
+ _lastFrameTime = currentTime
+ end
+
local return_val = 0;
if not range then
range = 40;
end
+
+ _rangeCache[range] = _rangeCache[range] or {}
+ if _rangeCache[range][unit] ~= nil then
+ return _rangeCache[range][unit]
+ end
+
if ( unit == "player" ) then
return_val = 1;
elseif HealBot_Integrations_ClassicAPI_Active and UnitDistanceSquared and UnitInLineOfSight then
@@ -56,5 +75,7 @@ function HealBot_Range_Check(unit, range)
end
end
end
+
+ _rangeCache[range][unit] = return_val
return return_val;
end
diff --git a/HealBot_Controller_Spells.lua b/HealBot_Controller_Spells.lua
index b9bea6c..82ee3a6 100644
--- a/HealBot_Controller_Spells.lua
+++ b/HealBot_Controller_Spells.lua
@@ -34,28 +34,27 @@ function HealBot_GetSpellName(id)
return spellName .. " (" .. subSpellName .. ")";
end
+HealBot_SpellNameToId = {}
+
+function HealBot_CacheSpellIDs()
+ HealBot_SpellNameToId = {}
+ local id = 1
+ while true do
+ local spellName, subSpellName = GetSpellName(id, BOOKTYPE_SPELL)
+ if not spellName then break end
+ if subSpellName and subSpellName ~= "" then
+ HealBot_SpellNameToId[spellName .. " (" .. subSpellName .. ")"] = id
+ HealBot_SpellNameToId[spellName .. "(" .. subSpellName .. ")"] = id
+ end
+ HealBot_SpellNameToId[spellName] = id
+ id = id + 1
+ end
+end
+
-- HealBot_GetSpellId: Internal utility: HealBot_GetSpellId
function HealBot_GetSpellId(spell)
- local id, idd = 1, 0;
- while true do
- local spellName, subSpellName = GetSpellName(id, BOOKTYPE_SPELL);
- if (spellName) then
- if (spell == spellName .. " (" .. subSpellName .. ")") or (spell == spellName .. "(" .. subSpellName .. ")") then
- return id;
- end
- if (spell == spellName) then
- idd = id;
- end
- else
- break
- end
- id = id + 1;
- end
- if idd > 0 then
- return idd
- else
- return nil;
- end
+ if not spell then return nil end
+ return HealBot_SpellNameToId[spell]
end
-- HealBot_CastSpellByName: Internal utility: HealBot_CastSpellByName
@@ -212,18 +211,6 @@ end
-- HealBot_StartCasting: Initiates spell cast and broadcasts incoming heal.
function HealBot_StartCasting(spell, target, ttype)
HealBot_CastFailed = false;
-
- -- Extract base spell for internal tracking
- local baseSpell = spell
- local parenIndex = string.find(spell, " %(")
- if parenIndex then
- baseSpell = string.sub(spell, 1, parenIndex - 1)
- else
- parenIndex = string.find(spell, "%(")
- if parenIndex then
- baseSpell = string.sub(spell, 1, parenIndex - 1)
- end
- end
HealBot_CastSpellByName(spell);
if ( SpellCanTargetUnit(target) ) then
@@ -238,11 +225,11 @@ function HealBot_StartCasting(spell, target, ttype)
end
end
- if ttype == "fired" and HealBot_Spells[baseSpell] then
+ if ttype == "fired" and HealBot_Spells[spell] then
if not HealBot_CastFailed then
- HealBot_CastingSpell = baseSpell;
+ HealBot_CastingSpell = spell;
HealBot_CastingTarget = target;
- HealBot_Process_HealValue(baseSpell, target);
+ HealBot_Process_HealValue(spell, target);
HealBot_AnnounceCast(spell, target);
end
end
@@ -648,7 +635,6 @@ function HealBot_RecalcParty()
end
end
end
- HealBot_Action_RefreshButtons();
end
-- HealBot_RecalcSpells: Recalculates spells and updates layout.
@@ -657,17 +643,24 @@ function HealBot_RecalcSpells()
HealBot_RecalcParty();
end
--- HealBot_GetTalentRank: Internal utility: HealBot_GetTalentRank
-function HealBot_GetTalentRank(talentName)
+HealBot_TalentRanks = {}
+
+function HealBot_UpdateTalentRanks()
+ HealBot_TalentRanks = {}
for t = 1, GetNumTalentTabs() do
for i = 1, GetNumTalents(t) do
- local nameTalent, icon, tier, column, currRank, maxRank = GetTalentInfo(t, i);
- if nameTalent == talentName then
- return currRank;
+ local nameTalent, icon, tier, column, currRank, maxRank = GetTalentInfo(t, i)
+ if nameTalent then
+ HealBot_TalentRanks[nameTalent] = currRank
end
end
end
- return 0;
+end
+
+-- HealBot_GetTalentRank: Internal utility: HealBot_GetTalentRank
+function HealBot_GetTalentRank(talentName)
+ if not next(HealBot_TalentRanks) then HealBot_UpdateTalentRanks() end
+ return HealBot_TalentRanks[talentName] or 0
end
-- HealBot_SpiBonus: Internal utility: HealBot_SpiBonus
@@ -702,6 +695,7 @@ end
-- HealBot_InitSpells: Scans spellbook to find available heals.
function HealBot_InitSpells()
+ HealBot_CacheSpellIDs()
local id = 1
local cnt = 0;
local class = HealBot_UnitClass("player")
diff --git a/HealBot_Data.lua b/HealBot_Data.lua
index 5695567..a4a4c8a 100644
--- a/HealBot_Data.lua
+++ b/HealBot_Data.lua
@@ -1,4 +1,5 @@
HealBot_ConfigDefaults = {
+ DebugMode = 0,
ShowManaBars=0,
ManaBarsHealersOnly=0,
Version = HEALBOT_VERSION,
@@ -260,6 +261,7 @@ HealBot_ConfigDefaults = {
HealBot_Integrations_Nampower = 0,
HealBot_Integrations_SuperWoW = 0,
HealBot_Integrations_ClassicAPI = 0,
+ HealBot_Integrations_ShowHoTTimers = 0,
};
HealBot_Config = {};
diff --git a/HealBot_Integrations.lua b/HealBot_Integrations.lua
index 42ac6c2..65a34a7 100644
--- a/HealBot_Integrations.lua
+++ b/HealBot_Integrations.lua
@@ -10,6 +10,8 @@ function HealBot_GetUnitGUID(unit)
return UnitGUID(unit)
elseif HealBot_Integrations_SuperWoW_Active and GetUnitGUID then
return GetUnitGUID(unit)
+ elseif HealBot_Integrations_Nampower_Active and GetUnitGUID then
+ return GetUnitGUID(unit)
end
return nil
end
@@ -40,6 +42,11 @@ function HealBot_Integrations_Toggle()
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
+ -- The player is ALWAYS the caster in these events for Nampower
+ if not HealBot_PlayerGUID then
+ HealBot_PlayerGUID = caster
+ end
+
local spellName = GetSpellRecField(spellID, "name")
if not spellName then return end
@@ -76,7 +83,7 @@ function HealBot_Integrations_PruneNampower()
local currentTime = GetTime()
for targetName, auras in pairs(HealBot_Nampower_Auras) do
-- Check if target is still in the raid/party
- if not HealBot_Model:GetUnitIDByName(targetName) then
+ if not HealBot_Model:GetUnitByGUID(targetName) and not HealBot_Model:GetUnitIDByName(targetName) then
HealBot_ReleaseTable(auras)
HealBot_Nampower_Auras[targetName] = nil
else
@@ -127,4 +134,16 @@ end
HealBot_Integrations_ClassicAPI_Active = false;
HealBot_AddDebug("ClassicAPI Integration: DISABLED");
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()
+ getglobal(HealBot_Options_Integrations_ShowHoTTimers:GetName().."Text"):SetTextColor(1, 1, 1)
+ else
+ HealBot_Options_Integrations_ShowHoTTimers:Disable()
+ getglobal(HealBot_Options_Integrations_ShowHoTTimers:GetName().."Text"):SetTextColor(0.5, 0.5, 0.5)
+ HealBot_Options_Integrations_ShowHoTTimers:SetChecked(0)
+ HealBot_Config.HealBot_Integrations_ShowHoTTimers = 0
+ end
+ end
end
diff --git a/HealBot_Localization.fr.lua b/HealBot_Localization.fr.lua
index f53e386..56f7b67 100644
--- a/HealBot_Localization.fr.lua
+++ b/HealBot_Localization.fr.lua
@@ -18,7 +18,7 @@ if (GetLocale() == "frFR") then
HEALBOT_DRUID = "Druide";
HEALBOT_HUNTER = "Chasseur";
-HEALBOT_MAGE = "Magier";
+HEALBOT_MAGE = "Mage";
HEALBOT_PALADIN = "Paladin";
HEALBOT_PRIEST = "Pr\195\170tre";
HEALBOT_ROGUE = "Voleur";
diff --git a/HealBot_Model.lua b/HealBot_Model.lua
index 5d3511b..2b8fd20 100644
--- a/HealBot_Model.lua
+++ b/HealBot_Model.lua
@@ -156,7 +156,7 @@ function HealBot_Model:UpdateUnitIdentity(unit)
self.units[unit].englishClass = englishClass
self.units[unit].class = UnitClass(unit)
- if (HealBot_Integrations_SuperWoW_Active or HealBot_Integrations_ClassicAPI_Active) and HealBot_GetUnitGUID then
+ if (HealBot_Integrations_SuperWoW_Active or HealBot_Integrations_ClassicAPI_Active or HealBot_Integrations_Nampower_Active) and HealBot_GetUnitGUID then
local guid = HealBot_GetUnitGUID(unit)
if guid and guid ~= "0" and guid ~= "0x0000000000000000" then
self.unitGUIDs[unit] = guid
@@ -185,7 +185,7 @@ end
-- HealBot_Model:PreserveStateByGUID: Preserves icon and debuff state when group indices shift during combat.
function HealBot_Model:PreserveStateByGUID()
- if not (HealBot_Integrations_SuperWoW_Active or HealBot_Integrations_ClassicAPI_Active) or not HealBot_GetUnitGUID then return end
+ if not (HealBot_Integrations_SuperWoW_Active or HealBot_Integrations_ClassicAPI_Active or HealBot_Integrations_Nampower_Active) or not HealBot_GetUnitGUID then return end
for k in pairs(pool_oldGUIDs) do pool_oldGUIDs[k] = nil end
local oldGUIDs = pool_oldGUIDs
diff --git a/HealBot_Options_Integrations.lua b/HealBot_Options_Integrations.lua
index 41cb703..d9d754b 100644
--- a/HealBot_Options_Integrations.lua
+++ b/HealBot_Options_Integrations.lua
@@ -4,4 +4,5 @@ function HealBot_Options_Integrations_OnShow(this)
HealBot_Options_Integrations_Nampower:SetChecked(HealBot_Config.HealBot_Integrations_Nampower);
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);
end
diff --git a/HealBot_Options_Integrations.xml b/HealBot_Options_Integrations.xml
index ab6cc20..f839c0f 100644
--- a/HealBot_Options_Integrations.xml
+++ b/HealBot_Options_Integrations.xml
@@ -65,7 +65,7 @@
-
+
@@ -80,6 +80,24 @@
+
+
+
+
+
+
+
+
+
+
+ getglobal(this:GetName().."Text"):SetText("Track Player HoT Timers");
+
+
+ HealBot_Config.HealBot_Integrations_ShowHoTTimers = this:GetChecked() and 1 or 0;
+
+
+
+
@@ -94,6 +112,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/HealBot_View_Layout.lua b/HealBot_View_Layout.lua
index 25179d3..e206bba 100644
--- a/HealBot_View_Layout.lua
+++ b/HealBot_View_Layout.lua
@@ -246,23 +246,38 @@ function HealBot_Action_EnableButton(button)
bar.txt:SetTextColor(sr, sg, sb, sa);
local fontName, fontHeight, fontFlags = bar.txt:GetFont()
local fontOutline = HealBot_Config.bfontoutline[HealBot_Config.Current_Skin] or 0
- if fontOutline == 1 then
- bar.txt:SetFont(fontName, fontHeight, "OUTLINE")
- else
- bar.txt:SetFont(fontName, fontHeight, "")
+ local expectedFlags = fontOutline == 1 and "OUTLINE" or ""
+ if fontFlags ~= expectedFlags then
+ bar.txt:SetFont(fontName, fontHeight, expectedFlags)
end
local iconSize = HealBot_Config.biconsize[HealBot_Config.Current_Skin] or 12
for i = 1, 10 do
- local icon = getglobal(button:GetName() .. "BarIcon" .. i)
+ local icon = button["icon"..i]
+ if not icon then
+ icon = getglobal(button:GetName() .. "BarIcon" .. i)
+ button["icon"..i] = icon
+ end
if icon then
if HealBot_UnitIcons and HealBot_UnitIcons[unit] and HealBot_UnitIcons[unit][i] then
- icon:SetTexture(HealBot_UnitIcons[unit][i])
- icon:SetWidth(iconSize)
- icon:SetHeight(iconSize)
- icon:Show()
+ local currentTexture = icon:GetTexture()
+ local newTexture = HealBot_UnitIcons[unit][i]
+ if currentTexture ~= newTexture then
+ icon:SetTexture(newTexture)
+ end
+
+ if icon:GetWidth() ~= iconSize then
+ icon:SetWidth(iconSize)
+ icon:SetHeight(iconSize)
+ end
+
+ if not icon:IsVisible() then
+ icon:Show()
+ end
else
- icon:Hide()
+ if icon:IsVisible() then
+ icon:Hide()
+ end
end
end
end
@@ -1224,3 +1239,155 @@ function HealBot_Action_AppendNewUnits()
end
HealBot_ReleaseTable(unitsToCheck)
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 = {}
+
+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
+
+ if HealBot_Config.HealBot_Integrations_ShowHoTTimers ~= 1 then
+ HealBot_PlayerHoTsListFrame:Hide()
+ return
+ else
+ HealBot_PlayerHoTsListFrame:Show()
+ 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
+ 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
+
+ -- Sort only the active portion by shifting expired ones to the end
+ table.sort(HealBot_PlayerHoTs_ListCache, function(a, b)
+ return (a.expires or 0) < (b.expires or 0)
+ end)
+
+ 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)
+ 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)
+ 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
+
+ local hot = HealBot_PlayerHoTs_ListCache[i]
+ local timeLeft = 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.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
diff --git a/README.md b/README.md
index a0a402c..d98adba 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\
* `/hb` - Toggles the main HealBot panel on and off
* `/hb options` - Toggles the HealBot options panel on and off
* `/hb reset` - Resets the contents of the main HealBot panel
+* `/hb debug` - Toggles in-game debug message output to the main chat frame
### Key Features & Current Functionality
* **MVC & Observer Architecture:** High-performance, reactive engine that updates frames dynamically on state changes, drastically reducing CPU and memory overhead. Features memory recycling pool and event loop decoupling for zero-stutter combat.
@@ -30,7 +31,7 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\
* **Talent-Based Calculations & Equipment Bonus:** Dynamic spell healing calculations for Druid, Priest, and Paladin based on talents, and automatically scans equipped gear to scale healing predictions.
* **Modifier-Aware Tooltips:** Tooltips dynamically update to show exact bound actions (Shift/Ctrl/Alt) and required mana costs (turns red if insufficient mana).
* **Blizzard Party Frame Toggle:** Toggle to automatically hide Blizzard's default party frames when in a group in favor of HealBot's layouts.
-* **HoT & Buff Tracking:** Intelligently track active HoTs and buffs directly on the grid frames with custom icons (e.g., Renew, Rejuvenation, Regrowth, Fear Ward).
+* **HoT & Buff Tracking:** Intelligently track active HoTs and buffs directly on the grid frames with custom icons (e.g., Renew, Rejuvenation, Regrowth, Fear Ward). Optional extended HoT timer tracker togglable below frames.
* **Mana Bars for Healers:** Enable and position mini-mana status bars next to unit frames, toggleable to display for healer classes only or all classes.
* **Curse & Debuff Warning (CDC):** Dynamic visual and audio alerts for cleanable debuffs. Customizable colors based on debuff type (Curse, Poison, Disease, Magic).
* **Pet & Familiar Frames:** Dedicated, toggleable frames for tracking and healing player pets.
@@ -49,6 +50,20 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\
### Change Log
+**v1.7.4**
+* **Performance Fix - CPU Spikes** - Fixed massive FPS drops during raid combat. The `HealBot_CheckBuffs` function previously forced the client to generate hidden UI tooltips for up to 32 buffs per player on every single `UNIT_AURA` event. Tooltip parsing is now cached per texture, bypassing thousands of redundant API calls per second.
+* **Performance Fix - Frame Rendering** - Eliminated redundant UI layout recalculations in `HealBot_Action_EnableButton`. The UI no longer forces `SetFont`, `SetWidth`, `SetHeight`, and `GetGlobal` lookups on every single combat tick (which previously caused 4,000+ layout updates per second in 40-man raids).
+* **Performance Fix - Range Checking** - Greatly reduced CPU stutter caused by range calculations. `HealBot_Range_Check` now caches map positioning math and API calls per-frame, preventing the addon from calculating the exact same `math.sqrt` distance up to 5 times per unit during heavy combat.
+* **Performance Fix - Unit ID Lookup** - Optimized `HealBot_FindUnitID` by pre-allocating raid unit tokens, preventing massive string concatenation and garbage collection spam during high-frequency combat events like `CHAT_MSG_ADDON` (HealComm).
+* **Performance Fix - String GC Leak** - Fixed garbage collection hitches caused by `string.gfind` closures in the HealComm parsing loop by rewriting it to use closure-free `string.find` native loops.
+* **Debug Output Command** - Added `/hb debug` (or `/hb dbg`) slash command to toggle real-time debug message printing directly into the standard chat frame without requiring custom chat channels.
+* **Performance Fix - Spell Lookup** - Fixed a massive CPU drain and garbage generation issue where the entire spellbook was linearly scanned every frame per unit. Spell IDs are now cached via O(1) dictionary lookup on login and talent/spell changes.
+* **Performance Fix - Unit Hash Lookup** - Eliminated heavy C-API calls to `UnitName()` during incoming heals by caching raid and party unit tokens in a fast O(1) hash table that updates on roster changes.
+* **Performance Fix - Range Cache GC Leak** - Fixed a continuous memory leak in the range checker caused by string concatenation (`unit .. "_" .. range`) running every frame. Range states are now stored in a nested numeric table cache.
+* **Performance Fix - Talent Rank Scan** - Fixed severe CPU stutter when gaining or losing auras that affect stats. Previously, inventory updates forced a full equipment and spell recalculation which linearly scanned the entire talent tree multiple times per rank. Talent ranks are now cached natively.
+* **Feature - HoT Tracking** - Added an option in the Integrations tab to track Player-cast HoT timers in a dedicated list below the main HealBot frame. Requires Nampower, SuperWoW, or ClassicAPI to fetch exact durations. Added support for tracking Power Word: Shield. Implemented with a pre-allocated static cache array for zero GC leaks, and precise GUID trapping for instant name resolution on self and non-party members.
+
+
**v1.7.3**
* **Performance Fix - Table Pooling Part 2** - Fixed remaining un-pooled table allocations in layout updates, macro parsing, nampower integration, and aura tracking by utilizing HealBot_GetTable().
* **Performance Fix - HealComm Networking** - Fixed issue where incoming `HealComm` messages triggered synchronous UI redraws, causing massive lag in 40-man raids during combat. `HealBot_RecalcHeals` is now decoupled and deferred through the `HealBot_OnUpdate` dirty queue, allowing multiple network events to collapse into a single frame redraw.
@@ -144,7 +159,6 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\
* **Chat Announcements & Mouseover Casts:** Chat announcements are now correctly fired when using native Hovercasting (Mouseover). Additionally, added safety checks to prevent spamming chat announcements when accidentally clicking on a dead player (unless casting a resurrection spell).
* **New Priest Buff (Enlighten):** Added the custom "Enlighten" buff to Priest trackable buffs, complete with the `btnholyscriptures` icon on unit frames.
-
**1.3.3**
* **Chat Panel Refactor:** Completely replaced the spell selection dropdowns in the Chat tab with manual text input boxes, allowing users to type exact spell names (with or without ranks) directly, matching the behavior of the key-bindings configuration. Fixed a string matching bug caused by Vanilla WoW's hidden trailing spaces when comparing spell names, ensuring that both rankless (e.g. `Flash Heal`) and ranked inputs trigger correctly.
* **Chat Layout Stack:** Restructured the Chat Options XML layout to correctly stack the inputs into neat rows, preventing horizontal overlap and providing more space for typing long announce messages.