Module:NationLister

From Terra Invicta Official Wiki

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

---
---Reads the Nation data files and presents them in readable formats, with additional calculated statistics.
---
---@module NationLister
local NationLister = {}

--region Dependencies
local IconModule = require('Module:Icon')
local TVModule = require('Module:TV')
local RegionData = mw.loadData('Module:CSVReader/Region')
local NationData = mw.loadData('Module:CSVReader/Nation')
local StartTimeData = mw.loadData('Module:CSVReader/StartTime')
local ClaimData = mw.loadData('Module:CSVReader/Claim')
local RegionArmyData = mw.loadData('Module:RegionLister/Armies')

local RegionNames = mw.loadData('Module:ENReader/Region')
local NationNames = mw.loadData('Module:ENReader/Nation')
--endregion

--region Private constants
local floor = math.floor
local mlog = math.log
local mmin = math.min
local mmax = math.max
local round = function(num, decimals)
    local mult = 10^(decimals or 0)
    return floor(num * mult + 0.5) / mult
end
local ssub = string.sub
--endregion

--region Private member variables
--none
--endregion

--region Private methods


--endregion

--region Public methods

function NationLister.calculateCPCost(GDP)
	return (GDP / 1000000000) ^ 0.6 / 2
end

function NationLister.calculateNumControlPoints(GDP)
	return mmin(6, mmax(1, round((GDP / 1000000000) ^ 0.18 / 1.09)))
end

---@return monthly research output for nation if unrest = 0 and there are no armies.
function NationLister.calculateIP(GDP)
	return (GDP / 1000000000) ^ 0.35
end


---@return monthly research output for nation if cohesion = 5 and unrest = 0.
function NationLister.calculateResearch(pop, gdppc, education, democracy, numControlPoints)
--float population_Millions = this.population_Millions;
--float perCapitaGDP = this.perCapitaGDP;
--return (population_Millions * ((perCapitaGDP <= 30000f) ? Mathf.Pow(this.perCapitaGDP / 15000f, 0.6f) : (1.5157166f + 0.90942997f * (Mathf.Log(this.perCapitaGDP / 15000f) - 0.6931472f))) * (this.education * Mathf.Min(this.education, 12f)) * Mathf.Pow(Mathf.Max(this.democracy, 1f), 0.16666667f) * 0.0075f + Mathf.Min(this.population / 5000f, (float)this.numControlPoints + this.education + this.democracy / 2f)) * (1.25f - Mathf.Abs(this.cohesion - 5f) / 10f) * (1f - this.unrest * this.unrest * 0.01f) * (1f + this.adviserScienceBonus);
	local gdppcMultiplier = 0
	if gdppc <= 30000 then
		gdppcMultiplier = (gdppc / 15000) ^ 0.6
	else
		gdppcMultiplier = 1.5157166 + 0.90942997 * (mlog(gdppc / 15000) - 0.6931472)
	end
	local educationMultiplier = education * mmin(education, 12)
	local democracyMultiplier = mmax(democracy, 1) ^ 0.16666667
	local baseValue = mmin(pop / 5000, numControlPoints + education + democracy / 2)
	return (pop / 1000000 * gdppcMultiplier * educationMultiplier * democracyMultiplier * 0.0075 + baseValue) * 1.25
end

---@return monthly spoils profit for nation if unrest = 0 and there are no armies and 100% of the IP is put into spoils.
function NationLister.calculateSpoils(IP, democracy, numResourceRegions)
	return (5 * IP + 5 * numResourceRegions + 2.5 * (10 - democracy)) * IP
end


function NationLister.ListPresentNations()
	return NationLister.ListNations('2022')
end

function NationLister.List2026Nations()
	return NationLister.ListNations('2026')
end

function NationLister.ListFutureNations()
	return NationLister.ListNations('2070')
end

