Module:WorkshopsData
From Against the Storm Official Wiki
Documentation for this module may be created at Module:WorkshopsData/doc
--- @module WorkshopsData local WorkshopsData = {} --region Dependencies local DATA_TEMPLATE_NAME = "Template:Workshops_csv" local CsvUtils = require("Module:CsvUtils") -- Some dependencies are loaded lazily local GoodsData --endregion --region Private member variables --- Main data table, like this: table[ID] = table containing data for that ID local workshopsTable --- Supporting table, list of names table[i] = name. local workshopsNames --- Lookup map. Built once and reused on all subsequent calls within this --- session, like this: table[displayName] = workshopID local mapNamesToIDs --- Lookup map. Built once and reused on all subsequent calls within this --- session, like this: table[recipeID] = { workshopName1, workshopName2, ... } local mapRecipeIDsToWorkshopNames --endregion --region Private constants 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 local 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] = tonumber(number1), [INDEX_CONSTRUCTION_GOODS_GOOD_ID] = id1 } or nil, number2 and id2 and { [INDEX_CONSTRUCTION_GOODS_STACK_SIZE] = tonumber(number2), [INDEX_CONSTRUCTION_GOODS_GOOD_ID] = id2 } or nil, number3 and id3 and { [INDEX_CONSTRUCTION_GOODS_STACK_SIZE] = tonumber(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 local 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 workshopName string the display name of the 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 local function makeRecipesSubtable(workshopName, 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 } if recipe1 and recipe1 ~= "" then if not mapRecipeIDsToWorkshopNames[recipe1] then mapRecipeIDsToWorkshopNames[recipe1] = {} end table.insert(mapRecipeIDsToWorkshopNames[recipe1], workshopName) end if recipe2 and recipe2 ~= "" then if not mapRecipeIDsToWorkshopNames[recipe2] then mapRecipeIDsToWorkshopNames[recipe2] = {} end table.insert(mapRecipeIDsToWorkshopNames[recipe2], workshopName) end if recipe3 and recipe3 ~= "" then if not mapRecipeIDsToWorkshopNames[recipe3] then mapRecipeIDsToWorkshopNames[recipe3] = {} end table.insert(mapRecipeIDsToWorkshopNames[recipe3], workshopName) end if recipe4 and recipe4 ~= "" then if not mapRecipeIDsToWorkshopNames[recipe4] then mapRecipeIDsToWorkshopNames[recipe4] = {} end table.insert(mapRecipeIDsToWorkshopNames[recipe4], workshopName) end 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 local function restructureWorkshopsTable(originalWorkshopsTable, workshopsHeaderLookup) -- A few constants we'll need only within this function. local DATA_ROWS = 2 local INDEX_ORIGINAL_ID = 1 local INDEX_ORIGINAL_NAME = 2 local INDEX_ORIGINAL_DESCRIPTION = 3 local INDEX_ORIGINAL_CATEGORY = 4 local INDEX_ORIGINAL_SIZE_X = 5 local INDEX_ORIGINAL_SIZE_Y = 6 -- Required goods are indexes 7, 8, 9 local INDEX_ORIGINAL_CONSTRUCTION_TIME = 10 local INDEX_ORIGINAL_CITY_SCORE = 11 local INDEX_ORIGINAL_MOVABLE = 12 local INDEX_ORIGINAL_ESSENTIAL = 13 local INDEX_ORIGINAL_STORAGE_CAP = 14 mapNamesToIDs = {} workshopsNames = {} mapRecipeIDsToWorkshopNames = {} local newWorkshopTable = {} for _, originalWorkshop in ipairs (originalWorkshopsTable[DATA_ROWS]) do -- Copy over the content, mapping unhelpful indexes into headers keys. local newWorkshop = {} newWorkshop[INDEX_ID] = originalWorkshop[INDEX_ORIGINAL_ID] newWorkshop[INDEX_NAME] = originalWorkshop[INDEX_ORIGINAL_NAME] newWorkshop[INDEX_DESCRIPTION] = originalWorkshop[INDEX_ORIGINAL_DESCRIPTION] newWorkshop[INDEX_CATEGORY] = originalWorkshop[INDEX_ORIGINAL_CATEGORY] newWorkshop[INDEX_SIZE_X] = tonumber(originalWorkshop[INDEX_ORIGINAL_SIZE_X]) newWorkshop[INDEX_SIZE_Y] = tonumber(originalWorkshop[INDEX_ORIGINAL_SIZE_Y]) newWorkshop[INDEX_CITY_SCORE] = tonumber(originalWorkshop[INDEX_ORIGINAL_CITY_SCORE]) newWorkshop[INDEX_MOVABLE] = originalWorkshop[INDEX_ORIGINAL_MOVABLE] == "TRUE" newWorkshop[INDEX_ESSENTIAL] = originalWorkshop[INDEX_ORIGINAL_ESSENTIAL] == "TRUE" newWorkshop[INDEX_STORAGE_CAP] = tonumber(originalWorkshop[INDEX_ORIGINAL_STORAGE_CAP]) newWorkshop[INDEX_CONSTRUCTION_TIME] = tonumber(originalWorkshop[INDEX_ORIGINAL_CONSTRUCTION_TIME]) newWorkshop[INDEX_REQUIRED_GOODS] = makeRequiredGoodsSubtable(originalWorkshop, workshopsHeaderLookup) newWorkshop[INDEX_WORKPLACES] = makeWorkplacesSubtable(originalWorkshop, workshopsHeaderLookup) newWorkshop[INDEX_RECIPES] = makeRecipesSubtable(newWorkshop[INDEX_NAME], originalWorkshop, workshopsHeaderLookup) newWorkshopTable[ newWorkshop[INDEX_ID] ] = newWorkshop table.insert(workshopsNames, newWorkshop[INDEX_NAME]) -- Also populate the map for looking up IDs with display names mapNamesToIDs[newWorkshop[INDEX_NAME]] = newWorkshop[INDEX_ID] end return newWorkshopTable end local function load() if not workshopsTable then -- Utility module retrieves the data as basic, flat lua tables. local originalWorkshopsTable, workshopsHeaderLookup = CsvUtils.extractTables(DATA_TEMPLATE_NAME) -- Now restructure to be more conducive. workshopsTable = restructureWorkshopsTable(originalWorkshopsTable, workshopsHeaderLookup) end end --endregion --region Public methods --- DEPRECATED --- DO NOT USE --- ---@deprecated function WorkshopsData.getAllWorkshopRecipes(displayName) if not displayName or displayName == "" then error("Parameter is nil or empty for display name.") end load() local id = mapNamesToIDs[displayName] if not id then return nil end local workshop = workshopsTable[id] if not workshop then return nil end return workshop[INDEX_RECIPES] end --- DEPRECATED --- DO NOT USE --- ---@deprecated function WorkshopsData.getWorkshopIcon(displayName) if not displayName or displayName == "" then error("Parameter is nil or empty for display name.") end load() local id = mapNamesToIDs[displayName] if not id then return nil end -- the base string of the icon is the ID. It has to be not nil to -- concatenate return id .. "_icon.png" end --- DEPRECATED --- DO NOT USE --- ---@deprecated function WorkshopsData.getWorkshopNamesWithRecipeID(recipeID) -- At runtime, this should never be nil or empty. if not recipeID or recipeID == "" then error("Parameter is nil or empty for product ID.") end load() return mapRecipeIDsToWorkshopNames[recipeID] end --endregion --region Public Building interface -- getID(displayName) -- getCategory(id) -- getCityScore(id) -- getConstructionCosts(id) -- returns { [goodName] = stack size } -- getConstructionTime(id) -- getDescription(id) -- getIcon(id) -- isMovable(id) -- getName(id) -- getNumberOfWorkplaces(id) -- getSize(id) -- returns string "X x Y" -- getStorage(id) function WorkshopsData.getID(displayName) load() return mapNamesToIDs[displayName] end function WorkshopsData.getCategory(id) load() return workshopsTable[id] ~= nil and workshopsTable[id][INDEX_CATEGORY] end function WorkshopsData.getCityScore(id) load() return workshopsTable[id] ~= nil and workshopsTable[id][INDEX_CITY_SCORE] end function WorkshopsData.getConstructionCosts(id) load() GoodsData = require("Module:GoodsData") workshop = workshopsTable[id] if not workshop then return nil end local constructionCosts = {} for _, stacks in ipairs(workshop[INDEX_REQUIRED_GOODS]) do local goodName = GoodsData.getGoodNameByID(stacks[INDEX_CONSTRUCTION_GOODS_GOOD_ID]) constructionCosts[goodName] = stacks[INDEX_CONSTRUCTION_GOODS_STACK_SIZE] end return constructionCosts end function WorkshopsData.getConstructionTime(id) load() return workshopsTable[id] ~= nil and workshopsTable[id][INDEX_CONSTRUCTION_TIME] end function WorkshopsData.getDescription(id) load() return workshopsTable[id] ~= nil and workshopsTable[id][INDEX_DESCRIPTION] end function WorkshopsData.getIcon(id) load() return workshopsTable[id] ~= nil and id .. "_icon.png" end function WorkshopsData.isMovable(id) load() return workshopsTable[id] ~= nil and workshopsTable[id][INDEX_MOVABLE] end function WorkshopsData.getName(id) load() return workshopsTable[id] ~= nil and workshopsTable[id][INDEX_NAME] end function WorkshopsData.getNumberOfWorkplaces(id) load() return workshopsTable[id] ~= nil and #workshopsTable[id][INDEX_WORKPLACES] end function WorkshopsData.getSize(id) load() workshop = workshopsTable[id] if not workshop then return nil end return workshop[INDEX_SIZE_X] .. " × " .. workshop[INDEX_SIZE_Y] end function WorkshopsData.getStorage(id) load() return workshopsTable[id] ~= nil and workshopsTable[id][INDEX_STORAGE_CAP] end --endregion return WorkshopsData