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
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
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
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 EffectType = require('Module:Card/Effect type')
local Locale = require('Module:Card/Locale')
local Type = require('Module:Card/Type')
-- ------------------------------------
-- Generic functions
-- * Might move these to a more dedicated module
-- ------------------------------------
-- Generic function for replacing 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
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
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,
attribute = nil,
types = {},
isMonster = nil,
isNormalMonster = nil,
isEffectMonster = nil,
isPendulumMonster = 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,
-- Params for rendered output
pageName = mw.title.getCurrentTitle().text,
categories = { 'All cards' },
styles = {
'Module:Card/base.css'
},
main = 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=]]',
-- 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,
},
-- 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, split into categories
-- All Type categories are empty here and should be set in modules that extend this one.
types = {
type = {},
summon = {},
ability = {},
tuner = {},
pendulum = {},
maximum = {},
token = {},
normal = {},
effect = {},
unknown = {},
}
}
}
-- 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
-- Essentially create a clone of the `Card` object defined earlier.
local c = {}
setmetatable(c, self)
self.__index = self
-- Update the `Type` class, so that it accepts values based on the card's config
Type:setAllowedValues(self.config.types)
-- Fill in the new card object with data from the arguments
c.demo = args.demo or false
-- The pagename can only be overwitten in demos
if (c.demo and args.page_name) then
c.pageName = args.page_name
end
c.number = args.number
c.property = args.property
c:setCardTypes(args.card_type, args)
c.property = args.property
c.attribute = args.attribute
c:setTypes(args.types)
c:setLinkArrows(args.link_arrows)
c.atk = args.atk
c.def = args.def
c.level = args.level
c.rank = args.rank
c.pendulumScale = args.pendulum_scale
c.limitationText = args.limitation_text
c.password = args.password
c:setEffectTypes(args.effect_types)
c.locales = Locale:createMany(c.config.langs, args)
c:setName(args.name)
c.customColor = args.color
c:setMainLink(args.main)
-- Add template specific styles or use the ones from Module:Card
table.insert(c.styles, args.templatestyles or 'Module:Card/styles.css')
-- Set parameters other than those supplied above
-- Classes that inherit from this one can use this to set additional data
c:setCustomArgs(args)
-- Set image data after the custom data
-- (There could be something in the custom data affecting the default image.
-- e.g. OCG-only cards having a different default backing.)
c:setImages(args.image)
return c
end
-- ------------------------------------
-- Setters
-- Functions that add/update information on an instance of the `Card` object based on input
-- ------------------------------------
-- 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
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
-- If the card type is explicitly stated use that.
-- Default to "Monster" if the card is a monster.
if (cardTypesString) then
self.cardTypes = mw.text.split(cardTypesString, '%s*/%s*')
elseif (self.isMonster) then
self.cardTypes = { 'Monster' }
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.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
table.insert(self.types, Type:new(typeName))
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
-- Set images data based on the input parameter
-- @param string input
function Card:setImages(input)
-- Clear existing value
self.images = {}
-- Default to backing image if no input
input = input or self.config.defaultImage
-- Split input by new line for multiple artworks
local inputLines = mw.text.split(input, '\n')
local previousArtwork = nil
-- For each line (artwork)
for _, line in pairs(inputLines) do
-- Ensure each item on the line ends with `;` to help pattern matching
line = line .. ';'
-- Content before first `;` is the image
local image = mw.text.split(line, ';')[1]
-- Content after `artwork::` in the params string is the artwork number.
-- Defaults to 1
local artwork = tonumber(line:match('; *artwork::([^;]-) *;') or 1)
-- Content after `thumb::` in the params string is the thumbnail
-- Defaults to the full image
local thumbnail = line:match('; *thumb::([^;]-) *;') or image
-- Content after `name::` in the params string is the artwork name
local name = line:match('; *name::([^;]-) *;')
-- If the params string contains `current`, mark as the current image
local isCurrent = line:match('; *current *;') ~= nil
-- If the artwork has the same base number as its predecessor
-- its number is .1 higher than it
if (previousArtwork ~= nil and math.floor(artwork) == math.floor(previousArtwork)) then
artwork = previousArtwork + .1
end
-- Add object to list of images
table.insert(self.images, {
image = image,
thumbnail = thumbnail,
artwork = artwork,
name = name,
isCurrent = isCurrent
})
-- Prepare for the next iteration of the loop.
previousArtwork = artwork
end
end
-- Function inherited classes can override to set additional parameters
-- @param args table
-- @return void
function Card:setCustomArgs(args)
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
local page = cardType .. ' Card'
if cardType == 'Token' then page = 'Monster Token'
elseif cardType == 'Counter' then page = 'Counter' end
table.insert(links, '[[' .. page .. '|' .. cardType .. ']]')
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] .. ' Card|' .. 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 text = '[[' .. (self.attribute or '???') .. ']]'
local icon = self:renderAttributeIcon()
self:addRow([[Attribute]], text .. ' ' .. icon)
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
self:addRow('[[Type]]s', self:renderTypeString())
end
-- Add a row specifically for the Level to be rendered in the output
function Card:addLevelRow()
-- Don't show for non-monsters
if (not self.isMonster) then return end
-- Don't show for Xyz or Link monsters
local summonType = self:getSummonType()
if (TableTools.inArray({'Xyz', 'Link'}, summonType)) then return end
self:addRow('[[Level]]', (self.level or '???') .. ' ' .. 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 (self:getSummonType() ~= '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 (self:getSummonType() ~= 'Link') then
return nil
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 or is a Link Monster don't show this row.
if (not self.isMonster or self:getSummonType() == '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()
if (self:getSummonType() ~= 'Link') then
return nil
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.pageName, 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 summon Type
-- @return string
function Card:getSummonType()
-- Exit early, if not a monster
if (not self.isMonster) then return nil end
-- loop through the Types until the Summon Type is found
for _, type in pairs(self.types) do
if (type.category == 'Summon') then
return type.name
end
end
-- Summon 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
-- Counters use the same color as Tokens
if (self:hasCardType('Counter')) then return 'token-card' end
-- If the card is not a monster, base the color on the card type
if (self.cardTypes[1] and self.cardTypes[1] ~= 'Monster') then
return string.lower(self.cardTypes[1]) .. '-card'
end
-- If the card has a Summon type (Fusion, Ritual, etc.),
-- base the color on that
local summonType = self:getSummonType()
if (summonType) then
-- The different types of Accel Synchro all use the Synchro color
if (string.find(summonType, 'Accel Synchro')) then
return 'synchro-card'
end
-- Lowercase, replace spaces with dashes, append "-card"
return string.gsub(string.lower(summonType), ' ', '-') .. '-card'
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 cardType string
-- @return boolean
function Card:hasCardType(cardType)
return TableTools.inArray(self.cardTypes, cardType)
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 (self.cardTypes[1] ~= 'Spell' and self.cardTypes[1] ~= 'Trap') 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],
cardType_uc = string.upper(self.cardTypes[1])
}
return replaceParams(pattern, params)
end
-- Render an icon representing the Property
-- @return string
function Card:renderPropertyIcon()
-- If the card is not a Spell or Trap, exit early.
-- Only bother with the first card type.
-- (Things with multiple card types don't use this icon.)
if (self.cardTypes[1] ~= 'Spell' and self.cardTypes[1] ~= 'Trap') 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],
property = self.property
}
return replaceParams(pattern, params)
end
-- Render an icon representing the Attribute
-- @return string
function Card:renderAttributeIcon()
-- Exit early, if not a monster
if (not self.isMonster) then return '' end
if (self.attribute == nil or self.attribute == '???') 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
local params = {
attribute = self.attribute
}
return replaceParams(pattern, params)
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.pageName, type.name)
table.insert(linkedTypes, linkedType)
end
-- Join all elements in the array, separating them with slashes.
return table.concat(linkedTypes, ' / ')
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
local summonType = self:getSummonType()
if (TableTools.inArray({'Xyz', 'Link'}, summonType)) 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 (self:getSummonType() ~= '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
return '[[File:' .. self.images[1].image .. '|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 = image.name or ('Artwork ' .. image.artwork)
local imageDisplay = '[[File:' .. image.image .. '|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
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()
return self:renderCardSection()
.. self:renderSetsSection()
.. self:renderLocalesSection()
.. self:renderCategories()
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
-- 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 in modules that extend this one.)
-- @return string
function Card:renderSetsSection()
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==Other languages=='
output = 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 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