Difference between pages "Yu-Gi-Oh! 5D's - Episode 141" and "Module:Card"

From Yugipedia
(Difference between pages)
Jump to: navigation, search
(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:
{{Infobox episode
+
-- Import modules
| image              = YGO5D141.jpg
+
TableTools  = require('Module:TableTools')
| ja_name            = 絶望のデュエル! 機動要塞フォルテシモ!
+
local Locale = require('Module:Card/Locale')
| romaji_name        = Zetsubō no Dyueru! Kidō Yōsai Foruteshimo!
+
local Type  = require('Module:Card/Type')
| ja_trans_name      = Duel of Despair! Fortissimo the Mobile Fortress!
 
| series            = Yu-Gi-Oh! 5D's
 
| number            = 141
 
| season            = 2
 
| ja_opening        = Road to Tomorrow ~Going My Way!!~
 
| ja_ending          = Future Colors
 
| screenwriter      = [[Yasuyuki Suzuki]]<ref name=CastandStaff />
 
| director          = [[Yukihiro Shino]]<ref name=CastandStaff />
 
| storyboard_artist  = [[Shogo Komoto]]<ref name=CastandStaff />
 
| animation_director =
 
* [[Kazuyuki Kobayashi]]<ref name=CastandStaff />
 
* [[Chuichi Iguchi]]<ref name=CastandStaff />
 
| ja_air_date        = December 28, 2010
 
| featured_card      = Fortissimo the Mobile Fortress (anime)
 
| one_point_cards    =  
 
* Fortissimo the Mobile Fortress (anime)
 
* Level Cannon
 
* Lock On Laser
 
}}
 
  
"'''Duel of Despair! Fortissimo the Mobile Fortress!'''" is the one-hundred-and-forty-first episode of the ''[[Yu-Gi-Oh! 5D's]]'' anime. While most episodes air on Wednesdays, this episode aired on a Tuesday. It first aired in Japan on December 28, 2010. The episode was never dubbed, but became available to the world with official subtitles via Crunchyroll on March 10, 2015.
+
-- 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
  
{{episode summary|[[Jack Atlas|Jack]], [[Leo]], and [[Luna]] arrive at the [[Planetary Gear]], where they are faced by [[Aporia]]. Aporia faces them in a Duel, where he has devices forcefully installed on them. The life of the players is expressed through their [[Life Points]], meaning when their Life Points hit 0, they die.}}
+
return message
 +
end
  
==Summary==
+
-- Capitalize the first letter in a string
===Present===
+
-- @param text string
[[Akiza Izinski|Akiza]] and [[Crow Hogan|Crow]] watch as their [[Planetary Gear]] stops. The walls created by [[Sherry LeBlanc|Sherry's]] "[[Soul Binding Gate (anime)|Soul Binding Gate]]" begin to crumble, causing Sherry to fall. Crow runs into the falling debris to try and save her. Afterwards, Sherry, Crow and Akiza find they are miraculously okay as [[Black Rose Dragon (character)|Black Rose Dragon]] has saved them. As Sherry weeps over what she has done, Crow talks her into coming with them to try and change the future.
+
-- @return string
 +
function ucfirst(text)
 +
return text:sub(1,1):upper() .. text:sub(2)
 +
end
  
The streets in [[New Domino City|New Domino]] are deserted as the [[Ark Cradle]] descends closer.
+
-- 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,
  
Up above, [[Bruno|Vizor]] and [[Yusei Fudo|Yusei]] have been traveling along their route for so long, they consider that they may be lost.
+
-- Params for rendered output
 +
styles = {
 +
'Module:Card/base.css'
 +
},
 +
main = false,
 +
images = {},
 +
rows = {},
  
In another route, [[Jack Atlas|Jack]], [[Leo]] and [[Luna]] have also been traveling for quite a while. Leo asks if they should turn back, but having come this far, Jack thinks they should continue. Leo agrees and assumes that they'll reach the core soon and see Yusei again. Luna then wonders if Yusei is really going to die, but Jack reminds them that they're here to stop that future. Jack then thinks to himself that if Yusei really does die, Jack won't be able to settle the scores and prays for Yusei not to die.
+
-- 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:5Dx141 Clamps and chains.jpg|thumb|left|The devices and cuffs Aporia attaches to his opponents.]]
+
cardTypes        = '[[File:{$cardType_uc}.svg|28px|alt=]]',
Jack and the twins come to the end of their route and dismount from [[Phoenix Whirlwind]]. Seeing the [[Ener-D]] light, they suspect that they have reached their Planetary Gear. [[Aporia]] then appears before them, affirms that they are correct and vows to stop them reaching the gear. A hole is opened in the wall and three devices are ejected. They clamp onto Jack, Leo and Luna's chests and cuffs come out of the floor and wrap around their left feet. They struggle to pull off the devices but they're stuck. Needles begins to come out of the devices and pierce into their bodies in the direction of their hearts. Jack curses Aporia and demands to know what these are. Aporia explains that these are to stop them running away, but he says that he shall crush them before they can even think of running away. He repeats that he shall not let them get to Planetary Gear, not unless they can defeat him in a Duel. The twins are worried, but Jack says to bring it on, as he shall defeat Aporia.
+
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  = {},
 +
}
 +
}
 +
}
  
