diff Modules/Queue.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 67bd5057ecb7
children 396c2960d54d
line wrap: on
line diff
--- a/Modules/Queue.lua	Tue Jan 18 00:28:24 2011 +0100
+++ b/Modules/Queue.lua	Tue Jan 18 00:30:15 2011 +0100
@@ -1,7 +1,209 @@
 local addon = select(2, ...);
 local mod = addon:NewModule("Queue", "AceEvent-3.0", "AceTimer-3.0");
 
-local pairs = pairs;
+local _G = _G;
+local tonumber, pairs, sformat, smatch, floor, ceil, tinsert, twipe = _G.tonumber, _G.pairs, _G.string.format, _G.string.match, _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 = {
+	["LOW_VALUE"] = { "|cffff6633Underpriced|r", "The recorded auction value of this item is below your price threshold." },
+	["CAPPED"] = { "|cff66ff33Fully stocked|r", "The recorded item count is above or equal to your minimum global stock setting." },
+	["MIN_CRAFTING_QUEUE"] = { "|cffffff00Min crafting queue|r", "The amount of missing items is below or equal to your \"don't queue if I only miss\"-setting." },
+	["NO_ITEMCOUNT_ADDON"] = { "|cffff0000No itemcount addon|r", "No compatible item count could be found." },
+	["NOT_CRAFTABLE"] = { "|cff3d3d3dNot in profession|r", "This item is not part of this profession." },
+};
+
+local function OnQueueCancel()
+	twipe(queue);
+	twipe(skipped);
+	
+	InventoriumQueuer:Hide();
+end
+
+local function OnQueueAccept()
+	-- Prepare a table with all possible tradeskill craftables
+	local craftables = mod:GetTradeskillCraftables();
+	
+	for _, q in pairs(queue) do
+		if craftables[q.itemId] then
+			if mod: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);
+				
+				OnQueueCancel();
+				return;
+			end
+		else
+			addon:Debug("Lost %s", IdToItemLink(q.itemId));
+		end
+	end
+	
+	twipe(queue);
+	twipe(skipped);
+	
+	InventoriumQueuer:Hide();
+end
+
+local function MakeQueueWindow()
+	do
+		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 * .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"] = "Amount",
+				["width"] = (scrollTableWidth * .20),
+				["align"] = "RIGHT",
+				["defaultsort"] = "dsc",
+				["sortnext"] = 1,
+				["tooltipTitle"] = (not addon.db.profile.defaults.hideHelp and "Amount"),
+				["tooltip"] = (not addon.db.profile.defaults.hideHelp and "Click to sort the list by the amount of items to be queued."),
+			},
+			{
+				["name"] = "Extra",
+				["width"] = (scrollTableWidth * .20),
+				["align"] = "RIGHT",
+				["defaultsort"] = "dsc",
+				["sortnext"] = 1,
+				["tooltipTitle"] = (not addon.db.profile.defaults.hideHelp and "Extra"),
+				["tooltip"] = (not addon.db.profile.defaults.hideHelp and "Click to sort the list by the amount of bonus items."),
+			},
+		};
+		
+		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 = 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"] = "Reason",
+				["width"] = (scrollTableWidth * .4),
+				["defaultsort"] = "dsc",
+				["sortnext"] = 1,
+				["tooltipTitle"] = (not addon.db.profile.defaults.hideHelp and "Reason"),
+				["tooltip"] = (not addon.db.profile.defaults.hideHelp and "Click to sort the list by the reason the items couldn't be queued."),
+			},
+		};
+		
+		local proceedButton = {
+			text = "Queue",
+			tooltipTitle = (not addon.db.profile.defaults.hideHelp and "Queue"),
+			tooltip = (not addon.db.profile.defaults.hideHelp and "Add these items to the queue of your crafting addon."),
+			onClick = OnQueueAccept,
+		};
+		local cancelButton = {
+			text = "Cancel",
+			tooltipTitle = (not addon.db.profile.defaults.hideHelp and "Cancel"),
+			tooltip = (not addon.db.profile.defaults.hideHelp and "Do not queue anything and close the window."),
+			onClick = OnQueueCancel,
+		};
+		
+		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
+
+local function DisplayQueue()
+	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
+	};
+	
+	-- 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
 
 function mod:OnEnable()
 	-- Register our own slash commands
@@ -12,6 +214,10 @@
 	
 	self:RegisterMessage("IM_QUEUE_ALL");
 	self:RegisterMessage("IM_QUEUE_GROUP");
+	
+	if not InventoriumQueuer then
+		addon:CreateQueueFrame(OnMoveAccept, OnMoveCancel);
+	end
 end
 
 function mod:IM_QUEUE_ALL()
@@ -23,6 +229,13 @@
 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
