Module:UnitStats: Difference between revisions
Appearance
Seed from wiki/seed |
Warmaninger (talk | contribs) Import generated wiki content (units, buildings, fortifications, overview, categories) |
||
| (2 intermediate revisions by the same user not shown) | |||
| Line 4: | Line 4: | ||
-- scripts/export-wiki-data.cjs, which refuses to write anything outside the | -- scripts/export-wiki-data.cjs, which refuses to write anything outside the | ||
-- Module:Data/ prefix, so this file is never overwritten by the export. | -- 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 = {} | local p = {} | ||
local data = mw.loadData | -- 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 35: | Line 50: | ||
end | end | ||
return string.format( '|-\n! %s\n| %s\n', label, value ) | 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 | end | ||
| Line 42: | 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] | ||
if not unit then | if not unit then | ||
return | return unknownUnit( code ) | ||
end | end | ||
| Line 59: | Line 105: | ||
end | end | ||
local | local tierCount = #tierNumbers( unit ) | ||
local tiersLabel = nil | |||
if tierCount > 1 then | |||
tiersLabel = string.format( '%d (see [[#Stats|all tiers]])', tierCount ) | |||
end | |||
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 | string.format( '|+ %s (tier %d)\n', unit.name, tier ), | ||
row( 'Domain', unit.domain ), | row( 'Domain', unit.domain ), | ||
row( 'Tiers', tiersLabel ), | |||
row( 'Attack', stats.attack ), | row( 'Attack', stats.attack ), | ||
row( 'Defense', stats.defense ), | row( 'Defense', stats.defense ), | ||
| Line 70: | Line 121: | ||
row( 'Range', stats.range ), | row( 'Range', stats.range ), | ||
row( 'Volume', stats.volume ), | row( 'Volume', stats.volume ), | ||
row( 'Cargo capacity', | row( 'Cargo capacity', cargoOf( stats ) ), | ||
row( 'Build time', stats.build_time ), | row( 'Build time', stats.build_time ), | ||
row( 'Cost', formatResources( stats.cost ) ), | row( 'Cost', formatResources( stats.cost ) ), | ||
| Line 76: | Line 127: | ||
'|}', | '|}', | ||
} ) | } ) | ||
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 | end | ||
return p | return p | ||
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