---@param campaignYear The start year of the campaign to show nations for. Defaults to 2022 if not specified
---@return table
function NationLister.ListNations(campaignYear)
	if not campaignYear then
		campaignYear = '2022'
	end
	local frame = mw.getCurrentFrame()
	
	local returnString = '{| class="wikitable sortable mw-collapsible" style="text-align: center;'
	returnString = returnString .. '\n|-'
	returnString = returnString .. '\n! rowspan=2 | Nation<br>Alternate Name<br>(# Regions)'
	returnString = returnString .. '\n! rowspan=2 | [[GDP]]<br>'  .. IconModule.Icon({args={'cp'}}) .. '<br>' .. IconModule.Icon({args={'ip', bgcolor='DarkSlateGray'}})
	returnString = returnString .. '\n! rowspan=2 | ' .. IconModule.Icon({args={'pop'}}) .. '<br>(Mns)'
	returnString = returnString .. '\n! colspan=2 | ' .. IconModule.Icon({args={'research', bgcolor='DarkSlateGray'}}) .. ' per'
	returnString = returnString .. '\n! colspan=2 | ' .. IconModule.Icon({args={'spoils'}}) .. ' per'
	returnString = returnString .. '\n! rowspan=2 | ' .. IconModule.Icon({args={'inequality', bgcolor='DarkSlateGray'}}) .. '<br>' .. IconModule.Icon({args={'sustainability'}})
	returnString = returnString .. '\n! rowspan=2 | ' .. IconModule.Icon({args={'democracy', bgcolor='DarkSlateGray'}})
	returnString = returnString .. '\n! rowspan=2 | ' .. IconModule.Icon({args={'education'}})
	returnString = returnString .. '\n! rowspan=2 | ' .. IconModule.Icon({args={'miltech'}}) .. '<br>' .. IconModule.Icon({args={'army'}}) .. '<br>' .. IconModule.Icon({args={'navy'}}) .. '<br>' .. IconModule.Icon({args={'found military'}}) 
	returnString = returnString .. '\n! rowspan=2 | ' .. IconModule.Icon({args={'nuke'}}) .. '<br>' .. IconModule.Icon({args={'develop atomic bomb'}})
	returnString = returnString .. '\n! rowspan=2 | ' .. IconModule.Icon({args={'boost'}}) .. '<br>' .. IconModule.Icon({args={'mc'}}) .. '<br>' .. IconModule.Icon({args={'money'}}) .. '<br>' .. IconModule.Icon({args={'found space program'}})
	returnString = returnString .. '\n! rowspan=2 | ' .. IconModule.Icon({args={'cohesion', bgcolor='DarkSlateGray'}}) .. '<br>' .. IconModule.Icon({args={'unrest', bgcolor='DarkSlateGray'}})
	returnString = returnString .. '\n|-'
	
	returnString = returnString .. '\n! Month' 
	returnString = returnString .. '\n! '  .. IconModule.Icon({args={'cp'}})
	returnString = returnString .. '\n! Month' 
	returnString = returnString .. '\n! '  .. IconModule.Icon({args={'cp'}})
	returnString = returnString .. '\n|-'
	
	for rowID, row in pairs(NationData) do
		if ((string.sub(rowID,1,4) == campaignYear) or (campaignYear == '2022' and string.sub(rowID,1,2) ~= '20')) then
			local GDP = tonumber(row['initialGDP'] or 0) or 0 
			startTimeDataName = campaignYear .. 'Start'
			if startTimeDataName == '2022Start' then
				startTimeDataName = 'ModernDayStart'
			end
			GDP = GDP * (tonumber(StartTimeData[startTimeDataName]['globalStartingGDPScaling'] or 1) or 1)
			local education = tonumber(row['education'] or 0) or 0
			local regionCount = 0
			local regionPopulationInMns = 0
			local totalRegionGDPShares = 0
			local regionBoost = 0
			local regionMC = 0
			local regionMCCap = 0
			local regionResource = 0
			local regionArmy = 0
			local regionNavy = 0
			local claimList = ClaimData[1][rowID]
			if claimList then
				for i, regionID in ipairs(claimList) do
					if ClaimData[3][rowID][regionID]['initialOwner'] == 'true' and RegionData[regionID] then
						regionCount = regionCount + 1
						regionPopulationInMns = regionPopulationInMns + (tonumber(RegionData[regionID]['population_Millions']) or 0)
						local regionMultiplier = 1
						if RegionData[regionID]['oilResource'] == 'true' or RegionData[regionID]['mining'] == 'true' or RegionData[regionID]['coreEco'] == 'true'  then
							regionMultiplier = 1.25
						elseif ClaimData[3][rowID][regionID]['initialColony'] == 'true' then
							regionMultiplier = 0.5
						end
						totalRegionGDPShares = totalRegionGDPShares + (tonumber(RegionData[regionID]['population_Millions']) or 0) * regionMultiplier
						regionBoost = regionBoost + (tonumber(RegionData[regionID]['boostPerYear_tons']) or 0)
						regionMC = regionMC + (tonumber(RegionData[regionID]['missionControl']) or 0)
						if RegionData[regionID]['oilResource'] == 'true' or RegionData[regionID]['mining'] == 'true' then
							regionResource = regionResource + 1
						end
						if RegionArmyData[regionID] then
							regionArmy = regionArmy + 1
							if RegionArmyData[regionID] == 'Naval' then
								regionNavy = regionNavy + 1
							end
						end
					end
				end
				for i, regionID in ipairs(claimList) do
					if ClaimData[3][rowID][regionID]['initialOwner'] == 'true' and RegionData[regionID] then
						local regionMultiplier = 1
						if RegionData[regionID]['oilResource'] == 'true' or RegionData[regionID]['mining'] == 'true' or RegionData[regionID]['coreEco'] == 'true'  then
							regionMultiplier = 1.25
						elseif ClaimData[3][rowID][regionID]['initialColony'] == 'true' then
							regionMultiplier = 0.5
						end
						local regionShare = (tonumber(RegionData[regionID]['population_Millions']) or 0) * regionMultiplier
						local regionGDP = GDP * regionShare / totalRegionGDPShares
						regionMCCap = regionMCCap + 1 + floor(regionGDP / 1000000000 / mmax(200,300 - education * 6))
					end
				end
			end
			-- Convert boost to monthly decatons, and round to 2 decimals.
			regionBoost = round(regionBoost / 120, 2)
			
			local unionFlag = ''
			if row['unionFlagResource'] and  row['unionFlagResource'] ~= '' then
				unionFlag = IconModule.Icon({args={ssub(rowID,-3) .. 'U',size='25px'}})
			end
			returnString = returnString .. '\n|' .. IconModule.Icon({args={ssub(rowID,-3),size='25px'}}) ..  '[[' .. ssub(rowID,-3) .. '|' .. (NationNames['TINationTemplate.displayName.' .. ssub(rowID,-3)] or rowID) .. ']]<br>' .. unionFlag .. (NationNames['TINationTemplate.unionDisplayName.' .. ssub(rowID,-3)] or '') .. '<br>(' .. tostring(regionCount) .. ' Initial Regions)'
			if regionCount > 0 then
				-- This nation actually exists at the start of the scenario.
				local CPCost = NationLister.calculateCPCost(GDP)
				local IP = NationLister.calculateIP(GDP)
				returnString = returnString .. '\n| ' .. round(GDP / 1000000000) .. '<br>' .. TVModule.TV({args={'cp', round(CPCost, 1), 'true'}}) .. '<br>' .. TVModule.TV({args={'ip', round(IP, 2), 'true'}})
				returnString = returnString .. '\n| ' .. round(regionPopulationInMns, 2)
				local numControlPoints = NationLister.calculateNumControlPoints(GDP)
				local gdppc = GDP / regionPopulationInMns / 1000000
				local democracy = tonumber(row['democracy'] or 0) or 0
				local researchOutput = NationLister.calculateResearch(regionPopulationInMns * 1000000, gdppc, education, democracy, numControlPoints)
				returnString = returnString .. '\n| ' .. round(researchOutput, 1)
				returnString = returnString .. '\n| ' .. round(researchOutput / CPCost, 1)
				local spoilsOutput = NationLister.calculateSpoils(IP, democracy, regionResource)
				returnString = returnString .. '\n| ' .. round(spoilsOutput, 1)
				returnString = returnString .. '\n| ' .. round(spoilsOutput / CPCost, 1)
				
				returnString = returnString .. '\n| ' ..  TVModule.TV({args={'inequality', row['inequality'], 'true'}})
				
				local pollutionMultiplier = tonumber(row['greenEconomy'])
				local sustainability = ''
				if pollutionMultiplier == 0 then
					sustainability = '10'
				elseif (1/pollutionMultiplier) > 9.99 then
					sustainability = '9.99+'
				elseif (1/pollutionMultiplier) > 9.9 then
					sustainability = '9.9+'
				else
					sustainability = tostring(floor(1/pollutionMultiplier * 100 + 0.5) / 100)
				end
				returnString = returnString .. '<br>' .. TVModule.TV({args={'sustainability', sustainability, 'true'}})
				
				returnString = returnString .. '\n| ' .. democracy
				
				returnString = returnString .. '\n| ' .. education
				
				-- Null value for found military actually implies it has been founded.
				local foundMilitaryIPs = tonumber(row['foundMilitaryIPs'] or 1) or 1
				local manfSortValue = regionArmy * 10000 + (tonumber(row['miltech'] or 0) or 0) * 100 + regionNavy + foundMilitaryIPs * 1000000
				returnString = returnString .. '\n| data-sort-value=' .. manfSortValue .. '|' 
				
				returnString = returnString .. TVModule.TV({args={'miltech', row['miltech'], 'true'}})
				
				if regionArmy > 0 then
					returnString = returnString .. '<br>' .. TVModule.TV({args={'army', regionArmy, 'true'}})
				end
				if regionNavy > 0 then
					returnString = returnString .. '<br>' .. TVModule.TV({args={'navy', regionNavy, 'true'}})
				end
				
				returnString = returnString .. '<br>' .. (foundMilitaryIPs * 100) .. '%' .. IconModule.Icon({args={'found military'}})
				
				local nukes = tonumber(row['nuclearWeapons'] or 0) or 0
				returnString = returnString .. '\n| ' .. TVModule.TV({args={'nuke', nukes, 'true'}})
				if nukes == 0 and row['nuclearProgramIPs'] then
					returnString = returnString .. '<br>' .. (tonumber(row['nuclearProgramIPs']) * 100) .. '%' .. IconModule.Icon({args={'develop atomic bomb'}})
				end
				
				
				local funding = tonumber(row['spaceFunding_year']) or 0
				-- Convert funding to monthly, and round to 2 decimals.
				funding = round(funding / 12,2)
				local initSpaceIPs = tonumber(row['initSpaceIPs'] or 0) or 0
				
				local bmfsSortValue = regionBoost * 10000 + regionMC * 100 + funding + initSpaceIPs / 100
				
				returnString = returnString .. '\n| data-sort-value=' .. bmfsSortValue .. '|' 
				
				if regionBoost > 0 then
					returnString = returnString .. TVModule.TV({args={'boost', regionBoost, 'true'}}) .. '<br>'
				end
				
				returnString = returnString .. TVModule.TV({args={'mc', tostring(regionMC) .. '/' .. tostring(regionMCCap), 'true'}}) .. '<br>'
				
				if funding > 0 then
					returnString = returnString .. TVModule.TV({args={'money', funding, 'true'}}) .. '<br>'
				end
				
				returnString = returnString .. (initSpaceIPs * 100) .. '%' .. IconModule.Icon({args={'found space program'}})
				
				returnString = returnString .. '\n| ' .. TVModule.TV({args={'cohesion', row['cohesion'], 'true'}}) .. '<br>' .. TVModule.TV({args={'unrest', row['unrest'], 'true'}})
				
		    end
		    returnString = returnString .. '\n|-'
	    end
	end
	returnString = returnString .. '\n|}'
	return returnString
end

--endregion

return NationLister