Module:RecipeData

--- -- Module for compiling recipe information from wiki data sources -- -- @module RecipeData local RecipeData = {}

--- -- Dependencies --- local CsvUtils = require("Module:CsvUtils")

--- -- Constants for this module --- local RECIPE_DATA_TEMPLATE_NAME = "Template:Workshops_Recipes_csv" local WORKSHOPS_DATA_TEMPLATE_NAME = "Template:Workshops_csv" local GOODS_DATA_TEMPLATE_NAME = "Template:Goods_csv"

local HEADER_ROW = 1 local DATA_ROWS = 2

local PATTERN_SPLIT_STACK_AND_ID = "(%d+)%s([%[%]%s%a]+)"

local INDEX_RECIPE_ID = 1 local INDEX_RECIPE_GRADE = 2 local INDEX_RECIPE_PRODTIME = 3 local INDEX_RECIPE_PRODUCT_STACK_SIZE = 4 local INDEX_RECIPE_PRODUCT_ID = 5 local INDEX_RECIPE_INGREDIENTS = 6

local INDEX_RECIPE_INGREDIENT_OPTION_GOODID = 2

local INDEX_WORKSHOP_ID = 1 local INDEX_WORKSHOP_NAME = 2 local INDEX_WORKSHOP_DESCRIPTION = 3 local INDEX_WORKSHOP_CATEGORY = 4 local INDEX_WORKSHOP_SIZE_X = 5 local INDEX_WORKSHOP_SIZE_Y = 6 local INDEX_WORKSHOP_CITY_SCORE = 7 local INDEX_WORKSHOP_MOVABLE = 8 local INDEX_WORKSHOP_INITIALLY_ESSENTIAL = 9 local INDEX_WORKSHOP_STORAGE = 10 local INDEX_WORKSHOP_CONSTRUCTION_TIME = 11 local INDEX_WORKSHOP_REQUIRED_GOODS = 12 local INDEX_WORKSHOP_WORKPLACES = 13 local INDEX_WORKSHOP_RECIPES = 14

local INDEX_GOOD_ID = 1 local INDEX_GOOD_NAME = 2 local INDEX_GOOD_DESCRIPTION = 3 local INDEX_GOOD_CATEGORY = 4 local INDEX_GOOD_EATABLE = 5 local INDEX_GOOD_CAN_BE_BURNED = 6 local INDEX_GOOD_BURNING_TIME = 7 local INDEX_GOOD_TRADING_SELL_VALUE = 8 local INDEX_GOOD_TRADING_BUY_VALUE = 9 local INDEX_GOOD_ICON_FILENAME = 10

--- -- Private member variables ---

-- Main data tables. Populated from CSV data and organized better for Lua.

-- like this: table[recipeID] = table containing recipe data local recipeTable

-- like this: table[workshopID] = table containing workshop data local workshopTable

-- like this: table[goodID] = table containing good data local goodsTable

-- Lookup maps. Built once and reused on subsequent calls

-- like this: table[recipeID] = { workshopID, workshopID, workshopID } local recipeIDToWorkshopID

-- like this: table[display name] = goodID local goodsNameToGoodID

-- like this: table[display name] = workshopID local workshopNameToWorkshopID

--- -- Data loader function. Calls the data templates, restructures the data to be -- useful for getter methods, and makes a merge table. -- -- This method is called the first time any of the actual template methods are -- invoked and they see that the data tables are nil. This method populates -- them and reorganizes them for easier use (and easier code). function loadRecipes -- Use the CSV utility module to load the data templates we need for -- recipes, which are the recipes themselves and the workshops (production) -- workshops. local originalRecipeTable, recipeHeaderLookup = CsvUtils.luaTableFromCSV(CsvUtils.extractCSV(RECIPE_DATA_TEMPLATE_NAME)) local originalWorkshopTable, workshopsHeaderLookup = CsvUtils.luaTableFromCSV(CsvUtils.extractCSV(WORKSHOPS_DATA_TEMPLATE_NAME)) local originalGoodsTable, goodsHeaderLookup = CsvUtils.luaTableFromCSV(CsvUtils.extractCSV(GOODS_DATA_TEMPLATE_NAME)) -- Now restructure the tables so subtables can be passed to member functions -- for cleaner code. recipeTable = restructureRecipeTable(originalRecipeTable, recipeHeaderLookup) workshopTable = restructureWorkshopTable(originalWorkshopTable, workshopsHeaderLookup) goodsTable = restructureGoodsTable(originalGoodsTable, goodsHeaderLookup) end

