--[[------------------------------------------------------------------ --SousChef.lua --Author: Wobin inspired by ingeniousclown's Research Assistant ------------------------------------------------------------------]]-- SousChef = {} local LAM = LibStub:GetLibrary("LibAddonMenu-1.0") local BACKPACK = ZO_PlayerInventoryBackpack local BANK = ZO_PlayerBankBackpack local GUILD_BANK = ZO_GuildBankBackpack local COOKING_RANK_1 = [[SousChef\media\One.dds]] local COOKING_RANK_2 = [[SousChef\media\Two.dds]] local COOKING_RANK_3 = [[SousChef\media\Three.dds]] local COOKING_RANK_4 = [[SousChef\media\Four.dds]] local COOKING_RANK_5 = [[SousChef\media\Five.dds]] local COOKING_RANK_6 = [[SousChef\media\Six.dds]] local COOKING_RANK_1B = [[SousChef\media\One_flat.dds]] local COOKING_RANK_2B = [[SousChef\media\Two_flat.dds]] local COOKING_RANK_3B = [[SousChef\media\Three_flat.dds]] local COOKING_RANK_4B = [[SousChef\media\Four_flat.dds]] local COOKING_RANK_5B = [[SousChef\media\Five_flat.dds]] local COOKING_RANK_6B = [[SousChef\media\Six_flat.dds]] local COOKING = { COOKING_RANK_1, COOKING_RANK_2, COOKING_RANK_3, COOKING_RANK_4, COOKING_RANK_5, COOKING_RANK_6 } local COOKINGB = { COOKING_RANK_1B, COOKING_RANK_2B, COOKING_RANK_3B, COOKING_RANK_4B, COOKING_RANK_5B, COOKING_RANK_6B } local CANLEARN = [[/esoui/art/loot/loot_finesseitem.dds]] local containerHooks = { INVENTORY_BACKPACK, INVENTORY_BANK, INVENTORY_GUILD_BANK } local itemQuality = { ITEM_QUALITY_MAGIC = { 0, 0, 1 }, ITEM_QUALITY_NORMAL = {1,1,1}, ITEM_QUALITY_ARCANE = {1, 0, 1}} SousChef.Pantry = {} SousChef.Cookbook = {} SousChef.ReverseCookbook = {} SousChef.settings = nil SousChef.slotLines = {} SousChef.hookedFunctions = {} SousChef.hookedDataFunction = nil local function GetItemID(link) return tonumber(string.match(string.match(link, "%d+:"), "%d+")) end local function EndsWith(String,End) return End=='' or string.sub(String,-string.len(End))==End end local function StartsWith(String,Start) return Start=='' or string.sub(String, 1, string.len(Start))==Start end local languageElements = {"de ", "à ", "la ", } local separators = {"%^(%a+)", ":(%a+)", "-", " " } local function StripLanguageIdentifiers(entry) for _,v in pairs(languageElements) do entry = entry:gsub(v, "") end return entry end local function Compress(entry) for _,v in pairs(separators) do entry = entry:gsub(v, "") end return entry end local function CleanString(entry) if SousChef.settings.experimentalMatch then entry = StripLanguageIdentifiers(entry) end return Compress(entry):lower() end local function MatchesRecipe(entry) return CleanString(entry):find(CleanString(GetString(SI_ITEMTYPE29))) end local function TableKeyConcat(t) local tt = {} for k in pairs(t) do tt[#tt+1]=k end return table.concat(tt, ", ") end local function MatchInCookbook(name) name = CleanString(name) for recipe,known in pairs(SousChef.Cookbook) do if StartsWith(name,recipe) or EndsWith(name, recipe) then local difference = (#recipe + #CleanString(GetString(SI_ITEMTYPE29)) - #name) if difference < 3 and difference >= 0 then return known end end end return nil end local function MatchInGlobalCookbook(name) name = CleanString(name) for recipe,known in pairs(SousChef.settings.Cookbook) do if StartsWith(name,recipe) or EndsWith(name, recipe) then local difference = (#recipe + #CleanString(GetString(SI_ITEMTYPE29)) - #name) if difference < 3 and difference >= 0 then return known end end end return nil end rowClicked = {} local function AddDetails(row) if not row.dataEntry or not row.dataEntry.data or rowClicked[row] then return false end local rowInfo = row.dataEntry.data local bagId = rowInfo.bagId local slotIndex = rowInfo.slotIndex if MatchesRecipe(rowInfo.name) then local gmatch = MatchInGlobalCookbook(rowInfo.name) if gmatch then ItemTooltip:AddLine("") ItemTooltip:AddLine("Known by ", "ZoFontWinH5", 1,1,1, BOTTOM, MODIFY_TEXT_TYPE_UPPERCASE) ItemTooltip:AddLine(TableKeyConcat(gmatch)) rowClicked[row] = true return end end if ((GetItemCraftingInfo(bagId, slotIndex)) ~= CRAFTING_TYPE_PROVISIONING) then return false end local usableIngredient = SousChef.ReverseCookbook[GetItemID(GetItemLink(bagId, slotIndex))] if SousChef.settings.showAltKnowledge then usableIngredient = SousChef.settings.ReverseCookbook[GetItemID(GetItemLink(bagId, slotIndex))] end if usableIngredient then ItemTooltip:AddLine("Used in:", "ZoFontWinH5", 1,1,1, BOTTOM, MODIFY_TEXT_TYPE_UPPERCASE) ItemTooltip:AddLine(table.concat(usableIngredient, ", ")) rowClicked[row] = true end return false end local function getIcon(row) local rankIcon = SousChef.slotLines[row:GetName()] if(not rankIcon) then rankIcon = WINDOW_MANAGER:CreateControl(row:GetName() .. "SousChef", row, CT_TEXTURE) SousChef.slotLines[row:GetName()] = rankIcon ZO_PreHookHandler(row, "OnMouseDown", AddDetails) ZO_PreHookHandler(row, "OnMouseExit", function(self) rowClicked[self] = nil return false end ) end return rankIcon end local rowHandler = {} local function AddRankToSlot(row) local slot = row.dataEntry.data local bagId = slot.bagId local slotIndex = slot.slotIndex local rankIcon = getIcon(row) -- Allow for ingeniousclown's Inventory Grid View if row:GetWidth() - row:GetHeight() < 5 then -- if we're mostly square rankIcon:SetDimensions(20,20) rankIcon:SetAnchor(TOPLEFT, row, TOPLEFT, 2) else rankIcon:SetDimensions(30, 30) rankIcon:SetAnchor(CENTER, row, CENTER, 200) end rankIcon:SetHidden(true) if ((GetItemCraftingInfo(bagId, slotIndex)) == CRAFTING_TYPE_PROVISIONING) then local texture = SousChef.Pantry[GetItemID(GetItemLink(bagId, slotIndex))] if SousChef.settings.showAltKnowledge then texture = SousChef.settings.Pantry[GetItemID(GetItemLink(bagId, slotIndex))] end if texture then rankIcon:SetColor(SousChef.settings.colour[1], SousChef.settings.colour[2], SousChef.settings.colour[3]) rankIcon:SetHidden(false) if SousChef.settings.boldIcon then rankIcon:SetTexture(COOKINGB[texture]) else rankIcon:SetTexture(COOKING[texture]) end end else if SousChef.settings.processRecipes then if GetItemType(bagId, slotIndex) == ITEMTYPE_RECIPE then local match = MatchInCookbook(slot.name) local gmatch = MatchInGlobalCookbook(slot.name) if (match and SousChef.settings.checkKnown == "known") or (not match and SousChef.settings.checkKnown == "unknown")then rankIcon:SetTexture(CANLEARN) rankIcon:SetHidden(false) if not match and gmatch and SousChef.settings.checkKnown == "unknown" and SousChef.settings.markAlt then rankIcon:SetColor(1,1,1,0.2) else rankIcon:SetColor(1,1,1,1) end end end end end end local function AddTradingSlot(row, result) local rankIcon = getIcon(row) rankIcon:SetHidden(true) if SousChef.settings.parseRecipes then if MatchesRecipe(result.name) then local match = MatchInCookbook(result.name) local gmatch = MatchInGlobalCookbook(result.name) if (match and SousChef.settings.checkKnown == "known") or (not match and SousChef.settings.checkKnown == "unknown")then rankIcon:SetDimensions(30, 30) rankIcon:SetAnchor(CENTER, row, CENTER, 230) rankIcon:SetTexture(CANLEARN) rankIcon:SetHidden(false) if not match and gmatch and SousChef.settings.checkKnown == "unknown" and SousChef.settings.markAlt then rankIcon:SetColor(1,1,1,0.2) else rankIcon:SetColor(1,1,1,1) end end end end end local function AddRecipe(Cookbook, link) for _,v in pairs(Cookbook) do if v == link then return end end table.insert(Cookbook, link) end local function 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 = CleanString((GetRecipeResultItemInfo(listIndex, recipeIndex))) SousChef.Cookbook[recipeName] = true if not SousChef.settings.Cookbook[recipeName] then SousChef.settings.Cookbook[recipeName] = {} end SousChef.settings.Cookbook[recipeName][GetUnitName("player")] = true local _, _, ingredientCount, level = GetRecipeInfo(listIndex, recipeIndex) for ingredientIndex = 1, ingredientCount do local link = GetItemID(GetRecipeIngredientItemLink(listIndex, recipeIndex, ingredientIndex, LINK_STYLE_NORMAL)) -- Store the fact that the ingredient is used SousChef.Pantry[link] = math.max(level, SousChef.Pantry[link] or 0) SousChef.settings.Pantry[link] = math.max(level, SousChef.Pantry[link] or 0) -- Store the recipe it's used in if not SousChef.ReverseCookbook[link] then SousChef.ReverseCookbook[link] = {} end AddRecipe(SousChef.ReverseCookbook[link], zo_strformat(SI_TOOLTIP_ITEM_NAME, GetRecipeResultItemLink(listIndex, recipeIndex, LINK_STYLE_BRACKETS))) -- Store the global reference if not SousChef.settings.ReverseCookbook[link] then SousChef.settings.ReverseCookbook[link] = {} end AddRecipe(SousChef.settings.ReverseCookbook[link], zo_strformat(SI_TOOLTIP_ITEM_NAME, GetRecipeResultItemLink(listIndex, recipeIndex, LINK_STYLE_BRACKETS))) end end end end end local function SousChefCreateSettings() d("Creating menu") local panel = LAM:CreateControlPanel("SousChefMenu", "Sous Chef") LAM:AddHeader(panel, "SousChef_General", "Settings") LAM:AddCheckbox(panel, "processRecipes", "Have Sous Chef check for recipes", "Non English clients may want to untick this if the experimental matching isn't sufficient", function() return SousChef.settings.processRecipes end, function(value) SousChef.settings.processRecipes = not SousChef.settings.processRecipes if SousChef.settings.processRecipes then ZO_Options_SetOptionInactive(markLearnt) ZO_Options_SetOptionInactive(markAltKnows) else ZO_Options_SetOptionActive(markLearnt) ZO_Options_SetOptionActive(markAltKnows) end end) LAM:AddDropdown(panel, "markLearnt", "Mark if recipes are ", "How do you want Sous Chef to indicate your knowledge of a recipe?", {"known", "unknown"}, function() return SousChef.settings.checkKnown end, function(valueString) SousChef.settings.checkKnown = valueString end) LAM:AddCheckbox(panel, "markAltKnows", "Alternate Character Check", "Indicate if an alt knows the recipe on unknown recipes. Will only work if the above setting is set to 'unknown'", function() return SousChef.settings.markAlt end, function(value) SousChef.settings.markAlt = not SousChef.settings.markAlt end) LAM:AddCheckbox(panel, "showAltKnows", "Alternate Character Recipe Knowledge", "Show rank indicators on alts for all recipe knowledge of all alternate characters", function() return SousChef.settings.showAltKnowledge end, function(value) SousChef.settings.showAltKnowledge = not SousChef.settings.showAltKnowledge end) LAM:AddCheckbox(panel, "useBold", "Use bolder icons", "Swap out rank icon to a more flat display", function() return SousChef.settings.boldIcon end, function(value) SousChef.settings.boldIcon = not SousChef.settings.boldIcon end) LAM:AddColorPicker(panel, "setColour", "Indicator colour", "Allows you to set the colour of the indicator", function() return SousChef.settings.colour[1], SousChef.settings.colour[2], SousChef.settings.colour[3] end, function(r,g,b) SousChef.settings.colour[1] = r; SousChef.settings.colour[2] = g; SousChef.settings.colour[3] = b end) LAM:AddCheckbox(panel, "experimental", "Use the experimental recipe matcher", [[Currently Sous Chef cannot match reliably in other languages. This will attempt to match as best it can by stripping out common prepositions so that the recipe and result match better. (NOTE: please file a bug report if you find recipes that don't match the results exactly, with the recipe/result names and/or screenshots so I can update this dictionary)]], function() return SousChef.settings.experimentalMatch end, function(value) SousChef.settings.experimentalMatch = not SousChef.settings.experimentalMatch end) end local function HookTrading(...) if SousChef.hookedDataFunction then return end SousChef.hookedDataFunction = ZO_TradingHouseItemPaneSearchResults.dataTypes[1].setupCallback ZO_TradingHouseItemPaneSearchResults.dataTypes[1].setupCallback = function(...) local row, data = ... SousChef.hookedDataFunction(...) AddTradingSlot(row, data) end end local function SousChef_Loaded(eventCode, addOnName) if(addOnName ~= "SousChef") then return end local defaults = { watching = true, checkKnown = "unknown", markAlt = false, colour = {1, 1, 1}, Cookbook = {}, Pantry = {}, ReverseCookbook = {}, showAltKnowledge = false, boldIcon = false, experimentalMatch = false, processRecipes = true } SousChef.settings = ZO_SavedVars:NewAccountWide("SousChef_Settings", 8, nil, defaults) local function tablelength(T) local count = 0 for _ in pairs(T) do count = count + 1 end return count end SLASH_COMMANDS['/scstats'] = function() d("Number of recipes known: ".. tablelength(SousChef.settings.Cookbook)) d("Number of ingredients tracked: "..tablelength(SousChef.settings.Pantry)) end SousChefCreateSettings() ParseRecipes() -- Now we want to hook into the function that sets the details on the inventory slot for _,v in pairs(PLAYER_INVENTORY.inventories) do local listView = v.listView if listView and listView.dataTypes and listView.dataTypes[1] then SousChef.hookedFunctions = listView.dataTypes[1].setupCallback listView.dataTypes[1].setupCallback = function(rowControl, slot) SousChef.hookedFunctions(rowControl, slot) AddRankToSlot(rowControl) end end end ZO_ScrollList_RefreshVisible(BACKPACK) ZO_ScrollList_RefreshVisible(BANK) ZO_ScrollList_RefreshVisible(GUILD_BANK) EVENT_MANAGER:RegisterForEvent("SousChefTRading", EVENT_TRADING_HOUSE_RESPONSE_RECEIVED, HookTrading) EVENT_MANAGER:RegisterForEvent("SousChefLearnt", EVENT_RECIPE_LEARNED, ParseRecipes) end local function SousChef_Initialized() EVENT_MANAGER:RegisterForEvent("SousChefLoaded", EVENT_ADD_ON_LOADED, SousChef_Loaded) end SousChef_Initialized()