Module:WorkshopsData: Difference between revisions

From Against the Storm Official Wiki
(More typos sadge)
(Removed module from private methods; finished top luadoc)
Line 1: Line 1:
---
---
-- Module for compiling goods (or resources as some call them) information from  
--- Module for compiling workshops (or production buildings as they are called
-- wiki data sources.
--- in-game) information from wiki data sources. Restructures the flat data
--
--- tables that are produced from CsvUtils to make them more conducive to Lua
-- @module WorkshopsData
--- methods that need to display the information.
----
--- 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. It is preferable to call getter methods with the name of the good
--- rather than retrieving the entire record for the good, so that your code
--- stays protected from variations in this module. The getter methods are all
--- called with the plain-language name of the good, as spelled in the game.
---
--- * longDescription = WorkshopsData.getWorkshopDescription(workshopName)
--- * constructionCategory = WorkshopsData.getWorkshopCategory(workshopName)
--- * sizeX, sizeY = WorkshopsData.getWorkshopSize(workshopName)
--- * requiredGoodsTable = WorkshopsData.getWorkshopRequiredGoods(workshopName)
--- * timeSeconds = WorkshopsData.getWorkshopConstructionTime(workshopName)
--- * cityScore = WorkshopsData.getWorkshopCityScore(workshopName)
--- * isMovable = WorkshopsData.isWorkshopMoveable(workshopName)
--- * isInitiallyEssential = WorkshopsData.isWorkshopInitiallyEssential(workshopName)
--- * storageCap = WorkshopsData.isWorkshopInitiallyEssential(workshopName)
--- * workplacesTable = WorkshopsData.getWorkshopWorkplaces(workshopName)
--- * recipesTable = WorkshopsData.getWorkshopRecipes(workshopName)
--- * iconFilename = WorkshopsData.getWorkshopIcon(workshopName)
---
--- As a last resort, or if you need to transform the data structure, you can
--- call the method getAllDataForWorkshop(displayName). 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 WorkshopsData
local WorkshopsData = {}
local WorkshopsData = {}
local CsvUtils = require("Module:CsvUtils")
--region Private member variables
--- Main data table, like this: table[ID] = table containing data for that ID
local workshopsTable
--- Lookup map. Built once and reused on all subsequent calls within this
--- session, like this: table[displayName] = workshopID
local mapNamesToIDs
--endregion
--region Private constants
local DATA_TEMPLATE_NAME = "Template:Workshops_csv"
local HEADER_ROW = 1
local DATA_ROWS = 2
local INDEX_ID = "id"
local INDEX_NAME = "displayName"
local INDEX_DESCRIPTION = "description"
local INDEX_CATEGORY = "category"
local INDEX_SIZE_X = "sizeX"
local INDEX_SIZE_Y = "sizeY"
local INDEX_CITY_SCORE = "cityScore"
local INDEX_MOVABLE = "movable"
local INDEX_ESSENTIAL = "initiallyEssential"
local INDEX_STORAGE_CAP = "storage"
local INDEX_CONSTRUCTION_TIME = "constructionTime"
local INDEX_REQUIRED_GOODS = "requiredGoods"
local INDEX_WORKPLACES = "workplaces"
local INDEX_RECIPES = "recipes"
local INDEX_CONSTRUCTION_GOODS_STACK_SIZE = "stackSize"
local INDEX_CONSTRUCTION_GOODS_GOOD_ID = "goodID"
local PATTERN_SPLIT_STACK_AND_ID = "(%d+)%s([%[%]%s%a]+)"
--endregion
--region Private methods


---
---
-- Dependencies
--- Creates a new subtable containing the construction goods required for the
--- specified workshop.
---
---
local CsvUtils = require("Module:CsvUtils")
--- @param originalWorkshop table workshop data record from which to make subtable
--- @param workshopsHeaderLookup table header lookup built from the CSV data
--- @return table subtable with the required construction goods
function makeRequiredGoodsSubtable(originalWorkshop, workshopsHeaderLookup)
 
-- A constant we'll need only within this function.
local REQ_GOOD_HEADER_BASE_STRING = "requiredGood"
 
-- Copy the originals directly into a subtable.
local requiredIndex1 = workshopsHeaderLookup[REQ_GOOD_HEADER_BASE_STRING .. "1"]
local requiredIndex2 = workshopsHeaderLookup[REQ_GOOD_HEADER_BASE_STRING .. "2"]
local requiredIndex3 = workshopsHeaderLookup[REQ_GOOD_HEADER_BASE_STRING .. "3"]
 
local number1, id1 = originalWorkshop[requiredIndex1]:match(PATTERN_SPLIT_STACK_AND_ID)
local number2, id2 = originalWorkshop[requiredIndex2]:match(PATTERN_SPLIT_STACK_AND_ID)
local number3, id3 = originalWorkshop[requiredIndex3]:match(PATTERN_SPLIT_STACK_AND_ID)
 
-- don't add subtables that would just contain nils
local requiredGoods = {
number1 and id1 and { [INDEX_CONSTRUCTION_GOODS_STACK_SIZE] = number1, [INDEX_CONSTRUCTION_GOODS_GOOD_ID] = id1 } or nil,
number2 and id2 and { [INDEX_CONSTRUCTION_GOODS_STACK_SIZE] = number2, [INDEX_CONSTRUCTION_GOODS_GOOD_ID] = id2 } or nil,
number3 and id3 and { [INDEX_CONSTRUCTION_GOODS_STACK_SIZE] = number3, [INDEX_CONSTRUCTION_GOODS_GOOD_ID] = id3 } or nil
}
 
return requiredGoods
end
 
 
 
