From a8626ae8fc42b71ad35fcf68f84849a7284c5dd8 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Wed, 13 May 2026 21:51:14 +0200 Subject: [PATCH] Fix filtered unit refresh rebuilding everybars in large raids ## Summary Fixes a UI lockup reported when using the class filter in a 40-bot raid and changing a Hunter combat buff. - Limit class-filtered unit state refreshes to the currently displayed bots instead of refreshing every bot in the raid. - Avoid rebuilding bridge everybars when the bot class, combat strategies, and non-combat strategies have not changed. - Add the same rebuild guard for the legacy `Strategies:` fallback path. ## Fixes Fixes #27 ## Testing - Ran `git diff --check`. - Manual test target: 40-bot raid -> PlayerBot Main Menu -> Class filter -> Hunter -> change a Hunter combat buff -> verify MultiBot buttons, Escape, Character, and Social remain responsive. --- Core/MultiBot.lua | 72 +++++++++++++++++++++++++++++-- Core/MultiBotHandler.lua | 23 +++++++++- UI/MultiBotUnitsRootUI.lua | 86 +++++++++++++++++++++++++++++++++++--- 3 files changed, 170 insertions(+), 11 deletions(-) diff --git a/Core/MultiBot.lua b/Core/MultiBot.lua index 211e1a5..1277176 100644 --- a/Core/MultiBot.lua +++ b/Core/MultiBot.lua @@ -1581,11 +1581,59 @@ local function RequestBridgeUnitsRelayout() end end +local function ShouldRebuildBridgeUnitFrame(unitFrame, button, combat, normal) + if not unitFrame or not button then + return true + end + + if unitFrame._mbBridgeBuilt ~= true then + return true + end + + if unitFrame._mbBridgeClass ~= button.class then + return true + end + + if unitFrame._mbBridgeCombat ~= combat then + return true + end + + if unitFrame._mbBridgeNormal ~= normal then + return true + end + + return false +end + +local function MarkBridgeUnitFrameBuilt(unitFrame, button, combat, normal) + if not unitFrame or not button then + return + end + + unitFrame._mbBridgeBuilt = true + unitFrame._mbBridgeClass = button.class + unitFrame._mbBridgeCombat = combat + unitFrame._mbBridgeNormal = normal +end + +local function EnsureBridgeActiveIndex(button, name) + if not button or not button.class or button.class == "" then + return + end + + MultiBot.index.classes.actives[button.class] = MultiBot.index.classes.actives[button.class] or {} + InsertBridgeNameUnique(MultiBot.index.classes.actives[button.class], name) + InsertBridgeNameUnique(MultiBot.index.actives, name) +end + function MultiBot.ApplyBridgeBotState(name, combat, normal) if type(name) ~= "string" or name == "" then return false end + combat = combat or "" + normal = normal or "" + if not (MultiBot.frames and MultiBot.frames["MultiBar"] and MultiBot.frames["MultiBar"].frames and MultiBot.frames["MultiBar"].frames["Units"]) then @@ -1598,6 +1646,9 @@ function MultiBot.ApplyBridgeBotState(name, combat, normal) return false end + local existingFrame = units.frames and units.frames[name] or nil + local shouldRebuildFrame = ShouldRebuildBridgeUnitFrame(existingFrame, button, combat, normal) + button.combat = combat or "" button.normal = normal or "" button.waitFor = "" @@ -1609,6 +1660,17 @@ function MultiBot.ApplyBridgeBotState(name, combat, normal) return true end + if not shouldRebuildFrame then + EnsureBridgeActiveIndex(button, name) + if button.setEnable then + button.setEnable() + end + + RequestBridgeUnitsRelayout() + return true + end + + local wasShown = existingFrame and existingFrame.IsShown and existingFrame:IsShown() local unitFrame = units.addFrame(name, -34, 2) if unitFrame and unitFrame.Hide then unitFrame:Hide() @@ -1626,15 +1688,17 @@ function MultiBot.ApplyBridgeBotState(name, combat, normal) MultiBot.addEvery(unitFrame, button.combat, button.normal) end - MultiBot.index.classes.actives[button.class] = MultiBot.index.classes.actives[button.class] or {} - InsertBridgeNameUnique(MultiBot.index.classes.actives[button.class], name) - InsertBridgeNameUnique(MultiBot.index.actives, name) + MarkBridgeUnitFrameBuilt(unitFrame, button, button.combat, button.normal) + + EnsureBridgeActiveIndex(button, name) if button.setEnable then button.setEnable() end - if unitFrame and unitFrame.Hide then + if wasShown and unitFrame and unitFrame.Show then + unitFrame:Show() + elseif unitFrame and unitFrame.Hide then unitFrame:Hide() end diff --git a/Core/MultiBotHandler.lua b/Core/MultiBotHandler.lua index 2f27ae9..36c61fb 100644 --- a/Core/MultiBotHandler.lua +++ b/Core/MultiBotHandler.lua @@ -2091,12 +2091,32 @@ function MultiBot.HandleMultiBotEvent(event, ...) tButton.waitFor = "IGNORE" tButton.normal = string.sub(arg1, 13) - local tFrame = MultiBot.frames["MultiBar"].frames["Units"].addFrame(arg2, tButton.x - tButton.size - 2, tButton.y + 2) + local tUnitsFrame = MultiBot.frames["MultiBar"].frames["Units"] + local tExistingFrame = tUnitsFrame.frames and tUnitsFrame.frames[arg2] or nil + local tCombat = tButton.combat or "" + local tNormal = tButton.normal or "" + + if tExistingFrame + and tExistingFrame._mbLegacyBuilt == true + and tExistingFrame._mbLegacyClass == tButton.class + and tExistingFrame._mbLegacyCombat == tCombat + and tExistingFrame._mbLegacyNormal == tNormal then + tButton.setEnable() + SendChatMessage("ss ?", "WHISPER", nil, arg2) + return + end + + local tWasShown = tExistingFrame and tExistingFrame.IsShown and tExistingFrame:IsShown() + local tFrame = tUnitsFrame.addFrame(arg2, tButton.x - tButton.size - 2, tButton.y + 2) tFrame.class = tButton.class tFrame.name = tButton.name MultiBot["add" .. tButton.class](tFrame, tButton.combat, tButton.normal) MultiBot.addEvery(tFrame, tButton.combat, tButton.normal) + tFrame._mbLegacyBuilt = true + tFrame._mbLegacyClass = tButton.class + tFrame._mbLegacyCombat = tCombat + tFrame._mbLegacyNormal = tNormal if(MultiBot.index.classes.actives[tButton.class] == nil) then MultiBot.index.classes.actives[tButton.class] = {} end if(MultiBot.isActive(tButton.name) == false) then @@ -2105,6 +2125,7 @@ function MultiBot.HandleMultiBotEvent(event, ...) end tButton.setEnable() + if tWasShown and tFrame.Show then tFrame:Show() end SendChatMessage("ss ?", "WHISPER", nil, arg2) return end diff --git a/UI/MultiBotUnitsRootUI.lua b/UI/MultiBotUnitsRootUI.lua index 2db2bec..162587f 100644 --- a/UI/MultiBotUnitsRootUI.lua +++ b/UI/MultiBotUnitsRootUI.lua @@ -124,6 +124,55 @@ local function getDisplayableUnits(unitsFrame, sourceTable) return display end +local function addRefreshTarget(targets, seen, unitsFrame, name) + if type(name) ~= "string" or name == "" or name == UnitName("player") then + return + end + + if seen[name] or not (unitsFrame and unitsFrame.buttons and unitsFrame.buttons[name]) then + return + end + + seen[name] = true + table.insert(targets, name) +end + +local function getVisibleRefreshTargets(unitsButton, unitsFrame) + local targets = {} + local seen = {} + + if not unitsButton or not unitsFrame then + return targets + end + + local visibleNames = unitsButton._visibleNames + if type(visibleNames) == "table" and #visibleNames > 0 then + for index = 1, #visibleNames do + addRefreshTarget(targets, seen, unitsFrame, visibleNames[index]) + end + + return targets + end + + local sourceTable = getUnitsSourceTable(unitsButton) + local display = getDisplayableUnits(unitsFrame, sourceTable) + local fromIndex = tonumber(unitsButton.from) or 1 + local toIndex = tonumber(unitsButton.to) or math.min(#display, UNITS_PAGE_SIZE) + + if fromIndex < 1 then + fromIndex = 1 + end + if toIndex < fromIndex then + toIndex = math.min(#display, UNITS_PAGE_SIZE) + end + + for index = fromIndex, math.min(toIndex, #display) do + addRefreshTarget(targets, seen, unitsFrame, display[index]) + end + + return targets +end + local function hideTrackedVisibleUnits(unitsButton, unitsFrame) local visibleNames = unitsButton and unitsButton._visibleNames if type(visibleNames) ~= "table" then @@ -292,7 +341,7 @@ local function refreshUnitsDisplay(unitsButton, requestedRoster, requestedFilter relayoutUnitsDisplay(unitsButton, unitsFrame) if refreshStrategiesForActiveBots then - refreshStrategiesForActiveBots() + refreshStrategiesForActiveBots(unitsButton) end end @@ -448,7 +497,14 @@ local function rebuildGuildAndFriendIndexes(button) return isGuildRetry end -refreshStrategiesForActiveBots = function() +refreshStrategiesForActiveBots = function(unitsButton) + local unitsFrame = MultiBot.frames + and MultiBot.frames["MultiBar"] + and MultiBot.frames["MultiBar"].frames + and MultiBot.frames["MultiBar"].frames[UNITS_FRAME_NAME] + local scopedToDisplayedUnits = unitsButton ~= nil + local targetNames = scopedToDisplayedUnits and getVisibleRefreshTargets(unitsButton, unitsFrame) or nil + if MultiBot.bridge and MultiBot.Comm and MultiBot.Comm.RequestStates then if not MultiBot.bridge.connected then return @@ -459,16 +515,24 @@ refreshStrategiesForActiveBots = function() return end - local unitsFrame = MultiBot.frames - and MultiBot.frames["MultiBar"] - and MultiBot.frames["MultiBar"].frames - and MultiBot.frames["MultiBar"].frames[UNITS_FRAME_NAME] local button = unitsFrame and unitsFrame.buttons and unitsFrame.buttons[name] if button then button.waitFor = "BRIDGE_STATE" end end + if scopedToDisplayedUnits then + if targetNames and #targetNames > 0 and MultiBot.Comm.RequestState then + for index = 1, #targetNames do + local name = targetNames[index] + markBridgeStateWait(name) + MultiBot.Comm.RequestState(name) + end + end + + return + end + if IsInRaid() then for index = 1, GetNumGroupMembers() do markBridgeStateWait(UnitName("raid" .. index)) @@ -527,6 +591,16 @@ refreshStrategiesForActiveBots = function() SendChatMessage("co ?", "WHISPER", nil, name) end + if scopedToDisplayedUnits then + if targetNames and #targetNames > 0 then + for index = 1, #targetNames do + refreshStrategiesFor(targetNames[index]) + end + end + + return + end + if IsInRaid() then for index = 1, GetNumGroupMembers() do refreshStrategiesFor(UnitName("raid" .. index))