view 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 source
local addon = select(2, ...);
local mod = addon:NewModule("Queue", "AceEvent-3.0", "AceTimer-3.0");

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
	-- /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");
	
	if not InventoriumQueuer then
		addon:CreateQueueFrame(OnMoveAccept, OnMoveCancel);
	end
end

function mod:IM_QUEUE_ALL()
	self:QueueAll();
end

function mod:IM_QUEUE_GROUP(event, groupName)
	self:QueueGroup(groupName);
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
	
	DisplayQueue();
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 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 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 queueing at least one AND more than the minimum amount, then proceed
					
					-- Auction value settings
					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(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(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);
	
	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

-- 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