Difference between pages "Yu-Gi-Oh! 5D's - Episode 141" and "Module:Card"
(Difference between pages)
(→Featured Duel: Jack Atlas, Leo and Luna vs. Aporia) (Tag: New user edit) |
(Add `lang` attributes to "other languages" table. Don't need the `locale.getLanguageName` method anymore.) |
||
Line 1: | Line 1: | ||
− | + | -- Import modules | |
− | + | TableTools = require('Module:TableTools') | |
− | + | local Locale = require('Module:Card/Locale') | |
− | + | local Type = require('Module:Card/Type') | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | -- Generic function for replacing parameters within a string | |
+ | -- Might move to module of its own. | ||
+ | -- @param message string | ||
+ | -- @param params table | ||
+ | -- @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 | |
− | + | -- @param text string | |
− | + | -- @return string | |
+ | function ucfirst(text) | ||
+ | return text:sub(1,1):upper() .. text:sub(2) | ||
+ | end | ||
− | + | -- Create an empty Card object | |
+ | local Card = { | ||
+ | cardType = nil, | ||
+ | property = nil, | ||
+ | attribute = nil, | ||
+ | types = {}, | ||
+ | isNormalMonster = nil, | ||
+ | isEffectMonster = nil, | ||
+ | isPendulumMonster = nil, | ||
+ | level = nil, | ||
+ | linkArrows = {}, | ||
+ | atk = nil, | ||
+ | def = nil, | ||
+ | linkRating = nil, | ||
+ | password = nil, | ||
+ | effectTypes = {}, | ||
+ | locales = { | ||
+ | en = { | ||
+ | name = nil, | ||
+ | lore = nil, | ||
+ | pendulumEffect = nil, | ||
+ | previousNames = {} | ||
+ | } | ||
+ | }, | ||
+ | customColor = nil, | ||
− | + | -- Params for rendered output | |
+ | styles = { | ||
+ | 'Module:Card/base.css' | ||
+ | }, | ||
+ | main = false, | ||
+ | images = {}, | ||
+ | rows = {}, | ||
− | + | -- Configuration options | |
+ | -- Overwrite these in modules that extend this one | ||
+ | config = { | ||
+ | baseClass = nil, | ||
+ | colorCoded = true, | ||
+ | defaultImage = 'Back-EN.png', | ||
+ | enableMainLinks = true, | ||
+ | icons = { | ||
+ | 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=]]', | ||
− | [[File: | + | cardTypes = '[[File:{$cardType_uc}.svg|28px|alt=]]', |
− | + | properties = '[[File:{$property}.svg|28px|alt=]]', | |
+ | attributes = '[[File:{$attribute}.svg|28px|alt=]]', | ||
+ | unknownAttribute = '[[File:UNKNOWN.svg|28px|alt=]]', | ||
+ | types = nil, | ||
+ | }, | ||
+ | langs = { 'en', 'fr', 'de', 'it', 'es', 'ja', 'ko' }, | ||
+ | -- Rows to show in the rendered output | ||
+ | rows = { | ||
+ | 'cardType', | ||
+ | 'property', | ||
+ | 'attribute', | ||
+ | 'type', | ||
+ | 'level', | ||
+ | 'rank', | ||
+ | 'pendulumScale', | ||
+ | 'linkArrows', | ||
+ | 'atkDef', | ||
+ | 'atkLink', | ||
+ | 'lore' | ||
+ | }, | ||
+ | types = { | ||
+ | type = {}, | ||
+ | summon = {}, | ||
+ | ability = {}, | ||
+ | tuner = {}, | ||
+ | pendulum = {}, | ||
+ | normal = {}, | ||
+ | effect = {}, | ||
+ | unknown = {}, | ||
+ | } | ||
+ | } | ||
+ | } | ||
− | |||
− | + | -- Create a new card object | |
+ | -- @param args table | ||
+ | function Card:new(args) | ||
+ | -- Create a new instance of the class with all the default values | ||
+ | local c = {} | ||
+ | setmetatable(c, self) | ||
+ | self.__index = self | ||
− | + | -- Type class to accept values based on the card's config | |
− | + | Type:setAllowedValues(self.config.types) | |
− | + | -- Fill with data from the arguments | |
+ | c.number = args.number | ||
+ | c.property = args.property | ||
+ | c:setCardType(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.password = args.password | ||
− | + | -- Set language related data | |
− | + | c.locales = Locale:createMany(c.config.langs, args) | |
− | + | c:setName(args.name) | |
− | + | c.customColor = args.color | |
+ | c:setMainLink(args.main) | ||
+ | c:setImages(args.image) | ||
− | + | -- 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) | ||
+ | return c | ||
+ | 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 | ||
+ | -- Get the name of the page | ||
+ | local pageName = mw.title.getCurrentTitle().text | ||
− | + | -- Use the page name before the first parenthesis as the card name. | |
+ | self.locales.en.name = mw.text.split(pageName, ' %(')[1] | ||
+ | end | ||
+ | end | ||
− | + | -- Set the card type | |
− | + | -- @param cardType string | |
+ | -- @param table args | ||
+ | function Card:setCardType(cardType, args) | ||
+ | -- If the card type is explicitly stated use that | ||
+ | if (cardType) then | ||
+ | self.cardType = cardType | ||
+ | -- Default to 'Monster' if the arguments contain any monster properties | ||
+ | elseif (args.atk or args.def or args.attribute or args.types) then | ||
+ | self.cardType = '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 | ||
+ | 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 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 page name, strip out parentheses text. | |
+ | local pageName = tostring(mw.title.getCurrentTitle()) | ||
+ | local cardName = mw.text.split(pageName, ' %(')[1] | ||
− | + | -- If there's a difference, use the non-paretheses version as the main. | |
− | + | if (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 | ||
+ | |||
+ | -- Get the card's summon Type | ||
+ | -- @return string | ||
+ | function Card:getSummonType() | ||
+ | -- Exit early, if not a monster | ||
+ | if (self.cardType ~= 'Monster') 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 | ||
+ | |||
+ | -- If the card is not a monster, base the color on the card type | ||
+ | if (self.cardType and self.cardType ~= 'Monster') then | ||
+ | return string.lower(self.cardType) .. '-card' | ||
+ | end | ||
+ | |||
+ | -- If the card has a Summon type (Fusion, Ritual, etc.), | ||
+ | -- base the color on that | ||
+ | local summonType = self:getSummonType() | ||
+ | if (summonType) then | ||
+ | -- 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 | ||
+ | |||
+ | -- Render an icon representing the card type | ||
+ | -- @return string | ||
+ | function Card:renderCardTypeIcon() | ||
+ | -- If the card is not a Spell or Trap, exit early | ||
+ | if (self.cardType ~= 'Spell' and self.cardType ~= '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.cardType, | ||
+ | cardType_uc = string.upper(self.cardType) | ||
+ | } | ||
+ | |||
+ | 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 | ||
+ | if (self.cardType ~= 'Spell' and self.cardType ~= '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.cardType, | ||
+ | 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 (self.cardType ~= 'Monster') 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 (self.cardType ~= 'Monster') 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 (self.cardType ~= 'Monster') 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 | ||
+ | |||
+ | -- 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 | ||
+ | |||
+ | -- 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) 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.cardType == nil) then return end | ||
+ | |||
+ | local text = '[[' .. self.cardType .. ' Card|' .. self.cardType .. ']]' | ||
+ | local icon = self:renderCardTypeIcon() or '' | ||
+ | self:addRow('[[Card type]]', text .. ' ' .. 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.cardType == nil or self.property == nil) then return end | ||
+ | |||
+ | local text = '[[' .. self.property .. ' ' .. self.cardType .. ' 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 (self.cardType ~= 'Monster') then return end | ||
+ | |||
+ | local text = '[[' .. (self.attribute or '???') .. ']]' | ||
+ | local icon = self:renderAttributeIcon() | ||
+ | |||
+ | self:addRow([[Attribute]], text .. ' ' .. icon) | ||
+ | end | ||
+ | |||
+ | function Card:addTypeRow() | ||
+ | -- If it's not a monster, don't continue | ||
+ | if (self.cardType ~= 'Monster') 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 (self.cardType ~= 'Monster') 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 | ||
+ | |||
+ | 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 (self.cardType ~= 'Monster' or self:getSummonType() == 'Link') 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 rowspecifically for the lore | ||
+ | function Card:addLoreRow() | ||
+ | local lore = self.locales.en:getFullLore(self.isNormalMonster) | ||
+ | |||
+ | 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 Password | ||
+ | function Card:addPasswordRow() | ||
+ | self:addRow('[[Password]]', self.password) | ||
+ | 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 HTML output | ||
+ | function Card:render() | ||
+ | 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>' | ||
+ | |||
+ | 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 .. '\n</div>' -- .card-table | ||
+ | |||
+ | output = output .. self:renderLocalesTable() | ||
+ | |||
+ | return output | ||
+ | end | ||
+ | |||
+ | function Card:renderLocalesTable() | ||
+ | -- 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>' | ||
+ | |||
+ | for _, locale in pairs(self.locales) do | ||
+ | local langCode = locale:getHtmlLang() | ||
+ | if locale.lang ~= 'en' then | ||
+ | output = output .. '\n<tr>' | ||
+ | output = output .. '\n <th scope="row">' .. locale.language.name .. '</th>' | ||
+ | output = output .. '\n <td lang="' .. langCode .. '">' .. (locale.name or '') .. '</td>' | ||
+ | output = output .. '\n <td lang="' .. langCode .. '">' .. (locale:getFullLore(self.isNormalMonster) or '') .. '</td>' | ||
+ | output = output .. '\n</tr>' | ||
+ | end | ||
+ | end | ||
+ | |||
+ | output = output .. '</table>' | ||
+ | |||
+ | return output | ||
+ | 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 | ||
+ | |||
+ | -- Create a new card object | ||
+ | local c = Card:new(args) | ||
+ | |||
+ | -- 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 |
Revision as of 11:50, 19 August 2023
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.
-- Import modules
TableTools = require('Module:TableTools')
local Locale = require('Module:Card/Locale')
local Type = require('Module:Card/Type')
-- Generic function for replacing parameters within a string
-- Might move to module of its own.
-- @param message string
-- @param params table
-- @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
-- @param text string
-- @return string
function ucfirst(text)
return text:sub(1,1):upper() .. text:sub(2)
end
-- Create an empty Card object
local Card = {
cardType = nil,
property = nil,
attribute = nil,
types = {},
isNormalMonster = nil,
isEffectMonster = nil,
isPendulumMonster = nil,
level = nil,
linkArrows = {},
atk = nil,
def = nil,
linkRating = nil,
password = nil,
effectTypes = {},
locales = {
en = {
name = nil,
lore = nil,
pendulumEffect = nil,
previousNames = {}
}
},
customColor = nil,
-- Params for rendered output
styles = {
'Module:Card/base.css'
},
main = false,
images = {},
rows = {},
-- Configuration options
-- Overwrite these in modules that extend this one
config = {
baseClass = nil,
colorCoded = true,
defaultImage = 'Back-EN.png',
enableMainLinks = true,
icons = {
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=]]',
cardTypes = '[[File:{$cardType_uc}.svg|28px|alt=]]',
properties = '[[File:{$property}.svg|28px|alt=]]',
attributes = '[[File:{$attribute}.svg|28px|alt=]]',
unknownAttribute = '[[File:UNKNOWN.svg|28px|alt=]]',
types = nil,
},
langs = { 'en', 'fr', 'de', 'it', 'es', 'ja', 'ko' },
-- Rows to show in the rendered output
rows = {
'cardType',
'property',
'attribute',
'type',
'level',
'rank',
'pendulumScale',
'linkArrows',
'atkDef',
'atkLink',
'lore'
},
types = {
type = {},
summon = {},
ability = {},
tuner = {},
pendulum = {},
normal = {},
effect = {},
unknown = {},
}
}
}
-- Create a new card object
-- @param args table
function Card:new(args)
-- Create a new instance of the class with all the default values
local c = {}
setmetatable(c, self)
self.__index = self
-- Type class to accept values based on the card's config
Type:setAllowedValues(self.config.types)
-- Fill with data from the arguments
c.number = args.number
c.property = args.property
c:setCardType(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.password = args.password
-- Set language related data
c.locales = Locale:createMany(c.config.langs, args)
c:setName(args.name)
c.customColor = args.color
c:setMainLink(args.main)
c:setImages(args.image)
-- 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)
return c
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
-- Get the name of the page
local pageName = mw.title.getCurrentTitle().text
-- Use the page name before the first parenthesis as the card name.
self.locales.en.name = mw.text.split(pageName, ' %(')[1]
end
end
-- Set the card type
-- @param cardType string
-- @param table args
function Card:setCardType(cardType, args)
-- If the card type is explicitly stated use that
if (cardType) then
self.cardType = cardType
-- Default to 'Monster' if the arguments contain any monster properties
elseif (args.atk or args.def or args.attribute or args.types) then
self.cardType = '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
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 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 page name, strip out parentheses text.
local pageName = tostring(mw.title.getCurrentTitle())
local cardName = mw.text.split(pageName, ' %(')[1]
-- If there's a difference, use the non-paretheses version as the main.
if (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
-- Get the card's summon Type
-- @return string
function Card:getSummonType()
-- Exit early, if not a monster
if (self.cardType ~= 'Monster') 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
-- If the card is not a monster, base the color on the card type
if (self.cardType and self.cardType ~= 'Monster') then
return string.lower(self.cardType) .. '-card'
end
-- If the card has a Summon type (Fusion, Ritual, etc.),
-- base the color on that
local summonType = self:getSummonType()
if (summonType) then
-- 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
-- Render an icon representing the card type
-- @return string
function Card:renderCardTypeIcon()
-- If the card is not a Spell or Trap, exit early
if (self.cardType ~= 'Spell' and self.cardType ~= '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.cardType,
cardType_uc = string.upper(self.cardType)
}
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
if (self.cardType ~= 'Spell' and self.cardType ~= '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.cardType,
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 (self.cardType ~= 'Monster') 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 (self.cardType ~= 'Monster') 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 (self.cardType ~= 'Monster') 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
-- 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
-- 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) 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.cardType == nil) then return end
local text = '[[' .. self.cardType .. ' Card|' .. self.cardType .. ']]'
local icon = self:renderCardTypeIcon() or ''
self:addRow('[[Card type]]', text .. ' ' .. 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.cardType == nil or self.property == nil) then return end
local text = '[[' .. self.property .. ' ' .. self.cardType .. ' 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 (self.cardType ~= 'Monster') then return end
local text = '[[' .. (self.attribute or '???') .. ']]'
local icon = self:renderAttributeIcon()
self:addRow([[Attribute]], text .. ' ' .. icon)
end
function Card:addTypeRow()
-- If it's not a monster, don't continue
if (self.cardType ~= 'Monster') 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 (self.cardType ~= 'Monster') 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
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 (self.cardType ~= 'Monster' or self:getSummonType() == 'Link') 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 rowspecifically for the lore
function Card:addLoreRow()
local lore = self.locales.en:getFullLore(self.isNormalMonster)
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 Password
function Card:addPasswordRow()
self:addRow('[[Password]]', self.password)
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 HTML output
function Card:render()
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>'
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 .. '\n</div>' -- .card-table
output = output .. self:renderLocalesTable()
return output
end
function Card:renderLocalesTable()
-- 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>'
for _, locale in pairs(self.locales) do
local langCode = locale:getHtmlLang()
if locale.lang ~= 'en' then
output = output .. '\n<tr>'
output = output .. '\n <th scope="row">' .. locale.language.name .. '</th>'
output = output .. '\n <td lang="' .. langCode .. '">' .. (locale.name or '') .. '</td>'
output = output .. '\n <td lang="' .. langCode .. '">' .. (locale:getFullLore(self.isNormalMonster) or '') .. '</td>'
output = output .. '\n</tr>'
end
end
output = output .. '</table>'
return output
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
-- Create a new card object
local c = Card:new(args)
-- 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