refactor: implement reliable shapeshift unshifting with a queued cast system and Nampower integration settings

This commit is contained in:
Bluewhale1337
2026-09-01 18:55:47 +02:00
parent 59ceb3b465
commit 16bc4d95ee
5 changed files with 109 additions and 33 deletions
+63 -19
View File
@@ -78,33 +78,75 @@ function HealBot_OnUpdate(this, arg1)
end
if HealBot_PendingShapeshiftCast then
if GetTime() > HealBot_PendingShapeshiftCast.expires then
HealBot_PendingShapeshiftCast = nil
else
-- Only cast once the form has successfully been removed
local currentForm = HealBot_GetShapeshiftForm()
if not currentForm then
local pendingCast = HealBot_PendingShapeshiftCast
HealBot_PendingShapeshiftCast = nil
local pendingCast = HealBot_PendingShapeshiftCast
-- Wait for server to process the unshift before attempting the cast
if GetTime() >= pendingCast.fireTime then
if not HealBot_GetShapeshiftForm() then
-- Target the unit if necessary before casting
-- Target the unit if necessary before casting
if pendingCast.oldTarget ~= UnitName(pendingCast.target) then
TargetUnit(pendingCast.target)
end
-- Attempt the cast now that form is cleared
HealBot_StartCasting(pendingCast.spell, pendingCast.target, "direct")
if pendingCast.targetEnemy then
HealBot_TargetRestorePending = { type = "enemy" }
elseif pendingCast.oldTarget and pendingCast.oldTarget ~= UnitName(pendingCast.target) then
HealBot_TargetRestorePending = { type = "friend" }
elseif not pendingCast.oldTarget then
HealBot_TargetRestorePending = { type = "clear" }
-- 1. Initialize the MVC side effects (AnnounceCast, Incoming Heals) only ONCE
if not pendingCast.started then
pendingCast.started = true
-- Extract base spell
local baseSpell = pendingCast.spell
local parenIndex = string.find(pendingCast.spell, "%(")
if parenIndex then
baseSpell = string.sub(pendingCast.spell, 1, parenIndex - 1)
end
baseSpell = string.gsub(baseSpell, "%s+$", "")
-- Force MVC updates since we guarantee the cast will eventually pierce the server
HealBot_CastFailed = false
HealBot_CastingSpell = baseSpell
HealBot_CastingTarget = pendingCast.target
HealBot_Process_HealValue(baseSpell, pendingCast.target)
HealBot_AnnounceCast(pendingCast.spell, pendingCast.target)
-- Restore target logic
if pendingCast.targetEnemy then
HealBot_TargetRestorePending = { type = "enemy" }
elseif pendingCast.oldTarget and pendingCast.oldTarget ~= UnitName(pendingCast.target) then
HealBot_TargetRestorePending = { type = "friend" }
elseif not pendingCast.oldTarget then
HealBot_TargetRestorePending = { type = "clear" }
end
HealBot_TargetRestoreTimer = 0
end
-- 2. Spam the raw cast silently to guarantee it pierces the server delay
if pendingCast.started then
if not pendingCast.nextSpam or GetTime() >= pendingCast.nextSpam then
pendingCast.nextSpam = GetTime() + 0.15
-- Safely suppress UI errors
UIErrorsFrame:UnregisterEvent("UI_ERROR_MESSAGE")
-- Only use HealBot's native cast wrapper
HealBot_CastSpellByName(pendingCast.spell)
if SpellCanTargetUnit(pendingCast.target) then
SpellTargetUnit(pendingCast.target)
elseif SpellIsTargeting() then
SpellTargetUnit(pendingCast.target)
SpellStopTargeting()
end
UIErrorsFrame:RegisterEvent("UI_ERROR_MESSAGE")
end
end
HealBot_TargetRestoreTimer = 0
end
end
-- Failsafe timeout
if HealBot_PendingShapeshiftCast and GetTime() > pendingCast.expires then
HealBot_PendingShapeshiftCast = nil
end
end
-- Process Dirty Queue for MVC View
@@ -524,6 +566,7 @@ end
-- HealBot_OnEvent_SpellcastStart: Internal utility: HealBot_OnEvent_SpellcastStart
function HealBot_OnEvent_SpellcastStart(this, spell, duration)
HealBot_IsCasting = true;
HealBot_PendingShapeshiftCast = nil;
HealBot_RecalcHeals();
HealBot_CheckCasting();
if spell == HEALBOT_RESURRECTION or spell == HEALBOT_ANCESTRALSPIRIT or spell == HEALBOT_REBIRTH or spell == HEALBOT_REDEMPTION then
@@ -537,6 +580,7 @@ end
-- HealBot_OnEvent_SpellcastStop: Internal utility: HealBot_OnEvent_SpellcastStop
function HealBot_OnEvent_SpellcastStop(this, eventName)
HealBot_IsCasting = false;
HealBot_PendingShapeshiftCast = nil;
if eventName == "SPELLCAST_FAILED" then
HealBot_CastFailed = true;
end
+24 -10
View File
@@ -171,6 +171,19 @@ 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
SpellTargetUnit(target);
@@ -184,14 +197,16 @@ function HealBot_StartCasting(spell, target, ttype)
end
end
if ttype == "fired" and HealBot_Spells[spell] then
if ttype == "fired" and HealBot_Spells[baseSpell] then
if not HealBot_CastFailed then
HealBot_CastingSpell = spell;
HealBot_CastingSpell = baseSpell;
HealBot_CastingTarget = target;
HealBot_Process_HealValue(spell, target);
HealBot_Process_HealValue(baseSpell, target);
HealBot_AnnounceCast(spell, target);
end
end
return ttype == "fired"
end
-- HealBot_StopCasting: Internal utility: HealBot_StopCasting
@@ -308,7 +323,9 @@ function HealBot_CastSpellOnFriend(spell, target)
end
if formCancelled then
HealBot_PendingShapeshiftCast = { spell = spell, target = target, targetEnemy = targetEnemy, oldTarget = oldTarget, expires = GetTime() + 1.0 }
-- 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".
HealBot_PendingShapeshiftCast = { spell = spell, target = target, targetEnemy = targetEnemy, oldTarget = oldTarget, fireTime = GetTime() + 0.05, expires = GetTime() + 1.0 }
return;
end
@@ -839,9 +856,9 @@ function HealBot_Generic_Patten(matchStr, matchPattern)
return tmpTest, _HealsMin, _HealsMax;
end
-- HealBot_UpdateShapeshiftForm: Internal utility: HealBot_UpdateShapeshiftForm
-- HealBot_UpdateShapeshiftForm: Called on UPDATE_SHAPESHIFT_FORM
function HealBot_UpdateShapeshiftForm()
-- Deprecated, state is pulled in real-time on cast
-- Deprecated
end
-- HealBot_GetShapeshiftForm: Detects active druid form to prevent invalid casts.
@@ -852,10 +869,7 @@ function HealBot_GetShapeshiftForm()
for i=1,forms do
local icon,name,active = GetShapeshiftFormInfo(i);
if active then
local icon_lower = string.lower(icon);
if not string.find(icon_lower, "humanoidform") and not string.find(icon_lower, "treeoflife") and not string.find(icon_lower, "stoneclawtotem") then
return i;
end
return i;
end
end
end
+2 -1
View File
@@ -27,8 +27,9 @@ HealBot_ConfigDefaults = {
ActionVisible = 1,
HideSolo = 0,
OverHeal = 0.25,
CastNotify = 1,
UpdateFreq = 0.5,
AutoUnshift = 1,
Integrations_Nampower_Active = true,
ChatMessages = {
[1] = { Spell = "None", Message = "Casting #Spell# on #Target#", Channel = "None" },
[2] = { Spell = "None", Message = "Casting #Spell# on #Target#", Channel = "None" },
+18 -3
View File
@@ -33,7 +33,7 @@
</Anchors>
<Scripts>
<OnLoad>
getglobal(this:GetName().."Text"):SetText("Enable nampower Integration (Buffs/Heals)");
getglobal(this:GetName().."Text"):SetText("Enable nampower Integration (Buffs/Heals/Queueing)");
</OnLoad>
<OnClick>
HealBot_Config.HealBot_Integrations_Nampower = this:GetChecked() and 1 or 0;
@@ -44,9 +44,9 @@
<CheckButton name="HealBot_Options_Integrations_SuperWoW" inherits="OptionsCheckButtonTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="HealBot_Options_Integrations_Nampower" relativePoint="BOTTOMLEFT">
<Anchor point="TOPLEFT">
<Offset>
<AbsDimension x="0" y="5"/>
<AbsDimension x="31" y="-145"/>
</Offset>
</Anchor>
</Anchors>
@@ -81,6 +81,21 @@
</CheckButton>
</Frames>
<Layers>
<Layer level="ARTWORK">
<FontString name="HealBot_Options_Integrations_NampowerInfo" inherits="GameFontHighlightSmall" justifyH="LEFT" justifyV="TOP" text="Note: For the 1-click Shapeshift auto-unshift to work smoothly, you must DISABLE 'Retry server rejected spells' in Nampower ENABLE Queue Instant Cast Spells and Queue Cast Time Spells.">
<Size x="330" y="30"/>
<Anchors>
<Anchor point="TOPLEFT">
<Offset>
<AbsDimension x="65" y="-95"/>
</Offset>
</Anchor>
</Anchors>
<Color r="0.7" g="0.7" b="0.7"/>
</FontString>
</Layer>
</Layers>
<Scripts>
<OnShow>
HealBot_Options_Integrations_OnShow(this);
+2
View File
@@ -59,6 +59,8 @@ Default installation path: `C:\Program Files\World of Warcraft\Interface\AddOns\
* **Bug Fix - Slow Client Init** - Fixed missing class colors after `/reload` by adding lazy-load identity fetches directly into the render pipeline for when `UnitClass` data is delayed by the server.
* **Bug Fix - Debuff Tracking** - HealBot now automatically tracks all curable debuffs for the player's class out of the box. Previously, users had to manually assign a cure spell to a click binding to enable CDC debuff tracking.
* **Bug Fix - CDC Filter** - Fixed an issue where the CDC module displayed all debuffs instead of filtering for dispellable ones, restoring proper health bar coloring.
* **Bug Fix - UnitClass Localization** - Added a fallback mapping for the Vanilla `UnitClass` API to always return the uppercase English class string. This fixes a bug where default options (like Debuff tracking) failed to initialize.
* **Feature - Shapeshift Spell Queue** - Built a native spell queue system for Druid auto-unshifting. Bypasses the "Cannot cast while shapeshifted" error and server lag when AutoUnshift is enabled, firing the heal seamlessly the exact millisecond the form is dropped. Now completely unified to use the standard casting API for maximum reliability across all client setups.
**v1.6.5**
* **Performance Update - Mana Tracking** - Added a fast-path redraw pipeline for unit power changes. This optimization prevents full frame redraws when players naturally regenerate or consume mana, resolving severe FPS drops in 40-man raids while tracking mana.