Module:Move/Components/Infobox
Appearance
This documentation is transcluded from Module:Move/Components/Infobox/doc.
Output an infobox for Module:Move using Module:InfoboxLua.
require( 'strict' )
local infoboxLua = require( 'Module:InfoboxLua' )
local gameData = require( 'Module:GameData' )
local effect = require( 'Module:Effect' )
local getThumbnail = require( 'Module:Move' ).getThumbnail
local energy = require( 'Module:Energy' )._main
local badge = require( 'Module:BadgeLua' )
local element = require( 'Module:Element' )
local p = {}
--- Move icons are roughly 36px of pixel art. Shown at 4x, with pixelated
--- rendering so the upscale stays crisp instead of being smoothed.
local ICON_SIZE = 144
--- The games name the same move type differently, and EvoCreo has a NONE type
--- that carries no colour of its own.
local moveTypeIds = {
NORMAL = 'normal',
SUPER = 'super',
ELITE = 'elite',
HEAL = 'heal',
HEALING = 'heal'
}
--- @param name string
--- @return string
local function titleCase( name )
return name:sub( 1, 1 ):upper() .. name:sub( 2 ):lower()
end
--- Badges return an empty string rather than nil for a field the game does not
--- record, so the row can be concatenated without leaving holes in the table.
---
--- @param name string|nil
--- @return string
local function getMoveTypeBadge( name )
if not name then
return ''
end
local id = moveTypeIds[name:upper()]
return badge.render( {
text = name,
class = id and ( 't-move-type t-move-type--' .. id ) or nil
} )
end
--- @param name string|nil
--- @return string
local function getClassBadge( name )
if not name then
return ''
end
return badge.render( {
text = name,
class = 't-move-class t-move-class--' .. name:lower()
} )
end
--- Element badges are looked up from a fixed table of the nine elements.
--- EvoCreo gives its typeless moves an element of NONE, which has no entry
--- there, so those fall back to a plain badge instead of raising an error.
---
--- @param name string|nil
--- @return string
local function getElementBadge( name )
if not name then
return ''
end
local ok, html = pcall( element._main, name )
if ok then
return html
end
return badge.render( { text = titleCase( name ) } )
end
--- The row of badges heading a game's stats.
---
--- Move type, category and element sit inside the game's own section rather
--- than in the infobox header, because the games disagree about them: of the
--- 162 moves they share, 57 change move type between EvoCreo and EvoCreo 2 and
--- 27 change category.
---
--- @param move table
--- @param args table
--- @return string
local function getBadgesHtml( move, args )
local badges = {
getMoveTypeBadge( args.type or move.type ),
getClassBadge( args.class or move.class ),
getElementBadge( args.element or move.element )
}
return tostring( mw.html.create( 'div' )
:addClass( 't-move-infobox-badges' )
:wikitext( table.concat( badges ) )
)
end
--- Normalise one game's record of a move into the fields this infobox renders.
---
--- The two games spell the same fields differently, and EvoCreo stores accuracy
--- as a fraction rather than a percentage. EvoCreo also leaves an attribute out
--- of a move's record rather than writing its default value, so every field
--- taken from it needs that default supplied here.
---
--- @param data table
--- @param gameId string
--- @return table|nil
local function getMove( data, gameId )
if gameId == 'evocreo2' then
local ec2 = data.evocreo2
if not ec2 then
return nil
end
return {
name = ec2.name,
class = ec2.moveClass.name,
type = ec2.moveType.name,
element = ec2.element.name,
damage = ec2.damage,
accuracy = ec2.accuracy,
recharge = ec2.recharge,
-- Only EvoCreo 2 records what a move actually does. The EvoCreo
-- data has no effects at all, so its section leaves this nil and
-- renders no Effects block.
effectMaps = ec2.moveEffectMaps,
icon = getThumbnail( ec2.name )
}
end
local ec1 = data.evocreo
if not ec1 then
return nil
end
return {
name = data.name,
class = ec1.type,
-- Bite and Scratch carry no skill type of their own.
type = ec1.skilltype or 'NORMAL',
element = ec1.element,
damage = ec1.basedamage,
accuracy = math.floor( tonumber( ec1.accuracy or 1 ) * 100 + 0.5 ),
-- Confused Strike and Rest carry no recharge, meaning none is needed.
recharge = ec1.recharge or 0,
icon = getThumbnail( data.name, 'evocreo' )
}
end
--- Every game this move appears in, in the order the games are defined.
---
--- @param data table
--- @return table[]
local function getMoves( data )
local moves = {}
for _, game in ipairs( gameData.getGames() ) do
local move = getMove( data, game.id )
if move then
table.insert( moves, { game = game, move = move } )
end
end
return moves
end
--- @param move table
--- @param gameId string
--- @param args table
--- @return table[]
local function getStatsItems( move, gameId, args )
local statsItems = {
{
label = 'Power',
content = args.power or tostring( move.damage )
},
{
label = 'Accuracy',
content = args.accuracy or tostring( move.accuracy )
},
{
-- EvoCreo moves take a number of turns to recharge; they do not
-- consume energy the way EvoCreo 2 moves do.
label = gameId == 'evocreo' and 'Recharge' or 'Energy',
content = energy( args.energy or move.recharge )
}
}
-- Appended rather than built in, because a move with no effects should show
-- no Effects block at all, and the EvoCreo section never has any.
for _, card in ipairs( effect.getInfoboxCards( move.effectMaps, 'move' ) ) do
table.insert( statsItems, card )
end
return statsItems
end
--- One game's account of the move: how it is classified, then its numbers.
---
--- @param entry table
--- @param args table
--- @return table
local function getGameSection( entry, args )
return {
label = entry.game.name,
columns = 3,
content = getBadgesHtml( entry.move, args ),
items = getStatsItems( entry.move, entry.game.id, args )
}
end
--- Every field below the header can differ between the games, so each game
--- gets its own tab. A move that only ever appeared in one game gets a plain
--- section instead, which needs no heading to say which game it belongs to.
---
--- @param moves table[]
--- @param args table
--- @return table
local function getGamesSection( moves, args )
if #moves == 1 then
local section = getGameSection( moves[1], args )
section.label = nil
return section
end
local sections = {}
for _, entry in ipairs( moves ) do
table.insert( sections, getGameSection( entry, args ) )
end
return {
sections = sections
}
end
--- Build the infobox data needed from the raw data
---
--- @param moves table[]
--- @param args table
--- @return table
local function getInfoboxData( moves, args )
local primary = moves[1].move
return {
title = primary.name,
image = {
src = args.image or primary.icon,
size = ICON_SIZE,
class = 'cp-image-pixelated t-move-infobox-image'
},
sections = {
getGamesSection( moves, args )
}
}
end
--- @param context ComponentContext
--- @return string
function p.render( context )
local moves = getMoves( context.data )
if #moves == 0 then
return tostring(
mw.html.create( 'div' )
:addClass( 'error' )
:wikitext( 'Move data is currently unavailable.' )
)
end
return mw.getCurrentFrame():extensionTag {
name = 'templatestyles', args = { src = 'Module:Move/Components/Infobox/styles.css' }
} .. infoboxLua.render( getInfoboxData( moves, context.args or {} ) )
end
return p