Merge branch 'dev' into main

Signed-off-by: Bluewhale1337 <295648290+Bluewhale1337@users.noreply.github.com>
This commit is contained in:
Bluewhale1337
2026-09-09 18:53:27 +02:00
committed by GitHub
9 changed files with 215 additions and 68 deletions
+1 -1
View File
@@ -50,7 +50,7 @@
<Texture name="$parentRaidIcon" file="Interface\TargetingFrame\UI-RaidTargetingIcons" hidden="true"> <Texture name="$parentRaidIcon" file="Interface\TargetingFrame\UI-RaidTargetingIcons" hidden="true">
<Size><AbsDimension x="14" y="14"/></Size> <Size><AbsDimension x="14" y="14"/></Size>
<Anchors> <Anchors>
<Anchor point="CENTER"> <Anchor point="TOP">
<Offset><AbsDimension x="0" y="0"/></Offset> <Offset><AbsDimension x="0" y="0"/></Offset>
</Anchor> </Anchor>
</Anchors> </Anchors>
+16 -10
View File
@@ -159,6 +159,7 @@ local HealBot_TrackedHoTs = {
} }
local HealBot_DebuffTypeMap = nil local HealBot_DebuffTypeMap = nil
local HealBot_AuraWarningPlayed = {}
-- HealBot_OnEvent_UnitAura: Updates debuff lists and icon textures on aura change. -- HealBot_OnEvent_UnitAura: Updates debuff lists and icon textures on aura change.
function HealBot_OnEvent_UnitAura(this, unit) function HealBot_OnEvent_UnitAura(this, unit)
@@ -276,19 +277,24 @@ function HealBot_OnEvent_UnitAura(this, unit)
end end
if HealBot_UnitDebuff[unit] then if HealBot_UnitDebuff[unit] then
if DebuffType and HealBot_Range_Check(unit, 27) == 1 then if DebuffType and HealBot_Range_Check(unit, 40) == 1 then
if HealBot_Config.ShowDebuffWarning == 1 then if HealBot_AuraWarningPlayed[unit] ~= DebuffType then
local color = HealBot_Config.CDCBarColour[DebuffType] HealBot_AuraWarningPlayed[unit] = DebuffType
local r, g, b = 1, 0, 0 if HealBot_Config.ShowDebuffWarning == 1 then
if color then local color = HealBot_Config.CDCBarColour[DebuffType]
r, g, b = color.R, color.G, color.B local r, g, b = 1, 0, 0
if color then
r, g, b = color.R, color.G, color.B
end
UIErrorsFrame:AddMessage(UnitName(unit) .. " suffers from " .. DebuffType,
r, g, b,
1, UIERRORS_HOLD_TIME);
end end
UIErrorsFrame:AddMessage(UnitName(unit) .. " suffers from " .. DebuffType, if HealBot_Config.SoundDebuffWarning == 1 then HealBot_PlaySound(HealBot_Config.SoundDebuffPlay); end
r, g, b,
1, UIERRORS_HOLD_TIME);
end end
if HealBot_Config.SoundDebuffWarning == 1 then HealBot_PlaySound(HealBot_Config.SoundDebuffPlay); end
end end
else
HealBot_AuraWarningPlayed[unit] = nil
end end
-- Check buffs synchronously because tooltip scanning fails in OnUpdate -- Check buffs synchronously because tooltip scanning fails in OnUpdate
HealBot_CheckBuffs(unit) HealBot_CheckBuffs(unit)
+102 -16
View File
@@ -71,33 +71,119 @@ function HealBot_SendAddonMessage(prefix, text)
end end
end end
-- HealBot_OnEvent_AddonMsg: Parses incoming heal data from other clients. if not HealBot_IncomingHealers then HealBot_IncomingHealers = {} end
function HealBot_OnEvent_AddonMsg(this, addon_id, inc_msg, dist_target, sender_id)
if addon_id == HEALBOT_ADDON_ID then -- Reusable global array for gfind string splitting
local tmpTest, unitname, heal_val if not HealBot_Comms_Args then HealBot_Comms_Args = {} end
tmpTest, tmpTest, unitname, heal_val = string.find(inc_msg, ">> (%a+) <<=>> (.%d+) <<" );
if heal_val then local function ClearIncomingHeal(sender)
if sender_id == UnitName("player") then return end local data = HealBot_IncomingHealers[sender]
if not HealBot_HealsIn[unitname] then if not data then return end
HealBot_HealsIn[unitname] = 0; local oldAmount = data.amount
for i=1, 5 do
local oldTarget = data.targets[i]
if oldTarget then
if HealBot_HealsIn[oldTarget] then
HealBot_HealsIn[oldTarget] = HealBot_HealsIn[oldTarget] - oldAmount
if HealBot_HealsIn[oldTarget] < 0 then HealBot_HealsIn[oldTarget] = 0 end
HealBot_RecalcHeals(HealBot_FindUnitID(oldTarget))
end end
HealBot_Healers[sender_id] = ">> " .. unitname .. " <<=>> " .. heal_val .. " <<"; data.targets[i] = nil
HealBot_HealsIn[unitname] = HealBot_HealsIn[unitname] + tonumber(heal_val); end
if tonumber(heal_val) > 0 then end
HealBot_RecalcHeals(HealBot_FindUnitID(unitname)) data.amount = 0
elseif HealBot_HealsIn[unitname] < 0 then end
HealBot_HealsIn[unitname] = 0;
local function SetIncomingHeal(sender, amount, protocol, t1, t2, t3, t4, t5)
-- prioritize HealComm over HealBot protocol to avoid double-counting
if HealBot_IncomingHealers[sender] and HealBot_IncomingHealers[sender].protocol == "HealComm" and protocol == "HealBot" then
return
end
if not HealBot_IncomingHealers[sender] then
HealBot_IncomingHealers[sender] = { targets = {}, amount = 0, protocol = protocol }
end
ClearIncomingHeal(sender)
local data = HealBot_IncomingHealers[sender]
data.protocol = protocol
if amount > 0 and t1 then
data.amount = amount
data.targets[1] = t1
data.targets[2] = t2
data.targets[3] = t3
data.targets[4] = t4
data.targets[5] = t5
for i=1, 5 do
local target = data.targets[i]
if target then
if not HealBot_HealsIn[target] then HealBot_HealsIn[target] = 0 end
HealBot_HealsIn[target] = HealBot_HealsIn[target] + amount
HealBot_RecalcHeals(HealBot_FindUnitID(target))
end end
end end
end
end
-- HealBot_OnEvent_AddonMsg: Parses incoming heal data from other clients.
function HealBot_OnEvent_AddonMsg(this, addon_id, inc_msg, dist_target, sender_id)
if sender_id == UnitName("player") then return end
if addon_id == "HealComm" then
-- 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
argCount = argCount + 1
HealBot_Comms_Args[argCount] = word
end
local cmd = HealBot_Comms_Args[1]
if cmd == "Heal" and HealBot_Comms_Args[2] and HealBot_Comms_Args[3] then
SetIncomingHeal(sender_id, tonumber(HealBot_Comms_Args[3]) or 0, "HealComm", HealBot_Comms_Args[2])
elseif cmd == "Healstop" then
SetIncomingHeal(sender_id, 0, "HealComm")
elseif cmd == "GrpHeal" and HealBot_Comms_Args[2] then
SetIncomingHeal(sender_id, tonumber(HealBot_Comms_Args[2]) or 0, "HealComm", HealBot_Comms_Args[4], HealBot_Comms_Args[5], HealBot_Comms_Args[6], HealBot_Comms_Args[7], HealBot_Comms_Args[8])
elseif cmd == "GrpHealstop" then
SetIncomingHeal(sender_id, 0, "HealComm")
elseif cmd == "Resurrection" then
if HealBot_Comms_Args[2] == "stop" then
HealBot_AddDebug(sender_id .. " Stopped ressing");
for unit, resser in pairs(HealBot_Ressing) do
if resser == sender_id then
HealBot_Ressing[unit] = nil;
HealBot_RecalcHeals(HealBot_FindUnitID(unit));
end
end
elseif HealBot_Comms_Args[2] and HealBot_Comms_Args[3] == "start" then
HealBot_AddDebug(sender_id .. " is ressing " .. HealBot_Comms_Args[2]);
HealBot_Ressing[HealBot_Comms_Args[2]] = sender_id;
HealBot_RecalcHeals(HealBot_FindUnitID(HealBot_Comms_Args[2]));
end
end
elseif addon_id == HEALBOT_ADDON_ID then
local tmpTest, tmpTest, unitname, heal_val = string.find(inc_msg, ">> (.-) <<=>> (.-) <<" );
if heal_val and unitname then
local amount = tonumber(heal_val) or 0
if amount < 0 then amount = 0 end -- HealBot sends negative to cancel
SetIncomingHeal(sender_id, amount, "HealBot", unitname)
end
elseif addon_id == "HealBot" then elseif addon_id == "HealBot" then
local tmpTest, datatype, datamsg, sender local tmpTest, datatype, datamsg, sender
local PName = UnitName("player"); local PName = UnitName("player");
tmpTest, tmpTest, datatype, sender, datamsg = string.find(inc_msg, ">> (%a+) <<=>> (%a+) <<=>> (.+)"); tmpTest, tmpTest, datatype, sender, datamsg = string.find(inc_msg, ">> (.-) <<=>> (.-) <<=>> (.+)");
if datatype == "RequestVersion" then if datatype == "RequestVersion" then
HealBot_SendAddonMessage("HealBot", ">> SendVersion <<=>> " .. sender .. " <<=>> Version=" .. HEALBOT_VERSION); HealBot_SendAddonMessage("HealBot", ">> SendVersion <<=>> " .. sender .. " <<=>> Version=" .. HEALBOT_VERSION);
elseif datatype == "SendVersion" and PName == sender then elseif datatype == "SendVersion" and PName == sender then
HealBot_AddChat(sender_id .. ": " .. datamsg); HealBot_AddChat(sender_id .. ": " .. datamsg);
end end
elseif addon_id == "CTRA" then elseif addon_id == "CTRA" then
if ( string.sub(inc_msg, 1, 3) == "RES" ) then if ( string.sub(inc_msg, 1, 3) == "RES" ) then
if ( inc_msg == "RESNO" ) then if ( inc_msg == "RESNO" ) then
+22 -11
View File
@@ -3,6 +3,8 @@
HealBot_View_DirtyUnits = {} HealBot_View_DirtyUnits = {}
HealBot_View_DirtyPower = {} HealBot_View_DirtyPower = {}
local unitsToRefresh = {}
local powerToRefresh = {}
local HealBot_Timer1, HealsIn_Timer = 0, 0; local HealBot_Timer1, HealsIn_Timer = 0, 0;
HealBot_LastModState = "" HealBot_LastModState = ""
@@ -152,7 +154,7 @@ function HealBot_OnUpdate(this, arg1)
end end
-- Process Dirty Queue for MVC View -- Process Dirty Queue for MVC View
local unitsToRefresh = {} for k in pairs(unitsToRefresh) do unitsToRefresh[k] = nil end
for unitID in pairs(HealBot_View_DirtyUnits) do for unitID in pairs(HealBot_View_DirtyUnits) do
unitsToRefresh[unitID] = true unitsToRefresh[unitID] = true
HealBot_View_DirtyUnits[unitID] = nil HealBot_View_DirtyUnits[unitID] = nil
@@ -162,7 +164,7 @@ function HealBot_OnUpdate(this, arg1)
HealBot_Action_Refresh(unitID) HealBot_Action_Refresh(unitID)
end end
local powerToRefresh = {} for k in pairs(powerToRefresh) do powerToRefresh[k] = nil end
for unitID in pairs(HealBot_View_DirtyPower) do for unitID in pairs(HealBot_View_DirtyPower) do
powerToRefresh[unitID] = true powerToRefresh[unitID] = true
HealBot_View_DirtyPower[unitID] = nil HealBot_View_DirtyPower[unitID] = nil
@@ -343,18 +345,19 @@ end
function HealBot_OnEvent_VariablesLoaded(this) function HealBot_OnEvent_VariablesLoaded(this)
local class = HealBot_UnitClass("player") local class = HealBot_UnitClass("player")
table.foreach(HealBot_ConfigDefaults, function (key, val) local function DeepCopyMissing(src, dest)
if not HealBot_Config[key] then for k, v in pairs(src) do
HealBot_Config[key] = val; if type(v) == "table" then
end if type(dest[k]) ~= "table" then dest[k] = {} end
if type(val) == "table" and type(HealBot_Config[key]) == "table" then DeepCopyMissing(v, dest[k])
for k, v in pairs(val) do else
if HealBot_Config[key][k] == nil then if dest[k] == nil then
HealBot_Config[key][k] = v dest[k] = v
end end
end end
end end
end); end
DeepCopyMissing(HealBot_ConfigDefaults, HealBot_Config)
local foundModern = false local foundModern = false
if HealBot_Config.Skins then if HealBot_Config.Skins then
@@ -480,6 +483,14 @@ end
function HealBot_OnEvent_PlayerRegenEnabled(this) function HealBot_OnEvent_PlayerRegenEnabled(this)
HealBot_IsFighting = false; HealBot_IsFighting = false;
HealBot_Delay_RecalcParty = 1; HealBot_Delay_RecalcParty = 1;
if HealBot_IncomingHealers then
for sender, data in pairs(HealBot_IncomingHealers) do
if not HealBot_FindUnitID(sender) then
HealBot_IncomingHealers[sender] = nil
end
end
end
end end
-- HealBot_OnEvent_PlayerTargetChanged: Internal utility: HealBot_OnEvent_PlayerTargetChanged -- HealBot_OnEvent_PlayerTargetChanged: Internal utility: HealBot_OnEvent_PlayerTargetChanged
+2
View File
@@ -159,6 +159,7 @@ function HealBot_Process_HealValue(spell, target)
local uname = UnitName(target) local uname = UnitName(target)
if uname then if uname then
HealBot_SendAddonMessage(HEALBOT_ADDON_ID, ">> " .. uname .. " <<=>> " .. HealBot_HealValue .. " << "); HealBot_SendAddonMessage(HEALBOT_ADDON_ID, ">> " .. uname .. " <<=>> " .. HealBot_HealValue .. " << ");
HealBot_SendAddonMessage("HealComm", "Heal/" .. uname .. "/" .. HealBot_HealValue .. "/1500/");
if not HealBot_HealsIn[uname] then if not HealBot_HealsIn[uname] then
HealBot_HealsIn[uname] = 0; HealBot_HealsIn[uname] = 0;
end end
@@ -217,6 +218,7 @@ function HealBot_StopCasting()
local uname = UnitName(HealBot_CastingTarget) local uname = UnitName(HealBot_CastingTarget)
if uname then if uname then
HealBot_SendAddonMessage(HEALBOT_ADDON_ID, ">> " .. uname .. " <<=>> " .. 0 - HealBot_HealValue .. " << "); HealBot_SendAddonMessage(HEALBOT_ADDON_ID, ">> " .. uname .. " <<=>> " .. 0 - HealBot_HealValue .. " << ");
HealBot_SendAddonMessage("HealComm", "Healstop");
if HealBot_HealsIn[uname] then if HealBot_HealsIn[uname] then
HealBot_HealsIn[uname] = HealBot_HealsIn[uname] - HealBot_HealValue; HealBot_HealsIn[uname] = HealBot_HealsIn[uname] - HealBot_HealValue;
if HealBot_HealsIn[uname] < 0 then if HealBot_HealsIn[uname] < 0 then
+14 -12
View File
@@ -36,22 +36,24 @@ function HealBot_Integrations_Toggle()
HealBot_NampowerFrame:RegisterEvent("AURA_CAST_ON_OTHER") HealBot_NampowerFrame:RegisterEvent("AURA_CAST_ON_OTHER")
HealBot_NampowerFrame:SetScript("OnEvent", function() HealBot_NampowerFrame:SetScript("OnEvent", function()
if not HealBot_Integrations_Nampower_Active then return end if not HealBot_Integrations_Nampower_Active then return end
local spellID, caster, target, _, _, _, _, duration = arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 if event == "AURA_CAST_ON_SELF" or event == "AURA_CAST_ON_OTHER" then
if not caster or not target or duration <= 0 then return end 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
local spellName = GetSpellRecField(spellID, "name") local spellName = GetSpellRecField(spellID, "name")
if not spellName then return end if not spellName then return end
if not HealBot_Nampower_Auras[target] then if not HealBot_Nampower_Auras[target] then
HealBot_Nampower_Auras[target] = {} HealBot_Nampower_Auras[target] = {}
end end
local expirationTime = GetTime() + (duration / 1000) local expirationTime = GetTime() + (duration / 1000)
HealBot_Nampower_Auras[target][spellName] = expirationTime HealBot_Nampower_Auras[target][spellName] = expirationTime
local unitID = HealBot_Model:GetUnitIDByName(target) local unitID = HealBot_Model:GetUnitIDByName(target)
if unitID then if unitID then
HealBot_OnEvent_UnitAura(nil, unitID) HealBot_OnEvent_UnitAura(nil, unitID)
end
end end
end) end)
end end
+34 -10
View File
@@ -1,6 +1,13 @@
-- HealBot_Model.lua -- HealBot_Model.lua
-- Centralized Data Store and Observer System for HealBotBlue -- Centralized Data Store and Observer System for HealBotBlue
local pool_oldGUIDs = {}
local pool_newUnitForGUID = {}
local pool_stateSwaps = {}
local pool_iconSwaps = {}
local pool_missingBuffSwaps = {}
local pool_debuffSwaps = {}
-- Safe local wrappers to prevent native UIDropDownMenu concatenation crashes -- Safe local wrappers to prevent native UIDropDownMenu concatenation crashes
-- when setting selected values on closed dropdowns during initialization. -- when setting selected values on closed dropdowns during initialization.
function HealBot_UIDropDownMenu_SetSelectedID(frame, id, useValue) function HealBot_UIDropDownMenu_SetSelectedID(frame, id, useValue)
@@ -180,14 +187,16 @@ end
function HealBot_Model:PreserveStateByGUID() 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 not HealBot_GetUnitGUID then return end
local oldGUIDs = {} for k in pairs(pool_oldGUIDs) do pool_oldGUIDs[k] = nil end
local oldGUIDs = pool_oldGUIDs
for unit, guid in pairs(self.unitGUIDs) do for unit, guid in pairs(self.unitGUIDs) do
if string.find(unit, "^party") or string.find(unit, "^raid") or unit == "player" or unit == "pet" then if string.find(unit, "^party") or string.find(unit, "^raid") or unit == "player" or unit == "pet" then
oldGUIDs[unit] = guid oldGUIDs[unit] = guid
end end
end end
local newUnitForGUID = {} for k in pairs(pool_newUnitForGUID) do pool_newUnitForGUID[k] = nil end
local newUnitForGUID = pool_newUnitForGUID
-- Scan the new roster -- Scan the new roster
for _, unit in ipairs(self.partyMembers) do for _, unit in ipairs(self.partyMembers) do
local guid = HealBot_GetUnitGUID(unit) local guid = HealBot_GetUnitGUID(unit)
@@ -206,10 +215,14 @@ function HealBot_Model:PreserveStateByGUID()
end end
end end
local stateSwaps = {} for k in pairs(pool_stateSwaps) do pool_stateSwaps[k] = nil end
local iconSwaps = {} local stateSwaps = pool_stateSwaps
local missingBuffSwaps = {} for k in pairs(pool_iconSwaps) do pool_iconSwaps[k] = nil end
local debuffSwaps = {} local iconSwaps = pool_iconSwaps
for k in pairs(pool_missingBuffSwaps) do pool_missingBuffSwaps[k] = nil end
local missingBuffSwaps = pool_missingBuffSwaps
for k in pairs(pool_debuffSwaps) do pool_debuffSwaps[k] = nil end
local debuffSwaps = pool_debuffSwaps
for oldUnit, guid in pairs(oldGUIDs) do for oldUnit, guid in pairs(oldGUIDs) do
local newUnit = newUnitForGUID[guid] local newUnit = newUnitForGUID[guid]
@@ -231,11 +244,22 @@ function HealBot_Model:PreserveStateByGUID()
for targetUnit, stateData in pairs(stateSwaps) do for targetUnit, stateData in pairs(stateSwaps) do
-- Deep copy to prevent memory aliasing -- Deep copy to prevent memory aliasing
self.units[targetUnit] = {} if not self.units[targetUnit] then
for k, v in pairs(stateData) do self.units[targetUnit] = { icons = {} }
self.units[targetUnit][k] = v
end end
self.units[targetUnit].icons = {}
local targetIcons = self.units[targetUnit].icons
if not targetIcons then targetIcons = {} end
for k in pairs(targetIcons) do targetIcons[k] = nil end
for k in pairs(self.units[targetUnit]) do self.units[targetUnit][k] = nil end
for k, v in pairs(stateData) do
if k ~= "icons" then
self.units[targetUnit][k] = v
end
end
self.units[targetUnit].icons = targetIcons
if HealBot_UnitIcons and iconSwaps[targetUnit] then if HealBot_UnitIcons and iconSwaps[targetUnit] then
if not HealBot_UnitIcons[targetUnit] then HealBot_UnitIcons[targetUnit] = {} end if not HealBot_UnitIcons[targetUnit] then HealBot_UnitIcons[targetUnit] = {} end
+12 -4
View File
@@ -234,10 +234,18 @@ end
-- HealBot_Options_Defaults_OnClick: UI handler for OnClick options panel. -- HealBot_Options_Defaults_OnClick: UI handler for OnClick options panel.
function HealBot_Options_Defaults_OnClick(this) function HealBot_Options_Defaults_OnClick(this)
HealBot_Options_CastNotify_OnClick(nil,0); HealBot_Options_CastNotify_OnClick(nil,0);
-- HealBot_Config = HealBot_ConfigDefaults; HealBot_Config = {}
table.foreach(HealBot_ConfigDefaults, function (key,val) local function DeepCopy(src, dest)
HealBot_Config[key] = val; for k, v in pairs(src) do
end); if type(v) == "table" then
dest[k] = {}
DeepCopy(v, dest[k])
else
dest[k] = v
end
end
end
DeepCopy(HealBot_ConfigDefaults, HealBot_Config)
HealBot_Options_OnShow(HealBot_Options); HealBot_Options_OnShow(HealBot_Options);
HealBot_RecalcSpells(); HealBot_RecalcSpells();
HealBot_Action_Reset(); HealBot_Action_Reset();
+8
View File
@@ -52,6 +52,14 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\
**v1.7.1** **v1.7.1**
* **Bug Fix - Shapeshift Auto-Unshift** - Fixed an issue where the shapeshift spell queue would incorrectly pull druids out of Tree of Life form . * **Bug Fix - Shapeshift Auto-Unshift** - Fixed an issue where the shapeshift spell queue would incorrectly pull druids out of Tree of Life form .
* **Cleanup** - Removed deprecated `UPDATE_SHAPESHIFT_FORM` event listeners and `HealBot_UpdateShapeshiftForm` function to save CPU cycles. * **Cleanup** - Removed deprecated `UPDATE_SHAPESHIFT_FORM` event listeners and `HealBot_UpdateShapeshiftForm` function to save CPU cycles.
*
* **v1.7.2**
* **Performance Fix - Table Pooling** - Fixed massive Vanilla Lua 5.0 garbage collection memory leaks caused by unbounded table allocations in high-frequency update loops (e.g., `OnUpdate` and `PreserveStateByGUID`). Moved tables to file-local scope and implemented inline clearing.
* **Performance Fix - OOC Cleanup** - Added an out-of-combat garbage collection hook (`PLAYER_REGEN_ENABLED`) to purge disconnected senders from the `HealBot_IncomingHealers` global table, preventing memory bloat during prolonged play sessions.
* **Bug Fix - Incoming Heals Comms** - Fixed a regex string parsing bug that caused incoming heals from other HealBot instances to drop if a unit's name contained non-alphabetic characters (e.g. dashes or spaces in pet names).
* **Feature - Standard HealComm Sync** - Implemented lightweight parsing of the standard `HealComm` addon channel. HealBot now perfectly syncs incoming heals with modern raid frames like Luna, Grid, and pfUI, while retaining backwards compatibility with older versions of HealBot.
* **UI Update - Raid Marks** - Anchored raid target icons to top of unit frames instead of center.
* **Bug Fix** - Fixed an issue where the debuff warning sound and UI message would spam repeatedly on every aura update. The warning now only plays once per unique dispellable debuff type applied to a unit. Increased warning trigger range to 40 yards.
**v1.7.0** **v1.7.0**
* **Feature - Raid Marks** - Added tracking and display of raid marks on unit frames. * **Feature - Raid Marks** - Added tracking and display of raid marks on unit frames.