Quantcast
-- $Revision$
-- Cauldron tradeskill functions

function Cauldron:UpdateSkills()
	self:debug("UpdateSkills enter");

--	self:info("updating skills: "..debugstack());

--	self:info("updatingSkills="..tostring(self.updatingSkills));
	if not self.updatingSkills then
		self:debug("not updating skills; return");
		return;
	end

	local skillName = GetTradeSkillLine();
	local baseSkillName = skillName;
	self:debug("UpdateSkills: skillName="..skillName);

	if skillName == "UNKNOWN" then
		return;
	end

	if IsTradeSkillLinked() then
		skillName = "Linked-"..skillName;
	end

	-- initialize the trade skill entry
	if not self.db.realm.userdata[self.vars.playername].skills[skillName] then
		self.db.realm.userdata[self.vars.playername].skills[skillName] = {};
	end

	-- initialize the reagent map
	if not self.db.realm.userdata[self.vars.playername].reagentMap then
		self.db.realm.userdata[self.vars.playername].reagentMap = {};
	end

	-- save the skill entry in a local var
	local skillDB = self.db.realm.userdata[self.vars.playername].skills[skillName];
	if not skillDB.recipes then
		skillDB.recipes = {};
	end

	-- initialize window information, if necessary
	if not skillDB.window then
		skillDB.window = {
			search = "",
			filter = {
				optimal = true,
				medium = true,
				easy = true,
				trivial = true,
				haveAllReagents = false,
				haveKeyReagents = false,
				haveAnyReagents = false,
				sortDefault = true,
				sortDifficulty = false,
				sortAlpha = false,
				sortBenefit = false,
				sortItemLevel = true,
	   			sortRequiredLevel = false,
	   			sortFavorites = false,
				favorites = false,
				achievements = false,
			},
			skills = {},
			slots = {},
			categories = {},
			selected = 1,
		};
	end

	-- make sure we're getting a full list
	SetTradeSkillItemNameFilter(nil);
	SetTradeSkillItemLevelFilter(0, 0);

	local category = "";

	for i=1,GetNumTradeSkills() do
--		self:info("i="..tostring(i));
		local name, difficulty, avail, expanded, verb = GetTradeSkillInfo(i);
