Module:Card/traits/has sets
This module is currently under development. It might not work as described for non-OCG/TCG cards. |
Module:Card/traits/has sets can be added to modules that extend Module:Card so that card can be used to add set data.
Card pages using this should not need to manually specify which sets the card appears in. Instead the information will be automatically drawn from set and set list pages.
If sets are found for a card, an extra section(s) will be added to the output to list the sets.
Contents
Usage[edit]
local Card = require('Module:Card')
require('Module:Card/traits/has sets')
-- Configure what media and languages to search for sets in.
-- This also controls the order that they appear in.
Card.setsConfig.breakdown = {}
-- Add additional filters to the SMW query that searches for cards.
Card.setsConfig.filter = '';
return Card
Configuration options[edit]
To set a configuration option, use Card.setConfig.optionName = value
.
Card.setsConfig.option1 = 'value 1'
Card.setsConfig.option2 = 'value 2'
Card.setsconfig.option3 = 'value 3'
Available options, include:
- breakdown
- Default:
{}
- This should include an array for each game, structured to include:
- name
- Page name of the game, e.g.
'Yu-Gi-Oh! Trading Card Game'
or'Yu-Gi-Oh! GX Tag Force'
- shortName
- Shortened name of the game to be used in section headers, e.g.
'TCG'
or'Tag Force 1'
- languages
- Languages the game has sets released in, e.g.
{ en, fr, de, it, es, ja }
. Leave empty for video games where sets are not connected to the language.
-- Example Card.setsConfig.breakdown = { { shortName = 'TCG', name = 'Yu-Gi-Oh! Trading Card Game', languages = { 'en', 'fr', 'de', 'it', 'pt', 'es' } }, { shortName = 'OCG', name = 'Yu-Gi-Oh! Official Card Game', languages = { 'ja', 'en', 'sc', 'tc', 'ko' } } }
- filter
- Default:
{}
- Add extra conditions to the SMW search to find the sets.
- Ordinarily, this is not needed. Module:OCG-TCG card uses it because a number of sets for other games, link to OCG/TCG cards in their lists, in lieu of having dedicated card pages for the respective other games.
Changes to the Card object[edit]
The Card
object returned by Module:Card will behave differently in the following ways:
Additional data[edit]
- sets
- An array containing all set data, grouped by game, grouped by language and grouped by region
Overridden methods[edit]
- setData(args)
- Does the same as before, but also sets the
sets
property. - renderAdditionalSections()
- Adds multiple sections for sets, one for each game, tabbed by language.
Additional methods[edit]
- hasSetsInGame(game)
- Checks if the card appears in the game in question. e.g.
self:hasSetsInGame('Yu-Gi-Oh! Trading Card Game')
returnsfalse
for an OCG-only card.
local languages = {
en = { abbr = 'en', name = 'English' , localities = { 'en', 'na', 'eu', 'au', 'ae' } },
fr = { abbr = 'fr', name = 'French' , localities = { 'fr', 'fc' } },
de = { abbr = 'de', name = 'German' , localities = { 'de'} },
it = { abbr = 'it', name = 'Italian' , localities = { 'it'} },
pt = { abbr = 'pt', name = 'Portuguese' , localities = { 'pt'} },
es = { abbr = 'es', name = 'Spanish' , localities = { 'es'} },
ja = { abbr = 'ja', name = 'Japanese' , localities = { 'jp', 'ja' } },
zh = { abbr = 'sc', name = 'Chinese' , localities = { 'zh'} },
sc = { abbr = 'sc', name = 'Simplified Chinese' , localities = { 'sc'} },
tc = { abbr = 'tc', name = 'Traditional Chinese', localities = { 'tc'} },
ko = { abbr = 'ko', name = 'Korean' , localities = { 'ko'} },
-- For games with language-agnostic sets
['-'] = { abbr = '', name = '-', localities = { '-' } }
}
-- Define objects for each locality
local localities = {
en = { abbr = 'en', name = 'Worldwide English', language = 'English' },
na = { abbr = 'na', name = 'North American English', language = 'English' },
eu = { abbr = 'eu', name = 'European English', language = 'English' },
au = { abbr = 'au', name = 'Australian English', language = 'English' },
fr = { abbr = 'fr', name = 'French', language = 'French' },
fc = { abbr = 'fc', name = 'French-Canadian', language = 'French' },
de = { abbr = 'de', name = 'German', language = 'German' },
it = { abbr = 'it', name = 'Italian', language = 'Italian' },
pt = { abbr = 'pt', name = 'Portuguese', language = 'Portuguese' },
es = { abbr = 'es', name = 'Spanish', language = 'Spanish' },
jp = { abbr = 'jp', name = 'Japanese', language = 'Japanese' },
ja = { abbr = 'ja', name = 'Japanese-Asian', language = 'Japanese' },
ae = { abbr = 'ae', name = 'Asian-English', language = 'English' },
sc = { abbr = 'sc', name = 'Simplified Chinese', language = 'Simplified Chinese' },
tc = { abbr = 'tc', name = 'Traditional Chinese', language = 'Traditional Chinese' },
ko = { abbr = 'ko', name = 'Korean', language = 'Korean' },
-- For games with language-agnostic sets
['-'] = { abbr = '', name = '-', language = '-' }
}
-- ------------------------------------
-- Internal functions
-- Functions only used within this class
-- ------------------------------------
-- Ensure the same format is used whether SMW found zero, one or multiple rarities
-- @param rarities table|string - The rarity/rarities found for one set
-- @return table
local function normalizeRarities(rarities)
-- Rarities is empty, return an empty array
if (rarities == nil or rarities == '') then return {} end
-- A single rarity was found, put it in an array
if (type(rarities) ~= 'table') then return { rarities } end
-- Multiple rarities were found, return as-is
return rarities
end
-- Find all sets that the card appears in.
-- @param card Card
-- @return table Array of elements containing information on each print
-- Example of an array element
--[[
["cardNumber"] = "PP02-FR007",
["rarities"] = { "Super Rare", "Secret Rare" },
["printedName"] = "Chaleur, Héros Élémentaire",
["release"] = "Yu-Gi-Oh! Trading Card Game",
["language"] = "French",
["locality"] = "French",
["setPage"] = "Premium Pack 2 (TCG)",
["setName"] = "Premium Pack 2",
["localSetName"] = "Paquet Premium 2",
["releaseDate"] = "2008-07-29",
]]--
local function lookupSets(card)
local sets = mw.smw.ask{
'[[Set contains::' .. card.pageName .. ']]' .. card.setsConfig.filter,
'?Card number = cardNumber',
'?Uses artwork = artwork',
'?Rarity = rarities',
'?Printed name = printedName',
'?Release# = cardRelease',
'?-Has subobject.Release# = setRelease',
'?-Has subobject.Language# = language',
'?-Has subobject.Locality# = locality',
'?-Has subobject.Set page# = setPage',
'?-Has subobject.Set page.English name = setName',
'?-Has subobject.Local name = localSetName',
'?-Has subobject.Release date# = releaseDate',
mainlabel = '-',
limit = 500
} or {}
-- Format the results
for _, set in pairs(sets) do
-- Merge `cardRelease` and `setRelease` into one
-- giving priority to the card's release.
set.release = set.cardRelease or set.setRelease
set.cardRelease = nil
set.setRelease = nil
-- Ensure the same format is used each result's rarities.
set.rarities = normalizeRarities(set.rarities)
end
return sets
end
-- Function to be used within the `table.sort()` function for comparing two values
-- @param set1 table
-- @param set2 table
-- @return bool - `true`: put set 1 before set2, `false`: put set 1 after set 2
local function orderSets(set1, set2)
-- If release dates are different, put the item with the newer release date first
if (set1.releaseDate ~= set2.releaseDate) then
-- If the date is null, treat it as 'a' (which gets sorted after all dates)
return (set1.releaseDate or 'a') < (set2.releaseDate or 'a')
end
-- If the release dates are the same, sort by set name alphabetically
if (set1.setName ~= set2.setName) then
return (set1.setName or '') < (set2.setName or '')
end
-- If the set names are the same, sort by card number, alphabetically
if (set1.cardNumber ~= set2.cardNumber) then
return (set1.setName or '') < (set2.setName or '')
end
-- If the card numbers are the same, sort by artwork
return (set1.artwork or '0') < (set2.artwork or '0')
end
-- Find sets, grouped by game, then by language, then by locality
-- For each result, put rarities in an array, regardless of the amount found
-- @param card Card
-- @return table e.g.
--[[
['Yu-Gi-Oh! Official Card Game'] = {
['Japanese'] = {
['Japanese'] = { ... },
['Japanese-Asian'] = { ... },
},
['Korean'] = {
['Korean'] = { ... }
},
}
['Yu-Gi-Oh! Trading Card Game'] = {
['English'] = {
['Worldwide English'] = { ... },
['North American English'] = { ... },
['European English'] = { ... }
},
['German'] = {
['German'] = { ... }
}
}
]]--
local function groupResults(sets)
local groupedSets = {}
local release
local language
local locality
for _, set in pairs(sets) do
release = set.release or ''
language = set.language or '-'
-- If there is no locality, default it to the same as the language
locality = set.locality or language
groupedSets[release] = groupedSets[release] or {}
groupedSets[release][language] = groupedSets[release][language] or {}
groupedSets[release][language][locality] = groupedSets[release][language][locality] or {}
table.insert(groupedSets[release][language][locality], set)
end
return groupedSets
end
-- Render a single table of sets
-- @param card Card
-- @param language table
-- @param sets table
-- @return string
local function renderSetsTable(card, language, sets)
local uses = card.setsConfig.uses
local usesArtworks = uses.artworks and card:setsHaveMultipleArtworks()
-- Make sure the list is in the correct order.
-- (Ideally this should have been done sooner, but the `groupResults`
-- function interferes with the ordering.)
table.sort(sets, orderSets)
-- Create the table
local setsTable = mw.html.create('table')
setsTable:attr('class', 'wikitable sortable cts card-list')
-- Set up the heading row.
local theadRow = setsTable:tag('tr')
if (uses.releaseDates) then
theadRow:tag('th'):attr('class', 'cts__header--release'):wikitext('Release')
end
if (uses.cardNumbers) then
theadRow:tag('th'):attr('class', 'cts__header--number'):wikitext('Number')
end
theadRow:tag('th'):attr('class', 'cts__header--set'):wikitext('Set')
if (language.abbr ~= 'en' and language.name ~= '-') then
-- English-language sets don't use this column.
theadRow:tag('th'):attr('class', 'cts__header--set-localized'):wikitext('Local name')
end
if (usesArtworks) then
theadRow:tag('th'):attr('class', 'cts__header--rarity'):wikitext('Artwork')
end
if (uses.rarities) then
theadRow:tag('th'):attr('class', 'cts__header--rarity'):wikitext('Rarity')
end
-- Set up the rows for each set
for _, set in pairs(sets) do
local cardNumber = set.cardNumber and ('[[' .. set.cardNumber .. ']]') or ''
local setLink = set.setPage and '[[' .. set.setPage .. '|<i>' .. (set.setName or set.setPage) .. '</i>]]' or ''
local rarities = table.concat(set.rarities, '<br />')
local row = setsTable:tag('tr')
if (uses.releaseDates) then
row:tag('td'):wikitext(set.releaseDate or '')
end
if (uses.cardNumbers) then
row:tag('td'):wikitext(cardNumber)
end
row:tag('td'):wikitext(setLink)
if (language.abbr ~= 'en' and language.name ~= '-') then
row:tag('td'):wikitext(set.localSetName or '')
end
if (usesArtworks) then
row:tag('td'):wikitext(set.artwork)
end
if (uses.rarities) then
row:tag('td'):wikitext(rarities)
end
end
return tostring(setsTable)
end
-- Render a tab in the sets section for a given language
-- @param card Card
-- @param language table
-- @param sets table
-- @return string
local function renderSetTab(card, language, sets)
local output = '<div>'
-- The label for the tab will be the name of the language
if (language.name ~= '-') then
output = output .. '<div class="switcher-label>' .. language.name .. '</div>'
end
-- The tab will have a subsection for each locality (Worldwide, North American, etc.)
for _, localityCode in pairs(language.localities) do
local locality = localities[localityCode]
if (sets[locality.name]) then
-- Show a heading for the locality
if (locality.name ~= '-') then
output = output .. '<p><b>' .. locality.name .. '</b></p>'
end
-- Show the table of sets for the locality
output = output .. renderSetsTable(card, language, sets[locality.name])
end
end
output = output .. '</div>'
return output
end
-- Render the entire contents of the "sets" section
-- @param card Card
-- @return string
local function renderSetsSection(card)
local output = ''
local games = card.setsConfig.breakdown;
for _, game in pairs(games) do
-- Only include the section if this game has sets
if (card.groupedSets[game.name]) then
-- If there are multiple games, include the game name in the heading
-- Otherwise, just use "Sets"
local heading = #games > 1
and '<i>' .. game.shortName .. '</i> sets'
or 'Sets'
local content = mw.html.create('div')
-- If the game is multilingual, the content will have a switcher
-- adding tabs per language
if (game.languages and #game.languages > 0) then
content:addClass('switcher-container switcher-container-sets')
end
-- The content area will have a tab for each language in the game
local languageCodes = game.languages or { '-' }
for _, languageCode in pairs(languageCodes) do
local language = languages[languageCode]
local sets = card.groupedSets[game.name][language.name]
-- Only add the tab to the switcher if the language has sets
if sets then
content:wikitext(renderSetTab(card, language, sets))
end
end
output = output .. card.renderSection(heading, tostring(content))
end
end
return output
end
-- ------------------------------------
-- Modify the card module
-- ------------------------------------
if not Card then
Card = require('Module:Card')
end
-- Take a copy of the original `Card` object before this makes changes to it.
local Parent = mw.clone(Card)
-- Add additional config to the `Card` object.
Card.setsConfig = {
breakdown = {},
filter = '',
uses = {
releaseDates = true,
cardNumbers = true,
rarities = true,
artworks = false
}
}
table.insert(Card.styles, 'Module:Card/traits/has sets/styles.css')
-- ------------------------------------
-- Override methods
-- These methods exist in the original `Card` module
-- And are having their behavior modified here.
-- ------------------------------------
-- Override the `setData` method to include sets
function Card:setData(args)
-- Call the original `setData` function
Parent.setData(self, args)
-- Look up the sets
self.sets = lookupSets(self)
table.sort(self.sets, orderSets)
-- Group the sets by game, language and region
self.groupedSets = groupResults(self.sets)
end
-- Override the `renderAdditionalSections` method to include a sets section
function Card:renderAdditionalSections()
-- Call the parent function
local sections = Parent.renderAdditionalSections(self)
-- Add sets to the additional sections
sections = sections .. renderSetsSection(self)
-- Return the combined section
return sections
end
-- ------------------------------------
-- New methods
-- Modify the card module to have these extra methods
-- ------------------------------------
-- Get the earliest release date
-- Usage note: This could be inaccurate if there are set lists missing
-- or set lists missing a release date
--
-- @return string ISO date
function Card:getFirstSetDate()
-- If there are no sets, exit early
if (#self.sets == 0) then return nil end;
-- Get the release date of the first set.
-- Sets should be ordered by release date at this point.
return self.sets[1].releaseDate
end
-- Get the earliest release date in a specified game
-- Usage note: This could be inaccurate if there are set lists missing
-- or set lists missing a release date
--
-- @return string ISO date
function Card:getFirstSetDateInGame(gameName)
-- If there are no sets in the game exit early
if (not self.groupedSets[gameName]) then return nil end
-- Loop through each set (ordered by date), stop on the first set found for the game
for i = 1, #self.sets do
if (self.sets[i].release == gameName) then
return self.sets[i].releaseDate
end
end
return nil
end
-- Check if the card has sets in a particular game
-- @param gameName string
-- @return boolean
function Card:hasSetsInGame(gameName)
return self.groupedSets[gameName] and true or false
end
-- Check if the card has multiple artworks across its sets
-- @return bool
function Card:setsHaveMultipleArtworks()
-- If there are fewer than two prints, there can't be multiple artworks
if (#self.sets < 2 ) then return false end
-- Get the artwork of the first print
local firstArtwork = self.sets[1].artwork
-- Loop through other prints until a different artwork is found
for i = 2, #self.sets do
if (self.sets[i].artwork ~= firstArtwork) then
return true
end
end
return false
end
return Card