Mercurial > wow > inventory
view Modules/Scanner.lua @ 144:12a8ea5af671
Added a ?remove? button to the crafting queue.
When removing an item from the queue or when it is finished casting (when using the Inventorium queue processer), items are moved to the ?unqueuables? window.
Fixed auction price checking.
Now resetting filters before scanning the tradeskill recipes.
author | Zerotorescue |
---|---|
date | Wed, 19 Jan 2011 23:21:16 +0100 |
parents | 56f33abee1e3 |
children | 07263a435f3c |
line wrap: on
line source
local addon = select(2, ...); local mod = addon:NewModule("Scanner", "AceEvent-3.0", "AceTimer-3.0"); local _G = _G; local select, pairs = _G.select, _G.pairs; local twipe, tinsert, mceil = _G.table.wipe, _G.table.insert, _G.math.ceil; local Mover, paused, currentLocation; local itemCache = {}; local function OnMoveAccept() mod:Pause(); Mover:BeginMove(currentLocation, mod.Unpause); if InventoriumItemMover then InventoriumItemMover:Hide(); end end local function OnMoveCancel() Mover:ResetQueue(); currentLocation = nil; if InventoriumItemMover then InventoriumItemMover:Hide(); end end local function GetSmallCoinTextureString(coins) if coins >= 10000000 then -- When we have1000g, hide silver and copper coins = (mceil(coins / 10000) * 10000); elseif coins >= 10000 then -- When we have 1g, hide copper coins = (mceil(coins / 100) * 100); end return GetCoinTextureString(coins); end -- Refill moves window: refill form storage such as the bank, guild bank and mailbox local function UseStorageRefillST() if not InventoriumItemMover then addon:CreateMoverFrame(); end local frame = InventoriumItemMover; -- both for speed as code-consistency -- Scrolling table with a list of items to be moved local scrollTableWidth = ( frame.frmMeasureDummy:GetWidth() - 30 ); -- adjust width by the scrollbar size local headers = { { ["name"] = "Item", ["width"] = (scrollTableWidth * .60), ["defaultsort"] = "asc", ["comparesort"] = function(this, aRow, bRow, column) local aName, _, aRarity = GetItemInfo(this:GetRow(aRow).rowData.itemId); local bName, _, bRarity = GetItemInfo(this:GetRow(bRow).rowData.itemId); local template = "%d%s"; aName = template:format((10 - (aRarity or 10)), (aName or ""):lower()); bName = template:format((10 - (bRarity or 10)), (bName or ""):lower()); if this.cols[column].sort == "dsc" then return aName > bName; else return aName < bName; end end, ["sort"] = "asc", -- when the data is set, use this column so sort the default data ["tooltipTitle"] = "Item", ["tooltip"] = "Click to sort the list by item quality then item name.", }, { ["name"] = "Moving", ["width"] = (scrollTableWidth * .15), ["align"] = "RIGHT", ["defaultsort"] = "dsc", ["sortnext"] = 1, ["tooltipTitle"] = "Moving", ["tooltip"] = "Click to sort the list by the amount of movable items.", }, { ["name"] = "Available", ["width"] = (scrollTableWidth * .25), ["align"] = "RIGHT", ["defaultsort"] = "dsc", ["sortnext"] = 1, ["comparesort"] = function(this, aRow, bRow, column) local aAvailablePercent = (this:GetRow(aRow).rowData.available / this:GetRow(aRow).rowData.missing); local bAvailablePercent = (this:GetRow(bRow).rowData.available / this:GetRow(bRow).rowData.missing); if this.cols[column].sort == "dsc" then return aAvailablePercent > bAvailablePercent; else return aAvailablePercent < bAvailablePercent; end end, ["tooltipTitle"] = "Item", ["tooltip"] = "Click to sort the list by the availibility percentage.", }, }; local proceedButton = { text = "Move Items", tooltipTitle = "Move Items", tooltip = "Start moving these items from the bank.", onClick = OnMoveAccept, }; local cancelButton = { text = "Cancel", tooltipTitle = "Cancel", tooltip = "Do not move anything and close the window.", onClick = OnMoveCancel, }; addon:SetMoverFrameSettings("Inventorium Storage Refill", "The items listed below can be refilled from this location, do you wish to move them to your bags?", proceedButton, cancelButton, headers); end -- Merchant restock window: restock from a merchant by buying items needed local function UseMerchantRestockST(totalCost) if not InventoriumItemMover then addon:CreateMoverFrame(); end local frame = InventoriumItemMover; -- both for speed as code-consistency -- Scrolling table with a list of items to be moved local scrollTableWidth = ( frame.frmMeasureDummy:GetWidth() - 30 ); -- adjust width by the scrollbar size local headers = { { ["name"] = "Item", ["width"] = (scrollTableWidth * .60), ["defaultsort"] = "asc", ["comparesort"] = function(this, aRow, bRow, column) local aName, _, aRarity = GetItemInfo(this:GetRow(aRow).rowData.itemId); local bName, _, bRarity = GetItemInfo(this:GetRow(bRow).rowData.itemId); local template = "%d%s"; aName = template:format((10 - (aRarity or 10)), (aName or ""):lower()); bName = template:format((10 - (bRarity or 10)), (bName or ""):lower()); if this.cols[column].sort == "dsc" then return aName > bName; else return aName < bName; end end, ["sort"] = "asc", -- when the data is set, use this column so sort the default data ["tooltipTitle"] = "Item", ["tooltip"] = "Click to sort the list by item quality then item name.", }, { ["name"] = "Buying", ["width"] = (scrollTableWidth * .20), ["align"] = "RIGHT", ["defaultsort"] = "dsc", ["sortnext"] = 1, ["tooltipTitle"] = "Buying", ["tooltip"] = "Click to sort the list by the amount of purchasable items.", }, { ["name"] = "Cost", ["width"] = (scrollTableWidth * .20), ["align"] = "RIGHT", ["defaultsort"] = "dsc", ["sortnext"] = 1, ["tooltipTitle"] = "Cost", ["tooltip"] = "Click to sort the list by the total cost of buying all these items.", }, }; local proceedButton = { text = "Purchase Items", tooltipTitle = "Purchase Items", tooltip = "Start purchasing these items from this merchant.", onClick = OnMoveAccept, }; local cancelButton = { text = "Cancel", tooltipTitle = "Cancel", tooltip = "Do not purchase anything and close the window.", onClick = OnMoveCancel, }; addon:SetMoverFrameSettings("Inventorium Merchant Restock", ("The following items can be restocked from this merchant for a total of %s. Do you wish to proceed?"):format(GetSmallCoinTextureString(totalCost)), proceedButton, cancelButton, headers); end function mod:ClearCache() twipe(itemCache); end function mod:CacheLocation(location, remember) -- Reset cache just in case it was filled self:ClearCache(); if location == addon.Locations.Bag or location == addon.Locations.Bank then local start, stop; if location == addon.Locations.Bag then start = 0; stop = NUM_BAG_SLOTS; else -- If we requested the bank then we don't want the bag info start = ( NUM_BAG_SLOTS + 1 ); stop = ( NUM_BAG_SLOTS + NUM_BANKBAGSLOTS ); end -- Go through all our bags, including the backpack for i = start, ((location == addon.Locations.Bag and stop) or (location == addon.Locations.Bank and (stop + 1))) do -- if scanning bags stop at normal bag slot, if scanning bank, stop one later to allow BANK_CONTAINER to be scanned too -- Scan the default 100 slots whenever we're at a non-existing index local bagId = (i == (stop + 1) and BANK_CONTAINER) or i; local slotId = GetContainerNumSlots(bagId); while slotId ~= 0 do -- A not equal-comparison should be quicker than a larger than-comparison local itemId = GetContainerItemID(bagId, slotId); local itemCount = itemId and select(2, GetContainerItemInfo(bagId, slotId)); if itemId and itemCount and itemCount > 0 then local containerItem; if not itemCache[itemId] then -- If this is the first time we see this item, make a new object containerItem = addon.ContainerItem:New(); itemCache[itemId] = containerItem; else -- If we had this item in another slot too containerItem = itemCache[itemId]; end containerItem:AddLocation(bagId, slotId, itemCount); end -- Continue scanning a different slot slotId = (slotId - 1); end end elseif location == addon.Locations.Guild then for tabId = 1, GetNumGuildBankTabs() do local _, _, isViewable, _, _, remainingWithdrawals = GetGuildBankTabInfo(tabId); if isViewable and (remainingWithdrawals > 0 or remainingWithdrawals == -1) then local slotId = (MAX_GUILDBANK_SLOTS_PER_TAB or 98); -- start by scanning the last slot while slotId ~= 0 do -- A not equal-comparison should be quicker than a larger than-comparison local itemLink = GetGuildBankItemLink(tabId, slotId); local itemId = itemLink and addon:GetItemId(itemLink); local itemCount = itemLink and select(2, GetGuildBankItemInfo(tabId, slotId)); if itemLink and itemId and itemCount and itemCount > 0 then -- If there is actually an item in this slot local containerItem; if not itemCache[itemId] then -- If this is the first time we see this item, make a new object containerItem = addon.ContainerItem:New(); itemCache[itemId] = containerItem; else -- If we had this item in another slot too containerItem = itemCache[itemId]; end containerItem:AddLocation(tabId, slotId, itemCount); end -- Continue scanning a different slot slotId = (slotId - 1); end end end elseif location == addon.Locations.Mailbox then for mailIndex = 1, GetInboxNumItems() do -- All mail items for attachIndex = 1, ATTACHMENTS_MAX_RECEIVE do -- All attachments local itemLink = GetInboxItemLink(mailIndex, attachIndex); local itemId = itemLink and addon:GetItemId(itemLink); local itemCount = itemLink and select(3, GetInboxItem(mailIndex, attachIndex)); if itemLink and itemId and itemCount and itemCount > 0 then local containerItem; if not itemCache[itemId] then -- If this is the first time we see this item, make a new object containerItem = addon.ContainerItem:New(); itemCache[itemId] = containerItem; else -- If we had this item in another slot too containerItem = itemCache[itemId]; end containerItem:AddLocation(mailIndex, attachIndex, itemCount); end end end elseif location == addon.Locations.Merchant then for itemIndex = 1, GetMerchantNumItems() do -- All merchant items local itemLink = GetMerchantItemLink(itemIndex); local itemId = itemLink and addon:GetItemId(itemLink); local _, _, vendorValue, quantity, numAvailable, _, extendedCost = GetMerchantItemInfo(itemIndex); if itemLink and itemId and numAvailable ~= 0 and not extendedCost then local containerItem; if not itemCache[itemId] then -- If this is the first time we see this item, make a new object containerItem = addon.ContainerItem:New(); containerItem.price = (vendorValue / quantity); -- remember the price for this item. We assume it's the same for this entire item id itemCache[itemId] = containerItem; else -- If we had this item in another slot too containerItem = itemCache[itemId]; end containerItem:AddLocation(1, itemIndex, numAvailable); end end else error("Invalid location provided for CacheLocation."); end if not remember then -- Copy the table as clearing the cache wipes it empty (and tables are passed by reference) local cacheCopy = CopyTable(itemCache); self:ClearCache(); return cacheCopy; end end function mod:Scan(location) -- We might pause the scanning when we invoke moves ourself if paused then addon:Debug("Not scanning; paused..."); return; end local playerName = UnitName("player"); currentLocation = location; self:CacheLocation(location, true); -- Ensure previous queue isn't remaining Mover:ResetQueue(); -- Restock = obtaining more, refill = merely moving local. IsRestock = are we buying/making more? local isRestock = (location == addon.Locations.Merchant); -- Go through all groups for groupName, values in pairs(addon.db.profile.groups) do -- Settings local trackAt = addon:GetOptionByKey(groupName, "trackAtCharacters"); local localItemData = addon:GetOptionByKey(groupName, "localItemData"); local requiredItems, bonusQueue, minCraftingQueue, isRefillEnabled; if isRestock then requiredItems = addon:GetOptionByKey(groupName, "restockTarget"); bonusQueue = addon:GetOptionByKey(groupName, "bonusQueue"); minCraftingQueue = floor( addon:GetOptionByKey(groupName, "minCraftingQueue") * requiredItems ); -- If the minCraftingQueue is 5% and restockTarget is 60, this will result in 3 else isRefillEnabled = addon:GetOptionByKey(groupName, "autoRefill"); requiredItems = addon:GetOptionByKey(groupName, "minLocalStock"); end local isTracked = (trackAt and trackAt[playerName]); -- Is this character interested in this data? local isConsideredLocal = (localItemData and localItemData[location]); -- if this location was checked as local storage, don't refill from it if values.items and isTracked and (isRestock or isRefillEnabled) and not isConsideredLocal then addon:Debug("Scanning |cff00ff00%s|r", groupName); for itemId, _ in pairs(values.items) do -- Find this item in the source local containerItem = itemCache[itemId]; if containerItem then -- Only do all the CPU intensive checks if this item is available -- When restocking use the global item count, when refilling use the local local currentItemCount = ((isRestock and addon:GetItemCount(itemId, groupName)) or addon:GetLocalItemCount(itemId, groupName)); -- Check if we have enough items local (but only do so if this location also has enough available) local missingItems = (requiredItems - currentItemCount); if isRestock and currentItemCount == 0 and bonusQueue and bonusQueue > 0 then -- If we have none left and the bonus queue is enabled, modify the amount to be queued missingItems = floor( ( missingItems * ( bonusQueue + 1 ) ) + .5 ); -- round end if missingItems > 0 and (not isRestock or missingItems >= minCraftingQueue) then -- Check how many are available local availableItems = ((containerItem.totalCount) or 0); -- Calculate how many we'll be moving (less missing than available? use missing, otherwise use available) -- -1 available items indicates unlimited amount, in that case we must cap at missing items local moving = (((availableItems == -1 or missingItems <= availableItems) and missingItems) or availableItems); if availableItems == -1 or availableItems > 0 then addon:Debug("Insufficient %s but this location has %d (moving %d)", IdToItemLink(itemId), availableItems, moving); Mover:AddMove(itemId, moving, missingItems, availableItems, containerItem.price); end end end end end end self:ClearCache(); if Mover:HasMoves() then if addon.db.profile.defaults.autoRefillSkipConfirm then OnMoveAccept(); else local moves = Mover:GetMoves(); -- This table is never copied, just referenced. It is the same for every row. local columns; if isRestock then local totalCost = 0; for _, move in pairs(moves) do totalCost = (totalCost + (move.cost * move.num)); end UseMerchantRestockST(totalCost); columns = { { ["value"] = function(data, cols, realrow, column, table) return IdToItemLink(data[realrow].rowData.itemId); end, }, -- item { ["value"] = function(data, cols, realrow, column, table) return data[realrow].rowData.num; end, }, -- buying { ["value"] = function(data, cols, realrow, column, table) return GetSmallCoinTextureString((data[realrow].rowData.cost * data[realrow].rowData.num)); end, }, -- cost }; else UseStorageRefillST(); columns = { { ["value"] = function(data, cols, realrow, column, table) return IdToItemLink(data[realrow].rowData.itemId); end, }, -- item { ["value"] = function(data, cols, realrow, column, table) return data[realrow].rowData.num; end, }, -- moving { ["value"] = function(data, cols, realrow, column, table) return addon:DisplayItemCount(data[realrow].rowData.available, data[realrow].rowData.missing); -- available / missing end, ["color"] = function(data, cols, realrow, column, table) return ((data[realrow].rowData.available < data[realrow].rowData.missing) and { r = 1, g = 0, b = 0, a = 1 }) or { r = 1, g = 1, b = 1, a = 1 }; end, }, -- missing / available }; end -- Store the list with rows in this local data = {}; for i, move in pairs(moves) do tinsert(data, { ["rowData"] = move, -- this is not a key usually found in a row item and ignored by the library ["cols"] = columns, }); end addon:SetMoverFrameData(data); end end end -- Events -- Player bank function mod:BANKFRAME_OPENED() addon:Debug("Scanner:BANKFRAME_OPENED"); mod:RegisterEvent("BANKFRAME_CLOSED"); -- Scan once when the bank is opened, but no need to scan after mod:Scan(addon.Locations.Bank); end function mod:BANKFRAME_CLOSED() addon:Debug("Scanner:BANKFRAME_CLOSED"); self:ClearCache(); mod:UnregisterEvent("BANKFRAME_CLOSED"); if InventoriumItemMover then InventoriumItemMover:Hide(); end Mover:ResetQueue(); end -- Guild bank local tmrScanGuild, scanned; function mod:GUILDBANKBAGSLOTS_CHANGED() -- This event is spammed the first time the guild bank is opened if not scanned then self:CancelTimer(tmrScanGuild, true); -- silent tmrScanGuild = self:ScheduleTimer("DoScanGuild", 1); end end function mod:DoScanGuild() if not scanned then addon:Debug("Scanner:DoScanGuild"); scanned = true; self:Scan(addon.Locations.Guild); end end function mod:GUILDBANKFRAME_CLOSED() addon:Debug("Scanner:GUILDBANKFRAME_CLOSED"); scanned = nil; self:ClearCache(); self:UnregisterEvent("GUILDBANKFRAME_CLOSED"); self:UnregisterEvent("GUILDBANKBAGSLOTS_CHANGED"); self:CancelTimer(tmrScanGuild, true); -- silent if InventoriumItemMover then InventoriumItemMover:Hide(); end Mover:ResetQueue(); end function mod:GUILDBANKFRAME_OPENED() addon:Debug("Scanner:GUILDBANKFRAME_OPENED"); scanned = nil; -- Get the contents for every tab into our cache for tabId = 1, GetNumGuildBankTabs() do local _, _, isViewable, _, _, remainingWithdrawals = GetGuildBankTabInfo(tabId); if isViewable and (remainingWithdrawals > 0 or remainingWithdrawals == -1) then QueryGuildBankTab(tabId); end end self:RegisterEvent("GUILDBANKFRAME_CLOSED"); self:RegisterEvent("GUILDBANKBAGSLOTS_CHANGED"); end function mod:MERCHANT_SHOW() addon:Debug("Scanner:MERCHANT_SHOW"); self:RegisterEvent("MERCHANT_CLOSED"); self:Scan(addon.Locations.Merchant); end function mod:MERCHANT_CLOSED() addon:Debug("Scanner:MERCHANT_CLOSED"); self:ClearCache(); self:UnregisterEvent("MERCHANT_CLOSED"); if InventoriumItemMover then InventoriumItemMover:Hide(); end Mover:ResetQueue(); end local previousMailCount; function mod:MAIL_SHOW() addon:Debug("Scanner:MAIL_SHOW"); self:RegisterEvent("MAIL_INBOX_UPDATE"); self:RegisterEvent("MAIL_CLOSED"); scanned = nil; previousMailCount = nil; self:Scan(addon.Locations.Mailbox); end function mod:MAIL_INBOX_UPDATE() if not scanned then addon:Debug("Scanner:MAIL_INBOX_UPDATE"); local current, total = GetInboxNumItems(); if not previousMailCount or current > previousMailCount then -- New mail received scanned = true; self:Scan(addon.Locations.Mailbox); end -- Also remember the new mailcount when losing items, otherwise deleting item 50 and getting to 50 again wouldn't trigger a re-scan previousMailCount = current; else addon:Debug("Scanner:MAIL_INBOX_UPDATE skipped, already scanned"); end end function mod:MAIL_CLOSED() addon:Debug("Scanner:MAIL_CLOSED"); previousMailCount = nil; scanned = nil; self:ClearCache(); self:UnregisterEvent("MAIL_INBOX_UPDATE"); self:UnregisterEvent("MAIL_CLOSED"); if InventoriumItemMover then InventoriumItemMover:Hide(); end Mover:ResetQueue(); end function mod:OnEnable() -- Scan once when the bankframe is opened self:RegisterEvent("BANKFRAME_OPENED"); self:RegisterEvent("GUILDBANKFRAME_OPENED"); self:RegisterEvent("MAIL_SHOW"); self:RegisterEvent("MERCHANT_SHOW"); Mover = addon:GetModule("Mover"); end function mod:OnDisable() Mover = nil; currentLocation = nil; paused = nil; -- Bank self:BANKFRAME_CLOSED(); self:UnregisterEvent("BANKFRAME_OPENED"); -- Guild self:GUILDBANKFRAME_CLOSED(); self:UnregisterEvent("GUILDBANKFRAME_OPENED"); -- Mailbox self:MAIL_CLOSED(); self:UnregisterEvent("MAIL_SHOW"); -- Merchant self:MERCHANT_CLOSED(); self:UnregisterEvent("MERCHANT_SHOW"); end function mod:Pause() paused = true; end function mod:Unpause() paused = nil; end