From 45dd663b415d0674f864cb811a235e730560b4ac Mon Sep 17 00:00:00 2001 From: Wishmaster117 <140754794+Wishmaster117@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:21:07 +0200 Subject: [PATCH 1/3] P2B: Inventory read/refresh strict bridge routing --- Core/MultiBotComm.lua | 9 +++++- Core/MultiBotEngine.lua | 55 +++++++++++++++++++++++++++-------- Core/MultiBotHandler.lua | 45 +++------------------------- UI/MultiBotInventoryFrame.lua | 51 +++++++++++++++++--------------- 4 files changed, 83 insertions(+), 77 deletions(-) diff --git a/Core/MultiBotComm.lua b/Core/MultiBotComm.lua index cd1925f..d276240 100644 --- a/Core/MultiBotComm.lua +++ b/Core/MultiBotComm.lua @@ -14,6 +14,7 @@ Comm.version = "1" local STATE_FRAMING_CAPABILITY = "STATE_FRAMING_V1" local STRATEGY_MUTATION_CAPABILITY = "STRATEGY_MUTATION_V1" local OUTFIT_CAPABILITY = "OUTFIT_V1" +local INVENTORY_CAPABILITY = "INVENTORY_V1" local STATE_TIMEOUT_SECONDS = 5.0 local STATES_TIMEOUT_SECONDS = 15.0 local STRATEGY_MUTATION_TIMEOUT_SECONDS = 5.0 @@ -211,6 +212,7 @@ local function ensureBridgeState() state.stateFramingCapable = state.stateFramingCapable or false state.strategyMutationCapable = state.strategyMutationCapable or false state.outfitCapable = state.outfitCapable or false + state.inventoryCapable = state.inventoryCapable or false state.strategyMutationSeq = state.strategyMutationSeq or 0 state.strategyMutationCommands = state.strategyMutationCommands or {} state.weaponEnchantDebugSeq = state.weaponEnchantDebugSeq or 0 @@ -1280,7 +1282,7 @@ end function Comm.RequestInventory(name) local state = ensureBridgeState() name = trim(name) - if name == "" or not state.connected then + if name == "" or not state.connected or state.inventoryCapable ~= true then return false end @@ -1562,6 +1564,7 @@ function Comm.MarkDisconnected(reason) state.formationQueryActive = nil state.strategyMutationCapable = false state.outfitCapable = false + state.inventoryCapable = false state.stateFramingCapable = false local pendingTokens = {} @@ -3234,6 +3237,7 @@ function Comm.HandleAddonMessage(prefix, message, distribution, sender) state.stateFramingCapable = false state.strategyMutationCapable = false state.outfitCapable = false + state.inventoryCapable = false for capability in string.gmatch(payload or "", "([^,]+)") do capability = trim(capability) if capability == STATE_FRAMING_CAPABILITY then @@ -3242,6 +3246,8 @@ function Comm.HandleAddonMessage(prefix, message, distribution, sender) state.strategyMutationCapable = true elseif capability == OUTFIT_CAPABILITY then state.outfitCapable = true + elseif capability == INVENTORY_CAPABILITY then + state.inventoryCapable = true end end debugPrint("ADDON:RX", "CAPS", payload or "") @@ -4443,6 +4449,7 @@ function Comm.OnPlayerEnteringWorld() state.stateFramingCapable = false state.strategyMutationCapable = false state.outfitCapable = false + state.inventoryCapable = false state.strategyMutationCommands = {} state.details = {} state.stats = {} diff --git a/Core/MultiBotEngine.lua b/Core/MultiBotEngine.lua index 8cfda4c..803cece 100644 --- a/Core/MultiBotEngine.lua +++ b/Core/MultiBotEngine.lua @@ -2425,9 +2425,11 @@ local function scheduleInventoryRefresh(delay, callback) end -- MULTIBOT:INVENTORY REFRESH -- --- Rafraîchit l’inventaire d’un bot en bridge-first. --- Le fallback chat legacy est désactivé par défaut ; l’activer explicitement avec --- MultiBot.allowLegacyChatFallback = true pendant un diagnostic legacy. +-- P2B policy: +-- bridge capable + send success -> BRIDGE only +-- bridge capable + send failure -> BLOCKED, never chat +-- capability unavailable + legacy fallback enabled -> LEGACY +-- capability unavailable + fallback disabled -> BLOCKED MultiBot.RequestInventoryRefresh = function(botName, delay, options) botName = botName or (MultiBot.inventory and MultiBot.inventory.name) or "" if not botName or botName == "" then @@ -2436,33 +2438,60 @@ MultiBot.RequestInventoryRefresh = function(botName, delay, options) options = options or {} + local function clearWaitState(waitButton) + if waitButton and (waitButton.waitFor == "INVENTORY" or waitButton.waitFor == "ITEM" or waitButton.waitFor == "LOOT") then + waitButton.waitFor = "" + end + end + local function doRefresh() local waitButton = getInventoryUnitButton(botName) local bridge = MultiBot.bridge or nil local comm = MultiBot.Comm or nil - local bridgeConnected = bridge and bridge.connected + local bridgeConnected = bridge and bridge.connected == true + local bridgeInventoryCapable = bridgeConnected and bridge.inventoryCapable == true - if bridgeConnected and comm and comm.RequestInventory and comm.RequestInventory(botName) then - if waitButton and (waitButton.waitFor == "INVENTORY" or waitButton.waitFor == "ITEM" or waitButton.waitFor == "LOOT") then - waitButton.waitFor = "" + if bridgeInventoryCapable then + if comm and type(comm.RequestInventory) == "function" and comm.RequestInventory(botName) then + if bridge then + bridge.lastError = nil + end + clearWaitState(waitButton) + return true end - return true - end - if bridgeConnected and options.noChatFallbackWhenBridgeConnected then + if bridge then + bridge.lastError = "INVENTORY_SEND_FAILED" + end + clearWaitState(waitButton) return false end if options.bridgeOnly or MultiBot.allowLegacyChatFallback ~= true then + if bridge then + bridge.lastError = "INVENTORY_CAPABILITY_UNAVAILABLE" + end + clearWaitState(waitButton) return false end if not waitButton then + if bridge then + bridge.lastError = "INVENTORY_LEGACY_NO_BUTTON" + end return false end + local inventory = MultiBot.inventory + if inventory and inventory.beginPayload then + inventory:beginPayload(botName) + end + waitButton.waitFor = "INVENTORY" SendChatMessage("items", "WHISPER", nil, botName) + if bridge then + bridge.lastError = nil + end return true end @@ -2480,10 +2509,12 @@ MultiBot.RequestInventoryPostActionRefresh = function(botName, firstDelay, secon end options = options or {} - local bridgeConnected = MultiBot.bridge and MultiBot.bridge.connected + local bridgeInventoryCapable = MultiBot.bridge + and MultiBot.bridge.connected == true + and MultiBot.bridge.inventoryCapable == true local requested = MultiBot.RequestInventoryRefresh(botName, firstDelay or 0.45, options) - if requested and bridgeConnected and type(secondDelay) == "number" and secondDelay > 0 then + if requested and bridgeInventoryCapable and type(secondDelay) == "number" and secondDelay > 0 then MultiBot.RequestInventoryRefresh(botName, secondDelay, options) end diff --git a/Core/MultiBotHandler.lua b/Core/MultiBotHandler.lua index d964558..0341985 100644 --- a/Core/MultiBotHandler.lua +++ b/Core/MultiBotHandler.lua @@ -2222,18 +2222,7 @@ function MultiBot.HandleMultiBotEvent(event, ...) return end - if LegacyChatFallbackEnabled() then - tButton.waitFor = "INVENTORY" - if(MultiBot.TimerAfter) then - MultiBot.TimerAfter(0.45, function() - SendChatMessage("items", "WHISPER", nil, tButton.name) - end) - else - SendChatMessage("items", "WHISPER", nil, tButton.name) - end - else - tButton.waitFor = "" - end + tButton.waitFor = "" return end @@ -2246,18 +2235,7 @@ function MultiBot.HandleMultiBotEvent(event, ...) return end - if LegacyChatFallbackEnabled() then - tButton.waitFor = "INVENTORY" - if(MultiBot.TimerAfter) then - MultiBot.TimerAfter(0.45, function() - SendChatMessage("items", "WHISPER", nil, tButton.name) - end) - else - SendChatMessage("items", "WHISPER", nil, tButton.name) - end - else - tButton.waitFor = "" - end + tButton.waitFor = "" return end @@ -2300,12 +2278,7 @@ function MultiBot.HandleMultiBotEvent(event, ...) return end - if LegacyChatFallbackEnabled() then - tButton.waitFor = "INVENTORY" - SendChatMessage("items", "WHISPER", nil, tButton.name) - else - tButton.waitFor = "" - end + tButton.waitFor = "" return end end @@ -2318,21 +2291,11 @@ function MultiBot.HandleMultiBotEvent(event, ...) local botName = inventory and inventory.name or "" if inventory and inventory:IsVisible() and botName ~= "" then - local bridgeConnected = MultiBot.bridge and MultiBot.bridge.connected - if MultiBot.RequestInventoryPostActionRefresh - and MultiBot.RequestInventoryPostActionRefresh(botName, 0.45, 1.20, { noChatFallbackWhenBridgeConnected = true }) then + and MultiBot.RequestInventoryPostActionRefresh(botName, 0.45, 1.20) then return end - if bridgeConnected then - return - end - - if MultiBot.RefreshInventory then - MultiBot.RefreshInventory(0.45) - return - end return end diff --git a/UI/MultiBotInventoryFrame.lua b/UI/MultiBotInventoryFrame.lua index cdb37d2..e6d3e16 100644 --- a/UI/MultiBotInventoryFrame.lua +++ b/UI/MultiBotInventoryFrame.lua @@ -818,25 +818,11 @@ local function resetInventoryViewState() end local function requestInventoryForBot(botName) - if botName and botName ~= "" and MultiBot.RequestInventoryRefresh and MultiBot.RequestInventoryRefresh(botName) then - return true - end - - if MultiBot.allowLegacyChatFallback ~= true then + if not botName or botName == "" or not MultiBot.RequestInventoryRefresh then return false end - local waitButton = getInventoryWaitButton(botName) - if waitButton then - waitButton.waitFor = "INVENTORY" - end - - if botName and botName ~= "" then - SendChatMessage("items", "WHISPER", nil, botName) - return true - end - - return false + return MultiBot.RequestInventoryRefresh(botName) end MultiBot.RequestBotInventory = function(botName) @@ -853,8 +839,7 @@ MultiBot.RequestBotInventory = function(botName) return inventory:requestBotInventory(botName) end - requestInventoryForBot(botName) - return true + return requestInventoryForBot(botName) end local function closeInventoryWindow() @@ -911,14 +896,27 @@ local function prepareInventoryForBot(botName) return false end + local inventory = MultiBot and MultiBot.inventory or nil + local previousBotName = inventory and inventory.name or "" + disableOtherInventoryButtons(botName) setInventoryBotName(botName) openInventoryWindow() openInspectForInventoryBot(botName) - local inventory = MultiBot and MultiBot.inventory or nil - if inventory and inventory.beginPayload then - inventory:beginPayload(botName) + if inventory and previousBotName ~= botName then + inventory.pendingLootBot = nil + if inventory.resetItems then + inventory:resetItems() + end + inventory.summary = { + bagUsed = nil, + bagTotal = nil, + gold = 0, + silver = 0, + copper = 0, + } + updateInventorySummaryLabels(inventory) end local sourceButton = getInventorySourceButton(botName) @@ -926,8 +924,15 @@ local function prepareInventoryForBot(botName) sourceButton.setEnable() end - requestInventoryForBot(botName) - return true + local requested = requestInventoryForBot(botName) + if not requested then + local waitButton = getInventoryWaitButton(botName) + if waitButton and (waitButton.waitFor == "INVENTORY" or waitButton.waitFor == "ITEM" or waitButton.waitFor == "LOOT") then + waitButton.waitFor = "" + end + end + + return requested end local function setInventoryActionState(buttonKey, options) From 94bc138856af3d2f6a0205cd8fd9453e9f6aad57 Mon Sep 17 00:00:00 2001 From: Wishmaster117 <140754794+Wishmaster117@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:59:29 +0200 Subject: [PATCH 2/3] P2B: fix inventory refresh reentrancy --- Core/MultiBotComm.lua | 5 ++++- Core/MultiBotEngine.lua | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Core/MultiBotComm.lua b/Core/MultiBotComm.lua index d276240..6349bfa 100644 --- a/Core/MultiBotComm.lua +++ b/Core/MultiBotComm.lua @@ -1293,6 +1293,7 @@ function Comm.RequestInventory(name) botNameKey = string.lower(name), token = token, startedAt = safeNow(), + begun = false, } if not Comm.Send("GET", "INVENTORY~" .. name .. "~" .. token) then @@ -3570,7 +3571,9 @@ function Comm.HandleAddonMessage(prefix, message, distribution, sender) state.connected = true state.lastError = nil - if getActiveInventoryRequest(botName, token) then + local active = getActiveInventoryRequest(botName, token) + if active then + active.begun = true local inventory = getInventoryFrame() if inventory and inventory.beginPayload then inventory:beginPayload(trim(botName)) diff --git a/Core/MultiBotEngine.lua b/Core/MultiBotEngine.lua index 803cece..270d145 100644 --- a/Core/MultiBotEngine.lua +++ b/Core/MultiBotEngine.lua @@ -2456,6 +2456,20 @@ MultiBot.RequestInventoryRefresh = function(botName, delay, options) if bridge then bridge.lastError = nil end + + -- P2B review fix v2: INV_* can be processed reentrantly before + -- RequestInventory returns. Neutralize stale data only while the + -- same request is still active and INV_BEGIN has not already run. + local activeInventoryRequest = bridge and bridge.inventoryActive or nil + if activeInventoryRequest + and activeInventoryRequest.botNameKey == string.lower(botName) + and activeInventoryRequest.begun ~= true then + local inventory = MultiBot.inventory + if inventory and inventory.name == botName and type(inventory.beginPayload) == "function" then + inventory:beginPayload(botName) + end + end + clearWaitState(waitButton) return true end From e2974ef4cfb3d27b8dfb5b17aa1465b8885aa1bf Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Mon, 10 Aug 2026 21:43:12 +0200 Subject: [PATCH 3/3] P2B: neutralize stale inventory on refresh failures --- Core/MultiBotEngine.lua | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Core/MultiBotEngine.lua b/Core/MultiBotEngine.lua index 270d145..baa1c32 100644 --- a/Core/MultiBotEngine.lua +++ b/Core/MultiBotEngine.lua @@ -2444,6 +2444,13 @@ MultiBot.RequestInventoryRefresh = function(botName, delay, options) end end + local function neutralizeCurrentInventoryView() + local inventory = MultiBot.inventory + if inventory and inventory.name == botName and type(inventory.beginPayload) == "function" then + inventory:beginPayload(botName) + end + end + local function doRefresh() local waitButton = getInventoryUnitButton(botName) local bridge = MultiBot.bridge or nil @@ -2464,10 +2471,7 @@ MultiBot.RequestInventoryRefresh = function(botName, delay, options) if activeInventoryRequest and activeInventoryRequest.botNameKey == string.lower(botName) and activeInventoryRequest.begun ~= true then - local inventory = MultiBot.inventory - if inventory and inventory.name == botName and type(inventory.beginPayload) == "function" then - inventory:beginPayload(botName) - end + neutralizeCurrentInventoryView() end clearWaitState(waitButton) @@ -2477,6 +2481,7 @@ MultiBot.RequestInventoryRefresh = function(botName, delay, options) if bridge then bridge.lastError = "INVENTORY_SEND_FAILED" end + neutralizeCurrentInventoryView() clearWaitState(waitButton) return false end @@ -2485,6 +2490,7 @@ MultiBot.RequestInventoryRefresh = function(botName, delay, options) if bridge then bridge.lastError = "INVENTORY_CAPABILITY_UNAVAILABLE" end + neutralizeCurrentInventoryView() clearWaitState(waitButton) return false end @@ -2493,6 +2499,7 @@ MultiBot.RequestInventoryRefresh = function(botName, delay, options) if bridge then bridge.lastError = "INVENTORY_LEGACY_NO_BUTTON" end + neutralizeCurrentInventoryView() return false end