--		self:debug("UpdateSkills: name="..name.."; difficulty="..difficulty.."; avail="..avail);

		if name and difficulty ~= "header" then

			local minMade, maxMade = GetTradeSkillNumMade(i);

			-- adjust min/max made for specialities
			-- TODO

			-- check if we're updating or adding
			if skillDB.recipes[name] then
				-- update the recipe info
				skillDB.recipes[name].index = i;
				skillDB.recipes[name].difficulty = difficulty;
				skillDB.recipes[name].available = avail;
				skillDB.recipes[name].minMade = minMade;
				skillDB.recipes[name].maxMade = maxMade;

				-- update reagent skill index
				for _,r in ipairs(skillDB.recipes[name].reagents) do
					r.skillIndex = i;
				end
			else
				-- add the recipe info and other miscellaneous info
				local itemLink = GetTradeSkillItemLink(i);
				local recipeLink = GetTradeSkillRecipeLink(i);
				local _, _, _, _, _, _, _, _, slot, _ = GetItemInfo(itemLink);

				local keywords = name;

				-- fill in the db entry
				skillDB.recipes[name] = {
					['index'] = i,
					['name'] = name,
					['itemLink'] = itemLink,
					['recipeLink'] = recipeLink,
					['icon'] = GetTradeSkillIcon(i),
					['tradeskill'] = baseSkillName,
					['difficulty'] = difficulty,
					['available'] = avail,
					['minMade'] = minMade,
					['maxMade'] = maxMade,

					-- filter information
					['slot'] = slot,
					['defaultCategory'] = category,

					['reagents'] = {},
				};

				-- set the action verb for this skill
				skillDB.recipes[name].verb = verb;

				-- make sure the skill window info is initialized
				if not skillDB.window.skills[name] then
					skillDB.window.skills[name] = {
						['expanded'] = false,
						['favorite'] = false,
					};
				end

				-- make sure the category for the window is initialized
				if category ~= "" then
					if not skillDB.window.categories[category] then
						skillDB.window.categories[category] = {
							['shown'] = true,
							['expanded'] = true,
						};
					end
				end

				-- populate the slot list
				Cauldron:debug("slot: "..tostring(slot));
				if slot and (slot ~= "") then
						skillDB.window.slots[slot] = {
							checked = true,
						};
				else
					-- special slot representing items that don't have a slot
					skillDB.window.slots.none = {
						checked = true,
					};
				end

				-- populate the reagent list
				local num = GetTradeSkillNumReagents(i);
				for j=1,num do
					local rName, rIcon, rCount, _ = GetTradeSkillReagentInfo(i, j);
					local rLink = GetTradeSkillReagentItemLink(i, j);
					local rItemId = Cauldron:GetIdFromLink(rLink);
					local key = not Cauldron:IsVendorItem(rItemId);

					if (not rName) or (not rIcon) or (not rLink) then
						Cauldron:debug("First attempt to get reagent info failed.  Trying again.  (skill: "..name..", reagentIndex: "..j..", name: "..tostring(rName)..", icon: "..tostring(rIcon)..", link: "..tostring(rLink)..")");
						-- be persisent about getting the info
						rName, rIcon, _, _ = GetTradeSkillReagentInfo(i, j);
						rLink = GetTradeSkillReagentItemLink(i, j);
					end

					if (not rName) or (not rIcon) or (not rLink) then
						-- be persisent about getting the info
						Cauldron:error("Can't get name/icon/link for reagent! (skill: "..name..", reagentIndex: "..j..", name: "..tostring(rName)..", icon: "..tostring(rIcon)..", link: "..tostring(rLink)..")");
					end

					local r = {
						["index"] = j,
						["name"] = rName,
						["numRequired"] = rCount,
						["skillIndex"] = i,
						["icon"] = rIcon,
						["link"] = rLink,
						["key"] = key,
					};

					table.insert(skillDB.recipes[name].reagents, r);

					-- add the reagent to the reagent map

					if rName then
						if not self.db.realm.userdata[self.vars.playername].reagentMap[rName] then
							self.db.realm.userdata[self.vars.playername].reagentMap[rName] = {};
						end
						table.insert(self.db.realm.userdata[self.vars.playername].reagentMap[rName], skillName..";"..recipeLink);

						keywords = keywords..","..rName;
					end
				end

				-- fill in the keywords db entry
				skillDB.recipes[name].keywords = keywords;
			end
	    else
	    	-- save the header name
	    	if name then
		    	category = name;

				-- expand the header, so we get all the skills
				if not expanded then
					ExpandTradeSkillSubClass(i);
				end
			end
		end
	end

	self:debug("UpdateSkills exit");
end

function Cauldron:GetDefaultCategories(player, skillName)
	self:debug("GetDefaultCategories enter");

	local categories = {};

	if self.db then
		for name, info in pairs(self.db.realm.userdata[player].skills[skillName].window.categories) do
			categories[name] = info.shown;
		end
	end

--	table.sort(categories);

	self:debug("GetDefaultCategories exit");

	return categories;
end

--[[
function Cauldron:GetCategories(skillList)
	self:debug("GetCategories enter");

	local categories = {};

	if not skillList then
		return categories;
	end

	for _, info in ipairs(skillList) do
		table.insert(categories, info.defaultCategory);
	end

	table.sort(categories);

	self:debug("GetCategories exit");

	return categories;
end
--]]

function Cauldron:GetSlots(player, skillName)
	self:debug("GetSlots enter");

	local slots = {};

	if self.db then
		for name, info in pairs(self.db.realm.userdata[player].skills[skillName].window.slots) do
			slots[name] = info;
		end
	end

--	table.sort(slots);

	self:debug("GetSlots exit");

	return slots;
end

