8 Commits

Author SHA1 Message Date
Bluewhale1337 12838ff250 Update HealBot_Localization.en.lua
in-game displayed version update
2026-07-19 22:43:28 +02:00
Bluewhale1337 2d75406d88 Merge pull request #6 from Bluewhale1337/dev
Merge pull request #5 from Bluewhale1337/main
2026-07-18 12:40:48 +02:00
Bluewhale1337 2e35be6516 Merge pull request #5 from Bluewhale1337/main
Hotfix merge to dev
2026-07-18 12:40:18 +02:00
Bluewhale1337 07a9c3a93d Merge branch 'dev' into main
Signed-off-by: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com>
2026-07-18 12:40:04 +02:00
Bluewhale1337 c8e62fe80e docs: restore v1.4.2 changelog entry 2026-07-18 12:36:38 +02:00
Bluewhale1337 1334fc09bf perf: replace table.foreach with pairs loops to avoid GC spikes during combat 2026-07-18 12:27:51 +02:00
Bluewhale1337 ce594af5fa fix: memory leak in UnitAura and bump version to 1.4.2 2026-07-18 10:47:28 +02:00
Bluewhale1337 f1c5d6a8d1 Features: Expand action handling to support inline scripts, macros, and items while adding dynamic tooltip mana colorization and table memory management. 2026-07-15 14:56:03 +02:00
9 changed files with 257 additions and 64 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
## Interface: 11200
## Title: HealBotBlue
## Version: 1.4.1
## Version: 1.5
## Author: Bluewhale
## Notes: Adds panel with skinable bars for healing and decursive
## SavedVariables: HealBot_Config
+134 -21
View File
@@ -22,9 +22,9 @@ function HealBot_ShouldHeal(unit)
end
function HealBot_Action_ShouldHealSome()
return table.foreach(HealBot_Action_HealButtons, function (index,button)
for index, button in pairs(HealBot_Action_HealButtons) do
if (HealBot_ShouldHeal(button.unit)) then return button.unit; end
end);
end
end
function HealBot_MustHeal(unit)
@@ -32,9 +32,9 @@ function HealBot_MustHeal(unit)
end
function HealBot_Action_MustHealSome()
return table.foreach(HealBot_Action_HealButtons, function (index,button)
for index, button in pairs(HealBot_Action_HealButtons) do
if (HealBot_MustHeal(button.unit)) then return button.unit; end
end);
end
end
function HealBot_GetRezSpellForClass()
@@ -117,11 +117,107 @@ function HealBot_Action_HealUnit_OnLeave(this)
HealBot_Action_HideTooltip(this);
end
--------------------------------------------------------------------------------------------------
-- Puppeteer Utility Ports for Macro and Item Execution
--------------------------------------------------------------------------------------------------
function HealBot_SplitString(str, delimiter)
local result = {}
local start_pos = 1
while true do
local end_pos = string.find(str, delimiter, start_pos, true)
if not end_pos then
table.insert(result, string.sub(str, start_pos))
break
end
table.insert(result, string.sub(str, start_pos, end_pos - 1))
start_pos = end_pos + string.len(delimiter)
end
return result
end
function HealBot_RunMacroText(body)
local commands = HealBot_SplitString(body, "\n")
for i = 1, table.getn(commands) do
ChatFrameEditBox:SetText(commands[i])
ChatEdit_SendText(ChatFrameEditBox)
end
end
function HealBot_RunMacro(name)
if GetMacroIndexByName(name) == 0 then return end
local _, _, body = GetMacroInfo(GetMacroIndexByName(name))
HealBot_RunMacroText(body)
end
function HealBot_GetBagSlotInfo(bag, slot)
local link = GetContainerItemLink(bag, slot)
if not link then return end
local _, _, name = string.find(link, "%[(.*)%]")
local _, count = GetContainerItemInfo(bag, slot)
return name, count
end
function HealBot_FindBagSlot(itemName)
local bestBag, bestSlot, lowestStackSize
for bag = 0, NUM_BAG_FRAMES do
for slot = 1, GetContainerNumSlots(bag) do
local name, count = HealBot_GetBagSlotInfo(bag, slot)
if itemName == name then
if not lowestStackSize or lowestStackSize > count then
bestBag = bag
bestSlot = slot
lowestStackSize = count
end
end
end
end
return bestBag, bestSlot
end
function HealBot_UseItem(itemName)
local bag, slot = HealBot_FindBagSlot(itemName)
if not bag then return false end
UseContainerItem(bag, slot)
return true
end
function HealBot_Action_HealUnit_OnClick(this,button)
local decode_button = HealBot_Decode_Button(button);
local pattern = HealBot_Action_SpellPattern(decode_button);
-- Resurrection override on dead target
if not pattern then return end
-- Buff casting override (Spells only)
if HealBot_Config.BuffWatch == 1 then
local inCombat = UnitAffectingCombat("player")
if (not inCombat) or (HealBot_Config.BuffWatchInCombat == 1) then
if HealBot_MissingBuffs[this.unit] then
local missingBuff = HealBot_MissingBuffs[this.unit]
if decode_button == "Left" or decode_button == "Right" then
HealBot_CastSpellOnFriend(missingBuff, this.unit)
return
end
end
end
end
-- Priority 1: Inline Scripts (starts with /)
if string.sub(pattern, 1, 1) == "/" then
local oldTarget = nil
if UnitExists("target") then oldTarget = UnitName("target") end
TargetUnit(this.unit)
HealBot_RunMacroText(pattern)
if oldTarget then
TargetByName(oldTarget)
else
ClearTarget()
end
return
end
-- Priority 2: Spells
-- Resurrection override on dead target first
if UnitIsDeadOrGhost(this.unit) then
local rezSpell = HealBot_GetRezSpellForClass();
if rezSpell then
@@ -130,23 +226,40 @@ function HealBot_Action_HealUnit_OnClick(this,button)
end
end
-- Buff casting override
if HealBot_Config.BuffWatch == 1 then
local inCombat = UnitAffectingCombat("player")
if (not inCombat) or (HealBot_Config.BuffWatchInCombat == 1) then
local myClass = UnitClass("player")
if HealBot_MissingBuffs[this.unit] then
local missingBuff = HealBot_MissingBuffs[this.unit]
if decode_button == "Left" or decode_button == "Right" then
HealBot_CastSpellOnFriend(missingBuff, this.unit)
return
end
end
end
end
if HealBot_Spells[pattern] or HealBot_GetSpellId(pattern) then
HealBot_HealUnit(this.unit, pattern);
return
end
HealBot_HealUnit(this.unit,pattern);
end
-- Priority 3: Named Macros
if GetMacroIndexByName(pattern) ~= 0 then
local oldTarget = nil
if UnitExists("target") then oldTarget = UnitName("target") end
TargetUnit(this.unit)
HealBot_RunMacro(pattern)
if oldTarget then
TargetByName(oldTarget)
else
ClearTarget()
end
return
end
-- Priority 4: Items
local bag, slot = HealBot_FindBagSlot(pattern)
if bag then
local oldTarget = nil
if UnitExists("target") then oldTarget = UnitName("target") end
TargetUnit(this.unit)
UseContainerItem(bag, slot)
if SpellIsTargeting() then SpellTargetUnit(this.unit) end
if oldTarget then TargetByName(oldTarget) else ClearTarget() end
return
end
-- Fallback attempt
HealBot_HealUnit(this.unit, pattern);
end
function HealBot_Action_HealUnitCheck_OnClick(this)
if not this.unit then return end
+16 -12
View File
@@ -115,23 +115,27 @@ 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,
}
function HealBot_OnEvent_UnitAura(this, unit)
local DebuffType;
if HealBot_Heals[unit] and unit ~= "target" then
HealBot_UnitIcons[unit] = {}
if not HealBot_UnitIcons[unit] then
HealBot_UnitIcons[unit] = {}
end
for j = 1, 5 do
HealBot_UnitIcons[unit][j] = nil
end
local iconCount = 0
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,
}
local i = 1;
while true do
local debuff, tmp, debuff_type = UnitDebuff(unit, i, 1)
+31 -7
View File
@@ -475,19 +475,43 @@ end
function HealBot_GetHealSpell(unit, pattern)
if (not UnitName(unit)) then return nil end;
if not pattern then return nil end;
if UnitOnTaxi("player") then return nil end;
if HealBot_Config.ProtectPvP == 1 and UnitIsPVP(unit) and not UnitIsPVP("player") then return nil end
if HealBot_UnitClass("player") == "DRUID" then
if HealBot_ActiveShapeshiftStance and HealBot_Config.AutoUnshift ~= 1 then return nil end;
end
local spell = HealBot_GetSpellName(HealBot_GetSpellId(pattern))
local range = 40;
if HealBot_Spells[spell] then
if not HealBot_CanCastSpell(spell, unit) then return nil end;
range = HealBot_Spells[spell].range;
-- Handle Inline Scripts
if string.sub(pattern, 1, 1) == "/" then
return pattern;
end
if HealBot_Range_Check(unit, range) == 0 then return nil end;
return spell;
-- Handle Spells
if HealBot_Spells[pattern] or HealBot_GetSpellId(pattern) then
local spell = HealBot_GetSpellName(HealBot_GetSpellId(pattern)) or pattern
local range = 40;
if HealBot_Spells[spell] then
if not HealBot_CanCastSpell(spell, unit) then return nil end;
range = HealBot_Spells[spell].range or 40;
end
if HealBot_Range_Check(unit, range) == 0 then return nil end;
return spell;
end
-- Handle Named Macros
if GetMacroIndexByName(pattern) ~= 0 then
return pattern;
end
-- Handle Items
local bag, slot = HealBot_FindBagSlot(pattern)
if bag then
-- optionally check item range, but skipping for now as it's complex in vanilla
return pattern;
end
return nil;
end
function HealBot_HealUnit(unit, pattern)
+20
View File
@@ -559,3 +559,23 @@ HealBot_SpamCnt=0;
HealBot_Action_TooltipUnit=nil;
HealBot_Ressing = {};
HealBot_IamRessing = false;
-- Table Recycling Pool (Puppeteer-inspired)
local tablePool = {}
function HealBot_GetTable()
local t = table.remove(tablePool)
if not t then
t = {}
end
return t
end
function HealBot_ReleaseTable(t)
if type(t) == "table" then
for k in pairs(t) do
t[k] = nil
end
table.insert(tablePool, t)
end
end
+1 -1
View File
@@ -1,4 +1,4 @@
HEALBOT_VERSION = "1.4.1";
HEALBOT_VERSION = "1.5";
-------------
-- ENGLISH --
+11 -11
View File
@@ -255,9 +255,9 @@ function HealBot_Action_EnableButton(button)
end
function HealBot_Action_EnableButtons()
table.foreach(HealBot_Action_HealButtons, function (index, button)
HealBot_Action_EnableButton(button);
end);
for index, button in pairs(HealBot_Action_HealButtons) do
HealBot_Action_EnableButton(button)
end
end
function HealBot_Action_RefreshButton(button)
@@ -285,13 +285,13 @@ end
function HealBot_Action_RefreshButtons(unit)
if unit and HealBot_Action_UnitButtons[unit] then
table.foreach(HealBot_Action_UnitButtons[unit], function (index, button)
HealBot_Action_RefreshButton(button);
end);
for index, button in pairs(HealBot_Action_UnitButtons[unit]) do
HealBot_Action_RefreshButton(button)
end
else
table.foreach(HealBot_Action_HealButtons, function (index, button)
HealBot_Action_RefreshButton(button);
end);
for index, button in pairs(HealBot_Action_HealButtons) do
HealBot_Action_RefreshButton(button)
end
end
end
@@ -748,7 +748,7 @@ function HealBot_Action_PartyChanged()
local i = 0;
local z = 1;
table.foreach(HealBot_Action_HealButtons, function (index, button)
for index, button in pairs(HealBot_Action_HealButtons) do
i = i + 1;
local checked = false;
local header;
@@ -787,7 +787,7 @@ function HealBot_Action_PartyChanged()
bar:SetScale(barScale);
bar2:SetHeight(bheight);
HealBot_Action_SetTexture(bar2, btexture);
end);
end
if MaxOffsetY < OffsetY then MaxOffsetY = OffsetY; end
+30 -10
View File
@@ -3,6 +3,16 @@
HealBot_Action_TooltipUnit = nil
function HealBot_Tooltip_GetSpellColor(spell)
local r, g, b = 1, 1, 0 -- default yellow
if spell and HealBot_Spells and HealBot_Spells[spell] and HealBot_Spells[spell].Mana then
if UnitMana("player") < HealBot_Spells[spell].Mana then
r, g, b = 1, 0, 0 -- red if not enough mana
end
end
return r, g, b
end
function HealBot_Action_RefreshTooltip(unit)
if HealBot_Config.ShowTooltip==0 then return end
if not unit then unit = HealBot_Action_TooltipUnit end
@@ -33,53 +43,63 @@ function HealBot_Action_RefreshTooltip(unit)
if HealBot_Config.Tooltip_ShowSpellDetail==1 then
if spellLeft then
linenum=linenum+2
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTONLEFT.." "..HEALBOT_OPTIONS_COMBOBUTTON..": "..spellLeft,1,1,0,linenum)
local sr, sg, sb = HealBot_Tooltip_GetSpellColor(spellLeft)
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTONLEFT.." "..HEALBOT_OPTIONS_COMBOBUTTON..": "..spellLeft,sr,sg,sb,linenum)
linenum=HealBot_Action_Tooltip_SpellInfo(spellLeft,linenum);
end
if spellMiddle then
linenum=linenum+2
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTONMIDDLE.." "..HEALBOT_OPTIONS_COMBOBUTTON..": "..spellMiddle,1,1,0,linenum)
local sr, sg, sb = HealBot_Tooltip_GetSpellColor(spellMiddle)
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTONMIDDLE.." "..HEALBOT_OPTIONS_COMBOBUTTON..": "..spellMiddle,sr,sg,sb,linenum)
linenum=HealBot_Action_Tooltip_SpellInfo(spellMiddle,linenum);
end
if spellRight then
linenum=linenum+2
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTONRIGHT.." "..HEALBOT_OPTIONS_COMBOBUTTON..": "..spellRight,1,1,0,linenum)
local sr, sg, sb = HealBot_Tooltip_GetSpellColor(spellRight)
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTONRIGHT.." "..HEALBOT_OPTIONS_COMBOBUTTON..": "..spellRight,sr,sg,sb,linenum)
linenum=HealBot_Action_Tooltip_SpellInfo(spellRight,linenum);
end
if spellButton4 then
linenum=linenum+2
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTON4.." "..HEALBOT_OPTIONS_COMBOBUTTON..": "..spellButton4,1,1,0,linenum)
local sr, sg, sb = HealBot_Tooltip_GetSpellColor(spellButton4)
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTON4.." "..HEALBOT_OPTIONS_COMBOBUTTON..": "..spellButton4,sr,sg,sb,linenum)
linenum=HealBot_Action_Tooltip_SpellInfo(spellButton4,linenum);
end
if spellButton5 then
linenum=linenum+2
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTON5.." "..HEALBOT_OPTIONS_COMBOBUTTON..": "..spellButton5,1,1,0,linenum)
local sr, sg, sb = HealBot_Tooltip_GetSpellColor(spellButton5)
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTON5.." "..HEALBOT_OPTIONS_COMBOBUTTON..": "..spellButton5,sr,sg,sb,linenum)
linenum=HealBot_Action_Tooltip_SpellInfo(spellButton5,linenum);
end
else
if spellLeft then
linenum=linenum+1
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTONLEFT..":",1,1,0,linenum)
local sr, sg, sb = HealBot_Tooltip_GetSpellColor(spellLeft)
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTONLEFT..":",sr,sg,sb,linenum)
HealBot_Action_Tooltip_SetLineRight(HealBot_Action_Tooltip_SpellSummary(spellLeft),1,1,1,linenum)
end
if spellMiddle then
linenum=linenum+1
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTONMIDDLE..":",1,1,0,linenum)
local sr, sg, sb = HealBot_Tooltip_GetSpellColor(spellMiddle)
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTONMIDDLE..":",sr,sg,sb,linenum)
HealBot_Action_Tooltip_SetLineRight(HealBot_Action_Tooltip_SpellSummary(spellMiddle),1,1,1,linenum)
end
if spellRight then
linenum=linenum+1
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTONRIGHT..":",1,1,0,linenum)
local sr, sg, sb = HealBot_Tooltip_GetSpellColor(spellRight)
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTONRIGHT..":",sr,sg,sb,linenum)
HealBot_Action_Tooltip_SetLineRight(HealBot_Action_Tooltip_SpellSummary(spellRight),1,1,1,linenum)
end
if spellButton4 then
linenum=linenum+1
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTON4..":",1,1,0,linenum)
local sr, sg, sb = HealBot_Tooltip_GetSpellColor(spellButton4)
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTON4..":",sr,sg,sb,linenum)
HealBot_Action_Tooltip_SetLineRight(HealBot_Action_Tooltip_SpellSummary(spellButton4),1,1,1,linenum)
end
if spellButton5 then
linenum=linenum+1
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTON5..":",1,1,0,linenum)
local sr, sg, sb = HealBot_Tooltip_GetSpellColor(spellButton5)
HealBot_Action_Tooltip_SetLineLeft(HEALBOT_OPTIONS_BUTTON5..":",sr,sg,sb,linenum)
HealBot_Action_Tooltip_SetLineRight(HealBot_Action_Tooltip_SpellSummary(spellButton5),1,1,1,linenum)
end
end
+12
View File
@@ -31,12 +31,24 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\
* **Non-Mana Resource Tracking:** Track Rage, Energy, and Focus for non-mana classes.
* **Highly Customizable Skins:** Fully configure dimensions (width, height), row spacing, column layouts, custom textures, opacity, class-colored frames, and outline of fonts.
### To be implemented
- **GUID-based frame mapping:** Migrate internal state to use lightweight GUID tracking to resolve bugs when targets switch or duplicate names appear (e.g., enemy mobs, warlock pets).
- **Mod Integration Optimization:** Add conditional SuperWoW/UnitXP SP3 integration to reduce GC spikes and improve frame rendering accuracy.
### Known issues
* **New spell rank bug** you may experience lua error spaming your chat frame after learning new healing spells. /reload will clear the issue. To be fixed next release
### Change Log
**v1.5**
* **Macro, Item, and Script Bindings:** Support for binding named macros, inventory items, and inline scripts (e.g. `/target`) directly to mouse clicks in the Spells tab. All of these now natively support implicit `@mouseover` targeting on the unit frame you click, without losing your current target.
* **Modifier-Aware Tooltips & Resource Costs:** The tooltip dynamically updates to show exactly what action is bound when holding down Shift, Ctrl, or Alt over a frame. Spells also show their required mana cost, turning red if you do not have enough mana to cast them.
* **Memory Recycling Pool:** Added a local table recycling pool in the data layer to greatly reduce Lua garbage collection (GC) spikes, improving overall client frame rates during intense combat.
**v1.4.2**
* **Memory Optimization:** Fixed a significant memory leak where tracking tables were being repeatedly allocated every tick during aura scanning, and replaced `table.foreach` with pairs loops to avoid GC spikes during combat.
**v1.4.1**
* **Talent-based Healing Calculations:** Implemented dynamic spell healing calculations for Druid, Priest, and Paladin classes based on talents.
* **Modifier Key Polling:** Replaced MODIFIER_STATE_CHANGED event with polling in HealBot_OnUpdate to track modifier keys reliably.