Module:LaserWeaponLister

From Terra Invicta Official Wiki

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

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

--region Dependencies
local IconModule = require('Module:Icon')
local LaserWeaponData = mw.loadData('Module:CSVReader/LaserWeapon')
local LaserWeaponNames = mw.loadData('Module:ENReader/LaserWeapon')
local ProjectNameBuilder = require('Module:ProjectLister/NameBuilder')
--endregion

--region Private constants
local mountNames = {
    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"
}
local nonShipMountNames = {
    T1BaseDefense = "Point Defense Array",
    T2BaseDefense = "Layered Defense Array", 
    T3BaseDefense = "Battlestation",
    RegionDefense = "[[Space_Defenses|Region]]"
}
local floor = math.floor
local ceil = math.ceil
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 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 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 calculatePowerPerShot(shotpower, eff)
    return tonumber(shotpower) / 1000 / tonumber(eff)
end

local function calculatePowerPerMinute(fireRate, powerPerShot)
    return fireRate * powerPerShot
end

local function calculateArmorEffectiveness(wavelength, beam, jitter, mirror, range)
	local mirrorDiameter = tonumber(mirror) * 0.02
	local spotDiameterPreciseFactor = 1000 * ((1.22 * tonumber(wavelength) / 1000000000 * tonumber(beam))^2 + (2 * tonumber(jitter) * mirrorDiameter)^2) ^ 0.5 / mirrorDiameter
	local spotDiameterPrecise = range * spotDiameterPreciseFactor
	local spotAreaPrecise = 0.7853982 * spotDiameterPrecise ^ 2
	return spotAreaPrecise / 0.005
end

local function calculateMinArmor(shotpower, wavelength, beam, jitter, mirror, range)
	local maxDamage = tonumber(shotpower) * 1.2 / 20
	local armorEffectiveness = calculateArmorEffectiveness(wavelength, beam, jitter, mirror, range)
	return ceil((maxDamage / armorEffectiveness) ^ (1 / 1.5))
end


local function calculateEstimatedDPS(shotpower, fireRate, wavelength, beam, jitter, mirror, range)
	local armorEffectiveness = {}
	armorEffectiveness[1] = calculateArmorEffectiveness(wavelength, beam, jitter, mirror, range)
	armorEffectiveness[2] = calculateArmorEffectiveness(wavelength, beam, jitter, mirror, 200)
	armorEffectiveness[3] = calculateArmorEffectiveness(wavelength, beam, jitter, mirror, 500)
	armorEffectiveness[4] = calculateArmorEffectiveness(wavelength, beam, jitter, mirror, 800)
	local damage = {}
	damage[1] = tonumber(shotpower) / 20 / armorEffectiveness[1]
	damage[2] = tonumber(shotpower) / 20 / armorEffectiveness[2]
	damage[3] = tonumber(shotpower) / 20 / armorEffectiveness[3]
	damage[4] = tonumber(shotpower) / 20 / armorEffectiveness[4]
	return (damage[1] * 0.3 + damage[2] * 0.15 + damage[3] * 0.25 + damage[4] * 0.3) * fireRate / 60
end

local function calculatePDValues(shotpower, fireRate, wavelength, beam, jitter, mirror, multiplier)
	local PDValues = {}
	PDValues[1] = calculateEstimatedDPS(shotpower, fireRate, wavelength, beam, jitter, mirror, 200) * multiplier 
	PDValues[2] = PDValues[1] + calculateEstimatedDPS(shotpower, fireRate, wavelength, beam, jitter, mirror, 400) * multiplier 
	PDValues[3] = PDValues[1] + (calculateEstimatedDPS(shotpower, fireRate, wavelength, beam, jitter, mirror, 300) + calculateEstimatedDPS(shotpower, fireRate, wavelength, beam, jitter, mirror, 600)) * multiplier 
	return PDValues
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:LaserWeaponLister|ListSpecific|detailed=true|alien=true|shipMounted=true}} 
function LaserWeaponLister.ListSpecific(frame)
	local detailed = frame.args['detailed'] == 'true'
	local alien = frame.args['alien'] == 'true'
	local shipMounted = frame.args['shipMounted'] == 'true'
	return LaserWeaponLister.ListLaserWeapons(detailed, alien, shipMounted)
end

