Module:BuildingStats
Appearance
Documentation for this module may be created at Module:BuildingStats/doc
-- Renders building statistics from [[Module:Data/Buildings]].
--
-- This module is hand-written, in the same style as Module:UnitStats.
-- Module:Data/Buildings is meant to be generated by scripts/export-wiki-data.cjs
-- (which only writes under the Module:Data/ prefix), so this file is never
-- overwritten by the export.
--
-- Entry points:
-- infobox - stat box for ONE level {{#invoke:BuildingStats|infobox|code=metal_mine|level=1}}
-- levels - comparison table, ALL levels {{#invoke:BuildingStats|levels|code=metal_mine}}
--
-- Expected shape of Module:Data/Buildings (field names follow the game's
-- building tier table: resource_output, unit_efficiency, maintenance_cost,
-- build_time, required_<resource>, upkeep_<resource>):
--
-- return {
-- metal_mine = {
-- name = 'Metal Mine',
-- kind = 'Resource Production', -- optional
-- produces = 'metal', -- optional, resource code
-- trains = 'Infantry', -- optional, free text
-- max_level = 5, -- optional
-- levels = {
-- [1] = {
-- output = 50, -- resource_output, per hour
-- efficiency = 1.0, -- unit_efficiency
-- maintenance = 10, -- maintenance_cost
-- build_time = '1h',
-- cost = { money = 100, metal = 0, concrete = 20, petrol = 0, uranium = 0 },
-- upkeep = { metal = 0, concrete = 0, petrol = 0, uranium = 0 },
-- },
-- [2] = { ... },
-- },
-- },
-- }
local p = {}
-- Module:Data/Buildings is absent until the export has run; fail soft instead
-- of printing a Lua error on every building page.
local loaded, data = pcall( mw.loadData, 'Module:Data/Buildings' )
if not loaded then
data = nil
end
local PENDING = "''Stats not yet available.''"
local RESOURCES = { 'money', 'food', 'metal', 'concrete', 'petrol', 'uranium' }
-- "100 money, 20 concrete", or "free" when nothing is required.
local function formatResources( bundle )
if not bundle then
return nil
end
local parts = {}
for _, name in ipairs( RESOURCES ) do
local amount = bundle[name]
if amount and amount > 0 then
parts[#parts + 1] = string.format( '%s %s', amount, name )
end
end
if #parts == 0 then
return 'free'
end
return table.concat( parts, ', ' )
end
-- Emits nothing at all for a missing value, so empty rows never show up.
local function row( label, value )
if value == nil or value == '' then
return ''
end
return string.format( '|-\n! %s\n| %s\n', label, value )
end
-- "50 metal/h" when both are known, the bare resource otherwise.
local function producesOf( building, stats )
if not building.produces then
return nil
end
if stats and stats.output then
return string.format( '%s %s/h', stats.output, building.produces )
end
return building.produces
end
-- Level numbers of a building, sorted ascending. Works on mw.loadData tables
-- (which support pairs() but not the # operator) and tolerates gaps.
local function levelNumbers( building )
local nums = {}
for key in pairs( building.levels or {} ) do
local n = tonumber( key )
if n then
nums[#nums + 1] = n
end
end
table.sort( nums )
return nums
end
local function unknownBuilding( code )
return string.format(
'<strong class="error">Unknown building code: %s</strong>',
tostring( code )
)
end
--- Stat box for one level of one building.
-- Usage: {{#invoke:BuildingStats|infobox|code=metal_mine|level=1}}
function p.infobox( frame )
local code = frame.args.code
local level = tonumber( frame.args.level ) or 1
if not data then
return PENDING
end
local building = data[code]
if not building then
return unknownBuilding( code )
end
local stats = building.levels and building.levels[level]
if not stats then
return string.format(
'<strong class="error">%s has no level %s</strong>',
building.name, tostring( level )
)
end
local maxLevel = building.max_level or #levelNumbers( building )
local levelsLabel = nil
if maxLevel and maxLevel > 1 then
levelsLabel = string.format( '%d (see [[#Stats|all levels]])', maxLevel )
end
return table.concat( {
'{| class="wikitable" style="float:right; margin:0 0 1em 1em; min-width:18em"\n',
string.format( '|+ %s — level %d\n', building.name, level ),
row( 'Kind', building.kind ),
row( 'Max level', levelsLabel ),
row( 'Produces', producesOf( building, stats ) ),
row( 'Trains', building.trains ),
row( 'Unit efficiency', stats.efficiency ),
row( 'Maintenance', stats.maintenance ),
row( 'Build time', stats.build_time ),
row( 'Cost', formatResources( stats.cost ) ),
row( 'Upkeep', formatResources( stats.upkeep ) ),
'|}',
} )
end
-- Rows of the level comparison table: label + accessor. A row is skipped
-- entirely when no level has a value for it.
local LEVEL_ROWS = {
{ 'Output /h', function( s ) return s.output end },
{ 'Unit efficiency', function( s ) return s.efficiency end },
{ 'Maintenance', function( s ) return s.maintenance end },
{ 'Build time', function( s ) return s.build_time end },
{ 'Cost', function( s ) return formatResources( s.cost ) end },
{ 'Upkeep', function( s ) return formatResources( s.upkeep ) end },
}
--- Comparison table of every level of one building (levels as columns).
-- Usage: {{#invoke:BuildingStats|levels|code=metal_mine}}
function p.levels( frame )
local code = frame.args.code
if not data then
return PENDING
end
local building = data[code]
if not building then
return unknownBuilding( code )
end
local nums = levelNumbers( building )
if #nums == 0 then
return string.format(
'<strong class="error">%s has no level data</strong>', building.name
)
end
local out = {
'{| class="wikitable" style="text-align:center"\n',
string.format( '|+ %s — stats by level\n', building.name ),
'! Stat\n',
}
for _, n in ipairs( nums ) do
out[#out + 1] = string.format( '! Level %d\n', n )
end
for _, def in ipairs( LEVEL_ROWS ) do
local label, get = def[1], def[2]
local cells, any = {}, false
for _, n in ipairs( nums ) do
local value = get( building.levels[n] )
if value ~= nil and value ~= '' then
any = true
cells[#cells + 1] = string.format( '| %s\n', value )
else
cells[#cells + 1] = '| —\n'
end
end
if any then
out[#out + 1] = string.format( '|-\n! style="text-align:left" | %s\n', label )
out[#out + 1] = table.concat( cells )
end
end
out[#out + 1] = '|}'
return table.concat( out )
end
return p