Jump to content

Module:FortificationStats

From WarExpand Wiki

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

-- Renders fortification statistics from [[Module:Data/Fortifications]].
--
-- This module is hand-written, in the same style as Module:UnitStats and
-- Module:BuildingStats. Module:Data/Fortifications 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:FortificationStats|infobox|code=auto_turret|level=1}}
--   levels   - comparison table, ALL levels  {{#invoke:FortificationStats|levels|code=auto_turret}}
--
-- Expected shape of Module:Data/Fortifications (field names follow the game's
-- country fortification tier table: attack, defense, durability, range,
-- detection_chance, trade_tax_min_pct, trade_tax_max_pct, build_time,
-- required_<resource>, upkeep_<resource>):
--
--   return {
--     auto_turret = {
--       name      = 'Automatic Turret',
--       ammo      = 'Turret Belts',            -- optional
--       max_level = 5,                         -- optional
--       levels = {
--         [1] = {
--           attack        = 10,
--           defense       = 5,
--           durability    = 100,
--           range         = 5,                 -- km, optional
--           detection     = 0.25,              -- detection_chance, 0..1, optional (Strategic Radar)
--           trade_tax_min = 2,                 -- trade_tax_min_pct, optional (Trade Hub)
--           trade_tax_max = 10,                -- trade_tax_max_pct, optional (Trade Hub)
--           build_time    = '2h',
--           cost   = { money = 100, food = 0, metal = 0, concrete = 50, petrol = 0, uranium = 0 },
--           upkeep = { money = 5, metal = 0, concrete = 0, petrol = 0, uranium = 0 },
--         },
--         [2] = { ... },
--       },
--     },
--   }

local p = {}

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

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

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

-- "100 money, 50 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

-- "25%" from a 0..1 chance (also accepts a value already in percent).
local function percentOf( value )
	if value == nil then
		return nil
	end
	if value <= 1 then
		value = value * 100
	end
	return string.format( '%d%%', math.floor( value + 0.5 ) )
end

-- "2-10%" for the Trade Hub tax band.
local function taxBandOf( stats )
	local lo, hi = stats.trade_tax_min, stats.trade_tax_max
	if lo == nil and hi == nil then
		return nil
	end
	if lo ~= nil and hi ~= nil then
		return string.format( '%s-%s%%', lo, hi )
	end
	return string.format( '%s%%', lo or hi )
end

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

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

--- Stat box for one level of one fortification.
-- Usage: {{#invoke:FortificationStats|infobox|code=auto_turret|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 fort = data[code]
	if not fort then
		return unknownFort( code )
	end

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

	local maxLevel = fort.max_level or #levelNumbers( fort )
	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', fort.name, level ),
		row( 'Kind', 'Country fortification' ),
		row( 'Max level', levelsLabel ),
		row( 'Attack', stats.attack ),
		row( 'Defense', stats.defense ),
		row( 'Durability', stats.durability ),
		row( 'Range', stats.range ),
		row( 'Detection chance', percentOf( stats.detection ) ),
		row( 'Trade tax', taxBandOf( stats ) ),
		row( 'Ammunition', fort.ammo ),
		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 = {
	{ 'Attack',           function( s ) return s.attack end },
	{ 'Defense',          function( s ) return s.defense end },
	{ 'Durability',       function( s ) return s.durability end },
	{ 'Range',            function( s ) return s.range end },
	{ 'Detection chance', function( s ) return percentOf( s.detection ) end },
	{ 'Trade tax',        taxBandOf },
	{ '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 fortification (levels as columns).
-- Usage: {{#invoke:FortificationStats|levels|code=auto_turret}}
function p.levels( frame )
	local code = frame.args.code

	if not data then
		return PENDING
	end

	local fort = data[code]
	if not fort then
		return unknownFort( code )
	end

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

	local out = {
		'{| class="wikitable" style="text-align:center"\n',
		string.format( '|+ %s: stats by level\n', fort.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( fort.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