Module:HabModuleLister/Planner

From Terra Invicta Official Wiki

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

---
---
---@module HabPlanner
local HabPlanner = {}

--region Dependencies
local IconModule = require('Module:Icon')
local TVModule = require('Module:TV')
local HmsModule = require('Module:HabModuleSpecial')
local HabModuleData = mw.loadData('Module:CSVReader/HabModule')
local HabModuleNames = mw.loadData('Module:ENReader/HabModule')
local ProjectNameBuilder = require('Module:ProjectLister/NameBuilder')
-- Science category names mapping (Note: These were converting in the wrong direction, I had to flip them around.)
local ScienceCategories = {
	Energy = 'energy',
	InformationScience='information',
	LifeScience='life',
	Materials='material',
	MilitaryScience='military',
	SocialScience='social',
	SpaceScience='space',
	Xenology='xenology'
}
--endregion

--region Private constants
local tinsert = table.insert
local tconcat = table.concat
local floor = math.floor
local round = function(num, decimals)
	local mult = 10^(decimals or 0)
	return floor(num * mult + 0.5) / mult
end
--endregion

--region Private methods
-- Safe value access with fallback with type conversion
local function getValue(row, key, default)
	local value = row[key]
	if value == nil or value == '' then
		return default or 0
	end
	
	-- Convert to number if it looks like a number
	if type(value) == 'string' and value:match('^[%-%d%.]+$') then
		return tonumber(value) or default or 0
	end
	
	-- Convert boolean strings
	if value == 'true' then return true end
	if value == 'false' then return false end
	
	return value
end

-- Calculate build cost for a resource
local function calculateBuildCost(mass, resourceWeight)
	return round(tonumber(mass) * tonumber(resourceWeight) / 10, 2)
end

-- Format resource with TV module
local function formatResource(amount, resourceType, showZero)
    amount = tonumber(amount) or 0
    if amount == 0 and not showZero then
        return ''
    end
    return TVModule.TV({args={resourceType, amount, 'true'}})
end

-- Format special rules
local function formatSpecialRules(row)
	local specialRules = {}
	
	-- Add special rules from array (using correct field names from CSV)
	local shipbuilder = false
	for i = 0, 5 do
		local rule = getValue(row, 'specialRules/' .. i)
		if rule and rule ~= '' and rule ~= 0 then
			tinsert(specialRules, "* " .. HmsModule.hms({args={rule, tier = getValue(row, 'tier'), constructionTimeModifier = getValue(row, 'constructionTimeModifier'), specialRulesValue = getValue(row, 'specialRulesValue'), techBonus = getValue(row, 'techBonuses/0/bonus'), techCat = (ScienceCategories[getValue(row, 'techBonuses/0/category')] or getValue(row, 'techBonuses/0/category') or ''), controlPointCapacity = getValue(row, 'controlPointCapacity')}}))
			
			if rule == 'Shipyard' then
				shipbuilder = true
			end
		end
	end
	if not shipbuilder and getValue(row, 'constructionTimeModifier') ~= 1 then
		tinsert(specialRules, "* " .. HmsModule.hms({args={'WikiRuleConstruction', tier = getValue(row, 'tier'), constructionTimeModifier = getValue(row, 'constructionTimeModifier'), specialRulesValue = getValue(row, 'specialRulesValue'), techBonus = getValue(row, 'techBonuses/0/bonus'), techCat = (ScienceCategories[getValue(row, 'techBonuses/0/category')] or getValue(row, 'techBonuses/0/category') or ''), controlPointCapacity = getValue(row, 'controlPointCapacity')}}))
	end
	
	-- Add one per hab restriction
	if getValue(row, 'onePerHab') == true or getValue(row, 'onePerHab') == 'true' then
		tinsert(specialRules, "* Max one per [[Habs|Hab]].")
	end
	
	-- Add resupply capability
	if getValue(row, 'allowsResupply') == true or getValue(row, 'allowsResupply') == 'true' then
		tinsert(specialRules, "* Allows [[Spaceships|Ships]] to resupply.")
	end
	
	return "\n" .. tconcat(specialRules, "\n")
end


-- Safe number comparison function
local function isGreaterThanZero(value)
	local num = tonumber(value) or 0
	return num > 0
end

-- Safe negative number check
local function isLessThanZero(value)
	local num = tonumber(value) or 0
	return num < 0
end
--endregion

