Module:Building: Difference between revisions
From Against the Storm Official Wiki
created to serve dev data on buildings to new data model |
m small refactoring for readability |
||
| (14 intermediate revisions by the same user not shown) | |||
| Line 6: | Line 6: | ||
---@class Building | ---@class Building | ||
---@field _id BuildingID Unique ID code of the building. | ---@field _id BuildingID Unique ID code of the building. | ||
---@field _displayName | ---@field _displayName BuildingName The display name in-game. | ||
---@field _description string The in-game descriptive text, including sprite icons, newlines, and escape characters. | ---@field _description string The in-game descriptive text, including sprite icons, newlines, and escape characters. | ||
---@field _iconFilename Filename The file name of the icon, including its extension. | ---@field _iconFilename Filename The file name of the icon, including its extension. | ||
| Line 23: | Line 23: | ||
---@field _waterUsed? WaterID Type of water used in the building, if any. | ---@field _waterUsed? WaterID Type of water used in the building, if any. | ||
---@field _levels? Level[] List of upgrades, if any. | ---@field _levels? Level[] List of upgrades, if any. | ||
---@field _recipes? | ---@field _recipes? RecipeBook Recipes available, if any, organized by product, grade, and stack size. | ||
-- A unique identifier for a building. | -- A unique identifier for a building. | ||
---@alias BuildingID string | ---@alias BuildingID string | ||
-- The display name of a building. | |||
---@alias BuildingName string | |||
-- An ID and amount of a good (e.g., for construction or upgrades). | -- An ID and amount of a good (e.g., for construction or upgrades). | ||
---@alias RequiredGoodPair {_id: ResourceID, _amount: | ---@alias RequiredGoodPair {_id: ResourceID, _amount: Amount} | ||
-- An ID of a water type. | -- An ID of a water type. | ||
| Line 99: | Line 102: | ||
Woodworking = "Woodworking", | Woodworking = "Woodworking", | ||
} | } | ||
--#region Dependencies | --#region Dependencies | ||
| Line 117: | Line 122: | ||
"Module:Hearth/hearths_data", | "Module:Hearth/hearths_data", | ||
"Module:Warehouse/warehouses_data", | "Module:Warehouse/warehouses_data", | ||
"Module:Farm/farm_fields_data" | "Module:Farm/farm_fields_data" | ||
} | } | ||
local Recipe = require("Module:Recipe") | |||
local copy = Recipe.copy | |||
local Wiki_Utility = require("Module:Wiki_Utility") | local Wiki_Utility = require("Module:Wiki_Utility") | ||
local icon = Wiki_Utility.renderIcon | local icon = Wiki_Utility.renderIcon | ||
local nowrap = Wiki_Utility.surroundWithNoWrap | local nowrap = Wiki_Utility.surroundWithNoWrap | ||
local | local wrapClasses = Wiki_Utility.surroundWithClasses | ||
local link = Wiki_Utility.renderWikiLink | local link = Wiki_Utility.renderWikiLink | ||
local NBSP = Wiki_Utility.NBSP | local NBSP = Wiki_Utility.NBSP | ||
| Line 131: | Line 138: | ||
--#endregion Dependencies | --#endregion Dependencies | ||
--#region Constants | --#region Constants | ||
| Line 137: | Line 146: | ||
--#endregion Constants | --#endregion Constants | ||
--#region Private Members | --#region Private Members | ||
| Line 142: | Line 153: | ||
---@type table<BuildingID, Building> | ---@type table<BuildingID, Building> | ||
local buildingData | local buildingData | ||
---@type table< | ---@type table<BuildingName, BuildingID> | ||
local mapNamesToIDs | local mapNamesToIDs | ||
| Line 166: | Line 177: | ||
-- Finds a building by its display name. | -- Finds a building by its display name. | ||
---@param buildingName | ---@param buildingName BuildingName display name | ||
---@return Building|nil foundBuilding | ---@return Building|nil foundBuilding | ||
local function findName(buildingName) | local function findName(buildingName) | ||
data() | data() | ||
local foundID = mapNamesToIDs[buildingName] | local foundID = mapNamesToIDs[buildingName] | ||
return foundID and buildingData[foundID] | return foundID and buildingData[foundID] or nil | ||
end | end | ||
| Line 178: | Line 189: | ||
-- If the specified iconSize is too small, the icon will be ommitted instead of drawn so small as to be unrecognizable. | -- If the specified iconSize is too small, the icon will be ommitted instead of drawn so small as to be unrecognizable. | ||
---@param building Building must not be nil | ---@param building Building must not be nil | ||
---@param iconSize string size of the icon including any units, e.g., `20em` or `x16px` or assumes `px` if no units | ---@param iconSize string|nil size of the icon including any units, e.g., `20em` or `x16px` or assumes `px` if no units, or nil if not relevant | ||
---@param needsIcon boolean | ---@param needsIcon boolean | ||
---@param needsText boolean | ---@param needsText boolean | ||
---@param needsPlural boolean|nil or nil for default (none) | |||
---@param pluralForm string|nil the plural form specified by the author, or nil if `needsPlural` is false | |||
---@return Wikitext wikitext | ---@return Wikitext wikitext | ||
function | local function buildingLink(building, iconSize, needsIcon, needsText, needsPlural, pluralForm) | ||
local wikitext = "" | local wikitext = "" | ||
local isValidSize, sizeN = isValidIconSize(iconSize) | local isValidSize, sizeN = isValidIconSize(iconSize) | ||
if needsIcon and isValidSize and sizeN >= MIN_ICON_SIZE then | if needsIcon and isValidSize and sizeN >= MIN_ICON_SIZE then | ||
-- Make it a height if it's not already. | -- Make it a height if it's not already. | ||
if not iconSize:match("^x") then | if iconSize and not iconSize:match("^x") then | ||
iconSize = "x" .. iconSize | iconSize = "x" .. iconSize | ||
end | end | ||
wikitext = wikitext .. | wikitext = wikitext .. wrapClasses(icon(building._iconFilename, iconSize, building._displayName, building._displayName), "ats-link-building", sizeN and sizeN < 23 and "ats-flag-small" or nil) | ||
end | end | ||
if (needsIcon and isValidSize and sizeN >= MIN_ICON_SIZE) and needsText then | if (needsIcon and isValidSize and sizeN >= MIN_ICON_SIZE) and needsText then | ||
| Line 196: | Line 209: | ||
end | end | ||
if needsText then | if needsText then | ||
wikitext = wikitext .. link(building._displayName, | local displayName = building._displayName | ||
if needsPlural then | |||
if pluralForm == "s" or pluralForm == "es" then | |||
displayName = displayName .. pluralForm | |||
else | |||
displayName = displayName:gsub("y$", "ies") | |||
end | |||
end | |||
wikitext = wikitext .. link(building._displayName, displayName) | |||
end | end | ||
return nowrap(wikitext) | return nowrap(wikitext) | ||
end | |||
-- Increments recipe counters in compileRecipeBook | |||
---@param recipe Recipe | |||
---@param recipeCount integer | |||
---@param maxIngredients integer | |||
---@param numBuildings integer | |||
---@return integer recipeCount + 1 | |||
---@return integer maxIngredients across all recipes so far | |||
---@return integer numBuildings + 1 | |||
local function addToRecipeCounts(recipe, recipeCount, maxIngredients, numBuildings) | |||
recipeCount = recipeCount + 1 | |||
maxIngredients = math.max(maxIngredients, Recipe.getNumIngredients(recipe)) | |||
numBuildings = numBuildings + 1 | |||
return recipeCount, maxIngredients, numBuildings | |||
end | end | ||
--#endregion Private Members | --#endregion Private Members | ||
--#region Public Methods | --#region Public Methods | ||
-- Checks if the given ID is a valid building ID. | |||
---@param id BuildingID | |||
---@return boolean | |||
function Building.isBuilding(id) | |||
return data()[id] ~= nil | |||
end | |||
-- Finds a building's ID by its display name. | |||
---@param displayName BuildingName | |||
---@return BuildingID|nil | |||
function Building.getID(displayName) | |||
data() | |||
return mapNamesToIDs[displayName] | |||
end | |||
-- Gets the specified building's display name. | |||
---@param id BuildingID | |||
---@return BuildingName | |||
function Building.getName(id) | |||
return data()[id]._displayName | |||
end | |||
-- Renders a table of building links. | |||
---@param buildingIDs BuildingID[] array of building IDs | |||
---@param iconSize string|nil size of the icon including any units, e.g., `20em` or `x16px` or assumes `px` if no units, or nil if not relevant | |||
---@vararg string|nil additional classes to add to the table, if any | |||
---@return Wikitext wikitext | |||
function Building.tableStack(buildingIDs, iconSize, ...) | |||
local classes = {...} | |||
local wikitext = "{|" .. ((#classes > 0) and ("class=" .. table.concat(classes, " ")) or "") .. "\n" | |||
for _, buildingID in ipairs(buildingIDs) do | |||
local building = data()[buildingID] | |||
if not building then return "[Building table stack ID not found: " .. buildingID .. "]" end | |||
wikitext = wikitext .. "|-\n" | |||
wikitext = wikitext .. "| " .. buildingLink(building, iconSize or standards.medium, true, true) .. "\n" | |||
end | |||
return wikitext .. "|}" | |||
end | |||
-- Retrieves a complete listing of recipes filtered down to those with the required product, building, and ingredient.<br> | |||
-- If any of the required arguments are nil, then all recipes that match the other arguments will be returned. | |||
---@param requiredProductID ProductID|nil the ID of the product, or nil if any | |||
---@param requiredBuildingID BuildingID|nil the ID of the building, or nil if any | |||
---@param requiredIngredientID ResourceID|nil the ID of the ingredient, or nil if any | |||
---@return RecipeBook recipeBook | |||
---@return integer recipeCount number of recipes in the RecipeBook | |||
---@return integer maxIngredients number of ingredients slots required to represent them all | |||
---@return integer numBuildings total number of buildings that can make recipes | |||
function Building.compileRecipeBook(requiredProductID, requiredBuildingID, requiredIngredientID) | |||
data() | |||
---@type RecipeBook | |||
local recipeBook = {} | |||
local recipeCount = 0 | |||
local maxIngredients = 0 | |||
local numBuildings = 0 | |||
for buildingID, building in pairs(buildingData) do | |||
-- if no filter on buildings OR match, proceed to productID | |||
if not requiredBuildingID or buildingID == requiredBuildingID then | |||
for productID, recipeListByGrade in pairs(building._recipes) do | |||
-- if no filter on product OR match, proceed to grade, stacksize, and ingredients | |||
if not requiredProductID or productID == requiredProductID then | |||
for grade, recipesByStacksize in pairs(recipeListByGrade) do | |||
for stacksize, recipe in pairs(recipesByStacksize) do | |||
-- if no filter on ingredient OR match, proceed to recipes | |||
if not requiredIngredientID or Recipe.isIngredientInOptions(recipe, requiredIngredientID) then | |||
-- add to book if it's the first for this product | |||
if recipeBook[productID] == nil then | |||
recipeBook[productID] = { [grade] = { [stacksize] = copy(recipe) } } | |||
recipeCount, maxIngredients, numBuildings = addToRecipeCounts(recipe, recipeCount, maxIngredients, numBuildings) | |||
else | |||
-- add to grade if the product's already there | |||
if recipeBook[productID][grade] == nil then | |||
recipeBook[productID][grade] = { [stacksize] = copy(recipe) } | |||
recipeCount, maxIngredients, numBuildings = addToRecipeCounts(recipe, recipeCount, maxIngredients, numBuildings) | |||
else | |||
-- add to stacksize if the product and grade are already there too | |||
if recipeBook[productID][grade][stacksize] == nil then | |||
recipeBook[productID][grade][stacksize] = copy(recipe) | |||
recipeCount, maxIngredients, numBuildings = addToRecipeCounts(recipe, recipeCount, maxIngredients, numBuildings) | |||
else | |||
-- add the building if the product, grade, and stacksize are already there | |||
Recipe.addBuilding(recipeBook[productID][grade][stacksize], buildingID) | |||
recipeCount, maxIngredients, numBuildings = addToRecipeCounts(recipe, recipeCount, maxIngredients, numBuildings) | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | |||
return recipeBook, recipeCount, maxIngredients, numBuildings | |||
end | |||
-- Renders a link to a building using its ID for when that's all you have. | |||
---@param buildingID BuildingID | |||
---@param iconSize string|nil size of the icon including any units, e.g., `20em` or `x16px` or assumes `px` if no units, or nil for default | |||
---@param needsIcon boolean | |||
---@param needsText boolean | |||
---@return Wikitext wikitext | |||
function Building.buildingLinkByID(buildingID, iconSize, needsIcon, needsText) | |||
return buildingLink(data()[buildingID], iconSize, needsIcon, needsText, false, nil) | |||
end | |||
-- Looks at the arguments to the original template to see if the author asked for a plural form. | |||
---@param frame Frame MediaWiki template context | |||
---@return boolean needsPlural | |||
---@return string|nil pluralForm the plural form specified by the author | |||
local function checkPlural(frame) | |||
if not frame:getParent() then return false end | |||
local parentArgs = frame:getParent().args | |||
for _, arg in pairs(parentArgs) do | |||
-- All we need is whether one of them is exactly "s" or "es" or "ies". | |||
if arg == "s" then return true, arg end | |||
if arg == "es" then return true, arg end | |||
if arg == "ies" then return true, arg end | |||
end | |||
return false | |||
end | |||
-- Building_link template invokes this method from MediaWiki. | -- Building_link template invokes this method from MediaWiki. | ||
| Line 215: | Line 375: | ||
local iconSize = standards[frame.args.icon] or frame.args.icon or "" | local iconSize = standards[frame.args.icon] or frame.args.icon or "" | ||
local needsIcon = iconSize ~= "none" | local needsIcon = iconSize ~= "none" | ||
-- If one of these was assigned into icon arg it was meant as the plural arg. | |||
-- Unset needsIcon because buildings get no icon by default. | |||
if iconSize == "s" or iconSize == "es" or iconSize == "ies" then | |||
needsIcon = false | |||
end | |||
-- Check if the size string is valid and doesn't represent a negative or too-large number. | -- Check if the size string is valid and doesn't represent a negative or too-large number. | ||
if needsIcon and not isValidIconSize(iconSize) then | if needsIcon and not isValidIconSize(iconSize) then | ||
| Line 224: | Line 389: | ||
end | end | ||
local needsText = display ~= "notext" | local needsText = display ~= "notext" | ||
local needsPlural, pluralForm = checkPlural(frame) | |||
local building = findName(name) | local building = findName(name) | ||
if not building then | if not building then | ||
return "[Building not found: " .. name .. "]" | return "[Building not found: " .. name .. "]" | ||
end | end | ||
return | return buildingLink(building, iconSize, needsIcon, needsText, needsPlural, pluralForm) | ||
end | end | ||
--#endregion Public Methods | --#endregion Public Methods | ||
return Building | return Building | ||
Latest revision as of 02:10, 12 November 2025
Documentation for this module may be created at Module:Building/doc
-- Provides a standard way of interacting with common building data.<br>
-- This means that every building *must* contain the underlying data accessed by these getters, and getting anything specific to the different kinds of buildings is not available from this module.<br>
local Building = {}
-- A single building with generic attributes like ID, name, category, etc.
---@class Building
---@field _id BuildingID Unique ID code of the building.
---@field _displayName BuildingName The display name in-game.
---@field _description string The in-game descriptive text, including sprite icons, newlines, and escape characters.
---@field _iconFilename Filename The file name of the icon, including its extension.
---@field _category CategoryConstruction Construction type from the construction toolbar, or non-buildable type.
---@field _categoryDisplay CategoryDisplay Type of building displayed in the in-game info box.
---@field _sizeX integer Width of the building.
---@field _sizeY integer Length of the building.
---@field _constructionCost RequiredGoodPair[] List of goods required for construction (array may be empty if it's free).
---@field _constructionTime integer Seconds.
---@field _cityScore integer Points awarded for each building in the settlement.
---@field _isMovable boolean `true` if the building can be moved after being built.
---@field _isInitiallyEssential boolean `false` for everything that isn't unlocked on a new profile, which is almost everything.
---@field _workerCapacity integer Max number of workers (may be zero).
---@field _tags Specialization[] List of any specializations (array may be empty).
---@field _storage? integer Number of goods that can be temporarily stored, if any, before any perks.
---@field _waterUsed? WaterID Type of water used in the building, if any.
---@field _levels? Level[] List of upgrades, if any.
---@field _recipes? RecipeBook Recipes available, if any, organized by product, grade, and stack size.
-- A unique identifier for a building.
---@alias BuildingID string
-- The display name of a building.
---@alias BuildingName string
-- An ID and amount of a good (e.g., for construction or upgrades).
---@alias RequiredGoodPair {_id: ResourceID, _amount: Amount}
-- An ID of a water type.
---@alias WaterID ResourceID
-- An upgrade level for a building.
---@alias Level {_upgradeCostOptions: UpgradeCost[], _upgrades: UpgradeID[]}
-- An ID of an upgrade.
---@alias UpgradeID string
-- One payment option for an upgrade (player chooses one from multiple UpgradeCosts).
---@alias UpgradeCost RequiredGoodPair[]
-- The construction category of the building from the construction toolbar.<br>
-- This is a complete list for coding/IDE purposes, because the `Building` interface provides access to the category field.<br>
-- However, the `Building` interface *does not* provides access to data for buildings of all categories.
---@enum CategoryConstruction
local CATEGORY_CONSTRUCTION = {
-- Buildings with recipes and workers
City = "City Buildings",
Food = "Food Production",
Industry = "Industry",
Resource = "Resource Acquisition",
-- Buildings without recipes
Decorations = "Decorations",
Housing = "Housing",
Roads = "Roads",
-- Non-buildable
Glade = "Glade Event",
Lore1 = "Lore Tablet I",
Lore2 = "Lore Tablet II",
Lore3 = "Lore Tablet III",
Lore4 = "Lore Tablet IV",
Lore5 = "Lore Tablet V",
Lore6 = "Lore Tablet VI",
Lore7 = "Lore Tablet VII",
Relic = "Ancient Relic",
}
-- The type of building displayed in the game in the info box.<br>
-- These are assigned based on which data file the building is loaded from, by its respective preprocessor script.
---@enum CategoryDisplay
local CATEGORY_DISPLAY = { -- for all buildings from...
"Decoration", -- decorations
"Extraction Building", -- extractors, rain catchers
"Farm", -- farms
"Fishing Hut", -- fishing huts
"Gathering Camp", -- camps, gathering huts
"Hearth", -- hearths
"Production Building", -- workshops
"Service Building", -- institutions
"Storage Building" -- storages
}
-- The affinity of the building for different species. Probably should be separated into its own module.
---@enum Specialization
local SPECIALIZATION = {
Alchemy = "Alchemy",
Animals = "Animals",
Brewing = "Brewing",
Cooperation = "FoxesCooperation",
Engineering = "Tech",
Farming = "Farming",
Forest = "Forest",
Masonry = "Stone",
Metallurgy = "Metallurgy",
Rainwater = "Rainwater",
Tailoring = "Cloth",
Warmth = "Warmth",
Woodworking = "Woodworking",
}
--#region Dependencies
-- List of data filenames, in order of likelihood of use.
local BUILDING_DATA_FILES = {
"Module:Workshop/workshops_data",
"Module:ServiceBuilding/service_buildings_data",
"Module:Farm/farms_data",
"Module:Camp/gathering_huts_data",
"Module:Camp/camps_data",
"Module:House/houses_data",
"Module:FishingHut/fishing_huts_data",
"Module:Mine/mines_data",
"Module:BlightPost/blight_posts_data",
"Module:RainCollector/rain_collectors_data",
"Module:Pump/pumps_data",
"Module:Hearth/hearths_data",
"Module:Warehouse/warehouses_data",
"Module:Farm/farm_fields_data"
}
local Recipe = require("Module:Recipe")
local copy = Recipe.copy
local Wiki_Utility = require("Module:Wiki_Utility")
local icon = Wiki_Utility.renderIcon
local nowrap = Wiki_Utility.surroundWithNoWrap
local wrapClasses = Wiki_Utility.surroundWithClasses
local link = Wiki_Utility.renderWikiLink
local NBSP = Wiki_Utility.NBSP
local standards = Wiki_Utility.StandardizedSizes
local isValidIconSize = Wiki_Utility.isValidIconSize
--#endregion Dependencies
--#region Constants
local MIN_ICON_SIZE = 20
--#endregion Constants
--#region Private Members
---@type table<BuildingID, Building>
local buildingData
---@type table<BuildingName, BuildingID>
local mapNamesToIDs
-- Loads the building data from the data files in sequence.<br>
-- Also populates the map of display names to IDs.<br>
-- I attempted to do this by lazy loading, but it would have required stateful code that would have been a lot more difficult to understand, troubleshoot, and maintain. If there is a noticeable performance issue, that would be a good first place to start.
---@return table<BuildingID, Building> buildingData
---@return table<string, BuildingID> mapNamesToIDs
local function data()
if not buildingData then
buildingData = {}
mapNamesToIDs = {}
for _, datafile in ipairs(BUILDING_DATA_FILES) do
local dataTable = mw.loadData(datafile)
for id, building in pairs(dataTable) do
buildingData[id] = building
mapNamesToIDs[building._displayName] = id
end
end
end
return buildingData, mapNamesToIDs
end
-- Finds a building by its display name.
---@param buildingName BuildingName display name
---@return Building|nil foundBuilding
local function findName(buildingName)
data()
local foundID = mapNamesToIDs[buildingName]
return foundID and buildingData[foundID] or nil
end
-- Renders a link to the given building's wiki page.<br>
-- The size of the icon will be used as the *height* of the icon.<br>
-- If the specified iconSize is too small, the icon will be ommitted instead of drawn so small as to be unrecognizable.
---@param building Building must not be nil
---@param iconSize string|nil size of the icon including any units, e.g., `20em` or `x16px` or assumes `px` if no units, or nil if not relevant
---@param needsIcon boolean
---@param needsText boolean
---@param needsPlural boolean|nil or nil for default (none)
---@param pluralForm string|nil the plural form specified by the author, or nil if `needsPlural` is false
---@return Wikitext wikitext
local function buildingLink(building, iconSize, needsIcon, needsText, needsPlural, pluralForm)
local wikitext = ""
local isValidSize, sizeN = isValidIconSize(iconSize)
if needsIcon and isValidSize and sizeN >= MIN_ICON_SIZE then
-- Make it a height if it's not already.
if iconSize and not iconSize:match("^x") then
iconSize = "x" .. iconSize
end
wikitext = wikitext .. wrapClasses(icon(building._iconFilename, iconSize, building._displayName, building._displayName), "ats-link-building", sizeN and sizeN < 23 and "ats-flag-small" or nil)
end
if (needsIcon and isValidSize and sizeN >= MIN_ICON_SIZE) and needsText then
wikitext = wikitext .. NBSP
end
if needsText then
local displayName = building._displayName
if needsPlural then
if pluralForm == "s" or pluralForm == "es" then
displayName = displayName .. pluralForm
else
displayName = displayName:gsub("y$", "ies")
end
end
wikitext = wikitext .. link(building._displayName, displayName)
end
return nowrap(wikitext)
end
-- Increments recipe counters in compileRecipeBook
---@param recipe Recipe
---@param recipeCount integer
---@param maxIngredients integer
---@param numBuildings integer
---@return integer recipeCount + 1
---@return integer maxIngredients across all recipes so far
---@return integer numBuildings + 1
local function addToRecipeCounts(recipe, recipeCount, maxIngredients, numBuildings)
recipeCount = recipeCount + 1
maxIngredients = math.max(maxIngredients, Recipe.getNumIngredients(recipe))
numBuildings = numBuildings + 1
return recipeCount, maxIngredients, numBuildings
end
--#endregion Private Members
--#region Public Methods
-- Checks if the given ID is a valid building ID.
---@param id BuildingID
---@return boolean
function Building.isBuilding(id)
return data()[id] ~= nil
end
-- Finds a building's ID by its display name.
---@param displayName BuildingName
---@return BuildingID|nil
function Building.getID(displayName)
data()
return mapNamesToIDs[displayName]
end
-- Gets the specified building's display name.
---@param id BuildingID
---@return BuildingName
function Building.getName(id)
return data()[id]._displayName
end
-- Renders a table of building links.
---@param buildingIDs BuildingID[] array of building IDs
---@param iconSize string|nil size of the icon including any units, e.g., `20em` or `x16px` or assumes `px` if no units, or nil if not relevant
---@vararg string|nil additional classes to add to the table, if any
---@return Wikitext wikitext
function Building.tableStack(buildingIDs, iconSize, ...)
local classes = {...}
local wikitext = "{|" .. ((#classes > 0) and ("class=" .. table.concat(classes, " ")) or "") .. "\n"
for _, buildingID in ipairs(buildingIDs) do
local building = data()[buildingID]
if not building then return "[Building table stack ID not found: " .. buildingID .. "]" end
wikitext = wikitext .. "|-\n"
wikitext = wikitext .. "| " .. buildingLink(building, iconSize or standards.medium, true, true) .. "\n"
end
return wikitext .. "|}"
end
-- Retrieves a complete listing of recipes filtered down to those with the required product, building, and ingredient.<br>
-- If any of the required arguments are nil, then all recipes that match the other arguments will be returned.
---@param requiredProductID ProductID|nil the ID of the product, or nil if any
---@param requiredBuildingID BuildingID|nil the ID of the building, or nil if any
---@param requiredIngredientID ResourceID|nil the ID of the ingredient, or nil if any
---@return RecipeBook recipeBook
---@return integer recipeCount number of recipes in the RecipeBook
---@return integer maxIngredients number of ingredients slots required to represent them all
---@return integer numBuildings total number of buildings that can make recipes
function Building.compileRecipeBook(requiredProductID, requiredBuildingID, requiredIngredientID)
data()
---@type RecipeBook
local recipeBook = {}
local recipeCount = 0
local maxIngredients = 0
local numBuildings = 0
for buildingID, building in pairs(buildingData) do
-- if no filter on buildings OR match, proceed to productID
if not requiredBuildingID or buildingID == requiredBuildingID then
for productID, recipeListByGrade in pairs(building._recipes) do
-- if no filter on product OR match, proceed to grade, stacksize, and ingredients
if not requiredProductID or productID == requiredProductID then
for grade, recipesByStacksize in pairs(recipeListByGrade) do
for stacksize, recipe in pairs(recipesByStacksize) do
-- if no filter on ingredient OR match, proceed to recipes
if not requiredIngredientID or Recipe.isIngredientInOptions(recipe, requiredIngredientID) then
-- add to book if it's the first for this product
if recipeBook[productID] == nil then
recipeBook[productID] = { [grade] = { [stacksize] = copy(recipe) } }
recipeCount, maxIngredients, numBuildings = addToRecipeCounts(recipe, recipeCount, maxIngredients, numBuildings)
else
-- add to grade if the product's already there
if recipeBook[productID][grade] == nil then
recipeBook[productID][grade] = { [stacksize] = copy(recipe) }
recipeCount, maxIngredients, numBuildings = addToRecipeCounts(recipe, recipeCount, maxIngredients, numBuildings)
else
-- add to stacksize if the product and grade are already there too
if recipeBook[productID][grade][stacksize] == nil then
recipeBook[productID][grade][stacksize] = copy(recipe)
recipeCount, maxIngredients, numBuildings = addToRecipeCounts(recipe, recipeCount, maxIngredients, numBuildings)
else
-- add the building if the product, grade, and stacksize are already there
Recipe.addBuilding(recipeBook[productID][grade][stacksize], buildingID)
recipeCount, maxIngredients, numBuildings = addToRecipeCounts(recipe, recipeCount, maxIngredients, numBuildings)
end
end
end
end
end
end
end
end
end
end
return recipeBook, recipeCount, maxIngredients, numBuildings
end
-- Renders a link to a building using its ID for when that's all you have.
---@param buildingID BuildingID
---@param iconSize string|nil size of the icon including any units, e.g., `20em` or `x16px` or assumes `px` if no units, or nil for default
---@param needsIcon boolean
---@param needsText boolean
---@return Wikitext wikitext
function Building.buildingLinkByID(buildingID, iconSize, needsIcon, needsText)
return buildingLink(data()[buildingID], iconSize, needsIcon, needsText, false, nil)
end
-- Looks at the arguments to the original template to see if the author asked for a plural form.
---@param frame Frame MediaWiki template context
---@return boolean needsPlural
---@return string|nil pluralForm the plural form specified by the author
local function checkPlural(frame)
if not frame:getParent() then return false end
local parentArgs = frame:getParent().args
for _, arg in pairs(parentArgs) do
-- All we need is whether one of them is exactly "s" or "es" or "ies".
if arg == "s" then return true, arg end
if arg == "es" then return true, arg end
if arg == "ies" then return true, arg end
end
return false
end
-- Building_link template invokes this method from MediaWiki.
---@param frame Frame MediaWiki template context
---@return Wikitext wikitext wikitext markup to link to the building's wiki page
function Building.link(frame)
local name = frame.args.name or ""
if name == "" then
return "[Building Link needs building name]"
end
local iconSize = standards[frame.args.icon] or frame.args.icon or ""
local needsIcon = iconSize ~= "none"
-- If one of these was assigned into icon arg it was meant as the plural arg.
-- Unset needsIcon because buildings get no icon by default.
if iconSize == "s" or iconSize == "es" or iconSize == "ies" then
needsIcon = false
end
-- Check if the size string is valid and doesn't represent a negative or too-large number.
if needsIcon and not isValidIconSize(iconSize) then
return "[Building Link size not valid: " .. iconSize .. "]"
end
local display = frame.args.display or ""
if display ~= "" and display ~= "notext" then
return "[Building Link display override not supported: " .. display .. "]"
end
local needsText = display ~= "notext"
local needsPlural, pluralForm = checkPlural(frame)
local building = findName(name)
if not building then
return "[Building not found: " .. name .. "]"
end
return buildingLink(building, iconSize, needsIcon, needsText, needsPlural, pluralForm)
end
--#endregion Public Methods
return Building


