view Modules/Queue.lua @ 142:56f33abee1e3

Always making sure the mover window is not there and making the frame when all data is ready, rather than before checking if the confirmation should even appear.
author Zerotorescue
date Tue, 18 Jan 2011 23:00:18 +0100
parents cd461a41723c
children 8eb0f5b5a885
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;
			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]) + 1;
						break;
					end
				end
			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, 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
					
					-- 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