Module:WorkshopsData

From Against the Storm Official Wiki
Revision as of 20:54, 12 November 2023 by Aeredor (talk | contribs) (Fixed a few typos)

Overview

Module for compiling workshops (or production buildings as they are called in-game) information from wiki data sources. Restructures the flat data tables that are produced from CsvUtils to make them more conducive to Lua methods that need to display the information.

Usage

The standard way of using this module is a call like the following:

iconFilename = WorkshopsData.getWorkshopIcon(workshopName)

This will return a string corresponding to the filename of the icon of the workshop, including the .png extension. It is preferable to call getter methods with the name of the workshop rather than retrieving the entire record for the workshop, so that your code stays protected from variations in this module. The getter methods are all called with the plain-language name of the workshop, as spelled in the game.

longDescription = WorkshopsData.getWorkshopDescription(workshopName)
constructionCategory = WorkshopsData.getWorkshopCategory(workshopName)
sizeX, sizeY = WorkshopsData.getWorkshopSize(workshopName)
requiredGoodID, stackSize = WorkshopsData.getWorkshopRequiredGood(workshopName, requirementIndex)
timeSeconds = WorkshopsData.getWorkshopConstructionTime(workshopName)
cityScore = WorkshopsData.getWorkshopCityScore(workshopName)
isMovable = WorkshopsData.isWorkshopMoveable(workshopName)
isInitiallyEssential = WorkshopsData.isWorkshopInitiallyEssential(workshopName)
storageCap = WorkshopsData.getWorkshopStorage(workshopName)
workplaces = WorkshopsData.getWorkshopNumberOfWorkplaces(workshopName)
recipesTable = WorkshopsData.getWorkshopRecipe(workshopName, recipeIndex)
iconFilename = WorkshopsData.getWorkshopIcon(workshopName)

And the following getter methods are all called with the workshop's ID. You will have an ID instead of a display name when dealing with other game data like, recipes, goods, etc.

name = WorkshopsData.getWorkshopNameByID(workshopID)
iconFilename = WorkshopsData.getWorkshopIconByID(workshopID)

The list of workshops that can produce a particular recipe ID can be retrieved with the following method

workshopNamesTable = WorkshopsData.getWorkshopNamesWithRecipeID(recipeID)

There are methods to retrieve the lists of required goods, workplaces, and recipes, but it is advised to instead use the getter methods getWorkshopRequiredGood, getWorkshopNumberOfWorkplaces, and getWorkshopRecipe with an index desired, which is good for loops. If you must get the tables, there are three "getAll" methods:

requiredGoodsTable = WorkshopsData.getAllWorkshopRequiredGoods(workshopName)
workplacesTable = WorkshopsData.getAllWorkshopWorkplaces(workshopName)
recipesTable = WorkshopsData.getAllWorkshopRecipes(workshopName)

As a last resort, or if you need to transform the data structure, you can call the method getAllDataForWorkshop(displayName) or getAllDataForWorkshopByID(workshopID). This returns a whole record from the data table corresponding to the requested display name.

The data table for workshops has the following structure:

workshopsTable = {
	["workshop1_ID"] = {
		["id"] = "workshop1_ID",
		["displayName"] = "Plain Language Name",
		["description"] = "A long string with some HTML entities too.",
		["category"] = "Construction toolbar category",
		["sizeX"] = 9, size in tiles
		["sizeY"] = 9, size in tiles
		["requiredGoods"] = {
			[1] = { ["stackSize"] = 99, ["goodID"] = "good1_ID" },
			[2] = { ["stackSize"] = 99, ["goodID"] = "good2_ID" },
			[3] = { ... } or missing if fewer
		},
		["constructionTime"] = 99, number of seconds
		["cityScore"] = 99, points?
		["movable"] = true or false, if it can be moved at any cost
		["initiallyEssential"] = true or false, if a new-game-starting blueprint
		["storage"] = 99, capacity
		["workplaces"] = {
			[1] = "Any",
			[2] = "Any",
			[3] = "Any",
			[4] = ... representing the number of workplaces, or missing if fewer
		},
		["recipes"] = {
			[1] = "recipe1_ID",
			[2] = "recipe2_ID",
			[3] = "recipe3_ID",
			[4] = ... or missing if fewer
		}
	},
	["workshop2_ID"] = {
		...
	},
	["workshop3_ID"] = {
		...
	},
	...
}

---
-- Module for compiling goods (or resources as some call them) information from 
-- wiki data sources.
--
-- @module WorkshopsData
local WorkshopsData = {}

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

---
-- Constants for this module
---
local WORKSHOPS_DATA_TEMPLATE_NAME = "Template:Workshops_csv"

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 HEADER_ROW = 1
local DATA_ROWS = 2

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


---
-- Private member variables
---

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

-- like this: table[workshopID] = table containing workshops data
local workshopsTable

-- Lookup map. Built once and reused on subsequent calls
-- 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 loadData()
	
	-- Use the CSV utility module to load the data templates we need for 
	-- resources.
	local originalWorkshopsTable, workshopsHeaderLookup = CsvUtils.luaTableFromCSV(CsvUtils.extractCSV(WORKSHOPS_DATA_TEMPLATE_NAME))
	
	-- Now restructure the table so subtables can be passed to member functions 
	-- for cleaner code.
	workshopsTable = restructureWorkshopTable(originalWorkshopsTable, workshopsHeaderLookup)
end



---
-- Retrieve all data for the specified workshop. Returned items look like this:
-- workshop {
-- 		id
--		display name
--		description
--		category
--		size x
--		size y
--		city score
--		movable
--		initially essential
--		storage
--		construction time
--		required goods (just their IDs) {
--			#1 { stack size, id }
--			#2 { stack size, id }
--			#3 { stack size, id }
--		}
--		workplaces {
--			workplace #1
--			workplace #2
--			workplace #3
--			workplace #4
--		}
--		recipes (just their IDs) {
--			recipe #1
--			recipe #2
--			recipe #3
--			recipe #4
--		}
-- }
-- 
-- @param workshopName plain language name of the workshop
-- @return a table containing the data for the specified workshop
function WorkshopsData.getAllDataForWorkshop(workshopName)
	
	if not workshopsTable then
		loadData()
	end
	
	local targetWorkshopID = findWorkshopIDByName(workshopName)
	if not targetWorkshopID then
		error("No building found. Please check spelling and any punctuation like an apostrophe: " .. workshopName)
	end
	
	return workshopsTable[targetGoodID]
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.
-- 
-- @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



---
-- 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 workshopsTable then
		loadData()
	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(workshopsTable) 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



---
-- Retrieves the name and icon filename for a workshop.
--
-- @param workshopID the ID of the workshop to look up
-- @return display name, icon filename
function WorkshopsData.getWorkshopNameAndIcon(workshopID)
	
	if not workshopsTable then
		loadData()
	end
	
	local workshop = workshopsTable[workshopID]
	
	if not workshop then
		error("ID for workshop not found to look up name and icon: " .. workshopID)
	end
	
	return workshop[INDEX_WORKSHOP_NAME], workshop[INDEX_WORKSHOP_NAME] .. "_icon"
end



return WorkshopsData