From e70c7d627fd1c3e4248a2454da2c2a3937ef3cc3 Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Sat, 5 Sep 2026 20:50:16 +0200 Subject: [PATCH] fix: auto-sell manually assigned Junk items --- Core/ManualJunkAutoSell.lua | 164 ++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 Core/ManualJunkAutoSell.lua diff --git a/Core/ManualJunkAutoSell.lua b/Core/ManualJunkAutoSell.lua new file mode 100644 index 0000000..5860eb0 --- /dev/null +++ b/Core/ManualJunkAutoSell.lua @@ -0,0 +1,164 @@ +-- Auto-sell items explicitly assigned by the user to the built-in Junk category. +-- Upstream Guda's vendor pass only sells poor-quality (gray) links, so a white, +-- green, etc. item manually moved to Junk is displayed as junk but never sold. +-- This module handles only those manual non-gray overrides; the original gray +-- junk seller remains unchanged. + +local addon = Guda + +local runner = CreateFrame("Frame") +runner:Hide() +runner.items = nil +runner.index = 0 +runner.waitElapsed = 0 +runner.pollElapsed = 0 +runner.soldCount = 0 + +local function IsMerchantOpen() + return MerchantFrame and MerchantFrame:IsShown() +end + +local function GetItemID(link) + if not link then return nil end + local Utils = addon.Modules and addon.Modules.Utils + if Utils and Utils.ExtractItemID then + return Utils:ExtractItemID(link) + end + local _, _, id = string.find(link, "item:(%d+)") + return id and tonumber(id) or nil +end + +local function IsProtected(itemID) + local DB = addon.Modules and addon.Modules.DB + return DB and DB.IsItemProtected and DB:IsItemProtected(itemID) +end + +local function IsManualJunk(itemID) + if not itemID then return false end + local CategoryManager = addon.Modules and addon.Modules.CategoryManager + if not CategoryManager or not CategoryManager.GetCategories then return false end + local cats = CategoryManager:GetCategories() + return cats and cats.itemOverrides and cats.itemOverrides[itemID] == "Junk" +end + +local function IsGrayLink(link) + return link and string.find(link, "|cff9d9d9d") ~= nil +end + +-- Wait for Guda's existing gray-junk seller to finish before starting this +-- pass, avoiding two vendor loops calling UseContainerItem in the same frame. +local function HasUnprotectedGrayJunk() + for bag = 0, 4 do + local numSlots = GetContainerNumSlots(bag) or 0 + for slot = 1, numSlots do + local link = GetContainerItemLink(bag, slot) + if IsGrayLink(link) then + local itemID = GetItemID(link) + if itemID and not IsProtected(itemID) then + return true + end + end + end + end + return false +end + +local function StopRunner() + runner:Hide() + runner.items = nil + runner.index = 0 + runner.waitElapsed = 0 + runner.pollElapsed = 0 + runner.soldCount = 0 +end + +local function StartManualJunkSale() + local DB = addon.Modules and addon.Modules.DB + if not DB then return end + + local autoVendor = DB:GetSetting("autoVendorJunk") + if autoVendor == nil then autoVendor = true end + if not autoVendor then return end + + local items = {} + for bag = 0, 4 do + local numSlots = GetContainerNumSlots(bag) or 0 + for slot = 1, numSlots do + local link = GetContainerItemLink(bag, slot) + -- Gray items are already handled by Guda's original vendor pass. + if link and not IsGrayLink(link) then + local itemID = GetItemID(link) + if itemID and IsManualJunk(itemID) and not IsProtected(itemID) then + local _, _, locked = GetContainerItemInfo(bag, slot) + if not locked then + table.insert(items, { bag = bag, slot = slot, itemID = itemID }) + end + end + end + end + end + + if table.getn(items) == 0 then return end + + runner.items = items + runner.index = 0 + runner.waitElapsed = 0 + runner.pollElapsed = 0 + runner.soldCount = 0 + runner:Show() +end + +runner:SetScript("OnUpdate", function() + if not IsMerchantOpen() then + StopRunner() + return + end + + -- Poll at 10 Hz while the original gray-item seller is active. A hard + -- two-second ceiling prevents a stuck/locked gray item from blocking the + -- manual-junk pass forever. + if this.waitElapsed < 2.0 then + this.waitElapsed = this.waitElapsed + arg1 + this.pollElapsed = this.pollElapsed + arg1 + if this.pollElapsed < 0.10 then return end + this.pollElapsed = 0 + if HasUnprotectedGrayJunk() then return end + this.waitElapsed = 2.0 + end + + this.index = this.index + 1 + local item = this.items and this.items[this.index] + if not item then + if this.soldCount > 0 then + addon:Debug("Sold %d manually assigned junk item(s)", this.soldCount) + end + StopRunner() + return + end + + -- Revalidate the slot immediately before selling. This protects against + -- inventory changes while the merchant window is open. + local link = GetContainerItemLink(item.bag, item.slot) + local itemID = GetItemID(link) + if link and itemID == item.itemID and not IsGrayLink(link) + and IsManualJunk(itemID) and not IsProtected(itemID) then + local _, _, locked = GetContainerItemInfo(item.bag, item.slot) + if not locked then + UseContainerItem(item.bag, item.slot) + this.soldCount = this.soldCount + 1 + end + end +end) + +local eventFrame = CreateFrame("Frame") +eventFrame:RegisterEvent("MERCHANT_SHOW") +eventFrame:RegisterEvent("MERCHANT_CLOSED") +eventFrame:SetScript("OnEvent", function() + if event == "MERCHANT_SHOW" then + StartManualJunkSale() + elseif event == "MERCHANT_CLOSED" then + StopRunner() + end +end) + +addon:Debug("Manual Junk auto-sell compatibility fix loaded")