---
--- Creates a new subtable containing the workplaces available in the specified
--- workshop.
---
--- @param originalWorkshop table workshop data record from which to make subtable
--- @param workshopsHeaderLookup table header lookup built from the CSV data
--- @return table subtable with the workplaces
function makeWorkplacesSubtable(originalWorkshop, workshopsHeaderLookup)
 
-- A constant we'll need only within this function.
local WORKPLACE_HEADER_BASE_STRING = "workplace"
 
-- Copy the originals directly into a subtable.
local workplaceIndex1 = workshopsHeaderLookup[WORKPLACE_HEADER_BASE_STRING .. "1"]
local workplaceIndex2 = workshopsHeaderLookup[WORKPLACE_HEADER_BASE_STRING .. "2"]
local workplaceIndex3 = workshopsHeaderLookup[WORKPLACE_HEADER_BASE_STRING .. "3"]
local workplaceIndex4 = workshopsHeaderLookup[WORKPLACE_HEADER_BASE_STRING .. "4"]
 
local workplace1 = originalWorkshop[workplaceIndex1]
local workplace2 = originalWorkshop[workplaceIndex2]
local workplace3 = originalWorkshop[workplaceIndex3]
local workplace4 = originalWorkshop[workplaceIndex4]
 
-- if it's not an empty string, then save that to the table, otherwise nil
local workplaces = {
(workplace1 ~= "" and workplace1) or nil,
(workplace2 ~= "" and workplace2) or nil,
(workplace3 ~= "" and workplace3) or nil,
(workplace4 ~= "" and workplace4) or nil
}
 
return workplaces
end
 
 


---
---
-- Constants for this module
--- Creates a new subtable containing the recipes available in the specified
--- workshop.
---
---
local WORKSHOPS_DATA_TEMPLATE_NAME = "Template:Workshops_csv"
--- @param originalWorkshop table workshop data record from which to make subtable
--- @param workshopsHeaderLookup table header lookup built from the CSV data
--- @return table subtable with the recipe IDs
function makeRecipesSubtable(originalWorkshop, workshopsHeaderLookup)
 
-- A constant we'll need only within this function.
local LOOKUP_RECIPE_BASE_STRING = "recipe"
 
-- Copy the originals directly into a subtable.
local recipeIndex1 = workshopsHeaderLookup[LOOKUP_RECIPE_BASE_STRING .. "1"]
local recipeIndex2 = workshopsHeaderLookup[LOOKUP_RECIPE_BASE_STRING .. "2"]
local recipeIndex3 = workshopsHeaderLookup[LOOKUP_RECIPE_BASE_STRING .. "3"]
local recipeIndex4 = workshopsHeaderLookup[LOOKUP_RECIPE_BASE_STRING .. "4"]
 
local recipe1 = originalWorkshop[recipeIndex1]
local recipe2 = originalWorkshop[recipeIndex2]
local recipe3 = originalWorkshop[recipeIndex3]
local recipe4 = originalWorkshop[recipeIndex4]


