Mercurial > wow > inventory
view Modules/Scanner.lua @ 132:8460855e3d90
Rewrote queueing module to insert a GUI.
Minor mover window changes.
author | Zerotorescue |
---|---|
date | Tue, 18 Jan 2011 00:30:15 +0100 |
parents | ee4672f21586 |
children | 2efe61ca718e |
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); InventoriumItemMover:Hide(); end local function OnMoveCancel() Mover:ResetQueue(); currentLocation = nil; InventoriumItemMover:Hide(); 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() 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"] = (not addon.db.profile.defaults.hideHelp and "Item"), ["tooltip"] = (not addon.db.profile.defaults.hideHelp and "Click to sort the list by item quality then item name."), }, { ["name"] = "Moving", ["width"] = (scrollTableWidth * .15), ["align"] = "RIGHT", ["defaultsort"] = "dsc", ["sortnext"] = 1, ["tooltipTitle"] = (not addon.db.profile.defaults.hideHelp and "Moving"), ["tooltip"] = (not addon.db.profile.defaults.hideHelp and "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"] = (not addon.db.profile.defaults.hideHelp and "Item"), ["tooltip"] = (not addon.db.profile.defaults.hideHelp and "Click to sort the list by the availibility percentage."), }, }; local proceedButton = { text = "Move Items", tooltipTitle = (not addon.db.profile.defaults.hideHelp and "Move Items"), tooltip = (not addon.db.profile.defaults.hideHelp and "Start moving these items from the bank."), onClick = OnMoveAccept, }; local cancelButton = { text = "Cancel", tooltipTitle = (not addon.db.profile.defaults.hideHelp and "Cancel"), tooltip = (not addon.db.profile.defaults.hideHelp and "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) 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"] = (not addon.db.profile.defaults.hideHelp and "Item"), ["tooltip"] = (not addon.db.profile.defaults.hideHelp and "Click to sort the list by item quality then item name."), }, { ["name"] = "Buying", ["width"] = (scrollTableWidth * .20), ["align"] = "RIGHT", ["defaultsort"] = "dsc", ["sortnext"] = 1, ["tooltipTitle"] = (not addon.db.profile.defaults.hideHelp and "Buying"), ["tooltip"] = (not addon.db.profile.defaults.hideHelp and "Click to sort the list by the amount of purchasable items."), }, { ["name"] = "Cost", ["width"] = (scrollTableWidth * .20), ["align"] = "RIGHT", ["defaultsort"] = "dsc", ["sortnext"] = 1, ["tooltipTitle"] = (not addon.db.profile.defaults.hideHelp and "Cost"), ["tooltip"] = (not addon.db.profile.defaults.hideHelp and "Click to sort the list by the total cost of buying all these items."), }, }; local proceedButton = { text = "Purchase Items", tooltipTitle = (not addon.db.profile.defaults.hideHelp and "Purchase Items"), tooltip = (not addon.db.profile.defaults.hideHelp and "Start purchasing these items from this merchant."), onClick = OnMoveAccept, }; local cancelButton = { text = "Cancel", tooltipTitle = (not addon.db.profile.defaults.hideHelp and "Cancel"), tooltip = (not addon.db.profile.defaults.hideHelp and "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"); InventoriumItemMover:Hide(); 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 InventoriumItemMover:Hide(); 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"); InventoriumItemMover:Hide(); 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"); InventoriumItemMover:Hide(); 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"); if not InventoriumItemMover then addon:CreateMoverFrame(); end 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