--- -- Retrieve all recipes that result in the specified product, and the buildings -- that produce them. With the following structure: -- -- table of matches { -- 		match[recipeID] between recipe and workshops = { --			recipe data { ... } --			workshops that make it { --				workshop data { ... } --				workshop data { ... } --				workshop data { ... } --			} --		} --		match[recipeID] between recipe and workshops = { --			... -- 		} -- } -- -- @param productName plain language name of the product (good) -- @return all recipes that result in the product, and all workshops that -- produce those recipes, in a nested table function RecipeData.getAllRecipesForProduct(productName) if not recipeTable or not workshopTable or not goodsTable then loadRecipes end local targetProductID = findGoodIDByName(productName) if not targetProductID then error("No product found. Please check spelling and any punctuation like an apostrophe: " .. productName) end -- First find all the relevant recipes. local foundRecipeIDs = {} for recipeID, recipe in pairs(recipeTable) do		if targetProductID == recipe[INDEX_RECIPE_PRODUCT_ID] then table.insert(foundRecipeIDs, recipeID) end end -- Now run the found recipes to get the workshops. local foundWorkshopIDs = findWorkshopsWithAnyOfTheseRecipes(foundRecipeIDs) -- Build the nested return table foundRecipes = {} for recipeID, listOfWorkshopIDs in pairs(foundWorkshopIDs) do		-- Make a new table to store the match between the recipe and the -- buildings that make it		local match = {} table.insert(match, recipeTable[recipeID]) local workshops = {} for i, workshopID in pairs(listOfWorkshopIDs) do			table.insert(workshops, workshopTable[workshopID]) end table.insert(match, workshops) foundRecipes[recipeID] = match end return foundRecipes end

--- -- Retrieve one specified recipe at the specified workshop. Returns an error if -- no such combination exists. Two return values: -- -- table of matches { -- 		match[recipeID] between recipe and workshops = { --			recipe data { ... } --			workshops that make it { --				workshop data { ... } --			} -- 		} -- } -- -- @param productName plain language name of the product (good) -- @param workshopName plain language name of the workshop (building) -- @return the requested recipe and workshop tables, if the combination exist function RecipeData.getOneRecipeAtBuilding(productName, workshopName) if not recipeTable or not workshopTable or not goodsTable then loadRecipes end local targetProductID = findGoodIDByName(productName) if not targetProductID then error("No product found. Please check spelling and any punctuation like an apostrophe: " .. productName) end local targetWorkshopID = findWorkshopIDByName(workshopName) if not targetWorkshopID then error("No building found. Please check spelling and any punctuation like an apostrophe: " .. workshopName) end local targetWorkshop = workshopTable[targetWorkshopID] local targetRecipeID = nil for _, recipeID in ipairs(targetWorkshop[INDEX_WORKSHOP_RECIPES]) do		if recipeID ~= "" and targetProductID == recipeTable[recipeID][INDEX_RECIPE_PRODUCT_ID] then targetRecipeID = recipeID break end end if not targetRecipeID then error("No combination exists. Please check spelling and punctuation (like an apostrophe) for this recipe and building: " .. productName .. " and " .. workshopName) end local tableToReturn = { [targetRecipeID] = { recipeTable[targetRecipeID], {				workshopTable[targetWorkshopID] }		}	}	return tableToReturn end

