Module:MissileLister

From Terra Invicta Official Wiki

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

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

--region Dependencies
local RestrictionChecker = require('Module:RestrictionChecker')
local IconModule = require('Module:Icon')

local MissileData = mw.loadData('Module:CSVReader/Missile')
local MissileNames = mw.loadData('Module:ENReader/Missile')
local ProjectNames = mw.loadData('Module:ENReader/Project')
--endregion

--region Private constants
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
local function getMountDisplayName(mount)
    local mountNames = {
        HalfHull = "Half Hull Slot",
        OneHull = "One Hull Slot",
        TwoHullHoriz = "Two Hull Slots", 
        FourHull = "Four Hull Slots",
        HalfNose = "Half Nose Slot",
        OneNose = "One Nose Slot",
        TwoNoseVert = "Two Nose Slots",
        ThreeNoseAngle = "Three Nose Slots",
        FourNose = "Four Nose Slots"
    }
    return mountNames[mount] or mount
end

local function calculateStationaryDamage(warheadClass, payload, wmass, muzzle)
	if warheadClass == 'Fragmentation' or warheadClass == 'Penetrator' then
		return round(tonumber(wmass) * tonumber(muzzle) ^ 2 / 40,2)
	elseif warheadClass == 'Explosive' then
		return round(tonumber(payload) / 20 + tonumber(wmass) * tonumber(muzzle) ^ 2 / 400,2)
	else
		return round(tonumber(payload) / 20,2)
	end
end

local function calculateFireRate(salvo, cooldown, scooldown)
    if not salvo or tonumber(salvo) == 1 then
        return round(60 / tonumber(cooldown),1)
    else
        local fullCooldown = tonumber(cooldown) + (tonumber(salvo) - 1) * tonumber(scooldown)
        return round(math.max(1,tonumber(salvo)) * 60 / fullCooldown,1)
    end
end

local function calculateFullCooldown(salvo, cooldown, scooldown)
    if not salvo or salvo == 1 then
        return tonumber(cooldown)
    else
        return tonumber(cooldown) + (tonumber(salvo) - 1) * tonumber(scooldown)
    end
end

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 calculateAmmoResourceCost(magazine, amass, resourceWeight)
    return tonumber(magazine) * tonumber(amass) * (tonumber(resourceWeight) or 0) / 10000
end

