diff --git a/UI/MultiBotBankFrame.lua b/UI/MultiBotBankFrame.lua new file mode 100644 index 0000000..c3b48d4 --- /dev/null +++ b/UI/MultiBotBankFrame.lua @@ -0,0 +1,437 @@ +if not MultiBot then + return +end + +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) + +local BANK_FRAME_WIDTH = 340 +local BANK_FRAME_HEIGHT = 420 +local BANK_FRAME_X = -360 +local BANK_ROW_WIDTH = 300 +local BANK_TEXT_WIDTH = 210 +local BANK_WITHDRAW_BUTTON_WIDTH = 70 +local BANK_REFRESH_DELAY = 0.65 + +local function L(key, fallback) + if MultiBot and type(MultiBot.L) == "function" then + return MultiBot.L(key, fallback) + end + + return fallback or key +end + +local function setButtonEnabled(button, enabled) + if not button then + return + end + + if enabled then + if button.Enable then button:Enable() end + if button.SetAlpha then button:SetAlpha(1) end + else + if button.Disable then button:Disable() end + if button.SetAlpha then button:SetAlpha(0.45) end + end +end + +local function createWindow(name, title, width, height, pointX) + if AceGUI then + local widget = AceGUI:Create("Window") + widget:SetTitle(title) + widget:SetLayout("Fill") + widget:SetWidth(width) + widget:SetHeight(height) + widget.frame:SetPoint("CENTER", UIParent, "CENTER", pointX or 0, 0) + widget.frame:SetFrameStrata("DIALOG") + widget:EnableResize(false) + return widget + end + + local frame = CreateFrame("Frame", name, UIParent, "BasicFrameTemplateWithInset") + frame:SetSize(width, height) + frame:SetPoint("CENTER", UIParent, "CENTER", pointX or 0, 0) + frame:SetFrameStrata("DIALOG") + frame.title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + frame.title:SetPoint("TOP", 0, -7) + frame.title:SetText(title) + return { + frame = frame, + content = frame, + SetTitle = function(self, value) self.frame.title:SetText(value) end, + Show = function(self) self.frame:Show() end, + Hide = function(self) self.frame:Hide() end, + IsShown = function(self) return self.frame:IsShown() end, + } +end + +local function getFrameContent(window) + if window and window.content then + return window.content + end + + return window and window.frame or nil +end + +local function setWindowTitle(window, title) + if not window then + return + end + + if window.SetTitle then + window:SetTitle(title) + elseif window.title then + window.title:SetText(title) + end +end + +local function createText(parent, font, point, x, y) + local text = parent:CreateFontString(nil, "OVERLAY", font) + text:SetPoint(point, parent, point, x, y) + text:SetJustifyH("LEFT") + return text +end + +local function systemMessage(message) + message = tostring(message or "") + if message == "" then + return + end + + if DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then + DEFAULT_CHAT_FRAME:AddMessage(message) + elseif print then + print(message) + end +end + +local function parseBankItemLine(line) + line = tostring(line or "") + local itemId = tonumber(string.match(line, "item:(%d+)") or "0") or 0 + if itemId <= 0 then + return nil + end + + local name, link, _, _, _, _, _, _, _, icon = GetItemInfo(itemId) + if not icon and GetItemIcon then + icon = GetItemIcon(itemId) + end + + local count = tonumber(string.match(line, "rx(%d+)") or string.match(line, "x(%d+)") or "1") or 1 + if count < 1 then + count = 1 + end + + return { + itemId = itemId, + name = name or ("item:" .. itemId), + link = link or line, + icon = icon or "Interface\\Icons\\INV_Misc_QuestionMark", + count = count, + line = line, + } +end + +local function getBankReasonText(reason) + reason = tostring(reason or "") + if reason == "" or reason == "OK" then + return "" + end + + return L("info.inventory.item_action.reason." .. reason, reason) +end + +local function getBankModeTitle(mode) + if mode == "gbank" then + return L("inventory.gbank.title", "Guild Bank") + end + + return L("inventory.bank.title", "Bot Bank") +end + +local function getBankModeLoadingText(mode) + if mode == "gbank" then + return L("inventory.gbank.loading", "Loading guild bank...") + end + + return L("inventory.bank.loading", "Loading bank...") +end + +local function getBankModeCountText(mode) + if mode == "gbank" then + return L("inventory.gbank.count", "guild bank item(s)") + end + + return L("inventory.bank.count", "bank item(s)") +end + +local function getBankModeBridgeRequiredText(mode) + if mode == "gbank" then + return L("inventory.gbank.bridge.required", "Guild bank bridge is not connected.") + end + + return L("inventory.bank.bridge.required", "Bank bridge is not connected.") +end + +local function canWithdrawFromMode(mode) + return mode ~= "gbank" +end + +local function ensureBankFrame() + if MultiBot.bankFrame then + return MultiBot.bankFrame + end + + local frame = createWindow("MultiBotBankFrame", L("inventory.bank.title", "Bot Bank"), BANK_FRAME_WIDTH, BANK_FRAME_HEIGHT, BANK_FRAME_X) + if not frame then + return nil + end + + local content = getFrameContent(frame) + frame.status = createText(content, "GameFontHighlightSmall", "TOPLEFT", 18, -10) + frame.rows = {} + frame.items = {} + frame.page = 1 + frame.pageSize = 12 + + for i = 1, frame.pageSize do + local row = CreateFrame("Button", nil, content) + row:SetPoint("TOPLEFT", content, "TOPLEFT", 18, -32 - ((i - 1) * 27)) + row:SetWidth(BANK_ROW_WIDTH) + row:SetHeight(24) + + row.icon = row:CreateTexture(nil, "ARTWORK") + row.icon:SetPoint("LEFT", row, "LEFT", 0, 0) + row.icon:SetWidth(22) + row.icon:SetHeight(22) + + row.text = createText(row, "GameFontHighlightSmall", "LEFT", 28, 0) + row.text:SetWidth(BANK_TEXT_WIDTH) + row.text:SetHeight(22) + + row.withdrawButton = CreateFrame("Button", nil, row, "UIPanelButtonTemplate") + row.withdrawButton:SetPoint("RIGHT", row, "RIGHT", 0, 0) + row.withdrawButton:SetWidth(BANK_WITHDRAW_BUTTON_WIDTH) + row.withdrawButton:SetHeight(20) + row.withdrawButton:SetText(L("inventory.bank.withdraw", "Withdraw")) + row.withdrawButton:SetScript("OnClick", function() + local item = row.item + if not item or not frame.botName or not canWithdrawFromMode(frame.mode) then + return + end + + if MultiBot.Comm and MultiBot.Comm.RunInventoryItemAction then + local token = MultiBot.Comm.RunInventoryItemAction(frame.botName, "BANK_WITHDRAW", item.itemId, 0) + if token then + frame.status:SetText(L("inventory.bank.withdraw.pending", "Withdraw requested...")) + setButtonEnabled(row.withdrawButton, false) + return + end + end + + frame.status:SetText(L("inventory.bank.withdraw.failed", "Withdraw request failed.")) + end) + + row:SetScript("OnEnter", function(self) + if not self.item or not GameTooltip then return end + GameTooltip:SetOwner(self, "ANCHOR_RIGHT") + if self.item.link and string.sub(self.item.link, 1, 1) == "|" then + GameTooltip:SetHyperlink(self.item.link) + else + GameTooltip:SetText(self.item.name or "") + end + GameTooltip:Show() + end) + row:SetScript("OnLeave", function() + if GameTooltip then GameTooltip:Hide() end + end) + + frame.rows[i] = row + end + + frame.prev = CreateFrame("Button", nil, content, "UIPanelButtonTemplate") + frame.prev:SetPoint("BOTTOMLEFT", content, "BOTTOMLEFT", 18, 8) + frame.prev:SetWidth(48) + frame.prev:SetHeight(20) + frame.prev:SetText("<") + + frame.pageText = createText(content, "GameFontNormalSmall", "BOTTOM", 0, 12) + + frame.next = CreateFrame("Button", nil, content, "UIPanelButtonTemplate") + frame.next:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", -18, 8) + frame.next:SetWidth(48) + frame.next:SetHeight(20) + frame.next:SetText(">") + + frame.render = function(self) + local maxPage = math.max(1, math.ceil(#self.items / self.pageSize)) + if self.page > maxPage then self.page = maxPage end + if self.page < 1 then self.page = 1 end + + self.pageText:SetText(self.page .. "/" .. maxPage) + setButtonEnabled(self.prev, self.page > 1) + setButtonEnabled(self.next, self.page < maxPage) + + local from = ((self.page - 1) * self.pageSize) + 1 + for i = 1, self.pageSize do + local row = self.rows[i] + local item = self.items[from + i - 1] + row.item = item + if item then + row.icon:SetTexture(MultiBot.SafeTexturePath(item.icon)) + row.text:SetText((item.name or ("item:" .. item.itemId)) .. " |cff999999x" .. tostring(item.count or 1) .. "|r") + if canWithdrawFromMode(self.mode) then + row.withdrawButton:SetText(L("inventory.bank.withdraw", "Withdraw")) + setButtonEnabled(row.withdrawButton, true) + row.withdrawButton:Show() + else + row.withdrawButton:Hide() + end + row:Show() + else + row.withdrawButton:Hide() + row:Hide() + end + end + end + + frame.prev:SetScript("OnClick", function() + frame.page = frame.page - 1 + frame:render() + end) + + frame.next:SetScript("OnClick", function() + frame.page = frame.page + 1 + frame:render() + end) + + frame.setItems = function(self, botName, lines, errorReason, mode) + self.botName = botName + self.mode = mode or self.mode or "bank" + self.items = {} + self.page = 1 + setWindowTitle(self, getBankModeTitle(self.mode) .. " - " .. tostring(botName or "")) + + if errorReason and errorReason ~= "" then + self.status:SetText(getBankReasonText(errorReason)) + else + for _, line in ipairs(lines or {}) do + local item = parseBankItemLine(line) + if item then + table.insert(self.items, item) + end + end + self.status:SetText(#self.items .. " " .. getBankModeCountText(self.mode)) + end + + self:render() + self:Show() + end + + frame:Hide() + MultiBot.bankFrame = frame + return frame +end + +function MultiBot.OpenBotBank(botName) + botName = tostring(botName or "") + if botName == "" then + return false + end + + local frame = ensureBankFrame() + frame.botName = botName + frame.mode = "bank" + frame.items = {} + frame.page = 1 + setWindowTitle(frame, getBankModeTitle(frame.mode) .. " - " .. botName) + frame.status:SetText(getBankModeLoadingText(frame.mode)) + frame:render() + frame:Show() + + if MultiBot.Comm and MultiBot.Comm.RequestBank then + return MultiBot.Comm.RequestBank(botName) + end + + systemMessage(getBankModeBridgeRequiredText(frame.mode)) + return false +end + +function MultiBot.OpenBotGuildBank(botName) + botName = tostring(botName or "") + if botName == "" then + return false + end + + local frame = ensureBankFrame() + frame.botName = botName + frame.mode = "gbank" + frame.items = {} + frame.page = 1 + setWindowTitle(frame, getBankModeTitle(frame.mode) .. " - " .. botName) + frame.status:SetText(getBankModeLoadingText(frame.mode)) + frame:render() + frame:Show() + + if MultiBot.Comm and MultiBot.Comm.RequestGuildBank then + return MultiBot.Comm.RequestGuildBank(botName) + end + + systemMessage(getBankModeBridgeRequiredText(frame.mode)) + return false +end + +function MultiBot.RefreshBotBank(botName, delay) + local frame = MultiBot.bankFrame + botName = tostring(botName or (frame and frame.botName) or "") + if botName == "" or not frame or frame.mode == "gbank" or not frame.IsShown or not frame:IsShown() then + return false + end + + local function refresh() + if frame and frame.IsShown and frame:IsShown() and MultiBot.Comm and MultiBot.Comm.RequestBank then + MultiBot.Comm.RequestBank(botName) + end + end + + if type(MultiBot.TimerAfter) == "function" then + MultiBot.TimerAfter(delay or BANK_REFRESH_DELAY, refresh) + else + refresh() + end + + return true +end + +function MultiBot.RefreshBotGuildBank(botName, delay) + local frame = MultiBot.bankFrame + botName = tostring(botName or (frame and frame.botName) or "") + if botName == "" or not frame or frame.mode ~= "gbank" or not frame.IsShown or not frame:IsShown() then + return false + end + + local function refresh() + if frame and frame.IsShown and frame:IsShown() and MultiBot.Comm and MultiBot.Comm.RequestGuildBank then + MultiBot.Comm.RequestGuildBank(botName) + end + end + + if type(MultiBot.TimerAfter) == "function" then + MultiBot.TimerAfter(delay or BANK_REFRESH_DELAY, refresh) + else + refresh() + end + + return true +end + +function MultiBot.OnBridgeBankItems(botName, lines, errorReason) + ensureBankFrame():setItems(botName, lines or {}, errorReason, "bank") +end + +function MultiBot.OnBridgeGuildBankItems(botName, lines, errorReason) + ensureBankFrame():setItems(botName, lines or {}, errorReason, "gbank") +end + +function MultiBot.InitializeBankFrame() + ensureBankFrame() +end \ No newline at end of file diff --git a/UI/MultiBotCharacterInfoFrame.lua b/UI/MultiBotCharacterInfoFrame.lua index 380c9d0..32fcef3 100644 --- a/UI/MultiBotCharacterInfoFrame.lua +++ b/UI/MultiBotCharacterInfoFrame.lua @@ -36,9 +36,9 @@ local CHARACTER_SKILL_VALUE_X = 142 local CHARACTER_SKILL_VALUE_WIDTH = 72 local RECIPE_FRAME_WIDTH = 340 local RECIPE_FRAME_X = 145 -local RECIPE_ROW_WIDTH = 302 -local RECIPE_CRAFT_BUTTON_WIDTH = 54 -local RECIPE_TEXT_WIDTH = 206 +local RECIPE_ROW_WIDTH = 286 +local RECIPE_CRAFT_BUTTON_WIDTH = 62 +local RECIPE_TEXT_WIDTH = 176 local RECIPE_REFRESH_DELAY = 3.0 local COOKING_SKILL_ID = 185 @@ -295,6 +295,39 @@ local function buildMaterialsText(recipe) return table.concat(parts, ", ") end +local function getFirstMissingMaterial(recipe) + for _, material in ipairs((recipe and recipe.materials) or {}) do + local itemId = tonumber(material.itemId or 0) or 0 + local required = tonumber(material.required or 0) or 0 + local available = tonumber(material.available or 0) or 0 + if itemId > 0 and required > available then + return { + itemId = itemId, + missing = required - available, + } + end + end + + return nil +end + +local function getMissingMaterials(recipe) + local missings = {} + for _, material in ipairs((recipe and recipe.materials) or {}) do + local itemId = tonumber(material.itemId or 0) or 0 + local required = tonumber(material.required or 0) or 0 + local available = tonumber(material.available or 0) or 0 + if itemId > 0 and required > available then + table.insert(missings, { + itemId = itemId, + missing = required - available, + }) + end + end + + return missings +end + local function getRecipePendingKey(botName, skillId, spellId) return string.lower(tostring(botName or "")) .. ":" .. tostring(tonumber(skillId or 0) or 0) .. ":" .. tostring(tonumber(spellId or 0) or 0) end @@ -389,7 +422,26 @@ local function ensureRecipeFrame() local spellId = tonumber(recipe.spellId or 0) or 0 local itemId = tonumber(recipe.itemId or 0) or 0 local craftable = tonumber(recipe.craftable or 0) or 0 - if skillId <= 0 or spellId <= 0 or craftable <= 0 then + if craftable <= 0 then + local missings = getMissingMaterials(recipe) + local requested = false + if #missings > 0 and MultiBot.Comm and MultiBot.Comm.RunInventoryItemAction then + for _, missing in ipairs(missings) do + local token = MultiBot.Comm.RunInventoryItemAction(frame.botName, "BUY_ITEM", missing.itemId, missing.missing) + if token then + requested = true + end + end + end + if requested then + frame.status:SetText(L("profession.recipes.buy_missing.pending", "Buy requested...")) + else + frame.status:SetText(L("profession.recipes.buy_missing.failed", "Buy request failed.")) + end + return + end + + if skillId <= 0 or spellId <= 0 then return end @@ -407,8 +459,13 @@ local function ensureRecipeFrame() row.craftButton:SetScript("OnEnter", function(self) if not GameTooltip then return end GameTooltip:SetOwner(self, "ANCHOR_RIGHT") - GameTooltip:AddLine(L("profession.recipes.craft", "Craft")) - GameTooltip:AddLine(L("profession.recipes.craft.tooltip", "Ask the bot to craft this recipe once."), 1, 1, 1, true) + if row.recipe and tonumber(row.recipe.craftable or 0) <= 0 and getFirstMissingMaterial(row.recipe) then + GameTooltip:AddLine(L("profession.recipes.buy_missing", "Buy")) + GameTooltip:AddLine(L("profession.recipes.buy_missing.tooltip", "Ask the bot to buy the first missing material from a nearby vendor."), 1, 1, 1, true) + else + GameTooltip:AddLine(L("profession.recipes.craft", "Craft")) + GameTooltip:AddLine(L("profession.recipes.craft.tooltip", "Ask the bot to craft this recipe once."), 1, 1, 1, true) + end GameTooltip:Show() end) row.craftButton:SetScript("OnLeave", function() @@ -466,10 +523,19 @@ local function ensureRecipeFrame() local color = DIFFICULTY_COLORS[recipe.difficulty or ""] or "|cffffffff" local craftable = tonumber(recipe.craftable or 0) or 0 local pending = self.pendingCrafts[getRecipePendingKey(self.botName, recipe.skillId, recipe.spellId)] + local missing = getFirstMissingMaterial(recipe) row.icon:SetTexture(MultiBot.SafeTexturePath(icon)) row.text:SetText(color .. name .. "|r |cff999999x" .. craftable .. "|r") - row.craftButton:SetText(pending and "..." or L("profession.recipes.craft", "Craft")) - setButtonEnabled(row.craftButton, craftable > 0 and tonumber(recipe.spellId or 0) > 0 and not pending) + if craftable > 0 then + row.craftButton:SetText(pending and "..." or L("profession.recipes.craft", "Craft")) + setButtonEnabled(row.craftButton, tonumber(recipe.spellId or 0) > 0 and not pending) + elseif missing then + row.craftButton:SetText(L("profession.recipes.buy_missing", "Buy")) + setButtonEnabled(row.craftButton, true) + else + row.craftButton:SetText(L("profession.recipes.craft", "Craft")) + setButtonEnabled(row.craftButton, false) + end row.craftButton:Show() row:Show() else diff --git a/UI/MultiBotInventoryFrame.lua b/UI/MultiBotInventoryFrame.lua index 2c982fe..cdb37d2 100644 --- a/UI/MultiBotInventoryFrame.lua +++ b/UI/MultiBotInventoryFrame.lua @@ -20,6 +20,9 @@ local INVENTORY_WINDOW_DEFAULTS = { instantActionColumns = 3, instantActionSpacingX = 29, instantActionSpacingY = 34, + modeActionColumns = 2, + modeActionSpacingX = 36, + modeActionSpacingY = 36, summaryTopPadding = 10, summaryLineSpacing = 16, itemSize = 32, @@ -32,12 +35,15 @@ local INVENTORY_WINDOW_DEFAULTS = { } local INVENTORY_LAYOUT_KEY = "InventoryPoint" -local ACTION_ORDER = { "Sell", "Equip", "Use", "Trade", "Destroy" } +local ACTION_ORDER = { "Sell", "Equip", "Use", "Trade", "Bank", "GuildBank", "Buy", "Destroy" } local ACTION_MODE_CONFIG = { Sell = { value = "s", cancelTradeOnActivate = true }, Equip = { value = "e", cancelTradeOnActivate = true }, Use = { value = "u", cancelTradeOnActivate = true }, Trade = { value = "give", cancelTradeOnActivate = false }, + Bank = { value = "bank", cancelTradeOnActivate = true }, + GuildBank = { value = "gb", cancelTradeOnActivate = true }, + Buy = { value = "b", cancelTradeOnActivate = true }, Destroy = { value = "destroy", cancelTradeOnActivate = true }, } @@ -567,6 +573,9 @@ local function updateModeLabel() e = "Equip", u = "Use", give = "Trade", + bank = MultiBot.L("inventory.mode.bank", "Bank"), + gb = MultiBot.L("inventory.mode.gbank", "Guild Bank"), + b = MultiBot.L("inventory.mode.buy", "Buy"), destroy = "Destroy", } @@ -1153,21 +1162,34 @@ local function createInventoryContent(window) { key = "Equip", texture = "inv_helmet_22", tip = MultiBot.L("tips.inventory.equip") }, { key = "Use", texture = "inv_gauntlets_25", tip = MultiBot.L("tips.inventory.use") }, { key = "Trade", texture = "achievement_reputation_01", tip = MultiBot.L("tips.inventory.trade") }, + { key = "Bank", texture = "inv_misc_bag_10", tip = MultiBot.L("tips.inventory.bank.deposit", "Deposit to bank") }, + { key = "GuildBank", texture = "inv_misc_bag_15", tip = MultiBot.L("tips.inventory.gbank.deposit", "Deposit to guild bank") }, + { key = "Buy", texture = "inv_misc_coin_05", tip = MultiBot.L("tips.inventory.buy", "Buy this item from a nearby vendor") }, { key = "Destroy", texture = "inv_hammer_15", tip = MultiBot.L("tips.inventory.drop") }, } local instantButtonDefs = { { key = "SellGrey", texture = "inv_misc_coin_03", tip = MultiBot.L("tips.inventory.sellgrey") }, { key = "SellVendor", texture = "inv_misc_coin_04", tip = MultiBot.L("tips.inventory.sellvendor") }, { key = "Open", texture = "inv_misc_gift_05", tip = MultiBot.L("tips.inventory.open") }, + { key = "BankOpen", texture = "inv_misc_bag_10", tip = MultiBot.L("tips.inventory.bank.open", "Open bot bank") }, + { key = "GuildBankOpen", texture = "inv_misc_bag_15", tip = MultiBot.L("tips.inventory.gbank.open", "Open bot guild bank") }, } for index, definition in ipairs(modeButtonDefs) do - local yOffset = -INVENTORY_WINDOW_DEFAULTS.buttonStartOffsetY - ((index - 1) * INVENTORY_WINDOW_DEFAULTS.buttonSpacing) - buttons[definition.key] = makeActionButton(leftPanel, definition.key, definition.texture, definition.tip, yOffset) + local columns = math.max(1, INVENTORY_WINDOW_DEFAULTS.modeActionColumns or 1) + local spacingX = INVENTORY_WINDOW_DEFAULTS.modeActionSpacingX or INVENTORY_WINDOW_DEFAULTS.buttonSpacing + local spacingY = INVENTORY_WINDOW_DEFAULTS.modeActionSpacingY or INVENTORY_WINDOW_DEFAULTS.buttonSpacing + local groupWidth = INVENTORY_WINDOW_DEFAULTS.buttonSize + ((columns - 1) * spacingX) + local startX = math.floor((INVENTORY_WINDOW_DEFAULTS.actionsWidth - groupWidth) / 2) + local column = (index - 1) % columns + local row = math.floor((index - 1) / columns) + local xOffset = startX + (column * spacingX) + local yOffset = -INVENTORY_WINDOW_DEFAULTS.buttonStartOffsetY - (row * spacingY) + buttons[definition.key] = makeActionButton(leftPanel, definition.key, definition.texture, definition.tip, yOffset, xOffset) end local instantStartY = -INVENTORY_WINDOW_DEFAULTS.buttonStartOffsetY - - (#modeButtonDefs * INVENTORY_WINDOW_DEFAULTS.buttonSpacing) + - (math.ceil(#modeButtonDefs / math.max(1, INVENTORY_WINDOW_DEFAULTS.modeActionColumns or 1)) * (INVENTORY_WINDOW_DEFAULTS.modeActionSpacingY or INVENTORY_WINDOW_DEFAULTS.buttonSpacing)) - INVENTORY_WINDOW_DEFAULTS.instantActionsTopPadding local instantColumns = math.max(1, INVENTORY_WINDOW_DEFAULTS.instantActionColumns or 1) local instantSpacingX = INVENTORY_WINDOW_DEFAULTS.instantActionSpacingX or INVENTORY_WINDOW_DEFAULTS.buttonSpacing @@ -1487,6 +1509,20 @@ function MultiBot.InitializeInventoryFrame() runInventoryInstantAction(pButton.getName(), "open items") end + inventory.buttons.BankOpen.doLeft = function(pButton) + local botName = pButton.getName and pButton.getName() or nil + if MultiBot.OpenBotBank then + MultiBot.OpenBotBank(botName) + end + end + + inventory.buttons.GuildBankOpen.doLeft = function(pButton) + local botName = pButton.getName and pButton.getName() or nil + if MultiBot.OpenBotGuildBank then + MultiBot.OpenBotGuildBank(botName) + end + end + setInventoryActionState("Sell", { cancelTrade = false }) resetInventoryViewState() updateInventorySummaryLabels(inventory) diff --git a/UI/MultiBotInventoryItem.lua b/UI/MultiBotInventoryItem.lua index 99c0836..a52835c 100644 --- a/UI/MultiBotInventoryItem.lua +++ b/UI/MultiBotInventoryItem.lua @@ -305,6 +305,19 @@ local function sendInventoryFeedback(key, fallback) SendChatMessage(inventoryItemL(key, fallback), "SAY") end +local function addInventorySystemMessage(message) + message = tostring(message or "") + if message == "" then + return + end + + if DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then + DEFAULT_CHAT_FRAME:AddMessage(message) + elseif print then + print(message) + end +end + local function isInventoryProtectedKey(item) return MultiBot.isInside(item and item.info or "", "%f[%a][Kk]ey%f[%A]") end @@ -373,6 +386,29 @@ local function sendInventoryItemCommand(command, button, botName, options) return true end +local function runBridgeInventoryItemAction(action, button, botName, options) + options = options or {} + if not action or action == "" or not button or not button.item or not botName or botName == "" then + return false + end + + local itemId = tonumber(button.item.id or 0) or 0 + if itemId <= 0 then + return false + end + + if not MultiBot.Comm or not MultiBot.Comm.RunInventoryItemAction then + return false + end + + local token = MultiBot.Comm.RunInventoryItemAction(botName, action, itemId, options.count or 0) + if not token then + return false + end + + return true +end + local function handleInventoryItemClick(button) local action, botName = getInventoryItemActionState() local item = button and button.item or nil @@ -415,6 +451,45 @@ local function handleInventoryItemClick(button) return end + if action == "bank" then + if runBridgeInventoryItemAction("BANK_DEPOSIT", button, botName) then + return + end + + sendInventoryItemCommand("bank", button, botName, { + postActionRefresh = true, + refreshDelay = 0.45, + followupRefreshDelay = 1.20, + }) + return + end + + if action == "gb" then + if runBridgeInventoryItemAction("GBANK_DEPOSIT", button, botName) then + return + end + + sendInventoryItemCommand("gb", button, botName, { + postActionRefresh = true, + refreshDelay = 0.45, + followupRefreshDelay = 1.20, + }) + return + end + + if action == "b" then + if runBridgeInventoryItemAction("BUY_ITEM", button, botName, { count = 1 }) then + return + end + + sendInventoryItemCommand("b", button, botName, { + postActionRefresh = true, + refreshDelay = 0.45, + followupRefreshDelay = 1.20, + }) + return + end + if action == "u" then registerInventoryPendingConsume(botName, item, 1) sendInventoryItemCommand(action, button, botName, { @@ -440,6 +515,87 @@ local function handleInventoryItemClick(button) }) end +local function getInventoryItemActionLabel(action) + action = tostring(action or "") + return inventoryItemL("inventory.action." .. action, action) +end + +local function getInventoryItemActionReason(reason) + reason = tostring(reason or "") + if reason == "" or reason == "OK" then + return "" + end + + return inventoryItemL("inventory.item_action.reason." .. reason, reason) +end + +function MultiBot.OnBridgeInventoryItemActionResult(botName, action, itemId, result, reason, moved) + local actionLabel = getInventoryItemActionLabel(action) + local itemName = tostring(itemId or "") + if GetItemInfo then + itemName = GetItemInfo(tonumber(itemId or 0) or 0) or itemName + end + + if result == "OK" then + addInventorySystemMessage(string.format( + inventoryItemL("inventory.item_action.ok", "%s: %s x%d."), + actionLabel, + itemName, + tonumber(moved or 0) or 0 + )) + + requestInventoryPostActionRefresh(botName, 0.45, 1.20) + if MultiBot.RefreshBotBank and (action == "BANK_DEPOSIT" or action == "BANK_WITHDRAW") then + MultiBot.RefreshBotBank(botName, 0.65) + end + if action == "GBANK_DEPOSIT" and MultiBot.RefreshBotGuildBank then + MultiBot.RefreshBotGuildBank(botName, 0.65) + end + if action == "BUY_ITEM" and MultiBot.professionRecipeFrame and MultiBot.professionRecipeFrame:IsShown() + and MultiBot.professionRecipeFrame.botName == botName + and MultiBot.professionRecipeFrame.skill + and MultiBot.Comm and MultiBot.Comm.RequestProfessionRecipes then + local skillId = tonumber(MultiBot.professionRecipeFrame.skill.skillId or 0) or 0 + if MultiBot.professionRecipeFrame.status then + MultiBot.professionRecipeFrame.status:SetText(inventoryItemL("inventory.item_action.buy.ok", "Purchase completed.")) + end + if skillId > 0 then + if type(MultiBot.TimerAfter) == "function" then + MultiBot.TimerAfter(0.75, function() + MultiBot.Comm.RequestProfessionRecipes(botName, skillId) + end) + else + MultiBot.Comm.RequestProfessionRecipes(botName, skillId) + end + end + end + return + end + + local reasonText = getInventoryItemActionReason(reason) + if action == "BUY_ITEM" and MultiBot.professionRecipeFrame and MultiBot.professionRecipeFrame:IsShown() + and MultiBot.professionRecipeFrame.botName == botName + and MultiBot.professionRecipeFrame.status then + MultiBot.professionRecipeFrame.status:SetText(string.format( + inventoryItemL("inventory.item_action.buy.err", "Purchase failed: %s"), + reasonText ~= "" and reasonText or tostring(reason or "") + )) + end + + if reasonText ~= "" then + addInventorySystemMessage(string.format( + inventoryItemL("inventory.item_action.err", "%s failed: %s"), + actionLabel, + reasonText + )) + else + addInventorySystemMessage(string.format( + inventoryItemL("inventory.item_action.failed", "%s failed."), + actionLabel + )) + end +end + MultiBot.InventoryAddItem = function(frame, itemInfo) if not frame then return nil