Module:TraitLister

From Terra Invicta Official Wiki

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

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

--region Dependencies
local RestrictionChecker = require('Module:RestrictionChecker')
local StringReplace = require('Module:StringReplace')
local IconModule = require('Module:Icon')
local TVModule = require('Module:TV')
local TraitData = mw.loadData('Module:CSVReader/Trait')
local TraitNames = mw.loadData('Module:ENReader/Trait')

local CouncilorTypeData = mw.loadData('Module:CSVReader/CouncilorType')
local CouncilorTypeTraits = mw.loadData('Module:TraitLister/Chances')
local CouncilorTypeNames = mw.loadData('Module:ENReader/CouncilorType')

local ProjectNameBuilder = require('Module:ProjectLister/NameBuilder')
--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
local sfind = string.find
local ssub = string.sub
local sreplace = StringReplace.replace

local priorityNamesToIcons = {
	Economy = 'economy',
	Welfare = 'welfare',
	Knowledge = 'knowledge',
	Environment = 'environment',
	Government = 'government priority',
	Unity = 'unity',
	Oppression = 'oppression',
	Funding = 'funding',
	Spoils = 'spoils',
	Civilian_InitiateSpaceflightProgram = 'found space program',
	LaunchFacilities = 'boost priority',
	MissionControl = 'mission control priority',
	Military_FoundMilitary = 'found military',
	Military = 'military priority',
	Military_BuildArmy = 'build army',
	Military_BuildNavy = 'build navy',
	Military_BuildNuclearWeapons = 'nuclear weapons',
	Military_InitiateNuclearProgram = 'develop atomic bomb',
}

local restrictedLocationsString = {
	HomeNation = 'Banned from Home [[Nation]]',
	HomeNationRivals = 'Banned from [[Rival]]s of Home [[Nation]]',
	HighUnrestNations = 'Banned from [[Nations]] with >6 ' .. IconModule.Icon({args={'unrest', bgcolor='DarkSlateGray'}}) .. '[[Unrest]]',
	Space = 'Banned from Space',
}

local conditionStringTable = {
	TICouncilorCondition_bInHomeNation = ' When in Home [[Nation]]',
	TIGlobalCondition_bNuclearWeaponsUsed = ' When ' .. IconModule.Icon({args={'nuke'}}) .. '[[Nuclear Weapons|Nukes]] have been used',
	TIFactionCondition_efResourceValue = ' When {TV}',
	TINationCondition_fInequality = ' When in [[Nation]] with ' .. TVModule.TV({args={'inequality', '{sign}{value}'}}),
	TINationCondition_fEducation = ' When in [[Nation]] with ' .. TVModule.TV({args={'education', '{sign}{value}'}}) ,
	TINationCondition_fUnrest = ' When in [[Nation]] with ' .. TVModule.TV({args={'unrest', '{sign}{value}'}}),
	TINationCondition_fCohesion = ' When in [[Nation]] with ' .. TVModule.TV({args={'cohesion', '{sign}{value}'}}),
	TINationCondition_fDemocracy = ' When in [[Nation]] with ' .. TVModule.TV({args={'democracy', '{sign}{value}'}}),
}

local conditionSignTable = {
	LessThanOrEqualTo = '≤',
	GreaterThanOrEqualTo = '≥',
	LessThan = '<',
	GreaterThan = '>',
	EqualTo = '=',
}

