Mercurial > wow > inventory
view Modules/Queue.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 | 8eb0f5b5a885 |
children | 6a52272a0e5a |
line wrap: on
line source
local addon = select(2, ...); local mod = addon:NewModule("Queue", "AceEvent-3.0", "AceTimer-3.0"); local _G = _G; local tonumber, tostring, pairs, sformat, smatch, slower, floor, ceil, tinsert, twipe = _G.tonumber, _G.tostring, _G.pairs, _G.string.format, _G.string.match, _G.string.lower, _G.floor, _G.ceil, _G.table.insert, _G.table.wipe; local queue, skipped = {}, {}; -- strings are passed by reference, so it takes no additional memory if one string was used in a thousand tables compared to any other reference type local skipReasons = { ["NOT_CRAFTABLE"] = { "|cff3d3d3dNot in profession|r", -- gray "This item is not part of this profession.", 0, }, ["CAPPED"] = { "|cff66ff33Fully stocked|r", -- lime/green "The recorded item count is above or equal to your minimum restock target setting.", 5, }, ["MIN_CRAFTING_QUEUE"] = { "|cffffff00Min crafting queue|r", -- yellow "The amount of missing items is below or equal to your \"don't queue if I only miss\"-setting.", 10, }, ["LOW_VALUE"] = { "|cffff6633Underpriced|r", -- orange "The recorded auction value of this item is below your price threshold.", 15, }, ["NO_ITEMCOUNT_ADDON"] = { "|cffff0000No itemcount addon|r", -- red "No compatible item count could be found.", 20, }, ["REMOVED"] = { -- because this is updated realtime, it is most useful around the top of the list "|cffff0000Removed|r", -- red "You manually removed this item from the queue.", 45, }, ["FINISHED"] = { -- because this is updated realtime, it is most useful on the top of the list "|cff00ff00Just finished|r", -- green "Just finished restocking this item.", 50, }, }; local function MakeQueueWindow() if not InventoriumQueuer then addon:CreateQueueFrame(); local frame = InventoriumQueuer; -- 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 * .65), ["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 = sformat(template, (10 - (aRarity or 10)), slower(aName or "")); bName = sformat(template, (10 - (bRarity or 10)), slower(bName or "")); 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"] = "Amount", ["width"] = (scrollTableWidth * .15), ["align"] = "RIGHT", ["defaultsort"] = "dsc", ["comparesort"] = function(this, aRow, bRow, column) if this.cols[column].sort == "dsc" then return (this:GetRow(aRow).rowData.amount > this:GetRow(bRow).rowData.amount); else return (this:GetRow(aRow).rowData.amount < this:GetRow(bRow).rowData.amount); end end, ["sortnext"] = 1, ["tooltipTitle"] = "Amount needed", ["tooltip"] = "Click to sort the list by the amount of items needed to reach the restock target.", }, { ["name"] = "Extra", ["width"] = (scrollTableWidth * .15), ["align"] = "RIGHT", ["defaultsort"] = "dsc", ["comparesort"] = function(this, aRow, bRow, column) if this.cols[column].sort == "dsc" then return (this:GetRow(aRow).rowData.bonus > this:GetRow(bRow).rowData.bonus); else return (this:GetRow(aRow).rowData.bonus < this:GetRow(bRow).rowData.bonus); end end, ["sortnext"] = 1, ["tooltipTitle"] = "Extra items", ["tooltip"] = "Click to sort the list by the amount of bonus items.", }, { ["name"] = "X", ["width"] = (scrollTableWidth * .05), ["align"] = "CENTER", ["sortnext"] = 1, ["tooltipTitle"] = "Remove", ["tooltip"] = "Click any of the fields in this column to remove this item from the queue.", ["onClick"] = function(rowData) -- Remove this element from the queue for index, q in pairs(queue) do if q == rowData then table.remove(queue, index); mod:Skip(q.itemId, skipReasons.REMOVED); break; end end -- Rebuild our scrolltable (records were removed and added) mod:BuildQueue(); end, ["color"] = { ["r"] = 1.0, ["g"] = 0.0, ["b"] = 0.0, ["a"] = 1, }, }, }; local scrollTableWidth = ( InventoriumQueuerUnqueueables.frmMeasureDummy:GetWidth() - 30 ); -- adjust width by the scrollbar size local unqueueablesHeaders = { { ["name"] = "Item", ["width"] = (scrollTableWidth * .6), ["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 = sformat(template, (10 - (aRarity or 10)), slower(aName or "")); bName = sformat(template, (10 - (bRarity or 10)), slower(bName or "")); if this.cols[column].sort == "dsc" then return aName > bName; else return aName < bName; end end, ["tooltipTitle"] = "Item", ["tooltip"] = "Click to sort the list by item quality then item name.", }, { ["name"] = "Reason", ["width"] = (scrollTableWidth * .4), ["defaultsort"] = "dsc", ["comparesort"] = function(this, aRow, bRow, column) if this.cols[column].sort == "dsc" then return this:GetRow(aRow).rowData.reason[3] > this:GetRow(bRow).rowData.reason[3]; else return this:GetRow(aRow).rowData.reason[3] < this:GetRow(bRow).rowData.reason[3]; end end, ["sort"] = "dsc", -- when the data is set, use this column to sort the default data ["sortnext"] = 1, ["tooltipTitle"] = "Reason", ["tooltip"] = "Click to sort the list by the reason the items couldn't be queued.", }, }; local proceedButton = { text = "Queue", tooltipTitle = "Queue", tooltip = "Add these items to the queue of your crafting addon.", onClick = function() mod:QueueProcess(); end, }; local cancelButton = { text = "Cancel", tooltipTitle = "Cancel", tooltip = "Do not queue anything and close the window.", onClick = function() mod:QueueAbort(); end, }; addon:SetQueueFrameSettings("Inventorium Queue", "The following items can be added to the queue of your crafting addon. Do you wish to proceed?", proceedButton, cancelButton, headers, unqueueablesHeaders); end end function mod:BuildQueue() MakeQueueWindow(); -- This table is never copied, just referenced. It is the same for every row. local queueablesColumns = { { ["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.amount; end, }, -- amount { ["value"] = function(data, cols, realrow, column, table) return ((data[realrow].rowData.bonus == 0) and 0) or "+" .. data[realrow].rowData.bonus; end, ["color"] = function(data, cols, realrow, column, table) return ((data[realrow].rowData.bonus == 0) and { r = 1, g = 1, b = 1, a = 0.5 }) or { r = 0, g = 1, b = 0, a = 1 }; end, }, -- extra { ["value"] = "X", }, }; -- Store the list with rows in this local queueables = {}; for _, q in pairs(queue) do tinsert(queueables, { ["rowData"] = q, -- this is not a key usually found in a row item and ignored by the library ["cols"] = queueablesColumns, }); end -- This table is never copied, just referenced. It is the same for every row. local unqueueablesColumns = { { ["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.reason[1]; end, }, -- reason }; -- Store the list with rows in this local unqueueables = {}; for _, s in pairs(skipped) do tinsert(unqueueables, { ["rowData"] = s, -- this is not a key usually found in a row item and ignored by the library ["cols"] = unqueueablesColumns, }); end addon:SetQueueFrameData(queueables, unqueueables); end local function RefreshQueue() InventoriumQueuer.scrollTable:Refresh(); end do -- Crafting region -- We are keeping these events within the module object to allow for easier testing and overriding -- To test: LibStub("AceAddon-3.0"):GetAddon("Inventorium"):GetModule("Queue"):FUNCTION_NAME(param1, param2, ...); -- Start crafting the selected skill (or the first in line) local currentQueueItem; function mod:StartCrafting(test) local frame = InventoriumQueuer; -- both for speed as code-consistency local selectedIndex = frame.scrollTable:GetSelection(); -- gets realrow index addon:Debug("%d was selected.", tostring(selectedIndex)); if not selectedIndex then -- Select the top most element (scrolltable with index of 1 will contain a index of the related realrow of the data table) selectedIndex = ((frame.scrollTable.sorttable and frame.scrollTable.sorttable[1]) or 1); addon:Debug("%d should be the top record.", tostring(selectedIndex)); end local nextQueue = frame.scrollTable.data[selectedIndex] or frame.scrollTable.data[1]; -- if the selected index still fails, try to get the first record if nextQueue then if not test then -- Initiate spell (test will be used while debugging to fake crafts) DoTradeSkill(nextQueue.craft.no, ceil(nextQueue.amount / nextQueue.craft.quantity)); end -- Remember what we're crafting (saves many loops and/or table storing) currentQueueItem = nextQueue; return; else addon:Print("Nothing is available in the craft queue.", addon.Colors.Red); end end function mod:SpellCastComplete(_, unit, _, _, _, spellId) -- Sadly the item isn't put in our inventory yet so we don't know how many were made. -- Because of that we assume the average amount was made, this isn't really the best solution, but it's pretty-est - for now. if unit == "player" and currentQueueItem and spellId == currentQueueItem.craft.spellId then -- Make sure the old amount is accurate, this won't be updated by the spell we just finished, that item hasn't been received yet currentQueueItem.amount = mod:GetRestockAmount(currentQueueItem.itemId, currentQueueItem.groupName); -- Decrease amount remaining by one quantity currentQueueItem.amount = ( currentQueueItem.amount - currentQueueItem.craft.quantity ); if currentQueueItem.amount < 1 then -- We finished crafting this item -- Remove this element from the queue for index, q in pairs(queue) do if q == currentQueueItem then table.remove(queue, index); break; end end -- Add this queue item to the "Unqueueables" frame - we finished it so it is no longer queueable and the user may become interested self:Skip(currentQueueItem.itemId, skipReasons.FINISHED); -- We are no longer crafting anything currentQueueItem = nil; -- Rebuild our scrolltable (records were removed and added) mod:BuildQueue(); else -- Refresh the scrolltable (update item counts) RefreshQueue(); end end end function mod:SpellCastStart(_, unit, _, _, _, spellId) if unit == "player" and currentQueueItem and spellId == currentQueueItem.craft.spellId then self.isProcessing = true; end end function mod:SpellCastStop(_, unit, _, _, _, spellId) if unit == "player" and currentQueueItem and spellId == currentQueueItem.craft.spellId then self.isProcessing = nil; end end function mod:SpellCastFailed(_, unit, _, _, _, spellId) if unit == "player" and currentQueueItem and spellId == currentQueueItem.craft.spellId then currentQueueItem = nil; self.isProcessing = nil; end end --@debug@ function TestCraft() mod:StartCrafting(true); mod:SpellCastComplete("UNIT_SPELLCAST_SUCCEEDED", "player", "Relentless Earthsiege Diamond", nil, nil, 55400); end --@end-debug@ end function mod:QueueProcess() -- Prepare a table with all possible tradeskill craftables local craftables = self:GetTradeskillCraftables(); for _, q in pairs(queue) do if craftables[q.itemId] then if self:QueueWithAddon(craftables[q.itemId].no, ceil(q.amount / craftables[q.itemId].quantity), q.groupName) == -1 then addon:Print("Couldn't queue, no supported crafting addon found.", addon.Colors.Red); self:QueueAbort(); return; else -- Update the crafted-item count for groupName, values in pairs(addon.db.profile.groups) do if values.items and values.items[q.itemId] then values.items[q.itemId] = (tonumber(values.items[q.itemId]) or 0) + 1; break; end end end else addon:Debug("Lost %s", IdToItemLink(q.itemId)); end end self:QueueHide(); end function mod:QueueAbort() self:QueueHide(); end function mod:QueueHide() twipe(queue); twipe(skipped); if InventoriumQueuer then InventoriumQueuer:Hide(); end end function mod:QueueAll() -- Prepare a table with all possible tradeskill craftables local craftables = self:GetTradeskillCraftables(); -- Forget old queue twipe(queue); twipe(skipped); local playerName = UnitName("player"); -- Go through all groups for groupName, values in pairs(addon.db.profile.groups) do local trackAt = addon:GetOptionByKey(groupName, "trackAtCharacters"); if trackAt[playerName] then self:QueueGroup(groupName, craftables); end end mod:BuildQueue(); end function mod:QueueGroup(groupName, craftables) -- Prepare a table with all possible tradeskill craftables if not craftables then craftables = self:GetTradeskillCraftables(); -- nil when no tradeskill window is open end if not craftables then addon:Print("No tradeskill window detected.", addon.Colors.Red); return; elseif not addon.db.profile.groups[groupName] then addon:Print(sformat("Tried to queue items from a group named \"%s\", but no such group exists.", groupName), addon.Colors.Red); return; elseif not addon.db.profile.groups[groupName].items then addon:Debug("This group (%s) has no items.", groupName); return; end -- Retrieve group settings local minCraftingQueue = floor( addon:GetOptionByKey(groupName, "minCraftingQueue") * addon:GetOptionByKey(groupName, "restockTarget") ); -- If the minCraftingQueue is 5% and restockTarget is 60, this will result in 3 local priceThreshold = addon:GetOptionByKey(groupName, "priceThreshold"); for itemId, count in pairs(addon.db.profile.groups[groupName].items) do if craftables[itemId] then local amount, bonus = self:GetRestockAmount(itemId, groupName); if amount and amount >= minCraftingQueue then -- If we are queuing at least one AND more than the minimum amount, then proceed -- Auction value settings local value = (priceThreshold ~= 0 and addon:GetAuctionValue(IdToItemLink(itemId), groupName)); if priceThreshold == 0 or value == -1 or value >= priceThreshold then -- If no price threshold is set or the auction value is equal to or larger than the price threshold, then proceed self:Queue(itemId, amount, bonus, craftables[itemId], groupName); else self:Skip(itemId, skipReasons.LOW_VALUE); --addon:Debug("%s is valued at %s while %s is needed", IdToItemLink(itemId), tostring(value), tostring(priceThreshold)); end else if not amount then -- nil = no item count addon self:Skip(itemId, skipReasons.NO_ITEMCOUNT_ADDON); addon:Print("No usable itemcount addon found."); return; elseif amount <= 0 then -- less than 0 = (over)capped self:Skip(itemId, skipReasons.CAPPED); else -- more than 0 = below min crafting queue self:Skip(itemId, skipReasons.MIN_CRAFTING_QUEUE); end end else self:Skip(itemId, skipReasons.NOT_CRAFTABLE); end end end function mod:GetRestockAmount(itemId, groupName) local currentStock = addon:GetItemCount(itemId, groupName); if currentStock >= 0 then -- Current stock will be -1 when no itemcount addon was found local restockTarget = addon:GetOptionByKey(groupName, "restockTarget"); local bonusQueue = addon:GetOptionByKey(groupName, "bonusQueue"); -- Calculate the amount to be queued local amount = ( restockTarget - currentStock ); local bonus = 0; if currentStock == 0 and bonusQueue > 0 then -- If we have none left and the bonus queue is enabled, modify the amount to be queued bonus = floor( ( amount * ( bonusQueue ) ) + .5 ); -- round -- Update amount amount = (amount + bonus); end return amount, bonus; else return; end end function mod:Queue(itemId, amount, bonus, craft, groupName) tinsert(queue, { ["itemId"] = itemId, -- needed to display the queued item in the queue window ["amount"] = amount, -- the amount missing ["bonus"] = bonus, -- the amount queued by the bonus queue ["craft"] = craft, -- (craftable) - needed to find the proper element of this parent array when crafting has finished (spellId), and to update the numCrafts (quantity) ["groupName"] = groupName, -- related group, needed to find the selected crafting addon }); end function mod:Skip(itemId, reason) tinsert(skipped, { ["itemId"] = itemId, ["reason"] = reason, }); end function mod:QueueWithAddon(tradeSkillIndex, amount, group) -- Sanity check tradeSkillIndex = tonumber(tradeSkillIndex); amount = tonumber(amount); local selectedExternalAddon = addon:GetOptionByKey(group, "craftingAddon"); if addon.supportedAddons.crafting[selectedExternalAddon] and addon.supportedAddons.crafting[selectedExternalAddon].IsEnabled() then -- Try to use the default auction pricing addon return addon.supportedAddons.crafting[selectedExternalAddon].Queue(tradeSkillIndex, amount); else -- Default not available, get the first one then for name, value in pairs(addon.supportedAddons.crafting) do if value.IsEnabled() then return value.Queue(tradeSkillIndex, amount); end end end return -1; end function mod:OnEnable() -- Register our own slash commands -- /im queue addon:RegisterSlash(function() mod:QueueAll(); end, { "q", "que", "queue" }, "|Hfunction:InventoriumCommandHandler:queue|h|cff00fff7/im queue|r|h (or /im q) - Queue all items found in the currently opened profession that are within the groups tracked at this current character."); self:RegisterMessage("IM_QUEUE_ALL"); self:RegisterMessage("IM_QUEUE_GROUP"); -- When closing the tradeskill window also hide the queue screen. -- We scan the recipes right before queueing not when the tradeskill is opened because we really don't need it at any other time. self:RegisterEvent("TRADE_SKILL_CLOSE", "QueueAbort"); -- Crafting events self:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED", "SpellCastComplete"); -- Button en-/disabling self:RegisterEvent("UNIT_SPELLCAST_START", "SpellCastStart"); self:RegisterEvent("UNIT_SPELLCAST_STOP", "SpellCastStop"); self:RegisterEvent("UNIT_SPELLCAST_FAILED", "SpellCastFailed"); self:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED", "SpellCastFailed"); end do -- Addon messages (Ace3) region function mod:IM_QUEUE_ALL() self:QueueAll(); end function mod:IM_QUEUE_GROUP(event, groupName) self:QueueGroup(groupName); end end do -- Trade skill recipes region -- Expand all categories so no crafts are hidden local function ExpandSubClasses() for i = GetNumTradeSkills(), 1, -1 do local _, skillType, _, isExpanded = GetTradeSkillInfo(i); if skillType == "header" and not isExpanded then ExpandTradeSkillSubClass(i); end end end -- Reset all filters so no crafts are hidden local function ResetFilters() SetTradeSkillSubClassFilter(0, 1, 1); SetTradeSkillItemNameFilter(""); SetTradeSkillItemLevelFilter(0, 0); TradeSkillOnlyShowSkillUps(false); TradeSkillOnlyShowMakeable(false); end -- Get all craftable items into a table. Each record contains "no", "spellId" and "quantity". The last is the average amount made per craft. function mod:GetTradeskillCraftables() local craftables = {}; if GetTradeSkillLine() ~= "UNKNOWN" then ExpandSubClasses(); ResetFilters(); -- Cache all craftable items for i = 1, GetNumTradeSkills() do local itemLink = GetTradeSkillItemLink(i); if itemLink then local itemId = addon:GetItemId(itemLink); if not itemId then -- If this isn't an item, it can only be an enchant instead itemId = tonumber(smatch(itemLink, "|Henchant:([-0-9]+)|h")); if itemId and addon.scrollIds[itemId] then -- Only if this scroll id actually exists itemId = addon.scrollIds[itemId]; -- change enchantIds into scrollIds end end -- Remember the average amount of items created per craft (doesn't need to be a round number, since we multiply this by the amount of items to be queued we're better off rounding at that time) local minMade, maxMade = GetTradeSkillNumMade(i); local average = ((minMade == maxMade) and minMade) or ((minMade + maxMade) / 2); local recipeLink = GetTradeSkillRecipeLink(i); local spellId = tonumber(smatch(recipeLink, "|Henchant:([-0-9]+)|h")); craftables[itemId] = { ["no"] = i, -- needed to start crafting at the end of the entire cycle ["spellId"] = spellId, -- needed to detect creation of this item was finished ["quantity"] = average, -- needed to calculate the amount of crafts }; end end else return; end return craftables; end end