--- -- Retrieve all the recipes that the specified workshop can produce, with the -- following structure: -- -- workshop, -- table of matches { -- 		match[recipeID] between recipe and workshops = { --			recipe data { ... }, --			workshops that make it { --				workshop data { ... } --			} --		}, --		match[recipeID] between recipe and workshops = { --			recipe data { ... }, --			workshops that make it { --				workshop data { ... } --			} -- 		} -- } -- -- @param workshopName the plain language display name of the workshop -- @return list of recipes at that building function RecipeData.getAllRecipesAtBuilding(workshopName) if not recipeTable or not workshopTable or not goodsTable then loadRecipes end local targetWorkshopID = findWorkshopIDByName(workshopName) if not targetWorkshopID then error("No building found. Please check spelling and any punctuation like an apostrophe: " .. workshopName) end local targetWorkshop = workshopTable[targetWorkshopID] -- Reuse this subtable for each recipe. local nestedWorkshopList = {} table.insert(nestedWorkshopList, targetWorkshop) local matches = {} for _, recipeID in ipairs(targetWorkshop[INDEX_WORKSHOP_RECIPES]) do		if recipeID ~= "" then local newMatch = {} table.insert(newMatch,recipeTable[recipeID]) table.insert(newMatch, nestedWorkshopList) matches[recipeID] = newMatch end end return targetWorkshop, matches end

--- -- Retrieves all recipes for which the specified good is an ingredient, with the -- following structure: -- -- ingredient good data { ... }, -- table of matches { -- 		match[recipeID] between recipe and workshops = { --			recipe data { ... } --			workshops that make it { --				workshop data { ... } --				workshop data { ... } --				workshop data { ... } --			} --		} --		match[recipeID] between recipe and workshops = { --			... -- 		} -- } -- -- @param ingredientName the plain language name of the good -- @return a table of matches function RecipeData.getAllRecipesWithIngredient(ingredientName) if not recipeTable or not workshopTable or not goodsTable then loadRecipes end local targetIngredientID = findGoodIDByName(ingredientName) if not targetIngredientID then error("No ingredient found. Please check spelling and any punctuation like an apostrophe: " .. ingredientName) end local foundRecipeIDs = {} for recipeID, recipe in pairs(recipeTable) do		local match for _, optionGroup in ipairs(recipe[INDEX_RECIPE_INGREDIENTS]) do			for _, option in ipairs (optionGroup) do				-- Compare the good id, no need to look at stack size if option[INDEX_RECIPE_INGREDIENT_OPTION_GOODID] == targetIngredientID then table.insert(foundRecipeIDs, recipeID) end end end end -- Expand the table to populate workshop IDs under each recipe ID	local matches = findWorkshopsWithAnyOfTheseRecipes(foundRecipeIDs) -- Now build the nested table to return with the data local ingredMatches = {} for recipeID, match in pairs(matches) do		newMatch = {} table.insert(newMatch, recipeTable[recipeID]) matchWorkshops = {} for _, workshopID in ipairs(match) do			table.insert(matchWorkshops, workshopTable[workshopID]) end table.insert(newMatch, matchWorkshops) ingredMatches[recipeID] = newMatch end return goodsTable[targetIngredientID], ingredMatches end

