Jump to content

Module:Creo/Components/Evolution

From Creopedia

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

Implements Template:Creo/Evolution.


require( 'strict' )

local getThumbnail = require( 'Module:Creo' ).getThumbnail
local cardLua = require( 'Module:CardLua' )
local element = require( 'Module:Element' )._main
local ec2GameData = require( 'Module:GameData/EvoCreo2' )

local p = {}


--- Build the evolution data structure from the raw arguments
---
--- @param args table
--- @return table
local function getEvolutionsData( args )
	local tempNodes = {}
	for k, v in pairs( args ) do
		if type( k ) == 'string' then
			local parts = mw.text.split( k, '_' )
			-- Example key: "creo_1_1_name" -> parts: {"creo", "1", "1", "name"}
			if parts[1] == 'creo' and #parts >= 3 then
				table.remove( parts, 1 )       -- Remove "creo" -> {"1", "1", "name"}
				local prop = table.remove( parts, #parts ) -- prop: "name", parts: {"1", "1"}

				-- We only care about name and content properties
				if prop == 'name' or prop == 'content' then
					local path = table.concat( parts, '_' ) -- path: "1_1"
					tempNodes[path] = tempNodes[path] or {}
					tempNodes[path][prop] = v
				end
			end
		end
	end

	local evolutions = {}
	local nodes = {} -- path -> final node map

	local sortedPaths = {}
	for path in pairs( tempNodes ) do
		table.insert( sortedPaths, path )
	end
	table.sort( sortedPaths )

	for _, path in ipairs( sortedPaths ) do
		local data = tempNodes[path]
		-- A node is only valid if it has a name.
		if data.name then
			local node = {
				name = data.name,
				content = data.content,
				evolutions = {}
			}

			local parentPath = path:match( '^(.*)_[^_]+$' )

			if parentPath then
				local parentNode = nodes[parentPath]
				if parentNode then
					table.insert( parentNode.evolutions, node )
				end
			else -- It's a root node
				table.insert( evolutions, node )
			end
			nodes[path] = node
		end
	end

	return evolutions
end

--- How many nodes a parsed argument tree describes.
---
--- Every Creo article was created with a stub that names only the Creo itself,
--- with the content "Evolution text for <Name>". A lone root describes no
--- family, so it is treated as unwritten and the tree comes from the game data
--- instead; a page that does spell out its family keeps what was written.
---
--- @param evolutions table|nil
--- @return number
local function countNodes( evolutions )
	local total = 0

	for _, node in ipairs( evolutions or {} ) do
		total = total + 1 + countNodes( node.evolutions )
	end

	return total
end

--- Describe what triggers one evolution.
---
--- The export only ever sets a level or an element affinity, but item and
--- loyalty evolutions exist in the schema and are handled so a future export
--- does not render a blank card.
---
--- @param step table
--- @return string|nil
local function getConditionWikitext( step )
	if step.element then
		return string.format( 'Maxed %s affinity', tostring( element( step.element ) ) )
	end

	if step.level then
		return string.format( 'Evolves at Level %s', tostring( step.level ) )
	end

	if step.item then
		return string.format( 'Evolves with %s', tostring( step.item ) )
	end

	if step.loyalty then
		return string.format( 'Evolves at %s loyalty', tostring( step.loyalty ) )
	end

	return nil
end

--- Build the family tree around the Creo whose article this is.
---
--- The index only records where an evolution leads, so the parent map is
--- rebuilt here in order to climb to the base form. Every member of a family
--- then renders the same tree, with its own entry marked as the current one by
--- getEvolutionListHtml.
---
--- @param name string
--- @return table
local function getEvolutionsFromData( name )
	local index = ec2GameData.getEvolutionIndex() or {}

	local parents = {}
	for parent, steps in pairs( index ) do
		for _, step in ipairs( steps ) do
			parents[step.name] = parent
		end
	end

	-- Climb to the base form. `seen` is paranoia: the export describes a tree
	-- today, and a cycle introduced later would otherwise hang the render.
	local root = name
	local seen = {}
	while parents[root] and not seen[root] do
		seen[root] = true
		root = parents[root]
	end

	--- @param creoName string
	--- @param content string|nil
	--- @return table
	local function build( creoName, content )
		local node = {
			name = creoName,
			content = content,
			evolutions = {}
		}

		for _, step in ipairs( index[creoName] or {} ) do
			table.insert( node.evolutions, build( step.name, getConditionWikitext( step ) ) )
		end

		return node
	end

	local content = 'Does not evolve'
	if index[root] then
		content = 'Base form'
	end

	return { build( root, content ) }
end

--- Return the wikitext for the evolution list item
---
--- @param name string
--- @param content string
--- @return string
local function getEvolutionListItemWikitext( name, content )
	return cardLua.render( {
		title = name,
		page = name,
		description = content,
		thumbnail = getThumbnail( name ),
		thumbnailSize = '80px',
		class = 'cp-image-pixelated'
	} )
end

--- Return the wikitext for the evolution list
---
--- @param evolutions table
--- @return mw.html|nil
local function getEvolutionListHtml( evolutions )
	if not evolutions or #evolutions == 0 then
		return nil
	end

	local currentPage = mw.title.getCurrentTitle().fullText

	local list = mw.html.create( 'ul' ):addClass( 't-creo-evolution-tree' )

	for _, evoNode in ipairs( evolutions ) do
		local li = list:tag( 'li' ):addClass( 't-creo-evolution-tree-item' )

		li:wikitext( getEvolutionListItemWikitext( evoNode.name, evoNode.content ) )

		if evoNode.name == currentPage then
			li:addClass( 't-creo-evolution-tree-item--current' )
		end

		local sublist = getEvolutionListHtml( evoNode.evolutions )
		if sublist then
			li:node( sublist )
		end
	end

	return list
end

--- Build the evolution list HTML recursively
---
--- @param evolutions table
--- @return mw.html
local function getEvolutionsHtml( evolutions )
	local html = mw.html.create( 'div' )
	html
		:addClass( 't-creo-evolution' )
		:tag( 'div' )
		:addClass( 't-creo-evolution-wrapper' )
		:node( getEvolutionListHtml( evolutions ) )
		:done()

	return html
end

--- Return the wikitext for the evolution list
---
--- @param context ComponentContext
--- @return string
function p.render( context )
	local evolutions = getEvolutionsData( context.args or {} )

	if countNodes( evolutions ) < 2 then
		evolutions = getEvolutionsFromData( mw.title.getCurrentTitle().text )
	end

	return mw.getCurrentFrame():extensionTag {
		name = 'templatestyles', args = { src = 'Module:Creo/Components/Evolution/styles.css' }
	} .. tostring( getEvolutionsHtml( evolutions ) )
end

return p