local specialTraitRulesTable = {
	Undercover = 'Can board enemy tier 1 [[Habs]] and [[Spaceships]].',
	LoyaltyLossOnFactionAtrocity = TVModule.TV({args={'loyalty', '{value}'}}) .. " When [[Councilors|Councilor's]] [[Factions|Faction]] commits an [[Atrocity]].",
	LoyaltyLossOnPersonalCritFailure = TVModule.TV({args={'loyalty', '{value}'}}) .. " When [[Councilors|Councilor]] critically fails a [[Missions|Mission]].",
	LoyaltyLossOnFactionCriticalFailure = TVModule.TV({args={'loyalty', '{value}'}}) .. " When any [[Councilors|Councilor]] of the same [[Factions|Faction]] critically fails a [[Missions|Mission]].",
	LoyaltyLossOnFactionHabDestroyedPerTier= TVModule.TV({args={'loyalty', '{value}'}}) .. " When a (non-automated) [[Habs|hab]] of [[Councilors|Councilor's]] [[Factions|Faction]] is destroyed. Larger [[Habs]] will cause a greater loss.",
	LoyaltyLossOnHomeRegionNuked = TVModule.TV({args={'loyalty', '{value}'}}) .. " When home [[Regions|region]] is " .. IconModule.Icon({args={'nuke'}}) .. "[[Nuclear Weapons|Nuked]]. Double when [[Councilors|Councilor's]] [[Factions|Faction]] is responsible",
	LoyaltyLossOnFactionCouncilorKilled = TVModule.TV({args={'loyalty', '{value}'}}) .. " When any [[Councilors|Councilor]] of the same [[Factions|Faction]] is " .. IconModule.MissionIcon({args={'assassinate'}})  .. "[[Assassinate]]d.",
	AtrocityIfAssassinationAttemptUpon = "Any attempt to " .. IconModule.MissionIcon({args={'assassinate'}})  .. "[[Assassinate]] this [[Councilors|Councilor]] that does not critically succeed will count as an [[Atrocity]] for the assassin's [[Factions|Faction]].",         
	MarkedToAssassin = "Any attempt to " .. IconModule.MissionIcon({args={'assassinate'}})  .. '[[Assassinate]] this [[Councilors|Councilor]] that does not critically succeed will give the assassin the "Marked" trait.',
	GlobalPropagandaIfKilled = "If this councilor dies violently, every [[Nation]] on [[Earth]] will receive [[propaganda]] of strength {value} for the [[Councilors|Councilor's]] [[Factions|Faction]].",
	ProjectUnlockChance = 'Increases every [[Technology#Unlocking_Projects|Project Unlock Chance]] by {value}%.'
}
--endregion



--region Private member variables
--none
--endregion

--region Private methods


--endregion

--region Public methods

function TraitLister.ListRestrictedTraits(frame)
	local focus = frame.args['Focus']
	if focus and focus ~= '' then
		focus = mw.text.trim(slower(focus))
		frame.args['Focus'] = nil
	end
	return TraitLister.ListTraits(frame.args, focus)
end