--- -- Transforms the oldRecipeTable returned from CSV processing to be more -- conducive to member functions looking up data. Essentially, we convert the -- text strings into tables with the same base name and an index. -- -- Each item in newRecipeTable[i] looks like this: -- recipe { -- 		id --		efficiency grade --		production time --		product stack size --		product good's id --		ingredients { --			options for ingredient #1 { --				{ option #1 good's stack size, option #1 good's id } --				{ option #2 good's stack size, option #2 good's id } --				{ option #3 good's stack size, option #3 good's id } --				etc. --			} --			options for ingredient #2 { --				same as above --			} --			options for ingredient #3 { --				same as above --			} --		} -- } -- -- @param oldRecipeTable the CSV-based table, with a header row then data rows -- @param recipeHeaderLookup the lookup table built from the CSV data -- @return a new table for use in looking up recipe data function restructureRecipeTable(oldRecipeTable, recipeHeaderLookup) -- New table structure is only data rows, no header row needed. Therefore, -- the new table is one degree flatter, like this: -- oldRecipeTable[2][1] contains the same data as newRecipeTable[1] -- (but with subtables) local newRecipeTable = {} oldRecipeTable = oldRecipeTable[DATA_ROWS] -- A few constants we need only in this function local INDEX_OLD_RECIPE_ID = 1 local INDEX_OLD_RECIPE_GRADE = 2 local INDEX_OLD_RECIPE_PRODTIME = 3 local INDEX_OLD_RECIPE_PRODUCT = 4 for i, oldRecipe in ipairs(oldRecipeTable) do		local newRecipe = {} -- Copy over the flat information from the old recipe. local newRecipeID = oldRecipe[INDEX_OLD_RECIPE_ID] newRecipe[INDEX_RECIPE_ID] = newRecipeID newRecipe[INDEX_RECIPE_GRADE] = oldRecipe[INDEX_OLD_RECIPE_GRADE] newRecipe[INDEX_RECIPE_PRODTIME] = oldRecipe[INDEX_OLD_RECIPE_PRODTIME] -- Split the digit part into the product stack size and the rest of the -- text into the product good's id. newRecipe[INDEX_RECIPE_PRODUCT_STACK_SIZE], newRecipe[INDEX_RECIPE_PRODUCT_ID] = oldRecipe[INDEX_OLD_RECIPE_PRODUCT]:match(PATTERN_SPLIT_STACK_AND_ID) newRecipe[INDEX_RECIPE_INGREDIENTS] = makeIngredientsSubtable(oldRecipe,recipeHeaderLookup) newRecipeTable[newRecipeID] = newRecipe end return newRecipeTable end

--- -- Loops through the old recipe, extracting ingredient options and putting them -- into new subtables. Uses the lookup table from the CSV extraction to -- keep the data in the same order. -- -- @param oldRecipe the recipe from which to extract ingredient information -- @param recipeHeaderLookup the lookup table built from the CSV data -- @return a subtable with the ingredients information from oldRecipe function makeIngredientsSubtable(oldRecipe, recipeHeaderLookup) -- A few constants we'll need only within this function. local RECIPE_INGRED_MAX = 3 local RECIPE_INGRED_OPTION_MAX = 6 local LOOKUP_INGREDIENT_BASE_STRING = "ingredient" local LOOKUP_OPTION_BASE_STRING = "Good" -- A subtable for the lists of ingredients and their options. local ingredients = {} for i = 1,RECIPE_INGRED_MAX do -- A subtable for the options for this ingredient. local options = {} for j = 1,RECIPE_INGRED_OPTION_MAX do local oldIndex = recipeHeaderLookup[LOOKUP_INGREDIENT_BASE_STRING .. i .. LOOKUP_OPTION_BASE_STRING .. j]			-- Once it gets to a blank, can advance to the next group local oldOption = oldRecipe[oldIndex] if oldOption == "" then break end -- Split the digit part into the ingredient stack size and the rest -- of the text into the ingredient good's id. local stackSize, goodID = oldOption:match(PATTERN_SPLIT_STACK_AND_ID) table.insert(options, {stackSize, goodID}) end -- Add finished options list to represent the ith ingredient ingredients[i] = options end return ingredients end

--- -- Transforms the oldWorkshopsTable returned from CSV processing to be more -- conducive to member functions looking up data. Essentially, we convert the -- text strings into tables with the same base name and an index. -- -- Each item newWorkshopTable[i] looks like this: -- workshop { -- 		id --		display name --		description --		category --		size x --		size y --		city score --		movable --		initially essential --		storage --		construction time --		required goods { --			required good #1 stack size --			required good #1 id --			required good #2 stack size --			required good #2 id --			required good #3 stack size --			required good #3 id --		} --		workplaces { --			workplace #1 --			workplace #2 --			workplace #3 --			workplace #4 --		} --		recipes { --			recipe #1 --			recipe #2 --			recipe #3 --			recipe #4 --		} -- } -- -- @param oldRecipeTable the CSV-based table, with a header row then data rows -- @param recipeHeaderLookup the lookup table built from the CSV data -- @return a new table for use in looking up recipe data function restructureWorkshopTable(oldWorkshopTable, workshopsHeaderLookup) -- New table structure is only data rows, no header row needed. Therefore, -- the new table is one degree flatter, like this: -- oldWorkshopTable[2][1] contains the same data as newWorkshopTable[1] -- (but organized by workshopID instead of by index and with subtables) local newWorkshopTable = {} oldWorkshopTable = oldWorkshopTable[DATA_ROWS] -- A few constants we need only in this function. local INDEX_OLD_WORKSHOP_ID = 1 local INDEX_OLD_WORKSHOP_NAME = 2 local INDEX_OLD_WORKSHOP_DESCRIPTION = 3 local INDEX_OLD_WORKSHOP_CATEGORY = 4 local INDEX_OLD_WORKSHOP_SIZE_X = 5 local INDEX_OLD_WORKSHOP_SIZE_Y = 6 local INDEX_OLD_WORKSHOP_CONSTRUCTION_TIME = 10 local INDEX_OLD_WORKSHOP_CITY_SCORE = 11 local INDEX_OLD_WORKSHOP_MOVABLE = 12 local INDEX_OLD_WORKSHOP_INITIALLY_ESSENTIAL = 13 local INDEX_OLD_WORKSHOP_STORAGE = 14 for i, oldWorkshop in ipairs(oldWorkshopTable) do		local newWorkshop = {} -- Copy over the flat information from the old recipe. local newWorkshopID = oldWorkshop[INDEX_OLD_WORKSHOP_ID] newWorkshop[INDEX_WORKSHOP_ID] = newWorkshopID newWorkshop[INDEX_WORKSHOP_NAME] = oldWorkshop[INDEX_OLD_WORKSHOP_NAME] newWorkshop[INDEX_WORKSHOP_DESCRIPTION] = oldWorkshop[INDEX_OLD_WORKSHOP_DESCRIPTION] newWorkshop[INDEX_WORKSHOP_CATEGORY] = oldWorkshop[INDEX_OLD_WORKSHOP_CATEGORY] newWorkshop[INDEX_WORKSHOP_SIZE_X] = oldWorkshop[INDEX_OLD_WORKSHOP_SIZE_X] newWorkshop[INDEX_WORKSHOP_SIZE_Y] = oldWorkshop[INDEX_OLD_WORKSHOP_SIZE_Y] newWorkshop[INDEX_WORKSHOP_CITY_SCORE] = oldWorkshop[INDEX_OLD_WORKSHOP_CITY_SCORE] newWorkshop[INDEX_WORKSHOP_MOVABLE] = oldWorkshop[INDEX_OLD_WORKSHOP_MOVABLE] newWorkshop[INDEX_WORKSHOP_INITIALLY_ESSENTIAL] = oldWorkshop[INDEX_OLD_WORKSHOP_INITIALLY_ESSENTIAL] newWorkshop[INDEX_WORKSHOP_STORAGE] = oldWorkshop[INDEX_OLD_WORKSHOP_STORAGE] newWorkshop[INDEX_WORKSHOP_CONSTRUCTION_TIME] = oldWorkshop[INDEX_OLD_WORKSHOP_CONSTRUCTION_TIME] newWorkshop[INDEX_WORKSHOP_REQUIRED_GOODS] = makeRequiredGoodsSubtable(oldWorkshop, workshopsHeaderLookup) newWorkshop[INDEX_WORKSHOP_WORKPLACES] = makeWorkplacesSubtable(oldWorkshop, workshopsHeaderLookup) newWorkshop[INDEX_WORKSHOP_RECIPES] = makeRecipesSubtable(oldWorkshop, workshopsHeaderLookup) newWorkshopTable[newWorkshopID] = newWorkshop end return newWorkshopTable end

--- -- Loops through the old workshop, extracting req'd goods and putting them -- into a new subtable. Uses the lookup table from the CSV extraction to -- keep the data in the same order. -- -- @param oldWorkshop the workshop from which to extract req'd goods information -- @param workshopsHeaderLookup the lookup table built from the CSV data -- @return a subtable with the req'd goods information from oldWorkshop function makeRequiredGoodsSubtable(oldWorkshop, workshopsHeaderLookup) -- A few constants we'll need only within this function. local REQ_GOOD_MAX = 3 local LOOKUP_REQ_GOOD_BASE_STRING = "requiredGood" -- A subtable to return local requiredGoods = {} -- We'll have to add twice as many new fields, so we need a separate -- counter. local k = 1 for i = 1,REQ_GOOD_MAX do local oldIndex = workshopsHeaderLookup[LOOKUP_REQ_GOOD_BASE_STRING .. i]		-- Split the digit part into the req'd good stack size and the rest -- of the text into the req'd good's id. requiredGoods[k], requiredGoods[k+1] = oldWorkshop[oldIndex]:match(PATTERN_SPLIT_STACK_AND_ID) k = k + 2 -- move +2 for each original +1 end return requiredGoods end

--- -- Loops through the old workshop, extracting workplaces and putting them -- into a new subtable. Uses the lookup table from the CSV extraction to -- keep the data in the same order. -- -- @param oldWorkshop the workshop from which to extract workplaces information -- @param workshopsHeaderLookup the lookup table built from the CSV data -- @return a subtable with the workplaces information from oldWorkshop function makeWorkplacesSubtable(oldWorkshop, workshopsHeaderLookup) -- A few constants we'll need only within this function. local WORKPLACE_MAX = 4 local LOOKUP_WORKPLACE_BASE_STRING = "workplace" -- A subtable to return local workplaces = {} for i = 1,WORKPLACE_MAX do local oldIndex = workshopsHeaderLookup[LOOKUP_WORKPLACE_BASE_STRING .. i]		workplaces[i] = oldWorkshop[oldIndex] end return workplaces end

--- -- Loops through the old workshop, extracting recipes and putting them -- into a new subtable. Uses the lookup table from the CSV extraction to -- keep the data in the same order. -- -- @param oldWorkshop the workshop from which to extract recipes -- @param workshopsHeaderLookup the lookup table built from the CSV data -- @return a subtable with the recipes from oldWorkshop function makeRecipesSubtable(oldWorkshop, workshopsHeaderLookup) -- A few constants we'll need only within this function. local RECIPE_MAX = 4 local LOOKUP_RECIPE_BASE_STRING = "recipe" -- A subtable to return local recipes = {} for i=1,RECIPE_MAX do local oldIndex = workshopsHeaderLookup[LOOKUP_RECIPE_BASE_STRING .. i]		recipes[i] = oldWorkshop[oldIndex] end return recipes end

--- -- Since the goods information is already flat and well structured, all this has -- to do is swap out the integer keys for the good IDs and only return the data -- rows, without the header row. -- -- @param oldGoodsTable the CSV-based table, with a header row then data rows -- @param goodsHeaderLookup the lookup table built from the CSV data -- @return a new table for use in looking up goods data function restructureGoodsTable(oldGoodsTable, goodsHeaderLookup) local newGoodsTable = {} for _, good in ipairs(oldGoodsTable[DATA_ROWS]) do		newGoodsTable[good[INDEX_GOOD_ID]] = good end return newGoodsTable end

--- -- Look up so products and ingredients can be found by their common id, rather -- than their display name, which people are more familiar with. -- -- Uses the display name provided to look up the associated ID for the good. -- Builds a lookup table the first time it's called so it only has to happen -- once. -- -- @param displayName the plain-language name of the good to find -- @return the ID of the good found, or nil if not found function findGoodIDByName(displayName) if not recipeTable or not workshopTable or not goodsTable then loadRecipes end local foundGoodID = nil -- Decide whether we need to traverse the big table. If this isn't the first -- time this method is called, we won't have to build the table again, we -- can just look it up. if not goodsNameToGoodID then goodsNameToGoodID = {} for goodID, good in pairs(goodsTable) do			if not foundGoodID and good[INDEX_GOOD_NAME] == displayName then -- Found it, keep traversing to build the rest of the map. foundGoodID = goodID end goodsNameToGoodID[good[INDEX_GOOD_NAME]] = goodID end else -- From the lookup table. foundGoodID = goodsNameToGoodID[displayName] end return foundGoodID end

--- -- Look up so workshops can be found by their common ID, rather than their -- display name, which people are more familiar with. -- -- Uses the display name provided to look up the associated ID for the workshop. -- Builds a lookup table the first time it's called so it only has to happen -- once. -- -- @param displayName the plain-language name of the workshop to find -- @return the ID of the workshop found, or nil if not found function findWorkshopIDByName(displayName) if not recipeTable or not workshopTable or not goodsTable then loadRecipes end local foundWorkshopID = nil -- Decide whether we need to traverse the big table. If this isn't the first -- time this method is called, we won't have to build the table again, we -- can just look it up. if not workshopNameToWorkshopID then workshopNameToWorkshopID = {} for workshopID, workshop in pairs(workshopTable) do			if not foundWorkshopID and workshop[INDEX_WORKSHOP_NAME] == displayName then -- Found it, but keep traversing to build the rest of the map. foundWorkshopID = workshopID end workshopNameToWorkshopID[workshop[INDEX_WORKSHOP_NAME]] = workshopID end else -- From the lookup table. foundWorkshopID = workshopNameToWorkshopID[displayName] end return foundWorkshopID end

--- -- Expand the table provided by running each recipe through the other method -- that returns all workshops for one recipe. -- -- @param targetRecipes a table of recipe IDs to find separately -- @return a table of workshops function findWorkshopsWithAnyOfTheseRecipes(targetRecipeIDs) if not recipeTable or not workshopTable or not goodsTable then loadRecipes end local workshopIDsFound = {} for _, targetRecID in ipairs(targetRecipeIDs) do		workshopIDsFound[targetRecID] = findWorkshopsWithRecipe(targetRecID) end return workshopIDsFound end

--- -- Compiles the records for the workshops that can produce the specified -- recipe. Returns the whole workshop record, so it can be filtered out by -- the calling method if needed, or not. -- -- @param targetRecipeID the id of the recipe to find -- @return a table of workshop records function findWorkshopsWithRecipe(targetRecipeID) if not recipeTable or not workshopTable or not goodsTable then loadRecipes end local workshopIDsFound = {} -- Decide whether we need to traverse the big table. If this isn't the first -- time this method is called, we won't have to build the table again, we -- can just look it up. if not recipeIDToWorkshopID then recipeIDToWorkshopID = {} for workshopID, workshop in pairs(workshopTable) do			for j, thisRecipeID in ipairs(workshop[INDEX_WORKSHOP_RECIPES]) do				-- Once found, add it to the list above but keep traversing -- entirely to fill in the lookup table. if targetRecipeID == thisRecipeID then table.insert(workshopIDsFound, workshopID) end -- Add every workshop's index to the appropriate recipe's ID. -- NOT the target but this one iterated over right now. if not recipeIDToWorkshopID[thisRecipeID] then recipeIDToWorkshopID[thisRecipeID] = {} end table.insert(recipeIDToWorkshopID[thisRecipeID], workshopID) end end else -- Load all the workshops in the list corresponding to the provided -- recipe ID. for _, workshopID in ipairs(recipeIDToWorkshopID[targetRecipeID]) do			table.insert(workshopIDsFound, workshopID) end end return workshopIDsFound end

--- -- Retrieves the name and icon filename for a good. -- -- @param goodID the ID of the good to look up -- @return display name, icon filename function getGoodNameAndIcon(goodID) if not recipeTable or not workshopTable or not goodsTable then loadRecipes end local good = goodsTable[goodID] if not good then error("ID for good not found to look up name and icon: " .. goodID) end return good[INDEX_GOOD_NAME], good[INDEX_GOOD_ICON_FILENAME] end

return RecipeData