Module:BaseDataModel: Difference between revisions
From Against the Storm Official Wiki
m forgot a return statement ugh |
m Updating for service buildings |
||
| Line 27: | Line 27: | ||
local INDEX_RECIPE_INGREDIENT_OPTION_AMOUNT = "amount" | local INDEX_RECIPE_INGREDIENT_OPTION_AMOUNT = "amount" | ||
local INDEX_RECIPE_INGREDIENT_OPTION_ID = "name" | local INDEX_RECIPE_INGREDIENT_OPTION_ID = "name" | ||
local INDEX_RECIPE_ING_SERVICE_GOODS = "goods" -- alt to ingredients for service buildings | |||
local INDEX_RECIPE_PRODUCT = "product" | local INDEX_RECIPE_PRODUCT = "product" | ||
local INDEX_RECIPE_PRODUCT_AMOUNT = "amount" | local INDEX_RECIPE_PRODUCT_AMOUNT = "amount" | ||
local INDEX_RECIPE_PRODUCT_ID = "name" | local INDEX_RECIPE_PRODUCT_ID = "name" | ||
local | local INDEX_RECIPE_PRO_DEPOSITS = "seekedDeposits" -- alt to product for huts and camps | ||
local INDEX_RECIPE_PRO_SERVICE = "servedNeed" -- alt to product for service buildings | |||
local INDEX_RECIPE_PRODUCTION_TIME = "productionTime" | local INDEX_RECIPE_PRODUCTION_TIME = "productionTime" | ||
local INDEX_RECIPE_PLANTING_TIME = "plantingTime" | local INDEX_RECIPE_PLANTING_TIME = "plantingTime" | ||
| Line 352: | Line 354: | ||
for id, building in pairs(self.dataTable) do | for id, building in pairs(self.dataTable) do | ||
for _, recipe in ipairs(building[INDEX_RECIPES]) do | for _, recipe in ipairs(building[INDEX_RECIPES]) do | ||
-- If there's a product with an amount: | |||
-- If there's a product subtable with an amount: | |||
if recipe[INDEX_RECIPE_PRODUCT] then | if recipe[INDEX_RECIPE_PRODUCT] then | ||
if recipe[INDEX_RECIPE_PRODUCT][INDEX_RECIPE_PRODUCT_ID] == productID then | if recipe[INDEX_RECIPE_PRODUCT][INDEX_RECIPE_PRODUCT_ID] == productID then | ||
| Line 360: | Line 363: | ||
}) | }) | ||
end | end | ||
-- There may also seeked deposits | |||
elseif recipe[ | -- There may also seeked deposits. | ||
if recipe[ | elseif recipe[INDEX_RECIPE_PRO_DEPOSITS] then | ||
if recipe[INDEX_RECIPE_PRO_DEPOSITS] == productID then | |||
table.insert(ret, { | |||
["buildingID"] = id, | |||
["recipe"] = recipe, | |||
}) | |||
end | |||
-- Or a service provided | |||
elseif recipe[INDEX_RECIPE_PRO_SERVICE] then | |||
if recipe[INDEX_RECIPE_PRO_SERVICE] == productID then | |||
table.insert(ret, { | table.insert(ret, { | ||
["buildingID"] = id, | ["buildingID"] = id, | ||
| Line 414: | Line 427: | ||
for id, building in pairs(self.dataTable) do | for id, building in pairs(self.dataTable) do | ||
for _, recipe in ipairs(building[INDEX_RECIPES]) do | for _, recipe in ipairs(building[INDEX_RECIPES]) do | ||
local ingredientsList = {} | |||
-- Some recipes don't have ingredients; skip them obviously. | -- Some recipes don't have ingredients; skip them obviously. | ||
if recipe[INDEX_RECIPE_INGREDIENTS] then | if recipe[INDEX_RECIPE_INGREDIENTS] then | ||
for _, ingredientSlot in ipairs( | ingredientsList = recipe[INDEX_RECIPE_INGREDIENTS] | ||
elseif recipe[INDEX_RECIPE_ING_SERVICE_GOODS] then | |||
ingredientsList = recipe[INDEX_RECIPE_ING_SERVICE_GOODS] | |||
end | |||
for _, ingredientSlot in ipairs(ingredientsList) do | |||
for _, option in ipairs(ingredientSlot) do | |||
if option[INDEX_RECIPE_INGREDIENT_OPTION_ID] == ingredientID then | |||
table.insert(ret, { | |||
["buildingID"] = id, | |||
["recipe"] = recipe, | |||
}) | |||
break | |||
end | end | ||
end | end | ||
end | end | ||
end | end | ||
end | end | ||
| Line 450: | Line 470: | ||
local ret = {} | local ret = {} | ||
for _, recipe in ipairs(building[INDEX_RECIPES]) do | for _, recipe in ipairs(building[INDEX_RECIPES]) do | ||
-- If there's a product subtable with an amount. | |||
if recipe[INDEX_RECIPE_PRODUCT] then | if recipe[INDEX_RECIPE_PRODUCT] then | ||
if recipe[INDEX_RECIPE_PRODUCT][INDEX_RECIPE_PRODUCT_ID] == productID then | if recipe[INDEX_RECIPE_PRODUCT][INDEX_RECIPE_PRODUCT_ID] == productID then | ||
| Line 459: | Line 481: | ||
return ret | return ret | ||
end | end | ||
if recipe[ | -- There could also be a seeked deposit. | ||
elseif recipe[INDEX_RECIPE_PRO_DEPOSITS] then | |||
if recipe[INDEX_RECIPE_PRO_DEPOSITS] == productID then | |||
table.insert(ret, { | |||
["buildingID"] = buildingID, | |||
["recipe"] = recipe, | |||
}) | |||
-- Only one ever per building, so return now | |||
return ret | |||
end | |||
-- Or a service provided. | |||
elseif recipe[INDEX_RECIPE_PRO_SERVICE] then | |||
if recipe[INDEX_RECIPE_PRO_SERVICE] == productID then | |||
table.insert(ret, { | table.insert(ret, { | ||
["buildingID"] = buildingID, | ["buildingID"] = buildingID, | ||
| Line 490: | Line 525: | ||
local ret = {} | local ret = {} | ||
for _, recipe in ipairs(building[INDEX_RECIPES]) do | for _, recipe in ipairs(building[INDEX_RECIPES]) do | ||
local ingredientsList = {} | |||
-- Some recipes don't have any ingredients; skip those obviously. | -- Some recipes don't have any ingredients; skip those obviously. | ||
if recipe[INDEX_RECIPE_INGREDIENTS] then | if recipe[INDEX_RECIPE_INGREDIENTS] then | ||
for _, ingredientSlot in ipairs( | ingredientsList = recipe[INDEX_RECIPE_INGREDIENTS] | ||
elseif recipe[INDEX_RECIPE_ING_SERVICE_GOODS] then | |||
ingredientsList = recipe[INDEX_RECIPE_ING_SERVICE_GOODS] | |||
end | |||
for _, ingredientSlot in ipairs(ingredientsList) do | |||
for _, option in ipairs(ingredientSlot) do | |||
if option[INDEX_RECIPE_INGREDIENT_OPTION_ID] == ingredientID then | |||
table.insert(ret, { | |||
["buildingID"] = buildingID, | |||
["recipe"] = recipe, | |||
}) | |||
break | |||
end | end | ||
end | end | ||
| Line 536: | Line 577: | ||
if recipeData.recipe[INDEX_RECIPE_PRODUCT] then | if recipeData.recipe[INDEX_RECIPE_PRODUCT] then | ||
return recipeData.recipe[INDEX_RECIPE_PRODUCT][INDEX_RECIPE_PRODUCT_ID] | return recipeData.recipe[INDEX_RECIPE_PRODUCT][INDEX_RECIPE_PRODUCT_ID] | ||
elseif recipeData.recipe[INDEX_RECIPE_PRO_DEPOSITS] then | |||
return recipeData.recipe[INDEX_RECIPE_PRO_DEPOSITS] | |||
elseif recipeData.recipe[INDEX_RECIPE_ING_SERVICE_GOODS] then | |||
return recipeData.recipe[INDEX_RECIPE_ING_SERVICE_GOODS] | |||
else | else | ||
return | return nil | ||
end | end | ||
end | end | ||
| Line 552: | Line 599: | ||
return recipeData.recipe[INDEX_RECIPE_PRODUCT][INDEX_RECIPE_PRODUCT_AMOUNT] | return recipeData.recipe[INDEX_RECIPE_PRODUCT][INDEX_RECIPE_PRODUCT_AMOUNT] | ||
-- Default, for deposits, from which gatherers always bring home 1 | -- Default, for deposits, from which gatherers always bring home 1 and for services, which always provide 1 | ||
else | else | ||
return 1 | return 1 | ||
| Line 594: | Line 641: | ||
return recipeData.recipe[INDEX_RECIPE_GATHERING_TIME] | return recipeData.recipe[INDEX_RECIPE_GATHERING_TIME] | ||
else | else -- for example, services don't have a time | ||
return 0 | return 0 | ||
end | end | ||
| Line 607: | Line 654: | ||
---@return number of ingredients (slots of options) | ---@return number of ingredients (slots of options) | ||
function BaseDataModel.getRecipeNumIngredientSlots(recipeData) | function BaseDataModel.getRecipeNumIngredientSlots(recipeData) | ||
if | |||
if recipeData.recipe[INDEX_RECIPE_INGREDIENTS] then | |||
return #recipeData.recipe[INDEX_RECIPE_INGREDIENTS] | |||
elseif recipeData.recipe[INDEX_RECIPE_ING_SERVICE_GOODS] then | |||
return #recipeData.recipe[INDEX_RECIPE_ING_SERVICE_GOODS] | |||
--Camps, for example, don't have ingredients. | |||
else | |||
return 0 | return 0 | ||
end | end | ||
end | end | ||
Revision as of 20:32, 25 October 2024
Documentation for this module may be created at Module:BaseDataModel/doc
--- @module BaseDataModel
local BaseDataModel = {}
--region Dependencies
local JsonUtils = require("Module:JsonUtils")
--endregion
--region Private constants
local INDEX_CATEGORY = "category"
local INDEX_CITY_SCORE = "cityScore"
local INDEX_CONSTRUCTION_TIME = "constructionTime"
local INDEX_DESCRIPTION = "description"
local INDEX_NAME = "displayName"
local INDEX_ID = "id"
local INDEX_IS_ESSENTIAL = "initiallyEssential"
local INDEX_IS_MOVABLE = "movable"
local INDEX_RECIPES = "recipes"
local INDEX_RECIPE_GRADE = "grade"
local INDEX_RECIPE_INGREDIENTS = "ingredients"
local INDEX_RECIPE_INGREDIENT_OPTION_AMOUNT = "amount"
local INDEX_RECIPE_INGREDIENT_OPTION_ID = "name"
local INDEX_RECIPE_ING_SERVICE_GOODS = "goods" -- alt to ingredients for service buildings
local INDEX_RECIPE_PRODUCT = "product"
local INDEX_RECIPE_PRODUCT_AMOUNT = "amount"
local INDEX_RECIPE_PRODUCT_ID = "name"
local INDEX_RECIPE_PRO_DEPOSITS = "seekedDeposits" -- alt to product for huts and camps
local INDEX_RECIPE_PRO_SERVICE = "servedNeed" -- alt to product for service buildings
local INDEX_RECIPE_PRODUCTION_TIME = "productionTime"
local INDEX_RECIPE_PLANTING_TIME = "plantingTime"
local INDEX_RECIPE_HARVESTING_TIME = "harvestingTime"
local INDEX_RECIPE_GATHERING_TIME = "gatheringTime"
local INDEX_CONSTRUCTION_GOODS = "requiredGoods"
local INDEX_CONSTRUCTION_GOODS_AMOUNT = "amount"
local INDEX_CONSTRUCTION_GOODS_ID = "name"
local INDEX_SIZE_X = "sizeX"
local INDEX_SIZE_Y = "sizeY"
local INDEX_STORAGE_CAP = "storage"
local INDEX_WORKPLACES = "workplaces"
local CONVERT_GRADE_TO_NUMBER = {
["Grade0"] = 0,
["Grade1"] = 1,
["Grade2"] = 2,
["Grade3"] = 3,
}
local ERROR_MESSAGE_INSTANCE_NOT_INITIALIZED = "This instance's data table was never initialized. Please check how and where this method was called to diagnose the problem"
--endregion
--region Private member variables
--- Main data array, indexed by ID.
local dataTable
--- Lookup table to make searching by names instant.
local mapNamesToIDs
--endregion
--region Private methods
---load
---Loads the JSON data into the two structured member variables for data access.
---
---Benchmarking: ~0.015 seconds
---
---@param instance table an instance of BaseDataModel
---@param dataFile string filename of Json data to load into this instance
---@return table, table instance's data structures --when uncommented for debugging
local function load(instance, dataFile)
-- Utility module loads the data from JSON.
local rawTable = JsonUtils.convertJSONToLuaTable(dataFile)
-- Lightweight (no deep copying) restructuring for faster look-ups.
instance.dataTable = {}
instance.mapNamesToIDs = {}
for _, record in ipairs(rawTable) do
local key = record[INDEX_ID]
local name = record[INDEX_NAME]
instance.dataTable[key] = record
instance.mapNamesToIDs[name] = key
end
--return instance.dataTable, instance.mapNamesToIDs --uncomment when debugging
end
---findID
---called with self and an ID, gets the record with that ID from that instance
---@param instance table an instance of BaseDataModel
---@param id string the ID
---@return table the record with that ID from that instance, or nil if not found
local function findID(instance, id)
if not instance.dataTable then
error(ERROR_MESSAGE_INSTANCE_NOT_INITIALIZED)
end
return instance.dataTable[id]
end
---findName
---called with self and a name gets the record with that name from that instance
---@param instance table an instance of BaseDataModel
---@param name string the display name
---@return table the record with that name, or nil if not found
local function findName(instance, name)
if not instance.dataTable or not instance.mapNamesToIDs then
error(ERROR_MESSAGE_INSTANCE_NOT_INITIALIZED)
end
local id = instance.mapNamesToIDs[name]
if not id then
return nil
end
return instance.dataTable[id]
end
--endregion
--region Public building interface
-- All building data models implement this 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)
---getID finds the specified name in the instance's data and returns the associated ID
---@param name string the display name
---@return string the ID, or nil if not found
function BaseDataModel:getID(name)
local building = findName(self, name)
if building then
return building[INDEX_ID]
else
return nil
end
end
---getCategory from the instance's data using an ID
---@param id string the ID
---@return string the category, or nil if not found
function BaseDataModel:getCategory(id)
local building = findID(self, id)
if building then
return building[INDEX_CATEGORY]
else
return nil
end
end
---getCityScore from the instance's data using an ID
---@param id string the ID
---@return number the city score, or nil if not found
function BaseDataModel:getCityScore(id)
local building = findID(self, id)
if building then
return building[INDEX_CITY_SCORE]
else
return nil
end
end
---getConstructionCosts from the instance's data using an ID in the form of an array of short tables describing goods required for construction.
--- example = {
--- [1] = {
--- ["amount"] = 5,
--- ["name"] = "[Mat Processed] Planks", -- note that this is NOT a name, but an ID
--- },
--- [2] = {
--- ["amount"] = 2,
--- ["name"] = "[Mat Processed] Bricks", -- note that this is NOT a name, but an ID
--- },
--- }
---@param id string the ID
---@return table an array of tables with amount and ID
function BaseDataModel:getConstructionCosts(id)
local building = findID(self, id)
if building then
return building[INDEX_CONSTRUCTION_GOODS]
else
return nil
end
end
---getConstructionTime from the instance's data using an ID
---@param id string the ID
---@return number the number of seconds, or nil if not found
function BaseDataModel:getConstructionTime(id)
local building = findID(self, id)
if building then
return building[INDEX_CONSTRUCTION_TIME]
else
return nil
end
end
---getDescription from the instance's data using an ID
---@param id string the ID
---@return string the description including sprite markup, or nil if not found
function BaseDataModel:getDescription(id)
local building = findID(self, id)
if building then
return building[INDEX_DESCRIPTION]
else
return nil
end
end
---getIcon from the instance's data using an ID
---@param id string the ID
---@return string the icon filename, or nil if not found
function BaseDataModel:getIcon(id)
local building = findID(self, id)
if building then
return id .. "_icon.png"
else
return nil
end
end
---isMovable from the instance's data using an ID
---@param id string the ID
---@return boolean true if the building can be moved, false if not (or nil if not found)
function BaseDataModel:isMovable(id)
local building = findID(self, id)
if building then
return building[INDEX_IS_MOVABLE]
else
return nil
end
end
---getName from the instance's data using an ID
---@param id string the ID
---@return string the name, or nil if not found
function BaseDataModel:getName(id)
local building = findID(self, id)
if building then
return building[INDEX_NAME]
else
return nil
end
end
---getNumberOfWorkplaces from the instance's data using an ID
---@param id string the ID
---@return number how many worker slots, or nil if not found
function BaseDataModel:getNumberOfWorkplaces(id)
local building = findID(self, id)
if building then
return building[INDEX_WORKPLACES]
else
return nil
end
end
---getSize from the instance's data using an ID
---@param id string the ID
---@return string as x-by-y, or nil if not found
function BaseDataModel:getSize(id)
local building = findID(self, id)
if building then
return building[INDEX_SIZE_X] .. " × " .. building[INDEX_SIZE_Y]
else
return nil
end
end
---getStorage from the instance's data using an ID
---@param id string the ID
---@return number the storage capacity, or nil if not found
function BaseDataModel:getStorage(id)
local building = findID(self, id)
if building then
return building[INDEX_STORAGE_CAP]
else
return nil
end
end
--endregion
--region Public building recipe query interface
-- All methods in the Building recipe query interface returns an array of pairs of building IDs and recipes, pulled straight from the JSON-based data table:
-- [1] = {
-- [1].buildingID = string --building ID
-- [1].recipe = {
-- ["product"] = { --product info
-- ["name"] = string --product ID *NOT* name!
-- ["amount"] = number
-- }
-- ["grade"] = string --like "Grade2"
-- ["productionTime"] = number --in seconds
-- ["ingredients"] = { --ingredient slots (between 0-3)
-- [1] = { --option list (between 1-6)
-- [1] = { --option info
-- ["name"] = string --ingredient ID *NOT* name
-- ["amount"] = number
-- },
-- ... --next option
-- },
-- ...--next ingredient slot
-- }
-- }
-- }
-- getIDsAndRecipesWhereProductID(productID)
-- getIDsAndRecipesWhereBuildingID(buildingID)
-- getIDsAndRecipesWhereIngredientID(ingredientID)
-- getIDsAndRecipesWhereProductIDAndBuildingID(productID, buildingID)
-- getIDsAndRecipesWhereIngredientIDAndBuildingID(ingredientID, buildingID)
---getIDsAndRecipesWhereProductID
---Looks through all the instance's buildings' recipes for any that have the specified product, and returns an array of pairs of building IDs and recipes.
---
---Handles when workshops and gathering huts name their fields differently.
---
---Benchmarking: ~0.0001
---
---@param productID string the product
---@return table array of pairs of buildingIDs and recipes, or {} if none found
function BaseDataModel:getIDsAndRecipesWhereProductID(productID)
if not self.dataTable then
error(ERROR_MESSAGE_INSTANCE_NOT_INITIALIZED)
end
local ret = {}
for id, building in pairs(self.dataTable) do
for _, recipe in ipairs(building[INDEX_RECIPES]) do
-- If there's a product subtable with an amount:
if recipe[INDEX_RECIPE_PRODUCT] then
if recipe[INDEX_RECIPE_PRODUCT][INDEX_RECIPE_PRODUCT_ID] == productID then
table.insert(ret, {
["buildingID"] = id,
["recipe"] = recipe,
})
end
-- There may also seeked deposits.
elseif recipe[INDEX_RECIPE_PRO_DEPOSITS] then
if recipe[INDEX_RECIPE_PRO_DEPOSITS] == productID then
table.insert(ret, {
["buildingID"] = id,
["recipe"] = recipe,
})
end
-- Or a service provided
elseif recipe[INDEX_RECIPE_PRO_SERVICE] then
if recipe[INDEX_RECIPE_PRO_SERVICE] == productID then
table.insert(ret, {
["buildingID"] = id,
["recipe"] = recipe,
})
end
end
end
end
return ret
end
---getIDsAndRecipesWhereBuildingID
---Loads the instance's building's recipes into an array of pairs of the same building ID and those recipes.
---
---Benchmarking: ~0.0000 seconds
---
---@param buildingID string the building
---@return table array of pairs of buildingIDs and recipes, or {} if none found
function BaseDataModel:getIDsAndRecipesWhereBuildingID(buildingID)
local building = findID(self, buildingID)
if not building then
return {}
end
local ret = {}
for _, recipe in ipairs(building[INDEX_RECIPES]) do
table.insert(ret, {
["buildingID"] = buildingID,
["recipe"] = recipe,
})
end
return ret
end
---getIDsAndRecipesWhereIngredientID
---Looks through all the instance's buildings' recipes to find any with the specified ingredients, and returns an array of pairs of building IDs and recipes.
---
---Benchmarking: ~0.0003 seconds
---
---@param ingredientID string the ingredient
---@return table array of pairs of buildingIDs and recipes, or {} if none found
function BaseDataModel:getIDsAndRecipesWhereIngredientID(ingredientID)
if not self.dataTable then
error(ERROR_MESSAGE_INSTANCE_NOT_INITIALIZED)
end
local ret = {}
for id, building in pairs(self.dataTable) do
for _, recipe in ipairs(building[INDEX_RECIPES]) do
local ingredientsList = {}
-- Some recipes don't have ingredients; skip them obviously.
if recipe[INDEX_RECIPE_INGREDIENTS] then
ingredientsList = recipe[INDEX_RECIPE_INGREDIENTS]
elseif recipe[INDEX_RECIPE_ING_SERVICE_GOODS] then
ingredientsList = recipe[INDEX_RECIPE_ING_SERVICE_GOODS]
end
for _, ingredientSlot in ipairs(ingredientsList) do
for _, option in ipairs(ingredientSlot) do
if option[INDEX_RECIPE_INGREDIENT_OPTION_ID] == ingredientID then
table.insert(ret, {
["buildingID"] = id,
["recipe"] = recipe,
})
break
end
end
end
end
end
return ret
end
---getIDsAndRecipesWhereProductIDAndBuildingID
---Looks through the instance's specified buildings' recipes to see whether one has the specified product, and returns an array of pairs of building IDs and recipes.
---
---Benchmarking: ~0.0000 seconds
---
---@param productID string the product
---@param buildingID string the building
---@return table array of pairs of buildingIDs and recipes, or {} if none found
function BaseDataModel:getIDsAndRecipesWhereProductIDAndBuildingID(productID, buildingID)
local building = findID(self, buildingID)
if not building then
return {}
end
local ret = {}
for _, recipe in ipairs(building[INDEX_RECIPES]) do
-- If there's a product subtable with an amount.
if recipe[INDEX_RECIPE_PRODUCT] then
if recipe[INDEX_RECIPE_PRODUCT][INDEX_RECIPE_PRODUCT_ID] == productID then
table.insert(ret, {
["buildingID"] = buildingID,
["recipe"] = recipe,
})
-- Only one ever per building, so return now
return ret
end
-- There could also be a seeked deposit.
elseif recipe[INDEX_RECIPE_PRO_DEPOSITS] then
if recipe[INDEX_RECIPE_PRO_DEPOSITS] == productID then
table.insert(ret, {
["buildingID"] = buildingID,
["recipe"] = recipe,
})
-- Only one ever per building, so return now
return ret
end
-- Or a service provided.
elseif recipe[INDEX_RECIPE_PRO_SERVICE] then
if recipe[INDEX_RECIPE_PRO_SERVICE] == productID then
table.insert(ret, {
["buildingID"] = buildingID,
["recipe"] = recipe,
})
-- Only one ever per building, so return now
return ret
end
end
end
return {}
end
---getIDsAndRecipesWhereIngredientIDAndBuildingID
---Looks through the instance's specified building's recipes to see whether any have the specified ingredient, and returns an array of pairs of building IDs and recipes.
---
---Benchmarking: ~0.0000 seconds
---
---@param ingredientID string
---@param buildingID string
---@return table array of pairs of buildingIDs and recipes, or {} if none found
function BaseDataModel:getIDsAndRecipesWhereIngredientIDAndBuildingID(ingredientID, buildingID)
local building = findID(self, buildingID)
if not building then
return {}
end
local ret = {}
for _, recipe in ipairs(building[INDEX_RECIPES]) do
local ingredientsList = {}
-- Some recipes don't have any ingredients; skip those obviously.
if recipe[INDEX_RECIPE_INGREDIENTS] then
ingredientsList = recipe[INDEX_RECIPE_INGREDIENTS]
elseif recipe[INDEX_RECIPE_ING_SERVICE_GOODS] then
ingredientsList = recipe[INDEX_RECIPE_ING_SERVICE_GOODS]
end
for _, ingredientSlot in ipairs(ingredientsList) do
for _, option in ipairs(ingredientSlot) do
if option[INDEX_RECIPE_INGREDIENT_OPTION_ID] == ingredientID then
table.insert(ret, {
["buildingID"] = buildingID,
["recipe"] = recipe,
})
break
end
end
end
end
return ret
end
--endregion
--region Public recipe data retrieval interface
-- Methods in this interface take a recipe taken from this BaseDataModel and return specific information within. This module owns the access to the inner data.
-- Unlike the recipe query and building interfaces, these methods are NOT called on instances, but the static class name, BaseDataModel.
--
-- BaseDataModel.getRecipeProductID(recipeData)
-- BaseDataModel.getRecipeProductAmount(recipeData)
-- BaseDataModel.getRecipeGrade(recipeData)
-- BaseDataModel.getRecipeBuildingID(recipeData)
-- BaseDataModel.getRecipeTime(recipeData)
-- BaseDataModel.getRecipeNumIngredientSlots(recipeData)
-- BaseDataModel.getRecipeIngredientNumOptions(recipeData, i)
-- BaseDataModel.getRecipeIngredientOptionIDAt(recipeData, i, j)
-- BaseDataModel.getRecipeIngredientOptionAmountAt(recipeData, i, j)
---getRecipeProductID
---Extracts the product ID from the provided recipe data table.
---
---@param recipeData table a recipe pair retrieved from one of the getters above
---@return string its productID
function BaseDataModel.getRecipeProductID(recipeData)
if recipeData.recipe[INDEX_RECIPE_PRODUCT] then
return recipeData.recipe[INDEX_RECIPE_PRODUCT][INDEX_RECIPE_PRODUCT_ID]
elseif recipeData.recipe[INDEX_RECIPE_PRO_DEPOSITS] then
return recipeData.recipe[INDEX_RECIPE_PRO_DEPOSITS]
elseif recipeData.recipe[INDEX_RECIPE_ING_SERVICE_GOODS] then
return recipeData.recipe[INDEX_RECIPE_ING_SERVICE_GOODS]
else
return nil
end
end
---getRecipeProductAmount
---Extracts the product amount from the provided recipe data table.
---@param recipeData table a recipe pair retrieved from one of the getters above
---@return number product amount
function BaseDataModel.getRecipeProductAmount(recipeData)
if recipeData.recipe[INDEX_RECIPE_PRODUCT] then
return recipeData.recipe[INDEX_RECIPE_PRODUCT][INDEX_RECIPE_PRODUCT_AMOUNT]
-- Default, for deposits, from which gatherers always bring home 1 and for services, which always provide 1
else
return 1
end
end
---getRecipeGrade
---Extracts the efficiency grade from the provided recipe data table.
---@param recipeData table a recipe pair retrieved from one of the getters above
---@return number efficiency grade
function BaseDataModel.getRecipeGrade(recipeData)
return CONVERT_GRADE_TO_NUMBER[recipeData.recipe[INDEX_RECIPE_GRADE]]
end
---getRecipeBuildingID
---Extracts the building ID from the provided recipe data table where this recipe was found.
---@param recipeData table a recipe pair retrieved from one of the getters above
---@return string ID of the building that makes this recipe
function BaseDataModel.getRecipeBuildingID(recipeData)
return recipeData.buildingID
end
---getRecipeTime
---Extracts the production time from the provided recipe data table.
---
---Returns the production time for industry buildings, the sum of planting and harvesting time for farms, and gathering time for camps, etc.
---
---@param recipeData table a recipe pair retrieved from one of the getters above
---@return number in seconds
function BaseDataModel.getRecipeTime(recipeData)
if recipeData.recipe[INDEX_RECIPE_PRODUCTION_TIME] then
return recipeData.recipe[INDEX_RECIPE_PRODUCTION_TIME]
elseif recipeData.recipe[INDEX_RECIPE_PLANTING_TIME] and recipeData.recipe[INDEX_RECIPE_HARVESTING_TIME] then
return recipeData.recipe[INDEX_RECIPE_PLANTING_TIME]
+ recipeData.recipe[INDEX_RECIPE_HARVESTING_TIME]
elseif recipeData.recipe[INDEX_RECIPE_GATHERING_TIME] then
return recipeData.recipe[INDEX_RECIPE_GATHERING_TIME]
else -- for example, services don't have a time
return 0
end
end
---getRecipeNumIngredientSlots
---Counts the number of slots for ingredients found in the provided recipe data table.
---
---If the recipe has no ingredients (like for farms and camps), this safely returns zero.
---
---@param recipeData table a recipe pair retrieved from one of the getters above
---@return number of ingredients (slots of options)
function BaseDataModel.getRecipeNumIngredientSlots(recipeData)
if recipeData.recipe[INDEX_RECIPE_INGREDIENTS] then
return #recipeData.recipe[INDEX_RECIPE_INGREDIENTS]
elseif recipeData.recipe[INDEX_RECIPE_ING_SERVICE_GOODS] then
return #recipeData.recipe[INDEX_RECIPE_ING_SERVICE_GOODS]
--Camps, for example, don't have ingredients.
else
return 0
end
end
---getRecipeIngredientNumOptions
---Counts the number of options for the specified ingredient slot in the provided recipe data table.
---
---This should never be called if the ingredients table doesn't exist.
---
---@param recipeData table a recipe pair retrieved from one of the getters above
---@param i number index of which ingredient slot
---@return number of options for that ingredient slot
function BaseDataModel.getRecipeIngredientNumOptions(recipeData, i)
return #recipeData.recipe[INDEX_RECIPE_INGREDIENTS][i]
end
---getRecipeIngredientOptionIDAt
---Extracts the good ID for the specified option for the specified ingredient slot in the provided recipe data table.
---
---This should never be called if the ingredients table doesn't exist.
---
---@param recipeData table a recipe pair retrieved from one of the getters above
---@param i number index of which ingredient slot
---@param j table index of which option at that slot
---@return string good ID
function BaseDataModel.getRecipeIngredientOptionIDAt(recipeData, i, j)
local optionsList = recipeData.recipe[INDEX_RECIPE_INGREDIENTS][i]
return optionsList[j][INDEX_RECIPE_INGREDIENT_OPTION_ID]
end
---getRecipeIngredientOptionAmountAt
---Extracts the good ID for the specified option for the specified ingredient slot in the provided recipe data table.
---
---This should never be called if the ingredients table doesn't exist.
---
---@param recipeData table a recipe pair retrieved from one of the getters above
---@param i number index of which ingredient slot
---@param j table index of which option at that slot
---@return number ingerdient amount
function BaseDataModel.getRecipeIngredientOptionAmountAt(recipeData, i, j)
local optionsList = recipeData.recipe[INDEX_RECIPE_INGREDIENTS][i]
return optionsList[j][INDEX_RECIPE_INGREDIENT_OPTION_AMOUNT]
end
--endregion
--region Public constructor
---new
---Constructs a new BaseDataModel by loading the specified data file into the instance.
---
---Benchmarking: ~0.0151 seconds
---
---@param dataFile string filename of Json data to load into this instance
function BaseDataModel.new(dataFile)
local instance = {}
setmetatable(instance, { __index = BaseDataModel })
load(instance, dataFile)
return instance
end
--endregion
return BaseDataModel


