Module:Card/traits/has sets

From Yugipedia
Jump to: navigation, search

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