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


