diff --git a/HealBot_Controller_Aura.lua b/Controller/HealBot_Controller_Aura.lua similarity index 100% rename from HealBot_Controller_Aura.lua rename to Controller/HealBot_Controller_Aura.lua diff --git a/Controller/HealBot_Controller_Casting.lua b/Controller/HealBot_Controller_Casting.lua new file mode 100644 index 0000000..b58f028 --- /dev/null +++ b/Controller/HealBot_Controller_Casting.lua @@ -0,0 +1,424 @@ +-- HealBot_Controller_Casting.lua +-- Combat casting engine, target-safe wrappers, casting state, and shapeshift handling + +HealBot_CastingSpell = nil; +HealBot_CastingTarget = nil; +HealBot_CastingGroupMembers = nil; +HealBot_TargetRestorePending = nil; +HealBot_TargetRestoreTimer = 0; + +local HealBot_Health60 = { + ["DRUID"] = 3500, + ["MAGE"] = 2500, + ["HUNTER"] = 3500, + ["PALADIN"] = 4000, + ["PRIEST"] = 2500, + ["ROGUE"] = 3500, + ["SHAMAN"] = 3800, + ["WARLOCK"] = 3500, + ["WARRIOR"] = 5000, +} + +-- HealBot_CastSpellByName: Internal utility: HealBot_CastSpellByName +function HealBot_CastSpellByName(spell) + if (HealBot_Spells[spell] and HealBot_Spells[spell].BagSlot) then + HealBot_UseItem(spell); + return; + end + local id; + if not HealBot_Spells[spell] then + id = HealBot_GetSpellId(spell); + elseif HealBot_Spells[spell].id then + id = HealBot_Spells[spell].id + else + id = HealBot_GetSpellId(spell); + end + if (not id) then + return; + end + CastSpell(id, BOOKTYPE_SPELL); +end + +-- HealBot_AnnounceCast: Internal utility: HealBot_AnnounceCast +function HealBot_AnnounceCast(spell, target) + if not spell or not target then return end + + if UnitIsDeadOrGhost(target) then + local spellLower = string.lower(spell) + if not (string.find(spellLower, "resurrection") or string.find(spellLower, "ancestral spirit") or string.find(spellLower, "redemption") or string.find(spellLower, "rebirth")) then + return + end + end + + if HealBot_Config.ChatMessages then + local tName = target + if target == "target" then tName = HealBot_TargetName() or "target" else tName = UnitName(target) end + if not tName then tName = target end + 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 + + -- Strip trailing spaces from base spell + baseSpell = string.gsub(baseSpell, "%s+$", "") + + for i = 1, 5 do + local msgConf = HealBot_Config.ChatMessages[i] + local configSpell = msgConf and msgConf.Spell or "" + -- Strip trailing spaces from config spell just in case + configSpell = string.gsub(configSpell, "%s+$", "") + + if msgConf and configSpell ~= "" and msgConf.Channel ~= "None" and (configSpell == spell or configSpell == baseSpell) then + local msg = msgConf.Message or "" + msg = string.gsub(msg, "#Spell#", spell) + msg = string.gsub(msg, "#Target#", tName) + local chan = msgConf.Channel + if chan == "Say" then + SendChatMessage(msg, "SAY") + elseif chan == "Party" and GetNumPartyMembers() > 0 then + SendChatMessage(msg, "PARTY") + elseif chan == "Raid" and GetNumRaidMembers() > 0 then + SendChatMessage(msg, "RAID") + elseif chan == "Whisper" and UnitIsPlayer(target) then + SendChatMessage(msg, "WHISPER", nil, tName) + end + end + end + end +end + +-- HealBot_StartCasting: Initiates spell cast and broadcasts incoming heal. +function HealBot_StartCasting(spell, target, ttype) + HealBot_CastFailed = false; + + HealBot_CastSpellByName(spell); + if ( SpellCanTargetUnit(target) ) then + SpellTargetUnit(target); + ttype = "fired"; + elseif SpellIsTargeting() then + SpellTargetUnit(target); + SpellStopTargeting() + elseif ttype == "direct" then + if ( UnitIsUnit(target, "player") or CheckInteractDistance(target, 4) ) then + ttype = "fired"; + end + end + + if ttype == "fired" and HealBot_Spells[spell] then + if not HealBot_CastFailed then + HealBot_CastingSpell = spell; + HealBot_CastingTarget = target; + HealBot_Process_HealValue(spell, target); + HealBot_AnnounceCast(spell, target); + end + end + + return ttype == "fired" +end + +-- HealBot_StopCasting: Internal utility: HealBot_StopCasting +function HealBot_StopCasting() + HealBot_IsCasting = false; + if HealBot_CastingTarget then + if HealBot_HealValue > 0 then + local uname = UnitName(HealBot_CastingTarget) + if uname then + if HealBot_CastingGroupMembers and table.getn(HealBot_CastingGroupMembers) > 1 then + HealBot_SendAddonMessage("HealComm", "GrpHealstop"); + for _, memberName in ipairs(HealBot_CastingGroupMembers) do + HealBot_SendAddonMessage(HEALBOT_ADDON_ID, ">> " .. memberName .. " <<=>> " .. 0 - HealBot_HealValue .. " << ") + if HealBot_HealsIn[memberName] then + HealBot_HealsIn[memberName] = HealBot_HealsIn[memberName] - HealBot_HealValue; + if HealBot_HealsIn[memberName] < 0 then HealBot_HealsIn[memberName] = 0; end + HealBot_RecalcHeals(HealBot_FindUnitID(memberName)); + end + end + else + HealBot_SendAddonMessage("HealComm", "Healstop"); + HealBot_SendAddonMessage(HEALBOT_ADDON_ID, ">> " .. uname .. " <<=>> " .. 0 - HealBot_HealValue .. " << "); + if HealBot_HealsIn[uname] then + HealBot_HealsIn[uname] = HealBot_HealsIn[uname] - HealBot_HealValue; + if HealBot_HealsIn[uname] < 0 then HealBot_HealsIn[uname] = 0; end + HealBot_RecalcHeals(HealBot_FindUnitID(uname)); + end + end + end + HealBot_HealValue = 0; + end + end + HealBot_CastingSpell = nil; + HealBot_CastingTarget = nil; + local bar = HealBot_Action_HealthBar(HealBot_Action_AbortButton); + local ar = HealBot_Config.babortcolr[HealBot_Config.Current_Skin] or 0.1; + local ag = HealBot_Config.babortcolg[HealBot_Config.Current_Skin] or 0.1; + local ab = HealBot_Config.babortcolb[HealBot_Config.Current_Skin] or 0.5; + local aa = HealBot_Config.babortcola[HealBot_Config.Current_Skin] or 1; + if not bar.txt then bar.txt = getglobal(bar:GetName() .. "_text") end + bar:SetStatusBarColor(ar, ag, ab, 0); + local sr = HealBot_Config.btextdisbledcolr[HealBot_Config.Current_Skin]; + local sg = HealBot_Config.btextdisbledcolg[HealBot_Config.Current_Skin]; + local sb = HealBot_Config.btextdisbledcolb[HealBot_Config.Current_Skin]; + local sa = HealBot_Config.btextdisbledcola[HealBot_Config.Current_Skin]; + bar.txt:SetTextColor(sr, sg, sb, sa); +end + +-- HealBot_UnitHealth: Internal utility: HealBot_UnitHealth +function HealBot_UnitHealth(unit) + local Current, Desired = UnitHealth(unit), UnitHealthMax(unit); + if unit == 'target' and Desired == 100 then + local class, level = HealBot_UnitClass(unit), UnitLevel(unit); + if HealBot_Health60[class] and level > 0 then + Desired = math.floor(HealBot_Health60[class] / 60 * level + 0.5) + else + Desired = UnitHealthMax('player'); + end + Current = Desired / 100 * Current; + end + return Current, Desired; +end + +-- HealBot_CheckCasting: Internal utility: HealBot_CheckCasting +function HealBot_CheckCasting(unit) + if not HealBot_CastingSpell or HealBot_AlwaysHeal() then return nil end + if not HealBot_Spells[HealBot_CastingSpell] then return nil end + if not unit then unit = HealBot_CastingTarget end + if unit ~= HealBot_CastingTarget then return nil end + + local bar = HealBot_Action_HealthBar(HealBot_Action_AbortButton); + local ar = HealBot_Config.babortcolr[HealBot_Config.Current_Skin] or 0.1; + local ag = HealBot_Config.babortcolg[HealBot_Config.Current_Skin] or 0.1; + local ab = HealBot_Config.babortcolb[HealBot_Config.Current_Skin] or 0.5; + local aa = HealBot_Config.babortcola[HealBot_Config.Current_Skin] or 1; + if not bar.txt then bar.txt = getglobal(bar:GetName() .. "_text") end + + if HealBot_IsCasting == false and HealBot_AbortButton == 0 then + bar:SetStatusBarColor(ar, ag, ab, 0); + local sr = HealBot_Config.btextdisbledcolr[HealBot_Config.Current_Skin]; + local sg = HealBot_Config.btextdisbledcolg[HealBot_Config.Current_Skin]; + local sb = HealBot_Config.btextdisbledcolb[HealBot_Config.Current_Skin]; + local sa = HealBot_Config.btextdisbledcola[HealBot_Config.Current_Skin]; + bar.txt:SetTextColor(sr, sg, sb, sa); + return nil + end + + local Current, Desired = HealBot_UnitHealth(unit) + local Needed = Desired - Current; + Needed = Needed * (1 + (HealBot_Config.OverHeal * 4)); + if Needed < 0 then Needed = 0 end + if (Needed > HealBot_Spells[HealBot_CastingSpell].HealsDur) then + local sr = HealBot_Config.btextdisbledcolr[HealBot_Config.Current_Skin]; + local sg = HealBot_Config.btextdisbledcolg[HealBot_Config.Current_Skin]; + local sb = HealBot_Config.btextdisbledcolb[HealBot_Config.Current_Skin]; + local sa = HealBot_Config.btextdisbledcola[HealBot_Config.Current_Skin]; + bar.txt:SetTextColor(sr, sg, sb, sa); + bar:SetStatusBarColor(ar, ag, ab, 0); + return nil + elseif HealBot_AbortButton == 1 and HealBot_IsCasting == true then + bar:SetStatusBarColor(ar, ag, ab, aa); + local sr = HealBot_Config.btextenabledcolr[HealBot_Config.Current_Skin]; + local sg = HealBot_Config.btextenabledcolg[HealBot_Config.Current_Skin]; + local sb = HealBot_Config.btextenabledcolb[HealBot_Config.Current_Skin]; + local sa = HealBot_Config.btextenabledcola[HealBot_Config.Current_Skin]; + if not bar.txt then bar.txt = getglobal(bar:GetName() .. "_text") end + bar.txt:SetTextColor(sr, sg, sb, sa); + end +end + +-- HealBot_CastSpellOnFriend: Target-safe wrapper for casting spells on allies. +function HealBot_CastSpellOnFriend(spell, target) + if (not spell or not target or not UnitName(target)) then + return; + end + local targetEnemy = UnitCanAttack("player", "target"); + local oldTarget = UnitName("target"); + + local formCancelled = false + if HealBot_UnitClass("player") == "DRUID" and HealBot_Config.AutoUnshift == 1 then + local currentForm = HealBot_GetShapeshiftForm() + if currentForm then + CastShapeshiftForm(currentForm); + formCancelled = true + end + end + + if formCancelled then + -- ALWAYS put the cast into the Pending queue so the OnUpdate loop can wait for the server + -- to process the unshift before attempting the cast, otherwise we get "You are in shapeshift form". + if not HealBot_PendingShapeshiftCast then HealBot_PendingShapeshiftCast = {} end + HealBot_PendingShapeshiftCast.spell = spell + HealBot_PendingShapeshiftCast.target = target + HealBot_PendingShapeshiftCast.targetEnemy = targetEnemy + HealBot_PendingShapeshiftCast.oldTarget = oldTarget + HealBot_PendingShapeshiftCast.fireTime = GetTime() + 0.05 + HealBot_PendingShapeshiftCast.expires = GetTime() + 2.0 + HealBot_PendingShapeshiftCast.started = nil + HealBot_PendingShapeshiftCast.nextSpam = nil + return; + end + + if oldTarget ~= UnitName(target) then + TargetUnit(target); + end + + HealBot_StartCasting(spell, target, "direct"); + + if targetEnemy then + HealBot_TargetRestorePending = "enemy"; + elseif oldTarget and oldTarget ~= UnitName(target) then + HealBot_TargetRestorePending = "friend"; + elseif not oldTarget then + HealBot_TargetRestorePending = "clear"; + end + HealBot_TargetRestoreTimer = 0; +end + +-- HealBot_CanCastSpell: Internal utility: HealBot_CanCastSpell +function HealBot_CanCastSpell(spell, unit) + local this = HealBot_Spells[spell]; + -- Removed manual mana check so WoW can properly handle 0-cost buffs (Clearcasting/Inner Focus) + -- and so tooltips can render in red instead of disappearing. + if this.BagSlot then + local bag, slot = HealBot_UnpackBagSlot(this.BagSlot); + local start, duration, enable = GetContainerItemCooldown(bag, slot); + if (start > 0 and duration > 0 and enable > 0) then + return false; + end + end + return true; +end + +-- HealBot_GetHealSpell: Resolves spell rank based on health deficit. +function HealBot_GetHealSpell(unit, pattern) + if not string.find(unit, "^Test") then + if (not UnitName(unit)) then return nil end; + end + if not pattern then return nil end; + if UnitOnTaxi("player") then return nil end; + if not string.find(unit, "^Test") then + if HealBot_Config.ProtectPvP == 1 and UnitIsPVP(unit) and not UnitIsPVP("player") then return nil end + end + if HealBot_UnitClass("player") == "DRUID" then + if HealBot_GetShapeshiftForm() and HealBot_Config.AutoUnshift ~= 1 then return nil end; + end + + -- Handle Inline Scripts + if string.sub(pattern, 1, 1) == "/" then + if HealBot_Range_Check(unit, 40) == 0 then return nil end; + return pattern; + end + + -- Handle Special Targeting Pattern + if string.lower(pattern) == "target" then + if HealBot_Range_Check(unit, 40) == 0 then return nil end; + return pattern; + end + + -- 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 HealBot_Spells[spell].range or 40; + else + local parenIdx = string.find(spell, "%s*%(") + if parenIdx then + local baseSpell = string.sub(spell, 1, parenIdx - 1) + baseSpell = string.gsub(baseSpell, "%s+$", "") + if HealBot_Spells[baseSpell] then + if not HealBot_CanCastSpell(baseSpell, unit) then return nil end; + range = HealBot_Spells[baseSpell].Range or HealBot_Spells[baseSpell].range or 40; + end + end + end + if HealBot_Range_Check(unit, range) == 0 then return nil end; + return spell; + end + + -- Handle Named Macros + if GetMacroIndexByName(pattern) ~= 0 then + if HealBot_Range_Check(unit, 40) == 0 then return nil end; + return pattern; + end + + -- Handle Items + local bag, slot = HealBot_FindBagSlot(pattern) + if bag then + local range = 40; + if HealBot_Spells[pattern] then + range = HealBot_Spells[pattern].Range or HealBot_Spells[pattern].range or 40; + end + if HealBot_Range_Check(unit, range) == 0 then return nil end; + return pattern; + end + + return nil; +end + +-- HealBot_HealUnit: Main entry point for healing a unit. +function HealBot_HealUnit(unit, pattern) + HealBot_CastSpellOnFriend(HealBot_GetHealSpell(unit, pattern), unit); +end + +-- HealBot_RecalcHeals: Flags unit for visual refresh. +function HealBot_RecalcHeals(unit) + if unit then + HealBot_View_DirtyUnits[unit] = true; + else + if HealBot_Action_UnitButtons then + for u, _ in pairs(HealBot_Action_UnitButtons) do + HealBot_View_DirtyUnits[u] = true; + end + end + end +end + +-- HealBot_RecalcParty: Triggers group layout rebuild. +function HealBot_RecalcParty() + HealBot_Action_PartyChanged(); + if HealBot_Action_HealButtons then + for _, button in ipairs(HealBot_Action_HealButtons) do + if button and button.unit then + HealBot_OnEvent_UnitAura(nil, button.unit); + end + end + end +end + +-- HealBot_RecalcSpells: Recalculates spells and updates layout. +function HealBot_RecalcSpells() + HealBot_FindHealSpells(); + HealBot_RecalcParty(); +end + +HealBot_TalentRanks = {} + +-- HealBot_GetShapeshiftForm: Detects active druid form to prevent invalid casts. +function HealBot_GetShapeshiftForm() + local forms = GetNumShapeshiftForms(); + if forms then + local i; + for i=1,forms do + local icon,name,active = GetShapeshiftFormInfo(i); + if active then + if icon then + local icon_lower = string.lower(icon) + -- Exclude non-standard/custom forms like Tree of Life from unshifting + if not string.find(icon_lower, "humanoidform") and not string.find(icon_lower, "treeoflife") and not string.find(icon_lower, "healingway") and not string.find(icon_lower, "stoneclawtotem") then + return nil; + end + else + return i; + end + end + end + end + return nil; +end + diff --git a/HealBot_Controller_Comms.lua b/Controller/HealBot_Controller_Comms.lua similarity index 100% rename from HealBot_Controller_Comms.lua rename to Controller/HealBot_Controller_Comms.lua diff --git a/HealBot_Controller_Events.lua b/Controller/HealBot_Controller_Events.lua similarity index 99% rename from HealBot_Controller_Events.lua rename to Controller/HealBot_Controller_Events.lua index 5276c04..bfa9cb3 100644 --- a/HealBot_Controller_Events.lua +++ b/Controller/HealBot_Controller_Events.lua @@ -7,6 +7,9 @@ local unitsToRefresh = {} local powerToRefresh = {} local HealBot_Timer1, HealsIn_Timer = 0, 0; HealBot_LastModState = "" +HealBot_EquipChangeTimer = 0 +HealBot_SpellsInitFlag = 1 +HealBot_Delay_RecalcParty = 0 -- HealBot_OnLoad: Registers core events and observers on addon load. function HealBot_OnLoad(this) @@ -189,7 +192,7 @@ function HealBot_OnUpdate(this, arg1) end end - if HealBot_EquipChangeTimer > 0 then + if (HealBot_EquipChangeTimer or 0) > 0 then HealBot_EquipChangeTimer = HealBot_EquipChangeTimer - arg1 if HealBot_EquipChangeTimer <= 0 then HealBot_EquipChangeTimer = 0 diff --git a/HealBot_Controller_Range.lua b/Controller/HealBot_Controller_Range.lua similarity index 100% rename from HealBot_Controller_Range.lua rename to Controller/HealBot_Controller_Range.lua diff --git a/HealBot_Controller_Spells.lua b/Controller/HealBot_Controller_SpellBook.lua similarity index 58% rename from HealBot_Controller_Spells.lua rename to Controller/HealBot_Controller_SpellBook.lua index 6c1de72..2591857 100644 --- a/HealBot_Controller_Spells.lua +++ b/Controller/HealBot_Controller_SpellBook.lua @@ -1,23 +1,5 @@ --- HealBot_Controller_Spells.lua --- Centralized Spell management, calculation, and casting logic - -HealBot_CastingSpell = nil; -HealBot_CastingTarget = nil; -HealBot_CastingGroupMembers = nil; -HealBot_TargetRestorePending = nil; -HealBot_TargetRestoreTimer = 0; - -local HealBot_Health60 = { - ["DRUID"] = 3500, - ["MAGE"] = 2500, - ["HUNTER"] = 3500, - ["PALADIN"] = 4000, - ["PRIEST"] = 2500, - ["ROGUE"] = 3500, - ["SHAMAN"] = 3800, - ["WARLOCK"] = 3500, - ["WARRIOR"] = 5000, -} +-- HealBot_Controller_SpellBook.lua +-- Spellbook parsing, tooltip data extraction, rank math, and equipment/talent bonuses -- HealBot_GetSpellName: Internal utility: HealBot_GetSpellName function HealBot_GetSpellName(id) @@ -57,80 +39,6 @@ function HealBot_GetSpellId(spell) return HealBot_SpellNameToId[spell] end --- HealBot_CastSpellByName: Internal utility: HealBot_CastSpellByName -function HealBot_CastSpellByName(spell) - if (HealBot_Spells[spell] and HealBot_Spells[spell].BagSlot) then - HealBot_UseItem(spell); - return; - end - local id; - if not HealBot_Spells[spell] then - id = HealBot_GetSpellId(spell); - elseif HealBot_Spells[spell].id then - id = HealBot_Spells[spell].id - else - id = HealBot_GetSpellId(spell); - end - if (not id) then - return; - end - CastSpell(id, BOOKTYPE_SPELL); -end - --- HealBot_AnnounceCast: Internal utility: HealBot_AnnounceCast -function HealBot_AnnounceCast(spell, target) - if not spell or not target then return end - - if UnitIsDeadOrGhost(target) then - local spellLower = string.lower(spell) - if not (string.find(spellLower, "resurrection") or string.find(spellLower, "ancestral spirit") or string.find(spellLower, "redemption") or string.find(spellLower, "rebirth")) then - return - end - end - - if HealBot_Config.ChatMessages then - local tName = target - if target == "target" then tName = HealBot_TargetName() or "target" else tName = UnitName(target) end - if not tName then tName = target end - 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 - - -- Strip trailing spaces from base spell - baseSpell = string.gsub(baseSpell, "%s+$", "") - - for i = 1, 5 do - local msgConf = HealBot_Config.ChatMessages[i] - local configSpell = msgConf and msgConf.Spell or "" - -- Strip trailing spaces from config spell just in case - configSpell = string.gsub(configSpell, "%s+$", "") - - if msgConf and configSpell ~= "" and msgConf.Channel ~= "None" and (configSpell == spell or configSpell == baseSpell) then - local msg = msgConf.Message or "" - msg = string.gsub(msg, "#Spell#", spell) - msg = string.gsub(msg, "#Target#", tName) - local chan = msgConf.Channel - if chan == "Say" then - SendChatMessage(msg, "SAY") - elseif chan == "Party" and GetNumPartyMembers() > 0 then - SendChatMessage(msg, "PARTY") - elseif chan == "Raid" and GetNumRaidMembers() > 0 then - SendChatMessage(msg, "RAID") - elseif chan == "Whisper" and UnitIsPlayer(target) then - SendChatMessage(msg, "WHISPER", nil, tName) - end - end - end - end -end - -- HealBot_Process_HealValue: Internal utility: HealBot_Process_HealValue function HealBot_Process_HealValue(spell, target) if HealBot_Spells[spell] and (HealBot_Spells[spell].CastTime or 0) > 1 then @@ -208,191 +116,6 @@ function HealBot_Process_HealValue(spell, target) end end --- HealBot_StartCasting: Initiates spell cast and broadcasts incoming heal. -function HealBot_StartCasting(spell, target, ttype) - HealBot_CastFailed = false; - - HealBot_CastSpellByName(spell); - if ( SpellCanTargetUnit(target) ) then - SpellTargetUnit(target); - ttype = "fired"; - elseif SpellIsTargeting() then - SpellTargetUnit(target); - SpellStopTargeting() - elseif ttype == "direct" then - if ( UnitIsUnit(target, "player") or CheckInteractDistance(target, 4) ) then - ttype = "fired"; - end - end - - if ttype == "fired" and HealBot_Spells[spell] then - if not HealBot_CastFailed then - HealBot_CastingSpell = spell; - HealBot_CastingTarget = target; - HealBot_Process_HealValue(spell, target); - HealBot_AnnounceCast(spell, target); - end - end - - return ttype == "fired" -end - --- HealBot_StopCasting: Internal utility: HealBot_StopCasting -function HealBot_StopCasting() - HealBot_IsCasting = false; - if HealBot_CastingTarget then - if HealBot_HealValue > 0 then - local uname = UnitName(HealBot_CastingTarget) - if uname then - if HealBot_CastingGroupMembers and table.getn(HealBot_CastingGroupMembers) > 1 then - HealBot_SendAddonMessage("HealComm", "GrpHealstop"); - for _, memberName in ipairs(HealBot_CastingGroupMembers) do - HealBot_SendAddonMessage(HEALBOT_ADDON_ID, ">> " .. memberName .. " <<=>> " .. 0 - HealBot_HealValue .. " << ") - if HealBot_HealsIn[memberName] then - HealBot_HealsIn[memberName] = HealBot_HealsIn[memberName] - HealBot_HealValue; - if HealBot_HealsIn[memberName] < 0 then HealBot_HealsIn[memberName] = 0; end - HealBot_RecalcHeals(HealBot_FindUnitID(memberName)); - end - end - else - HealBot_SendAddonMessage("HealComm", "Healstop"); - HealBot_SendAddonMessage(HEALBOT_ADDON_ID, ">> " .. uname .. " <<=>> " .. 0 - HealBot_HealValue .. " << "); - if HealBot_HealsIn[uname] then - HealBot_HealsIn[uname] = HealBot_HealsIn[uname] - HealBot_HealValue; - if HealBot_HealsIn[uname] < 0 then HealBot_HealsIn[uname] = 0; end - HealBot_RecalcHeals(HealBot_FindUnitID(uname)); - end - end - end - HealBot_HealValue = 0; - end - end - HealBot_CastingSpell = nil; - HealBot_CastingTarget = nil; - local bar = HealBot_Action_HealthBar(HealBot_Action_AbortButton); - local ar = HealBot_Config.babortcolr[HealBot_Config.Current_Skin] or 0.1; - local ag = HealBot_Config.babortcolg[HealBot_Config.Current_Skin] or 0.1; - local ab = HealBot_Config.babortcolb[HealBot_Config.Current_Skin] or 0.5; - local aa = HealBot_Config.babortcola[HealBot_Config.Current_Skin] or 1; - if not bar.txt then bar.txt = getglobal(bar:GetName() .. "_text") end - bar:SetStatusBarColor(ar, ag, ab, 0); - local sr = HealBot_Config.btextdisbledcolr[HealBot_Config.Current_Skin]; - local sg = HealBot_Config.btextdisbledcolg[HealBot_Config.Current_Skin]; - local sb = HealBot_Config.btextdisbledcolb[HealBot_Config.Current_Skin]; - local sa = HealBot_Config.btextdisbledcola[HealBot_Config.Current_Skin]; - bar.txt:SetTextColor(sr, sg, sb, sa); -end - --- HealBot_UnitHealth: Internal utility: HealBot_UnitHealth -function HealBot_UnitHealth(unit) - local Current, Desired = UnitHealth(unit), UnitHealthMax(unit); - if unit == 'target' and Desired == 100 then - local class, level = HealBot_UnitClass(unit), UnitLevel(unit); - if HealBot_Health60[class] and level > 0 then - Desired = math.floor(HealBot_Health60[class] / 60 * level + 0.5) - else - Desired = UnitHealthMax('player'); - end - Current = Desired / 100 * Current; - end - return Current, Desired; -end - --- HealBot_CheckCasting: Internal utility: HealBot_CheckCasting -function HealBot_CheckCasting(unit) - if not HealBot_CastingSpell or HealBot_AlwaysHeal() then return nil end - if not HealBot_Spells[HealBot_CastingSpell] then return nil end - if not unit then unit = HealBot_CastingTarget end - if unit ~= HealBot_CastingTarget then return nil end - - local bar = HealBot_Action_HealthBar(HealBot_Action_AbortButton); - local ar = HealBot_Config.babortcolr[HealBot_Config.Current_Skin] or 0.1; - local ag = HealBot_Config.babortcolg[HealBot_Config.Current_Skin] or 0.1; - local ab = HealBot_Config.babortcolb[HealBot_Config.Current_Skin] or 0.5; - local aa = HealBot_Config.babortcola[HealBot_Config.Current_Skin] or 1; - if not bar.txt then bar.txt = getglobal(bar:GetName() .. "_text") end - - if HealBot_IsCasting == false and HealBot_AbortButton == 0 then - bar:SetStatusBarColor(ar, ag, ab, 0); - local sr = HealBot_Config.btextdisbledcolr[HealBot_Config.Current_Skin]; - local sg = HealBot_Config.btextdisbledcolg[HealBot_Config.Current_Skin]; - local sb = HealBot_Config.btextdisbledcolb[HealBot_Config.Current_Skin]; - local sa = HealBot_Config.btextdisbledcola[HealBot_Config.Current_Skin]; - bar.txt:SetTextColor(sr, sg, sb, sa); - return nil - end - - local Current, Desired = HealBot_UnitHealth(unit) - local Needed = Desired - Current; - Needed = Needed * (1 + (HealBot_Config.OverHeal * 4)); - if Needed < 0 then Needed = 0 end - if (Needed > HealBot_Spells[HealBot_CastingSpell].HealsDur) then - local sr = HealBot_Config.btextdisbledcolr[HealBot_Config.Current_Skin]; - local sg = HealBot_Config.btextdisbledcolg[HealBot_Config.Current_Skin]; - local sb = HealBot_Config.btextdisbledcolb[HealBot_Config.Current_Skin]; - local sa = HealBot_Config.btextdisbledcola[HealBot_Config.Current_Skin]; - bar.txt:SetTextColor(sr, sg, sb, sa); - bar:SetStatusBarColor(ar, ag, ab, 0); - return nil - elseif HealBot_AbortButton == 1 and HealBot_IsCasting == true then - bar:SetStatusBarColor(ar, ag, ab, aa); - local sr = HealBot_Config.btextenabledcolr[HealBot_Config.Current_Skin]; - local sg = HealBot_Config.btextenabledcolg[HealBot_Config.Current_Skin]; - local sb = HealBot_Config.btextenabledcolb[HealBot_Config.Current_Skin]; - local sa = HealBot_Config.btextenabledcola[HealBot_Config.Current_Skin]; - if not bar.txt then bar.txt = getglobal(bar:GetName() .. "_text") end - bar.txt:SetTextColor(sr, sg, sb, sa); - end -end - --- HealBot_CastSpellOnFriend: Target-safe wrapper for casting spells on allies. -function HealBot_CastSpellOnFriend(spell, target) - if (not spell or not target or not UnitName(target)) then - return; - end - local targetEnemy = UnitCanAttack("player", "target"); - local oldTarget = UnitName("target"); - - local formCancelled = false - if HealBot_UnitClass("player") == "DRUID" and HealBot_Config.AutoUnshift == 1 then - local currentForm = HealBot_GetShapeshiftForm() - if currentForm then - CastShapeshiftForm(currentForm); - formCancelled = true - end - end - - if formCancelled then - -- ALWAYS put the cast into the Pending queue so the OnUpdate loop can wait for the server - -- to process the unshift before attempting the cast, otherwise we get "You are in shapeshift form". - if not HealBot_PendingShapeshiftCast then HealBot_PendingShapeshiftCast = {} end - HealBot_PendingShapeshiftCast.spell = spell - HealBot_PendingShapeshiftCast.target = target - HealBot_PendingShapeshiftCast.targetEnemy = targetEnemy - HealBot_PendingShapeshiftCast.oldTarget = oldTarget - HealBot_PendingShapeshiftCast.fireTime = GetTime() + 0.05 - HealBot_PendingShapeshiftCast.expires = GetTime() + 2.0 - HealBot_PendingShapeshiftCast.started = nil - HealBot_PendingShapeshiftCast.nextSpam = nil - return; - end - - if oldTarget ~= UnitName(target) then - TargetUnit(target); - end - - HealBot_StartCasting(spell, target, "direct"); - - if targetEnemy then - HealBot_TargetRestorePending = "enemy"; - elseif oldTarget and oldTarget ~= UnitName(target) then - HealBot_TargetRestorePending = "friend"; - elseif not oldTarget then - HealBot_TargetRestorePending = "clear"; - end - HealBot_TargetRestoreTimer = 0; -end - -- HealBot_SetItemDefaults: Internal utility: HealBot_SetItemDefaults function HealBot_SetItemDefaults(spell) if not HealBot_Spells[spell].Target then @@ -553,127 +276,6 @@ function HealBot_FindHealSpells() HealBot_CalcEquipBonus = false; end --- HealBot_CanCastSpell: Internal utility: HealBot_CanCastSpell -function HealBot_CanCastSpell(spell, unit) - local this = HealBot_Spells[spell]; - -- Removed manual mana check so WoW can properly handle 0-cost buffs (Clearcasting/Inner Focus) - -- and so tooltips can render in red instead of disappearing. - if this.BagSlot then - local bag, slot = HealBot_UnpackBagSlot(this.BagSlot); - local start, duration, enable = GetContainerItemCooldown(bag, slot); - if (start > 0 and duration > 0 and enable > 0) then - return false; - end - end - return true; -end - --- HealBot_GetHealSpell: Resolves spell rank based on health deficit. -function HealBot_GetHealSpell(unit, pattern) - if not string.find(unit, "^Test") then - if (not UnitName(unit)) then return nil end; - end - if not pattern then return nil end; - if UnitOnTaxi("player") then return nil end; - if not string.find(unit, "^Test") then - if HealBot_Config.ProtectPvP == 1 and UnitIsPVP(unit) and not UnitIsPVP("player") then return nil end - end - if HealBot_UnitClass("player") == "DRUID" then - if HealBot_GetShapeshiftForm() and HealBot_Config.AutoUnshift ~= 1 then return nil end; - end - - -- Handle Inline Scripts - if string.sub(pattern, 1, 1) == "/" then - if HealBot_Range_Check(unit, 40) == 0 then return nil end; - return pattern; - end - - -- Handle Special Targeting Pattern - if string.lower(pattern) == "target" then - if HealBot_Range_Check(unit, 40) == 0 then return nil end; - return pattern; - end - - -- 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 HealBot_Spells[spell].range or 40; - else - local parenIdx = string.find(spell, "%s*%(") - if parenIdx then - local baseSpell = string.sub(spell, 1, parenIdx - 1) - baseSpell = string.gsub(baseSpell, "%s+$", "") - if HealBot_Spells[baseSpell] then - if not HealBot_CanCastSpell(baseSpell, unit) then return nil end; - range = HealBot_Spells[baseSpell].Range or HealBot_Spells[baseSpell].range or 40; - end - end - end - if HealBot_Range_Check(unit, range) == 0 then return nil end; - return spell; - end - - -- Handle Named Macros - if GetMacroIndexByName(pattern) ~= 0 then - if HealBot_Range_Check(unit, 40) == 0 then return nil end; - return pattern; - end - - -- Handle Items - local bag, slot = HealBot_FindBagSlot(pattern) - if bag then - local range = 40; - if HealBot_Spells[pattern] then - range = HealBot_Spells[pattern].Range or HealBot_Spells[pattern].range or 40; - end - if HealBot_Range_Check(unit, range) == 0 then return nil end; - return pattern; - end - - return nil; -end - --- HealBot_HealUnit: Main entry point for healing a unit. -function HealBot_HealUnit(unit, pattern) - HealBot_CastSpellOnFriend(HealBot_GetHealSpell(unit, pattern), unit); -end - --- HealBot_RecalcHeals: Flags unit for visual refresh. -function HealBot_RecalcHeals(unit) - if unit then - HealBot_View_DirtyUnits[unit] = true; - else - if HealBot_Action_UnitButtons then - for u, _ in pairs(HealBot_Action_UnitButtons) do - HealBot_View_DirtyUnits[u] = true; - end - end - end -end - --- HealBot_RecalcParty: Triggers group layout rebuild. -function HealBot_RecalcParty() - HealBot_Action_PartyChanged(); - if HealBot_Action_HealButtons then - for _, button in ipairs(HealBot_Action_HealButtons) do - if button and button.unit then - HealBot_OnEvent_UnitAura(nil, button.unit); - end - end - end -end - --- HealBot_RecalcSpells: Recalculates spells and updates layout. -function HealBot_RecalcSpells() - HealBot_FindHealSpells(); - HealBot_RecalcParty(); -end - -HealBot_TalentRanks = {} - function HealBot_UpdateTalentRanks() HealBot_TalentRanks = {} for t = 1, GetNumTalentTabs() do @@ -962,26 +564,3 @@ end --- HealBot_GetShapeshiftForm: Detects active druid form to prevent invalid casts. -function HealBot_GetShapeshiftForm() - local forms = GetNumShapeshiftForms(); - if forms then - local i; - for i=1,forms do - local icon,name,active = GetShapeshiftFormInfo(i); - if active then - if icon then - local icon_lower = string.lower(icon) - -- Exclude non-standard/custom forms like Tree of Life from unshifting - if not string.find(icon_lower, "humanoidform") and not string.find(icon_lower, "treeoflife") and not string.find(icon_lower, "healingway") and not string.find(icon_lower, "stoneclawtotem") then - return nil; - end - else - return i; - end - end - end - end - return nil; -end - diff --git a/HealBot.xml b/HealBot.xml index 618ba49..a1241af 100644 --- a/HealBot.xml +++ b/HealBot.xml @@ -1,13 +1,6 @@ -