To their surprise, Leo and Luna find that their [[Duel Disk]]s can activate, meaning that the Ener-D is working again. Leo is excited as this means he can help too. Jack reminds him to be careful and this is a fight for [[Team 5D's]]. Leo gets even more excited and turns to Luna, telling her to do her best. Luna nods, but gets a bit worried. However Leo vows to protect her.
 
  
Aporia explains that this Duel shall follow [[Tag Duel]] rules. He will start with 4000 [[Life Points]] and go first. After his turn, his opponent's get a turn each and they will continue to rotate that way. The ring comes off Aporia's back and forms into his Duel Disk.  
+
-- 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
  
[[File:5Dx141 Connect.jpg|thumb|right|Aporia connects to Fortissimo.]]
+
-- Type class to accept values based on the card's config
Aporia begins by activating the [[Field Spell Card|Field Spell]] "[[Fortissimo the Mobile Fortress (anime)|Fortissimo the Mobile Fortress]]". The arena to a blue tube-like enclosure. The walls are lined with hexagons connected by pipes, all connected to a giant fortress that Aporia stands in. The floor Jack and the twins are standing on is left floating mid-air inside the tube. Aporia's legs join and his lower body open to reveal a number of connection ports. An array of wires come out of the fortress and connect to him. With this, Aporia says that the Duel is now setup; His opponent's life is connected to this Duel. If their [[Life Points]] reach 0, so do their lives. Leo realizes that this means they could die. Jack refuses to let Aporia treat their lives like that, but Aporia points out that he doesn't have a choice. He explains that thing attached to Jack, Leo and Luna's chests cause the Planetary Gear to spin, meaning that their "wasteful hearts will be fighting against machines".
+
Type:setAllowedValues(self.config.types)
  
{{clear}}
+
-- 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
  
===Flashback===
+
-- Set language related data
[[File:5Dx141 Embodiments.jpg|thumb|left|Z-one and Aporia discuss the embodiments of despair.]]
+
c.locales = Locale:createMany(c.config.langs, args)
[[Z-one]] approaches Aporia and asks if he wants to stop humanity. Aporia says that he does and Z-one asks Aporia what he means by humans. Z-one explains that Aporia's three embodiments of despair created three humans, whom Z-one has named [[Lester]], [[Primo]] and [[Jakob]] and the three of them were used for his rebirth. Aporia says that he no longer needs despair; After losing to Team 5D's in the [[World Racing Grand Prix]], he lost his feeling of despair and that is what saved him. When he lost and felt there would be trouble, he felt despair. That is why he has decided that he no longer needs it nor hope. He then says all he needs are machines because with them, he believes he can protect Z-one for the sake of the future.  
+
c:setName(args.name)
  
{{clear}}
+
c.customColor = args.color
 +
c:setMainLink(args.main)
 +
c:setImages(args.image)
  
===Present===
+
-- Add template specific styles or use the ones from Module:Card
Aporia says that for the sake of the future, he shall not let his opponents past, not until their Life Points hit 0.
+
table.insert(c.styles, args.templatestyles or 'Module:Card/styles.css')
  
The effect of "Fortissimo the Mobile Fortress" activates. Once per turn it lets Aporia [[Special Summon]] a [[Level]] 4 or lower [[Machine]]-[[Type]] [[Monster Card|monster]] from his [[hand]] So he Special Summons "[[Meklord Army of Wisel (anime)|Meklord Army of Wisel]]". He then [[Normal Summon]]s "[[Meklord Army of Skiel (anime)|Meklord Army of Skiel]]" and [[Set]]s three cards in his [[Spell & Trap Card Zone]] before ending his turn. The twins are surprised to see him using new monsters.
+
-- 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
  
[[File:5Dx141 Impact.jpg|thumb|right|The damage turns out to be real.]]
+
-- Set the English name
Leo goes next, thinking he is ready to prove his skills. He Summons "[[Morphtronic Staplen (anime)|Morphtronic Staplen]]" in [[Defense Position|Defense Mode]], but Aporia activates one of his [[face-down]] cards; the [[Continuous Trap Card]] "[[Level Cannon]]". When a player Summons a monster, "Level Cannon" inflicts 200 damage times the monster's [[Level]] to the player. Two blasts are fired at Leo, who tries running away, but falls forward, due to the cuff on his leg. His Life Points drop to 3200 and when the smoke from the blasts cleared, Luna noticed the floor had been damaged and wondered if that was a real impact. Aporia explained that like he said their lives are linked to the Duel, so as their Life Points decrease, they will receive real pain. If they don't want that to happen, he advises the to come at him with full force.
+
-- @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
  
Jack curses Aporia again and insists that Aproria should be fighting him alone. He orders Aporia to release the other two, but Aporia remains silent. Leo stands up and tells Jack not to worry about it. He insists it was only a small amount of damage and he's okay. However he secretly worries about Luna taking that kind of damage. Leo Sets three cards in his Spell & Trap Card Zone and ends his turn.
+
-- Use the page name before the first parenthesis as the card name.
 +
self.locales.en.name = mw.text.split(pageName, ' %(')[1]
 +
end
 +
end
  
[[File:5Dx141 Rockets.jpg|thumb|left|Luna gets hit by the rockets from "Damage Boost".]]
+
-- Set the card type
Luna goes next. As she [[Draw Phase|draws]] her card, she worries that she will take the same impact as Leo if she Summons a monster. Leo tells her not to worry, as he will protect her. Luna looks at his three face-down cards and thinks that he might have something that can prevent the damage. Luna Sets two cards in her Spell & Trap Card Zone and Summons "[[Armored White Bear (anime)|Armored White Bear]]" in Defense Mode. The effect of "Level Cannon" activates, but Leo [[chain]]s "[[Damage Eraser]]" to [[negate]] the damage and gain the same amount of Life Points. He says that he won't let Aporia  harm Luna so easily. However, Aporia chains "[[Damage Boost]]" to negate the effect of "Damage Eraser" and inflict double the damage. An array of rockets are fired at Luna, who lets out a wail as her Life Points drop to 2400. Leo asks if she's okay, but she drops to her knees and ends her turn.
+
-- @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
  
[[File:5Dx141 Laser.jpg|thumb|right|Luna gets struck by the laser from "Fortissimo".]]
+
-- Set Types-related data by passing in the type string
In Luna's [[End Phase]], Aporia uses the effect of "Fortissimo the Mobile Fortress" to inflict another 100 damage to Luna since she doesn't control a Machine-Type monster. Leo objects, but a beam is fired out of the wall and strikes Luna. Luna's Life Points are lowered to 2300 and she lies on the floor injured. Leo starts to blame himself for Luna taking so much damage and begins to cry. However Jack tells him not to cry, as they have to fight this battle until the end.
+
-- @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*')
  
With their lives on the line, Jack thinks that the twins bodies won't be able to take this, so he vows to stop it. Jack begins his turn and activates "[[Trap Pause]]" to negate the effects of his opponent's Continuous Trap Cards until the End Phase. Aporia's "Level Cannon" card turns gray as its effect is negated. For each card negated, Jack draws a card.
+
-- 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')
  
Jack discards "[[Synchro Soldier (anime)|Synchro Soldier]]" to Special Summon "[[Power Giant (anime)|Power Giant]]" from his hand. The Level of "Power Giant" is decreased by the Level of the monster discarded to Summon it, so its Level becomes 5. Next Jack Normal Summons "[[Dark Resonator (anime)|Dark Resonator]]". He tunes the two monsters and [[Summon chants|chants]] ''"The ruler's heartbeats will now file through here. Take witness to its creation shaking power. Synchro Summon! My soul, Red Demon's Dragon"'', as he [[Synchro Summon]]s "[[Red Dragon Archfiend (anime)|Red Dragon Archfiend]]".
+
-- 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
  
Leo celebrates and vows to also stay strong in order to protect Luna. Leo activates "[[Power-Up Adapter (anime)|Power-Up Adapter]]", which is equipped to "Morphtronic Staplen". This prevents "Morphtronic Staplen" from [[attack]]ing, buts transfers its ATK to "Red Dragon Archfiend". He tells Jack that the rest is up to him. Although Jack doesn't seem too impressed. Aporia activates "[[Power Gravity]]" to transfer to the ATK boost to one of his monsters and drop the original target's ATK to 0. The ATK of "Meklord Army of Wisel" and "Red Dragon Archfiend" become 3200 and 0 respectively. Leo starts to blame himself again. Annoyed, Jack Sets a card in his Spell & Trap Card Zone and ends his turn.
+
-- 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*')
  
[[File:5Dx141 My fault.jpg|thumb|left|Leo blaming himself.|Leo blaming himself]]
+
-- Link Rating is equal to the number of Link Arrows
In Jack's End Phase, the effect of "Fortissimo the Mobile Fortress" activates, dropping Jack's Life Points to 3900. Leo falls to his knees and apologizes to Jack. However Jack tells him not to cry again, as the Duel isn't over yet. He orders him to stand up and continue. Leo defeatedly asks what he can do, but Luna tells him that she's okay and asks him to do his best. Leo's eyes begin to water as he thinks he's not a good brother.
+
self.linkRating = #self.linkArrows
 +
end
  
Aporia begins his turn. Since there is only one card in his hand, he is able to activate "[[Aurora Draw (anime)|Aurora Draw]]", which lets him draw 2 cards. Aporia Normal Summons "[[Meklord Army of Granel (anime)|Meklord Army of Granel]]". The effect of "Level Cannon" then activates and drops his own Life Points to 3200. However he appears unharmed, when the smoke clears. Jack curses Aporia and asks if he feels the pain, but Aporia ignores.
+
-- 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
  
Aporia activates the effect of "Meklord Army of Granel", "Gravity Prester". This halves the [[DEF]] of "Armored White Bear". With "Meklord Army of Wisel", Aporia attacks "Red Dragon Archfiend". "Meklord Army of Wisel" uses its "[[Attack names|Ghoul Kick-Up]]" attack to slice "Red Dragon Archfiend" in two and drop Jack's Life Points to 700. The device on Jack's chest starts flashing red and Jack falls to the ground in pain. The effect of "Meklord Army of Wisel" allows "Meklord Army of Skiel" to inflict [[Piercing]] damage. "Meklord Army of Skiel" attacks "Armored White Bear" with its "Twin Pulse" attack, destroying it and dropping Luna's Life Points to 1800. Lastly "Meklord Army of Granel" attacks Luna with its "Gravity Prester" attack, dropping her Life Points to 200. She falls to the floor and Leo tries going over to her, but is held back by the cuff on his foot. He realizes that Luna and Jack are both lying unconscious and asks why this happened.
+
-- 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
  
[[File:Leo devasted from his actions in front of Aporia.jpg|thumb|right|Leo faces Aporia alone as Luna and Jack are knocked unconscious.]]
+
-- If the main link is specifically provided, use that.
Aporia activates "[[Lock On Laser]]". When an opponent [[Set]]s a card in the [[Spell and Trap Card Zone]], it will inflict 200 damage to them. He comments that his opponents act on their feelings alone and shall pay for such actions with their Life Points, as their real lives trickle away in the process. He suggests that Luna and Jack may even  already be gone, as they lie motionless. Leo drops to his knees. Aporia says that by having hearts humans are so easily robbed of hope and by spending their time searching for such desires they subject themselves to suffering. He calls this ridiculous and blames their hearts on the destruction of the future. He address Leo and informs him that this is despair. A tear drops from Leo's eye as Aporia announces the end of his turn.
+
if (main) then
 +
self.main = main
 +
table.insert(self.styles, 'Module:Hatnote/styles.css')
 +
return
 +
end
  
{{clear}}
+
-- Get the page name, strip out parentheses text.
 +
local pageName = tostring(mw.title.getCurrentTitle())
 +
local cardName = mw.text.split(pageName, ' %(')[1]
  
==Featured Duel: Jack Atlas, Leo and Luna vs. Aporia==
+
-- If there's a difference, use the non-paretheses version as the main.
[[File:5Dx141 Aporia VS Jack & Lua and Luca.jpg|thumb|right|250px|[[Aporia]] VS [[Jack Atlas|Jack]], [[Leo]] and [[Luna]].]]
+
if (pageName ~= cardName) then
<u>'''Turn 1: Aporia'''</u><br />
+
self.main = cardName
[[Aporia]] draws. He then [[activate]]s "[[Fortissimo the Mobile Fortress (anime)|Fortissimo the Mobile Fortress]]". Now once per turn, the turn player, during their Main Phase can Special Summon one Level 4 or lower Machine-Type monster from their hand. During each End Phase, if the turn player does not control a face-up Machine-Type monster, they take 100 damage. Aporia then activates the first effect of "Fortissimo the Mobile Fortress" to [[Special Summon]] "[[Meklord Army of Wisel (anime)|Meklord Army of Wisel]]" ([[File:CG Star.svg|16px]]4/1800/0) in Attack Position. He then Normal Summons "[[Meklord Army of Skiel (anime)|Meklord Army of Skiel]]" ([[File:CG Star.svg|16px]]4/1200/1000) in Attack Position and sets three cards.
+
table.insert(self.styles, 'Module:Hatnote/styles.css')
 +
return
 +
end
  
<u>'''Turn 2: Leo'''</u><br />
+
-- If "main" is not found at this point, assume there is none.
[[Leo]] draws "[[Morphtronic Staplen (anime)|Morphtronic Staplen]]" and subsequently Normal Summons it ([[File:CG Star.svg|16px]]4/1400/1000) in Defense Position. Aporia then activates his face-down "[[Level Cannon]]". Now when a player summons a monster, the controller of the summoned monster will take damage equal to the summoned monster's Level times 200. The effect of "Level Cannon" activates (Leo 4000 → 3200). Leo Sets three cards.
+
self.main = false
 +
end
  
<u>'''Turn 3: Luna'''</u><br />
+
-- Set images data based on the input parameter
[[Luna]] draws. She then Sets two cards. Luna then Normal Summons "[[Armored White Bear (anime)|Armored White Bear]]" ([[File:CG Star.svg|16px]]4/1800/1400) in [[Defense Position]]. The effect of "Level Cannon" activates, but Leo activates his face-down "[[Damage Eraser]]" to negate the damage and increase Luna's Life Points by the damage she would have taken, but [[Aporia]] activates his face-down "[[Damage Boost]]" to negate the effect of "Damage Eraser" and destroy it as well as double the damage Luna would take (Luna 4000 → 2400). On Luna's [[End Phase]], the second effect of "Fortissimo the Mobile Fortress" activates (Luna 2400 → 2300).
+
-- @param string input
 +
function Card:setImages(input)
 +
-- Clear existing value
 +
self.images = {}
  
<u>'''Turn 4: Jack Atlas'''</u><br />
+
-- Default to backing image if no input
[[Jack Atlas|Jack]] draws. He then activates "[[Trap Pause]]" to negate the effect of "Level Cannon" for one turn and draw a card. He then sends "[[Synchro Soldier (anime)|Synchro Soldier]]" from his hand to the Graveyard in order to [[Special Summon]] "[[Power Giant (anime)|Power Giant]]" ([[File:CG Star.svg|16px]]6/2200/0) in Attack Position. Since "Power Giant" was summoned this way, its Level decreases by the Level of "Synchro Soldier" ("Power Giant": [[File:CG Star.svg|16px]] 6 → 5). Jack then Normal Summons "[[Dark Resonator (anime)|Dark Resonator]]" ([[File:CG Star.svg|16px]]3/1300/300) in Attack Position. Jack then tunes "Power Giant" with "Dark Resonator" in order to [[Synchro Summon]] "[[Red Dragon Archfiend (anime)|Red Dragon Archfiend]]" ([[File:CG Star.svg|16px]]8/3000/2000) in Attack Position.  
+
input = input or self.config.defaultImage
  
Leo activates his face-down "[[Power-Up Adapter (anime)|Power-Up Adapter]]", equipping it to "Morphtronic Staplen" and increasing the ATK of "Red Dragon Archfiend" by the ATK of "Morphtronic Staplen" ("Red Dragon Archfiend": 3000 → 4400/2000), but Aporia activates his face-down "[[Power Gravity]]", equipping it to "Meklord Army of Wisel", increasing the ATK of "Meklord Army of Wisel" by the ATK "Red Dragon Archfiend" gained ("Meklord Army of Wisel": 1800 → 3200/0) and decreasing the ATK of "Red Dragon Archfiend" to 0 ("Red Dragon Archfiend": 4400 → 0/2000). Jack Sets a card. On Jack's End Phase, the second effect of "Fortissimo the Mobile Fortress" activates (Jack 4000 → 3900).
+
-- Split input by new line for multiple artworks
 +
local inputLines = mw.text.split(input, '\n')
  
<u>'''Turn 5: Aporia'''</u><br />
+
local previousArtwork = nil
Aporia draws "[[Aurora Draw (anime)|Aurora Draw]]" and subsequently activates it to draw two cards. He then Normal Summons "[[Meklord Army of Granel (anime)|Meklord Army of Granel]]" ([[File:CG Star.svg|16px]]4/1600/1200) in Attack Position. The effect of "Level Cannon" activates (Aporia 4000 → 3200). Aporia then activates the effect of "Meklord Army of Granel" to halve the DEF of "Armored White Bear" ("Armored White Bear": 1800/1400 → 700).
 
  
"Meklord Army of Wisel" attacks and destroys "Red Dragon Archfiend" (Jack 3900 → 700). Since "Red Dragon Archfiend" has been removed from the field, "Power-Up Adapter" is destroyed. "Meklord Army of Skiel" attacks and destroys "Armored White Bear". Aporia then activates the effect of "Meklord Army of Wisel", which allows "Meklord Army of Skiel" to inflict piercing damage to Luna (Luna 2300 → 1800). "Meklord Army of Granel" attacks Luna [[Direct attack|directly]] (Luna 1800 → 200). Aporia activates "[[Lock On Laser]]". Now whenever Aporia's opponents set a Spell or Trap Card, they will take 200 damage.
+
-- For each line (artwork)
 +
for _, line in pairs(inputLines) do
 +
-- Ensure each item on the line ends with `;` to help pattern matching
 +
line = line .. ';'
  
:''[[Yu-Gi-Oh! 5D's - Episode 142#Featured Duels|Continued next episode...]]''
+
-- Content before first `;` is the image
 +
local image    = mw.text.split(line, ';')[1]
  
==Featured cards==
+
-- Content after `artwork::` in the params string is the artwork number.
The following cards were used in this episode. Cards in italics debuted here.
+
-- Defaults to 1
 +
local artwork  = tonumber(line:match('; *artwork::([^;]-) *;') or 1)
  
{{Decklist|Aporia
+
-- Content after `thumb::` in the params string is the thumbnail
|effect monsters =
+
-- Defaults to the full image
* ''[[Meklord Army of Granel (anime)|Meklord Army of Granel]]''
+
local thumbnail = line:match('; *thumb::([^;]-) *;') or image
* ''[[Meklord Army of Skiel (anime)|Meklord Army of Skiel]]''
 
* ''[[Meklord Army of Wisel (anime)|Meklord Army of Wisel]]''
 
|spells =
 
* ''[[Aurora Draw (anime)|Aurora Draw]]''
 
* ''[[Fortissimo the Mobile Fortress (anime)|Fortissimo the Mobile Fortress]]''
 
* ''[[Lock On Laser]]''
 
|traps =
 
* ''[[Damage Boost]]''
 
* ''[[Level Cannon]]''
 
* ''[[Power Gravity]]''
 
}}
 
  
{{Decklist|Leo
+
-- Content after `name::` in the params string is the artwork name
|effect monsters =
+
local name      = line:match('; *name::([^;]-) *;')
* ''[[Morphtronic Staplen (anime)|Morphtronic Staplen]]''
 
|traps =
 
* ''[[Damage Eraser]]''
 
* ''[[Power-Up Adapter (anime)|Power-Up Adapter]]''
 
}}
 
  
{{Decklist|Luna
+
-- If the params string contains `current`, mark as the current image
|effect monsters =
+
local isCurrent = line:match('; *current *;') ~= nil
* ''[[Armored White Bear (anime)|Armored White Bear]]''
 
}}
 
  
{{Decklist|Jack Atlas
+
-- If the artwork has the same base number as its predecessor
|effect monsters =
+
-- its number is .1 higher than it
* [[Power Giant (anime)|Power Giant]]
+
if (previousArtwork ~= nil and math.floor(artwork) == math.floor(previousArtwork)) then
* [[Synchro Soldier (anime)|Synchro Soldier]]
+
artwork = previousArtwork + .1
|tuner monsters =
+
end
* [[Dark Resonator (anime)|Dark Resonator]]
 
|synchro monsters =
 
* [[Red Dragon Archfiend (anime)|Red Dragon Archfiend]]
 
|spells =
 
* ''[[Trap Pause]]''
 
}}
 
  
==Cast==
+
-- Add object to list of images
{| class=wikitable
+
table.insert(self.images, {
! Japanese character name<ref name=CastandStaff>[http://www.tv-tokyo.co.jp/program/detail/20078_201012281800.html tv-toyko.co.jp] ''Yu-Gi-Oh! 5D's'' "The Duel of Despair! Fortissimo the Moving Fortress!"</ref>
+
image    = image,
! Japanese voice actor<ref name=CastandStaff />
+
thumbnail = thumbnail,
|-
+
artwork  = artwork,
| [[Yusei Fudo]]
+
name      = name,
| [[Yuya Miyashita]]
+
isCurrent = isCurrent
|-
+
})
| [[Jack Atlas]]
 
| [[Takanori Hoshino]]
 
|-
 
| [[Leo|Lua]]
 
| [[Ai Horanai]]
 
|-
 
| [[Luna|Luca]]
 
| [[Yuka Terasaki]]
 
|-
 
| [[Bruno|Mysterious D-Wheeler]]
 
| [[Hiroki Tanaka]]
 
|-
 
| [[Aporia]]
 
| [[Masakazu Nemoto]]
 
|-
 
| [[Z-one]]
 
| [[Hideo Ishikawa]]
 
|-
 
| Other
 
| [[Masayuki Iwashita]], [[Koichi Yokota]], [[Masafumi Oku]]
 
|}
 
  
==References==
+
-- Prepare for the next iteration of the loop.
<references />
+
previousArtwork = artwork
 +
end
 +
end
  
{{Yu-Gi-Oh! 5D's episodes/season 2}}
+
-- 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('&nbsp;')
 +
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

-- 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('&nbsp;')
		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