---@param detailed boolean whether to show the detailed or basic version.
---@param alien boolean whether to show alien or human version.
---@param shipMounted boolean whether to show the ship or other version.
---@return wiki table of batteries
function LaserWeaponLister.ListLaserWeapons(detailed, alien, shipMounted)
    local returnString = '{| class="wikitable sortable mw-collapsible" style="text-align: center;"'
	returnString = returnString .. '\n! rowspan=2| Laser<br>Weapon'
	if detailed and not alien and shipMounted then
		returnString = returnString .. '\n! rowspan=2| Required<br>Project'
	end
	if detailed and shipMounted then
		returnString = returnString .. '\n! rowspan=2| Hard<br>Points'
		returnString = returnString .. '\n! rowspan=2| Can<br>PD'
	end
	if not shipMounted then
		returnString = returnString .. '\n! rowspan=2| Used<br>By'
	end
    
    returnString = returnString .. '\n! rowspan=2| Damage'
    
    if shipMounted then
    	returnString = returnString .. '\n! rowspan=2| Fire Rate<br>(shots/min)'
    	returnString = returnString .. '\n! rowspan=2| Range<br>(km)'
    end
    
	if detailed then
		returnString = returnString .. '\n! rowspan=2| Wavelength<br>(nm)'
		returnString = returnString .. '\n! rowspan=2| Mirror Radius<br>(cm)'
		returnString = returnString .. '\n! rowspan=2| Beam<br>Quality'
		returnString = returnString .. '\n! rowspan=2| Jitter<br>Radius'
		if shipMounted then
			returnString = returnString .. '\n! rowspan=2| Crew'
			returnString = returnString .. '\n! rowspan=2| Mass<br>(tons)'
			returnString = returnString .. '\n! colspan=5| Build Resources'
			returnString = returnString .. '\n! rowspan=2| Efficiency'
			returnString = returnString .. '\n! rowspan=2| Power Per Shot<br>(GJ)'
			returnString = returnString .. '\n! rowspan=2| Power Per Minute<br>(GJ/min)'
		end
	end
    
    if shipMounted then
    	returnString = returnString .. '\n! colspan=4| Minimum Armor to Block at Range'
    else
    	returnString = returnString .. '\n! colspan=3| Minimum Armor to Block at Range'
    	returnString = returnString .. '\n! colspan=3| PD Value at Range'
    end
    returnString = returnString .. '\n|-'
    
    if detailed and shipMounted then
		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={'exotics'}})
    end
    returnString = returnString .. '\n! 200km'
    returnString = returnString .. '\n! 400km'
    returnString = returnString .. '\n! 600km'
    if shipMounted then
    	returnString = returnString .. '\n! 800km'
    else
    	returnString = returnString .. '\n! 200km'
	    returnString = returnString .. '\n! 400km'
	    returnString = returnString .. '\n! 600km'
    end
    returnString = returnString .. '\n|-'
    
	for rowID, row in pairs(LaserWeaponData) do
		local mount = mountNames[getValue(row, 'mount', '')]
		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
			if (mount and shipMounted) or (not mount and not shipMounted) then
				returnString = returnString .. '\n| ' .. (LaserWeaponNames['TILaserWeaponTemplate.displayName.' .. rowID] or rowID)
				if detailed and not alien and shipMounted then
					returnString = returnString .. '\n| ' .. ProjectNameBuilder.buildName({args={row['requiredProjectName']}})
				end
				if detailed and shipMounted then
					returnString = returnString .. '\n| ' .. mount
					if getValue(row, 'defenseMode', 'false') == 'true' then
						returnString = returnString .. '\n| Yes'
					else
						returnString = returnString .. '\n| No'
					end
				end
				if not shipMounted then
					returnString = returnString .. '\n| ' .. nonShipMountNames[getValue(row, 'mount')]
				end
				local shotpower = getValue(row, 'shotPower_MJ', 0)
				returnString = returnString .. '\n| ' .. tostring(tonumber(shotpower) / 20)
				local fireRate = calculateFireRate(getValue(row, 'salvo_shots',1), getValue(row, 'cooldown_s'), getValue(row, 'intraSalvoCooldown_s'))
				if shipMounted then
					returnString = returnString .. '\n| ' .. fireRate
					returnString = returnString .. '\n| ' .. getValue(row, 'targetingRange_km', 0)
				end
				
				local wavelength = getValue(row, 'wavelength_nm', 0)
				local mirror = getValue(row, 'mirrorRadius_cm', 0)
				local beam = getValue(row, 'beam_quality', 0)
				local jitter = getValue(row, 'jitter_Rad', 0)
				if detailed then
					returnString = returnString .. '\n| ' .. wavelength
					returnString = returnString .. '\n| ' .. mirror
					returnString = returnString .. '\n| ' .. beam
					returnString = returnString .. '\n| ' .. jitter
					if shipMounted then
						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
						
						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)
						local exoticsCost = calculateResourceCost(getValue(row, 'baseWeaponMass_tons'), getValue(row, 'weightedBuildMaterials/exotics'), getValue(row, 'crew'), 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, 'efficiency', 1)
						
						local powerPerShot = calculatePowerPerShot(getValue(row, 'shotPower_MJ'), getValue(row, 'efficiency'))
						returnString = returnString .. '\n| ' .. round(powerPerShot,3)
						
						local powerPerMinute = calculatePowerPerMinute(fireRate, powerPerShot)
						returnString = returnString .. '\n| ' .. round(powerPerMinute,3)
					end
				end
				
				returnString = returnString .. '\n| ' .. calculateMinArmor(shotpower, wavelength, beam, jitter, mirror, 200)
				returnString = returnString .. '\n| ' .. calculateMinArmor(shotpower, wavelength, beam, jitter, mirror, 400)
				returnString = returnString .. '\n| ' .. calculateMinArmor(shotpower, wavelength, beam, jitter, mirror, 600)
				if shipMounted then
					returnString = returnString .. '\n| ' .. calculateMinArmor(shotpower, wavelength, beam, jitter, mirror, 800)
				else
					local multiplier = 30
					if mount ~= 'RegionDefense' then
						multiplier = 60
					end
					local PDValues = calculatePDValues(shotpower, fireRate, wavelength, beam, jitter, mirror, multiplier)
					returnString = returnString .. '\n| ' .. round(PDValues[1],1)
					returnString = returnString .. '\n| ' .. round(PDValues[2],1)
					returnString = returnString .. '\n| ' .. round(PDValues[3],1)
				end
				returnString = returnString .. '\n|-'
			end
		end
	end
	returnString = returnString .. '\n|}'
	return returnString
end

--endregion

return LaserWeaponLister