Module:SpaceBodyLister/Bombard/Single

From Terra Invicta Official Wiki

Documentation for this module may be created at Module:SpaceBodyLister/Bombard/Single/doc

---
---Reads the SpaceBody data files and uses them to compute bombard damage for different weapons.
---
---@module SpaceBodyLister
local SpaceBodyLister = {}

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

local MagneticGunData = mw.loadData('Module:CSVReader/MagneticGun')
local MagneticGunNames = mw.loadData('Module:ENReader/MagneticGun')
local SpaceBodyData = mw.loadData('Module:CSVReader/SpaceBody')
local SpaceBodyBombardData = mw.loadData('Module:SpaceBodyLister/Bombard')
local SpaceBodyNames = mw.loadData('Module:ENReader/SpaceBody')


--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 member variables
--none
--endregion

--region Private methods

-- 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

local function calculatePerAmmoResourceCost( amass, resourceWeight)
    return tonumber(amass) * (tonumber(resourceWeight) or 0) / 10000
end

--endregion

--region Public methods



---@return wiki table of Space Body Bombard Values
function SpaceBodyLister.ListBombardValues(frame)
	if not frame.args[1] or frame.args[1] == '' then
		return "Error: Space Body not specified."
	elseif not SpaceBodyData[frame.args[1]] then
		return "Error: Space Body " .. frame.args[1] .. " not found."
	end
	
	local spaceBodyName = (SpaceBodyNames['TISpaceBodyTemplate.displayName.' .. frame.args[1]] or frame.args[1])
	local spaceBodyMass =  SpaceBodyData[frame.args[1]]['mass_kg']
	local computedMeanRadius = SpaceBodyBombardData[frame.args[1]]['computedMeanRadius']
	local SOIradius = SpaceBodyBombardData[frame.args[1]]['SOIradius']
	local surfaceGravity =  SpaceBodyBombardData[frame.args[1]]['surfaceGravity']
	local atmosphere = SpaceBodyData[frame.args[1]]['atmosphere']
	local atmosphereScaleHeight_km = SpaceBodyData[frame.args[1]]['atmosphereScaleHeight_km']
	local atmosphereSurfaceDensity_kgpm3 = SpaceBodyData[frame.args[1]]['atmosphereSurfaceDensity_kgpm3']

	local distanceToSpaceBodyCoreM = {}
	distanceToSpaceBodyCoreM[1] = computedMeanRadius * 1000 + 200000
	distanceToSpaceBodyCoreM[2] = computedMeanRadius * 1000 + 400000
	distanceToSpaceBodyCoreM[3] = computedMeanRadius * 1000 + 600000
	
	local meanOrbitalSpeedMS = {}
	meanOrbitalSpeedMS[1] = (6.67384E-11 * spaceBodyMass / distanceToSpaceBodyCoreM[1]) ^ 0.5
	meanOrbitalSpeedMS[2] = (6.67384E-11 * spaceBodyMass / distanceToSpaceBodyCoreM[2]) ^ 0.5
	meanOrbitalSpeedMS[3] = (6.67384E-11 * spaceBodyMass / distanceToSpaceBodyCoreM[3]) ^ 0.5
	
	local initialGravitationalAccelerationMS2 = {}
	local averageGravitationalAccelerationMS2 = {}
	for i = 1, 3 do
		if distanceToSpaceBodyCoreM[i] > SOIradius * 1000 then
			initialGravitationalAccelerationMS2[i] = 0 
		else
			initialGravitationalAccelerationMS2[i] = 6.67384E-11 * spaceBodyMass / (distanceToSpaceBodyCoreM[i] ^ 2)
		end
		averageGravitationalAccelerationMS2[i] = (surfaceGravity + initialGravitationalAccelerationMS2[i]) / 2
	end
	
	local returnString = '{| class="wikitable sortable mw-collapsible" style="text-align: center;"'
	returnString = returnString .. '\n|+ Bombardment Values for ' .. spaceBodyName
	returnString = returnString .. '\n|-'
	returnString = returnString .. '\n! rowspan=2 | Weapon'
	returnString = returnString .. "\n! colspan=3 | Damage at Altitude"
	returnString = returnString .. "\n! colspan=3 | Anti-PD Value at Altitude"
	returnString = returnString .. "\n! colspan=3 | Cost per Ammo"

	returnString = returnString .. '\n|-'
	returnString = returnString .. '\n! 200km'
	returnString = returnString .. '\n! 400km'
	returnString = returnString .. '\n! 600km'
	returnString = returnString .. '\n! 200km'
	returnString = returnString .. '\n! 400km'
	returnString = returnString .. '\n! 600km'
	returnString = returnString .. '\n!' .. IconModule.Icon({args={'volatiles'}})
	returnString = returnString .. '\n!' .. IconModule.Icon({args={'metals'}})
	returnString = returnString .. '\n!' .. IconModule.Icon({args={'nobles'}})
	returnString = returnString .. '\n|-'
	
	for rowID, row in pairs(MagneticGunData) do
        returnString = returnString .. '\n| ' .. (MagneticGunNames['TIMagneticGunTemplate.displayName.' .. rowID] or rowID)
		
		local initialProjectileVelocityMS = {}
		local initialProjectileKineticEnergyJ = {}
		local initialProjectileGravitationalPotentialEnergyJ = {}
		local totalInitialProjectileEnergyJ = {}
		local baseSurfaceImpactVelocityMS = {}
		for i = 1, 3 do
			initialProjectileVelocityMS[i] = ((row['muzzleVelocity_kps'] * 1000) ^ 2 + meanOrbitalSpeedMS[i] ^ 2) ^ 0.5
			initialProjectileKineticEnergyJ[i] = 0.5 * row['warheadMass_kg'] * initialProjectileVelocityMS[i] ^ 2
			initialProjectileGravitationalPotentialEnergyJ[i] = row['warheadMass_kg'] * 200000 * i * averageGravitationalAccelerationMS2[i]
			totalInitialProjectileEnergyJ[i] = initialProjectileKineticEnergyJ[i] + initialProjectileGravitationalPotentialEnergyJ[i]
			baseSurfaceImpactVelocityMS[i] = (2 * totalInitialProjectileEnergyJ[i] / row['warheadMass_kg']) ^ 0.5
		end
		
		local actualSurfaceImpactVelocityMS = {}
		if atmosphere == 'Trace' or atmosphere == 'None' then
			actualSurfaceImpactVelocityMS = baseSurfaceImpactVelocityMS
		else 
			local lengthOfProjectilePathThroughAtmosphereM = {}
			local energyLostToAtmosphericDragJ = {}
			local remainingEnergyJ = {}
			local modifiedSurfaceImpactVelocityMS = {}
			local terminalVelocityMS = (2 * row['warheadMass_kg'] * surfaceGravity / 4.0000003E-5 / atmosphereSurfaceDensity_kgpm3) ^ 0.5
			for i = 1,3 do 
				lengthOfProjectilePathThroughAtmosphereM[i] = atmosphereScaleHeight_km * initialProjectileVelocityMS[i] / row['muzzleVelocity_kps']
				energyLostToAtmosphericDragJ[i] = atmosphereSurfaceDensity_kgpm3 * lengthOfProjectilePathThroughAtmosphereM[i] * 0.001 * initialProjectileVelocityMS[i] ^ 2 
				remainingEnergyJ[i] = totalInitialProjectileEnergyJ[i] - energyLostToAtmosphericDragJ[i]
				if remainingEnergyJ[i] < 0 then
					remainingEnergyJ[i] = 0
				end
				modifiedSurfaceImpactVelocityMS[i] = (2 * remainingEnergyJ[i] / row['warheadMass_kg']) ^ 0.5
				if baseSurfaceImpactVelocityMS[i] > terminalVelocityMS then
					if terminalVelocityMS > modifiedSurfaceImpactVelocityMS[i] then
						actualSurfaceImpactVelocityMS[i] = terminalVelocityMS
					else 
						actualSurfaceImpactVelocityMS[i] = modifiedSurfaceImpactVelocityMS[i]
					end
				else 
					if terminalVelocityMS < modifiedSurfaceImpactVelocityMS[i] then
						actualSurfaceImpactVelocityMS[i] = terminalVelocityMS
					else 
						actualSurfaceImpactVelocityMS[i] = modifiedSurfaceImpactVelocityMS[i]
					end
				end
			end
		end
		
		local impactEnergyJ = {}
		local projectileDamage = {}
		local antiPDValue = {}
		local averageCooldown = tonumber(row['cooldown_s'])
		if row['salvo_shots'] and row['salvo_shots'] ~= '' and tonumber(row['salvo_shots']) then
			averageCooldown = (averageCooldown + (tonumber(row['salvo_shots']) - 1) * tonumber(row['intraSalvoCooldown_s'])) / tonumber(row['salvo_shots'])
		end
		for i = 1,3 do
			impactEnergyJ[i] = 0.5 * row['warheadMass_kg'] * actualSurfaceImpactVelocityMS[i] ^ 2
			projectileDamage[i] = impactEnergyJ[i] / 20000000
			antiPDValue[i] = impactEnergyJ[i] / 1000000 / averageCooldown
		end
		
		local directDamageFraction = 1 - tonumber(row['flatChipping'])
		local directDamage = {}
		for i = 1,3 do
			directDamage[i] = directDamageFraction * projectileDamage[i]
			if directDamage[i] < projectileDamage[i] / 2 and frame.args[1] == 'Earth' then
				directDamage[i] = projectileDamage[i] / 2
			end
		end
		returnString = returnString .. '\n| ' .. round(directDamage[1],3)
		returnString = returnString .. '\n| ' .. round(directDamage[2],3)
		returnString = returnString .. '\n| ' .. round(directDamage[3],3)
		returnString = returnString .. '\n| ' .. round(antiPDValue[1],3)
		returnString = returnString .. '\n| ' .. round(antiPDValue[2],3)
		returnString = returnString .. '\n| ' .. round(antiPDValue[3],3)
		
		-- Ammo resources
		local ammoVolatiles = calculatePerAmmoResourceCost(getValue(row, 'ammoMass_kg'), getValue(row, 'ammoMaterials/volatiles'))
		local ammoMetals = calculatePerAmmoResourceCost(getValue(row, 'ammoMass_kg'), getValue(row, 'ammoMaterials/metals'))
		local ammoNobles = calculatePerAmmoResourceCost(getValue(row, 'ammoMass_kg'), getValue(row, 'ammoMaterials/nobleMetals'))
		
		returnString = returnString .. '\n| ' .. round(ammoVolatiles, 5)
		returnString = returnString .. '\n| ' .. round(ammoMetals, 5)
		returnString = returnString .. '\n| ' .. round(ammoNobles, 5)
		
    	returnString = returnString .. '\n|-'
	end
	returnString = returnString .. '\n|}'
	
	return returnString
end


--endregion

return SpaceBodyLister