Module:ItemTable
Appearance
Documentation for this module may be created at Module:ItemTable/doc
require( 'strict' )
--- One table of items per item type, joined across both games.
---
--- Modelled on Module:MoveTable, which does the same job for moves: read each
--- game's own data page, match records by name, and render one sortable table.
local tableLua = require( 'Module:TableLua' )
local itemLink = require( 'Module:ItemLink' )._main
local ecGameData = require( 'Module:GameData/EvoCreo' )
local p = {}
local EC2_ITEMS = 'Module:GameData/EvoCreo2/ItemTable.json'
local EC1_ITEMS = 'Module:GameData/EvoCreo/ItemData.json'
--- Which items actually have an uploaded icon.
---
--- Module:Item.getThumbnail builds a file name from the item name and never
--- checks that the file exists, so an item without art renders a red file link.
--- Testing each one with mw.title would work but costs an expensive parser
--- function per row, and this table runs to roughly five hundred rows against a
--- cap of five hundred. The list is generated instead, by
--- .claude/tools/items.py, and verified against the wiki.
local ICONS = 'Module:ItemTable/Icons.json'
--- The same, for the items that only ever appeared in EvoCreo 1.
---
--- Those have no EvoCreo 2 artwork to be illustrated with, so they fall back to
--- the icon from the game they did appear in. Generated by
--- `.claude/tools/items.py ec1index`.
local EC1_ICONS = 'Module:ItemTable/EvoCreoIcons.json'
--- Creo names, used to keep creo out of the item tables.
---
--- EvoCreo 1 sells a dozen creo straight out of its general item data, with a
--- cost attached: Pepita, Elacat, Deor and friends. They are creo rather than
--- items, and their articles are {{Creo}} pages, so a row for one would point
--- an item at a creo article.
local CREO = 'Module:GameData/EvoCreo2/CreoElementIndex.json'
local PRESENT = '✓'
local ABSENT = '—'
--- EvoCreo 1 ids whose EvoCreo 2 display name splits into different words, so
--- getName() cannot reproduce it. This is also why Breadcrumbs is missing from
--- Category:Items in EvoCreo, which matches on the same derivation.
---
--- Only spacing needs an entry here. A name the two games merely capitalise
--- differently is handled by getKey() below.
local ALIASES = {
BREAD_CRUMBS = 'Breadcrumbs'
}
--- Items whose own name is taken by another subject, so the article lives
--- elsewhere. Deter is a trait as well as an item.
local ARTICLES = {
Deter = 'Deter (item)'
}
--- Records the wiki does not cover.
---
--- The five BGC pieces are equipment entries with no artwork anywhere and no
--- description in any export, so a row for one could only ever be a name. Their
--- stub articles were deleted, and dropping the rows here is what keeps the
--- table from pointing at them.
---
--- Black Gemma is the same shape: an EvoCreo 2 general item with no icon and no
--- description text in either export.
local EXCLUDED = {
['BGC Belt'] = true,
['BGC Boots'] = true,
['BGC Drill'] = true,
['BGC Hammer'] = true,
['BGC Helmet'] = true,
['Black Gemma'] = true
}
--- Built once per page; every section on the Items article asks for the same
--- join.
local cache
--- Fold a display name into the key the two games are joined on.
---
--- getName() capitalises every word, while EvoCreo 2 ships a handful of names
--- in sentence case: 'Creo medicine', 'Mahogany log'. Matching on the name as
--- spelled therefore split those items across two rows, one marked EvoCreo 2
--- only and one marked EvoCreo only whose link was red, no article carrying the
--- derived spelling.
---
--- Dropping case and punctuation is the same key .claude/tools/items.py joins
--- on. Re-run `items.py ec1casing` after a data refresh: a newly shared item
--- spelled in sentence case would otherwise hit this again, silently.
---
--- @param name string
--- @return string
local function getKey( name )
return ( name:lower():gsub( '[^%w]', '' ) )
end
--- @return table
local function getData()
if cache then
return cache
end
local icons = mw.loadJsonData( ICONS ) or {}
local evocreoIcons = mw.loadJsonData( EC1_ICONS ) or {}
local creo = mw.loadJsonData( CREO ) or {}
local rows = {}
for _, item in ipairs( mw.loadJsonData( EC2_ITEMS ) or {} ) do
if item.name and not EXCLUDED[item.name] then
rows[getKey( item.name )] = {
name = item.name,
kind = item.type and item.type.name,
evocreo = false,
evocreo2 = true
}
end
end
for _, item in ipairs( mw.loadJsonData( EC1_ITEMS ) or {} ) do
local name = ALIASES[item.id] or ecGameData.getName( item.id )
if name and not creo[name] and not EXCLUDED[name] then
local key = getKey( name )
local row = rows[key]
if not row then
-- EvoCreo 1 only, so there is no EvoCreo 2 record to take a
-- display name or a type from.
row = {
name = name,
kind = item.type,
evocreo = false,
evocreo2 = false
}
rows[key] = row
end
row.evocreo = true
end
end
cache = { rows = rows, icons = icons, evocreoIcons = evocreoIcons }
return cache
end
--- @param row table
--- @param data table
--- @return string
local function getNameWikitext( row, data )
local article = ARTICLES[row.name] or row.name
if row.evocreo2 and data.icons[row.name] then
return itemLink( { text = row.name, link = article } )
end
-- An item that never appeared in EvoCreo 2 has no artwork under that name,
-- so asking for it would render a red file link. Illustrate it with the
-- icon from the game it did appear in.
if not row.evocreo2 and data.evocreoIcons[row.name] then
return itemLink( { text = row.name, link = article, game = 'evocreo' } )
end
return string.format( '[[%s|%s]]', article, row.name )
end
--- @return TableColumn[]
local function getColumns()
return {
{ id = 'name', label = 'Item' },
{ id = 'evocreo', label = '[[EvoCreo]]', textAlign = 'center' },
{ id = 'evocreo2', label = '[[EvoCreo 2]]', textAlign = 'center' }
}
end
--- @param kind string
--- @return TableRow[]
local function getRows( kind )
local data = getData()
local matched = {}
for _, row in pairs( data.rows ) do
if row.kind == kind then
table.insert( matched, row )
end
end
table.sort( matched, function ( a, b )
return a.name < b.name
end )
local rows = {}
for _, row in ipairs( matched ) do
table.insert( rows, {
getNameWikitext( row, data ),
row.evocreo and PRESENT or ABSENT,
row.evocreo2 and PRESENT or ABSENT
} )
end
return rows
end
--- Lua entry point for the module
---
--- @param kind string one of KEY, GENERAL, LINK, HEALTH, MOVE, EQUIPMENT, OUTFITS
--- @return string
function p._main( kind )
if type( kind ) ~= 'string' or kind == '' then
error( 'No item type provided' )
end
kind = kind:upper()
return tableLua.render( {
caption = kind,
hideCaption = true, -- the section heading already labels the table
columns = getColumns(),
data = getRows( kind ),
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
local args = getArgs( frame )
return p._main( args.type or args[1] )
end
return p