Module:Card
Jump to navigation
Jump to search
This Lua module is used primarily on pages in the Main namespace. It can be found on pages, or on about Expression error: Unexpected / operator.% of those pages.
Edits to this Lua module will be widely noticed. To avoid large-scale disruption and unnecessary server load, any changes to this Lua module should be tested in its sandbox or testcases subpages. The tested changes can then be added to this page in one single edit.
Please consider discussing any changes in the wiki's Discord.
This module implements {{Card}}.
local p = {}
local lib = require('Module:Feature')
local TemplateData = require('Module:TemplateData')
local Icon = require('Module:Icon')
local RARITY_STARS = {
--['0'] = '',
--['1'] = '✦',
--['2'] = '✦✦',notext
--['3'] = '✦✦✦',
--['4'] = '✦✦✦✦',
--['5'] = '✦✦✦✦✦'
['1'] = '[[File:Icon 1 Stars.png|x12px|link=|alt=Rarity 1]]',
['2'] = '[[File:Icon 2 Stars.png|x12px|link=|alt=Rarity 2]]',
['3'] = '[[File:Icon 3 Stars.png|x12px|link=|alt=Rarity 3]]',
['4'] = '[[File:Icon 4 Stars.png|x12px|link=|alt=Rarity 4]]',
['5'] = '[[File:Icon 5 Stars.png|x12px|link=|alt=Rarity 5]]'
}
local SUPERIMPOSITION_TABLE = {
['1'] = { num = '1', text = 'I' },
['I'] = { num = '1', text = 'I' },
['2'] = { num = '2', text = 'II' },
['II'] = { num = '2', text = 'II' },
['3'] = { num = '3', text = 'III' },
['III'] = { num = '3', text = 'III' },
['4'] = { num = '4', text = 'IV' },
['IV'] = { num = '4', text = 'IV' },
['5'] = { num = '5', text = 'V' },
['V'] = { num = '5', text = 'V' },
}
local PREFIX_ICONS = {
['Recipe: '] = {name='Recipe', type='Item'}
}
-- main template/module parameters
local CARD_ARGS = {
{
name='name', alias={1,'character'}, displayName=1,
status='suggested',
label='Name',
description='The name of the image (without the prefix or extension).',
default='Unknown',
example={'Credit', 'Stellar Jade', 'Amber'},
},
{
name='text', alias=2, displayName=2,
label='Card Text',
description='The text below the image.',
default='—',
displayDefault='"—" for most items; same as Image Name for characters',
example={'100', 'Lv. 1', 'Set'},
},
{
name='notext', type='1',
label='No Text',
trueDescription='remove text from bottom.',
},
{
name='multiline_text', type='1',
label='Allow Multiple Lines of Card Text',
trueDescription='allow card text to wrap to a new line if necessary.',
},
{
name='rarity', displayType='number',
label='Card Rarity',
description='The Rarity of the item. For light background, add 10. Use 0 for white version. Use multiple numbers for split rarities.',
default='0',
displayDefault='determined based on Image Name; "0" if unknown',
example={'1', '2', '3', '4', '5'},
},
{
name='type', placeholderFor='Module:Icon',
},
{
name='suffix', placeholderFor='Module:Icon',
},
{
name='extension', placeholderFor='Module:Icon',
},
{
name='link', displayType='wiki-page-name',
label='Card Link',
description='The page to link to when image or caption text is clicked.',
displayDefault='same as Image Name',
defaultFrom='name',
example={'Credit', 'Virtual Particle', 'Trailblazer'},
},
{
name='link_suffix',
label='Page Link Suffix',
description='Text to append to the Page Link. (Usually used to link to page sections.)',
default='',
example='#Steps',
},
{
name='nolink', type='1',
label='No Link',
trueDescription='remove default links from card image, icons, and caption.',
displayDefault='"1" if Image Name is default ("Unknown")',
},
{
name='icon', displayType='content',
label='Icon',
description='The icon at the top left of the card.',
example={'{{Icon/Element|Fire|25}}', '[[File:Item Stellar Jade.png|25px]]'},
},
{
name='icon_style', displayType='string',
label='Icon Style',
description='The custom style of the icon at the top left of the card. Omit the \'\' used for CSS.',
example='background: red;',
},
{
name='icon_right', displayType='content',
label='Top-Right Icon',
description='Icon on the top right of the card.',
example='{{Icon/Element|Fire|25}}',
},
{
name='icon_right_style', displayType='string',
label='Top-Right Icon Style',
description='The custom style of the icon at the top right of the card. Omit the \'\' used for CSS.',
example='background: red;',
},
{
name='show_caption', type='1',
label='Show Caption',
trueDescription='show caption text below the card.',
},
{
name='caption', displayType='string',
label='Caption Text',
description='The linked caption text that shows below the card.',
displayDefault='same as Name',
defaultFrom='name',
example={'Comfort Food', 'Rewards'},
},
{
name='caption_width',
label='Caption Width',
description='Width of the captions. Set to \'auto\' to automatically increase the caption width to prevent it from wrapping in the middle of words.',
displayDefault='same as card width (74px, or 40px for mini cards)',
example={'200px', 'auto'},
},
{
name='note', displayType='string',
label='Caption Note',
description='The unlinked caption text that shows below the linked caption text.',
example='(Bonus reward)',
},
{
name='superimposition', alias='si', displayType='number',
label='Superimposition Level',
description='The superimposition level of the light cone.', --Set to \'1a\' to denote a light cone that cannot be superimposed.
example={'1', '2', '3', '4', '5'}, --, '1a'
},
{
name='ascension', placeholderFor='Module:Icon',
},
{
name='eidolon', alias='e', displayType='number',
label='Eidolon Level',
description='Eidolon Level of the Character.',
example={'0', '1', '2', '3', '4', '5', '6'},
},
{
name='weakness', alias='w', displayType='string',
label='Enemy Weakness',
description='List of elements the enemy is weak against',
example={'Quantum; Wind'},
},
{
name='aether', displayType='string',
label='Aether Type',
description='Enemy\'s Aether type when an Aether Spirit',
example={'Humanoid', 'Mechanical', 'Aberrant'},
},
{
name='tier', displayType='string',
label='Enemy Tier',
description='List of elements the enemy is weak against',
example={'Normal', 'Elite', 'Boss', 'Echo of War'},
},
{
name='stars', type='1',
label='Show Item Stars',
trueDescription='show the stars of the rarity of the item. (Does not work if rarity is "0" or split (e.g., "123" or "34").)',
},
{
name='mini', type='1',
label='Mini',
trueDescription='show a smaller card. (Not all parameters apply to mini cards.)',
},
{
name='noprefix', type='1',
label='Ignore Prefix',
trueDescription='Skips any processing of prefixes (such as Recipe) for special cases.'
},
{
name='notice', displayType='string',
label='Notice Label',
description='Text to display in a small box in the style of the in-game "New" notifications.',
example={'New','2.2','Soon'}
},
{
name='minitext', type='1',
label='Allow Mini Card Text',
description='Flag to allow the text param for mini cards.'
},
}
-- add parameters inherited from Icon for the main image, top-left icon, and equipped icon
Icon.addIconArgs(CARD_ARGS, '', '', { -- exclude args already present in CARD_ARGS or that don't apply to the main image
name=false,
link=false,
size=false,
alt=false,
})
Icon.addIconArgs(CARD_ARGS, 'icon_', 'Top-Left Icon ', { -- override to add `element` alias and document automatic icons
name={
alias='element',
description='Name of the icon to display at the top left of the card (without the prefix or extension).',
displayDefault='element icon for characters; empty otherwise',
example={'Quantum'}
},
})
Icon.addIconArgs(CARD_ARGS, 'equipped_', 'Equipped ', { -- overrides to add `equipped`/`e` aliases and change default size
name={
alias={'equipped', 'eq'},
description='Name of the light cone or character to display at the top right of the card (or bottom left, if Eidolon Level is present; name does not include the prefix or extension).',
example={'In the Name of the World', 'Trailblazer'}
},
size={default='30'},
})
function p.main(frame)
local args = require('Module:Arguments').getArgs(frame, {
parentFirst = true,
wrapper = { 'Template:Card' }
})
return p._main(args, frame)
end
-- apply defaults; load and apply data (e.g., rarity, character element)
function p._processArgs(args)
local out = TemplateData.processArgs(args, CARD_ARGS)
-- set defaults that depend on other arguments
out.defaults.show_caption = lib.isNotEmpty(out.nonDefaults.caption)
out.defaults.nolink = out.name == out.defaults.name and lib.isEmpty(out.nonDefaults.link)
-- handle deprecated caption value
if tostring(out.caption) == '1' then
out.uses_deprecated_params = true
out.caption = nil
out.show_caption = true
end
-- handle nolink
out.final_link = out.nolink and '' or (out.link .. out.link_suffix)
-- build args for main card image
local mainImageArgs = Icon.extractIconArgs(out)
mainImageArgs.link = out.final_link
mainImageArgs.size = 74
if (not args.noprefix) then
-- check for prefixes in `name` that correspond to top-left icons
-- note: priority of icon specification: `icon` arg > `icon_name` arg > prefix of `name` > character element from data
local baseName, prefix = Icon.stripPrefixes(out.name, PREFIX_ICONS)
if prefix then
-- configure icon if none is specified by `icon_name`,
-- making sure not to change `icon_type` if `icon_name` is explicitly specified
if out.icon_name == out.defaults.icon_name then
local prefixIcon = PREFIX_ICONS[prefix]
out.icon_name = prefixIcon.name
out.icon_type = prefixIcon.type
end
mainImageArgs.name = baseName
end
end
-- create card image and get type/data
local card_type, data
out.image, card_type, data = Icon.createIcon(mainImageArgs)
-- set defaults based on data
if data then
if card_type == 'Character' or card_type == 'Currency Wars Character' then
if out.icon_name == out.defaults.icon_name and data.element ~= 'Adaptive' and lib.isNotEmpty(data.element) then
out.icon_name = data.element
out.icon_type = 'Icon'
end
out.defaults.text = data.name or out.name
end
if card_type == 'Light Cone' then
out.icon_name = data.path
out.icon_type = 'Icon'
out.icon_size = 15
-- if args.mini then
out.image.defaults.suffix = 'Icon'
-- else
-- out.image.size = '74x'
-- end
end
if card_type == 'Enemy' then
out.weakness = out.weakness or data.weakness
out.tier = out.tier or data.tier
out.final_link = (out.nolink and '') or data.link or out.final_link
end
if card_type == 'Aether Enemy' then
out.aether = out.aether or data.aethertype
out.final_link = (out.nolink and '') or data.link or out.final_link
end
if card_type == 'Dice Face' then
out.notext = true
out.final_link = '#' .. out.final_link
end
if data.title then
out.defaults.caption = data.title
end
if data.upcoming then
out.defaults.notice = data.upcoming
end
end
-- add top-left icon (if not specified by 'icon' wikitext)
if out.icon == out.defaults.icon then
local iconImage = Icon.createIcon(out, 'icon_')
iconImage.defaults.size = lib.ternary(out.mini, 16, 20)
-- disable link if nolink is explicitly set, but icon_link is not
if out.nonDefaults.nolink then
iconImage.defaults.link = ''
end
out.icon = iconImage:buildString()
end
-- add bottom-left icon for enemy weakness
if lib.isNotEmpty(out.weakness) then
local elements = lib.split(out.weakness, out.weakness:find(',') and ',' or ';')
out.icon_bottom_type = 'Icon'
out.icon_bottom = ''
for _, element in ipairs(elements) do
out.icon_bottom_name = element
local iconImage = Icon.createIcon(out, 'icon_bottom_')
iconImage.defaults.size = lib.ternary(out.mini, 10, 20)
iconImage.defaults.alt = 'Weak to ' .. element
-- disable link if nolink is explicitly set, but icon_link is not
if out.nonDefaults.nolink then
iconImage.defaults.link = ''
end
out.icon_bottom = out.icon_bottom .. iconImage:buildString()
end
end
-- add bottom-left icon for enemy aether type
if lib.isNotEmpty(out.aether) then
out.icon_bottom_type = 'Type'
out.icon_bottom_name = out.aether
local iconImage = Icon.createIcon(out, 'icon_bottom_')
iconImage.defaults.size = lib.ternary(out.mini, 10, 20)
iconImage.defaults.alt = 'Aether Enemy of the ' .. out.aether .. ' type'
-- disable link if nolink is explicitly set, but icon_link is not
if out.nonDefaults.nolink then iconImage.defaults.link = '' end
out.icon_bottom = iconImage:buildString()
end
-- add bottom-left icon currency wars bond(s)
if lib.isNotEmpty(out.bonds) then
local bonds = lib.split(out.bonds, ';')
out.icon_bottom_type = 'Bond'
out.icon_bottom = ''
out.icon_bottom_suffix = 'Icon'
out.icon_bottom_class = 'diamond-icon diamond-icon-morepadding'
for _, bond in ipairs(bonds) do
out.icon_bottom_name = bond
local iconImage = Icon.createIcon(out, 'icon_bottom_')
iconImage.defaults.size = lib.ternary(out.mini, 7, 15)
iconImage.defaults.alt = 'Bond: ' .. bond
-- disable link if nolink is explicitly set, but icon_link is not
if out.nonDefaults.nolink then
iconImage.defaults.link = ''
else
iconImage.defaults.link = 'Currency Wars: Zero-Sum Game/Bonds#' .. bond
end
out.icon_bottom = out.icon_bottom .. iconImage:buildString()
end
end
-- add enemy danger icon
if card_type == 'Enemy' and lib.isNotEmpty(out.tier) and out.tier ~= 'Normal' then
out.icon_right = '[[File:Icon ' .. out.tier .. ' Enemy.png|25x25px|link=|alt=' .. out.tier .. ' Enemy]]'
end
-- Currency Wars on/off-field
if card_type == 'Currency Wars Character' and lib.isNotEmpty(out.fieldposition) then
local posIcons = {}
for pos in lib.gsplit(out.fieldposition, ';', {removeEmpty=true}) do
table.insert(posIcons, '[[File:Icon ' .. pos .. ' White.png|20x20px|link=|alt=' .. pos .. '|class=diamond-icon]]')
end
out.icon_right = table.concat(posIcons, '')
end
-- add equipped icon
if lib.isNotEmpty(out.equipped_name) then
local equippedImage, equippedType = Icon.createIcon(out, 'equipped_')
equippedImage.defaults.suffix = 'Icon'
if equippedType == 'Character' then
equippedImage.defaults.size = 30
equippedImage.defaults.alt = 'Equipped on ' .. equippedImage.alt
else
equippedImage.defaults.alt = 'Equipped with ' .. equippedImage.alt
end
if out.nonDefaults.nolink then
equippedImage.defaults.link = ''
end
out.equipped = equippedImage:buildString()
end
return out, data
end
local function setCaptionWidth(node, width)
if width == 'auto' then
node:addClass('auto-width')
else
node:css('width', width)
end
end
-- Creates and returns a card element from *processed* card args.
function p._createCard(a)
local node_container = mw.html.create('div'):addClass('card-container')
if a.mini then
node_container:addClass('mini-card')
end
-- create two wrapper divs for the main card body
-- (required for CSS positioning and rounded corners, respectively)
local node_card = node_container:tag('span')
:addClass('card-wrapper')
:tag('span')
:addClass('card-body')
:addClass('card-' .. a.rarity)
if lib.isNotEmpty(a.icon) then
node_card:tag('span')
:addClass('card-icon')
:cssText(a.icon_style)
:wikitext(a.icon)
end
if lib.isNotEmpty(a.icon_bottom) then
node_card:tag('span')
:addClass('card-icon-bottom')
:cssText(a.icon_bottom_style)
:wikitext(a.icon_bottom)
end
local node_image = node_card:tag('span')
:addClass('card-image-container')
node_image:tag('span')
:wikitext(a.image:buildString())
if not a.mini then
if a.stars then
local starsImage = RARITY_STARS[a.rarity]
if starsImage then
node_card:tag('span')
:addClass('card-stars')
:addClass('star-' .. a.rarity)
:tag('span')
:wikitext(starsImage)
end
end
if lib.isNotEmpty(a.icon_right) then
node_card:tag('span')
:addClass('card-icon-right')
:cssText(a.icon_right_style)
:wikitext(a.icon_right)
end
if lib.isNotEmpty(a.equipped) then
node_card:tag('span')
:addClass('card-equipped')
:wikitext(a.equipped)
end
local si_cell = nil
if lib.isNotEmpty(a.superimposition) then si_cell = SUPERIMPOSITION_TABLE[a.superimposition] end
if si_cell ~= nil then
node_card:tag('span')
:addClass('card-superimposition')
:addClass('superimposition-' .. si_cell.num)
:wikitext(si_cell.text)
end
if lib.isNotEmpty(a.eidolon) then
node_card:tag('span')
:addClass('card-eidolon')
:wikitext(' E', a.eidolon)
node_card:addClass('card-with-eidolon')
end
if (lib.isNotEmpty(a.notice)) then
node_card:tag('span')
:addClass('card-notice-label')
:wikitext(a.notice)
end
end
if not a.notext and (not a.mini or a.minitext) then
local node_text = node_card:tag('span')
:addClass('card-text')
:addClass('card-font')
:wikitext(' ', a.text)
if a.multiline_text then
node_text:addClass('multi-line')
end
end
if a.show_caption and lib.isNotEmpty(a.caption) then
local node_caption = node_container:tag('span')
:addClass('card-caption')
:wikitext(' ', a.nolink and a.caption or ('[[' .. a.final_link .. '|' .. a.caption .. ']]'))
setCaptionWidth(node_caption, a.caption_width)
end
if lib.isNotEmpty(a.note) then
local node_note = node_container:tag('span')
:addClass('card-caption')
:wikitext(a.note)
setCaptionWidth(node_note, a.caption_width)
end
if a.uses_deprecated_params then
node_container:wikitext('[[Category:Pages Using Deprecated Template Parameters]]')
end
return node_container
end
-- Processes arguments and returns a corresponding Card element.
function p._main(args, frame)
local a = p._processArgs(args)
return p._createCard(a)
end
function p.templateData(frame)
return TemplateData.templateData(CARD_ARGS, {
description='This template is used to create an item display that is similar to the items in the Inventory in-game. Text can be shown below the item\'s image, background and stars can match the rarity, superimposition, icons, and set can be added.\n\nIf the image name matches an existing Item, then the rarity will automatically be set.\n\nIf the image name matches an existing Character of Light Cone, then the format will automatically change to match that type item.',
format='inline'
}, frame)
end
function p.syntax(frame)
return TemplateData.syntax(CARD_ARGS, frame)
end
return p