Mercurial > wow > inventory
view Modules/Queue.lua @ 241:65a9ef84d18f
User manual update.
author | Zerotorescue |
---|---|
date | Sat, 12 Feb 2011 20:15:40 +0100 |
parents | 2e4e52a589e5 |
children |
line wrap: on
line source
local addon = select(2, ...); local mod = addon:NewModule("Queue", "AceEvent-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, }, ["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 Compare(a, b, this, aRow, bRow, columnNo) if a == b then local column = this.cols[columnNo]; if column.sortnext then local nextcol = this.cols[column.sortnext]; if not(nextcol.sort) then if nextcol.comparesort then return nextcol.comparesort(this, aRow, bRow, column.sortnext); else return this:CompareSort(this, bRow, column.sortnext); end else return false; end else return false; end elseif (this.cols[columnNo].sort or this.cols[columnNo].defaultsort or "asc") == "dsc" then return a > b; else return a < b; end end 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, columnNo) local aName, _, aRarity = GetItemInfo(this:GetRow(aRow).rowData.itemId); local bName, _, bRarity = GetItemInfo(this:GetRow(bRow).rowData.itemId); aName = sformat("%d%s", (10 - (aRarity or 10)), slower(aName or "")); bName = sformat("%d%s", (10 - (bRarity or 10)), slower(bName or "")); return Compare(aName, bName, this, aRow, bRow, columnNo); 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, columnNo) local a = this:GetRow(aRow).rowData.amount; local b = this:GetRow(bRow).rowData.amount; return Compare(a, b, this, aRow, bRow, columnNo); 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, columnNo) local a = this:GetRow(aRow).rowData.bonus; local b = this:GetRow(bRow).rowData.bonus; return Compare(a, b, this, aRow, bRow, columnNo); 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, columnNo) local aName, _, aRarity = GetItemInfo(this:GetRow(aRow).rowData.itemId); local bName, _, bRarity = GetItemInfo(this:GetRow(bRow).rowData.itemId); aName = sformat("%d%s", (10 - (aRarity or 10)), slower(aName or "")); bName = sformat("%d%s", (10 - (bRarity or 10)), slower(bName or "")); return Compare(aName, bName, this, aRow, bRow, columnNo); 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, columnNo) local a = this:GetRow(aRow).rowData.reason[3]; local b = this:GetRow(bRow).rowData.reason[3]; return Compare(a, b, this, aRow, bRow, columnNo); 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, }; local craftButton = { text = "Craft", tooltipTitle = "Craft", tooltip = "Start crafting the first item.", onClick = function() mod:StartCrafting(); 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, craftButton, 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("%s 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("%s should be the top record.", tostring(selectedIndex)); end local nextQueue = frame.scrollTable.data[selectedIndex].rowData or frame.scrollTable.data[1].rowData; -- if the selected index still fails, try to get the first record if nextQueue then if not test then self:ResetTradeSkillFilters(); -- 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 -- 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(); local craftingAddon = self:GetCraftingAddon(group); if craftingAddon.OnQueueStart then craftingAddon.OnQueueStart(); end 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 if craftingAddon.OnQueueEnd then craftingAddon.OnQueueEnd(); 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 if not self:QueueGroup(groupName, craftables) then return; end end end mod:BuildQueue(); end function mod:QueueGroup(groupName, craftables, displayQueue) -- 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. Please open a profession before trying to queue.", addon.Colors.Red); return false; -- exit 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 false; -- exit elseif not addon.db.profile.groups[groupName].items then addon:Debug("This group (%s) has no items.", groupName); return true; -- continue with next group end -- Retrieve group settings local restockTarget = addon:GetOptionByKey(groupName, "restockTarget"); local bonusQueue = addon:GetOptionByKey(groupName, "bonusQueue"); local minCraftingQueue = floor( addon:GetOptionByKey(groupName, "minCraftingQueue") * 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 currentStock = addon:GetItemCount(itemId, groupName); if currentStock >= 0 then -- Current stock will be -1 when no itemcount addon was found -- 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 if amount > 0 and amount > minCraftingQueue then -- If we are queuing at least one AND more than the minimum amount, then proceed -- Get auction value when it is relevant 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 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 -- No item count addon addon:Print("No usable itemcount addon found.", addon.Colors.Red); return false; -- exit end else self:Skip(itemId, skipReasons.NOT_CRAFTABLE); end end if displayQueue then mod:BuildQueue(); end return true; -- continue 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:GetCraftingAddon(group) 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], selectedExternalAddon; else -- Default not available, get the first one then for name, value in pairs(addon.supportedAddons.crafting) do if value.IsEnabled() then return value, name; end end end return; end function mod:QueueWithAddon(tradeSkillIndex, amount, group) -- Sanity check tradeSkillIndex = tonumber(tradeSkillIndex); amount = tonumber(amount); local craftingAddon = self:GetCraftingAddon(group); if craftingAddon then return craftingAddon.Queue(tradeSkillIndex, amount); else return -1; end 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, nil, true); end end do -- Trade skill recipes region -- Reset all filters so no crafts are hidden function mod:ResetTradeSkillFilters() SetTradeSkillSubClassFilter(0, 1, 1); SetTradeSkillItemNameFilter(""); SetTradeSkillItemLevelFilter(0, 0); TradeSkillOnlyShowSkillUps(false); TradeSkillOnlyShowMakeable(false); -- Expand all categories so no crafts are hidden for i = GetNumTradeSkills(), 1, -1 do local _, skillType, _, isExpanded = GetTradeSkillInfo(i); if skillType == "header" and not isExpanded then ExpandTradeSkillSubClass(i); end end 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 self:ResetTradeSkillFilters(); -- 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