Jump to content

Module:Creo/Components/Abilities

From Creopedia

This documentation is transcluded from Module:Creo/Components/Abilities/doc.

Output the abilities table for Module:Creo using Module:TableLua.


require( 'strict' )

local gameTabs = require( 'Module:GameTabs' )
local tableLua = require( 'Module:TableLua' )
local level = require( 'Module:Level' )._main
local nameCell = require( 'Module:Creo/NameCell' ).render
local getAbilityThumbnail = require( 'Module:Ability' ).getThumbnail
local ecGameData = require( 'Module:GameData/EvoCreo' )
local ec2GameData = require( 'Module:GameData/EvoCreo2' )

local p = {}

--- Cached tutor lookup (nil until first use).
local tutors

--- Where each EvoCreo 2 ability is taught.
---
--- Derived from the tutor NPC dialogue in the EvoCreo 2 localization
--- workbook, whose keys encode the map the NPC stands on.
---
--- @return table
local function getTutors()
	if tutors == nil then
		tutors = mw.loadJsonData( 'Module:Creo/Components/Abilities/Tutors.json' )
	end

	return tutors
end

--- Get abilities for EvoCreo2 from creoTypes
---
--- Several abilities are reachable from more than one creo type -- Dive from
--- both Aquatic and Diver, Smash from Golem, Brawler and Smasher -- so a Creo
--- carrying two of them would otherwise list that ability once per type.
--- Ahura, Bibo, Seacean and Seffapede each hit this.
---
--- @param data table[]
--- @return table[]
local function getEvoCreo2AbilityMaps( data )
	if data == {} then
		return {}
	end

	local abilities = {}
	local seen = {}

	local creoTypeNames = {}
	for _, creoType in ipairs( data ) do
		table.insert( creoTypeNames, creoType.name )
	end

	for _, creoTypeName in ipairs( creoTypeNames ) do
		local creoType = ec2GameData.getCreoType( creoTypeName )
		if creoType then
			for _, ability in ipairs( creoType.abilities ) do
				if ability.name ~= nil and not seen[ability.name] then
					seen[ability.name] = true
					table.insert( abilities, ability )
				end
			end
		end
	end

	return abilities
end

--- @param levelNum number
--- @return string
local function getLevelWikitext( levelNum )
	-- HACK: Create a hidden element so that Module:TableLua can sort by level
	-- We need this because we are passing HTML in getRows()
	local sort = mw.html.create( 'span' )
	sort
		:attr( 'hidden', 'true' )
		:css( 'display', 'none' )
		:wikitext( tostring( levelNum ) )

	return tostring( sort ) .. ' ' .. level( levelNum )
end

--- Only EvoCreo 2 has ability art, and EvoCreo 1's field abilities are a
--- different set, but a handful of names overlap (Ride, Dive). The icon is
--- asked for either way and Module:Creo/NameCell drops it when the file does
--- not exist, so the overlapping rows borrow the EvoCreo 2 art and everything
--- else stays text-only.
---
--- @param name string
--- @return string
local function getAbilityWikitext( name )
	return nameCell( getAbilityThumbnail( name ), name, name )
end

--- Name the tutor who teaches an ability and link where they are found, e.g.
--- 'Fly tutor in Electree City'.
---
--- @param abilityName string
--- @return string|nil
local function getTutorWikitext( abilityName )
	local tutor = getTutors()[abilityName]

	if tutor == nil or tutor.locations == nil then
		return nil
	end

	local links = {}
	for _, location in ipairs( tutor.locations ) do
		table.insert( links, string.format( '[[%s]]', location ) )
	end

	if #links == 0 then
		return nil
	end

	return string.format( '%s tutor in %s', abilityName, table.concat( links, ' or ' ) )
end

--- @param args table
--- @param gameId string
--- @param abilityName string
--- @return string|nil
local function getUnlockArg( args, gameId, abilityName )
	local argName = string.format( '%s_%s_unlock', gameId, abilityName )
	return args[argName]
end

--- @param gameId string
--- @return TableColumn[]
local function getColumns( gameId )
	if gameId == 'evocreo2' then
		return {
			{ id = 'name', label = 'Name' },
			{ id = 'unlock', label = 'Unlock' }
		}
	elseif gameId == 'evocreo' then
		return {
			{ id = 'name', label = 'Name' },
			{ id = 'unlock', label = 'Unlock' }
		}
	end

	return {}
end

--- @param data table
--- @param gameId string
--- @param args table
--- @return TableRow[]
local function getRows( data, gameId, args )
	local rows = {}

	if gameId == 'evocreo2' then
		for _, ability in ipairs( getEvoCreo2AbilityMaps( data ) ) do
			if ability.name ~= nil then
				local row = {
					getAbilityWikitext( ability.name ),
					getUnlockArg( args, gameId, ability.name )
						or getTutorWikitext( ability.name )
						or '-'
				}
				table.insert( rows, row )
			end
		end
	elseif gameId == 'evocreo' then
		for _, ability in ipairs( data ) do
			if ability.id ~= nil then
				local abilityName = ecGameData.getName( ability.id )
				local row = {
					getAbilityWikitext( abilityName ),
					getUnlockArg( args, gameId, abilityName )
						or getLevelWikitext( ability.level )
				}
				table.insert( rows, row )
			end
		end
	end

	return rows
end

--- @param data table
--- @param gameId string
--- @param args table
--- @return TableProps
local function getTableProps( data, gameId, args )
	return {
		caption = 'Abilities',
		hideCaption = true,
		columns = getColumns( gameId ),
		data = getRows( data, gameId, args ),
		-- NOTE: no game defines a `level` column here, so this sort is a silent
		-- no-op and the rows keep their game-data order. Left as-is because the
		-- EvoCreo 2 unlock cell holds a tutor location rather than a level,
		-- which would sort alphabetically and read as arbitrary.
		sort = {
			level = 'asc',
		},
		class = table.concat( {
			't-creo-abilities-table',
			'wikitable--fluid' -- Citizen built-in class for broad tables
		}, ' ' )
	}
end

--- @param gameData table
--- @param gameId string
--- @param args table
--- @return string
local function getGameTabWikitext( gameData, gameId, args )
	local html = mw.html.create( 'div' ):addClass( 't-tabbedCard-table-container' )
	html:wikitext( tableLua.render( getTableProps( gameData, gameId, args ) ) )
	return tostring( html )
end

--- @param data table
--- @param args table
--- @return mw.html
local function getAbilitiesHtml( data, args )
	local html = mw.html.create( 'div' ):addClass( 't-creo-abilities' )

	local gameTabsData = gameTabs.mapGameArgs( data, function ( gameData, game )
		return getGameTabWikitext( gameData, game.id, args )
	end )
	gameTabsData.noPadding = true

	html:wikitext( gameTabs._main( gameTabsData ) )

	return html
end

--- @param context ComponentContext
--- @return string
function p.render( context )
	return tostring( getAbilitiesHtml( context.data, context.args ) )
end


return p