view Core.lua @ 224:f1d08aaafeaa

Added tag v0.5.5-BETA for changeset ed5047c8249d
author Zerotorescue
date Mon, 07 Feb 2011 00:06:07 +0100
parents c04257b42b03
children 2e4e52a589e5
line wrap: on
line source
-- You can access this addon's object through: LibStub("AceAddon-3.0"):GetAddon("Inventorium")
local addon = select(2, ...);
addon = LibStub("AceAddon-3.0"):NewAddon(addon, "Inventorium", "AceEvent-3.0");

local _G = _G;
local sformat, ssplit, slower, strim, smatch = _G.string.format, _G.string.split, _G.string.lower, _G.string.trim, _G.string.match;
local floor, print, pairs, tonumber = _G.floor, _G.print, _G.pairs, _G.tonumber;

--@debug@
local addonRevision = 1; -- used to update the database whenever required
--@end-debug@
--[===[@non-debug@
local addonRevision = @project-revision@;
--@end-non-debug@]===]

--  All modules must be able to retrieve our supported addons database, thus keep it a part of the addon object rather than local
addon.supportedAddons = {};
addon.supportedAddons.auctionPricing = {};
addon.supportedAddons.itemCount = {};
addon.supportedAddons.crafting = {};

addon.Locations = {
	["Bag"] = "Bag",
	["Bank"] = "Bank",
	["Guild"] = "Guild",
	["Mailbox"] = "Mailbox",
	["Merchant"] = "Merchant",
};