function Cauldron:GetSkillList(playername, skillName)
	self:debug("GetSkillList enter");

	if (not playername) or
	   (not skillName) then
		self:warn("GetSkillList: playername ("..tostring(playername)..") or skillName ("..tostring(skillName)..") not set!");
		return;
	end
	if (not self.db.realm.userdata[playername]) or
	   (not self.db.realm.userdata[playername].skills[skillName]) then
		return;
	end

	local skills = {};

	for name, recipe in pairs(self.db.realm.userdata[playername].skills[skillName].recipes) do
		self:debug("GetSkillList: name="..name);

		local add = true;

		-- check the search text
		local search = self.db.realm.userdata[playername].skills[skillName].window.search or "";
		self:debug("GetSkillList: search="..search);
		if #search > 0 then
			-- check for numbers
			local minLevel, maxLevel;
--			local matchItemLevel = false;
			local approxLevel, matchItemLevel = strmatch(search, "^~(%d+)(i?)");
			-- self:debug("GetSkillList: approxLevel="..tostring(approxLevel));
			if approxLevel then
				minLevel = approxLevel - 2;
				maxLevel = approxLevel + 2;
			else
				minLevel, maxLevel, matchItemLevel = strmatch(search, "^(%d+)%s*-*%s*(%d*)(i?)$");
				-- self:debug("GetSkillList: minLevel="..tostring(minLevel).."; maxLevel="..tostring(maxLevel));
			end
			if minLevel then
				if maxLevel == "" or maxLevel == "i" or maxLevel < minLevel then
					maxLevel = minLevel;
				end
				-- cleanse the level values
				minLevel = tonumber(minLevel) or 0;
				maxLevel = tonumber(maxLevel) or 0;

				local _,_,_,itemLevel,reqLevel,_,_,_,_,_ = GetItemInfo(recipe.itemLink);
				if itemLevel and (matchItemLevel == "i") then
					itemLevel = tonumber(itemLevel) or 0;
					self:debug("GetSkillList: match by item level");
					if itemLevel < minLevel or itemLevel > maxLevel then
						self:debug("skipping recipe: "..name.." (level: "..tostring(itemLevel)..")");
						add = false;
					end
				elseif reqLevel then
					reqLevel = tonumber(reqLevel) or 0;
					self:debug("GetSkillList: match by required level");
					if reqLevel < minLevel or reqLevel > maxLevel then
						self:debug("skipping recipe: "..name.." (level: "..tostring(reqLevel)..")");
						add = false;
					end
				end
			else
				-- match name or reagents
				self:debug("GetSkillList: match by name or reagents");
				if not string.find(string.lower(recipe.keywords or ""), string.lower(search)) then
					self:debug("skipping recipe: "..name.." (keywords: "..tostring(recipe.keywords)..")");
					add = false;
				end
			end

		end

		-- check difficulty filter
		if not self.db.realm.userdata[playername].skills[skillName].window.filter[recipe.difficulty] then
			self:debug("skipping recipe: "..name.." (difficulty: "..recipe.difficulty..")");
			add = false;
		end

		-- check categories
		local catInfo = self.db.realm.userdata[playername].skills[skillName].window.categories[recipe.defaultCategory];
		if catInfo and (not catInfo.shown) then
			self:debug("skipping recipe: "..name.." (category: "..recipe.defaultCategory..")");
			add = false;
		end

		-- check slot
		local slotName = recipe.slot;
		if (not slotName) or (slotName == "") then
			slotName = "none";
		end
		local slotInfo = self.db.realm.userdata[playername].skills[skillName].window.slots[slotName];
		self:debug("slotInfo: "..tostring(slotInfo));
		if slotInfo and not(slotInfo.checked) then
			self:debug("skipping recipe: "..name.." (slot: "..slotName..")");
			add = false;
		end

		-- check reagent filter
		if self.db.realm.userdata[playername].skills[skillName].window.filter.haveAllReagents then
			-- check if the available count is 0
			if recipe.available == 0 then
				add = false;
			end
		elseif self.db.realm.userdata[playername].skills[skillName].window.filter.haveKeyReagents then
			-- check if the reagent count for key reagents is 0
			for _, rinfo in ipairs(recipe.reagents) do
				-- check possession count
				if (GetItemCount(rinfo.link, false) < rinfo.numRequired) and (rinfo.key) then
					add = false;
				end
			end
		elseif self.db.realm.userdata[playername].skills[skillName].window.filter.haveAnyReagents then
			-- check if the reagent count for any reagent is > 0
			add = false;
			for _, rinfo in ipairs(recipe.reagents) do
				-- check possession count
				if GetItemCount(rinfo.link, false) > 0 then
					add = true;
				end
			end
		end

		-- check favorites filter
		if self.db.realm.userdata[playername].skills[skillName].window.filter.favorites then
			if not self.db.realm.userdata[playername].skills[skillName].window.skills[recipe.name].favorite then
				--@alpha@
				self:debug("skipping recipe: "..name.." (favorite: "..tostring(self.db.realm.userdata[playername].skills[skillName].window.skills[recipe.name].favorite)..")");
				--@end-alpha@
				add = false;
			end
		end

		-- check achievements filter
		if self.db.realm.userdata[playername].skills[skillName].window.filter.achievements then
			local achievements = Cauldron:GetAchievementsForSkill(recipe);
			if (not achievements) or (#achievements == 0) then
				--@alpha@
				self:debug("skipping recipe: "..name.." (achievements: "..tostring(self.db.realm.userdata[playername].skills[skillName].window.skills[recipe.name].achievements)..")");
				--@end-alpha@
				add = false;
			end
		end

		-- we got here, add the recipe to the list
		if add then
			table.insert(skills, recipe);
		end
	end

	-- sort the list
	table.sort(skills, function(r1, r2)
			if (not r1) or (not r2) then
				return true;
			end

			--@alpha@
			self:debug("GetSkillList: sorting: r1.name="..r1.name.."; r2.name="..r2.name);
			--@end-alpha@
			if self.db.realm.userdata[playername].skills[skillName].window.filter.sortDefault then
				--@alpha@
				self:debug("GetSkillList: sorting by default (skill index)");
				--@end-alpha@
				return r1.index < r2.index;
			elseif self.db.realm.userdata[playername].skills[skillName].window.filter.sortAlpha then
				--@alpha@
				self:debug("GetSkillList: sorting by alpha");
				--@end-alpha@
				return r1.name < r2.name;
			elseif self.db.realm.userdata[playername].skills[skillName].window.filter.sortDifficulty then
				--@alpha@
				self:debug("GetSkillList: sorting by difficulty");
				--@end-alpha@
				local difficulty = {
					optimal = 4,
					medium = 3,
					easy = 2,
					trivial = 1,
				};

				--@alpha@
				self:debug("GetSkillList: r1.difficulty="..r1.difficulty);
				self:debug("GetSkillList: r2.difficulty="..r2.difficulty);
				--@end-alpha@
				return difficulty[r1.difficulty] > difficulty[r2.difficulty];
			elseif self.db.realm.userdata[playername].skills[skillName].window.filter.sortItemLevel then
				--@alpha@
				self:debug("GetSkillList: sorting by item level");
				--@end-alpha@

				-- get item info from item link
				local _,_,_,r1iLevel,_,_,_,_,_,_ = GetItemInfo(r1.itemLink);
				local _,_,_,r2iLevel,_,_,_,_,_,_ = GetItemInfo(r2.itemLink);

				return (r2iLevel or 0) < (r1iLevel or 0);
			elseif self.db.realm.userdata[playername].skills[skillName].window.filter.sortRequiredLevel then
				--@alpha@
				self:debug("GetSkillList: sorting by required level");
				--@end-alpha@

				-- get item info from item link
				local _,_,_,_,r1Level,_,_,_,_,_ = GetItemInfo(r1.itemLink);
				local _,_,_,_,r2Level,_,_,_,_,_ = GetItemInfo(r2.itemLink);

				return (r2Level or 0) < (r1Level or 0);
			elseif self.db.realm.userdata[playername].skills[skillName].window.filter.sortFavorites then
				--@alpha@
				self:debug("GetSkillList: sorting by favorites");
				--@end-alpha@

				local r1f = r1.favorite and 100 or 1;
				local r2f = r2.favorite and 100 or 1;

				return r2f < r1f;
			elseif self.db.realm.userdata[playername].skills[skillName].window.filter.sortBenefit then
				--@alpha@
				self:debug("GetSkillList: returning true for benefit sorting");
				--@end-alpha@
				return true; -- TODO
			end

			--@alpha@
			self:debug("GetSkillList: returning default true");
			--@end-alpha@
			return true;
		end);

	--@alpha@
	self:debug("GetSkillList exit");

	return skills;
end

function Cauldron:GetSkillInfo(tradeskill, skill)
	self:debug("GetSkillInfo enter");

	-- sanity checks
	if (not tradeskill) or (not skill) then
		self:warn("GetSkillInfo: missing tradeskill ("..tostring(tradeskill)..") or skill ("..tostring(skill)..")!");
		return nil;
	end

	if not self.db.realm.userdata[self.vars.playername].skills[tradeskill] then
		return nil;
	end

	local skillInfo = self.db.realm.userdata[self.vars.playername].skills[tradeskill].recipes[skill];
	if not skillInfo then
		-- couldn't find a skill with the item name, so scan the list for skills that craft
		-- the item
		for _, recipe in pairs(self.db.realm.userdata[self.vars.playername].skills[tradeskill].recipes) do
			local name, _ = GetItemInfo(recipe.itemLink);
			if name == skill then
				return recipe;
			end
		end
	end

	self:debug("GetSkillInfo exit");

	return skillInfo;
end

function Cauldron:GetSkillInfoForItem(item)

	if not item then
		Cauldron:error("GetSkillInfoForItem: item is nil!");
		return nil;
	end

	for tradeskill, list in pairs(self.db.realm.userdata[self.vars.playername].skills) do
		-- skip linked skills
		if not (string.find(tradeskill, "Linked-")) then
			for _, recipeInfo in pairs(list.recipes) do
				local name, _ = GetItemInfo(recipeInfo.itemLink);
				if name == item then
					return recipeInfo;
				end
			end
		end
	end

	return nil;
end

function Cauldron:GetSkillInfoForLink(recipeLink)

	if not recipeLink then
		Cauldron:error("GetSkillInfoForLink: recipeLink is nil!");
		return nil;
	end

	for tradeskill, list in pairs(self.db.realm.userdata[self.vars.playername].skills) do
		-- skip linked skills
		if not (string.find(tradeskill, "Linked-")) then
			for _, recipeInfo in pairs(list.recipes) do
				local id = Cauldron:GetIdFromLink(recipeLink);
				local recipeId = Cauldron:GetIdFromLink(recipeInfo.recipeLink);
				if id == recipeId then
					return recipeInfo;
				end
			end
		end
	end

	return nil;
end

function Cauldron:GetSkillInfoByIndex(itemIndex)

	if not itemIndex then
		Cauldron:error("GetSkillInfoByIndex: itemIndex is nil!");
		return nil;
	end

	for tradeskill, list in pairs(self.db.realm.userdata[self.vars.playername].skills) do
		-- skip linked skills
		if not (string.find(tradeskill, "Linked-")) then
			for _, recipeInfo in pairs(list.recipes) do
				if recipeInfo.index == itemIndex then
					return recipeInfo;
				end
			end
		end
	end

	return nil;
end

function Cauldron:GetReagentInfoByIndex(item, reagentIndex)

	local skillInfo = nil;

	if type(item) == "table" then
		skillInfo = item;
	elseif type(item) == "number" then
		skillInfo = self:GetSkillInfoByIndex(tonumber(item));
	end

	if skillInfo then
		for i, reagentInfo in ipairs(skillInfo.reagents) do
			if reagentInfo.index == reagentIndex then
				return reagentInfo;
			end
		end
	end

	return nil;
end

function Cauldron:GetRequiredItems(skillInfo, amount)

	local intermediates = {};
	local reagents = {};

	-- sanity checks
	if not skillInfo then
		-- TODO: display error
		return intermediates, reagents;
	end

	amount = math.max(1, tonumber(amount) or 1);

	-- find out what the reagents are
	for i, reagent in ipairs(skillInfo.reagents) do

		-- copy the reagent info so we can modify the amounts
		local r = CopyTable(reagent);
		r.numRequired = r.numRequired * amount;

		-- see if the character can make the item
		local si = Cauldron:GetSkillInfoForItem(r.name);
		if si then
			table.insert(intermediates, r);
		else
			table.insert(reagents, r);
		end
	end

	return intermediates, reagents;
end

local function split(sep, str)
	local result = {};

	while str do
		local s1, rest = strsplit(sep, str, 2);
		table.insert(result, s1);
		str = rest;
	end

	return result;
end

--[==[
function Cauldron:GetReagentsForSkill(skillInfo)

	if not skillInfo then
		self:error("No skill info provided!");
		return {};
	end

	local reagents = {};

	local itemId = self:GetIdFromLink(skillInfo.itemLink);

	local skillName = CURRENT_TRADESKILL;
	local baseSkillName = CURRENT_TRADESKILL;
	if IsTradeSkillLinked() then
		skillName = "Linked-"..skillName;
	end

	-- check if the reagents are already populated
	if #skillInfo.reagents > 0 then
		return skillInfo.reagents;
	end

	-- check for the proper trade skill context
	if CURRENT_TRADESKILL == skillInfo.tradeskill then

		for i=1,GetTradeSkillNumReagents(skillInfo.index) do
			local name, icon, count, _ = GetTradeSkillReagentInfo(skillInfo.index, i);
			local link = GetTradeSkillReagentItemLink(skillInfo.index, i);
			local itemId = Cauldron:GetIdFromLink(link);
			local value, set = self.libs.PT:ItemInSet(itemId, "Tradeskill.Mat.BySource.Vendor");
			local key = true;
			if value then
				key = false;
			end

			local r = {
--				["toonHas"] = GetItemCount(link),
				["name"] = name,
				["numRequired"] = count,
				["skillIndex"] = skillInfo.index,
				["icon"] = icon,
				["link"] = link,
				["key"] = key,
			};

			table.insert(reagents, r);
		end

		-- save the reagent list
		skillInfo.reagents = reagents;
	else
		self:warn("No tradeskill context found; using PeriodicTable for reagent info!");

		-- check the standard skill
		local reagentStr = self.libs.PT:ItemInSet(itemId, "TradeskillResultMats.Forward."..skillInfo.tradeskill);
		if not reagentStr then
			-- lookup the mapped skill
			local skillMap = {
				['Mining'] = 'Smelting',
	--			['Inscription'] = 'Milling',
	--			['Jewelcrafting'] = 'Prospecting',
			};
			if skillMap[skillInfo.tradeskill] then
				reagentStr = self.libs.PT:ItemInSet(itemId, "TradeskillResultMats.Forward."..skillMap[skillInfo.tradeskill]);
			end
		end
		if not reagentStr then
			self:error("No reagents found for skill: "..skillInfo.name);
			return {};
		end

		-- split the reagent info
		for _, reagent in ipairs(split(";", reagentStr)) do
			local id, numRequired = strsplit("x", reagent);

			-- get item details for the reagent
			local name, link, _, _, _, _, _, _, _, icon = GetItemInfo(id);

			local r = {
--				["toonHas"] = GetItemCount(id),
				["name"] = name,
				["numRequired"] = tonumber(numRequired),
				["skillIndex"] = skillInfo.index,
				["icon"] = icon,
				["link"] = link,
			};

			table.insert(reagents, r);
		end
	end

	return reagents;
end
--]==]

function Cauldron:GetSkillsForReagent(id)
	if not id then
		return {};
	end

	local itemName,itemInfo,_ = GetItemInfo(id);
	local skillList = {};

	if not Cauldron.db.realm.userdata[Cauldron.vars.playername].reagentMap then
		return {};
	end

	-- find the reagent in the map
	local reagentMap = Cauldron.db.realm.userdata[Cauldron.vars.playername].reagentMap[itemName];
	if reagentMap then
		for _,skill in ipairs(reagentMap) do
			table.insert(skillList, skill);
		end
	end

	return skillList;
end