Module:Card
This module is currently under development. It is not ready for general usage and the documentation below may become outdated. |
This module is should not be used directly in pages or templates, other than for testing. Rather it is used to create other modules. |
Module:Card is a generic module used to create modules for cards from various media.
Contents
Setup[edit]
To create a module for a card for a particular medium:
- Add this module
- Set configuration options
- (optional) Override default functions set by this module
- (optional) Add some custom functions
- Return this module's object
local Card = require('Module:Card')
-- Add traits here
-- Set some configuration options here
-- Override default behavior of Module:Card here
-- Add custom functions here
return Card
Traits[edit]
Traits are used to add functionality, not enabled by default to just the modules that need it. They should be added using the require
keyword, immediately after requiring this module.
Card = require('Module:Card')
require('Module:Card/traits/example 1')
require('Module:Card/traits/example 2')
Available traits, include:
- Module:Card/traits/accepts image input, for cards that allow images to be added using an
image
parameter, as opposed to the images being added automatically - Module:Card/traits/has sets, for cards with sets
- Module:Card/traits/implicit Normal and Effect, for cards that don't necessarily include "/ Normal" and "/ Effect" after the Type for all Normal and Effect Monsters
Configuration options[edit]
To set a configuration option, use Card.config.optionName = value
.
Card.config.option1 = 'value 1'
Card.config.option2 = 'value 2'
Card.config.option2 = 'value 3'
Available options, include:
- baseClass
- Default:
nil
- CSS class to add to the card table for all cards
- colorCoded
- Default:
true
- If the color of the card table changes depending on the card or monster typing
- defaultImage
- Default:
'Back-EN.png'
- The image to appear if no other image was set/found.
- enableMainLinks
- Default:
true
- If "main card page" links can appear at the beginning of the page.
- icons
- A table mapping sub options to icons to represent them.
- Each value should contain the wikitext to render the icon. e.g. If an image, it must include the link brackets and "File:" prefix. If not an image, it can contain a Unicode symbol.
- Sub options include:
- level
- Default:
'[[File:CG Star.svg|18px|alt=]]'
- Icon to represent the Level. This icon will be repeated a number of times equal to the card's Level.
- If each Level is represented by single icon, use the
levels
config icon instead. - unknownLevel
- Default:
'[[File:CG Star Unknown.svg|18px|alt=]]'
- Icon to indicate that the Level is unknown
- negativeLevel
- Default:
'[[File:Negative Star.svg|18px|alt=]]'
- Icon to represent a negative Level. This icon will be repeated a number of times equal to the magnitude of card's Level. e.g. a Level of -7 will show the icon seven times.
- rank
- Default:
'[[File:Rank Star.svg|18px|alt=]]'
- Icon to represent the Rank. This icon will be repeated a number of times equal to the card's Rank.
- unknownRank
- Default:
'[[File:Rank Star Unknown.svg|18px|alt=]]'
- Icon to indicate that the Rank is unknown
- unknownAttribute
- Default:
'[[File:UNKNOWN.svg|28px|alt=]]'
- Icon to indicate that the Attribute in unknown
- cardTypes
- Default:
'[[File:{$cardType_uc}.svg|28px|alt=]]'
- A pattern to describe all icons used for the card type
{$cardType}
will be replaced with the cards' type. e.g. "Spell"{$cardType_uc}
will be replaced with the card's type in uppercase. e.g. "SPELL"- properties
- Default:
'[[File:{$property}.svg|28px|alt=]]'
- A pattern to describe all icons used for the Property
{$property}
will be replaced with the card's Property. e.g. "Continuous"{$cardType}
will be replaced with the card's type. e.g. "Spell"- attributes
- Default:
'[[File:{$attribute}.svg|28px|alt=]]'
- A pattern to describe all icons used for the Attribute
{$attribute}
will be replaced with the card's Attribute. e.g. "FIRE"- types
- Default:
nil
- A pattern to describe all icons used for the Type
{$type}
will be replaced with the card's Type. e.g. "Zombie"- levels
- Default:
nil
- A pattern to describe all icons used for the Level
{$level}
will be replaced with the card's Level. e.g. "4"- If a card's Level is represented by repeating icons instead of a single icon, leave this as
nil
and use thelevel
config icon instead.
- langs
- Default:
{ en }
- List of languages that the card can have information in.
- Acceptable options include
en
,fr
,de
,it
,pt
,es
,ja
,zh
,tc
,sc
,ko
,ru
- This is not the same as the language codes used in set prefixes. Other prefixes such as
c
andae
are not options.
- rows
- Default:
{}
- List of rows that can appear in the card table, in the order that they should appear
- Available options include:
number
,cardType
,property
,attribute
,type
,level
,rank
,pendulumScale
,linkArrows
,atkDef
,atkLink
,effectType
,lore
,limitationText
,password
- If rows other than those listed are needed, a custom function can be added to the module for that row. This is done by creating a function on the card object, named in camel case as: "add", followed by the name of the row, followed by "Row".
- e.g. to add a row for "Finishing Move"
function Card:addFinishingMoveRow()
self:addRow('Finishing Move', self.finishingMove)
end
- and add
'finishingMove'
to the list of rows
- types
- The list of allowed monster types in each category. This contains a table mapping categories to lists of types.
- Categories include:
- type
- Default:
{}
- Types e.g.
{ 'Spellcaster', 'Warrior', 'Zombie', 'Beast', 'Dragon' }
- If a Type name does not match its page name on the wiki format it as a pairing mapping the Type name to the page name e.g.
{ ['Galaxy'] = 'Galaxy (Type)' }
- summon
- Default:
{}
- Monster types for the cards' Summon method. e.g.
{ 'Fusion', 'Ritual', 'Synchro', 'Dark Synchro', 'Xyz', 'Link' }
- "Pendulum" and "Maximum" should not be included in this list. They are handled separately.
- ability
- Default:
{}
- Abilities e.g.
{ 'Flip', 'Toon', 'Union', 'Armor' }
- "Tuner" is not considered to be an ability. It is handled separately.
- pendulum
- Default:
{}
- Other than being left blank,
{ 'Pendulum' }
is the only allowed value. - maximum
- Default:
{}
- Other than being left blank,
{ 'Maximum' }
is the only allowed value. - normal
- Default:
{}
- Other than being left blank,
{ 'Normal' }
is the only allowed value. - effect
- Default:
{}
- Other than being left blank,
{ 'Effect' }
is the only allowed value. - token
- Default:
{}
- Other than being left blank,
{ 'Token' }
is the only allowed value. - This should only be used for video games where "Token" is listed alongside the monster's Type.
--[[
Module for creating card articles
General flow of what happens:
- Defines an empty `Card` object with default data
- `Card.card()` is called with data from a template call passed in
- Calls `Card:new()` to create a new `Card` object with the input data
- Runs through the "setter" functions to fill the data
- Runs through an "add row" function for each row in `Card.config.rows`
- Calls `Card:render()` to draw the final output
- Calls various other "render" functions to help with creating the output
This module should not be invoked directly.
Other modules should import this one, make some changes to configuration.
The other module will then run through the same flow as described above.
]]--
-- ------------------------------------
-- Import modules
-- ------------------------------------
-- Library of generic functions for helping with Lua tables
TableTools = require('Module:TableTools')
-- Pseudo-classes for individual elements of cards
-- e.g. `Type:new('Fusion')` returns an object with data on the Fusion type
local Attribute = require('Module:Card/models/Attribute')
local CardType = require('Module:Card/models/Card type')
local EffectType = require('Module:Card/models/Effect type')
local Locale = require('Module:Card/models/Locale')
local Type = require('Module:Card/models/Type')
-- ------------------------------------
-- Generic functions
-- Might move these to a more dedicated module
-- ------------------------------------
-- Replace parameters within a string
-- Might move to more dedicated module
-- e.g.
-- Input: `replaceParams('Hi {$name}!', { name = 'Yugi' })`
-- Output: "Hi Yugi!"
--
-- @param message string - Text to perform the replacements on
-- @param params table - Map of variables names to the text to replace them.
-- @return string
local function replaceParams(message, params)
for param, replacement in pairs(params) do
message = string.gsub(message, '{$' .. param .. '}', replacement)
end
return message
end
-- Capitalize the first letter in a string
-- Might move to more dedicated module
-- e.g. `ucfirst('abc')` returns "Abc"
-- @param text string
-- @return string
local function ucfirst(text)
return text:sub(1,1):upper() .. text:sub(2)
end
-- ------------------------------------
-- The main structure of the card object
-- - Create the object that contains data on the card itself and configuration options
-- ------------------------------------
-- Create an empty Card object with all the default values
local Card = {
-- Store all details on the card itself here.
cardTypes = {},
property = nil,
attributes = {},
types = {},
isMonster = nil,
isNormalMonster = nil,
isEffectMonster = nil,
isPendulumMonster = nil,
summonType = nil,
level = nil,
linkArrows = {},
atk = nil,
def = nil,
linkRating = nil,
limitationText = nil,
password = nil,
effectTypes = {},
locales = {
en = {
name = nil,
lore = nil,
pendulumEffect = nil,
previousNames = {}
}
},
customColor = nil,
debutDate = nil,
-- Params for rendered output
pageName = mw.title.getCurrentTitle().text,
cardImageName = nil,
categories = { 'All cards' },
styles = {
'Module:Card/base.css'
},
main = false,
hasCustomBacking = false,
images = {},
rows = {},
prev = nil,
next = nil,
-- Flag if the module is only being used for demonstrative purposes.
demo = false,
-- Configuration options
-- Overwrite these in modules that extend this one
config = {
baseClass = nil,
colorCoded = true,
defaultImage = 'Back-EN.png',
enableMainLinks = true,
footerText = nil,
icons = {
-- Individual (just one image for each item)
level = '[[File:CG Star.svg|18px|alt=]]',
unknownLevel = '[[File:CG Star Unknown.svg|18px|alt=]]',
negativeLevel = '[[File:Negative Star.svg|18px|alt=]]',
rank = '[[File:Rank Star.svg|18px|alt=]]',
unknownRank = '[[File:Rank Star Unknown.svg|18px|alt=]]',
unknownAttribute = '[[File:UNKNOWN.svg|28px|alt=]]',
effect = nil,
-- Multiple (contain patterns that represent multiple icons)
levels = nil,
cardTypes = '[[File:{$cardType_uc}.svg|28px|alt=]]',
properties = '[[File:{$property}.svg|28px|alt=]]',
attributes = '[[File:{$attribute}.svg|28px|alt=]]',
types = nil,
summonTypes = nil
},
-- Languages the card has details to be shown in
langs = { 'en' },
-- Rows to show in the rendered output
-- This is empty and should be set in modules that extend this one.
rows = {},
-- The Types that are allowed. (This includes anything on the Type line
-- e.g. 'Dragon', 'Fusion' and 'Effect' are all to be included.)
-- @todo: categorize cards that have a Type not in this list.
allowedTypes = {}
}
}
-- Create a new card object
-- e.g. `Card:new({ atk = 3000, def = 2500, level = 8, types = 'Dragon / Normal' })`
-- The parameters are most likely to come from template input.
-- This will create an object for a card with all those details.
-- The code will automatically fill in more details based on the input.
-- And it will be possible to call other functions on the Card object that make use of this data.
--
-- @param args table
-- @return Card
function Card:new(args)
-- Create a new instance of the class with all the default values
local c = mw.clone(Card)
-- Fill it with the supplied data
c:setData(args)
return c
end
-- ------------------------------------
-- Setters
-- Functions that add/update information on an instance of the `Card` object based on input
-- ------------------------------------
-- Main function for setting most data
-- @param args table
function Card:setData(args)
-- Fill in the new card object with data from the arguments
self.demo = args.demo or false
-- The pagename can only be overwitten in demos
if (self.demo and args.page_name) then
self.pageName = args.page_name
end
self.number = args.number
self.property = args.property
self:setCardTypes(args.card_type, args)
self.property = args.property
self:setAttributes(args.attribute)
self:setTypes(args.types)
self:setLinkArrows(args.link_arrows)
self.atk = args.atk
self.def = args.def
self.level = args.level
self.rank = args.rank
self.pendulumScale = args.pendulum_scale
self.limitationText = args.limitation_text
self.password = args.password
self:setEffectTypes(args.effect_types)
self.locales = Locale:createMany(self.config.langs, args)
self:setName(args.name)
self.customColor = args.color
self:setMainLink(args.main)
end
-- Set the English name
-- @param name string - The card's name. Default to the page name.
function Card:setName(name)
-- Check if a name was supplied
if (name) then
-- Use the supplied name as the card name
self.locales.en.name = name
else
-- Use the page name before the first parenthesis as the card name.
self.locales.en.name = mw.text.split(self.pageName, ' %(')[1]
end
-- Set the name to be used in card images.
-- Strip out punctuation, spaces, control characters and select symbols
-- todo: Establish a single source of truth between this and `Module:Card image name`
self.cardImageName = self.locales.en.name:gsub('[%p%c%s–☆★・]', '')
end
-- Set the card types
-- e.g. 'Spell','Trap', 'Virus'
-- It is usually not necessary to set 'Monster', it can be asumed based on other information.
-- Rarely a card can have multiple card types e.g. 'Counter / Token' or 'Spell / Trap'
--
-- @param cardTypesString string
-- @param args table
function Card:setCardTypes(cardTypesString, args)
-- Flag if the card is a monster.
-- Even if the cardType is not set to "Monster", the card could be a monster
-- e.g. if cardType is "Token".
self.isMonster = (cardTypesString == 'Monster' or args.atk or args.def or args.attribute or args.types)
and true
or false
local cardTypeNames = {}
-- If the card type is explicitly stated use that.
-- Default to "Monster" if the card is a monster.
if (cardTypesString) then
cardTypeNames = mw.text.split(cardTypesString, '%s*/%s*')
elseif (self.isMonster) then
cardNameTypes = { 'Monster' }
end
-- Fill the cardTypes table with an object for each card type.
for _, cardTypeName in pairs(cardTypeNames) do
table.insert(self.cardTypes, CardType:new(cardTypeName))
end
end
-- Set Types-related data by passing in the type string
-- @param typesString string '/'-separated list of types as printed on the card
function Card:setTypes(typesString)
-- Blank everything and start from a clean slate
self.isNormalMonster = false
self.isEffectMonster = false
self.isPendulumMonster = false
self.summonType = nil
self.types = {}
if (typesString) then
-- Split the type input by forward slash
-- Allow spaces at either side of the slash
-- e.g. 'Pyro / Fusion / Effect' → { 'Pyro', 'Fusion', 'Effect' }
local typeNames = mw.text.split(typesString, '%s*/%s*')
-- Set these values if their Types are anywhere in the array
self.isNormalMonster = TableTools.inArray(typeNames, 'Normal')
self.isEffectMonster = TableTools.inArray(typeNames, 'Effect')
self.isPendulumMonster = TableTools.inArray(typeNames, 'Pendulum')
-- Fill the Types table with an object for each type.
for _, typeName in pairs(typeNames) do
local type = Type:new(typeName)
table.insert(self.types, type)
-- If the category is 'Summon', set the Type as the card's SummonType
if (self.summonType == nil and type.category == 'Summon') then
self.summonType = type
end
end
end
end
-- Set Attributes-related data by passing in the Attribute string
-- @param typesString string '/'-separated list of Attributes
function Card:setAttributes(attributesString)
self.attributes = {}
if (attributesString) then
-- Split the Attribute input by forward slash
-- Allow spaces at either side of the slash
-- e.g. 'DARK / FIRE' → { 'DARK', 'FIRE' }
local attributeNames = mw.text.split(attributesString, '%s*/%s*')
-- Fill the Attributes table with an object for each Attribute.
for _, attributeName in pairs(attributeNames) do
local attribute = Attribute:new(attributeName)
table.insert(self.attributes, attribute)
end
end
end
-- Set the `effectTypes` property by passing the input effect types
-- @param effectTypesString string comma-separated list of effect type names
function Card:setEffectTypes(effectTypesString)
-- Ensure this starts from a clean slate
self.effectTypes = {}
-- If there's nothing to set, end early
if (not effectTypesString or effectTypesString == '') then return end
-- Split the effect type input by cammo
-- Allow spaces at either side of the comma
local effectTypeNames = mw.text.split(effectTypesString, '%s*,%s*')
-- Fill the effect types list with an object for each effect type.
for _, effectTypeName in pairs(effectTypeNames) do
table.insert(self.effectTypes, EffectType:new(effectTypeName))
end
end
-- Set the Link Arrows data
-- Doing this will also set the Link Rating equal to the number of arrows
-- @param linkArrowsInput string
function Card:setLinkArrows(linkArrowsInput)
-- Split the input by comma to form an array
self.linkArrows = mw.text.split(linkArrowsInput or '', '%s*,%s*')
-- Link Rating is equal to the number of Link Arrows
self.linkRating = #self.linkArrows
end
-- Set the main link
-- @param main string - manually specified main link.
-- The link will determined automatically otherwise.
function Card:setMainLink(main)
-- If main links are not enabled, "main" is always `false`.
if (not self.config.enableMainLinks) then
self.main = false
return
end
-- If the input specifically says not to use a main link, "main" is `false`.
if (TableTools.inArray({ 'no', 'none', 'false' }, main)) then
self.main = false
return
end
-- If the main link is specifically provided, use that.
if (main) then
self.main = main
table.insert(self.styles, 'Module:Hatnote/styles.css')
return
end
-- Get the name by, stripping parentheses text from the page name.
local cardName = mw.text.split(self.pageName, ' %(')[1]
-- If there's a difference, use the non-paretheses version as the main.
if (self.pageName ~= cardName) then
self.main = cardName
table.insert(self.styles, 'Module:Hatnote/styles.css')
return
end
-- If "main" is not found at this point, assume there is none.
self.main = false
end
-- ------------------------------------
-- Row-adding functions
-- Functions that add key-value pairs to `Card.rows`
-- ------------------------------------
-- Add something to the list of rows to be rendered in the output
-- @param string label - Text to display in the label cell
-- @param string value - Text/icons to display in the data cell
function Card:addRow(label, value)
if (value and value ~= '') then
table.insert(self.rows, { label = label, value = value })
end
end
-- Add row specifically for the Number
function Card:addNumberRow()
self:addRow('Number', self.number)
end
-- Add a row specifically for the card type to be rendered in the output
function Card:addCardTypeRow()
if (self.cardTypes == nil or #self.cardTypes == 0) then return end
local links = {}
for _, cardType in pairs(self.cardTypes) do
table.insert(links, '[[' .. cardType.link .. '|' .. cardType.name .. ']]')
end
local icon = self:renderCardTypeIcon() or ''
self:addRow('[[Card type]]', table.concat(links, ' / ') .. ' ' .. icon)
end
-- Add a row specifically for the property to be rendered in the output
function Card:addPropertyRow()
-- Exit early if no card type or property
if (self.cardTypes == nil or #self.cardTypes == 0 or self.property == nil) then return end
local text = '[[' .. self.property .. ' ' .. self.cardTypes[1].link .. ' |' .. self.property .. ']]'
local icon = self:renderPropertyIcon()
self:addRow('[[Property]]', text .. ' ' .. icon)
end
-- Add a row specifically for the Attribute to be rendered in the output
function Card:addAttributeRow()
-- If it's not a monster, don't continue
if (not self.isMonster) then return end
local attributesWithIcons = {}
for _, attribute in pairs(self.attributes) do
local link = '[[' .. attribute.link .. '|' .. attribute.name .. ']]'
local icon = self:renderAttributeIcon(attribute.name)
table.insert(attributesWithIcons, link .. ' ' .. icon)
end
self:addRow('[[Attribute]]', table.concat(attributesWithIcons, ' / '))
end
-- Add a row specifically for the Type
function Card:addTypeRow()
-- If it's not a monster, don't continue
if (not self.isMonster) then return end
local text = self:renderTypeString()
local icons = self:renderTypeIcons()
self:addRow('[[Type]]s', mw.text.trim(text .. ' ' .. icons))
end
-- Add a row specifically for the Level to be rendered in the output
function Card:addLevelRow()
-- Don't show if the card doesn't have a Level
if (not self.level) then return end
-- Don't show for Xyz or Link monsters
if (self.summonType and TableTools.inArray({'Xyz', 'Link'}, self.summonType.name)) then
return
end
self:addRow('[[Level]]', (self.level) .. ' ' .. self:renderLevelStars())
end
-- Add a row specifically for the Rank to be rendered in the output
function Card:addRankRow()
-- Only show for Xyz Monsters
if (not self.summonType or self.summonType.name ~= 'Xyz') then return end
self:addRow('[[Rank]]', (self.rank or '???') .. ' ' .. self:renderRankStars())
end
-- Add a row specifically for the Link Arrows
function Card:addLinkArrowsRow()
-- Not a Link Monster, exit early
if (not self.summonType or self.summonType.name ~= 'Link') then return end
-- Cell contains the map showing the different arrows and a textual list
-- Wrap the two in a flex div to vertically center them
local cell = mw.html.create('div')
cell
:css('display', 'flex')
:css('align-items', 'center')
:css('gap', '.25em')
:wikitext(self:renderLinkMap())
:tag('div')
:wikitext(table.concat(self.linkArrows, ', '))
self:addRow('[[Link Arrow]]s', tostring(cell))
end
-- Add a row specifically for the Pendulum Scale
function Card:addPendulumScaleRow()
-- Only allow this row for Pendulum Monsters with a Pendulum Scale
if (not self.isPendulumMonster or self.pendulumScale == nil) then
return nil
end
local icon = '[[File:Pendulum Scale.png|22px|alt=|class=noviewer]]'
self:addRow('[[Pendulum Scale]]', icon .. ' ' .. self.pendulumScale)
end
-- Add a row specifically for ATK and DEF to be rendered in the output
-- Only shows for monsters that are not Link Monsters
function Card:addAtkDefRow()
-- If the card is not a monster don't show this row.
if (not self.isMonster) then return nil end
-- If the card is a Link Monster don't show this row.
if (self.summonType and self.summonType.name == 'Link') then return nil end
-- If both values are blank and the cardType is "Token" don't show the row.
if (not self.atk and not self.def and self:hasCardType('Token')) then
return nil
end
local atk = self.atk or '???'
local def = self.def or '???'
self:addRow('[[ATK]] / [[DEF]]', atk .. ' / ' .. def)
end
-- Add a row specifically for ATK and Link to be rendered in the output
-- Only shows for Link Monsters
function Card:addAtkLinkRow()
-- Don't show this row for non-Link monsters
if (not self.summonType or self.summonType ~= 'Link') then return end
local atk = self.atk or '???'
local link = self.linkRating or '???'
self:addRow('[[ATK]] / [[Link Rating|LINK]]', atk .. ' / ' .. link)
end
-- Add a row specifically for the effect types
function Card:addEffectTypeRow()
if (#self.effectTypes == 0) then return end
local linkedEffectTypes = {}
-- Loop through all the card's effect types
for _, effectType in pairs(self.effectTypes) do
-- Create a pipe link and add it to the array.
local linkedEffectType = ('[[%s|%s]]'):format(effectType.link, effectType.name)
table.insert(linkedEffectTypes, linkedEffectType)
end
-- Join all elements in the array, separating them with commas
local effectTypesString = table.concat(linkedEffectTypes, ', ')
self:addRow('Effect types', effectTypesString)
end
-- Add a rowspecifically for the lore
function Card:addLoreRow()
local lore = self.locales.en:getFullLore(self)
if (lore == nil or lore == '') then return end
local loreHtml = mw.html.create('div')
loreHtml:attr('class', 'lore')
if (self.locales.en.pendulumEffect) then
-- Place Pendulum Effect and monster text in a description list
local dl = mw.html.create('dl')
dl:css('margin', '.5em')
dl:tag('dt'):wikitext("'''Pendulum Effect'''")
dl:tag('dd'):wikitext(self.locales.en.pendulumEffect)
dl:tag('dt'):wikitext("'''Monster text'''")
dl:tag('dd'):wikitext(lore)
loreHtml:wikitext(tostring(dl))
else
-- Place lore in a single paragraph in the
loreHtml:tag('p'):wikitext(lore)
end
self:addRow(nil, tostring(loreHtml))
end
-- Add row specifically for the Limitation text
function Card:addLimitationTextRow()
self:addRow('[[Limitation text]]', self.limitationText)
end
-- Add row specifically for the Password
function Card:addPasswordRow()
self:addRow('[[Password]]', self.password)
end
-- ------------------------------------
-- Getters
-- Functions that retrieve information from the instance of the `Card` object
-- ------------------------------------
-- Get the card's (standard) Type
-- @return string
function Card:getType()
-- Exit early, if not a monster
if (not self.isMonster) then return nil end
-- loop through the Types until the standard Type is found
for _, type in pairs(self.types) do
if (type.category == 'Type') then
return type.name
end
end
-- Type was not found, return `nil`
return nil
end
-- Get the CSS class for the card's color
-- (Does not include Pendulum)
-- @return string
function Card:getColorClass()
-- If the card has a custom color, use that
if (self.customColor) then
return self.customColor .. '-card'
end
-- If the card type has a color class use that
if (self.cardTypes[1] and self.cardTypes[1].colorClass) then
return self.cardTypes[1].colorClass
end
-- If the summon type (Fusion, Ritual, etc.) has a colour, use that
if (self.summonType and self.summonType.colorClass) then
return self.summonType.colorClass
end
-- If the card is an Effect Monster, base the color on that
if (self.isEffectMonster) then
return 'effect-card'
end
-- If the card is a Normal Monster, base the color on that
if (self.isNormalMonster) then
return 'normal-card'
end
-- If the color couldn't be determined above, use this as the default
return 'blank-card'
end
-- Get the variable CSS classes used in the HTML output
-- Includes the main color class and the pendulum color class
-- @return string
function Card:getCssClass()
local cssClass = ''
-- If there is a class to be used for all cards, add that
if (self.config.baseClass) then
cssClass = cssClass .. self.config.baseClass
end
-- If the cards are color-coded, add more classes
if (self.config.colorCoded) then
cssClass = cssClass .. self:getColorClass()
if (self.isPendulumMonster) then
cssClass = cssClass .. ' pendulum-card'
end
end
return cssClass
end
-- Check if the card has a specified card type
-- @param cardTypeName string
-- @return boolean
function Card:hasCardType(cardTypeName)
-- Loop through each card type
for _, cardType in pairs(self.cardTypes) do
-- If one of them has the supplied name, return true
if (cardType.name == cardTypeName) then
return true
end
end
-- Otherwise return false
return false
end
-- ------------------------------------
-- Rendering functions
-- Functions that format details from the card in a certain way
-- ------------------------------------
-- Render an icon representing the card type
-- @return string
function Card:renderCardTypeIcon()
-- If the card is not a Spell or Trap, exit early
-- Only bother with the first supplied card type.
-- (Things with multiple card types don't use this icon.)
if (not self.cardTypes[1].hasIcon) then
return ''
end
-- String containing a pattern matching the card type icons
local pattern = self.config.icons.cardTypes
-- If there's no pattern, don't try to render an icon.
if (not pattern) then return '' end
-- Replace variables in the pattern.
-- e.g. replace '{$cardType}' with 'Spell' if this is a Spell Card.
local params = {
cardType = self.cardTypes[1].name,
cardType_uc = string.upper(self.cardTypes[1].name)
}
return replaceParams(pattern, params)
end
-- Render an icon representing the Property
-- @return string
function Card:renderPropertyIcon()
-- If the card type doesn't have an icon, end early
if (#self.cardTypes[1] == 0 or self.cardTypes[1].hasIcon == nil) then
return ''
end
-- String containing a pattern matching the property icons
local pattern = self.config.icons.properties
-- If there's no pattern, don't try to render an icon.
if (not pattern) then return '' end
-- Replace variables in the pattern.
-- e.g. replace '{$cardType}' with 'Spell' if this is a Spell Card.
local params = {
cardType = self.cardTypes[1].name,
property = self.property
}
return replaceParams(pattern, params)
end
-- Render an icon representing the Attribute
-- @return string
function Card:renderAttributeIcon(attributeName)
-- Exit early, if not a monster
if (not self.isMonster) then return '' end
if (attributeName == nil or attributeName == '???' or attributeName == '?') then
return self.config.icons.unknownAttribute
end
local pattern = self.config.icons.attributes
-- If there's no pattern, don't try to render an icon.
if (not pattern) then return '' end
return replaceParams(pattern, { attribute = attributeName })
end
-- Render the list of Types as a string with links and separators
-- @return string
function Card:renderTypeString()
-- Exit early, if not a monster
if (not self.isMonster) then return end
-- Array containing links to each of the card's Types.
local linkedTypes = {}
-- Loop through all the card's Types
for _, type in pairs(self.types) do
-- Create a pipe link and add it to the array.
local linkedType = ('[[%s|%s]]'):format(type.link, type.name)
table.insert(linkedTypes, linkedType)
end
-- Join all elements in the array, separating them with slashes.
return table.concat(linkedTypes, ' / ')
end
-- Render an icon representing the Attribute
-- @return string
function Card:renderTypeIcons()
-- Exit early, if not a monster
if (not self.isMonster) then return '' end
local icons = ''
local standardType = self:getType()
local hasEffect = self.isEffectMonster
local typePattern = self.config.icons.types
local summonTypePattern = self.config.icons.summonTypes
local effectIcon = self.config.icons.effect
if (standardType and typePattern) then
icons = icons .. replaceParams(typePattern, { type = standardType })
end
if (self.summonType and summonTypePattern) then
icons = icons .. replaceParams(summonTypePattern, { type = self.summonType.name })
end
if (hasEffect and effectIcon) then
icons = icons .. effectIcon
end
return icons
end
-- Render the Level as star icons
-- @return string
function Card:renderLevelStars()
-- Don't show for non-monsters
if (not self.isMonster) then return '' end
-- Don't show for Xyz or Link monsters
if (self.summonType and TableTools.inArray({'Xyz', 'Link'}, self.summonType.name)) then
return ''
end
local icons = self.config.icons
-- If unknown Level, just show a single instance of the unknown icon.
if (self.level == nil or self.level == '?' or self.level == '???') then
return icons.unknownLevel
end
-- Now that unknown is out of the way, this can be cast as a number.
local level = tonumber(self.level)
-- Exit early if the Level is 0
if (level == 0) then return '' end
-- If there is one icon pattern to cover all Levels use that.
if (icons.levels ~= nil) then
return replaceParams(icons.levels, { level = self.level})
end
-- Use different icons if Level is positive or negative
local icon = level >= 0 and icons.level or icons.negativeLevel
-- Repeat the same icon a number of times equal to the Level/Rank (stars)
return '<span class="card-stars">' ..
string.rep(icon, math.abs(level)) ..
'</span>'
end
-- Render the Rank as star icons
-- @return string
function Card:renderRankStars()
-- Exit early, if not an Xyz Monster
if (not self.summontype or self.summonType.name ~= 'Xyz') then return '' end
local icons = self.config.icons
-- If unknown Level/Rank, just show a single instance of the unknown icon.
if (self.rank == nil or self.rank == '?' or self.rank == '???') then
return icons.unknownRank
end
-- Exit early if the Rank is 0
if (self.rank == 0) then return '' end
-- Repeat the same icon a number of times equal to the Rank
return '<span class="card-stars">' .. string.rep(icons.rank, self.rank) .. '</span>'
end
-- Render a grid showing the Link Arrows
-- @return string
function Card:renderLinkMap()
local gridPositions = {
'Top-Left', 'Top-Center', 'Top-Right',
'Middle-Left', 'Middle-Center', 'Middle-Right',
'Bottom-Left', 'Bottom-Center', 'Bottom-Right'
}
-- `div` for a 3x3 grid
local map = mw.html.create('div')
map
:css('display', 'inline-grid')
:css('width', '36px'):css('height', '46px')
:css('grid-template-rows', '12px 12px 12px')
:css('grid-template-columns', '10px 15px 10px')
-- Add an element for each position in the grid
for _, position in pairs(gridPositions) do
if (position == 'Middle-Center') then
-- Nothing in the center square
map:wikitext(' ')
else
-- Arrow will be highlighted in in the list of Link Arrows
local active = TableTools.inArray(self.linkArrows, position)
-- These two images have a different width than the others
local size = TableTools.inArray({ 'Top-Center', 'Bottom-Center' }, position) and 'x10px' or '10px'
-- Form the file name
-- Remove '-' from the position name, add "2" if the position is inactive
local file = 'LM-' .. (position:gsub('-', '')) ..(not active and '2' or '') .. '.png'
map:wikitext('[[File:' .. file .. '|' ..size .. '|alt=]]')
end
end
return tostring(map)
end
-- Render the card image section
-- Either a single image or a switcher for multiple images
-- @return string
function Card:renderImages()
-- If there's only one image, show it
if (#self.images == 1) then
local imagePageName = self.images[1].image ~= ''
and self.images[1].image
or self.config.defaultImage
return '[[File:' .. imagePageName .. '|200px]]'
end
-- Create the HTML for the image switcher wrapper
local imageSwitcher = mw.html.create('div')
imageSwitcher
:addClass('switcher-container')
:css('margin', '0 auto')
:css('max-width', '200px')
:css('text-align', 'left')
-- Add a child element for each image and its label
for _, image in pairs(self.images) do
local title = nil
if (image.name) then
title = image.name
-- If this is the only image other than the backing, set "Front" as its caption.
elseif (#self.images == 2 and self.hasCustomBacking and not image.isBack) then
title = 'Front'
else
title = 'Artwork ' .. image.artwork
end
local imagePageName = image.image ~= ''
and image.image
or self.config.defaultImage
local imageDisplay = '[[File:' .. imagePageName .. '|200px|alt=' .. title .. ']]'
local labelHtml = mw.html.create('div')
labelHtml:attr('class', 'switcher-label'):wikitext(title)
if (image.isCurrent) then
labelHtml:attr('data-switcher-default', 'true')
end
imageSwitcher
:tag('div')
:css('margin-bottom', '5px')
:wikitext(imageDisplay .. tostring(labelHtml))
end
return tostring(imageSwitcher)
end
-- Create a TemplateStyles tag
local function renderTemplateStyles(page)
if (page == nil or page == '') then return end
return tostring( mw.getCurrentFrame():extensionTag{ name = 'templatestyles', args = { src = page } } )
end
-- Generate the wikitext output
function Card:render()
local output = self:renderCardSection()
.. self:renderAdditionalSections()
.. self:renderLocalesSection()
.. self:renderCategories()
-- Show the debut date
-- Might add a section for debugging information later.
-- Or something to show SMW data when that's set up.
if (self.debutDate) then
output = output .. '<pre>Debut date: ' .. self.debutDate .. '</pre>'
end
return output
end
-- Generate the wikitext for the card details portion of the output
-- @return string
function Card:renderCardSection()
local output = ''
-- Add template styles
for _, style in pairs(self.styles) do
output = output .. '\n' .. renderTemplateStyles(style)
end
if (self.main) then
output = output .. '<div role="note" class="hatnote navigation-not-searchable">Main card page: "[[' .. self.main .. ']]"</div>'
end
output = output .. '\n<div class="card-table ' .. self:getCssClass() .. '">'
output = output .. '\n <div class="heading">' .. self.locales.en.name .. '</div>'
if (self.locales.ja) then
output = output .. '\n <div class="above hlist">' .. self:renderJaNamesList() .. '</div>'
end
output = output .. '\n <div class="card-table-columns">'
output = output .. '\n <div class="imagecolumn">'
output = output .. self:renderImages()
output = output .. '\n </div>' -- .imagecolumn
output = output .. '\n <div class="infocolumn">'
output = output .. '\n <table class="innertable">'
for _, row in pairs(self.rows) do
output = output .. '\n<tr>'
if (row.label) then
output = output .. '\n <th scope="row" style="text-align: left;">' .. row.label .. '</th>'
output = output .. '\n <td>' .. row.value .. '</td>'
else
output = output .. '\n <td colspan="2">' .. row.value .. '</td>'
end
output = output .. '\n</tr>'
end
output = output .. '\n </table>'
output = output .. '\n </div>' -- .infocolumn
output = output .. '\n </div>' -- .card-table-columns
output = output .. self:renderFooter()
output = output .. '\n</div>' -- .card-table
return output
end
-- Generate a section
-- This function is necessary for sections to be collapsible on mobile.
-- Elements generated by Scribunto are added after MobileFrontEnd processes page
-- content and makes headings collapsible.
-- This function will prep them to be collapsible. It's not an ideal solution.
--
-- @param title string
-- @param content string
-- @return string
function Card.renderSection(title, content)
local heading = mw.html.create('h2')
:addClass('section-heading')
:addClass('collapsible-heading')
:addClass('collapsible-heading--imitation')
:wikitext(title)
local body = mw.html.create('div')
:addClass('collapsible-block')
:addClass('collapsible-block--imitation')
:wikitext(content)
return tostring(heading) .. tostring(body);
end
-- Render the footer section of the card-details output
-- @return string
function Card:renderFooter()
if (not self.config.footerText or self.config.footerText == '') then return '' end
local output = '<div class="below">'
if (self.prev or self.next) then
output = output .. '\n <div class="chronology">'
output = output .. '\n <div class="prev">' .. (self.prev or '') .. '</div>'
output = output .. '\n <div class="curr">' .. self.config.footerText .. '</div>'
output = output .. '\n <div class="prev">' .. (self.next or '') .. '</div>'
output = output .. '\n </div>'
else
output = output .. self.config.footerText
end
output = output .. '\n</div>'
return output
end
-- Generate the wikitext for the "sets" section of the output
-- (Empty here. Can be overwritten/extended in modules that extend this one.)
-- @return string
function Card:renderAdditionalSections()
return ''
end
-- Generate the wikitext for the "other languages" section
-- @return string
function Card:renderLocalesSection()
-- Needs to have at least two languages
-- i.e. Needs at least one language other than English
if (TableTools.size(self.locales) < 2) then return '' end
output = '\n<table class="wikitable">'
output = output .. '\n <tr>'
output = output .. '\n <th scope="col">Language</th>'
output = output .. '\n <th scope="col">Name</th>'
output = output .. '\n <th scope="col">Card text</th>'
output = output .. '\n </tr>'
-- Iterate through `self.config.langs`, rather than `self.locales`
-- because "langs" is numerically indexed, so its ordering is respected
for _, lang in pairs(self.config.langs) do
-- Only include languages that have recorded data.
-- Don't include English.
if (lang ~= 'en' and self.locales[lang]) then
local locale = self.locales[lang]
local langCode = locale:getHtmlLang()
output = output .. '\n<tr>'
output = output .. '\n <th scope="row">' .. locale.language.name .. '</th>'
output = output .. '\n <td lang="' .. langCode .. '">'
output = output .. (locale.name or '')
if (locale.romanizedName) then
output = output .. '<br />(<i lang="' .. locale:getRomanizedHtmlLang() .. '">' .. locale.romanizedName .. '</i>)'
end
output = output .. '</td>'
output = output .. '\n <td lang="' .. langCode .. '">' .. locale:getFullLore(self) .. '</td>'
output = output .. '\n</tr>'
end
end
output = output .. '</table>'
return self.renderSection('Other languages', output)
end
-- Render a description list for the different forms of the Japanese name
-- @return string
function Card:renderJaNamesList()
if not self.locales.ja then return '' end
local ja = self.locales.ja
local jaBaseName = ja:getBaseName()
local jaTopName = ja:getTopName()
local list = mw.html.create('dl')
-- Main Japanese name
if (ja.name) then
list:tag('dt'):wikitext('Japanese')
list:tag('dd'):tag('span'):attr('lang', 'ja-Japn'):wikitext(' ' .. ja.name)
end
-- Base Japanese name, if different than main
if (jaBaseName and jaBaseName ~= ja.name) then
list:tag('dt'):wikitext('Base')
list:tag('dd'):tag('span'):attr('lang', 'ja-Japn'):wikitext(' ' .. jaBaseName)
end
-- Base rōmaji name, if different than main rōmaji name
if (ja.baseRomanizedName and ja.baseRomanizedName ~= ja.romanizedName) then
list:tag('dt'):wikitext("Base ''rōmaji''")
list:tag('dd'):tag('span'):attr('lang', 'ja-Latn-hepburn'):wikitext(' <i>' .. ja.BaseRomanizedName .. '</i>')
end
-- Base translated name, if different than main translated name
if (ja.baseTranslatedName and ja.baseTranslatedName ~= ja.translatedName) then
list:tag('dt'):wikitext("Base translated")
list:tag('dd'):tag('span'):wikitext(' ' .. jaBaseTranslatedName)
end
-- Furigana name, if different than main
if (jaTopName and jaTopName ~= ja.name) then
list:tag('dt'):wikitext('Furigana')
list:tag('dd'):tag('span'):attr('lang', 'ja-Hrkt'):wikitext(' ' .. jaTopName)
end
-- Rōmaji name
if (ja.romanizedName) then
local label = (ja.baseRomanizedName)
and "Furigana ''rōmaji''"
or "''Rōmaji''"
list:tag('dt'):wikitext(label)
list:tag('dd'):tag('span'):attr('lang', 'ja-Latn-hepburn'):wikitext(' <i>' .. ja.romanizedName .. '</i>')
end
-- Translated name
if (ja.translatedName) then
local label = (ja.baseTranslatedName)
and 'Furigana translated'
or 'Translated'
list:tag('dt'):wikitext(label)
list:tag('dd'):tag('span'):wikitext(' ' .. ja.translatedName)
end
return tostring(list)
end
-- Render the categories
-- If this is a demo page, show a section with links to each category.
-- For non-demo pages, actually add the categories.
-- @return string
function Card:renderCategories()
if not self.demo then
local categoryText = ''
for _, category in pairs(self.categories) do
categoryText = categoryText .. '[[Category:' .. category .. ']]'
end
return categoryText
end
local categoryBlock = mw.html.create('div')
:attr('class', 'catlinks')
:wikitext('Categories: ')
for i, category in pairs(self.categories) do
if (i ~= 1) then
categoryBlock:wikitext(' | ' )
end
categoryBlock:wikitext('[[:Category:' .. category .. '|' .. category .. ']]')
end
return tostring(categoryBlock)
end
-- Function to be invoked by the module
-- Takes all template parameters and returns the HTML
function Card.card(frame)
-- Get the template parameters
local args = frame:getParent().args
-- If a parameter contains an empty string, ignore it
local normalizedArgs = {}
for param, value in pairs(args) do
if (value and mw.text.trim(value) ~= '') then
normalizedArgs[param] = value
end
end
-- Create a new card object
local c = Card:new(normalizedArgs)
-- For each row configured to show, call the function to add it
for _, row in pairs(c.config.rows) do
c['add' .. ucfirst(row) .. 'Row'](c)
end
-- Render the output
return c:render()
end
return Card