--{{!}} {{#switch: {{{warhead}}}
--| Nuclear
--| Antimatter = {{#expr: ( {{{flat}}} / 12566.371 ) ^ 0.5 / 20 round 1}}
--| ShapedNuclear = {{#expr: ( {{{flat}}} * 0.1 ) ^ 0.5 / 3.1415927 / tan(0.017453292 * {{{shapeangle}}} ) / 1000 / 20 round 1}}
--}}
local function calculateBlastRadius(warhead, flat, shapeangle)
	if warhead == 'Nuclear' or warhead == 'Antimatter' then
		return (flat / 12566.371) ^ 0.5 / 20
	elseif warhead == 'ShapedNuclear' then
		return (flat * 0.1) ^ 0.5 / 3.1415927 / math.tan(0.017453292 * shapeangle) / 1000 / 20
	end
	return 0
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
function MissileLister.ListBasicMissiles(frame)
	local returnString = '{| class="wikitable sortable mw-collapsible" style="text-align: center;"'
	returnString = returnString .. '\n! Missile'
	returnString = returnString .. '\n! rowspan=2|Warhead<br>Class'
	returnString = returnString .. '\n! Warhead Mass<br>(kg)'
	returnString = returnString .. '\n! Delta-v<br>(km/s)'
	returnString = returnString .. '\n! Acceleration<br>(G)'
	returnString = returnString .. '\n! Stationary Damage'
	returnString = returnString .. '\n! Fire Rate<br>(shots/min)'
	returnString = returnString .. '\n! Range<br>(km)'
	returnString = returnString .. '\n! Magazine'
	returnString = returnString .. '\n! Blast Radius<br>(km)'
	returnString = returnString .. '\n|-'
	
	for rowID, row in pairs(MissileData) do
		if not frame or not frame.args or RestrictionChecker.AllPass(frame.args, row) then
			returnString = returnString .. '\n| ' .. (MissileNames['TIMissileTemplate.displayName.' .. rowID] or rowID)
			local warheadClass = getValue(row, 'warheadClass', '')
			if warheadClass == 'ShapedNuclear' then
				returnString = returnString .. '\n| Shaped Nuclear'
			else
				returnString = returnString .. '\n| ' .. warheadClass
			end
			returnString = returnString .. '\n| ' .. getValue(row, 'warheadMass_kg', 0)
			returnString = returnString .. '\n| ' .. getValue(row, 'deltaV_kps', 0)
			returnString = returnString .. '\n| ' .. getValue(row, 'acceleration_g', 0)
			
			local stationaryDamage = calculateStationaryDamage(warheadClass, getValue(row, 'flatDamage_MJ'), getValue(row, 'warheadMass_kg'), getValue(row, 'deltaV_kps'))
			returnString = returnString .. '\n| ' .. stationaryDamage
			
			local fireRate = calculateFireRate(getValue(row, 'salvo_shots',1), getValue(row, 'cooldown_s'), getValue(row, 'intraSalvoCooldown_s'))
			returnString = returnString .. '\n| ' .. fireRate
			
			returnString = returnString .. '\n| ' .. getValue(row, 'targetingRange_km', 0)
			returnString = returnString .. '\n| ' .. getValue(row, 'magazine', 0)
			
			returnString = returnString .. '\n| ' 
			local blastrad = calculateBlastRadius(warheadClass, getValue(row, 'flatDamage_MJ'), getValue(row, 'shapedChargeAngle'))
			if blastrad then
				returnString = returnString .. round(blastrad,1)
			end
			returnString = returnString .. '\n|-'
		end
	end
	
	returnString = returnString .. '\n|}'
	return returnString
end

function MissileLister.ListDetailedMissiles(frame)
	local returnString = '{| class="wikitable sortable mw-collapsible" style="text-align: center;"'
	returnString = returnString .. '\n! rowspan=2|Missile'
	returnString = returnString .. '\n! rowspan=2|Required Project'
	returnString = returnString .. '\n! rowspan=2|Hard Points'
	returnString = returnString .. '\n! rowspan=2|Can PD'
	returnString = returnString .. '\n! rowspan=2|Warhead<br>Class'
	returnString = returnString .. '\n! rowspan=2|Warhead Mass<br>(kg)'
	returnString = returnString .. '\n! rowspan=2|Delta-v<br>(km/s)'
	returnString = returnString .. '\n! rowspan=2|Acceleration<br>(G)'
	returnString = returnString .. '\n! rowspan=2|Payload Damage'
	returnString = returnString .. '\n! rowspan=2|Stationary Damage'
	returnString = returnString .. '\n! rowspan=2|Chip %'
	returnString = returnString .. '\n! rowspan=2|Salvo'
	returnString = returnString .. '\n! rowspan=2|Full Cooldown<br>(s)'
	returnString = returnString .. '\n! rowspan=2|Fire Rate<br>(shots/min)'
	returnString = returnString .. '\n! rowspan=2|Range<br>(km)'
	returnString = returnString .. '\n! rowspan=2|Crew'
	returnString = returnString .. '\n! rowspan=2|Mass<br>(tons)'
	returnString = returnString .. '\n! colspan=4| Non-Ammo Build Resources'
	returnString = returnString .. '\n! rowspan=2|Magazine'
	returnString = returnString .. '\n! rowspan=2|Ammo Mass<br>(kg)'
	returnString = returnString .. '\n! colspan=6| Full Ammo Resources'
	returnString = returnString .. '\n! rowspan=2|Destruction<br>Cone'
	returnString = returnString .. '\n! rowspan=2|Blast Radius<br>(km)'
	returnString = returnString .. '\n|-'
	returnString = returnString .. '\n!' .. IconModule.Icon({args={'water'}})
	returnString = returnString .. '\n!' .. IconModule.Icon({args={'volatiles'}})
	returnString = returnString .. '\n!' .. IconModule.Icon({args={'metals'}})
	returnString = returnString .. '\n!' .. IconModule.Icon({args={'nobles'}})
	returnString = returnString .. '\n!' .. IconModule.Icon({args={'volatiles'}})
	returnString = returnString .. '\n!' .. IconModule.Icon({args={'metals'}})
	returnString = returnString .. '\n!' .. IconModule.Icon({args={'nobles'}})
	returnString = returnString .. '\n!' .. IconModule.Icon({args={'fissiles'}})
	returnString = returnString .. '\n!' .. IconModule.Icon({args={'exotics'}})
	returnString = returnString .. '\n!' .. IconModule.Icon({args={'antimatter'}})
	returnString = returnString .. '\n|-'
	
	for rowID, row in pairs(MissileData) do
		if not frame or not frame.args or RestrictionChecker.AllPass(frame.args, row) then
			returnString = returnString .. '\n| ' .. (MissileNames['TIMissileTemplate.displayName.' .. rowID] or rowID)
			
			local projectID = getValue(row, 'requiredProjectName','')
			if not projectID or projectID == '' then
			    returnString = returnString .. '\n|' 
			elseif projectID == 'Project_AlienMasterProject' or projectID == 'Project_AlienAdvancedMasterProject' then
			    returnString = returnString .. '\n| Alien Only'
			else
			    local projectName = ProjectNames['TIProjectTemplate.displayName.' .. projectID] or projectID
			    returnString = returnString .. '\n| [[' .. projectID .. '|' .. projectName .. ']]'
			end
			
			returnString = returnString .. '\n| ' .. getMountDisplayName(getValue(row, 'mount'))
			if getValue(row, 'defenseMode', 'false') == 'true' then
				returnString = returnString .. '\n| Only Missiles'
			else
				returnString = returnString .. '\n| No'
			end
			
			local warheadClass = getValue(row, 'warheadClass', '')
			if warheadClass == 'ShapedNuclear' then
				returnString = returnString .. '\n| Shaped Nuclear'
			else
				returnString = returnString .. '\n| ' .. warheadClass
			end
			returnString = returnString .. '\n| ' .. getValue(row, 'warheadMass_kg', 0)
			returnString = returnString .. '\n| ' .. getValue(row, 'deltaV_kps', 0)
			returnString = returnString .. '\n| ' .. getValue(row, 'acceleration_g', 0)
			returnString = returnString .. '\n| ' .. (tonumber(getValue(row, 'flatDamage_MJ', 0)) / 20)
			
			local stationaryDamage = calculateStationaryDamage(warheadClass, getValue(row, 'flatDamage_MJ'), getValue(row, 'warheadMass_kg'), getValue(row, 'deltaV_kps'))
			returnString = returnString .. '\n| ' .. stationaryDamage
			
			local chipPercent = (tonumber(getValue(row, 'flatChipping')) or 0) * 100
			returnString = returnString .. '\n| ' .. chipPercent .. '%'
			returnString = returnString .. '\n| ' .. getValue(row, 'salvo_shots',1)
			
			local fullCooldown = calculateFullCooldown(getValue(row, 'salvo_shots',1), getValue(row, 'cooldown_s'), getValue(row, 'intraSalvoCooldown_s'))
			returnString = returnString .. '\n| ' .. fullCooldown
			
			local fireRate = calculateFireRate(getValue(row, 'salvo_shots',1), getValue(row, 'cooldown_s'), getValue(row, 'intraSalvoCooldown_s'))
			returnString = returnString .. '\n| ' .. fireRate
			
			returnString = returnString .. '\n| ' .. getValue(row, 'targetingRange_km', 0)
			returnString = returnString .. '\n| ' .. getValue(row, 'crew', 0)
			
			local baseMass = tonumber(getValue(row, 'baseWeaponMass_tons', 0))
			local crewCount = tonumber(getValue(row, 'crew', 0))
			local massWithCrew = baseMass + crewCount * 4
			returnString = returnString .. '\n| ' .. massWithCrew
			
			-- Non-ammo build resources
			local waterCost = calculateResourceCost(getValue(row, 'baseWeaponMass_tons'), 0, getValue(row, 'crew'), 0.2)
			local volatilesCost = calculateResourceCost(getValue(row, 'baseWeaponMass_tons'), getValue(row, 'weightedBuildMaterials/volatiles'), getValue(row, 'crew'), 0.2)
			local metalsCost = calculateResourceCost(getValue(row, 'baseWeaponMass_tons'), getValue(row, 'weightedBuildMaterials/metals'), getValue(row, 'crew'), 0)
			local noblesCost = calculateResourceCost(getValue(row, 'baseWeaponMass_tons'), getValue(row, 'weightedBuildMaterials/nobleMetals'), getValue(row, 'crew'), 0)
			
			returnString = returnString .. '\n| ' .. round(waterCost, 1)
			returnString = returnString .. '\n| ' .. round(volatilesCost, 1)
			returnString = returnString .. '\n| ' .. round(metalsCost, 1)
			returnString = returnString .. '\n| ' .. round(noblesCost, 1)
			
			returnString = returnString .. '\n| ' .. getValue(row, 'magazine', 0)
			returnString = returnString .. '\n| ' .. getValue(row, 'ammoMass_kg', 0)
			
			-- Ammo resources
			local ammoVolatiles = calculateAmmoResourceCost(getValue(row, 'magazine'), getValue(row, 'ammoMass_kg'), getValue(row, 'ammoMaterials/volatiles'))
			local ammoMetals = calculateAmmoResourceCost(getValue(row, 'magazine'), getValue(row, 'ammoMass_kg'), getValue(row, 'ammoMaterials/metals'))
			local ammoNobles = calculateAmmoResourceCost(getValue(row, 'magazine'), getValue(row, 'ammoMass_kg'), getValue(row, 'ammoMaterials/nobleMetals'))
			local ammoFissiles = calculateAmmoResourceCost(getValue(row, 'magazine'), getValue(row, 'ammoMass_kg'), getValue(row, 'ammoMaterials/fissiles'))
			local ammoExotics = calculateAmmoResourceCost(getValue(row, 'magazine'), getValue(row, 'ammoMass_kg'), getValue(row, 'ammoMaterials/exotics'))
			local ammoAntimatter = calculateAmmoResourceCost(getValue(row, 'magazine'), getValue(row, 'ammoMass_kg'), getValue(row, 'ammoMaterials/antimatter'))
			
			returnString = returnString .. '\n| ' .. ammoVolatiles
			returnString = returnString .. '\n| ' .. ammoMetals
			returnString = returnString .. '\n| ' .. ammoNobles
			returnString = returnString .. '\n| ' .. ammoFissiles
			returnString = returnString .. '\n| ' .. ammoExotics
			returnString = returnString .. '\n| ' .. ammoAntimatter
			
			returnString = returnString .. '\n| ' 
			if getValue(row, 'shapedChargeAngle', '') ~= '' then
				returnString = returnString .. getValue(row, 'shapedChargeAngle', '') .. '°'
			end
			
			returnString = returnString .. '\n| ' 
			local blastrad = calculateBlastRadius(warheadClass, getValue(row, 'flatDamage_MJ'), getValue(row, 'shapedChargeAngle'))
			if blastrad then
				returnString = returnString .. round(blastrad,1)
			end
			returnString = returnString .. '\n|-'
		end
	end
	
	returnString = returnString .. '\n|}'
	
	return returnString
end

--endregion

return MissileLister