function addon:OnInitialize()
	-- SAVED VARIABLES
	
	local defaults = {
		global = {
			version = nil,
		},
		profile = {
			defaults = {
				-- General
				auctionPricingAddon = "Auctioneer",
				itemCountAddon = "DataStore (with guilds)",
				craftingAddon = "AdvancedTradeSkillWindow",
				trackAtCharacters = { },
				dontAlertAtCharacters = { },
				localItemData = {
					["Bag"] = true,
					["Auction House"] = true,
				},
				
				-- Minimumm stock
				minLocalStock = 20,
				alertBelowLocalMinimum = true,
				autoRefill = true,
				autoRefillSkipConfirm = false,
				minGlobalStock = 40,
				alertBelowGlobalMinimum = true,
				
				-- Queueing
				restockTarget = 40,
				minCraftingQueue = 0.05,
				bonusQueue = 0.1,
				priceThreshold = 0,
				
				-- Summary
				summaryThresholdShow = 100, -- 10.000%
				summaryHidePriceThreshold = false,
				
				-- Global (can't be overridden)
				minimapIcon = true,
				hideHelp = false,
				scanInterval = "0.1", -- string because the associated select requires it to be 
				summary = {
					speed = 20,
					width = 700,
					height = 600,
				},
				colors = {
					red = 0,
					orange = 0.3,
					yellow = 0.6,
					green = 0.95,
				},
				itemCountGuildsExcluded = { },
			},
			groups = {
				-- items = {},
				-- isVirtual = nil,
			},
		},
		factionrealm = {
			characters = {
			},
		},
	};
	
	-- Register our saved variables database
	self.db = LibStub("AceDB-3.0"):New("InventoriumDB", defaults, true);
	
	-- SLASH COMMANDS
	
	-- Disable the AddonLoader slash commands
	SLASH_INVENTORIUM1 = nil;
	SLASH_IM1 = nil;
	
	-- Register our own slash commands
	SLASH_INVENTORIUM1 = "/inventorium";
	SLASH_INVENTORIUM2 = "/im";
	SlashCmdList["INVENTORIUM"] = function(msg)
		addon:CommandHandler(msg);
	end;
	
	-- Debug command handling
	self:RegisterSlash(function(this)
		this.debugChannel = false;
		for i = 1, NUM_CHAT_WINDOWS do
			local name = GetChatWindowInfo(i);
			
			if string.upper(name) == "IMDEBUG" then
				addon:Print("A debug channel already exists, removing the old one... (" .. i .. ")");
				FCF_Close(_G["ChatFrame" .. i]);
			end
		end
		
		if not this.debugChannel then
			-- Create a new debug channel
			local chatFrame = FCF_OpenNewWindow('IMDebug');
			ChatFrame_RemoveAllMessageGroups(chatFrame);
			this.debugChannel = chatFrame;
			
			addon:Print("New debug channel created.");
		end
	end, { "d", "debug", "imdebug" });

	-- Remember this character is on this account
	local playerName = UnitName("player");
	if not self.db.factionrealm.characters[playerName] then
		self.db.factionrealm.characters[playerName] = true;
		
		-- Default to tracking on all chars, untracking is a convenience, not tracking by default would probably get multiple issue reports.
		self.db.profile.defaults.trackAtCharacters[playerName] = true;
	end
	
	self:UpdateDatabase();
end





-- Database patching after new revisions

function addon:UpdateDatabase()
	if not self.db.global.version or self.db.global.version < addonRevision then
		-- Is our database outdated? Then patch it.
		
		--[[if self.db.global.version < 1337 then
		end]]
		
		-- Remember the version of our database
		self.db.global.version = addonRevision;
	end
end

function addon:GetOptionByKey(groupName, optionName, noDefault)
	if groupName and addon.db.profile.groups[groupName] and addon.db.profile.groups[groupName][optionName] ~= nil then
		-- If this option exists within the settings of this group
		
		return addon.db.profile.groups[groupName][optionName];
	elseif groupName and addon.db.profile.groups[groupName] and addon.db.profile.groups[groupName].virtualGroup ~= "" and not noDefault then
		-- If a virtual group was selected
		
		return self:GetOptionByKey(addon.db.profile.groups[groupName].virtualGroup, optionName, noDefault);
	elseif addon.db.profile.defaults[optionName] and not noDefault then
		return addon.db.profile.defaults[optionName];
	else
		return nil;
	end
end

local autoSelectedItemCountAddon;
function addon:GetItemCountAddon(group)
	local selectedExternalAddon = self:GetOptionByKey(group, "itemCountAddon");
	
	if self.supportedAddons.itemCount[selectedExternalAddon] and self.supportedAddons.itemCount[selectedExternalAddon].IsEnabled() then
		-- Try to use the default item count addon
		
		if self.supportedAddons.itemCount[selectedExternalAddon].SetGuildState then
			self.supportedAddons.itemCount[selectedExternalAddon].SetGuildState(self.db.profile.defaults.itemCountGuildsExcluded);
		end
		
		return self.supportedAddons.itemCount[selectedExternalAddon], selectedExternalAddon;
	elseif self.supportedAddons.itemCount[autoSelectedItemCountAddon] and self.supportedAddons.itemCount[autoSelectedItemCountAddon].IsEnabled() then
		-- Use previously automatically selected addon
		
		if self.supportedAddons.itemCount[autoSelectedItemCountAddon].SetGuildState then
			self.supportedAddons.itemCount[autoSelectedItemCountAddon].SetGuildState(self.db.profile.defaults.itemCountGuildsExcluded);
		end
		
		return self.supportedAddons.itemCount[autoSelectedItemCountAddon], autoSelectedItemCountAddon;
	else
		-- Default not available, get the first one then
		
		-- We are finding the best match, quality is used to compare everything
		local altName, altValue, altQuality;
		
		for name, value in pairs(self.supportedAddons.itemCount) do
			if value.IsEnabled() then
				-- Quality is based on functionality supported; TotalCount, LocalCount & GuildSelect = 3; TotalCount & LocalCount = 2, TotalCount = 1
				local quality = ((value.GetTotalCount and value.GetCharacterCount and value.SetGuildState and 3) or (value.GetTotalCount and value.GetCharacterCount and 2) or (value.GetTotalCount and 1) or 0);
				
				if quality == 3 then
					-- Best quality means instant return
					
					-- Remember this was auto selected so we don't loop again 
					autoSelectedItemCountAddon = name;
					
					return value, name;
				elseif not altQuality or quality > altQuality then
					-- Compare quality; improvement? = overwrite
					altName = name;
					altValue = value;
					altQuality = quality;
				end
			end
		end
		
		if altName and altValue and altQuality then
			-- Remember this was auto selected so we don't loop again 
			autoSelectedItemCountAddon = altName;
			
			return altValue, altName;
		end
	end
	
	return;
end

function addon:GetItemCount(itemId, group)
	itemId = tonumber(itemId);
	
	if not itemId then return; end
	
	local itemCountAddon = self:GetItemCountAddon(group);
	
	return (itemCountAddon and itemCountAddon.GetTotalCount and itemCountAddon.GetTotalCount(itemId)) or -1;
end

function addon:GetLocalItemCount(itemId, group)
	itemId = tonumber(itemId);
	
	if not itemId then return; end
	
	local itemCountAddon = self:GetItemCountAddon(group);
	
	local currentItemCount;
	
	if itemCountAddon and itemCountAddon.GetCharacterCount then
		local bag, bank, auctionHouse, mail = itemCountAddon.GetCharacterCount(itemId);
		
		local selectedLocalItemCountSources = self:GetOptionByKey(group, "localItemData");
		
		currentItemCount = 0;
		if selectedLocalItemCountSources["Bag"] then
			currentItemCount = currentItemCount + bag;
		end
		if selectedLocalItemCountSources["Bank"] then
			currentItemCount = currentItemCount + bank;
		end
		if selectedLocalItemCountSources["Auction House"] then
			currentItemCount = currentItemCount + auctionHouse;
		end
		if selectedLocalItemCountSources["Mailbox"] then
			currentItemCount = currentItemCount + mail;
		end
	end
	
	return currentItemCount or -1;
end

function addon:GetAuctionValue(itemLink, group)
	if not itemLink then return -5; end
	
	local selectedExternalAddon = self:GetOptionByKey(group, "auctionPricingAddon");
	
	if self.supportedAddons.auctionPricing[selectedExternalAddon] and self.supportedAddons.auctionPricing[selectedExternalAddon].IsEnabled() then
		-- Try to use the default auction pricing addon
		
		return self.supportedAddons.auctionPricing[selectedExternalAddon].GetValue(itemLink);
	else
		-- Default not available, get the first one then
		
		for name, value in pairs(self.supportedAddons.auctionPricing) do
			if value.IsEnabled() then
				return value.GetValue(itemLink);
			end
		end
	end
	
	return -2;
end





-- Slash commands

local slashArgs = {};
local slashError = "Wrong argument, the following arguments are available:";

function addon:CommandHandler(message)
	local cmd, arg = ssplit(" ", (message or ""), 2);
	cmd = slower(cmd);
	
	if slashArgs[cmd] then
		-- Pass a reference to the addon (to be used as "self") and the provided arg
		slashArgs[cmd](addon, arg);
	else
		addon:Print(slashError);
	end
end

function addon:RegisterSlash(func, args, description)
	for _, arg in pairs(args) do
		slashArgs[arg] = func;
	end
	
	if description then
		slashError = slashError .. "\n" .. description;
	end
end





function addon:ColorCode(num, required)
	local percentage = ( num / required );
	
	if percentage >= addon.db.profile.defaults.colors.green then
		return sformat("|cff00ff00%d|r", num);
	elseif percentage >= addon.db.profile.defaults.colors.yellow then
		return sformat("|cffffff00%d|r", num);
	elseif percentage >= addon.db.profile.defaults.colors.orange then
		return sformat("|cffff9933%d|r", num);
	elseif percentage >= addon.db.profile.defaults.colors.red then
		return sformat("|cffff0000%d|r", num);
	else
		return num;
	end
end

function addon:DisplayItemCount(value, minimumStock)
	if value == -1 then
		return "|cffffff00Unknown|r";
	elseif value == -2 then
		return "|cffffff00-|r";
	elseif value == -3 then
		return "|cffffff00Unknown|r";
	else
		return sformat("%s / %d", self:ColorCode(value, minimumStock), minimumStock);
	end
end

-- Readable money

local goldText = "%s%d|cffffd700g|r ";
local silverText = "%s%d|cffc7c7cfs|r ";
local copperText = "%s%d|cffeda55fc|r";

function addon:ReadableMoney(copper, clean)
	local text = "";
	
	local gold = floor( copper / COPPER_PER_GOLD );
	if gold > 0 then
		text = sformat(goldText, text, gold);
	end
	
	if not clean or (not gold or gold < 10) then
		local silver = floor( ( copper % COPPER_PER_GOLD ) / COPPER_PER_SILVER );
		if silver > 0 then
			text = sformat(silverText, text, silver);
		end
		
		if not clean or (not gold or gold < 1) then
			local copper = floor( copper % COPPER_PER_SILVER );
			if copper > 0 or text == "" then
				text = sformat(copperText, text, copper);
			end
		end
	end
	
	
	return strim(text);
end

function addon:ReadableMoneyToCopper(value)
	-- If a player enters a value it will be filled without color codes
	-- If it is retrieved from the database, it will be colored coded
	-- Thus we look for both
	local gold = tonumber(smatch(value, "(%d+)|c[a-fA-F0-9]+g|r") or smatch(value, "(%d+)g"));
	local silver = tonumber(smatch(value, "(%d+)|c[a-fA-F0-9]+s|r") or smatch(value, "(%d+)s"));
	local copper = tonumber(smatch(value, "(%d+)|c[a-fA-F0-9]+c|r") or smatch(value, "(%d+)c"));
		
	return ( (gold or 0) * COPPER_PER_GOLD ) + ( (silver or 0) * COPPER_PER_SILVER ) + (copper or 0);
end

function addon:ValidateReadableMoney(info, value)
	-- If a player enters a value it will be filled without color codes
	-- If it is retrieved from the database, it will be colored coded
	-- Thus we look for both
	local gold = tonumber(smatch(value, "(%d+)|c[a-fA-F0-9]+g|r") or smatch(value, "(%d+)g"));
	local silver = tonumber(smatch(value, "(%d+)|c[a-fA-F0-9]+s|r") or smatch(value, "(%d+)s"));
	local copper = tonumber(smatch(value, "(%d+)|c[a-fA-F0-9]+c|r") or smatch(value, "(%d+)c"));
	
	if not gold and not silver and not copper then
		return "The provided amount of money is invalid. Please provide the amount of money as #g#s#c, e.g. 591617g24s43c.";
	else
		return true;
	end
end





-- Public

function IMRegisterPricingAddon(name, get, enabled, onSelect)
	addon.supportedAddons.auctionPricing[name] = {
		["GetValue"] = get,
		["IsEnabled"] = enabled,
		["OnSelect"] = onSelect,
	};
end

function IMRegisterItemCountAddon(name, getTotal, getCharacter, enabled, onSelect, getGuildNames, setGuildState)
	addon.supportedAddons.itemCount[name] = {
		["GetTotalCount"] = getTotal,
		["GetCharacterCount"] = getCharacter,
		["IsEnabled"] = enabled,
		["OnSelect"] = onSelect,
		["GetGuildNames"] = getGuildNames,
		["SetGuildState"] = setGuildState,
	};
end

function IMRegisterCraftingAddon(name, queue, enabled, onSelect)
	addon.supportedAddons.crafting[name] = {
		["Queue"] = queue,
		["IsEnabled"] = enabled,
		["OnSelect"] = onSelect,
	};
end

-- We need a global command handler for our chat-links
function InventoriumCommandHandler(msg)
	addon:CommandHandler(msg);
end





-- General

addon.Colors = {
	["Red"] = { 1, 0, 0 },
	["Orange"] = { 1, .46, .1 },
	["Green"] = { 0, 1, 0 },
	["Blue"] = { 0, 0, 1 },
	["Yellow"] = { 1, 1, 0 },
	["Cyan"] = { 0, 1, 1 },
}; -- easy to extend if more colors are needed
function addon:Print(text, color)
	local red, green, blue;
	
	if color then
		red, green, blue = color[1], color[2], color[3];
	end
	
	DEFAULT_CHAT_FRAME:AddMessage(text or "", red, green, blue, nil, 5);
end

function addon:GetItemId(itemLink)
	itemLink = itemLink and itemLink:match("|Hitem:([-0-9]+):"); -- if itemLink is nil, it won't execute the second part
	itemLink = itemLink and tonumber(itemLink);
	
	return itemLink;
end

-- Debug

local function ReadableTable(t, includeKeys, jumps)
	local tabs = "";
	for i = 1, (jumps or 0) do
		tabs = tabs .. "  ";
	end

	local temp = "{\n";

	for i, v in pairs(t) do
		if type(v) == "table" then
			if includeKeys then
				local key = (type(i) == "number" and tostring(i)) or sformat("\"%s\"", tostring(i));
				
				temp = sformat("%s%s  [%s] => %s,\n", temp, tabs, key, ReadableTable(v, includeKeys, (jumps or 0) + 1));
			else
				temp = sformat("%s%s  %s,\n", temp, tabs, ReadableTable(v, includeKeys, (jumps or 0) + 1));
			end
		else
			if includeKeys then
				local key = (type(i) == "number" and tostring(i)) or sformat("\"%s\"", tostring(i));
				local value = (type(v) == "number" and tostring(v)) or sformat("\"%s\"", tostring(v));
				
				temp = sformat("%s%s  [%s] => %s,\n", temp, tabs, key, value);
			else
				local value = (type(v) == "number" and tostring(v)) or sformat("\"%s\"", tostring(v));
				
				temp = sformat("%s%s  %s,\n", temp, tabs, value);
			end
		end
	end
	temp = temp .. tabs .. "}";

	return temp;
end

function addon:Debug(t, ...)
	if not self.debugChannel and self.debugChannel ~= false then
		-- We want to check just once, so if you add a debug channel later just do a /reload (registering an event for this is wasted resources)
		self.debugChannel = false;
		
		for i = 1, NUM_CHAT_WINDOWS do
			local name = GetChatWindowInfo(i);
			
			if name:upper() == "IMDEBUG" then
				self.debugChannel = _G["ChatFrame" .. i];
			end
		end
	end
	
	if self.debugChannel then
		if type(t) == "table" then
			t = ReadableTable(t, true);
		end
		
		self.debugChannel:AddMessage("|cffffff00Inventorium|r:" .. sformat(t, ...));
	end
end