---@param restrictionsTable table of restrictions
---@return wiki table of traits
function TraitLister.ListTraits(restrictionsTable, focus)
	-- TODO add focus handling, maybe.
	
	local returnString = '{| class="wikitable sortable mw-collapsible" '
	returnString = returnString .. '\n|-'
	
	returnString = returnString .. '\n! Trait'
	returnString = returnString .. '\n! Group'
	returnString = returnString .. '\n! Description'
	returnString = returnString .. '\n! Initial Chances'
	returnString = returnString .. '\n! Effects'
	returnString = returnString .. '\n! Requirements'
	
	returnString = returnString .. '\n|-'
	
	for rowID, row in pairs(TraitData) do
		if rowID ~= 'dummy' and RestrictionChecker.AllPass(restrictionsTable, row) then
			returnString = returnString .. '\n| ' .. (TraitNames['TITraitTemplate.displayName.' .. rowID] or rowID)
			returnString = returnString .. '\n| ' .. (row['grouping'] or '')
			returnString = returnString .. '\n| ' .. (TraitNames['TITraitTemplate.description.' .. rowID] or '')
			
			-- Chances
			returnString = returnString .. '\n| ' 
			local chanceRows = ''
			for profession, _ in pairs(CouncilorTypeData) do
				if CouncilorTypeTraits[profession][rowID] > 0 then
					chanceRows = chanceRows .. '\n| ' .. (CouncilorTypeNames['TICouncilorTypeTemplate.displayName.' .. profession] or profession)
					chanceRows = chanceRows .. '\n| ' .. round(CouncilorTypeTraits[profession][rowID],2) .. '%'
					chanceRows = chanceRows .. '\n|-'
				end
			end
			if chanceRows ~= '' then
				returnString = returnString .. '\n{| class="wikitable sortable mw-collapsible" style="text-align: center;"'
				returnString = returnString .. '\n|-'
				returnString = returnString .. '\n! Profession'
				returnString = returnString .. '\n! Chance'
				returnString = returnString .. '\n|-'
				returnString = returnString .. chanceRows
				returnString = returnString .. '\n|}'
			else
				returnString = returnString .. '0%'
			end
			
			-- Effects
			returnString = returnString .. '\n| '
			local incomesString = ''
			local incomeValue = tonumber(row['incomeMoney'] or 0) or 0
			if incomeValue ~= 0 then
				incomesString = incomesString .. TVModule.TV({args={'money', incomeValue}})
			end
			incomeValue = tonumber(row['incomeInfluence'] or 0) or 0
			if incomeValue ~= 0 then
				incomesString = incomesString .. TVModule.TV({args={'influence', incomeValue}})
			end
			incomeValue = tonumber(row['incomeOps'] or 0) or 0
			if incomeValue ~= 0 then
				incomesString = incomesString .. TVModule.TV({args={'ops', incomeValue}})
			end
			incomeValue = tonumber(row['incomeBoost'] or 0) or 0
			if incomeValue ~= 0 then
				incomesString = incomesString .. TVModule.TV({args={'boost', incomeValue}})
			end
			incomeValue = tonumber(row['incomeResearch'] or 0) or 0
			if incomeValue ~= 0 then
				incomesString = incomesString .. TVModule.TV({args={'research', incomeValue}})
			end
			incomeValue = tonumber(row['incomeProjects'] or 0) or 0
			if incomeValue ~= 0 then
				incomesString = incomesString .. TVModule.TV({args={'projects', incomeValue}})
			end
			if incomesString ~= '' then
				returnString = returnString .. '\n* Income: ' .. incomesString
			end
			
			local missionGrantString = ''
			for i = 0, 2 do
				if row['missionsGrantedNames/' .. i] and row['missionsGrantedNames/' .. i] ~= '' then
					missionGrantString = missionGrantString .. IconModule.MissionIcon({args={row['missionsGrantedNames/' .. i]}})
				end
			end
			if missionGrantString ~= '' then
				returnString = returnString .. '\n* Grants ' .. missionGrantString
			end
			
			local missionBlockString = ''
			for i = 0, 4 do
				if row['restrictedMissionNames/' .. i] and row['restrictedMissionNames/' .. i] ~= '' then
					missionBlockString = missionBlockString .. IconModule.MissionIcon({args={row['restrictedMissionNames/' .. i]}})
				end
			end
			if missionBlockString ~= '' then
				returnString = returnString .. '\n* Blocks ' .. missionBlockString
			end
			
			if row['restrictedLocations'] and restrictedLocationsString[row['restrictedLocations']] then
				returnString = returnString .. '\n* ' .. restrictedLocationsString[row['restrictedLocations']]
			end
			
			local XPModifier = tonumber(row['XPModifier'] or 0) or 0
			XPModifier = XPModifier * 100 -- Convert to %
			if XPModifier > 0 then
				returnString = returnString .. '\n* ' .. TVModule.TV({args={'xp', '+' .. XPModifier .. '%'}}) .. ' Costs'
			elseif XPModifier < 0 then
				returnString = returnString .. '\n* ' .. TVModule.TV({args={'xp', XPModifier .. '%'}}) .. ' Costs'
			end
			
			local priorityString = ''
			for i = 0, 2 do
				if row['priorityBonuses/' .. i .. '/priority'] and row['priorityBonuses/' .. i .. '/priority'] ~= '' then
					local priorityBonus = tonumber(row['priorityBonuses/' .. i .. '/bonus'] or 0) or 0
					priorityBonus = priorityBonus * 100 -- Convert to %
					if priorityBonus ~= 0 then
						priorityString = priorityString .. TVModule.TV({args={priorityNamesToIcons[row['priorityBonuses/' .. i .. '/priority']],  priorityBonus .. '%', 'true'}})
					end
				end
			end
			if priorityString ~= '' then
				returnString = returnString .. '\n* ' .. priorityString
			end
			
			local detectionInvBonus = tonumber(row['detectionInvBonus'] or 0) or 0
			local detectionEspBonus = tonumber(row['detectionEspBonus'] or 0) or 0
			if detectionInvBonus ~= 0 then
				returnString = returnString .. '\n* ' .. TVModule.TV({args={'investigation', detectionInvBonus}}) .. ' for [[Detection]]'
			end
			if detectionEspBonus ~= 0 then
				returnString = returnString .. '\n* ' .. TVModule.TV({args={'espionage', detectionEspBonus}}) .. ' for [[Detection]]'
			end
			
			for i = 0, 4 do
				local statValue = row['statMods/' .. i .. '/strValue']
				local statString = row['statMods/' .. i .. '/stat']
				if statValue and statValue ~= '' and statString then
					if statValue == 'Loyalty' then
						statValue = IconModule.Icon({args={'Loyalty'}})
					end
					if row['statMods/' .. i .. '/operation'] == 'Additive' then
						returnString = returnString .. '\n* ' .. TVModule.TV({args={statString, statValue}}) 
					elseif row['statMods/' .. i .. '/operation'] == 'SetToFixedValue' then
						returnString = returnString .. '\n* Set ' .. TVModule.TV({args={statString}})  .. ' to ' .. statValue
					elseif row['statMods/' .. i .. '/operation'] == 'SetToAnotherAttribute' then
						returnString = returnString .. '\n* Set ' .. TVModule.TV({args={statString}}) .. ' to ' .. statValue
					end
					
					local conditionType = row['statMods/' .. i .. '/condition/$type']
					if conditionType and conditionType ~= '' and conditionStringTable[conditionType] then
						local conditionString = conditionStringTable[conditionType]
						conditionString = sreplace(conditionString, '{TV}', TVModule.TV({args={row['statMods/' .. i .. '/condition/strIdx'], '{sign}{value}'}}))
						conditionString = sreplace(conditionString, '{sign}', conditionSignTable[row['statMods/' .. i .. '/condition/sign']])
						conditionString = sreplace(conditionString, '{value}', row['statMods/' .. i .. '/condition/strValue'])
						returnString = returnString .. conditionString
					end
				end
			end
			
			if row['specialTraitRule'] and specialTraitRulesTable[row['specialTraitRule']] then
				returnString = returnString .. '\n* ' .. sreplace(specialTraitRulesTable[row['specialTraitRule']], '{value}', row['specialTraitRuleValue'])
			end
			
			if row['techBonuses/0/category'] and row['techBonuses/0/category'] ~= '' then
				local techBonusValue = tonumber(row['techBonuses/0/bonus'] or 0) or 9
				techBonusValue = techBonusValue * 100 -- Convert to %
				returnString = returnString .. '\n* ' .. TVModule.TV({args={row['techBonuses/0/category'], techBonusValue .. '%', 'true'}}) 
			end
			
			-- Requirements
			returnString = returnString .. '\n| '
			local costsString = ''
			local costValue = tonumber(row['XPCost'] or 0) or 0
			if costValue ~= 0 then
				costsString = costsString .. TVModule.TV({args={'xp', costValue}})
			end
			costValue = tonumber(row['moneyCost'] or 0) or 0
			if costValue ~= 0 then
				costsString = costsString .. TVModule.TV({args={'money', costValue}})
			end
			costValue = tonumber(row['influenceCost'] or 0) or 0
			if costValue ~= 0 then
				costsString = costsString .. TVModule.TV({args={'influence', costValue}})
			end
			costValue = tonumber(row['opsCost'] or 0) or 0
			if costValue ~= 0 then
				costsString = costsString .. TVModule.TV({args={'ops', costValue}})
			end
			costValue = tonumber(row['boostCost'] or 0) or 0
			if costValue ~= 0 then
				costsString = costsString .. TVModule.TV({args={'boost', costValue}})
			end
			if costsString ~= '' then
				returnString = returnString .. '\n* ' .. costsString
			end
			
			if row['projectDataName'] and row['projectDataName'] ~= '' then
				returnString = returnString .. '\n* ' .. ProjectNameBuilder.buildName({args={row['projectDataName']}})
			end
			
			if row['upgradesFrom'] and row['upgradesFrom'] ~= '' then
				returnString = returnString .. '\n* Upgrades from ' .. (TraitNames['TITraitTemplate.displayName.' .. row['upgradesFrom']] or row['upgradesFrom'])
			end
			
			returnString = returnString .. '\n|-'
		end
	end
	returnString = returnString .. '\n|}'
	
	return returnString
end

--endregion

return TraitLister