@@ -30,108 +243,108 @@
 		local trackAt = addon:GetOptionByKey(groupName, "trackAtCharacters");
 		
 		if trackAt[playerName] then
-			self:QueueGroup(groupName);
+			self:QueueGroup(groupName, craftables);
 		end
 	end
+	
+	DisplayQueue();
 end
 
-function mod:QueueGroup(groupName)
-	if not addon.db.profile.groups[groupName] then
-		addon:Print(("Tried to queue items from a group named \"%s\", but no such group exists."):format(groupName), addon.Colors.Red);
+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
 	
-	local temp = {};
+	-- 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");
 	
-	local tradeskillName, currentLevel, maxLevel = GetTradeSkillLine();
-	
-	if tradeskillName ~= "UNKNOWN" then
-		-- Go through all trade skills for the profession
-		for i = 1, GetNumTradeSkills() do
-			-- Process every single tradeskill
-			self:ProcessTradeSkill(i, groupName, temp);
-		end
-	end
-	
-	if addon.db.profile.groups[groupName].items then
-		for itemId, _ in pairs(addon.db.profile.groups[groupName].items) do
-			if not temp[itemId] then
-				local itemLink = select(2, GetItemInfo(itemId));
-				
-				addon:Print(("Couldn't queue %s (not part of this profession)"):format((itemLink or itemId or "???")), addon.Colors.Orange);
-			end
-		end
-	end
-end
-
-function mod:ProcessTradeSkill(i, groupName, temp)
-	-- Try to retrieve the itemlink, this will be nil if current item is a group instead
-	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(itemLink:match("|Henchant:([-0-9]+)|h"));
-			
-			itemId = addon.scrollIds[itemId]; -- change enchantIds into scrollIds
-		end
-		
-		if addon.db.profile.groups[groupName].items and addon.db.profile.groups[groupName].items[itemId] then
-			-- This item is in this group, queue it!
-			
-			if temp then
-				-- Remember which items have been processed
-				temp[itemId] = true;
-			end
-			
+	for itemId 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
 				
-				-- 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
-				
 				-- 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
 					
-					amount = floor( ( amount * ( bonusQueue + 1 ) ) + .5 ); -- round
+					bonus = floor( ( amount * ( bonusQueue ) ) + .5 ); -- round
+					
+					-- Update amount
+					amount = (amount + bonus);
 				end
 				
 				if amount > 0 and amount >= minCraftingQueue then
 					-- If we are queueing at least one AND more than the minimum amount, then proceed
 					
 					-- Auction value settings
-					local priceThreshold = addon:GetOptionByKey(groupName, "priceThreshold");
 					local value = (priceThreshold ~= 0 and addon:GetAuctionValue(itemLink, 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(i, amount, groupName);
-						
-						addon:Print(("Queued %d of %s"):format(amount, itemLink));
+						self:Queue(itemId, amount, bonus, groupName);
+					else
+						self:Skip(itemId, skipReasons.LOW_VALUE);
+					end
+				else
+					if amount <= 0 then
+						self:Skip(itemId, skipReasons.CAPPED);
+					else
+						self:Skip(itemId, skipReasons.MIN_CRAFTING_QUEUE);
 					end
 				end
 			else
+				self:Skip(itemId, skipReasons.NO_ITEMCOUNT_ADDON);
 				addon:Print("No usable itemcount addon found.");
+				return;
 			end
+		else
+			self:Skip(itemId, skipReasons.NOT_CRAFTABLE);
 		end
 	end
 end
 
-function mod:Queue(tradeSkillIndex, amount, group)
+function mod:Queue(itemId, amount, bonus, groupName)
+	tinsert(queue, {
+		["itemId"] = itemId,
+		["amount"] = amount,
+		["bonus"] = bonus,
+		["groupName"] = groupName,
+	});
+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);
 	
-	if not tradeSkillIndex or not amount then return; end
-	
 	local selectedExternalAddon = addon:GetOptionByKey(group, "craftingAddon");
 	
 	if addon.supportedAddons.crafting[selectedExternalAddon] and addon.supportedAddons.crafting[selectedExternalAddon].IsEnabled() then
@@ -148,5 +361,52 @@
 		end
 	end
 	
-	return -2;
+	return -1;
 end
+
+-- Expand all categories
+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
+
+function mod:GetTradeskillCraftables()
+	local craftables = {};
+	
+	if GetTradeSkillLine() ~= "UNKNOWN" then
+		ExpandSubClasses();
+		
+		-- 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"));
+					
+					itemId = addon.scrollIds[itemId]; -- change enchantIds into scrollIds
+				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);
+				
+				craftables[itemId] = {
+					["no"] = i,
+					["quantity"] = average,
+				};
+			end
+		end
+	else
+		return;
+	end
+	
+	return craftables;
+end