--[[------------------------------------------------------------------ --SousChef.lua --Author: Wobin inspired by ingeniousclown's Research Assistant Thanks to Ayantir for the French translations, and sirinsidiator for the German ------------------------------------------------------------------]]-- SousChef = {} SousChef.Utility = {} SousChef.Media = {} SousChef.version = "2.19-beta1" local SousChef = SousChef local u = SousChef.Utility local LAM = LibStub:GetLibrary("LibAddonMenu-2.0") local BACKPACK = ZO_PlayerInventoryBackpack local BANK = ZO_PlayerBankBackpack local GUILD_BANK = ZO_GuildBankBackpack SousChef.Pantry = {} SousChef.Cookbook = {} SousChef.CookbookIndex = {} SousChef.ReverseCookbook = {} SousChef.settings = nil SousChef.slotLines = {} SousChef.hookedFunctions = {} SousChef.hookedDataFunction = nil SousChef.lang = GetCVar("Language.2") if SousChef.lang ~= "en" and SousChef.lang ~= "de" and SousChef.lang ~= "fr" then SousChef.lang = "en" end local rowHandler = {} -- FUNCTIONS -- AddRecipe(Cookbook, link) adds the linked recipe to the player cookbook if it isn't there already. local function AddRecipe(Cookbook, link) for _,v in pairs(Cookbook) do if v == link then return end end table.insert(Cookbook, link) end -- RefreshViews() incorporates changes to the rendered inventory if it is visible. function SousChef:RefreshViews() ZO_ScrollList_RefreshVisible(BACKPACK) ZO_ScrollList_RefreshVisible(BANK) ZO_ScrollList_RefreshVisible(GUILD_BANK) end -- every time you change the main character in the settings menu, you need to update the "player" cookbook, cookbook index, pantry, and reverse cookbook with the new character's data local function ChangeMainChar() if SousChef.settings.mainChar == "(current)" then SousChef.CookbookIndex = SousChef.settings.CookbookIndex[GetUnitName("player")] else SousChef.CookbookIndex = SousChef.settings.CookbookIndex[SousChef.settings.mainChar] end SousChef.Cookbook = {} SousChef.Pantry = {} SousChef.ReverseCookbook = {} if SousChef.CookbookIndex == nil then SousChef.CookbookIndex = {} end for name, data in pairs(SousChef.CookbookIndex) do SousChef.Cookbook[u.CleanString((GetRecipeResultItemInfo(data.listIndex, data.recipeIndex)))] = true local _, _, _, level, color, specialType = GetRecipeInfo(data.listIndex, data.recipeIndex) for ingredient = 1, data.numIngredients do local ingredientID = u.GetItemID(GetRecipeIngredientItemLink(data.listIndex, data.recipeIndex, ingredient)) if SousChef.Pantry[ingredientID] == nil then if GetItemLinkItemType(GetRecipeIngredientItemLink(listIndex, recipeIndex, ingredientIndex)) == ITEMTYPE_FLAVOR then SousChef.settings.Pantry[link] = 2 elseif GetItemLinkItemType(GetRecipeIngredientItemLink(listIndex, recipeIndex, ingredientIndex)) == ITEMTYPE_SPICE then SousChef.settings.Pantry[link] = 1 elseif ingredient <= color then -- if this is a base ingredient, record the skill it governs if ingredient == 1 and (data.listIndex == 1 or data.listIndex == 4 or data.listIndex == 5 or data.listIndex == 7) then -- meats SousChef.Pantry[ingredientID] = 3 elseif (ingredient == 1 and (data.listIndex == 2 or data.listIndex == 6) or (ingredient == 2 and (data.listIndex == 4 or data.listIndex == 7))) then -- fruits SousChef.Pantry[ingredientID] = 4 elseif (ingredient == 1 and data.listIndex == 3) or (ingredient == 2 and (data.listIndex == 5 or data.listIndex == 6)) or (ingredient == 3 and data.listIndex == 7) then -- veggies SousChef.Pantry[ingredientID] = 5 elseif (ingredient == 1 and (data.listIndex == 8 or data.listIndex == 11 or data.listIndex == 12 or data.listIndex == 14)) then -- booze SousChef.Pantry[ingredientID] = 6 elseif (ingredient == 1 and (data.listIndex == 9 or data.listIndex == 13)) or (ingredient == 2 and (data.listIndex == 11 or data.listIndex == 14)) then -- tea SousChef.Pantry[ingredientID] = 7 else -- probably tonics SousChef.Pantry[ingredientID] = 8 end else -- this is a leveller, record whether it makes food or drinks SousChef.Pantry[ingredientID] = specialType + 8 end end if not SousChef.ReverseCookbook[ingredientID] then SousChef.ReverseCookbook[ingredientID] = {} end AddRecipe(SousChef.ReverseCookbook[ingredientID], name) end end end -- ParseRecipes() goes through the player's known recipes and records their info. function SousChef:ParseRecipes() local lists = GetNumRecipeLists() for listIndex = 1, lists do local name, count = GetRecipeListInfo(listIndex) for recipeIndex = 1, count do if GetRecipeInfo(listIndex, recipeIndex) then -- Store the recipes known: local recipeName = u.CleanString((GetRecipeResultItemInfo(listIndex, recipeIndex))) if not SousChef.settings.Cookbook[recipeName] then SousChef.settings.Cookbook[recipeName] = {} end SousChef.settings.Cookbook[recipeName][GetUnitName("player")] = true -- now record information about the recipe's ingregients local _, _, ingredientCount, level, color, specialType = GetRecipeInfo(listIndex, recipeIndex) -- store the recipe's index numbers and number of ingredients local resultLink = GetRecipeResultItemLink(listIndex, recipeIndex) local coloredName = u.GetColoredLinkName(resultLink) if not SousChef.settings.CookbookIndex[GetUnitName("player")] then SousChef.settings.CookbookIndex[GetUnitName("player")] = {} end SousChef.settings.CookbookIndex[GetUnitName("player")][coloredName] = {listIndex = listIndex, recipeIndex = recipeIndex, numIngredients = ingredientCount} -- now, for every ingredient in the current recipe... for ingredientIndex = 1, ingredientCount do local link = u.GetItemID(GetRecipeIngredientItemLink(listIndex, recipeIndex, ingredientIndex, LINK_STYLE_DEFAULT)) -- Store the fact that the ingredient is used, if we don't already know that if SousChef.settings.Pantry[link] == nil then if GetItemLinkItemType(GetRecipeIngredientItemLink(listIndex, recipeIndex, ingredientIndex)) == ITEMTYPE_FLAVOR then SousChef.settings.Pantry[link] = 2 elseif GetItemLinkItemType(GetRecipeIngredientItemLink(listIndex, recipeIndex, ingredientIndex)) == ITEMTYPE_SPICE then SousChef.settings.Pantry[link] = 1 elseif ingredientIndex <= color then -- if this is a base ingredient, record the skill it governs if ingredientIndex == 1 and (listIndex == 1 or listIndex == 4 or listIndex == 5 or listIndex == 7) then -- meats SousChef.settings.Pantry[link] = 3 elseif (ingredientIndex == 1 and (listIndex == 2 or listIndex == 6) or (ingredientIndex == 2 and (listIndex == 4 or listIndex == 7))) then -- fruits SousChef.settings.Pantry[link] = 4 elseif (ingredientIndex == 1 and listIndex == 3) or (ingredientIndex == 2 and (listIndex == 5 or listIndex == 6)) or (ingredientIndex == 3 and listIndex == 7) then -- veggies SousChef.settings.Pantry[link] = 5 elseif (ingredientIndex == 1 and (listIndex == 8 or listIndex == 11 or listIndex == 12 or listIndex == 14)) then -- booze SousChef.settings.Pantry[link] = 6 elseif (ingredientIndex == 1 and (listIndex == 9 or listIndex == 13)) or (ingredientIndex == 2 and (listIndex == 11 or listIndex == 14)) then -- tea SousChef.settings.Pantry[link] = 7 else -- probably tonics SousChef.settings.Pantry[link] = 8 end else -- this is a leveller, record whether it makes food or drinks SousChef.settings.Pantry[link] = specialType + 8 end end -- Store the recipe it's used in to the reverseCookbook if not SousChef.settings.ReverseCookbook[link] then SousChef.settings.ReverseCookbook[link] = {} end AddRecipe(SousChef.settings.ReverseCookbook[link], coloredName) end end end end ChangeMainChar() SousChef:RefreshViews() end -- auto-junk ingredients if they're not in the shopping list local function AutoJunker() local bagSize = GetBagSize(BAG_BACKPACK) for i = 0, bagSize do local itemLink = GetItemLink(BAG_BACKPACK, i) if itemLink ~= "" then local itemType = GetItemLinkItemType(itemLink) if itemType == ITEMTYPE_FLAVORING or itemType == ITEMTYPE_SPICE or itemType == ITEMTYPE_INGREDIENT then if not SousChef:IsOnShoppingList(u.GetItemID(itemLink)) then SetItemIsJunk(BAG_BACKPACK, i, true) end end end end end -- removes a character from SousChef's saved variables local function DeleteCharacter(charName) -- remove any of the character's recipes from the shopping list for recipe, people in pairs(SousChef.settings.shoppingList) do people[charName] = nil if NonContiguousCount(people) == 0 then SousChef.settings.shoppingList[recipe] = nil end end -- record all the recipes that only the deleted character knows local soloRecipes = {} -- remove the character from the cookbook for recipe, people in pairs(SousChef.settings.Cookbook) do people[charName] = nil if NonContiguousCount(people) == 0 then table.insert(soloRecipes, recipe) SousChef.settings.Cookbook[recipe] = nil end end -- record any ingredients that only the deleted character could use local soloIngredients = {} -- if we're removing any recipes, remove them from the reverse cookbook -- first, get the removed recipe info from the cookbook index for recipe, recipeInfo in pairs(SousChef.settings.CookbookIndex[charName]) do for i, delRecipe in ipairs(soloRecipes) do if (u.CleanString(string.sub(recipe, 9, -3))) == delRecipe then for j = 1, recipeInfo.numIngredients, 1 do local ingrNum = u.GetItemID(GetRecipeIngredientItemLink(recipeInfo.listIndex, recipeInfo.recipeIndex, j)) for k, rName in pairs(SousChef.settings.ReverseCookbook[ingrNum]) do if rName == recipe then SousChef.settings.ReverseCookbook[ingrNum][k] = nil if NonContiguousCount(SousChef.settings.ReverseCookbook[ingrNum]) == 0 then table.insert(soloIngredients, ingrNum) SousChef.settings.ReverseCookbook[ingrNum] = nil end end end end end end end -- remove any ingredients from the pantry that we no longer know how to use for i, ingredient in ipairs(soloIngredients) do SousChef.settings.Pantry[ingredient] = nil end -- remove the character from the cookbook index SousChef.settings.CookbookIndex[charName] = nil -- remove the character from the list of known characters for k, v in pairs(SousChef.settings.knownChars) do if v == charName then table.remove(SousChef.settings.knownChars, k) break end end -- and finally, refresh the data currently in memory ChangeMainChar() end -- SousChefCreateSettings() creates the configuration menu for the add-on local function SousChefCreateSettings() local str = SousChef.Strings[SousChef.lang] local panelData = { type = "panel", name = "Sous Chef", displayName = "Sous Chef", author = "Wobin & KatKat42 & CrazyDutchGuy", version = SousChef.version, registerForRefresh = true, slashCommand = "/souschef", } LAM:RegisterAddonPanel("SousChefSettings", panelData) local optionsMenu = { } table.insert(optionsMenu, { type = "dropdown", name = str.MENU_MAIN_CHAR, tooltip = str.MENU_MAIN_CHAR_TOOLTIP, choices = SousChef.settings.knownChars, getFunc = function() return SousChef.settings.mainChar end, setFunc = function(value) SousChef.settings.mainChar = value ChangeMainChar() SousChef.RefreshViews() end, disabled = function() return not SousChef.settings.processRecipes end, }) table.insert(optionsMenu, { type = "header", name = str.MENU_RECIPE_HEADER, width = "full", }) table.insert(optionsMenu, { type = "checkbox", name = str.MENU_PROCESS_RECIPES, tooltip = str.MENU_PROCESS_RECIPES_TOOLTIP, getFunc = function() return SousChef.settings.processRecipes end, setFunc = function(value) SousChef.settings.processRecipes = value SousChef.RefreshViews() end, width = "full", }) table.insert(optionsMenu, { type = "dropdown", name = str.MENU_MARK_IF_KNOWN, tooltip = str.MENU_MARK_IF_KNOWN_TOOLTIP, choices = {str.MENU_KNOWN, str.MENU_UNKNOWN}, getFunc = function() if SousChef.settings.checkKnown == "known" then return str.MENU_KNOWN elseif SousChef.settings.checkKnown == "unknown" then return str.MENU_UNKNOWN else -- can't happen d("Yikes! MENU_MARK_IF_KNOWN getter") return str.MENU_UNKNOWN end end, setFunc = function(valueString) if valueString == str.MENU_KNOWN then SousChef.settings.checkKnown = "known" elseif valueString == str.MENU_UNKNOWN then SousChef.settings.checkKnown = "unknown" else -- can't happen d("Oops! MENU_MARK_IF_KNOWN setter") SousChef.settings.checkKnown = "unknown" end SousChef.RefreshViews() end, disabled = function() return not SousChef.settings.processRecipes end, }) table.insert(optionsMenu, { type = "checkbox", name = str.MENU_MARK_IF_ALT_KNOWS, tooltip = str.MENU_MARK_IF_ALT_KNOWS_TOOLTIP, getFunc = function() return SousChef.settings.markAlt end, setFunc = function(value) SousChef.settings.markAlt = value SousChef:RefreshViews() SousChef.RefreshViews() end, disabled = function() return (not SousChef.settings.processRecipes) or (SousChef.settings.checkKnown == "known") end, }) table.insert(optionsMenu, { type = "header", name = str.MENU_RECIPE_TOOLTIP_HEADER, width = "full", }) table.insert(optionsMenu, { type = "checkbox", name = str.MENU_TOOLTIP_IF_ALT_KNOWS, tooltip = str.MENU_TOOLTIP_IF_ALT_KNOWS_TOOLTIP, getFunc = function() return SousChef.settings.showAltKnowledge end, setFunc = function(value) SousChef.settings.showAltKnowledge = value SousChef:RefreshViews() end, disabled = function() return not SousChef.settings.processRecipes end, }) table.insert(optionsMenu, { type = "header", name = str.MENU_TOOLTIP_HEADER, width = "full", }) table.insert(optionsMenu, { type = "checkbox", name = str.MENU_TOOLTIP_CLICK, tooltip = str.MENU_TOOLTIP_CLICK_TOOLTIP, warning = str.MENU_RELOAD, getFunc = function() return SousChef.settings.showOnClick end, setFunc = function(value) SousChef.settings.showOnClick = value end, }) table.insert(optionsMenu, { type = "checkbox", name = str.MENU_RESULT_COUNTS, tooltip = str.MENU_RESULT_COUNTS_TOOLTIP, getFunc = function() return SousChef.settings.showCounts end, setFunc = function(value) SousChef.settings.showCounts = value end, }) table.insert(optionsMenu, { type = "checkbox", name = str.MENU_ALT_USE, tooltip = str.MENU_ALT_USE_TOOLTIP, getFunc = function() return SousChef.settings.showAltIngredientKnowledge end, setFunc = function(value) SousChef.settings.showAltIngredientKnowledge = value SousChef:RefreshViews() end, }) table.insert(optionsMenu, { type = "header", name = str.MENU_INDICATOR_HEADER, width = "full", }) table.insert(optionsMenu, { type = "colorpicker", name = str.MENU_INDICATOR_COLOR, tooltip = str.MENU_INDICATOR_COLOR_TOOLTIP, getFunc = function() return SousChef.settings.colour[1], SousChef.settings.colour[2], SousChef.settings.colour[3] end, setFunc = function(r,g,b) SousChef.settings.colour[1] = r SousChef.settings.colour[2] = g SousChef.settings.colour[3] = b SousChef:RefreshViews() end, }) table.insert(optionsMenu, { type = "colorpicker", name = str.MENU_SHOPPING_COLOR, tooltip = str.MENU_SHOPPING_COLOR_TOOLTIP, getFunc = function() return SousChef.settings.shoppingColour[1], SousChef.settings.shoppingColour[2], SousChef.settings.shoppingColour[3] end, setFunc = function(r,g,b) SousChef.settings.shoppingColour[1] = r SousChef.settings.shoppingColour[2] = g SousChef.settings.shoppingColour[3] = b SousChef:RefreshViews() end, }) table.insert(optionsMenu, { type = "checkbox", name = str.MENU_SHOW_ALT_SHOPPING, tooltip = str.MENU_SHOW_ALT_SHOPPING_TOOLTIP, getFunc = function() return SousChef.settings.showAltShopping end, setFunc = function(value) SousChef.settings.showAltShopping = value SousChef:RefreshViews() end, }) table.insert(optionsMenu, { type = "checkbox", name = str.MENU_ONLY_MARK_SHOPPING, tooltip = str.MENU_ONLY_MARK_SHOPPING_TOOLTIP, getFunc = function() return SousChef.settings.onlyShowShopping end, setFunc = function(value) SousChef.settings.onlyShowShopping = value SousChef:RefreshViews() end, }) table.insert(optionsMenu, { type = "checkbox", name = str.MENU_AUTO_JUNK, tooltip = str.MENU_AUTO_JUNK_TOOLTIP, getFunc = function() return SousChef.settings.autoJunk end, setFunc = function(value) if value then SousChef.settings.autoJunk = true EVENT_MANAGER:RegisterForEvent("SousChefLootJunker", EVENT_LOOT_CLOSED, function(...) zo_callLater(AutoJunker, 100) end) else SousChef.settings.autoJunk = false EVENT_MANAGER:UnregisterForEvent("SousChefLootJunker", EVENT_LOOT_CLOSED) end end, warning = str.MENU_AUTO_JUNK_WARNING, }) table.insert(optionsMenu, { type = "checkbox", name = str.MENU_SORT_INGREDIENTS, tooltip = str.MENU_SORT_INGREDIENTS_TOOLTIP, getFunc = function() return SousChef.settings.sortKnownIngredients end, setFunc = function(value) SousChef.settings.sortKnownIngredients = not SousChef.settings.sortKnownIngredients if not SousChef.settings.sortKnownIngredients then SousChef.UnregisterSort() else SousChef.SetupSort() end SousChef.RefreshViews() end, }) local charList = { " " } for k, v in pairs(SousChef.settings.knownChars) do if v ~= "(current)" then table.insert(charList, v) end end local charToDelete = " " table.insert(optionsMenu, { type = "submenu", name = str.MENU_DELETE_CHAR, controls = { [1] = { type = "dropdown", name = str.MENU_DELETE_CHAR, tooltip = str.MENU_DELETE_CHAR_TOOLTIP, choices = charList, getFunc = function() return charToDelete end, setFunc = function(value) charToDelete = value end, }, [2] = { type = "button", name = str.MENU_DELETE_CHAR_BUTTON, tooltip = str.MENU_DELETE_CHAR_WARNING, func = function() DeleteCharacter(charToDelete) end, }, } }) LAM:RegisterOptionControls("SousChefSettings", optionsMenu) end -- SousChef_Loaded(eventCode, addOnName) runs when the EVENT_ADD_ON_LOADED event fires. local function SousChef_Loaded(eventCode, addOnName) if(addOnName ~= "SousChef") then return end -- default config settings local defaults = { --watching = true, checkKnown = "unknown", markAlt = false, colour = {1, 1, 1}, shoppingColour = {0,1,1}, Cookbook = {}, CookbookIndex = {}, Pantry = {}, ReverseCookbook = {}, showAltKnowledge = false, showAltIngredientKnowledge = false, boldIcon = false, typeIcon = true, processRecipes = true, showSpecialIngredients = false, ignoredRecipes = {}, showOnClick = false, showCounts = true, shoppingList = {}, onlyShowShopping = false, qualityChecked = false, sortKnownIngredients = false, mainChar = GetUnitName("player"), knownChars = { "(current)" }, autoJunk = false, showAltShopping = true, } local localized = SousChef.Strings[SousChef.lang] -- Fetch the saved variables SousChef.settings = ZO_SavedVars:NewAccountWide("SousChef_Settings", 11, SousChef.lang, defaults) -- if this character isn't in the list of known chars, add it local addMe = true local addCurrent = true for _, v in pairs(SousChef.settings.knownChars) do if GetUnitName("player") == v then addMe = false end if v == "(current)" then addCurrent = false end end if addMe then local myName = GetUnitName("player") table.insert(SousChef.settings.knownChars, GetUnitName("player")) end if addCurrent then table.insert(SousChef.settings.knownChars, "(current)") end -- define some slash commands SLASH_COMMANDS['/scstats'] = function() d(localized.SC_NUM_RECIPES_KNOWN.. NonContiguousCount(SousChef.settings.Cookbook)) d(localized.SC_NUM_INGREDIENTS_TRACKED..NonContiguousCount(SousChef.settings.Pantry)) end SLASH_COMMANDS['/sciadd'] = SousChef.AddRecipeToIgnoreList SLASH_COMMANDS['/sciremove'] = SousChef.RemoveRecipeFromIgnoreList SLASH_COMMANDS['/scilist'] = SousChef.ListIgnoredRecipes -- initialize the configuration menu SousChefCreateSettings() -- parse the recipes this character knows, in a second zo_callLater(SousChef.ParseRecipes, 500) SousChef:HookGetRecipeInfo() if SousChef.settings.sortKnownIngredients then SousChef.SetupSort() end ZO_CreateStringId("SI_BINDING_NAME_SC_MARK_RECIPE", localized.KEY_MARK) -- Now we register for some events, and hook into the function that sets the details on the inventory slot zo_callLater(SousChef.HookEvents, 2000) end -- HookEvents() registers the add-on for some events function SousChef.HookEvents() -- let us know if we're opening a trading house, so we can look at its contents EVENT_MANAGER:RegisterForEvent("SousChefTrading", EVENT_TRADING_HOUSE_RESPONSE_RECEIVED, SousChef.HookTrading) -- let us know if we've learned a new recipe, so we can integrate it into our cookbook EVENT_MANAGER:RegisterForEvent("SousChefLearnt", EVENT_RECIPE_LEARNED, SousChef.ParseRecipes) -- if the user has turned on auto-junking unmarked ingredients, set that up if SousChef.settings.autoJunk then EVENT_MANAGER:RegisterForEvent("SousChefLootJunker", EVENT_LOOT_CLOSED, function(...) zo_callLater(AutoJunker, 100) end) end -- let us know when we open a crafting station, so we can sort the recipe tree EVENT_MANAGER:RegisterForEvent("SousChefProvi", EVENT_CRAFTING_STATION_INTERACT, function(...) SousChef:HookRecipeTree(...) end) EVENT_MANAGER:RegisterForEvent("SousChefProviEnd", EVENT_END_CRAFTING_STATION_INTERACT, function() SousChef:UnhookRecipeTree() end) -- and finally, hook the opening of the inventory, bank, guild bank, and loot windows so we can add icons and hook the tooltips SousChef.HookInventory() end EVENT_MANAGER:RegisterForEvent("SousChefLoaded", EVENT_ADD_ON_LOADED, SousChef_Loaded)