Module:ShipHullLister

From Terra Invicta Official Wiki

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

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

--region Dependencies
local IconModule = require('Module:Icon')
local ShipHullData = mw.loadData('Module:CSVReader/ShipHull')
local HabModuleData = mw.loadData('Module:CSVReader/HabModule')
local ShipHullNames = mw.loadData('Module:ENReader/ShipHull')
local ProjectNameBuilder = require('Module:ProjectLister/NameBuilder')
--endregion

--region Private constants
local floor = math.floor
local ssub = string.sub
local round = function(num, decimals)
    local mult = 10^(decimals or 0)
    return floor(num * mult + 0.5) / mult
end
local dockDataNames = {"SpaceDock", "Shipyard", "Spaceworks", "AlienSpacedock", "AlienShipyard", "AlienSpaceworks"}
local maxArmorMultipliers = {3.6, 12, 3.6, 1.8, 6, 0}
--endregion

--region Private methods

local function calculateResourceCost(mass, resourceWeight, crew, crewResource)
    local baseCost = tonumber(mass) * (tonumber(resourceWeight) or 0) / 10
    local crewCost = tonumber(crew) * (tonumber(crewResource) or 0)
    return baseCost + crewCost
end

local function calculateBuildTime(baseDays, consTier, alien, dockTier)
	if consTier < dockTier then
		local constructionTimeModifier = 0
		if alien then
			constructionTimeModifier = HabModuleData[dockDataNames[dockTier + 3]]['constructionTimeModifier']
		else
			constructionTimeModifier = HabModuleData[dockDataNames[dockTier]]['constructionTimeModifier']
		end
		return baseDays * constructionTimeModifier ^ (dockTier - consTier)
	elseif consTier > dockTier then
		return baseDays * 1.5 ^ (consTier - dockTier)
	else
		return baseDays
	end
end

-- param @facing 1 = Nose, 2 = Side, 3 = Tail
local function calculateMaxArmorThickness(length, width, simple, facing)
	if facing == 2 then
		if simple then
			return floor(width * maxArmorMultipliers[facing + 3])
		else
			return floor(width * maxArmorMultipliers[facing])
		end
	else
		if simple then
			return floor(length * maxArmorMultipliers[facing + 3])
		else
			return floor(length * maxArmorMultipliers[facing])
		end
	end
end

-- param @facing 1 = Nose/Tail, 2 = Side
local function calculateRoughArmorMultiplier(length, width, facing, cinematic)
	if facing == 2 then
		if cinematic then
			return 3.1415927 * length * width * 0.75
		else
			return 3.1415927 * length * width * 0.5
		end
	else
		if cinematic then
			return 3.1415927 * width ^ 2 / 4
		else
			return 3.1415927 * width ^ 2 / 4 * 3
		end
	end
end

-- Safe value access with fallback
local function getValue(row, key, default)
    local value = row[key]
    if value == nil or value == '' then
        return default or 0
    end
    return value
end
--endregion

