Jump to content

Module:UnitStats: Difference between revisions

From WarExpand Wiki
Import generated wiki content (units, buildings, fortifications, overview, categories)
Import generated wiki content (units, buildings, fortifications, overview, categories)
 
(One intermediate revision by the same user not shown)
Line 15: Line 15:
local p = {}
local p = {}


local data = mw.loadData( 'Module:Data/Units' )
-- Module:Data/Units is absent until the export has run; fail soft instead
-- of printing a Lua error on every unit page.
local loaded, data = pcall( mw.loadData, 'Module:Data/Units' )
if not loaded then
data = nil
end
 
local PENDING = "''Stats not yet available.''"


local RESOURCES = { 'money', 'food', 'metal', 'petrol', 'uranium' }
local RESOURCES = { 'money', 'food', 'metal', 'petrol', 'uranium' }
Line 80: Line 87:
local code = frame.args.code
local code = frame.args.code
local tier = tonumber( frame.args.tier ) or 1
local tier = tonumber( frame.args.tier ) or 1
if not data then
return PENDING
end


local unit = data[code]
local unit = data[code]
Line 102: Line 113:
return table.concat( {
return table.concat( {
'{| class="wikitable" style="float:right; margin:0 0 1em 1em; min-width:18em"\n',
'{| class="wikitable" style="float:right; margin:0 0 1em 1em; min-width:18em"\n',
string.format( '|+ %s — tier %d\n', unit.name, tier ),
string.format( '|+ %s (tier %d)\n', unit.name, tier ),
row( 'Domain', unit.domain ),
row( 'Domain', unit.domain ),
row( 'Tiers', tiersLabel ),
row( 'Tiers', tiersLabel ),
Line 136: Line 147:
function p.tiers( frame )
function p.tiers( frame )
local code = frame.args.code
local code = frame.args.code
if not data then
return PENDING
end


local unit = data[code]
local unit = data[code]
Line 151: Line 166:
local out = {
local out = {
'{| class="wikitable" style="text-align:center"\n',
'{| class="wikitable" style="text-align:center"\n',
string.format( '|+ %s — stats by tier\n', unit.name ),
string.format( '|+ %s: stats by tier\n', unit.name ),
'! Stat\n',
'! Stat\n',
}
}
Line 167: Line 182:
cells[#cells + 1] = string.format( '| %s\n', value )
cells[#cells + 1] = string.format( '| %s\n', value )
else
else
cells[#cells + 1] = '| —\n'
cells[#cells + 1] = '| -\n'
end
end
end
end

Latest revision as of 22:48, 21 August 2026

Documentation for this module may be created at Module:UnitStats/doc

-- Renders unit statistics from [[Module:Data/Units]].
--
-- This module is hand-written. Module:Data/Units is generated by
-- scripts/export-wiki-data.cjs, which refuses to write anything outside the
-- Module:Data/ prefix, so this file is never overwritten by the export.
--
-- Entry points:
--   infobox  - stat box for ONE tier      {{#invoke:UnitStats|infobox|code=light_infantry|tier=1}}
--   tiers    - comparison table, ALL tiers {{#invoke:UnitStats|tiers|code=light_infantry}}
--
-- Both read the same fields from Module:Data/Units:
--   data[code] = { name, domain, tiers = { [1] = { attack, defense, speed,
--     range, volume, cargo_volume, build_time, cost = {...}, upkeep = {...} } } }

local p = {}

-- Module:Data/Units is absent until the export has run; fail soft instead
-- of printing a Lua error on every unit page.
local loaded, data = pcall( mw.loadData, 'Module:Data/Units' )
if not loaded then
	data = nil
end

local PENDING = "''Stats not yet available.''"

local RESOURCES = { 'money', 'food', 'metal', 'petrol', 'uranium' }

-- "100 money, 20 metal", or "free" when a unit costs nothing.
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

-- Cargo capacity is only meaningful when it is positive.
local function cargoOf( stats )
	local cargo = stats.cargo_volume or 0
	if cargo > 0 then
		return cargo
	end
	return nil
end

-- Tier numbers of a unit, sorted ascending. Works on mw.loadData tables
-- (which support pairs() but not the # operator) and tolerates gaps.
local function tierNumbers( unit )
	local nums = {}
	for key in pairs( unit.tiers or {} ) do
		local n = tonumber( key )
		if n then
			nums[#nums + 1] = n
		end
	end
	table.sort( nums )
	return nums
end

local function unknownUnit( code )
	return string.format(
		'<strong class="error">Unknown unit code: %s</strong>',
		tostring( code )
	)
end

--- Stat box for one tier of one unit.
-- Usage: {{#invoke:UnitStats|infobox|code=light_infantry|tier=1}}
function p.infobox( frame )
	local code = frame.args.code
	local tier = tonumber( frame.args.tier ) or 1

	if not data then
		return PENDING
	end

	local unit = data[code]
	if not unit then
		return unknownUnit( code )
	end

	local stats = unit.tiers[tier]
	if not stats then
		return string.format(
			'<strong class="error">%s has no tier %s</strong>',
			unit.name, tostring( tier )
		)
	end

	local tierCount = #tierNumbers( unit )
	local tiersLabel = nil
	if tierCount > 1 then
		tiersLabel = string.format( '%d (see [[#Stats|all tiers]])', tierCount )
	end

	return table.concat( {
		'{| class="wikitable" style="float:right; margin:0 0 1em 1em; min-width:18em"\n',
		string.format( '|+ %s (tier %d)\n', unit.name, tier ),
		row( 'Domain', unit.domain ),
		row( 'Tiers', tiersLabel ),
		row( 'Attack', stats.attack ),
		row( 'Defense', stats.defense ),
		row( 'Speed', stats.speed ),
		row( 'Range', stats.range ),
		row( 'Volume', stats.volume ),
		row( 'Cargo capacity', cargoOf( stats ) ),
		row( 'Build time', stats.build_time ),
		row( 'Cost', formatResources( stats.cost ) ),
		row( 'Upkeep', formatResources( stats.upkeep ) ),
		'|}',
	} )
end

-- Rows of the tier comparison table: label + accessor. A row is skipped
-- entirely when no tier has a value for it.
local TIER_ROWS = {
	{ 'Attack',         function( s ) return s.attack end },
	{ 'Defense',        function( s ) return s.defense end },
	{ 'Speed',          function( s ) return s.speed end },
	{ 'Range',          function( s ) return s.range end },
	{ 'Volume',         function( s ) return s.volume end },
	{ 'Cargo capacity', cargoOf },
	{ '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 tier of one unit (tiers as columns).
-- Usage: {{#invoke:UnitStats|tiers|code=light_infantry}}
function p.tiers( frame )
	local code = frame.args.code

	if not data then
		return PENDING
	end

	local unit = data[code]
	if not unit then
		return unknownUnit( code )
	end

	local nums = tierNumbers( unit )
	if #nums == 0 then
		return string.format(
			'<strong class="error">%s has no tier data</strong>', unit.name
		)
	end

	local out = {
		'{| class="wikitable" style="text-align:center"\n',
		string.format( '|+ %s: stats by tier\n', unit.name ),
		'! Stat\n',
	}
	for _, n in ipairs( nums ) do
		out[#out + 1] = string.format( '! Tier %d\n', n )
	end

	for _, def in ipairs( TIER_ROWS ) do
		local label, get = def[1], def[2]
		local cells, any = {}, false
		for _, n in ipairs( nums ) do
			local value = get( unit.tiers[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