Jump to content
Main menu
Main menu
move to sidebar
hide
Navigation
Main page
Recent changes
Random page
MediaWiki help
Creopedia
Search
Search
Appearance
Log in
Personal tools
Log in
View source for Module:MoveTable
Module
Discussion
English
Read
View source
View history
Tools
Tools
move to sidebar
hide
Actions
Read
View source
View history
General
What links here
Related changes
Special pages
Page information
Appearance
move to sidebar
hide
←
Module:MoveTable
require( 'strict' ) local TableLua = require( 'Module:TableLua' ) local GameTabs = require( 'Module:GameTabs' ) local Element = require( 'Module:Element' ) local IconLink = require( 'Module:IconLink' ) local Move = require( 'Module:Move' ) local p = {} --- The games this module can render, and where each one keeps its move data. --- EvoCreo is a flat list keyed by an UPPER_SNAKE id; EvoCreo 2 carries a --- display name plus nested lookup objects. --- --- `costLabel` differs because the games name the stat differently: EvoCreo --- calls it recharge, EvoCreo 2 calls it energy. Each game has its own tab, --- so each keeps its own wording. local GAMES = { evocreo = { label = 'EvoCreo', costLabel = 'Recharge', data = 'Module:GameData/EvoCreo/MoveData.json' }, evocreo2 = { label = 'EvoCreo 2', costLabel = 'Energy', data = 'Module:GameData/EvoCreo2/MoveTable.json' } } --- Values the two games spell differently while meaning the same thing. --- EvoCreo's HEALING and EvoCreo 2's HEAL are one tier, so the combined table --- folds them together instead of reporting them as a difference between the --- games. Genuine reclassifications, such as a move EvoCreo 2 promoted to --- Super or a heal it moved from Effect to Special, are left alone. local ENUM_ALIASES = { HEALING = 'HEAL' } --- Move icons are 36px of pixel art, shown here at half size, which keeps the --- pixel grid exact and matches the icons the element badges already carry. local ICON_SIZE = '18px' --- Moves that are battle mechanics rather than moves a Creo can be taught. --- No Creo learns them and no tome teaches them; the game plays them on a --- Creo's behalf, gives them no icon, and leaves the two of them that have no --- element out of the element listings anyway. They are left out of these --- tables entirely, and the Moves page describes them in its own section. local MECHANICS = { ['Desperate Strike'] = true, ['Confused Strike'] = true, ['Rest'] = true } --- Normalised move lists, keyed by game id. The combined tables ask for the --- same list once per element, so the work is done once per page instead. local moveCache = {} --- Names of every move EvoCreo 2 carries, for choosing which game's artwork --- illustrates a move. local evocreo2Names --- Turn an UPPER_SNAKE identifier into display text: FIRE_PUNCH -> Fire Punch. --- --- @param id string --- @return string local function toTitle( id ) local words = {} for word in id:gmatch( '[^_]+' ) do table.insert( words, word:sub( 1, 1 ):upper() .. word:sub( 2 ):lower() ) end return table.concat( words, ' ' ) end --- @param value string|nil --- @return string local function formatEnum( value ) if type( value ) ~= 'string' or value == '' or value == 'NONE' then return '—' end return toTitle( ENUM_ALIASES[value:upper()] or value ) end --- Plain numbers keep the column client-sortable; a dash marks missing data. --- EvoCreo 2 stores recharge as a string, so values are coerced rather than --- type-checked. --- --- @param value number|string|nil --- @param scale number|nil --- @return string|number local function formatNumber( value, scale ) local number = tonumber( value ) if number == nil then return '—' end return number * ( scale or 1 ) end --- Normalise one game's move data into a common shape. --- --- The two games name these fields differently: EvoCreo's `type` (Physical, --- Special, Effect) is EvoCreo 2's `moveClass`, and EvoCreo's `skilltype` --- (Normal, Elite, Heal) is EvoCreo 2's `moveType`. The column names here --- follow the Creo pages: Category for the first, Type for the second. --- --- Accuracy is a 0-1 fraction in EvoCreo and already a percentage in --- EvoCreo 2, so the first game's values are scaled to match. --- --- EvoCreo also leaves a field out of a move's record when it holds the --- default, which its data file documents as NORMAL for the skill type. Bite --- and Scratch are the two moves written that way, and they are Normal moves --- rather than moves with no tier. --- --- @param gameId string --- @return table local function getMoves( gameId ) if moveCache[gameId] then return moveCache[gameId] end local moves = mw.loadJsonData( GAMES[gameId].data ) local list = {} for _, move in ipairs( moves ) do local record if gameId == 'evocreo' then record = { name = toTitle( move.id ), element = move.element, category = move.type, skillType = move.skilltype or 'NORMAL', power = formatNumber( move.basedamage ), accuracy = formatNumber( move.accuracy, 100 ), cost = formatNumber( move.recharge ) } else record = { name = move.name, element = move.element and move.element.name, category = move.moveClass and move.moveClass.name, skillType = move.moveType and move.moveType.name, power = formatNumber( move.damage ), accuracy = formatNumber( move.accuracy ), cost = formatNumber( move.recharge ) } end if not MECHANICS[record.name] then table.insert( list, record ) end end moveCache[gameId] = list return list end --- @param name string --- @return boolean local function isInEvoCreo2( name ) if not evocreo2Names then evocreo2Names = {} for _, move in ipairs( getMoves( 'evocreo2' ) ) do evocreo2Names[move.name] = true end end return evocreo2Names[name] == true end --- @param move table --- @param elementName string|nil --- @return boolean local function matchesElement( move, elementName ) if elementName == nil then return true end return type( move.element ) == 'string' and move.element:upper() == elementName:upper() end --- Render the element badge, or a dash for a move with no element. --- --- @param elementName string|nil --- @return string local function formatElement( elementName ) if type( elementName ) ~= 'string' or elementName == '' or elementName == 'NONE' then return '—' end return Element._main( elementName ) end --- Link a move under its own name, behind its icon, and following the --- redirect-free article title for the two moves whose name another subject --- already owns. --- --- A move is illustrated with its EvoCreo 2 artwork, which is the convention --- the move infoboxes follow; the older game's icon stands in for the moves --- that never made it into the sequel. --- --- @param name string --- @return string local function formatLink( name ) local icon = isInEvoCreo2( name ) and Move.getThumbnail( name ) or Move.getThumbnail( name, 'evocreo' ) return IconLink._main( { link = Move.getArticle( name ), text = name, icon = icon, size = ICON_SIZE, class = 'cp-image-pixelated' } ) end --- The element column is dropped when the table is already filtered to one --- element, since every row would repeat the same badge. --- --- @param showElement boolean --- @param gameId string --- @return table local function getColumns( showElement, gameId ) local columns = { { label = 'Move' } } if showElement then table.insert( columns, { label = 'Element' } ) end table.insert( columns, { label = 'Category' } ) table.insert( columns, { label = 'Type' } ) table.insert( columns, { label = 'Power', textAlign = 'number' } ) table.insert( columns, { label = 'Accuracy (%)', textAlign = 'number' } ) table.insert( columns, { label = GAMES[gameId].costLabel, textAlign = 'number' } ) return columns end --- @param move table --- @param showElement boolean --- @return table local function getRow( move, showElement ) local row = { formatLink( move.name ) } if showElement then table.insert( row, formatElement( move.element ) ) end table.insert( row, formatEnum( move.category ) ) table.insert( row, formatEnum( move.skillType ) ) table.insert( row, move.power ) table.insert( row, move.accuracy ) table.insert( row, move.cost ) return row end --- Render the table for a single game. --- --- @param gameId string --- @param elementName string|nil --- @return string local function renderTable( gameId, elementName ) local game = GAMES[gameId] if not game then error( 'Unknown game: ' .. tostring( gameId ) ) end local showElement = elementName == nil local rows = {} for _, move in ipairs( getMoves( gameId ) ) do if matchesElement( move, elementName ) then table.insert( rows, getRow( move, showElement ) ) end end local caption if elementName then caption = string.format( '%s moves in %s', toTitle( elementName ), game.label ) else caption = string.format( 'Moves in %s', game.label ) end return TableLua.render( { caption = caption, hideCaption = true, columns = getColumns( showElement, gameId ), data = rows, class = 'sortable' } ) end --- Wrap a table so the tabbed card styles can stretch it to the panel edges. --- --- @param wikitext string --- @return string local function wrapForCard( wikitext ) return tostring( mw.html.create( 'div' ) :addClass( 't-tabbedCard-table-container' ) :wikitext( wikitext ) ) end --- Render one tab per game inside a tabbed card, the same presentation the --- Creo pages use for their move tables. --- --- @param elementName string|nil --- @return string local function renderTabs( elementName ) local args = { noPadding = true } for _, game in ipairs( GameTabs.getGames() ) do if GAMES[game.id] then args[game.id] = wrapForCard( renderTable( game.id, elementName ) ) end end return GameTabs._main( args ) end -------------------------------------------------------------------------- --- Combined table --- --- One row per move across both games, listing which games it appears in. --- Power, accuracy and the energy cost are left out: they were rebalanced --- between the games and a single column could not honestly hold both. -------------------------------------------------------------------------- --- Join the games' lists into one record per move. --- --- Moves are matched on name, which works because EvoCreo's UPPER_SNAKE ids --- title-case to exactly the names EvoCreo 2 stores: FIRE_PUNCH and --- Fire Punch are the same move. Each record keeps every game's own category --- and type rather than a merged value, because the games disagree often --- enough to matter. EvoCreo has no Super tier at all. --- --- @param elementName string|nil --- @return table[] local function getCombinedMoves( elementName ) local byName = {} local combined = {} for _, game in ipairs( GameTabs.getGames() ) do if GAMES[game.id] then for _, move in ipairs( getMoves( game.id ) ) do if matchesElement( move, elementName ) then local record = byName[move.name] if not record then record = { name = move.name, element = move.element, games = {} } byName[move.name] = record table.insert( combined, record ) end table.insert( record.games, { label = game.name, category = move.category, skillType = move.skillType } ) end end end end table.sort( combined, function ( a, b ) return a.name < b.name end ) return combined end --- Render a field each game answers for itself: one value where the games --- agree, or where only one of them has the move, and one labelled line per --- game where they do not. --- --- @param record table --- @param key string --- @return string local function formatAcrossGames( record, key ) local first = formatEnum( record.games[1][key] ) local lines = {} local differs = false for _, game in ipairs( record.games ) do local value = formatEnum( game[key] ) if value ~= first then differs = true end table.insert( lines, string.format( '%s <small>(%s)</small>', value, game.label ) ) end if not differs then return first end return table.concat( lines, '<br />' ) end --- @param record table --- @return string local function formatGames( record ) local links = {} for _, game in ipairs( record.games ) do table.insert( links, string.format( '[[%s]]', game.label ) ) end return table.concat( links, ', ' ) end --- @param showElement boolean --- @return table local function getCombinedColumns( showElement ) local columns = { { label = 'Move' } } if showElement then table.insert( columns, { label = 'Element' } ) end table.insert( columns, { label = 'Category' } ) table.insert( columns, { label = 'Type' } ) table.insert( columns, { label = 'Games' } ) return columns end --- The caption is written for screen readers but not shown, matching the --- per-game tables. A page stacking one table per element labels them with --- its own section headings, so a visible caption would only repeat those. --- --- @param elementName string|nil --- @return string local function getCombinedCaption( elementName ) if elementName == nil then return 'All moves' end return string.format( '%s moves', toTitle( elementName ) ) end --- @param elementName string|nil --- @return string local function renderCombinedTable( elementName ) local showElement = elementName == nil local rows = {} for _, record in ipairs( getCombinedMoves( elementName ) ) do local row = { formatLink( record.name ) } if showElement then table.insert( row, formatElement( record.element ) ) end table.insert( row, formatAcrossGames( record, 'category' ) ) table.insert( row, formatAcrossGames( record, 'skillType' ) ) table.insert( row, formatGames( record ) ) table.insert( rows, row ) end return TableLua.render( { caption = getCombinedCaption( elementName ), hideCaption = true, columns = getCombinedColumns( showElement ), data = rows, class = 'sortable' } ) end --- Wikitext entry point for the module --- --- @param frame mw.frame --- @return string function p.main( frame ) local getArgs = require( 'Module:Arguments' ).getArgs return p._main( getArgs( frame ) ) end --- Lua entry point for the module --- --- With no `game` argument every game gets its own tab; passing `game` --- renders that one game's table on its own, and `game=all` renders a single --- table spanning the games with a Games column in place of the stats. --- --- @param args table --- @return string function p._main( args ) args = args or {} local elementName = args.element if elementName == '' then elementName = nil end local gameId = args.game or args[1] if gameId == 'all' then return renderCombinedTable( elementName ) end if gameId then return renderTable( gameId, elementName ) end return renderTabs( elementName ) end return p
Template used on this page:
Module:MoveTable/doc
(
view source
)
Return to
Module:MoveTable
.