--region Public methods
--- Example Usage: {{#invoke:ShipHullLister|ListSpecific|detailed=true|alien=true}} 
function ShipHullLister.ListSpecific(frame)
	local detailed = frame.args['detailed'] == 'true'
	local alien = frame.args['alien'] == 'true'
	return ShipHullLister.ListShipHulls(detailed, alien)
end

---@param detailed boolean whether to show the detailed or basic version.
---@param alien boolean whether to show alien or human version.
---@return wiki table of batteries
function ShipHullLister.ListShipHulls(detailed, alien)
	local returnString = '{| class="wikitable sortable mw-collapsible" style="text-align: center;"'
	returnString = returnString .. '\n! rowspan=3| Ship<br>Hull'
	if detailed and not alien then
		returnString = returnString .. '\n! rowspan=3| Required<br>Project'
	end
	
	returnString = returnString .. '\n! colspan=3| Hard Points'
	returnString = returnString .. '\n! rowspan=3| Structural<br>Integrity'
	returnString = returnString .. '\n! rowspan=3| Max<br>Officers'
	
	if detailed then
		returnString = returnString .. '\n! colspan=5| Construction'
		returnString = returnString .. '\n! colspan=2| Dimensions (m)'
		returnString = returnString .. '\n! colspan=3| Maximum Armor Thickness (cm)'
	else
		returnString = returnString .. '\n! colspan=3| Construction Time (days)'
	end
	
	returnString = returnString .. '\n! colspan=4| Rough Armor Cost Multiplier'
	
	if detailed then
		returnString = returnString .. '\n! rowspan=3| Crew'
		returnString = returnString .. '\n! rowspan=3| Mass<br>(tons)'
		returnString = returnString .. '\n! colspan=5| Build Cost'
		returnString = returnString .. '\n! colspan=2| Upkeep Cost'
	end
	
	returnString = returnString .. '\n|-'
	returnString = returnString .. '\n! rowspan=2| ' .. IconModule.Icon({args={'nose slot'}}) .. '<br>Nose'
	returnString = returnString .. '\n! rowspan=2| ' .. IconModule.Icon({args={'hull slot'}}) .. '<br>Hull'
	returnString = returnString .. '\n! rowspan=2| ' .. IconModule.Icon({args={'utility slot'}}) .. '<br>Utility'
	if detailed then
		returnString = returnString .. '\n! rowspan=2| Base<br>Days'
		returnString = returnString .. '\n! rowspan=2| Ship<br>Tier'
	end
	returnString = returnString .. '\n! rowspan=2| ' .. IconModule.Icon({args={'spacedock'}}) .. '<br>T1'
	returnString = returnString .. '\n! rowspan=2| ' .. IconModule.Icon({args={'shipyard'}}) .. '<br>T2'
	returnString = returnString .. '\n! rowspan=2| ' .. IconModule.Icon({args={'spaceworks'}}) .. '<br>T3'
	if detailed then
		returnString = returnString .. '\n! rowspan=2| Length'
		returnString = returnString .. '\n! rowspan=2| Width'
		returnString = returnString .. '\n! rowspan=2| Nose'
		returnString = returnString .. '\n! rowspan=2| Side'
		returnString = returnString .. '\n! rowspan=2| Tail'
	end
	
	returnString = returnString .. '\n! colspan=2| Cinematic'
	returnString = returnString .. '\n! colspan=2| Realistic'
	
	if detailed then
		returnString = returnString .. '\n! rowspan=2| ' .. IconModule.Icon({args={'water'}})
		returnString = returnString .. '\n! rowspan=2| ' .. IconModule.Icon({args={'volatiles'}})
		returnString = returnString .. '\n! rowspan=2| ' .. IconModule.Icon({args={'metals'}})
		returnString = returnString .. '\n! rowspan=2| ' .. IconModule.Icon({args={'nobles'}})
		returnString = returnString .. '\n! rowspan=2| ' .. IconModule.Icon({args={'exotics'}})
		returnString = returnString .. '\n! rowspan=2| ' .. IconModule.Icon({args={'mc'}})
		returnString = returnString .. '\n! rowspan=2| ' .. IconModule.Icon({args={'money'}})
	end
	
	returnString = returnString .. '\n|-'
	returnString = returnString .. '\n! Nose/Tail'
	returnString = returnString .. '\n! Side'
	returnString = returnString .. '\n! Nose/Tail'
	returnString = returnString .. '\n! Side'
	
	returnString = returnString .. '\n|-'
	
	for rowID, row in pairs(ShipHullData) do
		local projectID = getValue(row, 'requiredProjectName', '')
	    if (alien and (projectID == 'Project_AlienMasterProject' or projectID == 'Project_AlienAdvancedMasterProject')) or ((not alien) and projectID ~= 'Project_AlienMasterProject' and projectID ~= 'Project_AlienAdvancedMasterProject') then
			returnString = returnString .. '\n| ' .. (ShipHullNames['TIShipHullTemplate.displayName.' .. rowID] or rowID)
			if detailed and not alien then
				returnString = returnString .. '\n| ' .. ProjectNameBuilder.buildName({args={row['requiredProjectName']}})
			end
			returnString = returnString .. '\n| ' .. getValue(row, 'noseHardpoints', 0)
			returnString = returnString .. '\n| ' .. getValue(row, 'hullHardpoints', 0)
			returnString = returnString .. '\n| ' .. getValue(row, 'internalModules', 0)
			returnString = returnString .. '\n| ' .. getValue(row, 'structuralIntegrity', 0)
			returnString = returnString .. '\n| ' .. getValue(row, 'maxOfficers', 0)
			local baseDays = tonumber(getValue(row, 'baseConstructionTime_days', 0)) or 0
			local consTier = tonumber(getValue(row, 'consTier', 0)) or 0
			if detailed then
				returnString = returnString .. '\n| ' .. baseDays
				returnString = returnString .. '\n| ' .. consTier
			end
			local simple = (row['simpleHull'] == 'true')
			if simple then
				returnString = returnString .. '\n| '
				returnString = returnString .. '\n| '
				returnString = returnString .. '\n| '
			else
				returnString = returnString .. '\n| ' .. round(calculateBuildTime(baseDays, consTier, alien, 1),1)
				returnString = returnString .. '\n| ' .. round(calculateBuildTime(baseDays, consTier, alien, 2),1)
				returnString = returnString .. '\n| ' .. round(calculateBuildTime(baseDays, consTier, alien, 3),1)
			end
			
			local length = tonumber(getValue(row, 'length_m', 0)) or 0
			local width = tonumber(getValue(row, 'width_m', 0)) or 0
			if detailed then
				returnString = returnString .. '\n| ' .. length
				returnString = returnString .. '\n| ' .. width
				returnString = returnString .. '\n| ' .. calculateMaxArmorThickness(length, width, simple, 1)
				returnString = returnString .. '\n| ' .. calculateMaxArmorThickness(length, width, simple, 2)
				returnString = returnString .. '\n| ' .. calculateMaxArmorThickness(length, width, simple, 3)
			end
			
			returnString = returnString .. '\n| ' .. round(calculateRoughArmorMultiplier(length, width, 1, true))
			returnString = returnString .. '\n| ' .. round(calculateRoughArmorMultiplier(length, width, 2, true))
			returnString = returnString .. '\n| ' .. round(calculateRoughArmorMultiplier(length, width, 1, false))
			returnString = returnString .. '\n| ' .. round(calculateRoughArmorMultiplier(length, width, 2, false))
			
			if detailed then
				local crewCount = tonumber(getValue(row, 'crew', 0)) or 0
				local baseMass = tonumber(getValue(row, 'mass_tons', 0)) or 0
				local massWithCrew = baseMass + crewCount * 4
				returnString = returnString .. '\n| ' .. crewCount
				returnString = returnString .. '\n| ' .. massWithCrew
				local waterCost = calculateResourceCost(baseMass, 0, crewCount, 0.2)
				local volatilesCost = calculateResourceCost(baseMass, getValue(row, 'weightedBuildMaterials/volatiles'), crewCount, 0.2)
				local metalsCost = calculateResourceCost(baseMass, getValue(row, 'weightedBuildMaterials/metals'), crewCount, 0)
				local noblesCost = calculateResourceCost(baseMass, getValue(row, 'weightedBuildMaterials/nobleMetals'), crewCount, 0)
				local exoticsCost = calculateResourceCost(baseMass, getValue(row, 'weightedBuildMaterials/exotics'), crewCount, 0)
				
				returnString = returnString .. '\n| ' .. round(waterCost, 3)
				returnString = returnString .. '\n| ' .. round(volatilesCost, 3)
				returnString = returnString .. '\n| ' .. round(metalsCost, 3)
				returnString = returnString .. '\n| ' .. round(noblesCost, 3)
				returnString = returnString .. '\n| ' .. round(exoticsCost, 3)
				
				returnString = returnString .. '\n| ' .. getValue(row, 'missionControl', 0)
				local moneyUpkeep = ((tonumber(getValue(row, 'monthlyIncome_Money', 0)) or 0) * -1)
				if moneyUpkeep > 0 then
					returnString = returnString .. '\n| ' .. moneyUpkeep
				else
					returnString = returnString .. '\n| 0' -- If ships ever GIVE money, then this needs changing.
				end
			end
			
			returnString = returnString .. '\n|-'
		end
	end
	returnString = returnString .. '\n|}'
	return returnString
end

function ShipHullLister.CompareShipHulls()
	local returnString = '{| class="wikitable sortable mw-collapsible" style="text-align: center;"'
	returnString = returnString .. '\n! rowspan=3| Ship<br>Hull'
	returnString = returnString .. '\n! colspan=6| Hard Points'
	returnString = returnString .. '\n! rowspan=2 colspan=2| Structural<br>Integrity'
	returnString = returnString .. '\n! colspan=6| Construction Time (days)'
	
	returnString = returnString .. '\n|-'
	returnString = returnString .. '\n! colspan=2| ' .. IconModule.Icon({args={'nose slot'}}) .. '<br>Nose'
	returnString = returnString .. '\n! colspan=2| ' .. IconModule.Icon({args={'hull slot'}}) .. '<br>Hull'
	returnString = returnString .. '\n! colspan=2| ' .. IconModule.Icon({args={'utility slot'}}) .. '<br>Utility'
	returnString = returnString .. '\n! colspan=2| ' .. IconModule.Icon({args={'spacedock'}}) .. '<br>T1'
	returnString = returnString .. '\n! colspan=2| ' .. IconModule.Icon({args={'shipyard'}}) .. '<br>T2'
	returnString = returnString .. '\n! colspan=2| ' .. IconModule.Icon({args={'spaceworks'}}) .. '<br>T3'
	
	returnString = returnString .. '\n|-'
	returnString = returnString .. '\n! Human'
	returnString = returnString .. '\n! Alien'
	returnString = returnString .. '\n! Human'
	returnString = returnString .. '\n! Alien'
	returnString = returnString .. '\n! Human'
	returnString = returnString .. '\n! Alien'
	returnString = returnString .. '\n! Human'
	returnString = returnString .. '\n! Alien'
	returnString = returnString .. '\n! Human'
	returnString = returnString .. '\n! Alien'
	returnString = returnString .. '\n! Human'
	returnString = returnString .. '\n! Alien'
	returnString = returnString .. '\n! Human'
	returnString = returnString .. '\n! Alien'
	returnString = returnString .. '\n|-'
	
	for rowID, row in pairs(ShipHullData) do
	    if (getValue(row, 'alien') == 'true') then
			returnString = returnString .. '\n| ' .. (ShipHullNames['TIShipHullTemplate.displayName.' .. rowID] or rowID)
			local humanID = ssub(rowID,6)
			if not ShipHullData[humanID] then
				if rowID == 'SalamanderGunship' then
					humanID = 'STOFighter'
				else
					humanID = nil
				end
			end
			local alienHardpoints = {
				tonumber(getValue(row, 'noseHardpoints', 0)) or 0, 
				tonumber(getValue(row, 'hullHardpoints', 0)) or 0, 
				tonumber(getValue(row, 'internalModules', 0)) or 0
			}
			if not ShipHullData[humanID] then
				returnString = returnString .. '\n| \n| ' .. alienHardpoints[1]
				returnString = returnString .. '\n| \n| ' .. alienHardpoints[2]
				returnString = returnString .. '\n| \n| ' .. alienHardpoints[3]
			else
				local humanHardpoints = {
					tonumber(getValue(ShipHullData[humanID], 'noseHardpoints', 0)) or 0, 
					tonumber(getValue(ShipHullData[humanID], 'hullHardpoints', 0)) or 0, 
					tonumber(getValue(ShipHullData[humanID], 'internalModules', 0)) or 0
				}
				for i = 1, 3 do
					returnString = returnString .. '\n|' .. humanHardpoints[i] .. '\n| ' .. alienHardpoints[i]
					if humanHardpoints[i] > alienHardpoints[i] then
						returnString = returnString .. IconModule.Icon({args={'greenarrowdown'}})
					elseif humanHardpoints[i] < alienHardpoints[i] then
						returnString = returnString .. IconModule.Icon({args={'redarrowup'}})
					end
				end
			end
			local alienStructuralIntegrity = tonumber(getValue(row, 'structuralIntegrity', 0)) or 0
			if not ShipHullData[humanID] then
				returnString = returnString .. '\n| \n| ' .. alienStructuralIntegrity
			else
				local humanStructuralIntegrity = tonumber(getValue(ShipHullData[humanID], 'structuralIntegrity', 0)) or 0
				returnString = returnString .. '\n|' .. humanStructuralIntegrity .. '\n| ' .. alienStructuralIntegrity
				if humanStructuralIntegrity > alienStructuralIntegrity then
					returnString = returnString .. IconModule.Icon({args={'greenarrowdown'}})
				elseif humanStructuralIntegrity < alienStructuralIntegrity then
					returnString = returnString .. IconModule.Icon({args={'redarrowup'}})
				end
			end
			
			local simple = (row['simpleHull'] == 'true')
			if simple then
				returnString = returnString .. '\n| \n| '
				returnString = returnString .. '\n| \n| '
				returnString = returnString .. '\n| \n| '
			else
				local alienBaseDays = tonumber(getValue(row, 'baseConstructionTime_days', 0)) or 0
				local alienConsTier = tonumber(getValue(row, 'consTier', 0)) or 0
				local alienBuildTime = {
					round(calculateBuildTime(alienBaseDays, alienConsTier, true, 1),1),
					round(calculateBuildTime(alienBaseDays, alienConsTier, true, 2),1),
					round(calculateBuildTime(alienBaseDays, alienConsTier, true, 3),1)
				}
				if not ShipHullData[humanID] then
					returnString = returnString .. '\n| \n| ' .. alienBuildTime[1]
					returnString = returnString .. '\n| \n| ' .. alienBuildTime[2]
					returnString = returnString .. '\n| \n| ' .. alienBuildTime[3]
				else
					local humanBaseDays = tonumber(getValue(ShipHullData[humanID], 'baseConstructionTime_days', 0)) or 0
					local humanConsTier = tonumber(getValue(ShipHullData[humanID], 'consTier', 0)) or 0
					local humanBuildTime = {
						round(calculateBuildTime(humanBaseDays, humanConsTier, false, 1),1),
						round(calculateBuildTime(humanBaseDays, humanConsTier, false, 2),1),
						round(calculateBuildTime(humanBaseDays, humanConsTier, false, 3),1)
					}
					for i = 1, 3 do
						returnString = returnString .. '\n|' .. humanBuildTime[i] .. '\n| ' .. alienBuildTime[i]
						if humanBuildTime[i] > alienBuildTime[i] then
							returnString = returnString .. IconModule.Icon({args={'redarrowdown'}})
						elseif humanBuildTime[i] < alienBuildTime[i] then
							returnString = returnString .. IconModule.Icon({args={'greenarrowup'}})
						end
					end
				end
			end
			returnString = returnString .. '\n|-'
		end
	end
	returnString = returnString .. '\n|}'
	return returnString
end

--endregion

return ShipHullLister