From 61bb1d9d8e0b24f600f5733ef80aeba06e3b2bdc Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sat, 27 Dec 2025 01:59:14 +0400 Subject: [PATCH 01/26] feat: mailbox bug --- Core/Database.lua | 21 +++ Core/Events.lua | 8 ++ Core/Init.lua | 2 + Core/Main.lua | 7 + Data/MailboxScanner.lua | 126 +++++++++++++++++ Guda.toc | 3 + UI/BagFrame.lua | 113 +++++++++++++++ UI/BagFrame.xml | 46 ++++++ UI/BankFrame.xml | 46 ++++++ UI/ItemButton.lua | 38 ++++- UI/MailboxFrame.lua | 302 ++++++++++++++++++++++++++++++++++++++++ UI/MailboxFrame.xml | 244 ++++++++++++++++++++++++++++++++ 12 files changed, 951 insertions(+), 5 deletions(-) create mode 100644 Data/MailboxScanner.lua create mode 100644 UI/MailboxFrame.lua create mode 100644 UI/MailboxFrame.xml diff --git a/Core/Database.lua b/Core/Database.lua index 533b830..aa1d9b7 100644 --- a/Core/Database.lua +++ b/Core/Database.lua @@ -131,6 +131,7 @@ function DB:Initialize() money = 0, bags = {}, bank = {}, + mailbox = {}, -- Add mailbox storage equipped = {}, -- Add equipped items storage character = {}, -- Add character info storage lastUpdate = time(), @@ -153,6 +154,10 @@ function DB:Initialize() char.character = {} addon:Debug("Added character field to existing character") end + if not char.mailbox then + char.mailbox = {} + addon:Debug("Added mailbox field to existing character") + end end addon:Debug("Database initialized for %s", fullName) @@ -215,6 +220,16 @@ function DB:SaveMoney(copper) end end +-- Save mailbox data +function DB:SaveMailbox(mailboxData) + local char = self:GetCurrentCharacter() + if char then + char.mailbox = mailboxData + char.lastUpdate = time() + addon:Debug("Saved mailbox data") + end +end + -- Get all characters (optionally filter by faction and/or realm) function DB:GetAllCharacters(sameFactionOnly, currentRealmOnly) local chars = {} @@ -256,6 +271,12 @@ function DB:GetCharacterBank(fullName) return char and char.bank or {} end +-- Get character's mailbox +function DB:GetCharacterMailbox(fullName) + local char = Guda_DB.characters[fullName] + return char and char.mailbox or {} +end + -- Get character's equipped items function DB:GetCharacterEquipped(fullName) local char = Guda_DB.characters[fullName] diff --git a/Core/Events.lua b/Core/Events.lua index ec4b7a4..b690f16 100644 --- a/Core/Events.lua +++ b/Core/Events.lua @@ -79,3 +79,11 @@ end function Events:OnPlayerLogout(callback, owner) self:Register("PLAYER_LOGOUT", callback, owner) end + +function Events:OnMailShow(callback, owner) + self:Register("MAIL_SHOW", callback, owner) +end + +function Events:OnMailClosed(callback, owner) + self:Register("MAIL_CLOSED", callback, owner) +end diff --git a/Core/Init.lua b/Core/Init.lua index aa68ab4..9066e04 100644 --- a/Core/Init.lua +++ b/Core/Init.lua @@ -102,11 +102,13 @@ addon.Modules = { Tooltip = {}, BagScanner = {}, BankScanner = {}, + MailboxScanner = {}, MoneyTracker = {}, EquipmentScanner = {}, SortEngine = {}, BagFrame = {}, BankFrame = {}, + MailboxFrame = {}, QuestItemBar = {}, TrackedItemBar = {}, SettingsPopup = {}, diff --git a/Core/Main.lua b/Core/Main.lua index 9316884..2025155 100644 --- a/Core/Main.lua +++ b/Core/Main.lua @@ -18,6 +18,7 @@ function Main:Initialize() -- Initialize scanners addon.Modules.BagScanner:Initialize() addon.Modules.BankScanner:Initialize() + addon.Modules.MailboxScanner:Initialize() addon.Modules.MoneyTracker:Initialize() addon.Modules.EquipmentScanner:Initialize() @@ -25,6 +26,7 @@ function Main:Initialize() addon:Print("Initializing UI...") addon.Modules.BagFrame:Initialize() addon.Modules.BankFrame:Initialize() + addon.Modules.MailboxFrame:Initialize() addon:Debug("Checking QuestItemBar module...") if addon.Modules.QuestItemBar and addon.Modules.QuestItemBar.isLoaded then @@ -103,6 +105,10 @@ function Main:SetupSlashCommands() -- Toggle bank addon.Modules.BankFrame:Toggle() + elseif msg == "mail" or msg == "mailbox" then + -- Toggle mailbox + addon.Modules.MailboxFrame:Toggle() + elseif msg == "sort" then -- Sort bags addon.Modules.SortEngine:SortBags() @@ -143,6 +149,7 @@ function Main:SetupSlashCommands() addon:Print("Commands:") addon:Print("/guda - Toggle bags") addon:Print("/guda bank - Toggle bank") + addon:Print("/guda mail - Toggle mailbox") addon:Print("/guda sort - Sort bags") addon:Print("/guda sortbank - Sort bank") addon:Print("/guda track - Toggle item tracking") diff --git a/Data/MailboxScanner.lua b/Data/MailboxScanner.lua new file mode 100644 index 0000000..1c484fe --- /dev/null +++ b/Data/MailboxScanner.lua @@ -0,0 +1,126 @@ +-- Guda Mailbox Scanner +-- Scans and stores mailbox contents + +local addon = Guda + +local MailboxScanner = {} +addon.Modules.MailboxScanner = MailboxScanner + +local mailboxOpen = false + +-- Scan all mailbox items and return data +function MailboxScanner:ScanMailbox() + if not mailboxOpen then + addon:Debug("Cannot scan mailbox - not open") + return {} + end + + local mailboxData = {} + local numItems = GetInboxNumItems() + + for i = 1, numItems do + mailboxData[i] = self:ScanMailItem(i) + end + + return mailboxData +end + +-- Scan a single mail item +function MailboxScanner:ScanMailItem(index) + -- GetInboxItem(index) returns: packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply, isGM + local packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply, isGM = GetInboxItem(index) + + local itemData = nil + if hasItem then + addon:Debug("Scanning mail item %d", index) + -- In 1.12.1, GetInboxItemLink(index) returns the link + local itemLink = GetInboxItemLink(index) + + -- GetInboxItemInfo(index) returns: name, texture, count, quality, canUse + local infoName, infoTexture, infoCount, infoQuality, infoCanUse = GetInboxItemInfo(index) + + addon:Debug("Mail item %d: infoName=%s, infoTexture=%s", index, tostring(infoName), tostring(infoTexture)) + + -- Even if link is missing (not cached), we can store what we have from GetInboxItemInfo + itemData = { + link = itemLink, + texture = infoTexture or packageIcon or "Interface\\Icons\\INV_Misc_Bag_08", + count = infoCount or 1, + quality = infoQuality or 0, + name = infoName or subject or "Unknown Item", + } + + -- If we have a link, try to get more detailed info + if itemLink then + local itemName, link, itemQuality, iLevel, itemCategory, itemType, itemStackCount, itemSubType, itemTexture, itemEquipLoc, itemSellPrice = addon.Modules.Utils:GetItemInfo(itemLink) + if itemName then + itemData.name = itemName + itemData.quality = itemQuality or itemData.quality + itemData.iLevel = iLevel + itemData.type = itemType + itemData.class = itemCategory + itemData.subclass = itemSubType + itemData.equipSlot = itemEquipLoc + if itemTexture then itemData.texture = itemTexture end + end + end + end + + return { + sender = sender, + subject = subject, + money = money, + CODAmount = CODAmount, + daysLeft = daysLeft, + hasItem = hasItem, + item = itemData, + wasRead = wasRead, + packageIcon = packageIcon, + } +end + +-- Save current mailbox to database +function MailboxScanner:SaveToDatabase() + if not mailboxOpen then + return + end + + local mailboxData = self:ScanMailbox() + addon.Modules.DB:SaveMailbox(mailboxData) + addon:Debug("Mailbox data saved") +end + +-- Initialize mailbox scanner +function MailboxScanner:Initialize() + -- Mailbox opened + addon.Modules.Events:OnMailShow(function() + mailboxOpen = true + addon:Debug("Mailbox opened") + + -- Delay scan slightly to ensure item info is available + local frame = CreateFrame("Frame") + local elapsed = 0 + frame:SetScript("OnUpdate", function() + elapsed = elapsed + arg1 + if elapsed >= 0.5 then + frame:SetScript("OnUpdate", nil) + if mailboxOpen then + MailboxScanner:SaveToDatabase() + end + end + end) + end, "MailboxScanner") + + -- Mailbox closed + addon.Modules.Events:OnMailClosed(function() + -- Final save on close + self:SaveToDatabase() + mailboxOpen = false + addon:Debug("Mailbox closed") + end, "MailboxScanner") +end + +-- Check if mailbox is currently open +function MailboxScanner:IsMailboxOpen() + return mailboxOpen +end diff --git a/Guda.toc b/Guda.toc index 2902f4c..8dddb87 100644 --- a/Guda.toc +++ b/Guda.toc @@ -16,6 +16,7 @@ Core\Tooltip.lua Data\BagScanner.lua Data\BankScanner.lua +Data\MailboxScanner.lua Data\MoneyTracker.lua Data\EquipmentScanner.lua @@ -25,12 +26,14 @@ Sorting\SortEngine.lua UI\ItemButton.lua UI\BagFrame.lua UI\BankFrame.lua +UI\MailboxFrame.lua UI\QuestItemBar.lua UI\TrackedItemBar.lua UI\SettingsPopup.lua UI\ItemButton.xml UI\BagFrame.xml UI\BankFrame.xml +UI\MailboxFrame.xml UI\SettingsPopup.xml Core\Main.lua diff --git a/UI/BagFrame.lua b/UI/BagFrame.lua index 3e30964..7e01ff7 100644 --- a/UI/BagFrame.lua +++ b/UI/BagFrame.lua @@ -1578,6 +1578,119 @@ local function HideCharacterDropdown() end end +-- Toggle mail dropdown +function Guda_BagFrame_ToggleMailDropdown(button) +-- Hide character dropdown if it's shown + if characterDropdown and characterDropdown:IsShown() then + characterDropdown:Hide() + end + + if bankDropdown and bankDropdown:IsShown() then + bankDropdown:Hide() + end + + if mailDropdown and mailDropdown:IsShown() then + mailDropdown:Hide() + return + end + + if not mailDropdown then + -- Create dropdown frame + mailDropdown = CreateFrame("Frame", "Guda_MailDropdown", UIParent) + mailDropdown:SetFrameStrata("DIALOG") + mailDropdown:SetWidth(200) + mailDropdown:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", + tile = true, + tileSize = 16, + edgeSize = 16, + insets = { left = 4, right = 4, top = 4, bottom = 4 } + }) + mailDropdown:SetBackdropColor(0, 0, 0, 0.95) + mailDropdown:EnableMouse(true) + mailDropdown:Hide() + + mailDropdown.buttons = {} + end + + -- Position dropdown below the button + mailDropdown:ClearAllPoints() + mailDropdown:SetPoint("TOPLEFT", button, "BOTTOMLEFT", 0, -2) + -- Clear existing buttons + for _, btn in ipairs(mailDropdown.buttons) do + btn:Hide() + end + mailDropdown.buttons = {} + + -- Get all characters on current realm + local chars = addon.Modules.DB:GetAllCharacters(false, true) + + local yOffset = -8 + + -- Add character buttons + for _, char in ipairs(chars) do + -- Capture variables in local scope for closure + local charFullName = char.fullName + local charName = char.name + local charClassToken = char.classToken + + local charButton = CreateFrame("Button", nil, mailDropdown) + charButton:SetWidth(188) + charButton:SetHeight(20) + charButton:SetPoint("TOP", mailDropdown, "TOP", 0, yOffset) + + -- Button background on hover + local charBg = charButton:CreateTexture(nil, "BACKGROUND") + charBg:SetAllPoints() + charBg:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") + charBg:SetBlendMode("ADD") + charBg:SetAlpha(0) + + -- Get class color + local classColor = charClassToken and RAID_CLASS_COLORS[charClassToken] + local r, g, b = 1, 1, 1 + if classColor then + r, g, b = classColor.r, classColor.g, classColor.b + end + + -- Button text + local charText = charButton:CreateFontString(nil, "ARTWORK", "GameFontHighlight") + charText:SetPoint("LEFT", charButton, "LEFT", 8, 0) + charText:SetText(charName) + charText:SetTextColor(r, g, b) + + -- Button scripts + charButton:SetScript("OnEnter", function() + charBg:SetAlpha(0.3) + end) + charButton:SetScript("OnLeave", function() + charBg:SetAlpha(0) + end) + charButton:SetScript("OnClick", function() + if charFullName then + -- Show mailbox for this character + addon.Modules.MailboxFrame:ShowCharacter(charFullName) + if not Guda_MailboxFrame:IsShown() then + Guda_MailboxFrame:Show() + end + mailDropdown:Hide() + else + addon:Print("Error: Character fullName is nil") + end + end) + + table.insert(mailDropdown.buttons, charButton) + yOffset = yOffset - 20 + end + + -- Set dropdown height based on content + mailDropdown:SetHeight(math.abs(yOffset) + 8) + + -- Show dropdown + mailDropdown:Show() +end + -- Toggle bank dropdown function Guda_BagFrame_ToggleBankDropdown(button) -- Hide character dropdown if it's shown diff --git a/UI/BagFrame.xml b/UI/BagFrame.xml index aa887a5..76d6b7a 100644 --- a/UI/BagFrame.xml +++ b/UI/BagFrame.xml @@ -248,6 +248,52 @@ + + + diff --git a/UI/BankFrame.xml b/UI/BankFrame.xml index 0d22445..5db740e 100644 --- a/UI/BankFrame.xml +++ b/UI/BankFrame.xml @@ -107,6 +107,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Guda_MailboxFrame_OnSearchTextChanged() + + + this:ClearFocus() + + + this:ClearFocus() + + + if this:GetText() == "Search mailbox..." then + this:SetText("") + this:SetTextColor(1, 1, 1, 1) + end + if Guda_MailboxClickCatcher then + Guda_MailboxClickCatcher:Show() + end + + + if this:GetText() == "" then + this:SetText("Search mailbox...") + this:SetTextColor(0.5, 0.5, 0.5, 1) + end + if Guda_MailboxClickCatcher then + Guda_MailboxClickCatcher:Hide() + end + + + + + + + this:SetBackdropBorderColor(0.4, 0.4, 0.4) + this:SetBackdropColor(0.15, 0.15, 0.15, 0.5) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Guda_MailboxFrame_OnLoad(this) + + + Guda_MailboxFrame_OnShow(this) + + + this:StartMoving() + + + this:StopMovingOrSizing() + + + + From 6ca33c1c9f242c5ff3edcd1d8f1003fb62d35c45 Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sat, 27 Dec 2025 17:31:41 +0400 Subject: [PATCH 02/26] fix: mailbox tooltip --- Core/Utils.lua | 30 ++++- Data/MailboxScanner.lua | 80 +++++++------ UI/ItemButton.lua | 71 +++++++++--- UI/MailboxFrame.lua | 241 ++++++++++++++++++++++------------------ UI/MailboxFrame.xml | 113 +++++++++++++++++++ 5 files changed, 375 insertions(+), 160 deletions(-) diff --git a/Core/Utils.lua b/Core/Utils.lua index 4997fe8..05cd468 100644 --- a/Core/Utils.lua +++ b/Core/Utils.lua @@ -95,6 +95,31 @@ function Utils:GetItemInfo(itemLink) return nil end +-- Create a hidden tooltip for scanning +local scanTooltip = CreateFrame("GameTooltip", "GudaBagScanTooltip", nil, "GameTooltipTemplate") +scanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE") + +-- Get item link from mailbox attachment (WoW 1.12.1 workaround) +function Utils:GetInboxItemLink(index, itemIndex) + -- In 1.12.1, GameTooltip:GetHyperlink() does not exist. + -- Actually, in 1.12.1, there's no official API to get the link from a mailbox item + -- if GetInboxItemLink(index) is not available (it's often NOT available in Vanilla). + + -- Let's try to use GetItemInfo(name) as the primary way. + local name = GetInboxItem(index, itemIndex) + if name then + local _, link = GetItemInfo(name) + if link then + return link + end + end + + -- Fallback: If we still don't have it, we might try to scan the tooltip for the name + -- but GetInboxItem already gave us the name. + + return nil +end + -- Get quality color function Utils:GetQualityColor(quality) local color = addon.Constants.QUALITY_COLORS[quality] or addon.Constants.QUALITY_COLORS[1] @@ -156,12 +181,7 @@ function Utils:FormatTimeAgo(timestamp) end -- Create hidden tooltip for scanning (only once) -local scanTooltip = nil local function GetScanTooltip() - if not scanTooltip then - scanTooltip = CreateFrame("GameTooltip", "GudaBagScanTooltip", nil, "GameTooltipTemplate") - scanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE") - end return scanTooltip end diff --git a/Data/MailboxScanner.lua b/Data/MailboxScanner.lua index 1c484fe..0c2e982 100644 --- a/Data/MailboxScanner.lua +++ b/Data/MailboxScanner.lua @@ -27,42 +27,40 @@ end -- Scan a single mail item function MailboxScanner:ScanMailItem(index) - -- GetInboxItem(index) returns: packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply, isGM - local packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply, isGM = GetInboxItem(index) - - local itemData = nil + -- GetInboxHeaderInfo(index) returns: packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply, isGM + local packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply, isGM = GetInboxHeaderInfo(index) + + local items = {} if hasItem then - addon:Debug("Scanning mail item %d", index) - -- In 1.12.1, GetInboxItemLink(index) returns the link - local itemLink = GetInboxItemLink(index) - - -- GetInboxItemInfo(index) returns: name, texture, count, quality, canUse - local infoName, infoTexture, infoCount, infoQuality, infoCanUse = GetInboxItemInfo(index) - - addon:Debug("Mail item %d: infoName=%s, infoTexture=%s", index, tostring(infoName), tostring(infoTexture)) + -- Turtle WoW supports up to 12 attachments per mail + -- GetInboxItem(index, itemIndex) returns: name, texture, count, quality, canUse + local name, texture, count, quality, canUse = GetInboxItem(index, itemIndex) + if name then + local itemLink = addon.Modules.Utils:GetInboxItemLink(index, itemIndex) - -- Even if link is missing (not cached), we can store what we have from GetInboxItemInfo - itemData = { - link = itemLink, - texture = infoTexture or packageIcon or "Interface\\Icons\\INV_Misc_Bag_08", - count = infoCount or 1, - quality = infoQuality or 0, - name = infoName or subject or "Unknown Item", - } + local itemData = { + link = itemLink, + texture = texture or "Interface\\Icons\\INV_Misc_Bag_08", + count = count or 1, + quality = quality or 0, + name = name, + } - -- If we have a link, try to get more detailed info - if itemLink then - local itemName, link, itemQuality, iLevel, itemCategory, itemType, itemStackCount, itemSubType, itemTexture, itemEquipLoc, itemSellPrice = addon.Modules.Utils:GetItemInfo(itemLink) - if itemName then - itemData.name = itemName - itemData.quality = itemQuality or itemData.quality - itemData.iLevel = iLevel - itemData.type = itemType - itemData.class = itemCategory - itemData.subclass = itemSubType - itemData.equipSlot = itemEquipLoc - if itemTexture then itemData.texture = itemTexture end - end + -- If we have a link, try to get more detailed info + if itemLink then + local itemName, link, itemQuality, iLevel, itemCategory, itemType, itemStackCount, itemSubType, itemTexture, itemEquipLoc, itemSellPrice = addon.Modules.Utils:GetItemInfo(itemLink) + if itemName then + itemData.name = itemName + itemData.quality = itemQuality or itemData.quality + itemData.iLevel = iLevel + itemData.type = itemType + itemData.class = itemCategory + itemData.subclass = itemSubType + itemData.equipSlot = itemEquipLoc + if itemTexture then itemData.texture = itemTexture end + end + end + table.insert(items, itemData) end end @@ -72,8 +70,9 @@ function MailboxScanner:ScanMailItem(index) money = money, CODAmount = CODAmount, daysLeft = daysLeft, - hasItem = hasItem, - item = itemData, + hasItem = (table.getn(items) > 0), + item = items[1], + items = items, wasRead = wasRead, packageIcon = packageIcon, } @@ -111,6 +110,17 @@ function MailboxScanner:Initialize() end) end, "MailboxScanner") + -- Register for GET_ITEM_INFO_RECEIVED to refresh if item data arrives + addon.Modules.Events:Register("GET_ITEM_INFO_RECEIVED", function() + if mailboxOpen then + addon:Debug("GET_ITEM_INFO_RECEIVED: Refreshing mailbox") + MailboxScanner:SaveToDatabase() + end + end, "MailboxScanner") + + -- Register for UI_ERROR_MESSAGE to handle "item not found" situations if needed + -- (Some items might not be in cache and fail silently otherwise) + -- Mailbox closed addon.Modules.Events:OnMailClosed(function() -- Final save on close diff --git a/UI/ItemButton.lua b/UI/ItemButton.lua index fd20345..0f44585 100644 --- a/UI/ItemButton.lua +++ b/UI/ItemButton.lua @@ -11,6 +11,8 @@ scanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE") -- Check if an item is a quest item by scanning its tooltip -- Check if an item is a quest item by scanning its tooltip and determine type local function IsQuestItem(bagID, slotID, isBank) + bagID = tonumber(bagID) + slotID = tonumber(slotID) if not bagID or not slotID then return false end scanTooltip:ClearLines() @@ -117,6 +119,8 @@ end -- Scan tooltip for red text that is NOT a durability line local function IsItemUnusable(bagID, slotID, isBank) + bagID = tonumber(bagID) + slotID = tonumber(slotID) if not bagID or not slotID then return false end -- Some clients require SetOwner before every SetBagItem/SetInventoryItem to populate lines @@ -425,6 +429,10 @@ end -- Set item data function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCharName, matchesFilter, isReadOnly) + -- Proactively convert to number to avoid comparisons with strings in downstream functions + bagID = tonumber(bagID) + slotID = tonumber(slotID) + -- Proactively clear any previous cooldown overlay state before reassigning this pooled button do local cd = getglobal(self:GetName().."Cooldown") or self.cooldown @@ -459,6 +467,8 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha self.otherChar = otherCharName self.isReadOnly = isReadOnly or false -- Track if this is read-only mode self.isMail = false -- Clear mailbox flag by default + self.mailIndex = nil + self.mailItemIndex = nil -- Re-register for drag/drop every time (crucial for button reuse in Classic/Vanilla) if not self.isReadOnly and not self.otherChar then @@ -486,6 +496,8 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha matchesFilter = true end + self.mailData = nil -- Clear mail metadata by default + -- Use Blizzard's default count fontstring (ContainerFrameItemButtonTemplate creates $parentCount) local countText = getglobal(self:GetName().."Count") local emptySlotBg = getglobal(self:GetName().."_EmptySlotBg") @@ -947,17 +959,7 @@ end -- OnEnter handler (show tooltip) function Guda_ItemButton_OnEnter(self) - -- DETACH from Blizzard's internal tooltip logic for mailbox items - if self.isMail or this.isMail or (this:GetParent() and this:GetParent():GetName() == "Guda_MailboxFrame_ItemContainer") then - return - end - - -- Prevent Blizzard's ContainerFrameItemButton_OnEnter from running if it somehow got through - if not self.bagID or self.bagID == -100 then - return - end - --- Highlight the corresponding bag button in the footer (works for empty and filled slots) + -- Highlight the corresponding bag button in the footer (works for empty and filled slots) if not self.otherChar and self.bagID then if self.isBank then -- Bank item - highlight bank bag button @@ -969,7 +971,7 @@ function Guda_ItemButton_OnEnter(self) end -- Early return for empty slots (no tooltip needed) - if not self.hasItem then + if not self.hasItem and not self.isMail then return end @@ -977,7 +979,50 @@ function Guda_ItemButton_OnEnter(self) -- important position of tooltip GameTooltip:SetPoint("BOTTOMRIGHT", self, "TOPLEFT", 10, 0) - if self.otherChar or self.isReadOnly then + -- Mailbox tooltip handling + if self.isMail then + local currentPlayerName = addon.Modules.DB:GetPlayerFullName() + if (not self.otherChar or self.otherChar == currentPlayerName) and self.mailIndex then + -- Live mailbox for current character + GameTooltip:SetInboxItem(self.mailIndex, self.mailItemIndex or 1) + elseif self.itemData and self.itemData.link then + -- Read-only / other character mailbox + GameTooltip.GudaViewedCharacter = self.otherChar + GameTooltip:SetHyperlink(self.itemData.link) + elseif self.itemData and self.itemData.name then + -- Money or generic mail + GameTooltip:AddLine(self.itemData.name, 1, 1, 1) + end + + -- Add mailbox metadata if available + local mailData = self.mailData + if mailData then + -- If we already added a line (for money/generic), or SetHyperlink/SetInboxItem added lines, + -- we might want a separator if we're adding sender info. + if GameTooltip:NumLines() > 0 then + GameTooltip:AddLine(" ") + end + + if mailData.sender then + GameTooltip:AddLine("From: " .. mailData.sender, 1, 1, 1) + end + if mailData.subject then + GameTooltip:AddLine("Subject: " .. mailData.subject, 1, 1, 0.8) + end + if (mailData.money or 0) > 0 and not (self.itemData and self.itemData.name == "Money") then + GameTooltip:AddLine("Money: " .. addon.Modules.Utils:FormatMoney(mailData.money), 1, 1, 1) + end + if (mailData.CODAmount or 0) > 0 then + GameTooltip:AddLine("COD: " .. addon.Modules.Utils:FormatMoney(mailData.CODAmount), 1, 0, 0) + end + if mailData.daysLeft then + GameTooltip:AddLine("Days left: " .. math.floor(mailData.daysLeft), 0.5, 0.5, 0.5) + end + end + + GameTooltip:Show() + return + elseif self.otherChar or self.isReadOnly then GameTooltip.GudaViewedCharacter = self.otherChar if self.itemData and self.itemData.link then GameTooltip:SetHyperlink(self.itemData.link) diff --git a/UI/MailboxFrame.lua b/UI/MailboxFrame.lua index f55cf51..d13ab5b 100644 --- a/UI/MailboxFrame.lua +++ b/UI/MailboxFrame.lua @@ -10,6 +10,9 @@ local currentViewChar = nil local searchText = "" local isReadOnlyMode = true -- Mailbox is always read-only in this addon (viewing offline data) local itemButtons = {} +local mailboxRows = {} +local currentPage = 1 +local ITEMS_PER_PAGE = 7 local mailboxClickCatcher = nil -- OnLoad @@ -70,9 +73,23 @@ end -- Show specific character's mailbox function MailboxFrame:ShowCharacter(fullName) currentViewChar = fullName + currentPage = 1 self:Update() end +-- Pagination +function MailboxFrame:NextPage() + currentPage = currentPage + 1 + self:Update() +end + +function MailboxFrame:PrevPage() + if currentPage > 1 then + currentPage = currentPage - 1 + self:Update() + end +end + -- Initialize module function MailboxFrame:Initialize() -- Register events if needed @@ -105,13 +122,38 @@ function MailboxFrame:Update() matchesSearch = true elseif mail.subject and string.find(string.lower(mail.subject), searchText) then matchesSearch = true + elseif mail.items then + for _, item in ipairs(mail.items) do + if item.name and string.find(string.lower(item.name), searchText) then + matchesSearch = true + break + end + end elseif mail.item and mail.item.name and string.find(string.lower(mail.item.name), searchText) then matchesSearch = true end end if matchesSearch then - table.insert(filteredItems, mail) + -- Flatten mails with multiple items so they all show up in the grid + if mail.items and table.getn(mail.items) > 1 then + for itemIndex, item in ipairs(mail.items) do + local mailCopy = {} + for k, v in pairs(mail) do mailCopy[k] = v end + mailCopy.item = item + mailCopy.mailIndex = i + mailCopy.itemIndex = itemIndex + -- Clear items list in copy to avoid recursive flattening + mailCopy.items = nil + table.insert(filteredItems, mailCopy) + end + else + local mailCopy = {} + for k, v in pairs(mail) do mailCopy[k] = v end + mailCopy.mailIndex = i + mailCopy.itemIndex = 1 + table.insert(filteredItems, mailCopy) + end end end @@ -135,61 +177,76 @@ function MailboxFrame:Update() MoneyFrame_Update("Guda_MailboxFrame_MoneyFrame", totalMoney) end --- Display mailbox items in a grid +-- Display mailbox items in a list function MailboxFrame:DisplayItems(items, charFullName) local container = getglobal("Guda_MailboxFrame_ItemContainer") - -- Explicitly set container ID to -100 to avoid being picked up as a bag - if container.SetID then container:SetID(-100) end - local columns = 10 - local buttonSize = 34 - local spacing = 2 - - -- Hide all existing buttons first - for _, button in pairs(itemButtons) do - button:Hide() - button.inUse = false + -- Hide all existing rows first + for _, row in pairs(mailboxRows) do + row:Hide() end - local row = 0 - local col = 0 + local totalItems = table.getn(items) + local totalPages = math.max(1, math.ceil(totalItems / ITEMS_PER_PAGE)) - for i, mail in ipairs(items) do - local button = itemButtons[i] - if not button then - button = Guda_GetItemButton(container) - itemButtons[i] = button + if currentPage > totalPages then + currentPage = totalPages + end + + local startIndex = (currentPage - 1) * ITEMS_PER_PAGE + 1 + local endIndex = math.min(startIndex + ITEMS_PER_PAGE - 1, totalItems) + + local rowIndex = 1 + for i = startIndex, endIndex do + local mail = items[i] + local row = mailboxRows[rowIndex] + if not row then + row = CreateFrame("Frame", "Guda_MailboxRow" .. rowIndex, container, "Guda_MailboxRowTemplate") + mailboxRows[rowIndex] = row end - - -- Consistently set button size for the mailbox grid - button:SetWidth(buttonSize) - button:SetHeight(buttonSize) - - button:ClearAllPoints() - button:SetPoint("TOPLEFT", container, "TOPLEFT", col * (buttonSize + spacing), -row * (buttonSize + spacing)) - -- Set button data - button.isBank = false - button.otherChar = charFullName - button.isMail = true -- Custom flag for mailbox items - button.inUse = true + row:ClearAllPoints() + row:SetPoint("TOPLEFT", container, "TOPLEFT", 0, -(rowIndex - 1) * 55) + + -- Fill row data + getglobal(row:GetName() .. "_Sender"):SetText(mail.sender or "Unknown") + getglobal(row:GetName() .. "_Subject"):SetText(mail.subject or "No Subject") + + local expireText = "" + if mail.daysLeft then + if mail.daysLeft < 1 then + expireText = string.format("|cffff0000%dh|r", math.floor(mail.daysLeft * 24)) + else + expireText = string.format("%dd", math.floor(mail.daysLeft)) + end + end + getglobal(row:GetName() .. "_ExpireTime"):SetText(expireText) + + -- Item Button + local itemButton = getglobal(row:GetName() .. "_ItemButton") + -- Re-assign shared namespace function if needed, but it should be available globally or via addon + + itemButton.isBank = false + itemButton.otherChar = charFullName + itemButton.mailData = mail + itemButton.isMail = true + itemButton.mailIndex = mail.mailIndex + itemButton.mailItemIndex = mail.itemIndex or 1 if mail.item and (mail.item.texture or mail.item.link) then - Guda_ItemButton_SetItem(button, nil, nil, mail.item, false, charFullName, true, true) - -- Re-enforce size because SetItem overrides it with global settings - button:SetWidth(buttonSize) - button:SetHeight(buttonSize) - button.isMail = true -- Re-apply after SetItem clears it + Guda_ItemButton_SetItem(itemButton, nil, nil, mail.item, false, charFullName, true, true) + itemButton.isMail = true + itemButton.mailData = mail + itemButton.mailIndex = mail.mailIndex + itemButton.mailItemIndex = mail.itemIndex or 1 else - -- Use SetItem with nil itemData to clear the button properly first - Guda_ItemButton_SetItem(button, nil, nil, nil, false, charFullName, true, true) - button:SetWidth(buttonSize) - button:SetHeight(buttonSize) - button.isMail = true -- Re-apply after SetItem clears it - - -- Then show our custom icon for money/mail - button.itemData = nil - local icon = getglobal(button:GetName().."IconTexture") or getglobal(button:GetName().."Icon") + Guda_ItemButton_SetItem(itemButton, nil, nil, nil, false, charFullName, true, true) + itemButton.isMail = true + itemButton.mailData = mail + itemButton.mailIndex = mail.mailIndex + itemButton.mailItemIndex = 1 + + local icon = getglobal(itemButton:GetName().."IconTexture") or getglobal(itemButton:GetName().."Icon") if icon then if (mail.money or 0) > 0 then icon:SetTexture("Interface\\Icons\\INV_Misc_Coin_01") @@ -201,73 +258,42 @@ function MailboxFrame:DisplayItems(items, charFullName) icon:SetTexCoord(0.08, 0.92, 0.08, 0.92) icon:Show() end - - if button.qualityBorder then button.qualityBorder:Hide() end + if itemButton.qualityBorder then itemButton.qualityBorder:Hide() end end - - -- Custom Tooltip for mail items - button.mailData = mail -- Store data on button to avoid closure issues in Lua 5.0 - button.isMail = true -- Redundant set to be safe - button:SetScript("OnEnter", function() - -- Force detachment from Blizzard logic inside the closure as well - if this.SetID then this:SetID(0) end - this.isMail = true - - local mailData = this.mailData - if not mailData then return end - - -- Explicitly use GameTooltip:ClearLines() and SetOwner to ensure a clean tooltip - GameTooltip:SetOwner(this, "ANCHOR_RIGHT") - GameTooltip:ClearLines() - - if mailData.item and mailData.item.link then - GameTooltip:SetHyperlink(mailData.item.link) - -- Add a gap if there's also sender/subject info to show - GameTooltip:AddLine(" ") - elseif mailData.item and mailData.item.name then - -- Fallback if we have item data but no link (e.g. not in cache yet) - GameTooltip:AddLine(mailData.item.name, 1, 1, 1) - GameTooltip:AddLine(" ") - end - - GameTooltip:AddLine("From: " .. (mailData.sender or "Unknown"), 1, 1, 1) - GameTooltip:AddLine("Subject: " .. (mailData.subject or "No Subject"), 1, 1, 0.8) - - if (mailData.money or 0) > 0 then - GameTooltip:AddLine("Money: " .. addon.Modules.Utils:FormatMoney(mailData.money), 1, 1, 1) - end - if (mailData.CODAmount or 0) > 0 then - GameTooltip:AddLine("COD: " .. addon.Modules.Utils:FormatMoney(mailData.CODAmount), 1, 0, 0) - end - - if mailData.daysLeft then - GameTooltip:AddLine("Days left: " .. math.floor(mailData.daysLeft), 0.5, 0.5, 0.5) - end - - GameTooltip:Show() - end) - button:SetScript("OnLeave", function() - GameTooltip:Hide() - end) - -- Mailbox is read-only, so no clicking/dragging - button:RegisterForClicks() - button:SetScript("OnClick", nil) - button:SetScript("OnDragStart", nil) - button:SetScript("OnReceiveDrag", nil) - - button:Show() - - col = col + 1 - if col >= columns then - col = 0 - row = row + 1 + -- Row Money Frame + local moneyFrame = getglobal(row:GetName() .. "_MoneyFrame") + if (mail.money or 0) > 0 then + MoneyFrame_Update(moneyFrame:GetName(), mail.money) + moneyFrame:Show() + else + moneyFrame:Hide() end + + row:Show() + rowIndex = rowIndex + 1 end - -- Adjust container height - local totalRows = row + (col > 0 and 1 or 0) - container:SetHeight(math.max(420, totalRows * (buttonSize + spacing))) + -- Update pagination buttons + local prevBtn = getglobal("Guda_MailboxFrame_PrevPageButton") + local nextBtn = getglobal("Guda_MailboxFrame_NextPageButton") + + if currentPage > 1 then + prevBtn:Enable() + else + prevBtn:Disable() + end + + if currentPage < totalPages then + nextBtn:Enable() + else + nextBtn:Disable() + end + + -- Update page text in footer + local footerFontString = getglobal("Guda_MailboxFrame_Footer_Text") + local footerText = footerFontString:GetText() or "" + footerFontString:SetText(footerText .. " (Page " .. currentPage .. " of " .. totalPages .. ")") end -- Search text changed @@ -279,6 +305,7 @@ function Guda_MailboxFrame_OnSearchTextChanged() else searchText = string.lower(text) end + currentPage = 1 MailboxFrame:Update() end diff --git a/UI/MailboxFrame.xml b/UI/MailboxFrame.xml index a2dc88d..619fdee 100644 --- a/UI/MailboxFrame.xml +++ b/UI/MailboxFrame.xml @@ -1,6 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -224,6 +300,43 @@ + + + + + From 5f0ade6013c4bb284659dc3108bd1b4208b1000d Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sat, 27 Dec 2025 17:56:03 +0400 Subject: [PATCH 03/26] fix: pagination --- UI/MailboxFrame.lua | 32 ++++++------------- UI/MailboxFrame.xml | 77 +++++++++++++++++---------------------------- 2 files changed, 37 insertions(+), 72 deletions(-) diff --git a/UI/MailboxFrame.lua b/UI/MailboxFrame.lua index d13ab5b..465f01a 100644 --- a/UI/MailboxFrame.lua +++ b/UI/MailboxFrame.lua @@ -114,6 +114,7 @@ function MailboxFrame:Update() -- Filter items based on search text local filteredItems = {} + local totalItems = table.getn(mailboxData) for i, mail in ipairs(mailboxData) do local matchesSearch = true if searchText ~= "" then @@ -158,27 +159,11 @@ function MailboxFrame:Update() end -- Display items - self:DisplayItems(filteredItems, charFullName) - - -- Update footer info - local totalItems = table.getn(mailboxData) - local displayedItems = table.getn(filteredItems) - local footerText = string.format("Items: %d", totalItems) - if searchText ~= "" then - footerText = string.format("Filtered: %d / %d", displayedItems, totalItems) - end - getglobal("Guda_MailboxFrame_Footer_Text"):SetText(footerText) - - -- Update money (total money in mailbox) - local totalMoney = 0 - for _, mail in ipairs(mailboxData) do - totalMoney = totalMoney + (mail.money or 0) - end - MoneyFrame_Update("Guda_MailboxFrame_MoneyFrame", totalMoney) + self:DisplayItems(filteredItems, charFullName, totalItems) end -- Display mailbox items in a list -function MailboxFrame:DisplayItems(items, charFullName) +function MailboxFrame:DisplayItems(items, charFullName, totalMails) local container = getglobal("Guda_MailboxFrame_ItemContainer") -- Hide all existing rows first @@ -260,7 +245,7 @@ function MailboxFrame:DisplayItems(items, charFullName) end if itemButton.qualityBorder then itemButton.qualityBorder:Hide() end end - + -- Row Money Frame local moneyFrame = getglobal(row:GetName() .. "_MoneyFrame") if (mail.money or 0) > 0 then @@ -290,10 +275,11 @@ function MailboxFrame:DisplayItems(items, charFullName) nextBtn:Disable() end - -- Update page text in footer - local footerFontString = getglobal("Guda_MailboxFrame_Footer_Text") - local footerText = footerFontString:GetText() or "" - footerFontString:SetText(footerText .. " (Page " .. currentPage .. " of " .. totalPages .. ")") + -- Update pagination text + local paginationText = getglobal("Guda_MailboxFrame_Pagination_Text") + if paginationText then + paginationText:SetText(string.format("%d/%d (items: %d)", currentPage, totalPages, totalMails or 0)) + end end -- Search text changed diff --git a/UI/MailboxFrame.xml b/UI/MailboxFrame.xml index 619fdee..88c7f10 100644 --- a/UI/MailboxFrame.xml +++ b/UI/MailboxFrame.xml @@ -244,8 +244,8 @@ - - + + @@ -256,49 +256,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -307,9 +264,9 @@ - + - + @@ -325,9 +282,9 @@ - + - + @@ -337,6 +294,28 @@ + + + + + + + + + + + + + + + + + + + + + + From a62d949f308b6124aa6368fda9d8c8619bcfbf26 Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sat, 27 Dec 2025 18:13:04 +0400 Subject: [PATCH 04/26] fix: item list --- Core/Tooltip.lua | 108 +++++++++++++++++++++++++++++++++++++--- Data/MailboxScanner.lua | 66 +++++++++++++++++------- UI/MailboxFrame.lua | 27 +--------- 3 files changed, 152 insertions(+), 49 deletions(-) diff --git a/Core/Tooltip.lua b/Core/Tooltip.lua index 8663ce8..26aa665 100644 --- a/Core/Tooltip.lua +++ b/Core/Tooltip.lua @@ -13,6 +13,7 @@ end local function CountCurrentCharacterItems(itemID) local bagCount = 0 local bankCount = 0 + local mailCount = 0 local equippedCount = 0 -- Count current character's bags in real-time @@ -83,6 +84,51 @@ local function CountCurrentCharacterItems(itemID) end end + -- Count current character's mailbox in real-time if mailbox is open + if addon.Modules.MailboxScanner and addon.Modules.MailboxScanner:IsMailboxOpen() then + local numInboxItems = GetInboxNumItems() + for i = 1, numInboxItems do + local _, _, _, _, _, _, _, hasItem = GetInboxHeaderInfo(i) + if hasItem then + for j = 1, 12 do -- Turtle WoW supports up to 12 attachments + local name, _, count = GetInboxItem(i, j) + if name then + local itemLink = addon.Modules.Utils:GetInboxItemLink(i, j) + if itemLink then + local slotItemID = GetItemIDFromLink(itemLink) + if slotItemID == itemID then + mailCount = mailCount + (count or 1) + end + end + end + end + end + end + else + -- Mailbox not open - use saved data + local playerName = addon.Modules.DB:GetPlayerFullName() + local charData = Guda_DB and Guda_DB.characters and Guda_DB.characters[playerName] + if charData and charData.mailbox and type(charData.mailbox) == "table" then + for _, mail in ipairs(charData.mailbox) do + if mail.items then + for _, item in ipairs(mail.items) do + if item.link then + local slotItemID = GetItemIDFromLink(item.link) + if slotItemID == itemID then + mailCount = mailCount + (item.count or 1) + end + end + end + elseif mail.item and mail.item.link then -- Fallback for single item data structure + local slotItemID = GetItemIDFromLink(mail.item.link) + if slotItemID == itemID then + mailCount = mailCount + (mail.item.count or 1) + end + end + end + end + end + -- Count equipped items in real-time for slotID = 1, 19 do -- All equipment slots local link = GetInventoryItemLink("player", slotID) @@ -94,7 +140,7 @@ local function CountCurrentCharacterItems(itemID) end end - return bagCount, bankCount, equippedCount + return bagCount, bankCount, equippedCount, mailCount end -- Count items for a specific character with real-time data for current character @@ -107,6 +153,7 @@ local function CountItemsForCharacter(itemID, characterData, isCurrentChar) -- For other characters, use saved data local bagCount = 0 local bankCount = 0 + local mailCount = 0 local equippedCount = 0 -- Count bags from saved data @@ -141,6 +188,27 @@ local function CountItemsForCharacter(itemID, characterData, isCurrentChar) end end + -- Count mailbox from saved data + if characterData.mailbox and type(characterData.mailbox) == "table" then + for _, mail in ipairs(characterData.mailbox) do + if mail.items then + for _, item in ipairs(mail.items) do + if item.link then + local slotItemID = GetItemIDFromLink(item.link) + if slotItemID == itemID then + mailCount = mailCount + (item.count or 1) + end + end + end + elseif mail.item and mail.item.link then -- Fallback for single item data structure + local slotItemID = GetItemIDFromLink(mail.item.link) + if slotItemID == itemID then + mailCount = mailCount + (mail.item.count or 1) + end + end + end + end + -- Count equipped items from saved data if characterData.equipped and type(characterData.equipped) == "table" then for slotName, itemData in pairs(characterData.equipped) do @@ -153,7 +221,7 @@ local function CountItemsForCharacter(itemID, characterData, isCurrentChar) end end - return bagCount, bankCount, equippedCount + return bagCount, bankCount, equippedCount, mailCount end @@ -189,6 +257,7 @@ function Tooltip:AddInventoryInfo(tooltip, link) local totalBags = 0 local totalBank = 0 + local totalMail = 0 local totalEquipped = 0 local characterCounts = {} local hasAnyItems = false @@ -201,18 +270,20 @@ function Tooltip:AddInventoryInfo(tooltip, link) -- Ensure charData is actually a table and on current realm if type(charData) == "table" and charData.realm == currentRealm then local isCurrentChar = (charName == currentPlayerName) - local bagCount, bankCount, equippedCount = CountItemsForCharacter(itemID, charData, isCurrentChar) + local bagCount, bankCount, equippedCount, mailCount = CountItemsForCharacter(itemID, charData, isCurrentChar) - if bagCount > 0 or bankCount > 0 or equippedCount > 0 then + if bagCount > 0 or bankCount > 0 or equippedCount > 0 or mailCount > 0 then hasAnyItems = true totalBags = totalBags + bagCount totalBank = totalBank + bankCount + totalMail = totalMail + mailCount totalEquipped = totalEquipped + equippedCount table.insert(characterCounts, { name = charData.name or charName, classToken = charData.classToken, bagCount = bagCount, bankCount = bankCount, + mailCount = mailCount, equippedCount = equippedCount, isCurrent = isCurrentChar }) @@ -221,7 +292,7 @@ function Tooltip:AddInventoryInfo(tooltip, link) -- If charData is not a table (string, number, etc.), just skip it end - local totalCount = totalBags + totalBank + totalEquipped + local totalCount = totalBags + totalBank + totalMail + totalEquipped if hasAnyItems then @@ -233,7 +304,16 @@ function Tooltip:AddInventoryInfo(tooltip, link) -- Total line with cyan label and white count local totalText = "|cFF00FFFFTotal|r: |cFFFFFFFF" .. totalCount .. "|r" - local breakdownText = "(|cFF00FFFFBags|r: |cFFFFFFFF" .. totalBags .. "|r | |cFF00FFFFBank|r: |cFFFFFFFF" .. totalBank .. "|r)" + local breakdownParts = {} + if totalBags > 0 then table.insert(breakdownParts, "|cFF00FFFFBags|r: |cFFFFFFFF" .. totalBags .. "|r") end + if totalBank > 0 then table.insert(breakdownParts, "|cFF00FFFFBank|r: |cFFFFFFFF" .. totalBank .. "|r") end + if totalMail > 0 then table.insert(breakdownParts, "|cFF00FFFFMail|r: |cFFFFFFFF" .. totalMail .. "|r") end + if totalEquipped > 0 then table.insert(breakdownParts, "|cFF00FFFFEquipped|r: |cFFFFFFFF" .. totalEquipped .. "|r") end + + local breakdownText = "" + if table.getn(breakdownParts) > 0 then + breakdownText = "(" .. table.concat(breakdownParts, " | ") .. ")" + end tooltip:AddDoubleLine(totalText, breakdownText, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0) -- Sort with current character first @@ -254,6 +334,9 @@ function Tooltip:AddInventoryInfo(tooltip, link) if charInfo.bankCount > 0 then table.insert(parts, "|cFF00FFFFBank|r: |cFFFFFFFF" .. charInfo.bankCount .. "|r") end + if charInfo.mailCount > 0 then + table.insert(parts, "|cFF00FFFFMail|r: |cFFFFFFFF" .. charInfo.mailCount .. "|r") + end if charInfo.equippedCount > 0 then table.insert(parts, "|cFF00FFFFEquipped|r: |cFFFFFFFF" .. charInfo.equippedCount .. "|r") end @@ -453,6 +536,19 @@ function Tooltip:Initialize() end) end + -- Hook SetInboxItem for mailbox + local oldSetInboxItem = GameTooltip.SetInboxItem + function GameTooltip:SetInboxItem(index, itemIndex) + return WithDeferredMoney(self, function() + local ret = oldSetInboxItem(self, index, itemIndex) + local link = addon.Modules.Utils:GetInboxItemLink(index, itemIndex) + if link then + Tooltip:AddInventoryInfo(self, link) + end + return ret + end) + end + -- Hook SetTradeSkillItem for profession reagents and items local oldSetTradeSkillItem = GameTooltip.SetTradeSkillItem function GameTooltip:SetTradeSkillItem(skillIndex, reagentIndex) diff --git a/Data/MailboxScanner.lua b/Data/MailboxScanner.lua index 0c2e982..747a146 100644 --- a/Data/MailboxScanner.lua +++ b/Data/MailboxScanner.lua @@ -19,20 +19,25 @@ function MailboxScanner:ScanMailbox() local numItems = GetInboxNumItems() for i = 1, numItems do - mailboxData[i] = self:ScanMailItem(i) + local mailRows = self:ScanMailItemRows(i) + for _, row in ipairs(mailRows) do + table.insert(mailboxData, row) + end end return mailboxData end --- Scan a single mail item -function MailboxScanner:ScanMailItem(index) +-- Scan a single mail into one or more rows (flattened) +function MailboxScanner:ScanMailItemRows(index) -- GetInboxHeaderInfo(index) returns: packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply, isGM local packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply, isGM = GetInboxHeaderInfo(index) - local items = {} + local rows = {} + if hasItem then -- Turtle WoW supports up to 12 attachments per mail + -- GetInboxItem(index, itemIndex) returns: name, texture, count, quality, canUse local name, texture, count, quality, canUse = GetInboxItem(index, itemIndex) if name then @@ -60,22 +65,41 @@ function MailboxScanner:ScanMailItem(index) if itemTexture then itemData.texture = itemTexture end end end - table.insert(items, itemData) + + table.insert(rows, { + sender = sender, + subject = subject, + money = (itemIndex == 1) and money or 0, -- Attach money only to the first row of this mail + CODAmount = (itemIndex == 1) and CODAmount or 0, + daysLeft = daysLeft, + hasItem = true, + item = itemData, + mailIndex = index, + itemIndex = itemIndex, + wasRead = wasRead, + packageIcon = packageIcon, + }) end end - return { - sender = sender, - subject = subject, - money = money, - CODAmount = CODAmount, - daysLeft = daysLeft, - hasItem = (table.getn(items) > 0), - item = items[1], - items = items, - wasRead = wasRead, - packageIcon = packageIcon, - } + -- If no items found but there is money or it's just a letter + if table.getn(rows) == 0 then + table.insert(rows, { + sender = sender, + subject = subject, + money = money, + CODAmount = CODAmount, + daysLeft = daysLeft, + hasItem = false, + item = nil, + mailIndex = index, + itemIndex = 1, + wasRead = wasRead, + packageIcon = packageIcon, + }) + end + + return rows end -- Save current mailbox to database @@ -118,6 +142,14 @@ function MailboxScanner:Initialize() end end, "MailboxScanner") + -- Register for MAIL_INBOX_UPDATE to detect when mail content changes + addon.Modules.Events:Register("MAIL_INBOX_UPDATE", function() + if mailboxOpen then + addon:Debug("MAIL_INBOX_UPDATE: Refreshing mailbox") + MailboxScanner:SaveToDatabase() + end + end, "MailboxScanner") + -- Register for UI_ERROR_MESSAGE to handle "item not found" situations if needed -- (Some items might not be in cache and fail silently otherwise) diff --git a/UI/MailboxFrame.lua b/UI/MailboxFrame.lua index 465f01a..163e4ec 100644 --- a/UI/MailboxFrame.lua +++ b/UI/MailboxFrame.lua @@ -123,38 +123,13 @@ function MailboxFrame:Update() matchesSearch = true elseif mail.subject and string.find(string.lower(mail.subject), searchText) then matchesSearch = true - elseif mail.items then - for _, item in ipairs(mail.items) do - if item.name and string.find(string.lower(item.name), searchText) then - matchesSearch = true - break - end - end elseif mail.item and mail.item.name and string.find(string.lower(mail.item.name), searchText) then matchesSearch = true end end if matchesSearch then - -- Flatten mails with multiple items so they all show up in the grid - if mail.items and table.getn(mail.items) > 1 then - for itemIndex, item in ipairs(mail.items) do - local mailCopy = {} - for k, v in pairs(mail) do mailCopy[k] = v end - mailCopy.item = item - mailCopy.mailIndex = i - mailCopy.itemIndex = itemIndex - -- Clear items list in copy to avoid recursive flattening - mailCopy.items = nil - table.insert(filteredItems, mailCopy) - end - else - local mailCopy = {} - for k, v in pairs(mail) do mailCopy[k] = v end - mailCopy.mailIndex = i - mailCopy.itemIndex = 1 - table.insert(filteredItems, mailCopy) - end + table.insert(filteredItems, mail) end end From 839118841e7524e958c13c2ec5119fb7efe47a61 Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sat, 27 Dec 2025 18:28:13 +0400 Subject: [PATCH 05/26] fix: mailbox count --- Core/Tooltip.lua | 46 +++++++++++++++++------- Data/MailboxScanner.lua | 77 ++++++++++++++++++++--------------------- UI/MailboxFrame.xml | 4 +-- 3 files changed, 74 insertions(+), 53 deletions(-) diff --git a/Core/Tooltip.lua b/Core/Tooltip.lua index 26aa665..b9201e9 100644 --- a/Core/Tooltip.lua +++ b/Core/Tooltip.lua @@ -112,17 +112,28 @@ local function CountCurrentCharacterItems(itemID) for _, mail in ipairs(charData.mailbox) do if mail.items then for _, item in ipairs(mail.items) do - if item.link then - local slotItemID = GetItemIDFromLink(item.link) - if slotItemID == itemID then + local slotItemID = item.link and GetItemIDFromLink(item.link) + if slotItemID == itemID then + mailCount = mailCount + (item.count or 1) + elseif not slotItemID and item.name then + -- Fallback to name matching if link is missing + local targetName = GetItemInfo(itemID) + if targetName == item.name then mailCount = mailCount + (item.count or 1) end end end - elseif mail.item and mail.item.link then -- Fallback for single item data structure - local slotItemID = GetItemIDFromLink(mail.item.link) + elseif mail.item then -- Fallback for single item data structure + local item = mail.item + local slotItemID = item.link and GetItemIDFromLink(item.link) if slotItemID == itemID then - mailCount = mailCount + (mail.item.count or 1) + mailCount = mailCount + (item.count or 1) + elseif not slotItemID and item.name then + -- Fallback to name matching if link is missing + local targetName = GetItemInfo(itemID) + if targetName == item.name then + mailCount = mailCount + (item.count or 1) + end end end end @@ -193,17 +204,28 @@ local function CountItemsForCharacter(itemID, characterData, isCurrentChar) for _, mail in ipairs(characterData.mailbox) do if mail.items then for _, item in ipairs(mail.items) do - if item.link then - local slotItemID = GetItemIDFromLink(item.link) - if slotItemID == itemID then + local slotItemID = item.link and GetItemIDFromLink(item.link) + if slotItemID == itemID then + mailCount = mailCount + (item.count or 1) + elseif not slotItemID and item.name then + -- Fallback to name matching if link is missing + local targetName = GetItemInfo(itemID) + if targetName == item.name then mailCount = mailCount + (item.count or 1) end end end - elseif mail.item and mail.item.link then -- Fallback for single item data structure - local slotItemID = GetItemIDFromLink(mail.item.link) + elseif mail.item then -- Fallback for single item data structure + local item = mail.item + local slotItemID = item.link and GetItemIDFromLink(item.link) if slotItemID == itemID then - mailCount = mailCount + (mail.item.count or 1) + mailCount = mailCount + (item.count or 1) + elseif not slotItemID and item.name then + -- Fallback to name matching if link is missing + local targetName = GetItemInfo(itemID) + if targetName == item.name then + mailCount = mailCount + (item.count or 1) + end end end end diff --git a/Data/MailboxScanner.lua b/Data/MailboxScanner.lua index 747a146..e981cf0 100644 --- a/Data/MailboxScanner.lua +++ b/Data/MailboxScanner.lua @@ -36,49 +36,48 @@ function MailboxScanner:ScanMailItemRows(index) local rows = {} if hasItem then - -- Turtle WoW supports up to 12 attachments per mail + local itemIndex = 1 + -- GetInboxItem(index, itemIndex) returns: name, texture, count, quality, canUse + local name, texture, count, quality, canUse = GetInboxItem(index, itemIndex) + if name then + local itemLink = addon.Modules.Utils:GetInboxItemLink(index, itemIndex) - -- GetInboxItem(index, itemIndex) returns: name, texture, count, quality, canUse - local name, texture, count, quality, canUse = GetInboxItem(index, itemIndex) - if name then - local itemLink = addon.Modules.Utils:GetInboxItemLink(index, itemIndex) + local itemData = { + link = itemLink, + texture = texture or "Interface\\Icons\\INV_Misc_Bag_08", + count = count or 1, + quality = quality or 0, + name = name, + } - local itemData = { - link = itemLink, - texture = texture or "Interface\\Icons\\INV_Misc_Bag_08", - count = count or 1, - quality = quality or 0, - name = name, - } - - -- If we have a link, try to get more detailed info - if itemLink then - local itemName, link, itemQuality, iLevel, itemCategory, itemType, itemStackCount, itemSubType, itemTexture, itemEquipLoc, itemSellPrice = addon.Modules.Utils:GetItemInfo(itemLink) - if itemName then - itemData.name = itemName - itemData.quality = itemQuality or itemData.quality - itemData.iLevel = iLevel - itemData.type = itemType - itemData.class = itemCategory - itemData.subclass = itemSubType - itemData.equipSlot = itemEquipLoc - if itemTexture then itemData.texture = itemTexture end - end + -- If we have a link, try to get more detailed info + if itemLink then + local itemName, link, itemQuality, iLevel, itemCategory, itemType, itemStackCount, itemSubType, itemTexture, itemEquipLoc, itemSellPrice = addon.Modules.Utils:GetItemInfo(itemLink) + if itemName then + itemData.name = itemName + itemData.quality = itemQuality or itemData.quality + itemData.iLevel = iLevel + itemData.type = itemType + itemData.class = itemCategory + itemData.subclass = itemSubType + itemData.equipSlot = itemEquipLoc + if itemTexture then itemData.texture = itemTexture end end + end - table.insert(rows, { - sender = sender, - subject = subject, - money = (itemIndex == 1) and money or 0, -- Attach money only to the first row of this mail - CODAmount = (itemIndex == 1) and CODAmount or 0, - daysLeft = daysLeft, - hasItem = true, - item = itemData, - mailIndex = index, - itemIndex = itemIndex, - wasRead = wasRead, - packageIcon = packageIcon, - }) + table.insert(rows, { + sender = sender, + subject = subject, + money = (itemIndex == 1) and money or 0, -- Attach money only to the first row of this mail + CODAmount = (itemIndex == 1) and CODAmount or 0, + daysLeft = daysLeft, + hasItem = true, + item = itemData, + mailIndex = index, + itemIndex = itemIndex, + wasRead = wasRead, + packageIcon = packageIcon, + }) end end diff --git a/UI/MailboxFrame.xml b/UI/MailboxFrame.xml index 88c7f10..5577d3d 100644 --- a/UI/MailboxFrame.xml +++ b/UI/MailboxFrame.xml @@ -150,12 +150,12 @@ - + - + From 0c2954d230b90709ab261438229d37ec4a635d4b Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sat, 27 Dec 2025 20:15:41 +0400 Subject: [PATCH 06/26] mailbox frame styles --- UI/MailboxFrame.lua | 11 +++++++++++ UI/MailboxFrame.xml | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/UI/MailboxFrame.lua b/UI/MailboxFrame.lua index 163e4ec..73d62fa 100644 --- a/UI/MailboxFrame.lua +++ b/UI/MailboxFrame.lua @@ -192,6 +192,12 @@ function MailboxFrame:DisplayItems(items, charFullName, totalMails) itemButton.isMail = true itemButton.mailIndex = mail.mailIndex itemButton.mailItemIndex = mail.itemIndex or 1 + + -- Force left alignment in case template or other code changed anchors + if itemButton then + itemButton:ClearAllPoints() + itemButton:SetPoint("TOPLEFT", row, "TOPLEFT", 10, -8) + end if mail.item and (mail.item.texture or mail.item.link) then Guda_ItemButton_SetItem(itemButton, nil, nil, mail.item, false, charFullName, true, true) @@ -223,6 +229,11 @@ function MailboxFrame:DisplayItems(items, charFullName, totalMails) -- Row Money Frame local moneyFrame = getglobal(row:GetName() .. "_MoneyFrame") + if moneyFrame then + -- Force left alignment for the money frame + moneyFrame:ClearAllPoints() + moneyFrame:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 10, 8) + end if (mail.money or 0) > 0 then MoneyFrame_Update(moneyFrame:GetName(), mail.money) moneyFrame:Show() diff --git a/UI/MailboxFrame.xml b/UI/MailboxFrame.xml index 5577d3d..3ae8c80 100644 --- a/UI/MailboxFrame.xml +++ b/UI/MailboxFrame.xml @@ -4,7 +4,7 @@ - + From 6808a21479a6fe561e5347ef93360b2946302988 Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sat, 27 Dec 2025 22:59:42 +0400 Subject: [PATCH 07/26] fix: poor tooltip for mailboxframe --- Core/Database.lua | 42 ++++++++++++++++++ Core/Utils.lua | 12 +++--- Data/MailboxScanner.lua | 96 +++++++++++++++++++++++++++++++++++++++-- UI/ItemButton.lua | 18 +++++--- 4 files changed, 153 insertions(+), 15 deletions(-) diff --git a/Core/Database.lua b/Core/Database.lua index aa1d9b7..50bb90c 100644 --- a/Core/Database.lua +++ b/Core/Database.lua @@ -230,6 +230,48 @@ function DB:SaveMailbox(mailboxData) end end +-- Add a single mail entry to a character's mailbox +function DB:AddMailToCharacter(name, realm, mailRow) + local fullName = name .. "-" .. (realm or playerRealm) + local char = Guda_DB.characters[fullName] + + if char then + if not char.mailbox then + char.mailbox = {} + end + + -- Check if this exact mail already exists (simplistic check) + local exists = false + for _, m in ipairs(char.mailbox) do + if m.sender == mailRow.sender and m.subject == mailRow.subject and m.money == mailRow.money then + if (not m.item and not mailRow.item) or (m.item and mailRow.item and m.item.name == mailRow.item.name and m.item.count == mailRow.item.count) then + exists = true + -- Update link/itemID if missing in existing but present in new + if mailRow.item and m.item then + if not m.item.link and mailRow.item.link then + m.item.link = mailRow.item.link + addon:Debug("Updated link for existing mail item") + end + if not m.item.itemID and mailRow.item.itemID then + m.item.itemID = mailRow.item.itemID + addon:Debug("Updated itemID for existing mail item") + end + end + break + end + end + end + + if not exists then + table.insert(char.mailbox, 1, mailRow) -- Add to beginning + char.lastUpdate = time() + addon:Debug("Added outgoing mail to %s's mailbox", fullName) + return true + end + end + return false +end + -- Get all characters (optionally filter by faction and/or realm) function DB:GetAllCharacters(sameFactionOnly, currentRealmOnly) local chars = {} diff --git a/Core/Utils.lua b/Core/Utils.lua index 05cd468..9951ea7 100644 --- a/Core/Utils.lua +++ b/Core/Utils.lua @@ -101,10 +101,13 @@ scanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE") -- Get item link from mailbox attachment (WoW 1.12.1 workaround) function Utils:GetInboxItemLink(index, itemIndex) + -- Try global function first (if it exists on this server/version) + if GetInboxItemLink then + local link = GetInboxItemLink(index) + if link then return link end + end + -- In 1.12.1, GameTooltip:GetHyperlink() does not exist. - -- Actually, in 1.12.1, there's no official API to get the link from a mailbox item - -- if GetInboxItemLink(index) is not available (it's often NOT available in Vanilla). - -- Let's try to use GetItemInfo(name) as the primary way. local name = GetInboxItem(index, itemIndex) if name then @@ -114,9 +117,6 @@ function Utils:GetInboxItemLink(index, itemIndex) end end - -- Fallback: If we still don't have it, we might try to scan the tooltip for the name - -- but GetInboxItem already gave us the name. - return nil end diff --git a/Data/MailboxScanner.lua b/Data/MailboxScanner.lua index e981cf0..7f2e6d6 100644 --- a/Data/MailboxScanner.lua +++ b/Data/MailboxScanner.lua @@ -48,11 +48,36 @@ function MailboxScanner:ScanMailItemRows(index) count = count or 1, quality = quality or 0, name = name, + itemID = addon.Modules.Utils:ExtractItemID(itemLink), } + -- Fallback: If link/itemID is missing, try to recover from existing database + if not itemData.itemID or not itemData.link then + local existingMailbox = addon.Modules.DB:GetCharacterMailbox(addon.Modules.DB:GetPlayerFullName()) + for _, oldMail in ipairs(existingMailbox) do + -- Match by sender, subject and item name + if oldMail.sender == sender and oldMail.subject == subject and oldMail.item and oldMail.item.name == name then + if not itemData.link and oldMail.item.link then + itemData.link = oldMail.item.link + addon:Debug("Recovered link from database for %s", name) + end + if not itemData.itemID and oldMail.item.itemID then + itemData.itemID = oldMail.item.itemID + addon:Debug("Recovered itemID from database for %s", name) + end + if itemData.link and itemData.itemID then break end + end + end + end + + -- If we still don't have itemID but have a link (or vice versa), fix it + if itemData.link and not itemData.itemID then + itemData.itemID = addon.Modules.Utils:ExtractItemID(itemData.link) + end + -- If we have a link, try to get more detailed info - if itemLink then - local itemName, link, itemQuality, iLevel, itemCategory, itemType, itemStackCount, itemSubType, itemTexture, itemEquipLoc, itemSellPrice = addon.Modules.Utils:GetItemInfo(itemLink) + if itemData.link then + local itemName, link, itemQuality, iLevel, itemCategory, itemType, itemStackCount, itemSubType, itemTexture, itemEquipLoc, itemSellPrice = addon.Modules.Utils:GetItemInfo(itemData.link) if itemName then itemData.name = itemName itemData.quality = itemQuality or itemData.quality @@ -112,8 +137,73 @@ function MailboxScanner:SaveToDatabase() addon:Debug("Mailbox data saved") end +-- Handle outgoing mail +function MailboxScanner:OnSendMail(recipient, subject, body) + if not recipient or recipient == "" then return end + + -- In WoW 1.12.1, SendMail(recipient, subject, body) is the signature. + -- To get the attached item, we use GetSendMailItem(). + -- GetSendMailItem() returns: name, texture, count, quality + local name, texture, count, quality = GetSendMailItem() + local moneyAmount = GetSendMailMoney() + + local itemData = nil + if name then + local _, link = GetItemInfo(name) + itemData = { + name = name, + texture = texture or "Interface\\Icons\\INV_Misc_Bag_08", + count = count or 1, + quality = quality or 0, + link = link, + itemID = addon.Modules.Utils:ExtractItemID(link), + } + + -- Try to get more info if it's in cache + local itemName, retLink, itemQuality, iLevel, itemCategory, itemType, itemStackCount, itemSubType, itemTexture, itemEquipLoc, itemSellPrice = addon.Modules.Utils:GetItemInfo(name) + if itemName then + itemData.link = retLink or itemData.link + itemData.itemID = addon.Modules.Utils:ExtractItemID(itemData.link) or itemData.itemID + itemData.quality = itemQuality or itemData.quality + itemData.iLevel = iLevel + itemData.type = itemType + itemData.class = itemCategory + itemData.subclass = itemSubType + itemData.equipSlot = itemEquipLoc + if itemTexture then itemData.texture = itemTexture end + end + + -- Double check itemID + if not itemData.itemID and itemData.link then + itemData.itemID = addon.Modules.Utils:ExtractItemID(itemData.link) + end + end + + if itemData or moneyAmount > 0 then + local mailRow = { + sender = UnitName("player"), + subject = (subject and subject ~= "") and subject or "No Subject", + money = moneyAmount, + CODAmount = 0, + daysLeft = 30, -- Outgoing mail typically has 30 days + hasItem = itemData ~= nil, + item = itemData, + wasRead = false, + } + + addon.Modules.DB:AddMailToCharacter(recipient, nil, mailRow) + end +end + -- Initialize mailbox scanner function MailboxScanner:Initialize() + -- Hook SendMail to capture outgoing mail to alts + local originalSendMail = SendMail + SendMail = function(recipient, subject, body) + MailboxScanner:OnSendMail(recipient, subject, body) + return originalSendMail(recipient, subject, body) + end + -- Mailbox opened addon.Modules.Events:OnMailShow(function() mailboxOpen = true @@ -155,7 +245,7 @@ function MailboxScanner:Initialize() -- Mailbox closed addon.Modules.Events:OnMailClosed(function() -- Final save on close - self:SaveToDatabase() + MailboxScanner:SaveToDatabase() mailboxOpen = false addon:Debug("Mailbox closed") end, "MailboxScanner") diff --git a/UI/ItemButton.lua b/UI/ItemButton.lua index 0f44585..758fa5d 100644 --- a/UI/ItemButton.lua +++ b/UI/ItemButton.lua @@ -982,13 +982,19 @@ function Guda_ItemButton_OnEnter(self) -- Mailbox tooltip handling if self.isMail then local currentPlayerName = addon.Modules.DB:GetPlayerFullName() - if (not self.otherChar or self.otherChar == currentPlayerName) and self.mailIndex then - -- Live mailbox for current character + local isMailboxOpen = addon.Modules.MailboxScanner and addon.Modules.MailboxScanner:IsMailboxOpen() + + if (not self.otherChar or self.otherChar == currentPlayerName) and self.mailIndex and isMailboxOpen then + -- Live mailbox for current character (only when mailbox is actually open) GameTooltip:SetInboxItem(self.mailIndex, self.mailItemIndex or 1) - elseif self.itemData and self.itemData.link then - -- Read-only / other character mailbox - GameTooltip.GudaViewedCharacter = self.otherChar - GameTooltip:SetHyperlink(self.itemData.link) + elseif self.itemData and (self.itemData.link or self.itemData.itemID) then + -- Read-only / other character mailbox OR current character mailbox when closed + GameTooltip.GudaViewedCharacter = self.otherChar or currentPlayerName + if self.itemData.link then + GameTooltip:SetHyperlink(self.itemData.link) + else + GameTooltip:SetHyperlink("item:" .. self.itemData.itemID .. ":0:0:0") + end elseif self.itemData and self.itemData.name then -- Money or generic mail GameTooltip:AddLine(self.itemData.name, 1, 1, 1) From e4bae8e4daad7fbbab2635a3636a6f0cb0c7d3f1 Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sat, 27 Dec 2025 23:04:41 +0400 Subject: [PATCH 08/26] feat: auction house buy --- Data/MailboxScanner.lua | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/Data/MailboxScanner.lua b/Data/MailboxScanner.lua index 7f2e6d6..f513b65 100644 --- a/Data/MailboxScanner.lua +++ b/Data/MailboxScanner.lua @@ -195,6 +195,51 @@ function MailboxScanner:OnSendMail(recipient, subject, body) end end +-- Handle auction house buyouts +function MailboxScanner:OnAuctionBid(type, index, bid) + local name, texture, count, quality, canUse, level, minBid, minIncrement, buyoutPrice, bidAmount, highBidder, owner = GetAuctionItemInfo(type, index) + + if name and buyoutPrice > 0 and bid >= buyoutPrice then + local link = GetAuctionItemLink(type, index) + local itemData = { + name = name, + texture = texture or "Interface\\Icons\\INV_Misc_Bag_08", + count = count or 1, + quality = quality or 0, + link = link, + itemID = addon.Modules.Utils:ExtractItemID(link), + } + + -- Try to get more info if it's in cache + local itemName, retLink, itemQuality, iLevel, itemCategory, itemType, itemStackCount, itemSubType, itemTexture, itemEquipLoc, itemSellPrice = addon.Modules.Utils:GetItemInfo(link or name) + if itemName then + itemData.link = retLink or itemData.link + itemData.itemID = addon.Modules.Utils:ExtractItemID(itemData.link) or itemData.itemID + itemData.quality = itemQuality or itemData.quality + itemData.iLevel = iLevel + itemData.type = itemType + itemData.class = itemCategory + itemData.subclass = itemSubType + itemData.equipSlot = itemEquipLoc + if itemTexture then itemData.texture = itemTexture end + end + + local mailRow = { + sender = "Auction House", + subject = "Auction won: " .. name, + money = 0, + CODAmount = 0, + daysLeft = 30, + hasItem = true, + item = itemData, + wasRead = false, + } + + addon.Modules.DB:AddMailToCharacter(UnitName("player"), nil, mailRow) + addon:Debug("Captured AH buyout: %s", name) + end +end + -- Initialize mailbox scanner function MailboxScanner:Initialize() -- Hook SendMail to capture outgoing mail to alts @@ -204,6 +249,13 @@ function MailboxScanner:Initialize() return originalSendMail(recipient, subject, body) end + -- Hook PlaceAuctionBid to capture AH buyouts + local originalPlaceAuctionBid = PlaceAuctionBid + PlaceAuctionBid = function(type, index, bid) + MailboxScanner:OnAuctionBid(type, index, bid) + return originalPlaceAuctionBid(type, index, bid) + end + -- Mailbox opened addon.Modules.Events:OnMailShow(function() mailboxOpen = true From e6953aafc5ca80f7b1abea995a1f5b229f5ef20b Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sat, 27 Dec 2025 23:12:32 +0400 Subject: [PATCH 09/26] dropdown --- UI/BagFrame.lua | 189 +++++++------------------------------------- UI/BankFrame.lua | 122 +++++++--------------------- UI/MailboxFrame.lua | 41 +++++++--- 3 files changed, 87 insertions(+), 265 deletions(-) diff --git a/UI/BagFrame.lua b/UI/BagFrame.lua index 7e01ff7..3900577 100644 --- a/UI/BagFrame.lua +++ b/UI/BagFrame.lua @@ -7,6 +7,9 @@ local BagFrame = {} addon.Modules.BagFrame = BagFrame local currentViewChar = nil -- nil = current character +function BagFrame:GetCurrentViewChar() + return currentViewChar +end local searchText = "" local itemButtons = {} local showKeyring = false -- Toggle for keyring display @@ -1405,119 +1408,15 @@ function Guda_BagFrame_MoneyOnEnter(self) end -- Dropdown management -local characterDropdown = nil -local bankDropdown = nil - --- Toggle character dropdown -function Guda_BagFrame_ToggleCharacterDropdown(button) --- Hide bank dropdown if it's shown - if bankDropdown and bankDropdown:IsShown() then - bankDropdown:Hide() - end - - if characterDropdown and characterDropdown:IsShown() then - characterDropdown:Hide() - return - end - - if not characterDropdown then - -- Create dropdown frame - characterDropdown = CreateFrame("Frame", "Guda_CharacterDropdown", UIParent) - characterDropdown:SetFrameStrata("DIALOG") - characterDropdown:SetWidth(200) - characterDropdown:SetBackdrop({ - bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", - edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", - tile = true, - tileSize = 16, - edgeSize = 16, - insets = { left = 4, right = 4, top = 4, bottom = 4 } - }) - characterDropdown:SetBackdropColor(0, 0, 0, 0.95) - characterDropdown:EnableMouse(true) - characterDropdown:Hide() - - characterDropdown.buttons = {} - end - - -- Position dropdown below the button - characterDropdown:ClearAllPoints() - characterDropdown:SetPoint("TOPLEFT", button, "BOTTOMLEFT", 0, -2) - - -- Clear existing buttons - for _, btn in ipairs(characterDropdown.buttons) do - btn:Hide() - end - characterDropdown.buttons = {} - - -- Get all characters on current realm - local chars = addon.Modules.DB:GetAllCharacters(false, true) - - -- Add "Current Character" option at the top - local yOffset = -8 - local currentCharButton = CreateFrame("Button", nil, characterDropdown) - currentCharButton:SetWidth(188) - currentCharButton:SetHeight(20) - currentCharButton:SetPoint("TOP", characterDropdown, "TOP", 0, yOffset) - - -- Button background on hover - local currentCharBg = currentCharButton:CreateTexture(nil, "BACKGROUND") - currentCharBg:SetAllPoints() - currentCharBg:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") - currentCharBg:SetBlendMode("ADD") - currentCharBg:SetAlpha(0) - - -- Button text - local currentCharText = currentCharButton:CreateFontString(nil, "ARTWORK", "GameFontHighlight") - currentCharText:SetPoint("LEFT", currentCharButton, "LEFT", 8, 0) - currentCharText:SetText("Characters") - - -- Button scripts - currentCharButton:SetScript("OnEnter", function() - currentCharBg:SetAlpha(0.3) - end) - currentCharButton:SetScript("OnLeave", function() - currentCharBg:SetAlpha(0) - end) - currentCharButton:SetScript("OnClick", function() - addon.Modules.BagFrame:ShowCurrentCharacter() - characterDropdown:Hide() - end) - - table.insert(characterDropdown.buttons, currentCharButton) - yOffset = yOffset - 20 - - -- Add separator - local separator = characterDropdown:CreateTexture(nil, "ARTWORK") - separator:SetHeight(1) - separator:SetWidth(180) - separator:SetPoint("TOP", characterDropdown, "TOP", 0, yOffset) - separator:SetTexture(1, 1, 1, 0.2) - yOffset = yOffset - 4 - - -- Get current player's full name for comparison +local function Guda_BagCharacterMenu_Initialize() + local characters = addon.Modules.DB:GetAllCharacters(false, true) + local info local currentPlayerFullName = addon.Modules.DB:GetPlayerFullName() + local currentViewChar = addon.Modules.BagFrame:GetCurrentViewChar() - -- Add character buttons - for _, char in ipairs(chars) do - -- Capture variables in local scope for closure + for i, char in ipairs(characters) do local charFullName = char.fullName - local charName = char.name - local charMoney = char.money or 0 local charClassToken = char.classToken - local isCurrentChar = (charFullName == currentPlayerFullName) - - local charButton = CreateFrame("Button", nil, characterDropdown) - charButton:SetWidth(188) - charButton:SetHeight(20) - charButton:SetPoint("TOP", characterDropdown, "TOP", 0, yOffset) - - -- Button background on hover - local charBg = charButton:CreateTexture(nil, "BACKGROUND") - charBg:SetAllPoints() - charBg:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") - charBg:SetBlendMode("ADD") - charBg:SetAlpha(0) -- Get class color local classColor = charClassToken and RAID_CLASS_COLORS[charClassToken] @@ -1526,69 +1425,40 @@ function Guda_BagFrame_ToggleCharacterDropdown(button) r, g, b = classColor.r, classColor.g, classColor.b end - -- Button text - local charText = charButton:CreateFontString(nil, "ARTWORK", "GameFontHighlight") - charText:SetPoint("LEFT", charButton, "LEFT", 8, 0) - charText:SetText(charName) - charText:SetTextColor(r, g, b) + -- Create colored name + local coloredName = addon.Modules.Utils:ColorText(char.name, r, g, b) - -- Money text - local moneyText = charButton:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") - moneyText:SetPoint("RIGHT", charButton, "RIGHT", -8, 0) - moneyText:SetText(addon.Modules.Utils:FormatMoney(charMoney)) - moneyText:SetTextColor(0.7, 0.7, 0.7) - - -- Button scripts - charButton:SetScript("OnEnter", function() - charBg:SetAlpha(0.3) - end) - charButton:SetScript("OnLeave", function() - charBg:SetAlpha(0) - end) - charButton:SetScript("OnClick", function() - if charFullName then - if isCurrentChar then - -- Clicking current character - show current live view - addon.Modules.BagFrame:ShowCurrentCharacter() - else - -- Clicking different character - show their stored bags - addon.Modules.BagFrame:ShowCharacter(charFullName) - end - characterDropdown:Hide() + info = {} + info.text = coloredName + info.func = function() + if charFullName == currentPlayerFullName then + addon.Modules.BagFrame:ShowCurrentCharacter() else - addon:Print("Error: Character fullName is nil") + addon.Modules.BagFrame:ShowCharacter(charFullName) end - end) - - table.insert(characterDropdown.buttons, charButton) - yOffset = yOffset - 20 + end + info.checked = (currentViewChar == charFullName or (not currentViewChar and charFullName == currentPlayerFullName)) + UIDropDownMenu_AddButton(info) end +end - -- Set dropdown height based on content - characterDropdown:SetHeight(math.abs(yOffset) + 8) - - -- Show dropdown - characterDropdown:Show() +-- Toggle character dropdown +function Guda_BagFrame_ToggleCharacterDropdown(button) + local menuFrame = getglobal("Guda_BagCharacterMenu") + if not menuFrame then + menuFrame = CreateFrame("Frame", "Guda_BagCharacterMenu", UIParent, "UIDropDownMenuTemplate") + end + UIDropDownMenu_Initialize(menuFrame, Guda_BagCharacterMenu_Initialize, "MENU") + ToggleDropDownMenu(1, nil, menuFrame, "cursor", 0, 0) end -- Hide dropdown when clicking elsewhere local function HideCharacterDropdown() - if characterDropdown then - characterDropdown:Hide() - end + -- Handled by UIDropDownMenu system end -- Toggle mail dropdown function Guda_BagFrame_ToggleMailDropdown(button) --- Hide character dropdown if it's shown - if characterDropdown and characterDropdown:IsShown() then - characterDropdown:Hide() - end - - if bankDropdown and bankDropdown:IsShown() then - bankDropdown:Hide() - end - if mailDropdown and mailDropdown:IsShown() then mailDropdown:Hide() return @@ -2680,7 +2550,6 @@ function BagFrame:Initialize() if bagFrame then local originalOnMouseDown = bagFrame:GetScript("OnMouseDown") bagFrame:SetScript("OnMouseDown", function() - HideCharacterDropdown() if originalOnMouseDown then originalOnMouseDown() end diff --git a/UI/BankFrame.lua b/UI/BankFrame.lua index 446fa21..2e5c0c8 100644 --- a/UI/BankFrame.lua +++ b/UI/BankFrame.lua @@ -7,6 +7,9 @@ local BankFrame = {} addon.Modules.BankFrame = BankFrame local currentViewChar = nil +function BankFrame:GetCurrentViewChar() + return currentViewChar +end local searchText = "" local isReadOnlyMode = false -- Track if viewing saved bank (read-only) or live bank (interactive) local hiddenBankBags = {} -- Track which bank bags are hidden (bagID -> true/false) @@ -1829,69 +1832,16 @@ function Guda_BankFrame_ClearBagButtonHighlight() end -- Bank character dropdown (similar to BagFrame's bank dropdown) -local bankCharDropdown +local function Guda_BankCharacterMenu_Initialize() + local characters = addon.Modules.DB:GetAllCharacters(false, true) + local info + local currentPlayerFullName = addon.Modules.DB:GetPlayerFullName() + local currentViewChar = addon.Modules.BankFrame:GetCurrentViewChar() -function Guda_BankFrame_ToggleBankDropdown(button) - if bankCharDropdown and bankCharDropdown:IsShown() then - bankCharDropdown:Hide() - return - end - - if not bankCharDropdown then - -- Create dropdown frame - bankCharDropdown = CreateFrame("Frame", "Guda_BankCharDropdown", UIParent) - bankCharDropdown:SetFrameStrata("DIALOG") - bankCharDropdown:SetWidth(200) - bankCharDropdown:SetBackdrop({ - bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", - edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", - tile = true, - tileSize = 16, - edgeSize = 16, - insets = { left = 4, right = 4, top = 4, bottom = 4 } - }) - bankCharDropdown:SetBackdropColor(0, 0, 0, 0.95) - bankCharDropdown:EnableMouse(true) - bankCharDropdown:Hide() - - bankCharDropdown.buttons = {} - end - - -- Position dropdown below the button - bankCharDropdown:ClearAllPoints() - bankCharDropdown:SetPoint("TOPLEFT", button, "BOTTOMLEFT", 0, -2) - - -- Clear existing buttons - for _, btn in ipairs(bankCharDropdown.buttons) do - btn:Hide() - end - bankCharDropdown.buttons = {} - - -- Get all characters on current realm - local chars = addon.Modules.DB:GetAllCharacters(false, true) - - local yOffset = -8 - - -- Add character buttons - for _, char in ipairs(chars) do - -- Capture variables in local scope for closure + for i, char in ipairs(characters) do local charFullName = char.fullName - local charName = char.name - local charMoney = char.money or 0 local charClassToken = char.classToken - - local charButton = CreateFrame("Button", nil, bankCharDropdown) - charButton:SetWidth(188) - charButton:SetHeight(20) - charButton:SetPoint("TOP", bankCharDropdown, "TOP", 0, yOffset) - - -- Button background on hover - local charBg = charButton:CreateTexture(nil, "BACKGROUND") - charBg:SetAllPoints() - charBg:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") - charBg:SetBlendMode("ADD") - charBg:SetAlpha(0) - + -- Get class color local classColor = charClassToken and RAID_CLASS_COLORS[charClassToken] local r, g, b = 1, 1, 1 @@ -1899,44 +1849,30 @@ function Guda_BankFrame_ToggleBankDropdown(button) r, g, b = classColor.r, classColor.g, classColor.b end - -- Button text - local charText = charButton:CreateFontString(nil, "ARTWORK", "GameFontHighlight") - charText:SetPoint("LEFT", charButton, "LEFT", 8, 0) - charText:SetText(charName) - charText:SetTextColor(r, g, b) + -- Create colored name + local coloredName = addon.Modules.Utils:ColorText(char.name, r, g, b) - -- Money text - local moneyText = charButton:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") - moneyText:SetPoint("RIGHT", charButton, "RIGHT", -8, 0) - moneyText:SetText(addon.Modules.Utils:FormatMoney(charMoney)) - moneyText:SetTextColor(0.7, 0.7, 0.7) - - -- Button scripts - charButton:SetScript("OnEnter", function() - charBg:SetAlpha(0.3) - end) - charButton:SetScript("OnLeave", function() - charBg:SetAlpha(0) - end) - charButton:SetScript("OnClick", function() - if charFullName then - -- Show bank for this character using the BankFrame module - addon.Modules.BankFrame:ShowCharacter(charFullName) - bankCharDropdown:Hide() + info = {} + info.text = coloredName + info.func = function() + if charFullName == currentPlayerFullName then + addon.Modules.BankFrame:ShowCurrentCharacter() else - addon:Print("Error: Character fullName is nil") + addon.Modules.BankFrame:ShowCharacter(charFullName) end - end) - - table.insert(bankCharDropdown.buttons, charButton) - yOffset = yOffset - 20 + end + info.checked = (currentViewChar == charFullName or (not currentViewChar and charFullName == currentPlayerFullName)) + UIDropDownMenu_AddButton(info) end +end - -- Set dropdown height based on content - bankCharDropdown:SetHeight(math.abs(yOffset) + 8) - - -- Show dropdown - bankCharDropdown:Show() +function Guda_BankFrame_ToggleBankDropdown(button) + local menuFrame = getglobal("Guda_BankCharacterMenu") + if not menuFrame then + menuFrame = CreateFrame("Frame", "Guda_BankCharacterMenu", UIParent, "UIDropDownMenuTemplate") + end + UIDropDownMenu_Initialize(menuFrame, Guda_BankCharacterMenu_Initialize, "MENU") + ToggleDropDownMenu(1, nil, menuFrame, "cursor", 0, 0) end -- Drag handlers for bank bag slots diff --git a/UI/MailboxFrame.lua b/UI/MailboxFrame.lua index 73d62fa..b18fc0e 100644 --- a/UI/MailboxFrame.lua +++ b/UI/MailboxFrame.lua @@ -282,20 +282,37 @@ function Guda_MailboxFrame_OnSearchTextChanged() end -- Show character selection menu -function Guda_MailboxFrame_ShowCharacterMenu() +local function Guda_MailboxCharacterMenu_Initialize() local characters = addon.Modules.DB:GetAllCharacters(true, true) - local menu = {} - + local info + for i, char in ipairs(characters) do local charFullName = char.fullName - table.insert(menu, { - text = char.name, - func = function() MailboxFrame:ShowCharacter(charFullName) end, - checked = (currentViewChar == char.fullName or (not currentViewChar and char.fullName == addon.Modules.DB:GetPlayerFullName())) - }) + local charClassToken = char.classToken + + -- Get class color + local classColor = charClassToken and RAID_CLASS_COLORS[charClassToken] + local r, g, b = 1, 1, 1 + if classColor then + r, g, b = classColor.r, classColor.g, classColor.b + end + + -- Create colored name + local coloredName = addon.Modules.Utils:ColorText(char.name, r, g, b) + + info = {} + info.text = coloredName + info.func = function() MailboxFrame:ShowCharacter(charFullName) end + info.checked = (currentViewChar == char.fullName or (not currentViewChar and char.fullName == addon.Modules.DB:GetPlayerFullName())) + UIDropDownMenu_AddButton(info) end - - -- EasyMenu is available in 1.12.1 - local menuFrame = CreateFrame("Frame", "Guda_MailboxCharacterMenu", UIParent, "UIDropDownMenuTemplate") - EasyMenu(menu, menuFrame, "cursor", 0, 0, "MENU") +end + +function Guda_MailboxFrame_ShowCharacterMenu() + local menuFrame = getglobal("Guda_MailboxCharacterMenu") + if not menuFrame then + menuFrame = CreateFrame("Frame", "Guda_MailboxCharacterMenu", UIParent, "UIDropDownMenuTemplate") + end + UIDropDownMenu_Initialize(menuFrame, Guda_MailboxCharacterMenu_Initialize, "MENU") + ToggleDropDownMenu(1, nil, menuFrame, "cursor", 0, 0) end From bf4ea760b218897c44f519d22a339ec91d7e17d0 Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sat, 27 Dec 2025 23:29:44 +0400 Subject: [PATCH 10/26] fix: dropdown --- UI/BagFrame.lua | 257 +++++++++++--------------------------------- UI/BankFrame.lua | 12 +++ UI/BankFrame.xml | 6 +- UI/MailboxFrame.lua | 10 ++ UI/MailboxFrame.xml | 3 + 5 files changed, 87 insertions(+), 201 deletions(-) diff --git a/UI/BagFrame.lua b/UI/BagFrame.lua index 3900577..3d5e70a 100644 --- a/UI/BagFrame.lua +++ b/UI/BagFrame.lua @@ -112,6 +112,9 @@ end -- OnHide function Guda_BagFrame_OnHide(self) + -- Close any open dropdown menus when the bag frame is hidden + CloseDropDownMenus() + -- Clean up all buttons when frame is hidden (safe since we're not displaying) for _, bagParent in pairs(bagParents) do if bagParent then @@ -1452,71 +1455,58 @@ function Guda_BagFrame_ToggleCharacterDropdown(button) ToggleDropDownMenu(1, nil, menuFrame, "cursor", 0, 0) end --- Hide dropdown when clicking elsewhere -local function HideCharacterDropdown() - -- Handled by UIDropDownMenu system +-- Mailbox character dropdown +local function Guda_BagMailboxMenu_Initialize() + local characters = addon.Modules.DB:GetAllCharacters(false, true) + local info + + for i, char in ipairs(characters) do + local charFullName = char.fullName + local charClassToken = char.classToken + + -- Get class color + local classColor = charClassToken and RAID_CLASS_COLORS[charClassToken] + local r, g, b = 1, 1, 1 + if classColor then + r, g, b = classColor.r, classColor.g, classColor.b + end + + -- Create colored name + local coloredName = addon.Modules.Utils:ColorText(char.name, r, g, b) + + info = {} + info.text = coloredName + info.func = function() + addon.Modules.MailboxFrame:ShowCharacter(charFullName) + if not Guda_MailboxFrame:IsShown() then + Guda_MailboxFrame:Show() + end + end + local mailboxViewChar = addon.Modules.MailboxFrame:GetCurrentViewChar() + info.checked = (mailboxViewChar == charFullName or (not mailboxViewChar and charFullName == addon.Modules.DB:GetPlayerFullName())) + UIDropDownMenu_AddButton(info) + end end -- Toggle mail dropdown function Guda_BagFrame_ToggleMailDropdown(button) - if mailDropdown and mailDropdown:IsShown() then - mailDropdown:Hide() - return + local menuFrame = getglobal("Guda_BagMailboxMenu") + if not menuFrame then + menuFrame = CreateFrame("Frame", "Guda_BagMailboxMenu", UIParent, "UIDropDownMenuTemplate") end + UIDropDownMenu_Initialize(menuFrame, Guda_BagMailboxMenu_Initialize, "MENU") + ToggleDropDownMenu(1, nil, menuFrame, "cursor", 0, 0) +end - if not mailDropdown then - -- Create dropdown frame - mailDropdown = CreateFrame("Frame", "Guda_MailDropdown", UIParent) - mailDropdown:SetFrameStrata("DIALOG") - mailDropdown:SetWidth(200) - mailDropdown:SetBackdrop({ - bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", - edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", - tile = true, - tileSize = 16, - edgeSize = 16, - insets = { left = 4, right = 4, top = 4, bottom = 4 } - }) - mailDropdown:SetBackdropColor(0, 0, 0, 0.95) - mailDropdown:EnableMouse(true) - mailDropdown:Hide() +-- Bank character dropdown +local function Guda_BagBankMenu_Initialize() + local characters = addon.Modules.DB:GetAllCharacters(false, true) + local info - mailDropdown.buttons = {} - end - - -- Position dropdown below the button - mailDropdown:ClearAllPoints() - mailDropdown:SetPoint("TOPLEFT", button, "BOTTOMLEFT", 0, -2) - -- Clear existing buttons - for _, btn in ipairs(mailDropdown.buttons) do - btn:Hide() - end - mailDropdown.buttons = {} - - -- Get all characters on current realm - local chars = addon.Modules.DB:GetAllCharacters(false, true) - - local yOffset = -8 - - -- Add character buttons - for _, char in ipairs(chars) do - -- Capture variables in local scope for closure + for i, char in ipairs(characters) do local charFullName = char.fullName - local charName = char.name local charClassToken = char.classToken - local charButton = CreateFrame("Button", nil, mailDropdown) - charButton:SetWidth(188) - charButton:SetHeight(20) - charButton:SetPoint("TOP", mailDropdown, "TOP", 0, yOffset) - - -- Button background on hover - local charBg = charButton:CreateTexture(nil, "BACKGROUND") - charBg:SetAllPoints() - charBg:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") - charBg:SetBlendMode("ADD") - charBg:SetAlpha(0) - -- Get class color local classColor = charClassToken and RAID_CLASS_COLORS[charClassToken] local r, g, b = 1, 1, 1 @@ -1524,154 +1514,29 @@ function Guda_BagFrame_ToggleMailDropdown(button) r, g, b = classColor.r, classColor.g, classColor.b end - -- Button text - local charText = charButton:CreateFontString(nil, "ARTWORK", "GameFontHighlight") - charText:SetPoint("LEFT", charButton, "LEFT", 8, 0) - charText:SetText(charName) - charText:SetTextColor(r, g, b) + -- Create colored name + local charName = char.name + local coloredName = addon.Modules.Utils:ColorText(charName, r, g, b) - -- Button scripts - charButton:SetScript("OnEnter", function() - charBg:SetAlpha(0.3) - end) - charButton:SetScript("OnLeave", function() - charBg:SetAlpha(0) - end) - charButton:SetScript("OnClick", function() - if charFullName then - -- Show mailbox for this character - addon.Modules.MailboxFrame:ShowCharacter(charFullName) - if not Guda_MailboxFrame:IsShown() then - Guda_MailboxFrame:Show() - end - mailDropdown:Hide() - else - addon:Print("Error: Character fullName is nil") - end - end) - - table.insert(mailDropdown.buttons, charButton) - yOffset = yOffset - 20 + info = {} + info.text = coloredName + info.func = function() + Guda_BagFrame_ShowCharacterBank(charFullName, charName) + end + local bankViewChar = addon.Modules.BankFrame:GetCurrentViewChar() + info.checked = (bankViewChar == charFullName or (not bankViewChar and charFullName == addon.Modules.DB:GetPlayerFullName())) + UIDropDownMenu_AddButton(info) end - - -- Set dropdown height based on content - mailDropdown:SetHeight(math.abs(yOffset) + 8) - - -- Show dropdown - mailDropdown:Show() end -- Toggle bank dropdown function Guda_BagFrame_ToggleBankDropdown(button) --- Hide character dropdown if it's shown - if characterDropdown and characterDropdown:IsShown() then - characterDropdown:Hide() + local menuFrame = getglobal("Guda_BagBankMenu") + if not menuFrame then + menuFrame = CreateFrame("Frame", "Guda_BagBankMenu", UIParent, "UIDropDownMenuTemplate") end - - if bankDropdown and bankDropdown:IsShown() then - bankDropdown:Hide() - return - end - - if not bankDropdown then - -- Create dropdown frame - bankDropdown = CreateFrame("Frame", "Guda_BankDropdown", UIParent) - bankDropdown:SetFrameStrata("DIALOG") - bankDropdown:SetWidth(200) - bankDropdown:SetBackdrop({ - bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", - edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", - tile = true, - tileSize = 16, - edgeSize = 16, - insets = { left = 4, right = 4, top = 4, bottom = 4 } - }) - bankDropdown:SetBackdropColor(0, 0, 0, 0.95) - bankDropdown:EnableMouse(true) - bankDropdown:Hide() - - bankDropdown.buttons = {} - end - - -- Position dropdown below the button - bankDropdown:ClearAllPoints() - bankDropdown:SetPoint("TOPLEFT", button, "BOTTOMLEFT", 0, -2) - -- Clear existing buttons - for _, btn in ipairs(bankDropdown.buttons) do - btn:Hide() - end - bankDropdown.buttons = {} - - -- Get all characters on current realm - local chars = addon.Modules.DB:GetAllCharacters(false, true) - - local yOffset = -8 - - -- Add character buttons - for _, char in ipairs(chars) do - -- Capture variables in local scope for closure - local charFullName = char.fullName - local charName = char.name - local charMoney = char.money or 0 - local charClassToken = char.classToken - - local charButton = CreateFrame("Button", nil, bankDropdown) - charButton:SetWidth(188) - charButton:SetHeight(20) - charButton:SetPoint("TOP", bankDropdown, "TOP", 0, yOffset) - - -- Button background on hover - local charBg = charButton:CreateTexture(nil, "BACKGROUND") - charBg:SetAllPoints() - charBg:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") - charBg:SetBlendMode("ADD") - charBg:SetAlpha(0) - - -- Get class color - local classColor = charClassToken and RAID_CLASS_COLORS[charClassToken] - local r, g, b = 1, 1, 1 - if classColor then - r, g, b = classColor.r, classColor.g, classColor.b - end - - -- Button text - local charText = charButton:CreateFontString(nil, "ARTWORK", "GameFontHighlight") - charText:SetPoint("LEFT", charButton, "LEFT", 8, 0) - charText:SetText(charName) - charText:SetTextColor(r, g, b) - - -- Money text - local moneyText = charButton:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") - moneyText:SetPoint("RIGHT", charButton, "RIGHT", -8, 0) - moneyText:SetText(addon.Modules.Utils:FormatMoney(charMoney)) - moneyText:SetTextColor(0.7, 0.7, 0.7) - - -- Button scripts - charButton:SetScript("OnEnter", function() - charBg:SetAlpha(0.3) - end) - charButton:SetScript("OnLeave", function() - charBg:SetAlpha(0) - end) - charButton:SetScript("OnClick", function() - if charFullName then - -- Show bank for this character - Guda_BagFrame_ShowCharacterBank(charFullName, charName) - bankDropdown:Hide() - else - addon:Print("Error: Character fullName is nil") - end - end) - - table.insert(bankDropdown.buttons, charButton) - yOffset = yOffset - 20 - end - - -- Set dropdown height based on content - bankDropdown:SetHeight(math.abs(yOffset) + 8) - - -- Show dropdown - bankDropdown:Show() + UIDropDownMenu_Initialize(menuFrame, Guda_BagBankMenu_Initialize, "MENU") + ToggleDropDownMenu(1, nil, menuFrame, "cursor", 0, 0) end -- Show character's bank diff --git a/UI/BankFrame.lua b/UI/BankFrame.lua index 2e5c0c8..5aede91 100644 --- a/UI/BankFrame.lua +++ b/UI/BankFrame.lua @@ -81,6 +81,18 @@ function Guda_BankFrame_OnShow(self) BankFrame:Update() end +-- OnHide +function Guda_BankFrame_OnHide(self) + -- Close any open dropdown menus when the bank frame is hidden + CloseDropDownMenus() + + -- Close the actual Blizzard bank too + local blizzardBankFrame = getglobal("BankFrame") + if blizzardBankFrame and blizzardBankFrame:IsShown() then + CloseBankFrame() + end +end + -- Toggle visibility function BankFrame:Toggle() if Guda_BankFrame:IsShown() then diff --git a/UI/BankFrame.xml b/UI/BankFrame.xml index 5db740e..e4e1af4 100644 --- a/UI/BankFrame.xml +++ b/UI/BankFrame.xml @@ -648,11 +648,7 @@ Guda_BankFrame_OnShow(this) - Guda_BankFrame:Hide() - -- Close the actual Blizzard bank too - if BankFrame and BankFrame:IsShown() then - CloseBankFrame() - end + Guda_BankFrame_OnHide(this) -- Clear search box focus when clicking on bank frame diff --git a/UI/MailboxFrame.lua b/UI/MailboxFrame.lua index b18fc0e..ec3206d 100644 --- a/UI/MailboxFrame.lua +++ b/UI/MailboxFrame.lua @@ -61,6 +61,12 @@ function Guda_MailboxFrame_OnShow(self) MailboxFrame:Update() end +-- OnHide +function Guda_MailboxFrame_OnHide(self) + -- Close any open dropdown menus when the mailbox frame is hidden + CloseDropDownMenus() +end + -- Toggle visibility function MailboxFrame:Toggle() if Guda_MailboxFrame:IsShown() then @@ -71,6 +77,10 @@ function MailboxFrame:Toggle() end -- Show specific character's mailbox +function MailboxFrame:GetCurrentViewChar() + return currentViewChar +end + function MailboxFrame:ShowCharacter(fullName) currentViewChar = fullName currentPage = 1 diff --git a/UI/MailboxFrame.xml b/UI/MailboxFrame.xml index 3ae8c80..4b54022 100644 --- a/UI/MailboxFrame.xml +++ b/UI/MailboxFrame.xml @@ -325,6 +325,9 @@ Guda_MailboxFrame_OnShow(this) + + Guda_MailboxFrame_OnHide(this) + this:StartMoving() From 8b1a0b4b7a671cf68569ce7e1132a7cb9ddd62e5 Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sat, 27 Dec 2025 23:33:37 +0400 Subject: [PATCH 11/26] fix: mailbox item position --- UI/MailboxFrame.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/UI/MailboxFrame.lua b/UI/MailboxFrame.lua index ec3206d..5b84e87 100644 --- a/UI/MailboxFrame.lua +++ b/UI/MailboxFrame.lua @@ -203,10 +203,10 @@ function MailboxFrame:DisplayItems(items, charFullName, totalMails) itemButton.mailIndex = mail.mailIndex itemButton.mailItemIndex = mail.itemIndex or 1 - -- Force left alignment in case template or other code changed anchors + -- Center vertically in the row if itemButton then itemButton:ClearAllPoints() - itemButton:SetPoint("TOPLEFT", row, "TOPLEFT", 10, -8) + itemButton:SetPoint("LEFT", row, "LEFT", 10, 0) end if mail.item and (mail.item.texture or mail.item.link) then @@ -240,9 +240,9 @@ function MailboxFrame:DisplayItems(items, charFullName, totalMails) -- Row Money Frame local moneyFrame = getglobal(row:GetName() .. "_MoneyFrame") if moneyFrame then - -- Force left alignment for the money frame + -- Restore right alignment for the money frame to avoid overlap with icon moneyFrame:ClearAllPoints() - moneyFrame:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 10, 8) + moneyFrame:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", -10, 8) end if (mail.money or 0) > 0 then MoneyFrame_Update(moneyFrame:GetName(), mail.money) From 58746cb7f72458c2ca9ad2fe4e8a5c969696a3dc Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sat, 27 Dec 2025 23:57:21 +0400 Subject: [PATCH 12/26] Envelope icon --- Assets/Envelope.blp | Bin 0 -> 6676 bytes UI/BagFrame.xml | 2 +- UI/BankFrame.xml | 2 +- UI/MailboxFrame.xml | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 Assets/Envelope.blp diff --git a/Assets/Envelope.blp b/Assets/Envelope.blp new file mode 100644 index 0000000000000000000000000000000000000000..5d5ea4f5d1ef1e7badb477ab7a9327b9c3f9e433 GIT binary patch literal 6676 zcmeI0e{fS(7RPUs{zzJ@K`mC1@l_g@mWd#4fn5fDEemVW9jVjXq!`kdF#M{3nU3Af zv`8Oe#I;%rtQDcGK3bKMSPlK zc6Mj~;!WOkK0Wu>`QCfaz31gUx@1-yLP(`aieurg{JtCy@9prW!+R$}et7%f{SMyv zyAZnl7ha@;MxYG@4a@DxIQ|8^@!U}0h5|Pf_<02qe)R7f`~yJ<%HQ6n-4=K3HwfW{ z%L>yaPio6?LaY#Eu|+(fzT!ENTy=#Y2cyAt7|Z1|wu8}%KEDwBCj~AuAB0eADDQl&b5xmhcNO?;nAv}jv z2$bIvziM^3TnfjBxqQpG_S$6IH5a zA`LpjAha$GTQoNC_npbXk7Ifm@q-II)+aoxp0H{V3cy3hlX!yF1?tjrh6psa=JU+Q z3BSF}7V=ViArY$8+EhK|S)@iX8cWQSYosTos?yM5%8Nk{lfxdO+#zm>RFNKqkBRzl zrPRbz`^rdPZ*XX^pYlRrX+ToSFAO{VQh$F1wd=!qo$8W6gfgr@C(3py_C1G?rpRn5 zndJW4AA*~rU*>|pMq|OUth1gKQ)aI}6Z3yzc{#1W?kc0p$;natD{vVyVC9q?dBGbgvI)&q5jY%OT(yW2_JJ{U=XSmi>3R4i8b* zhx>Z@Pd?c|xkGI4#iFQ;PahVF5#k-x9w&176_zhrC^L<$uEb)tEPkx^nVucbbj$uP zgJcr{W{ep6)1+$~)u%EzLt+vRd+QO<()UCCqq9TUA)-Z3^F zZ#9!^-eFMM6AtkOhV0~IT3;w;btaQ($%6bJ`af~lv%YW9`aU_Hm;Y<$Hd=lHVbW9K zp`0x}Cx*DyioWNgn>z)e!A9*XBi`+HQ`Zafe)%36YVU^H-=2)VP%SKt86d z`lGx&TAnFmbxW5%zT~%TZM0|a*!&P3I2Zx4=q1|2-8wiwaR+FAcl4!WDwxeY}tq<|tX-I*iaCCp2jfSj6 zCR;V-IA3UuD#egP+181CT;=K4DQEaXR{N4A-S4!-&g*-aGDc%In~Ux(JUBUcFf}+5 z|BQvI5&yq#kNIDqjOUmrOq-vhSI+xpDYuhvB8q*l*yu;DwgBxfOA=a>T@^~aIwrCe zUazf$+RsH%88*}?`{`Vi%f}Y8a$dCe^3FPgT20IAA-)e1BcZ%9;`Hlt+C<8kVXw8z zWM?UdTKNJ5f>4I_P~6?!E6ruex381$??G^XGn*~2z943edk8wd?e61snMc9@^70k< zf&_Y9t0l1hbov&o_-pg?3{n}C{$acoFgq=@zs#_$v*DskDX$Ns`jc`$qw}{g%vaZ% z1_jD-KB0K&iDbpz==Zk9sho;_?Y$X3)Lu7zD>_6rcM##<1I2og40FbUe#JKUMP>TV?%Ra77v&f2dWSkukkO ztxPF=8QDke)=vIVvdgK=r_~>7J^c4mA!>K}k#9zuIKF&=-OjSqE;zY#o6S}hC$$ZK zTs!;$3-Rsgrx(F`95?x~`T5T{E-{e{!WD|vQeS=8hJZ^Shx^1H`38n`k3js7&w%@s zImQgy^Kz!#e@aUf*Ix&{GM7&RHBdjn*yEG+yM^KM8O@Aw`k{JOmmL_>FT=;BqqZ^L zBg7 zh>wt8=*3B!!S4BMvb?@AE@`-RQjERN-XE)9LG_mB3)=fhyrp(J2*{Uro3z_fGVYi2 z%|Ke9XE3&a*TVf%)TSu+H#y&w)14F1+E~80C=BBnf;F;v5**6#&9s-(VWWV^UFE8W zfgxV%Fy6>R`*}m~fi$l^mJfw)>V#ylF4_^x-;I?UR9B1Ehb=ct+J7*bKwVuLjUV@u zM)&O9Cgr}jU&{I?G`oq~vm{?{QTQ;a68vUKQq@7mob7l5&!SO3hs@Z&E(f$CMpu_HH9gw}sc+~oFd98&I! z@nOE8a`{jbwKw>Ar&cv~UuyKTWtX z(fAS$V4T)X9-BIw3wozPd?|^=Z-nlv>vJ~d#^N)9Qwyndt-KqO>H}c^5EyMuK|Y8; z$x&tea9e^1l^qy4AIUv(d>(cE9PlN0(9k6T+J+5IR~!9zv=Rx;d2BiICklhLFA z!+Ff0KkdxS=wvjiBk(zJjtl=`!@bLXb zhfBE9xQ=V5h;6z0IryHnd0zmNM5GZ^8|FEo|5C0J6T|)oIQ7KupY;jp&m28nUh~=n d9#$cGqwIr%xA(eSYu1k_y#lg-NN9h;{|4^bZa4q{ literal 0 HcmV?d00001 diff --git a/UI/BagFrame.xml b/UI/BagFrame.xml index 76d6b7a..e56cc85 100644 --- a/UI/BagFrame.xml +++ b/UI/BagFrame.xml @@ -277,7 +277,7 @@ local icon = getglobal(this:GetName().."_Icon") if icon then - icon:SetTexture("Interface\\Icons\\INV_Letter_15") + icon:SetTexture("Interface\\AddOns\\Guda\\Assets\\Envelope") end diff --git a/UI/BankFrame.xml b/UI/BankFrame.xml index e4e1af4..8a6e766 100644 --- a/UI/BankFrame.xml +++ b/UI/BankFrame.xml @@ -136,7 +136,7 @@ local icon = getglobal(this:GetName().."_Icon") if icon then - icon:SetTexture("Interface\\Icons\\INV_Letter_15") + icon:SetTexture("Interface\\AddOns\\Guda\\Assets\\Envelope") end diff --git a/UI/MailboxFrame.xml b/UI/MailboxFrame.xml index 4b54022..8c9e87c 100644 --- a/UI/MailboxFrame.xml +++ b/UI/MailboxFrame.xml @@ -227,7 +227,7 @@ local icon = getglobal(this:GetName().."_Icon") if icon then - icon:SetTexture("Interface\\AddOns\\Guda\\Assets\\All_Characters") + icon:SetTexture("Interface\\AddOns\\Guda\\Assets\\Envelope") end From 4119547d1f3ddc1ed53f509fe0dd5da8b0cc92df Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sun, 28 Dec 2025 00:02:06 +0400 Subject: [PATCH 13/26] feat: offline mailbox --- UI/MailboxFrame.lua | 22 +++++++++++++++++++++- UI/MailboxFrame.xml | 21 ++++++++++----------- UI/SettingsPopup.lua | 11 ++++++++++- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/UI/MailboxFrame.lua b/UI/MailboxFrame.lua index 5b84e87..f1da651 100644 --- a/UI/MailboxFrame.lua +++ b/UI/MailboxFrame.lua @@ -17,8 +17,13 @@ local mailboxClickCatcher = nil -- OnLoad function Guda_MailboxFrame_OnLoad(self) + -- Apply border visibility setting + if MailboxFrame.UpdateBorderVisibility then + MailboxFrame:UpdateBorderVisibility() + end + -- Set up initial backdrop - addon:ApplyBackdrop(self, "DEFAULT_FRAME") + -- (No longer needed here as UpdateBorderVisibility handles it) -- Set up search box placeholder local searchBox = getglobal(self:GetName().."_SearchBar_SearchBox") @@ -53,6 +58,11 @@ end -- OnShow function Guda_MailboxFrame_OnShow(self) + -- Apply border visibility setting + if MailboxFrame.UpdateBorderVisibility then + MailboxFrame:UpdateBorderVisibility() + end + -- Apply frame transparency if Guda_ApplyBackgroundTransparency then Guda_ApplyBackgroundTransparency() @@ -61,6 +71,16 @@ function Guda_MailboxFrame_OnShow(self) MailboxFrame:Update() end +-- Update border visibility +function MailboxFrame:UpdateBorderVisibility() + local hideBorders = addon.Modules.DB:GetSetting("hideBorders") + if hideBorders then + Guda:ApplyBackdrop(Guda_MailboxFrame, "MINIMALIST_BORDER", "DEFAULT") + else + Guda:ApplyBackdrop(Guda_MailboxFrame, "DEFAULT_FRAME", "DEFAULT") + end +end + -- OnHide function Guda_MailboxFrame_OnHide(self) -- Close any open dropdown menus when the mailbox frame is hidden diff --git a/UI/MailboxFrame.xml b/UI/MailboxFrame.xml index 8c9e87c..9975466 100644 --- a/UI/MailboxFrame.xml +++ b/UI/MailboxFrame.xml @@ -136,26 +136,31 @@ - + - + - + - + - + + + + + + @@ -190,12 +195,6 @@ - - - this:SetBackdropBorderColor(0.4, 0.4, 0.4) - this:SetBackdropColor(0.15, 0.15, 0.15, 0.5) - - diff --git a/UI/SettingsPopup.lua b/UI/SettingsPopup.lua index 56f4030..a57b499 100644 --- a/UI/SettingsPopup.lua +++ b/UI/SettingsPopup.lua @@ -368,7 +368,7 @@ function Guda_ApplyBackgroundTransparency() local transparency = Guda.Modules.DB:GetSetting("bgTransparency") or 0.15 local alpha = 1.0 - transparency - local frames = { "Guda_BagFrame", "Guda_BankFrame", "Guda_SettingsPopup", "Guda_QuestItemBar" } + local frames = { "Guda_BagFrame", "Guda_BankFrame", "Guda_MailboxFrame", "Guda_SettingsPopup", "Guda_QuestItemBar" } for _, frameName in ipairs(frames) do local frame = getglobal(frameName) if frame then @@ -603,6 +603,15 @@ function Guda_SettingsPopup_HideBordersCheckbox_OnClick(self) end end + local mailboxFrame = getglobal("Guda_MailboxFrame") + if mailboxFrame then + if isChecked then + Guda:ApplyBackdrop(mailboxFrame, "MINIMALIST_BORDER", "DEFAULT") + else + Guda:ApplyBackdrop(mailboxFrame, "DEFAULT_FRAME", "DEFAULT") + end + end + -- Update border visibility on settings frame if SettingsPopup.UpdateBorderVisibility then SettingsPopup:UpdateBorderVisibility() From 8c17797133bcdc83828e93a40852843350da0d80 Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sun, 28 Dec 2025 00:24:18 +0400 Subject: [PATCH 14/26] fix: money sent/received --- UI/MailboxFrame.lua | 35 +++++++++++++++++++++-------------- UI/MailboxFrame.xml | 2 +- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/UI/MailboxFrame.lua b/UI/MailboxFrame.lua index f1da651..8c604ae 100644 --- a/UI/MailboxFrame.lua +++ b/UI/MailboxFrame.lua @@ -197,6 +197,27 @@ function MailboxFrame:DisplayItems(items, charFullName, totalMails) row:ClearAllPoints() row:SetPoint("TOPLEFT", container, "TOPLEFT", 0, -(rowIndex - 1) * 55) + + -- Row Money Frame + local moneyFrame = getglobal(row:GetName() .. "_MoneyFrame") + if moneyFrame then + -- Ensure row money frame doesn't auto-update to character money + moneyFrame.moneyType = "STATIC" + moneyFrame:UnregisterAllEvents() + moneyFrame:SetScript("OnShow", nil) -- Disable Blizzard's auto-update OnShow + moneyFrame:SetScript("OnEvent", nil) + + -- Restore right alignment for the money frame to avoid overlap with icon + moneyFrame:ClearAllPoints() + moneyFrame:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", -10, 8) + end + + if (mail.money or 0) > 0 then + moneyFrame:Show() + MoneyFrame_Update(moneyFrame:GetName(), mail.money) + else + if moneyFrame then moneyFrame:Hide() end + end -- Fill row data getglobal(row:GetName() .. "_Sender"):SetText(mail.sender or "Unknown") @@ -257,20 +278,6 @@ function MailboxFrame:DisplayItems(items, charFullName, totalMails) if itemButton.qualityBorder then itemButton.qualityBorder:Hide() end end - -- Row Money Frame - local moneyFrame = getglobal(row:GetName() .. "_MoneyFrame") - if moneyFrame then - -- Restore right alignment for the money frame to avoid overlap with icon - moneyFrame:ClearAllPoints() - moneyFrame:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", -10, 8) - end - if (mail.money or 0) > 0 then - MoneyFrame_Update(moneyFrame:GetName(), mail.money) - moneyFrame:Show() - else - moneyFrame:Hide() - end - row:Show() rowIndex = rowIndex + 1 end diff --git a/UI/MailboxFrame.xml b/UI/MailboxFrame.xml index 9975466..d4b841c 100644 --- a/UI/MailboxFrame.xml +++ b/UI/MailboxFrame.xml @@ -205,7 +205,7 @@ - + From c35260bb44d7fe7f6493ecb72ec2792d33d0cfb2 Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sun, 28 Dec 2025 00:29:45 +0400 Subject: [PATCH 15/26] fix: quest item sort --- Sorting/SortEngine.lua | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Sorting/SortEngine.lua b/Sorting/SortEngine.lua index 3623a60..d7a859c 100644 --- a/Sorting/SortEngine.lua +++ b/Sorting/SortEngine.lua @@ -57,6 +57,7 @@ local CATEGORY_ORDER = { ["Weapon"] = 4, -- Non-equippable weapons ["Armor"] = 5, -- Non-equippable armor ["Tools"] = 6, + ["Quest"] = 7, ["Quiver"] = 8, ["Reagent"] = 9, ["Trade Goods"] = 10, @@ -64,7 +65,6 @@ local CATEGORY_ORDER = { ["Container"] = 12, ["Key"] = 14, ["Miscellaneous"] = 15, - ["Quest"] = 16, ["Junk"] = 17, ["Class Items"] = 18, } @@ -519,15 +519,15 @@ local function AddSortKeys(items) item.equipSlotOrder = EQUIP_SLOT_ORDER[itemSubType] or 999 else item.sortedClass = CATEGORY_ORDER[itemCategory] or 99 - -- Heuristic: Detect items that should be in the Quest category (priority 11) + -- Heuristic: Detect items that should be in the Quest category (priority 7) -- but aren't categorized as such by the game (e.g. some "Manual" items) - if item.sortedClass ~= (CATEGORY_ORDER["Quest"] or 99) then + if item.sortedClass ~= (CATEGORY_ORDER["Quest"] or 7) then local nameLower = item.itemName and string.lower(item.itemName) or "" if string.find(nameLower, "manual") or string.find(nameLower, "quest") then - item.sortedClass = 11 + item.sortedClass = CATEGORY_ORDER["Quest"] or 7 elseif IsQuestItemTooltip(item.bagID, item.slot) then -- If name-based heuristic fails, check tooltip - item.sortedClass = 11 + item.sortedClass = CATEGORY_ORDER["Quest"] or 7 end end item.equipSlotOrder = 999 @@ -670,6 +670,13 @@ local function SortItems(items) if ra ~= rb then return ra > rb end + elseif a.isQuest ~= b.isQuest then + -- If only one is a quest item and they are in the same sortedClass, + -- put the quest item first within that class (or just let sortedClass handle it) + -- Since we moved Quest to its own class 7, this might not be needed unless + -- they are in different classes but one is marked isQuest. + -- However, if they have same sortedClass, we want quest items first. + return a.isQuest end if a.sortedSubclass ~= b.sortedSubclass then return a.sortedSubclass < b.sortedSubclass From 89db6d04fae39f56422a09d8278fd68943bfcbd3 Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sun, 28 Dec 2025 00:33:55 +0400 Subject: [PATCH 16/26] feat: quality item border for mailbox --- UI/ItemButton.lua | 12 +++++++----- UI/MailboxFrame.lua | 11 +++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/UI/ItemButton.lua b/UI/ItemButton.lua index 758fa5d..99a1562 100644 --- a/UI/ItemButton.lua +++ b/UI/ItemButton.lua @@ -774,7 +774,7 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha -- Special border for keyring items (cyan/blue) self.qualityBorder:SetBackdropBorderColor(0.2, 0.8, 1.0, 1) self.qualityBorder:Show() - elseif itemQuality and itemLink then + elseif itemQuality then -- Check settings to determine if we should show borders (nil-safe) local showEquipmentBorder, showOtherBorder if addon and addon.Modules and addon.Modules.DB and addon.Modules.DB.GetSetting then @@ -795,10 +795,12 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha -- Check if item is equipment (nil-safe) local isEquipment = false - if addon and addon.Modules and addon.Modules.Utils and addon.Modules.Utils.IsEquipment then - isEquipment = addon.Modules.Utils:IsEquipment(itemLink) - elseif Guda and Guda.Modules and Guda.Modules.Utils and Guda.Modules.Utils.IsEquipment then - isEquipment = Guda.Modules.Utils:IsEquipment(itemLink) + if itemLink then + if addon and addon.Modules and addon.Modules.Utils and addon.Modules.Utils.IsEquipment then + isEquipment = addon.Modules.Utils:IsEquipment(itemLink) + elseif Guda and Guda.Modules and Guda.Modules.Utils and Guda.Modules.Utils.IsEquipment then + isEquipment = Guda.Modules.Utils:IsEquipment(itemLink) + end end -- Determine if we should show the border based on item type and settings diff --git a/UI/MailboxFrame.lua b/UI/MailboxFrame.lua index 8c604ae..4f8eb29 100644 --- a/UI/MailboxFrame.lua +++ b/UI/MailboxFrame.lua @@ -220,8 +220,15 @@ function MailboxFrame:DisplayItems(items, charFullName, totalMails) end -- Fill row data - getglobal(row:GetName() .. "_Sender"):SetText(mail.sender or "Unknown") - getglobal(row:GetName() .. "_Subject"):SetText(mail.subject or "No Subject") + local sender = mail.sender or "Unknown" + getglobal(row:GetName() .. "_Sender"):SetText(sender) + + local subject = mail.subject or "No Subject" + if mail.item and mail.item.quality then + local r, g, b = addon.Modules.Utils:GetQualityColor(mail.item.quality) + subject = addon.Modules.Utils:ColorText(subject, r, g, b) + end + getglobal(row:GetName() .. "_Subject"):SetText(subject) local expireText = "" if mail.daysLeft then From 0b1b3e2dc05660c1c9c8d95328b6e326e23d2dcf Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sun, 28 Dec 2025 00:52:51 +0400 Subject: [PATCH 17/26] fix: tooltip --- Core/Database.lua | 53 ++++++++++++++++++++++++++++++++++++ Core/Tooltip.lua | 11 +++++++- Core/Utils.lua | 11 +++++--- Data/MailboxScanner.lua | 60 +++++++++++++++++++++-------------------- UI/ItemButton.lua | 15 ++++++++++- 5 files changed, 116 insertions(+), 34 deletions(-) diff --git a/Core/Database.lua b/Core/Database.lua index 50bb90c..f77621e 100644 --- a/Core/Database.lua +++ b/Core/Database.lua @@ -319,6 +319,59 @@ function DB:GetCharacterMailbox(fullName) return char and char.mailbox or {} end +-- Find an item ID and link by name in any character's data +function DB:FindItemByName(name) + if not name or name == "" or not Guda_DB or not Guda_DB.characters then return nil, nil end + + for fullName, char in pairs(Guda_DB.characters) do + -- Check bags + if char.bags then + for bagID, bagData in pairs(char.bags) do + if type(bagData) == "table" and bagData.slots then + for slotID, item in pairs(bagData.slots) do + if item and item.name == name and item.link then + local itemID = addon.Modules.Utils:ExtractItemID(item.link) + if itemID then return itemID, item.link end + end + end + end + end + end + -- Check bank + if char.bank then + for bagID, bagData in pairs(char.bank) do + if type(bagData) == "table" and bagData.slots then + for slotID, item in pairs(bagData.slots) do + if item and item.name == name and item.link then + local itemID = addon.Modules.Utils:ExtractItemID(item.link) + if itemID then return itemID, item.link end + end + end + end + end + end + -- Check equipped + if char.equipped then + for slot, item in pairs(char.equipped) do + if item and item.name == name and item.link then + local itemID = addon.Modules.Utils:ExtractItemID(item.link) + if itemID then return itemID, item.link end + end + end + end + -- Check mailbox + if char.mailbox then + for _, mail in ipairs(char.mailbox) do + if mail.item and mail.item.name == name and mail.item.link then + local itemID = addon.Modules.Utils:ExtractItemID(mail.item.link) + if itemID then return itemID, mail.item.link end + end + end + end + end + return nil, nil +end + -- Get character's equipped items function DB:GetCharacterEquipped(fullName) local char = Guda_DB.characters[fullName] diff --git a/Core/Tooltip.lua b/Core/Tooltip.lua index b9201e9..c5539ce 100644 --- a/Core/Tooltip.lua +++ b/Core/Tooltip.lua @@ -7,7 +7,10 @@ addon.Modules.Tooltip = Tooltip -- Helper function to get item ID from link (Lua 5.0 compatible) local function GetItemIDFromLink(link) if not link then return nil end - local _, _, itemID = strfind(link, "item:(%d+):?") + if type(link) == "number" then return link end + + -- Try to find itemID in a standard link or a raw item:ID string + local _, _, itemID = string.find(link, "item:(%d+)") return itemID and tonumber(itemID) or nil end local function CountCurrentCharacterItems(itemID) @@ -277,6 +280,12 @@ function Tooltip:AddInventoryInfo(tooltip, link) return end + -- Guard against double-adding for the same item on the same tooltip + if tooltip.GudaInventoryAdded == itemID then + return + end + tooltip.GudaInventoryAdded = itemID + local totalBags = 0 local totalBank = 0 local totalMail = 0 diff --git a/Core/Utils.lua b/Core/Utils.lua index 9951ea7..6e65be3 100644 --- a/Core/Utils.lua +++ b/Core/Utils.lua @@ -103,15 +103,20 @@ scanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE") function Utils:GetInboxItemLink(index, itemIndex) -- Try global function first (if it exists on this server/version) if GetInboxItemLink then - local link = GetInboxItemLink(index) + -- Turtle WoW might support (index, itemIndex) for multiple attachments + local link = GetInboxItemLink(index, itemIndex or 1) + if link then return link end + + -- Fallback to single argument if that failed + link = GetInboxItemLink(index) if link then return link end end -- In 1.12.1, GameTooltip:GetHyperlink() does not exist. -- Let's try to use GetItemInfo(name) as the primary way. - local name = GetInboxItem(index, itemIndex) + local name, texture, count, quality = GetInboxItem(index, itemIndex or 1) if name then - local _, link = GetItemInfo(name) + local itemName, link = GetItemInfo(name) if link then return link end diff --git a/Data/MailboxScanner.lua b/Data/MailboxScanner.lua index f513b65..7f70e3d 100644 --- a/Data/MailboxScanner.lua +++ b/Data/MailboxScanner.lua @@ -36,46 +36,48 @@ function MailboxScanner:ScanMailItemRows(index) local rows = {} if hasItem then - local itemIndex = 1 - -- GetInboxItem(index, itemIndex) returns: name, texture, count, quality, canUse - local name, texture, count, quality, canUse = GetInboxItem(index, itemIndex) - if name then - local itemLink = addon.Modules.Utils:GetInboxItemLink(index, itemIndex) + -- Turtle WoW supports up to 12 attachments per mail + for itemIndex = 1, 12 do + -- GetInboxItem(index, itemIndex) returns: name, texture, count, quality, canUse + local name, texture, count, quality, canUse = GetInboxItem(index, itemIndex) + if not name then break end + local itemLink = addon.Modules.Utils:GetInboxItemLink(index, itemIndex) + local itemID = itemLink and addon.Modules.Utils:ExtractItemID(itemLink) + + -- Fallback 1: If link/itemID is missing, try GetItemInfo(name) which might be cached now + if not itemID or not itemLink then + local _, link = GetItemInfo(name) + if link then + itemLink = link + itemID = addon.Modules.Utils:ExtractItemID(link) + addon:Debug("Recovered link from GetItemInfo for %s", name) + end + end + + -- Fallback 2: If still missing, try to recover from existing database (any character) + if not itemID or not itemLink then + itemID, itemLink = addon.Modules.DB:FindItemByName(name) + if itemID then + addon:Debug("Recovered link from DB for %s", name) + end + end + local itemData = { link = itemLink, texture = texture or "Interface\\Icons\\INV_Misc_Bag_08", count = count or 1, quality = quality or 0, name = name, - itemID = addon.Modules.Utils:ExtractItemID(itemLink), + itemID = itemID, } - -- Fallback: If link/itemID is missing, try to recover from existing database - if not itemData.itemID or not itemData.link then - local existingMailbox = addon.Modules.DB:GetCharacterMailbox(addon.Modules.DB:GetPlayerFullName()) - for _, oldMail in ipairs(existingMailbox) do - -- Match by sender, subject and item name - if oldMail.sender == sender and oldMail.subject == subject and oldMail.item and oldMail.item.name == name then - if not itemData.link and oldMail.item.link then - itemData.link = oldMail.item.link - addon:Debug("Recovered link from database for %s", name) - end - if not itemData.itemID and oldMail.item.itemID then - itemData.itemID = oldMail.item.itemID - addon:Debug("Recovered itemID from database for %s", name) - end - if itemData.link and itemData.itemID then break end - end - end - end - - -- If we still don't have itemID but have a link (or vice versa), fix it - if itemData.link and not itemData.itemID then - itemData.itemID = addon.Modules.Utils:ExtractItemID(itemData.link) + -- Ensure we have a link if we have an itemID + if itemData.itemID and not itemData.link then + itemData.link = "item:" .. itemData.itemID .. ":0:0:0" end - -- If we have a link, try to get more detailed info + -- If we have a link, try to get more detailed info from cache if itemData.link then local itemName, link, itemQuality, iLevel, itemCategory, itemType, itemStackCount, itemSubType, itemTexture, itemEquipLoc, itemSellPrice = addon.Modules.Utils:GetItemInfo(itemData.link) if itemName then diff --git a/UI/ItemButton.lua b/UI/ItemButton.lua index 99a1562..ec4bae8 100644 --- a/UI/ItemButton.lua +++ b/UI/ItemButton.lua @@ -1004,6 +1004,7 @@ function Guda_ItemButton_OnEnter(self) -- Add mailbox metadata if available local mailData = self.mailData + if mailData then -- If we already added a line (for money/generic), or SetHyperlink/SetInboxItem added lines, -- we might want a separator if we're adding sender info. @@ -1027,6 +1028,17 @@ function Guda_ItemButton_OnEnter(self) GameTooltip:AddLine("Days left: " .. math.floor(mailData.daysLeft), 0.5, 0.5, 0.5) end end + + -- Add Inventory counts for mailbox items at the very bottom + if addon.Modules.Tooltip and addon.Modules.Tooltip.AddInventoryInfo then + local link = self.itemData and (self.itemData.link or (self.itemData.itemID and ("item:" .. self.itemData.itemID .. ":0:0:0"))) + if not link and self.mailIndex and isMailboxOpen then + link = addon.Modules.Utils:GetInboxItemLink(self.mailIndex, self.mailItemIndex or 1) + end + if link then + addon.Modules.Tooltip:AddInventoryInfo(GameTooltip, link) + end + end GameTooltip:Show() return @@ -1098,8 +1110,9 @@ end -- OnLeave handler function Guda_ItemButton_OnLeave(self) -- Clear any viewed character hint on the tooltip when leaving - if GameTooltip and GameTooltip.GudaViewedCharacter then + if GameTooltip then GameTooltip.GudaViewedCharacter = nil + GameTooltip.GudaInventoryAdded = nil end GameTooltip:Hide() ResetCursor() From d4b1b73dd2ba9fb115c52c799778adb86288b453 Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sun, 28 Dec 2025 00:56:51 +0400 Subject: [PATCH 18/26] fix: item rows --- Core/Tooltip.lua | 14 +++++++++++++- Data/MailboxScanner.lua | 16 ++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Core/Tooltip.lua b/Core/Tooltip.lua index c5539ce..ba118f5 100644 --- a/Core/Tooltip.lua +++ b/Core/Tooltip.lua @@ -93,7 +93,19 @@ local function CountCurrentCharacterItems(itemID) for i = 1, numInboxItems do local _, _, _, _, _, _, _, hasItem = GetInboxHeaderInfo(i) if hasItem then - for j = 1, 12 do -- Turtle WoW supports up to 12 attachments + -- Turtle WoW supports up to 12 attachments per mail. + -- We use GetInboxNumAttachments if available to avoid over-scanning. + local numAttachments = 0 + if GetInboxNumAttachments then + numAttachments = GetInboxNumAttachments(i) or 0 + end + + -- Fallback: if we don't have the count but header says there's an item, assume at least 1. + if numAttachments == 0 and hasItem then + numAttachments = 1 + end + + for j = 1, numAttachments do -- Turtle WoW supports up to 12 attachments local name, _, count = GetInboxItem(i, j) if name then local itemLink = addon.Modules.Utils:GetInboxItemLink(i, j) diff --git a/Data/MailboxScanner.lua b/Data/MailboxScanner.lua index 7f70e3d..bcf2644 100644 --- a/Data/MailboxScanner.lua +++ b/Data/MailboxScanner.lua @@ -36,8 +36,20 @@ function MailboxScanner:ScanMailItemRows(index) local rows = {} if hasItem then - -- Turtle WoW supports up to 12 attachments per mail - for itemIndex = 1, 12 do + -- Turtle WoW supports up to 12 attachments per mail. + -- We use GetInboxNumAttachments if available to avoid over-scanning on servers + -- that might ignore the second argument of GetInboxItem. + local numAttachments = 0 + if GetInboxNumAttachments then + numAttachments = GetInboxNumAttachments(index) or 0 + end + + -- Fallback: if we don't have the count but header says there's an item, assume at least 1. + if numAttachments == 0 and hasItem then + numAttachments = 1 + end + + for itemIndex = 1, numAttachments do -- GetInboxItem(index, itemIndex) returns: name, texture, count, quality, canUse local name, texture, count, quality, canUse = GetInboxItem(index, itemIndex) if not name then break end From 8c507b34e7a3d1b9211f608e4a0fe209741ce0b3 Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sun, 28 Dec 2025 01:54:34 +0400 Subject: [PATCH 19/26] backdrop color header/footer --- UI/BagFrame.xml | 9 --------- UI/BankFrame.xml | 9 --------- 2 files changed, 18 deletions(-) diff --git a/UI/BagFrame.xml b/UI/BagFrame.xml index e56cc85..68f0e85 100644 --- a/UI/BagFrame.xml +++ b/UI/BagFrame.xml @@ -700,15 +700,6 @@ - - - - - - - - - diff --git a/UI/BankFrame.xml b/UI/BankFrame.xml index 8a6e766..440c5ac 100644 --- a/UI/BankFrame.xml +++ b/UI/BankFrame.xml @@ -619,15 +619,6 @@ - - - - - - - - - -- Clear search focus when clicking in item area From 6888e91cbc6768a906d76c053dd9a13c2fdeebaf Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sun, 28 Dec 2025 01:56:03 +0400 Subject: [PATCH 20/26] fix: remove mailbox icon from bankframe --- UI/BankFrame.xml | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/UI/BankFrame.xml b/UI/BankFrame.xml index 440c5ac..225f974 100644 --- a/UI/BankFrame.xml +++ b/UI/BankFrame.xml @@ -107,52 +107,6 @@ - - -