Jump to content

Module:BuildingStats

From WarExpand Wiki
Revision as of 22:48, 21 August 2026 by Warmaninger (talk | contribs) (Import generated wiki content (units, buildings, fortifications, overview, categories))
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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