Module:RecipeData/Data

From Against the Storm Official Wiki

Documentation for this module may be created at Module:RecipeData/Data/doc

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


-- @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 {
--			#1 { stack size, id }
--			#2 { stack size, id }
--			#3 { stack size, 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 = {}
	for i = 1,REQ_GOOD_MAX do
		
		local oldIndex = workshopsHeaderLookup[LOOKUP_REQ_GOOD_BASE_STRING .. i]
		
		local newGroup = {}
		-- Split the digit part into the req'd good stack size and the rest 
		-- of the text into the req'd good's id.
		newGroup[1], newGroup[2] = oldWorkshop[oldIndex]:match(PATTERN_SPLIT_STACK_AND_ID)
		table.insert(requiredGoods, newGroup)
	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

-- 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)

-- Build the recipeIDToWorkshopID table
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

-- Build workshopNameToWorkshopID table
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

-- Build goodsNameToGoodID
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

return {recipeTable, recipeIDToWorkshopID, workshopTable, workshopNameToWorkshopID, goodsTable, goodsNameToGoodID}