--region Public methods
function HabPlanner.ShowPlan(frame)
	
	local returnString = '{| class="wikitable sortable mw-collapsible" style="width:100%;text-align:center;"'
	returnString = returnString .. '\n|-'
	returnString = returnString .. '\n! Hab Module'
	returnString = returnString .. '\n! Number'
	returnString = returnString .. '\n! Build Cost'
	returnString = returnString .. '\n! Monthly Upkeep'
	returnString = returnString .. '\n! Monthly Income'
	returnString = returnString .. '\n! Special'
	returnString = returnString .. '\n|-'
	
	local totalModuleNumber = 0
	local totalBuildCosts = {
		mass = 0,
		water = 0,
		volatiles = 0,
		metals = 0,
		nobles = 0,
		fissiles = 0,
		exotics = 0,
		antimatter = 0,
	}
	local totalMonthlyUpkeep = {
		money = 0,
		water = 0,
		volatiles = 0,
		metals = 0,
		nobles = 0,
		fissiles = 0,
		mc = 0,
		boost = 0,
	}
	local totalPower = 0
	local totalCrew = 0
	local totalFarm = 0
	local totalUnpaidCrew = 0
	local totalEfficiency = 1
	local totalMonthlyIncome = {
		money = 0,
		water = 0,
		volatiles = 0,
		metals = 0,
		nobles = 0,
		fissiles = 0,
		exotics = 0,
		antimatter = 0,
		mc = 0,
		research = 0,
		projects = 0,
		influence = 0,
		ops = 0,
	}

	for rowID, row in pairs(HabModuleData) do
		if frame.args[rowID] and tonumber(frame.args[rowID]) and tonumber(frame.args[rowID]) > 0 then
			local moduleNumber = tonumber(frame.args[rowID])
			totalModuleNumber = totalModuleNumber + moduleNumber
			local moduleName = HabModuleNames['TIHabModuleTemplate.displayName.' .. rowID] or getValue(row, 'friendlyName') or rowID
			
			-- Module name column
			returnString = returnString .. '\n| ' .. moduleName
			returnString = returnString .. '\n| ' .. moduleNumber
			
			-- Build cost column
			local mass = getValue(row, 'baseMass_tons', 0)
			totalBuildCosts['mass'] = totalBuildCosts['mass'] + mass * moduleNumber
			local buildCosts = {}
			
			local waterCost = calculateBuildCost(mass, getValue(row, 'weightedBuildMaterials/water'))
			if waterCost > 0 then
				totalBuildCosts['water'] = totalBuildCosts['water'] + waterCost * moduleNumber
				tinsert(buildCosts, formatResource(waterCost, 'water', true))
			end
			
			local volatilesCost = calculateBuildCost(mass, getValue(row, 'weightedBuildMaterials/volatiles'))
			if volatilesCost > 0 then
				totalBuildCosts['volatiles'] = totalBuildCosts['volatiles'] + volatilesCost * moduleNumber
				tinsert(buildCosts, formatResource(volatilesCost, 'volatiles', true))
			end
			
			local metalsCost = calculateBuildCost(mass, getValue(row, 'weightedBuildMaterials/metals'))
			if metalsCost > 0 then
				totalBuildCosts['metals'] = totalBuildCosts['metals'] + metalsCost * moduleNumber
				tinsert(buildCosts, formatResource(metalsCost, 'metals', true))
			end
			
			local noblesCost = calculateBuildCost(mass, getValue(row, 'weightedBuildMaterials/nobleMetals'))
			if noblesCost > 0 then
				totalBuildCosts['nobles'] = totalBuildCosts['nobles'] + noblesCost * moduleNumber
				tinsert(buildCosts, formatResource(noblesCost, 'nobles', true))
			end
			
			local fissilesCost = calculateBuildCost(mass, getValue(row, 'weightedBuildMaterials/fissiles'))
			if fissilesCost > 0 then
				totalBuildCosts['fissiles'] = totalBuildCosts['fissiles'] + fissilesCost * moduleNumber
				tinsert(buildCosts, formatResource(fissilesCost, 'fissiles', true))
			end
			
			local exoticsCost = calculateBuildCost(mass, getValue(row, 'weightedBuildMaterials/exotics'))
			if exoticsCost > 0 then
				totalBuildCosts['exotics'] = totalBuildCosts['exotics'] + exoticsCost * moduleNumber
				tinsert(buildCosts, formatResource(exoticsCost, 'exotics', true))
			end
			
			local antimatterCost = calculateBuildCost(mass, getValue(row, 'weightbuildmaterials/antimatter'))
			if antimatterCost > 0 then
				totalBuildCosts['antimatter'] = totalBuildCosts['antimatter'] + antimatterCost * moduleNumber
				tinsert(buildCosts, formatResource(antimatterCost, 'antimatter', true))
			end
			
			returnString = returnString .. '\n| ' .. tconcat(buildCosts, ' ')
			
			-- Monthly upkeep column
			local upkeepCosts = {}
			
			local moneyUpkeep = getValue(row, 'supportMaterials_month/money')
			if isGreaterThanZero(moneyUpkeep) then
				totalMonthlyUpkeep['money'] = totalMonthlyUpkeep['money'] + moneyUpkeep * moduleNumber
				tinsert(upkeepCosts, formatResource(moneyUpkeep, 'money', false))
			end
			
			local waterUpkeep = getValue(row, 'supportMaterials_month/water')
			if isGreaterThanZero(waterUpkeep) then
				totalMonthlyUpkeep['water'] = totalMonthlyUpkeep['water'] + waterUpkeep * moduleNumber
				tinsert(upkeepCosts, formatResource(waterUpkeep, 'water', false))
			end
			
			local volatilesUpkeep = getValue(row, 'supportMaterials_month/volatiles')
			if isGreaterThanZero(volatilesUpkeep) then
				totalMonthlyUpkeep['volatiles'] = totalMonthlyUpkeep['volatiles'] + volatilesUpkeep * moduleNumber
				tinsert(upkeepCosts, formatResource(volatilesUpkeep, 'volatiles', false))
			end
			
			local metalsUpkeep = getValue(row, 'supportMaterials_month/metals')
			if isGreaterThanZero(metalsUpkeep) then
				totalMonthlyUpkeep['metals'] = totalMonthlyUpkeep['metals'] + metalsUpkeep * moduleNumber
				tinsert(upkeepCosts, formatResource(metalsUpkeep, 'metals', false))
			end
			
			local noblesUpkeep = getValue(row, 'supportMaterials_month/nobleMetals')
			if isGreaterThanZero(noblesUpkeep) then
				totalMonthlyUpkeep['nobles'] = totalMonthlyUpkeep['nobles'] + noblesUpkeep * moduleNumber
				tinsert(upkeepCosts, formatResource(noblesUpkeep, 'nobles', false))
			end
			
			local fissilesUpkeep = getValue(row, 'supportMaterials_month/fissiles')
			if isGreaterThanZero(fissilesUpkeep) then
				totalMonthlyUpkeep['fissiles'] = totalMonthlyUpkeep['fissiles'] + fissilesUpkeep * moduleNumber
				tinsert(upkeepCosts, formatResource(fissilesUpkeep, 'fissiles', false))
			end
			
			local boostUpkeep = getValue(row, 'supportMaterials_month/boost')
			if isGreaterThanZero(boostUpkeep) then
				totalMonthlyUpkeep['boost'] = totalMonthlyUpkeep['boost'] + boostUpkeep * moduleNumber
				tinsert(upkeepCosts, formatResource(boostUpkeep, 'boost', false))
			end
			
			local powerProduction = getValue(row, 'power')
			if frame.args[rowID .. 'Power'] and tonumber(frame.args[rowID .. 'Power']) and tonumber(frame.args[rowID .. 'Power']) ~= 0 then
				powerProduction = tonumber(frame.args[rowID .. 'Power'])
			end
			totalPower = totalPower + powerProduction * moduleNumber
			if isLessThanZero(powerProduction) then
				tinsert(upkeepCosts, formatResource(-powerProduction, 'powerconsumption', false))
			end
			
			local mcProduction = getValue(row, 'missionControl')
			if isLessThanZero(mcProduction) then
				totalMonthlyUpkeep['mc'] = totalMonthlyUpkeep['mc'] - mcProduction * moduleNumber
				tinsert(upkeepCosts, formatResource(-mcProduction, 'mc', false))
			end
			
			local crew = getValue(row, 'crew')
			if isGreaterThanZero(crew) then
				totalCrew = totalCrew + crew * moduleNumber
				tinsert(upkeepCosts, formatResource(crew, 'crew', false))
			end
			
			returnString = returnString .. '\n| ' .. tconcat(upkeepCosts, ' ')
			
			-- Monthly income column
			local incomeItems = {}
			
			local moneyIncome = getValue(row, 'incomeMoney_month')
			if isGreaterThanZero(moneyIncome) then
				totalMonthlyIncome['money'] = totalMonthlyIncome['money'] + moneyIncome * moduleNumber
				tinsert(incomeItems, formatResource(moneyIncome, 'money', false))
			end
			
			local influenceIncome = getValue(row, 'incomeInfluence_month')
			if isGreaterThanZero(influenceIncome) then
				totalMonthlyIncome['influence'] = totalMonthlyIncome['influence'] + influenceIncome * moduleNumber
				tinsert(incomeItems, formatResource(influenceIncome, 'influence', false))
			end
			
			local opsIncome = getValue(row, 'incomeOps_month')
			if isGreaterThanZero(opsIncome) then
				totalMonthlyIncome['ops'] = totalMonthlyIncome['ops'] + opsIncome * moduleNumber
				tinsert(incomeItems, formatResource(opsIncome, 'ops', false))
			end
			
			local researchIncome = getValue(row, 'incomeResearch_month')
			if isGreaterThanZero(researchIncome) then
				totalMonthlyIncome['research'] = totalMonthlyIncome['research'] + researchIncome * moduleNumber
				tinsert(incomeItems, formatResource(researchIncome, 'research', false))
			end
			
			local projectsIncome = getValue(row, 'incomeProjects')
			if isGreaterThanZero(projectsIncome) then
				totalMonthlyIncome['projects'] = totalMonthlyIncome['projects'] + projectsIncome * moduleNumber
				tinsert(incomeItems, formatResource(projectsIncome, 'projects', false))
			end
			
			local volatilesIncome = getValue(row, 'incomeVolatiles_month')
			if isGreaterThanZero(volatilesIncome) then
				totalMonthlyIncome['volatiles'] = totalMonthlyIncome['volatiles'] + volatilesIncome * moduleNumber
				tinsert(incomeItems, formatResource(volatilesIncome, 'volatiles', false))
			end
			
			local metalsIncome = getValue(row, 'incomeMetals_month')
			if isGreaterThanZero(metalsIncome) then
				totalMonthlyIncome['metals'] = totalMonthlyIncome['metals'] + metalsIncome * moduleNumber
				tinsert(incomeItems, formatResource(metalsIncome, 'metals', false))
			end
			
			local noblesIncome = getValue(row, 'incomeNobles_month')
			if isGreaterThanZero(noblesIncome) then
				totalMonthlyIncome['nobles'] = totalMonthlyIncome['nobles'] + noblesIncome * moduleNumber
				tinsert(incomeItems, formatResource(noblesIncome, 'nobles', false))
			end
			
			local fissilesIncome = getValue(row, 'incomeFissiles_month')
			if isGreaterThanZero(fissilesIncome) then
				totalMonthlyIncome['fissiles'] = totalMonthlyIncome['fissiles'] + fissilesIncome * moduleNumber
				tinsert(incomeItems, formatResource(fissilesIncome, 'fissiles', false))
			end
			
			local exoticsIncome = getValue(row, 'incomeExotics_month')
			if isGreaterThanZero(exoticsIncome) then
				totalMonthlyIncome['exotics'] = totalMonthlyIncome['exotics'] + exoticsIncome * moduleNumber
				tinsert(incomeItems, formatResource(exoticsIncome, 'exotics', false))
			end
			
			local antimatterIncome = getValue(row, 'incomeAntimatter_month')
			if isGreaterThanZero(antimatterIncome) then
				totalMonthlyIncome['antimatter'] = totalMonthlyIncome['antimatter'] + antimatterIncome * moduleNumber
				tinsert(incomeItems, formatResource(antimatterIncome, 'antimatter', false))
			end
			
			if isGreaterThanZero(powerProduction) then
				tinsert(incomeItems, formatResource(powerProduction, 'power', false))
			end
			
			if isGreaterThanZero(mcProduction) then
				totalMonthlyIncome['mc'] = totalMonthlyIncome['mc'] + mcProduction * moduleNumber
				tinsert(incomeItems, formatResource(mcProduction, 'mc', false))
			end
			
			local miningModifier = getValue(row, 'miningModifier')
			if isGreaterThanZero(miningModifier) then
				tinsert(incomeItems, tostring(miningModifier) .. " × Hab Site Resources")
			end
			
			returnString = returnString .. '\n| ' .. tconcat(incomeItems, ' ')
			
			-- Special column
			local specialRules = formatSpecialRules(row)
			returnString = returnString .. '\n| style="text-align:left;" | ' .. (specialRules ~= '' and specialRules or '')
			
			for i = 0, 5 do
				local rule = getValue(row, 'specialRules/' .. i)
				if rule == 'Farm' then
					totalFarm = totalFarm + (tonumber(getValue(row, 'specialRulesValue')) or 0) * moduleNumber
				elseif rule == 'Efficiency' then
					totalEfficiency = totalEfficiency + (tonumber(getValue(row, 'specialRulesValue')) or 0) * moduleNumber
				elseif rule == 'Stability' then
					totalUnpaidCrew = totalUnpaidCrew + crew * moduleNumber
				end
			end
			
			returnString = returnString .. '\n|-'
		end
	end
	
	returnString = returnString .. '\n|- class="sortbottom"'
	returnString = returnString .. "\n! '''Total'''"
	returnString = returnString .. '\n| ' .. totalModuleNumber
	local tBuildCosts = {}
	for costType, costValue in pairs(totalBuildCosts) do
		if costType ~= 'mass' and costValue > 0 then
			tinsert(tBuildCosts, formatResource(costValue, costType, true))
		end
	end
	returnString = returnString .. '\n| ' .. tconcat(tBuildCosts, ' ') .. '<br>Total Mass: ' .. totalBuildCosts['mass']
	
	local tUpkeepCosts = {}
	for costType, costValue in pairs(totalMonthlyUpkeep) do
		if costValue > 0 then
			tinsert(tUpkeepCosts, formatResource(costValue, costType, true))
		end
	end
	returnString = returnString .. '\n| ' .. tconcat(tUpkeepCosts, ' ') .. '<br>Total Crew: ' .. totalCrew

	local tMonthlyIncome = {}
	for incomeType, incomeValue in pairs(totalMonthlyIncome) do
		if incomeValue > 0 then
			if incomeType ~= 'antimatter' 
				and incomeType ~= 'exotics' 
				and incomeType ~= 'mc' 
				and incomeType ~= 'projects' then
				incomeValue = incomeValue * totalEfficiency
			end
			tinsert(tMonthlyIncome, formatResource(incomeValue, incomeType, true))
		end
	end
	returnString = returnString .. '\n| ' .. tconcat(tMonthlyIncome, ' ') .. '<br>Total Farm: ' .. totalFarm 
	
	returnString = returnString .. '\n| style="text-align:left;" |' 
	if totalModuleNumber > 21 then
		returnString = returnString .. '\n* <span style="color:red;">Too Many Modules!</span>'
	end
	if totalPower < 0 then
		returnString = returnString .. '\n* <span style="color:red;">Missing Power: ' .. (-totalPower) .. '</span>'
	elseif totalPower > 0 then
		returnString = returnString .. '\n* <span style="color:green;">Excess Power: ' .. (totalPower) .. '</span>'
	end
	if totalCrew > totalFarm then
		returnString = returnString .. '\n* <span style="color:orange;">Unfed Crew: ' .. (totalCrew - totalFarm) .. '</span>'
	elseif totalCrew < totalFarm then
		returnString = returnString .. '\n* <span style="color:green;">Excess Farm: ' .. (totalFarm - totalCrew) .. '</span>'
	end
	if totalUnpaidCrew > 0 then
		returnString = returnString .. '\n* <span style="color:green;">Unpaid Crew: ' .. totalUnpaidCrew .. '</span>'
	end
	returnString = returnString .. '\n* Crew Adds Costs: '
	returnString = returnString .. TVModule.TV({args={'money', round((totalCrew - totalUnpaidCrew) * 0.1 / 12, 2), 'true'}})
	if totalCrew > totalFarm then
		returnString = returnString .. TVModule.TV({args={'water', round((totalCrew - totalFarm) * 0.35 / 12, 2), 'true'}})
		returnString = returnString .. TVModule.TV({args={'volatiles', round((totalCrew - totalFarm) * 0.35 / 12, 2), 'true'}})
	end

	returnString = returnString .. '\n|}'
	
	return returnString
	
end


--endregion

return HabPlanner