local INDEX_WORKSHOP_ID = 1
local recipes = {
local INDEX_WORKSHOP_NAME = 2
(recipe1 ~= "" and recipe1) or nil,
local INDEX_WORKSHOP_DESCRIPTION = 3
(recipe2 ~= "" and recipe2) or nil,
local INDEX_WORKSHOP_CATEGORY = 4
(recipe3 ~= "" and recipe3) or nil,
local INDEX_WORKSHOP_SIZE_X = 5
(recipe4 ~= "" and recipe4) or nil
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
return recipes
local DATA_ROWS = 2
end


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




---
---
-- Private member variables
--- Transforms the originalWorkshopTable returned from CSV processing to be
--- more conducive to member functions looking up data. Converts text strings
--- into subtables. Converts header row into field keys on every record and
--- stores records by id rather than arbitrary integer.
---
---
--- @param originalWorkshopsTable table of CSV-based data, with header row, data rows
--- @param workshopsHeaderLookup table lookup table of headers to get indexes
--- @return table better structured with IDs as keys
function restructureWorkshopsTable(originalWorkshopsTable, workshopsHeaderLookup)
-- A few constants we'll need only within this function.
local HEADER_REQUIRED_GOODS_BASE = "requiredGood"
local HEADER_WORKPLACE_BASE = "workplace"
local HEADER_RECIPES_BASE = "recipe"


-- Main data tables. Populated from CSV data and organized better for Lua.
mapNamesToIDs = {}


-- like this: table[workshopID] = table containing workshops data
local newWorkshopTable = {}
local workshopsTable
for _, originalWorkshop in ipairs (originalWorkshopsTable[DATA_ROWS]) do
 
-- Copy over the content, mapping unhelpful indexes into headers keys.
local newWorkshop = {}
for index, key in pairs(originalWorkshopsTable[HEADER_ROW]) do
 
-- Convert string versions of true/false to actual booleans
if key == INDEX_MOVABLE or key == INDEX_ESSENTIAL then
newWorkshop[key] = originalWorkshop[index] == "True"
else
-- Convert a some of the strings to subtables. But only once
-- per group of related strings.
if key:sub(1, #HEADER_REQUIRED_GOODS_BASE) == HEADER_REQUIRED_GOODS_BASE then
if not newWorkshop[INDEX_REQUIRED_GOODS] then
newWorkshop[INDEX_REQUIRED_GOODS] = makeRequiredGoodsSubtable(originalWorkshop, workshopsHeaderLookup)
end
else
if key:sub(1, #HEADER_WORKPLACE_BASE) == HEADER_WORKPLACE_BASE then
if not newWorkshop[INDEX_WORKPLACES] then
newWorkshop[INDEX_WORKPLACES] = makeWorkplacesSubtable(originalWorkshop, workshopsHeaderLookup)
end
else
if key:sub(1, #HEADER_RECIPES_BASE) == HEADER_RECIPES_BASE then
if not newWorkshop[INDEX_RECIPES] then
newWorkshop[INDEX_RECIPES] = makeRecipesSubtable(originalWorkshop, workshopsHeaderLookup)
end
else
-- Everything else can be directly copied over.
newWorkshop[key] = originalWorkshop[index]
end
end
end
end
end
 
newWorkshopTable[ newWorkshop[INDEX_ID] ] = newWorkshop


-- Lookup map. Built once and reused on subsequent calls
-- Also populate the map for looking up IDs with display names
-- like this:  table[display name] = workshopID
mapNamesToIDs[newWorkshop[INDEX_NAME]] = newWorkshop[INDEX_ID]
local workshopNameToWorkshopID
end
 
return newWorkshopTable
end






---
---
-- Data loader function. Calls the data templates, restructures the data to be
--- Data loader function that uses the utility module and restructures the data
-- useful for getter methods, and makes a merge table.
--- to be easier to access for invoking methods and later method calls. This
--
--- method is automatically called by all public member functions if the main
-- This method is called the first time any of the actual template methods are
--- data table has not yet been populated in the current session.
-- 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()
function loadData()
 
-- Use the CSV utility module to load the data templates we need for
-- Utility module retrieves the data as basic, flat lua tables.
-- resources.
local originalWorkshopsTable, workshopsHeaderLookup = CsvUtils.luaTableFromCSV(CsvUtils.extractCSV(DATA_TEMPLATE_NAME))
local originalWorkshopsTable, workshopsHeaderLookup = CsvUtils.luaTableFromCSV(CsvUtils.extractCSV(WORKSHOPS_DATA_TEMPLATE_NAME))
 
-- Now restructure to be more conducive for data about goods.
-- Now restructure the table so subtables can be passed to member functions
workshopsTable = restructureWorkshopsTable(originalWorkshopsTable, workshopsHeaderLookup)
-- for cleaner code.
workshopsTable = restructureWorkshopTable(originalWorkshopsTable, workshopsHeaderLookup)
end
end


Line 73: Line 315:


---
---
-- Retrieve all data for the specified workshop. Returned items look like this:
--- Uses the display name, which people are more familiar with, to find the
-- workshop {
--- encoded ID of the workshop. Useful for retrieving the workshop data that is
-- id
--- indexed by ID.
-- display name
---
-- description
--- Returns nil if the workshop with the specified name is not found.
-- category
---
-- size x
--- @param displayName string plain-language name of the workshop to find
-- size y
--- @return string ID of the workshop found, or nil if not found
-- city score
function findWorkshopIDByName(displayName)
-- movable
 
-- initially essential
-- At runtime, this should never be nil or empty, so throw an error.
-- storage
if not displayName or displayName == "" then
-- construction time
error("Parameter is nil or empty for the workshop's name: " .. displayName .. ".")
-- required goods (just their IDs) {
end
-- #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
if not workshopsTable then
loadData()
loadData()
end
end
 
local targetWorkshopID = findWorkshopIDByName(workshopName)
return mapNamesToIDs[displayName]
if not targetWorkshopID then
end
error("No building found. Please check spelling and any punctuation like an apostrophe: " .. workshopName)
 
--endregion
 
 
 
--region Public methods
 
---
--- Retrieve the whole table of data for the specified workshop. Instead of
--- this, you should probably be calling the individual getter methods.
---
--- Throws an error if called with nil or empty string. Returns nil if the
--- specified workshop cannot be found.
---
--- @param displayName string plain language name of the workshop
--- @return table containing the data for the specified workshop with key-value pairs, or nil if not found
function WorkshopsData.getAllDataForWorkshop(displayName)
 
-- At runtime, this should never be nil or empty.
if not displayName or displayName == "" then
error("Parameter is nil or empty for the workshop's name: " .. displayName .. ".")
end
 
if not workshopsTable then
WorkshopsData.loadData()
end
end
 
return workshopsTable[targetWorkshopID]
local workshopID = findWorkshopIDByName(displayName)
if not workshopID then
return nil
end
 
return workshopsTable[workshopID]
end
 
 
 
---
--- Retrieves the description for the workshop specified by its plain language
--- display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return string the in-game description of the specified workshop
function WorkshopsData.getWorkshopDescription(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
return nil
end
 
return workshop[INDEX_DESCRIPTION]
end
end


Line 124: Line 395:


---
---
-- Transforms the oldWorkshopsTable returned from CSV processing to be more
--- Retrieves the construction toolbar category for the workshop specified by
-- conducive to member functions looking up data. Essentially, we convert the
--- its plain language display name.
-- text strings into tables with the same base name and an index.
---
--  
--- Returns nil if the workshop named was not found.
-- @param oldRecipeTable the CSV-based table, with a header row then data rows
---
-- @param recipeHeaderLookup the lookup table built from the CSV data
---@param displayName string with the plain language name of the workshop
-- @return a new table for use in looking up recipe data
---@return string the category
function restructureWorkshopTable(oldWorkshopTable, workshopsHeaderLookup)
function WorkshopsData.getWorkshopCategory(displayName)
 
-- New table structure is only data rows, no header row needed. Therefore,
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
-- the new table is one degree flatter, like this:
 
-- oldWorkshopTable[2][1] contains the same data as newWorkshopTable[1]
if not workshop then
-- (but organized by workshopID instead of by index and with subtables)
return nil
local newWorkshopTable = {}
end
oldWorkshopTable = oldWorkshopTable[DATA_ROWS]
 
return workshop[INDEX_CATEGORY]
-- A few constants we need only in this function.
end
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
--- Retrieves the 2x2 size for the workshop specified by its plain language
local INDEX_OLD_WORKSHOP_SIZE_Y = 6
--- display name, in two return values.
local INDEX_OLD_WORKSHOP_CONSTRUCTION_TIME = 10
---
local INDEX_OLD_WORKSHOP_CITY_SCORE = 11
--- Returns nil if the workshop named was not found.
local INDEX_OLD_WORKSHOP_MOVABLE = 12
---
local INDEX_OLD_WORKSHOP_INITIALLY_ESSENTIAL = 13
---@param displayName string with the plain language name of the workshop
local INDEX_OLD_WORKSHOP_STORAGE = 14
---@return number the X-size of the workshop
---@return number the Y-size of the workshop
for i, oldWorkshop in ipairs(oldWorkshopTable) do
function WorkshopsData.getWorkshopSize(displayName)
 
local newWorkshop = {}
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
-- Copy over the flat information from the old recipe.
if not workshop then
local newWorkshopID = oldWorkshop[INDEX_OLD_WORKSHOP_ID]
return nil
newWorkshop[INDEX_WORKSHOP_ID] = newWorkshopID
end
newWorkshop[INDEX_WORKSHOP_NAME] = oldWorkshop[INDEX_OLD_WORKSHOP_NAME]
 
newWorkshop[INDEX_WORKSHOP_DESCRIPTION] = oldWorkshop[INDEX_OLD_WORKSHOP_DESCRIPTION]
return workshop[INDEX_SIZE_X], workshop[INDEX_SIZE_Y]
newWorkshop[INDEX_WORKSHOP_CATEGORY] = oldWorkshop[INDEX_OLD_WORKSHOP_CATEGORY]
end
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]
--- Retrieves the goods required for construction for the workshop specified by
newWorkshop[INDEX_WORKSHOP_STORAGE] = oldWorkshop[INDEX_OLD_WORKSHOP_STORAGE]
--- its plain language display name, in a table that looks like this:
newWorkshop[INDEX_WORKSHOP_CONSTRUCTION_TIME] = oldWorkshop[INDEX_OLD_WORKSHOP_CONSTRUCTION_TIME]
---
--- ["requiredGoods"] = {
newWorkshop[INDEX_WORKSHOP_REQUIRED_GOODS] = makeRequiredGoodsSubtable(oldWorkshop, workshopsHeaderLookup)
--- [1] = { ["stackSize"] = 99, ["goodID"] = "good1_ID" },
newWorkshop[INDEX_WORKSHOP_WORKPLACES] = makeWorkplacesSubtable(oldWorkshop, workshopsHeaderLookup)
--- [2] = { ["stackSize"] = 99, ["goodID"] = "good2_ID" },
newWorkshop[INDEX_WORKSHOP_RECIPES] = makeRecipesSubtable(oldWorkshop, workshopsHeaderLookup)
--- [3] = { ... } or missing if fewer
--- }
newWorkshopTable[newWorkshopID] = newWorkshop
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return table of required goods
function WorkshopsData.getWorkshopRequiredGoods(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
return nil
end
end
 
return newWorkshopTable
return workshop[INDEX_REQUIRED_GOODS]
end
end


Line 184: Line 465:


---
---
-- Loops through the old workshop, extracting req'd goods and putting them
--- Retrieves the construction time for the workshop specified by its plain
-- into a new subtable. Uses the lookup table from the CSV extraction to
--- language display name.
-- keep the data in the same order.
---
--
--- Returns nil if the workshop named was not found.
-- @param oldWorkshop the workshop from which to extract req'd goods information
---
-- @param workshopsHeaderLookup the lookup table built from the CSV data
---@param displayName string with the plain language name of the workshop
-- @return a subtable with the req'd goods information from oldWorkshop
---@return number of seconds it takes to construct the workshop
function makeRequiredGoodsSubtable(oldWorkshop, workshopsHeaderLookup)
function WorkshopsData.getWorkshopConstructionTime(displayName)
 
-- A few constants we'll need only within this function.
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
local REQ_GOOD_MAX = 3
 
local LOOKUP_REQ_GOOD_BASE_STRING = "requiredGood"
if not workshop then
return nil
-- 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
end
 
return requiredGoods
return workshop[INDEX_CONSTRUCTION_TIME]
end
end


Line 216: Line 486:


---
---
-- Loops through the old workshop, extracting workplaces and putting them
--- Retrieves the city score awarded for the workshop specified by its plain
-- into a new subtable. Uses the lookup table from the CSV extraction to
--- language display name.
-- keep the data in the same order.
---
--
--- Returns nil if the workshop named was not found.
-- @param oldWorkshop the workshop from which to extract workplaces information
---
-- @param workshopsHeaderLookup the lookup table built from the CSV data
---@param displayName string with the plain language name of the workshop
-- @return a subtable with the workplaces information from oldWorkshop
---@return number of points for city score from having the workshop
function makeWorkplacesSubtable(oldWorkshop, workshopsHeaderLookup)
function WorkshopsData.getWorkshopCityScore(displayName)
 
-- A few constants we'll need only within this function.
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
local WORKPLACE_MAX = 4
 
local LOOKUP_WORKPLACE_BASE_STRING = "workplace"
if not workshop then
return nil
-- 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
end
 
return workplaces
return workshop[INDEX_CITY_SCORE]
end
end


Line 244: Line 507:


---
---
-- Loops through the old workshop, extracting recipes and putting them
--- Retrieves whether the workshop specified by its plain language display
-- into a new subtable. Uses the lookup table from the CSV extraction to
--- name can be moved, at any cost.
-- keep the data in the same order.
---
--
--- Returns nil if the workshop named was not found.
-- @param oldWorkshop the workshop from which to extract recipes
---
-- @param workshopsHeaderLookup the lookup table built from the CSV data
---@param displayName string with the plain language name of the workshop
-- @return a subtable with the recipes from oldWorkshop
---@return boolean of whether workshop can be moved
function makeRecipesSubtable(oldWorkshop, workshopsHeaderLookup)
function WorkshopsData.isWorkshopMoveable(displayName)
 
-- A few constants we'll need only within this function.
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
local RECIPE_MAX = 4
 
local LOOKUP_RECIPE_BASE_STRING = "recipe"
if not workshop then
return nil
-- 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
end
 
return recipes
return workshop[INDEX_MOVABLE]
end
end


Line 272: Line 528:


---
---
-- Look up so workshops can be found by their common ID, rather than their
--- Retrieves whether the workshop specified by its plain language display
-- display name, which people are more familiar with.
--- name has an essential starting blueprint, and for a new game profile.
--  
---
-- Uses the display name provided to look up the associated ID for the workshop.
--- Returns nil if the workshop named was not found.
-- Builds a lookup table the first time it's called so it only has to happen
---
-- once.
---@param displayName string with the plain language name of the workshop
--
---@return boolean of whether workshop's blueprint is essential.
-- @param displayName the plain-language name of the workshop to find
function WorkshopsData.isWorkshopInitiallyEssential(displayName)
-- @return the ID of the workshop found, or nil if not found
 
function findWorkshopIDByName(displayName)
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshopsTable then
if not workshop then
loadData()
return nil
end
end
 
local foundWorkshopID = nil
return workshop[INDEX_ESSENTIAL]
-- Decide whether we need to traverse the big table. If this isn't the first
end
-- 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 = {}
--- Retrieves the storage capacity of the workshop specified by its plain
--- language display name.
for workshopID, workshop in pairs(workshopsTable) do
---
--- Returns nil if the workshop named was not found.
if not foundWorkshopID and workshop[INDEX_WORKSHOP_NAME] == displayName then
---
-- Found it, but keep traversing to build the rest of the map.
---@param displayName string with the plain language name of the workshop
foundWorkshopID = workshopID
---@return number representing the storage capacity of the workshop
end
function WorkshopsData.getWorkshopStorage(displayName)
 
workshopNameToWorkshopID[workshop[INDEX_WORKSHOP_NAME]] = workshopID
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
end
 
else
if not workshop then
-- From the lookup table.
return nil
foundWorkshopID = workshopNameToWorkshopID[displayName]
end
end
 
return foundWorkshopID
return workshop[INDEX_STORAGE_CAP]
end
end


Line 315: Line 570:


---
---
-- Retrieves the name and icon filename for a workshop.
--- Retrieves the workplaces at the workshop specified by its plain language
--
--- display name, in a table that looks like this:
-- @param workshopID the ID of the workshop to look up
---
-- @return display name, icon filename
--- ["workplaces"] = {
function WorkshopsData.getWorkshopNameAndIcon(workshopID)
--- [1] = "Any",
--- [2] = "Any",
if not workshopsTable then
--- [3] = "Any",
loadData()
--- [4] = ... or missing if fewer
--- }
---
--- The number of workplaces can easily be found with #workplaces.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return table of workplaces
function WorkshopsData.getWorkshopWorkplaces(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
return nil
end
end
 
local workshop = workshopsTable[workshopID]
return workshop[INDEX_WORKPLACES]
end
 
 
 
---
--- Retrieves the recipes possible at the workshop specified by its plain
--- language display name, in a table that looks like this:
---
--- ["recipes"] = {
--- [1] = "recipe1_ID",
--- [2] = "recipe2_ID",
--- [3] = "recipe3_ID",
--- [4] = ... or missing if fewer
--- }
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return table of recipe IDs
function WorkshopsData.getWorkshopRecipes(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
if not workshop then
error("ID for workshop not found to look up name and icon: " .. workshopID)
return nil
end
end
 
return workshop[INDEX_WORKSHOP_NAME], workshop[INDEX_WORKSHOP_NAME] .. "_icon"
return workshop[INDEX_RECIPES]
end
end




---
--- Retrieves the icon filename for the workshop specified by its plain
--- language display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return string the workshop's icon filename, including the extension
function WorkshopsData.getWorkshopIcon(displayName)
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
if not workshop then
return nil
end
-- the base string of the icon is the ID. it has to be not nil to
-- concatenate
if workshop[INDEX_ID] then
return workshop[INDEX_ID] .. "_icon.png"
else
return nil
end
end
--endregion


return WorkshopsData
return WorkshopsData

Revision as of 04:29, 18 November 2023

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

---
--- 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.
----
--- 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. It is preferable to call getter methods with the name of the good
--- rather than retrieving the entire record for the good, so that your code
--- stays protected from variations in this module. The getter methods are all
--- called with the plain-language name of the good, as spelled in the game.
---
--- * longDescription = WorkshopsData.getWorkshopDescription(workshopName)
--- * constructionCategory = WorkshopsData.getWorkshopCategory(workshopName)
--- * sizeX, sizeY = WorkshopsData.getWorkshopSize(workshopName)
--- * requiredGoodsTable = WorkshopsData.getWorkshopRequiredGoods(workshopName)
--- * timeSeconds = WorkshopsData.getWorkshopConstructionTime(workshopName)
--- * cityScore = WorkshopsData.getWorkshopCityScore(workshopName)
--- * isMovable = WorkshopsData.isWorkshopMoveable(workshopName)
--- * isInitiallyEssential = WorkshopsData.isWorkshopInitiallyEssential(workshopName)
--- * storageCap = WorkshopsData.isWorkshopInitiallyEssential(workshopName)
--- * workplacesTable = WorkshopsData.getWorkshopWorkplaces(workshopName)
--- * recipesTable = WorkshopsData.getWorkshopRecipes(workshopName)
--- * iconFilename = WorkshopsData.getWorkshopIcon(workshopName)
---
--- As a last resort, or if you need to transform the data structure, you can
--- call the method getAllDataForWorkshop(displayName). 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 WorkshopsData
local WorkshopsData = {}



local CsvUtils = require("Module:CsvUtils")



--region Private member variables

--- Main data table, like this: table[ID] = table containing data for that ID
local workshopsTable

--- Lookup map. Built once and reused on all subsequent calls within this
--- session, like this: table[displayName] = workshopID
local mapNamesToIDs

--endregion



--region Private constants

local DATA_TEMPLATE_NAME = "Template:Workshops_csv"

local HEADER_ROW = 1
local DATA_ROWS = 2

local INDEX_ID = "id"
local INDEX_NAME = "displayName"
local INDEX_DESCRIPTION = "description"
local INDEX_CATEGORY = "category"
local INDEX_SIZE_X = "sizeX"
local INDEX_SIZE_Y = "sizeY"
local INDEX_CITY_SCORE = "cityScore"
local INDEX_MOVABLE = "movable"
local INDEX_ESSENTIAL = "initiallyEssential"
local INDEX_STORAGE_CAP = "storage"
local INDEX_CONSTRUCTION_TIME = "constructionTime"
local INDEX_REQUIRED_GOODS = "requiredGoods"
local INDEX_WORKPLACES = "workplaces"
local INDEX_RECIPES = "recipes"

local INDEX_CONSTRUCTION_GOODS_STACK_SIZE = "stackSize"
local INDEX_CONSTRUCTION_GOODS_GOOD_ID = "goodID"

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

--endregion



--region Private methods

---
--- Creates a new subtable containing the construction goods required for the
--- specified workshop.
---
--- @param originalWorkshop table workshop data record from which to make subtable
--- @param workshopsHeaderLookup table header lookup built from the CSV data
--- @return table subtable with the required construction goods
function makeRequiredGoodsSubtable(originalWorkshop, workshopsHeaderLookup)

	-- A constant we'll need only within this function.
	local REQ_GOOD_HEADER_BASE_STRING = "requiredGood"

	-- Copy the originals directly into a subtable.
	local requiredIndex1 = workshopsHeaderLookup[REQ_GOOD_HEADER_BASE_STRING .. "1"]
	local requiredIndex2 = workshopsHeaderLookup[REQ_GOOD_HEADER_BASE_STRING .. "2"]
	local requiredIndex3 = workshopsHeaderLookup[REQ_GOOD_HEADER_BASE_STRING .. "3"]

	local number1, id1 = originalWorkshop[requiredIndex1]:match(PATTERN_SPLIT_STACK_AND_ID)
	local number2, id2 = originalWorkshop[requiredIndex2]:match(PATTERN_SPLIT_STACK_AND_ID)
	local number3, id3 = originalWorkshop[requiredIndex3]:match(PATTERN_SPLIT_STACK_AND_ID)

	-- don't add subtables that would just contain nils
	local requiredGoods = {
		number1 and id1 and { [INDEX_CONSTRUCTION_GOODS_STACK_SIZE] = number1, [INDEX_CONSTRUCTION_GOODS_GOOD_ID] = id1 } or nil,
		number2 and id2 and { [INDEX_CONSTRUCTION_GOODS_STACK_SIZE] = number2, [INDEX_CONSTRUCTION_GOODS_GOOD_ID] = id2 } or nil,
		number3 and id3 and { [INDEX_CONSTRUCTION_GOODS_STACK_SIZE] = number3, [INDEX_CONSTRUCTION_GOODS_GOOD_ID] = id3 } or nil
	}

	return requiredGoods
end



---
--- Creates a new subtable containing the workplaces available in the specified
--- workshop.
---
--- @param originalWorkshop table workshop data record from which to make subtable
--- @param workshopsHeaderLookup table header lookup built from the CSV data
--- @return table subtable with the workplaces
function makeWorkplacesSubtable(originalWorkshop, workshopsHeaderLookup)

	-- A constant we'll need only within this function.
	local WORKPLACE_HEADER_BASE_STRING = "workplace"

	-- Copy the originals directly into a subtable.
	local workplaceIndex1 = workshopsHeaderLookup[WORKPLACE_HEADER_BASE_STRING .. "1"]
	local workplaceIndex2 = workshopsHeaderLookup[WORKPLACE_HEADER_BASE_STRING .. "2"]
	local workplaceIndex3 = workshopsHeaderLookup[WORKPLACE_HEADER_BASE_STRING .. "3"]
	local workplaceIndex4 = workshopsHeaderLookup[WORKPLACE_HEADER_BASE_STRING .. "4"]

	local workplace1 = originalWorkshop[workplaceIndex1]
	local workplace2 = originalWorkshop[workplaceIndex2]
	local workplace3 = originalWorkshop[workplaceIndex3]
	local workplace4 = originalWorkshop[workplaceIndex4]

	-- if it's not an empty string, then save that to the table, otherwise nil
	local workplaces = {
		(workplace1 ~= "" and workplace1) or nil,
		(workplace2 ~= "" and workplace2) or nil,
		(workplace3 ~= "" and workplace3) or nil,
		(workplace4 ~= "" and workplace4) or nil
	}

	return workplaces
end



---
--- Creates a new subtable containing the recipes available in the specified
--- workshop.
---
--- @param originalWorkshop table workshop data record from which to make subtable
--- @param workshopsHeaderLookup table header lookup built from the CSV data
--- @return table subtable with the recipe IDs
function makeRecipesSubtable(originalWorkshop, workshopsHeaderLookup)

	-- A constant we'll need only within this function.
	local LOOKUP_RECIPE_BASE_STRING = "recipe"

	-- Copy the originals directly into a subtable.
	local recipeIndex1 = workshopsHeaderLookup[LOOKUP_RECIPE_BASE_STRING .. "1"]
	local recipeIndex2 = workshopsHeaderLookup[LOOKUP_RECIPE_BASE_STRING .. "2"]
	local recipeIndex3 = workshopsHeaderLookup[LOOKUP_RECIPE_BASE_STRING .. "3"]
	local recipeIndex4 = workshopsHeaderLookup[LOOKUP_RECIPE_BASE_STRING .. "4"]

	local recipe1 = originalWorkshop[recipeIndex1]
	local recipe2 = originalWorkshop[recipeIndex2]
	local recipe3 = originalWorkshop[recipeIndex3]
	local recipe4 = originalWorkshop[recipeIndex4]

	local recipes = {
		(recipe1 ~= "" and recipe1) or nil,
		(recipe2 ~= "" and recipe2) or nil,
		(recipe3 ~= "" and recipe3) or nil,
		(recipe4 ~= "" and recipe4) or nil
	}

	return recipes
end



---
--- Transforms the originalWorkshopTable returned from CSV processing to be
--- more conducive to member functions looking up data. Converts text strings
--- into subtables. Converts header row into field keys on every record and
--- stores records by id rather than arbitrary integer.
---
--- @param originalWorkshopsTable table of CSV-based data, with header row, data rows
--- @param workshopsHeaderLookup table lookup table of headers to get indexes
--- @return table better structured with IDs as keys
function restructureWorkshopsTable(originalWorkshopsTable, workshopsHeaderLookup)

	-- A few constants we'll need only within this function.
	local HEADER_REQUIRED_GOODS_BASE = "requiredGood"
	local HEADER_WORKPLACE_BASE = "workplace"
	local HEADER_RECIPES_BASE = "recipe"

	mapNamesToIDs = {}

	local newWorkshopTable = {}
	for _, originalWorkshop in ipairs (originalWorkshopsTable[DATA_ROWS]) do

		-- Copy over the content, mapping unhelpful indexes into headers keys.
		local newWorkshop = {}
		for index, key in pairs(originalWorkshopsTable[HEADER_ROW]) do

			-- Convert string versions of true/false to actual booleans
			if key == INDEX_MOVABLE or key == INDEX_ESSENTIAL then
				newWorkshop[key] = originalWorkshop[index] == "True"
			else
				-- Convert a some of the strings to subtables. But only once
				-- per group of related strings.
				if key:sub(1, #HEADER_REQUIRED_GOODS_BASE) == HEADER_REQUIRED_GOODS_BASE then
					if not newWorkshop[INDEX_REQUIRED_GOODS] then
						newWorkshop[INDEX_REQUIRED_GOODS] = makeRequiredGoodsSubtable(originalWorkshop, workshopsHeaderLookup)
					end
				else
					if key:sub(1, #HEADER_WORKPLACE_BASE) == HEADER_WORKPLACE_BASE then
						if not newWorkshop[INDEX_WORKPLACES] then
							newWorkshop[INDEX_WORKPLACES] = makeWorkplacesSubtable(originalWorkshop, workshopsHeaderLookup)
						end
					else
						if key:sub(1, #HEADER_RECIPES_BASE) == HEADER_RECIPES_BASE then
							if not newWorkshop[INDEX_RECIPES] then
								newWorkshop[INDEX_RECIPES] = makeRecipesSubtable(originalWorkshop, workshopsHeaderLookup)
							end
						else
							-- Everything else can be directly copied over.
							newWorkshop[key] = originalWorkshop[index]
						end
					end
				end
			end
		end

		newWorkshopTable[ newWorkshop[INDEX_ID] ] = newWorkshop

		-- Also populate the map for looking up IDs with display names
		mapNamesToIDs[newWorkshop[INDEX_NAME]] = newWorkshop[INDEX_ID]
	end

	return newWorkshopTable
end



---
--- Data loader function that uses the utility module and restructures the data
--- to be easier to access for invoking methods and later method calls. This
--- method is automatically called by all public member functions if the main
--- data table has not yet been populated in the current session.
function loadData()

	-- Utility module retrieves the data as basic, flat lua tables.
	local originalWorkshopsTable, workshopsHeaderLookup = CsvUtils.luaTableFromCSV(CsvUtils.extractCSV(DATA_TEMPLATE_NAME))

	-- Now restructure to be more conducive for data about goods.
	workshopsTable = restructureWorkshopsTable(originalWorkshopsTable, workshopsHeaderLookup)
end



---
--- Uses the display name, which people are more familiar with, to find the
--- encoded ID of the workshop. Useful for retrieving the workshop data that is
--- indexed by ID.
---
--- Returns nil if the workshop with the specified name is not found.
---
--- @param displayName string plain-language name of the workshop to find
--- @return string ID of the workshop found, or nil if not found
function findWorkshopIDByName(displayName)

	-- At runtime, this should never be nil or empty, so throw an error.
	if not displayName or displayName == "" then
		error("Parameter is nil or empty for the workshop's name: " .. displayName .. ".")
	end

	if not workshopsTable then
		loadData()
	end

	return mapNamesToIDs[displayName]
end

--endregion



--region Public methods

---
--- Retrieve the whole table of data for the specified workshop. Instead of
--- this, you should probably be calling the individual getter methods.
---
--- Throws an error if called with nil or empty string. Returns nil if the
--- specified workshop cannot be found.
---
--- @param displayName string plain language name of the workshop
--- @return table containing the data for the specified workshop with key-value pairs, or nil if not found
function WorkshopsData.getAllDataForWorkshop(displayName)

	-- At runtime, this should never be nil or empty.
	if not displayName or displayName == "" then
		error("Parameter is nil or empty for the workshop's name: " .. displayName .. ".")
	end

	if not workshopsTable then
		WorkshopsData.loadData()
	end

	local workshopID = findWorkshopIDByName(displayName)
	if not workshopID then
		return nil
	end

	return workshopsTable[workshopID]
end



---
--- Retrieves the description for the workshop specified by its plain language
--- display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return string the in-game description of the specified workshop
function WorkshopsData.getWorkshopDescription(displayName)

	local workshop = WorkshopsData.getAllDataForWorkshop(displayName)

	if not workshop then
		return nil
	end

	return workshop[INDEX_DESCRIPTION]
end



---
--- Retrieves the construction toolbar category for the workshop specified by
--- its plain language display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return string the category
function WorkshopsData.getWorkshopCategory(displayName)

	local workshop = WorkshopsData.getAllDataForWorkshop(displayName)

	if not workshop then
		return nil
	end

	return workshop[INDEX_CATEGORY]
end



---
--- Retrieves the 2x2 size for the workshop specified by its plain language
--- display name, in two return values.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return number the X-size of the workshop
---@return number the Y-size of the workshop
function WorkshopsData.getWorkshopSize(displayName)

	local workshop = WorkshopsData.getAllDataForWorkshop(displayName)

	if not workshop then
		return nil
	end

	return workshop[INDEX_SIZE_X], workshop[INDEX_SIZE_Y]
end



---
--- Retrieves the goods required for construction for the workshop specified by
--- its plain language display name, in a table that looks like this:
---
--- ["requiredGoods"] = {
--- 	[1] = { ["stackSize"] = 99, ["goodID"] = "good1_ID" },
--- 	[2] = { ["stackSize"] = 99, ["goodID"] = "good2_ID" },
--- 	[3] = { ... } or missing if fewer
--- }
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return table of required goods
function WorkshopsData.getWorkshopRequiredGoods(displayName)

	local workshop = WorkshopsData.getAllDataForWorkshop(displayName)

	if not workshop then
		return nil
	end

	return workshop[INDEX_REQUIRED_GOODS]
end



---
--- Retrieves the construction time for the workshop specified by its plain
--- language display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return number of seconds it takes to construct the workshop
function WorkshopsData.getWorkshopConstructionTime(displayName)

	local workshop = WorkshopsData.getAllDataForWorkshop(displayName)

	if not workshop then
		return nil
	end

	return workshop[INDEX_CONSTRUCTION_TIME]
end



---
--- Retrieves the city score awarded for the workshop specified by its plain
--- language display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return number of points for city score from having the workshop
function WorkshopsData.getWorkshopCityScore(displayName)

	local workshop = WorkshopsData.getAllDataForWorkshop(displayName)

	if not workshop then
		return nil
	end

	return workshop[INDEX_CITY_SCORE]
end



---
--- Retrieves whether the workshop specified by its plain language display
--- name can be moved, at any cost.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return boolean of whether workshop can be moved
function WorkshopsData.isWorkshopMoveable(displayName)

	local workshop = WorkshopsData.getAllDataForWorkshop(displayName)

	if not workshop then
		return nil
	end

	return workshop[INDEX_MOVABLE]
end



---
--- Retrieves whether the workshop specified by its plain language display
--- name has an essential starting blueprint, and for a new game profile.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return boolean of whether workshop's blueprint is essential.
function WorkshopsData.isWorkshopInitiallyEssential(displayName)

	local workshop = WorkshopsData.getAllDataForWorkshop(displayName)

	if not workshop then
		return nil
	end

	return workshop[INDEX_ESSENTIAL]
end



---
--- Retrieves the storage capacity of the workshop specified by its plain
--- language display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return number representing the storage capacity of the workshop
function WorkshopsData.getWorkshopStorage(displayName)

	local workshop = WorkshopsData.getAllDataForWorkshop(displayName)

	if not workshop then
		return nil
	end

	return workshop[INDEX_STORAGE_CAP]
end



---
--- Retrieves the workplaces at the workshop specified by its plain language
--- display name, in a table that looks like this:
---
--- ["workplaces"] = {
--- 	[1] = "Any",
--- 	[2] = "Any",
--- 	[3] = "Any",
--- 	[4] = ... or missing if fewer
--- }
---
--- The number of workplaces can easily be found with #workplaces.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return table of workplaces
function WorkshopsData.getWorkshopWorkplaces(displayName)

	local workshop = WorkshopsData.getAllDataForWorkshop(displayName)

	if not workshop then
		return nil
	end

	return workshop[INDEX_WORKPLACES]
end



---
--- Retrieves the recipes possible at the workshop specified by its plain
--- language display name, in a table that looks like this:
---
--- ["recipes"] = {
--- 	[1] = "recipe1_ID",
--- 	[2] = "recipe2_ID",
--- 	[3] = "recipe3_ID",
--- 	[4] = ... or missing if fewer
--- }
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return table of recipe IDs
function WorkshopsData.getWorkshopRecipes(displayName)

	local workshop = WorkshopsData.getAllDataForWorkshop(displayName)

	if not workshop then
		return nil
	end

	return workshop[INDEX_RECIPES]
end



---
--- Retrieves the icon filename for the workshop specified by its plain
--- language display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string with the plain language name of the workshop
---@return string the workshop's icon filename, including the extension
function WorkshopsData.getWorkshopIcon(displayName)

	local workshop = WorkshopsData.getAllDataForWorkshop(displayName)

	if not workshop then
		return nil
	end

	-- the base string of the icon is the ID. it has to be not nil to
	-- concatenate
	if workshop[INDEX_ID] then
		return workshop[INDEX_ID] .. "_icon.png"
	else
		return nil
	end
end

--endregion

return WorkshopsData