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 | --- 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 | ||
-- @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 | |||
--- | --- | ||
-- | --- Creates a new subtable containing the construction goods required for the | ||
--- specified workshop. | |||
--- | --- | ||
local | --- @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. | |||
--- | --- | ||
local | --- @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 | 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 | --- 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() | function loadData() | ||
-- | -- Utility module retrieves the data as basic, flat lua tables. | ||
local originalWorkshopsTable, workshopsHeaderLookup = CsvUtils.luaTableFromCSV(CsvUtils.extractCSV(DATA_TEMPLATE_NAME)) | |||
local originalWorkshopsTable, workshopsHeaderLookup = CsvUtils.luaTableFromCSV(CsvUtils.extractCSV( | |||
-- Now restructure to be more conducive for data about goods. | |||
-- Now restructure | workshopsTable = restructureWorkshopsTable(originalWorkshopsTable, workshopsHeaderLookup) | ||
workshopsTable = | |||
end | end | ||
| Line 73: | Line 315: | ||
--- | --- | ||
-- | --- 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 | |||
-- | |||
- | |||
- | |||
-- | |||
-- | |||
- | |||
-- | |||
- | |||
-- | |||
- | |||
- | |||
-- @param | |||
-- @return | |||
function | |||
if not workshopsTable then | if not workshopsTable then | ||
loadData() | loadData() | ||
end | end | ||
return mapNamesToIDs[displayName] | |||
if not | end | ||
error(" | |||
--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[ | 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: | ||
--- | --- | ||
-- | --- 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 | ---@param displayName string with the plain language name of the workshop | ||
-- @return | ---@return string the category | ||
function | function WorkshopsData.getWorkshopCategory(displayName) | ||
-- | local workshop = WorkshopsData.getAllDataForWorkshop(displayName) | ||
if not workshop then | |||
return nil | |||
end | |||
return workshop[INDEX_CATEGORY] | |||
end | |||
local | |||
--- | |||
--- 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 | end | ||
return | return workshop[INDEX_REQUIRED_GOODS] | ||
end | end | ||
| Line 184: | Line 465: | ||
--- | --- | ||
-- | --- Retrieves the construction time for the workshop specified by its plain | ||
-- | --- language display name. | ||
-- | --- | ||
-- | --- Returns nil if the workshop named was not found. | ||
-- @param | --- | ||
- | ---@param displayName string with the plain language name of the workshop | ||
-- @return | ---@return number of seconds it takes to construct the workshop | ||
function | function WorkshopsData.getWorkshopConstructionTime(displayName) | ||
local workshop = WorkshopsData.getAllDataForWorkshop(displayName) | |||
local | |||
if not workshop then | |||
return nil | |||
end | end | ||
return | return workshop[INDEX_CONSTRUCTION_TIME] | ||
end | end | ||
| Line 216: | Line 486: | ||
--- | --- | ||
-- | --- 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 | --- | ||
- | ---@param displayName string with the plain language name of the workshop | ||
-- @return | ---@return number of points for city score from having the workshop | ||
function | function WorkshopsData.getWorkshopCityScore(displayName) | ||
local workshop = WorkshopsData.getAllDataForWorkshop(displayName) | |||
local | if not workshop then | ||
return nil | |||
end | end | ||
return | return workshop[INDEX_CITY_SCORE] | ||
end | end | ||
| Line 244: | Line 507: | ||
--- | --- | ||
-- | --- 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 | --- | ||
- | ---@param displayName string with the plain language name of the workshop | ||
-- @return | ---@return boolean of whether workshop can be moved | ||
function | function WorkshopsData.isWorkshopMoveable(displayName) | ||
local workshop = WorkshopsData.getAllDataForWorkshop(displayName) | |||
local | if not workshop then | ||
return nil | |||
end | end | ||
return | return workshop[INDEX_MOVABLE] | ||
end | end | ||
| Line 272: | Line 528: | ||
--- | --- | ||
-- | --- 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. | ||
-- @param displayName the plain | function WorkshopsData.isWorkshopInitiallyEssential(displayName) | ||
-- @return | |||
function | local workshop = WorkshopsData.getAllDataForWorkshop(displayName) | ||
if not | if not workshop then | ||
return nil | |||
end | 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 | end | ||
return | return workshop[INDEX_STORAGE_CAP] | ||
end | end | ||
| Line 315: | Line 570: | ||
--- | --- | ||
-- Retrieves the name | --- Retrieves the workplaces at the workshop specified by its plain language | ||
-- | --- display name, in a table that looks like this: | ||
-- @param | --- | ||
-- @return | --- ["workplaces"] = { | ||
function WorkshopsData. | --- [1] = "Any", | ||
--- [2] = "Any", | |||
if not | --- [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 | 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 | if not workshop then | ||
return nil | |||
end | end | ||
return workshop[ | 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


