League of Legends Wiki

Want to contribute to this wiki?
Sign up for an account, and get started!
You can even turn off ads in your preferences.

Come join the LoL Wiki community Discord server!

READ MORE

League of Legends Wiki
Advertisement

Description

This Module handles everything around Legends of Runeterra Collectibles and Cosmetics. Currently we're testing skins, but we want to include other forms of cosmetics as well, such as card backs, emotes, boards and icons.

Submodules:

Public Functions

-- Creates a tabber with all skins of a single card, to be used on a subpage or under a heading on that page.
{{#invoke: LoRCosmetics | skinpage
| cardcode    OR 1 = String (The cardcode of the card for which to generate skins)
}}

-- Generates a tooltip for a skin. Called by {{t|Tooltip/Skin/LoR}}, should not be used manually.
Note that the argument names are determined by the Tooltip template! This applies to any card, not just champion cards!
{{#invoke: LoRCosmetics | skintooltip
| champion          = String (The name of the card)
| skin              = String (The name of the skin, defaults to "Original")
| variant           = String (The cardcode of the card, defaults to the ''first'' cardcode of the card, i.e. Level 1)
}}

-- Generates categories to appear on the file pages. Called by LoRData.fileCategories
{{#invoke: LoRCosmetics | fileCategories 
| cardcode          = String (The cardcode of the card)
| skin              = String (The name of the skin)
| modifiers         = Table 
|   HD                = Boolean (Is this an HD file?)
|   alternate         = Boolean (Is this a censored file?)
|   display           = Boolean (Is this a League Displays File?)
|   artwork           = Boolean (Is this a full artwork? If not, it's just a card.)
}}


-- Generates links to all skins that an artist worked on.
Either cardcode or cardname and either skin or skinindex must be given to identify the skin!
{{#invoke: LoRCosmetics | splashartistPage 
| artist or 1       = String (The name of the artist or studio to search for, defaults to the page title)
}}

-- Prints a display of the skin. To be used on patchnotes, but currently no template uses this.
{{#invoke: LoRCosmetics | displaySkin 
| cardcode          = String (The cardcode of the card)
| cardname or 1     = String (The name of the card)
| skin     or 2     = String (The name of the skin)
| skinindex         = Number (The number of the skin to show)
| limit             = Number (How many skins to display, defaults to unlimited)
}}

-- Links to a skin and creates a tooltip when hovered. Used by {{t|LoR Card Skin Link}}.
Either cardcode or cardname and either skin or skinindex must be given to identify the skin!
{{#invoke: LoRCosmetics | linkSkin 
| cardcode          = String (The cardcode of the card)
| cardname or 1     = String (The name of the card)
| skin     or 2     = String (The name of the skin)
| skinindex         = Number (The number of the skin to show)
| text              = String (The text of the link, defaults to "cardname skin")
| card              = Number or "all" (If "all", lists all cards with a tooltip. 
                                     If a number is given, only links to that card of the skin, i.e. Level 1, Level 2 or other associated cards like Living Shadow.)
}}

-- Returns a String with the Voiceactor of a skin.
Either cardcode or cardname must be given to identify the skin!
{{#invoke: LoRCosmetics | getVoiceactor 
| cardcode          = String (The cardcode of the card)
| cardname or 1     = String (The name of the card)
| skin     or 2     = String (The name of the skin, defaults to "Original")
}}

-- Returns a String with the Splashartist of a skin.
Either cardcode or cardname must be given to identify the skin!
{{#invoke: LoRCosmetics | getSplashartist 
| cardcode          = String (The cardcode of the card)
| cardname or 1     = String (The name of the card)
| skin     or 2     = String (The name of the skin, defaults to "Original")
}}

-- Returns a String with the Sets / Themes of a skin.
Either cardcode or cardname must be given to identify the skin!
{{#invoke: LoRCosmetics | getSet 
| cardcode          = String (The cardcode of the card)
| cardname or 1     = String (The name of the card)
| skin     or 2     = String (The name of the skin, defaults to "Original")
}}

-- Returns a String with the lore / flavortext of a skin. Either returns the text for a single card, or concatenates all cards with <br />
Either cardcode or cardname must be given to identify the skin!
{{#invoke: LoRCosmetics | getLore 
| cardcode          = String (The cardcode of the card)
| cardname or 1     = String (The name of the card)
| skin     or 2     = String (The name of the skin, defaults to "Original")
| card              = Number or "all" (If "all", lists the flavor text of all cards. 
                                     If a number is given, only lists the flavor text of that card of the skin, i.e. Level 1, Level 2 or other associated cards like Living Shadow.)
}}

-- <pre>
local p = {}

local lorSkindata  = mw.loadData('Module:LoRCosmetics/skins')
local lorData	= require('Module:LoRData')
local get	    = require('Module:LoRData/getter')
local color     = require('Module:Color')
local lib       = require('Module:Feature')
local userError = require('Dev:User error')

--------------------------
-- GENERATING FUNCTIONS --
--------------------------

-- generates the skins cosmetics section for a card.
function p.skinPage(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    	
    args['cardcode'] = args['cardcode'] or args[1]
    local cardname = get.name(args['cardcode'])
    
	local t = lorSkindata[args['cardcode']]
    if t == nil then
        return userError("Card ''" .. args['cardcode'] .. "'' does not exist in Module:LoRCosmetics/skins", "LuaError")
    end
	
    local temp_t = {}
    for skinname, skin in pairs(t) do
    	table.insert(temp_t, {skinname, skin})	
    end
    table.sort(temp_t, function (a,b) return idxcomp(a[2], b[2]) end)
    
    -- generates all categories of all sets of all skins of said champion
    local k = "[[" .. "Category:LoR Card cosmetics]] [[Category:"..cardname.."]]"
    for skinname, skin in pairs(t) do
    	local theme = p.getSet{cardcode = args['cardcode'], skin = skinname}
    	if theme ~= nil then
        	k = k .. require('Module:SkinThemes').handleThemes{theme = theme, date = skin.release}
        end
    end 

    --Generated string begins here    
    local s = '<div style="clear:both"></div>\n== Skins ==\n<div style="font-size:small"><tabber>'
    for i, value in ipairs(temp_t) do
    	local skinname = value[1]
    	local skin = value[2]
		if (skin.idx > 0) then
			s = s .. "|-|\n"
		end
		s = s .. skinname .. "=\n"
        s = s .. skinDisplay(args['cardcode'], skinname, skin, nil)
    end
    
    s = s .. '</tabber></div><div style="clear:both"></div>' .. k -- append categories and stuff
    return frame:preprocess(s)
end

-- Generates mouseover overview for skins
function p.skinTooltip(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    -- Leftover from the templates that generate tooltips, we only have access to variables "champion", "skin" and "variant"
    local cardname = args['champion'] or "Zed" 
    local skin = args['skin'] or "Original" -- Original Zed as default
    local t, cardcode
    t, skin, cardcode = findSkin{cardname, skin} --  Also returns skinname (known) and cardcode.
    local associatedCard = args['variant'] or cardcode
    
	local IL        = require('Module:ImageLink')
	local FN        = require('Module:Filename')
    
    local c = nil -- array of things associated with a specific card of the skin
    for _, v in pairs(t.associatedCards) do
    	if v.cardcode == associatedCard then
    		c = v
    		break
    	end
    end
	
	if c == nil then
		return userError("Can not find associated card ''".. associatedCard .. "'' for skin ''" .. formatname .. "''", "LuaError")
	end
    local cost         = t['cost']
    local rarity	   = t['rarity'] or "Base"
    local voiceactor   = p.getVoiceactor{cardcode = cardcode, skin=skin}
    local splashartist = p.getSplashartist{cardcode = cardcode, skin=skin}
    local set          = p.getSet{cardcode = cardcode, skin=skin}
    local changedcardname = c['name'] or get.name(associatedCard)
    local formatname	= t['formatname'] or skin .. ' ' .. cardname
    local lore         = c['flavor']
    local filename	   = FN.lorskin{cardcode=c.cardcode, skin=skin}
    local filter       = t['voice'] == "filter" -- voice can be original, original but filtered, new quotes and completely new VO
    local newquotes    = t['voice'] == "added"
    local newvoice     = t['voice'] == "new"
    local neweffects   = t['newEffects'] -- currently unused but possible in the future
    local newlevelanimation = t['newLevelAnimation'] -- Level up only, other things count as EFFECTS.
    local extras       = t['extras'] -- unknown if that will be used
    
    local s            = ''
    
    s = s .. '<div style="position:relative;">[[File:' .. filename .. '|700px]]'
        s = s .. '<div class="skin-features" style="padding:15px 45px 15px 15px; position:absolute; bottom:16px; left:16px; background-color:RGBA(10, 24, 39, 0.75); max-width:576px;">'
            s = s .. '<div style="font-family:BeaufortLoL;">'
                s = s .. '<span style="font-size:16px; color:#f1e6d0; width:100%; text-transform:uppercase;">'
                    s = s .. formatname .. '&nbsp;<small>(' .. changedcardname .. ')</small>'
                s = s .. '</span>&nbsp;<span style="font-size:14px; color:#c9aa71; width:100%;">'
                
                    if cost == 0 and rarity == "Base" then
                        s = s .. '&#x2011;&nbsp;' .. tostring(IL.basic{["link"] = "Wildcard (Legends of Runeterra)", ["text"] = 1, ["alttext"] = "1 Wildcards", ["image"] = "LoR " .. get.rarity(cardcode) .. " Wildcard icon.png", ["border"] = "false", ["labellink"] = "false"})
                    elseif cost == "special" then
                        -- having another option just in case
                    else -- default
                        s = s .. '&#x2011;&nbsp;' .. tostring(IL.basic{["link"] = "Coin_(Legends_of_Runeterra)", ["text"] = cost, ["alttext"] = cost .. " Coins", ["image"] = "Coin icon.png", ["border"] = "false", ["labellink"] = "false"}) 
                    end
                    
                s = s .. lib.ternary(distribution ~= nil, ' ' .. tostring(distribution), '') .. '</span>'
            s = s .. '</div>'
            s = s .. '<div style="font-size:11px">'
                s = s .. lib.ternary(voiceactor ~= nil, '<div style="display:inline-block; padding-right:1em;">[[File:Actor.png|20px|link=]]' .. tostring(voiceactor) .. '</div>', '')
                s = s .. lib.ternary(splashartist ~= nil, '<div style="display:inline-block; padding-right:1em;">[[File:Artist.png|20px|link=]]' .. tostring(splashartist) .. '</div>', '')
                s = s .. lib.ternary(set ~= nil, '<div style="display:inline-block; padding-right:1em;">[[File:Set piece.png|20px|link=]]' .. tostring(set) .. '</div>', '')
                s = s .. lib.ternary(lore ~= nil, '<div style="line-height: 1.7em; text-align: justify">' .. tostring(lore) .. '</div>', '')
            
            if availability or filter or newquotes or newvoice or neweffects or newlevelanimation or extras then -- cut out a few options here that don't apply to LOR skins but to League
                s = s .. '<div>'
                
                if availability == 'Limited' then
                    s = s .. '<div style="display:inline-grid; padding:0 1em; text-align:center;"><div>[[File:Limited skin.png|50px|link=]]</div><div>Limited Edition</div></div>'
                elseif availability == 'Legacy' then
                    s = s .. '<div style="display:inline-grid; padding:0 1em; text-align:center;"><div>[[File:Legacy skin.png|50px|link=]]</div><div>Legacy Vault</div></div>'
                end
                        
                s = s .. lib.ternary(filter, '<div style="display:inline-grid; padding:0 1em; text-align:center;"><div>[[File:Voice filter.png|50px|link=]]</div><div>Voice Filter</div></div>', '')
                s = s .. lib.ternary(newquotes, '<div style="display:inline-grid; padding:0 1em; text-align:center;"><div>[[File:Additional quotes.png|50px|link=]]</div><div>Additional Quotes</div></div>', '')
                s = s .. lib.ternary(newvoice, '<div style="display:inline-grid; padding:0 1em; text-align:center;"><div>[[File:New voice.png|50px|link=]]</div><div>New Voice</div></div>', '')
                s = s .. lib.ternary(neweffects, '<div style="display:inline-grid; padding:0 1em; text-align:center;"><div>[[File:New effects.png|50px|link=]]</div><div>New SFX/VFX</div></div>', '')
                s = s .. lib.ternary(newlevelanimation, '<div style="display:inline-grid; padding:0 1em; text-align:center;"><div>[[File:New animations.png|50px|link=]]</div><div>New Level-up animation</div></div>', '')
                s = s .. lib.ternary(extras ~= nil, '<div style="display:inline-grid; padding:0 1em; text-align:center;"><div>[[File:Includes extras.png|50px|link=]]</div><div>Includes Extras</div></div>', '')
                s = s .. '</div>'
            end
            s = s .. '</div>'
        s = s .. '</div>'
    s = s .. '</div>'
    
    return s
end

-- Creates all necessary categories for a skin image file.
function p.fileCategories(frame) 
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
	
	local t, skinname, basecard = findSkinForCardcode(args['cardcode'], inflate(args['skin']))
	local basecardname = get.name(basecard)
	local name = get.name(args['cardcode'])

    local s = "[[" .. "Category:LoR Card cosmetics]] [[Category:" .. name .. " Card Skins]] [[Category:" .. basecardname .. " Card Skins]]"
    local introduction = ""
    
    for i,v in ipairs(t.associatedCards) do
		if v.cardcode == args['cardcode'] then name = v.name or name end -- can only replace it with the name from the skindata HERE beause above needs the real name
	end
    
    local cardset = lorData.getSetFromCardcode{args['cardcode']}
    if cardset ~= 0 and cardset ~= 99 then
        s = s .. "[[" .. "Category:LoR Set " .. cardset .. " Skin]]"
    end
    
    s = s .. require('Module:SkinThemes').handleThemes{theme = p.getSet{cardcode = basecard, skin = skinname}, date = t.release}
    
	local cardContents = {}
	
	-- This is the ...
	if args['modifiers'].HD == true then
    	table.insert(cardContents, 'HD')
    end
    if args['modifiers'].alternate == true then
    	table.insert(cardContents, 'alternate')
    end
    if args['modifiers'].display == true then
    	table.insert(cardContents, 'League Displays')
    end
    if args['modifiers'].artwork == true then
    	table.insert(cardContents, 'artwork')
    else
    	table.insert(cardContents, 'card image')
    end
	-- ... for "Cardname" with the "skinname" Skin.
	local cardContent = table.concat(cardContents, " ")
    
	local linktext = t.displayname or (skinname .. " " .. basecardname)
    local skintext = "[[" .. basecardname .. " (Legends of Runeterra)#Skins|" .. linktext .. "]]"
	
    introduction = mw.html.create('table')
    	:attr('align', 'center' )
    	:css('margin-top', '1em')
    	:css('width', '90%')
    	:css('border', '2px solid #FFFFFF')
    	:css('font-size', '95%')
    	:css('text-align', 'justify')
    	:css('border-collapse', 'collapse')
    	:tag('tr')
    		:tag('td')
	    		:attr('colspan', '2')
	    		:css('background-color', '#061E36')
	    		:css('text-align', 'center')
	    		:css('font-family', 'BeaufortLoL')
	    		:css('font-size', '120%')
	    		:css('font-weight', '700')
	    		:css('padding', '0.2em 0.5em')
	    		:css('border-bottom', '2px solid #C9AA71;')
	    		:wikitext('This is the ' .. cardContent .. ' for [[' .. args['cardcode'] .. ' (Legends of Runeterra)|' .. name .. ']] of the ' .. skintext .. ' skin. ')
	    		:wikitext('<br />')
	    		:wikitext('You can also view all other [[:Category:' .. basecardname .. ' Card Skins|' .. basecardname .. ' Skins]].')
	    		:done()
	    	:done()
    	:done()
    
    return tostring(introduction) .. s
end

-- Generates a list of skins an artist worked on.
function p.splashartistPage(frame)
	local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
		
    local s = ""
    
    args['artist'] = args['artist'] or args[1] or mw.title.getCurrentTitle().text
    
    local resulttable = {}
    for keys, skin,_ in skinIter(lorSkindata) do
    	if skin.splashartist ~= nil then
            for _, splashartistname in pairs(skin.splashartist) do
                if splashartistname == args['artist'] then
                    table.insert(resulttable, {keys[1], (skin.formatname or (keys[2] .. " " .. get.name(keys[1]))), keys[2], skin})
                end
            end
        end
    end
    
    table.sort(resulttable, function(a, b) return a[2] < b[2] end)
    
    if #resulttable == 0 then
        return userError("No results for " .. searchstring, "LuaError")
    end
    s = ";{{tip|Legends of Runeterra}} skin artworks\n{{Column|2|\n"
    for _, skindata in ipairs(resulttable) do
        s = s .. "* {{LoR|code=" .. skindata[1] .. "|text=" .. skindata[2] .. "}}: "
        
    	local skin = skindata[4]
        for i, card in ipairs(skin.associatedCards) do
        	local name = card.name or get.name(card.cardcode)
        	if (i == 1) then -- first card
        		s = s .. "{{LoR|code=" .. card.cardcode .. "|text=" .. name .. "|skin=" .. skindata[3] .."|nolink=true|artwork=true|indicator=false}}" 
        	else
        		s = s .. ", {{LoR|code=" .. card.cardcode .. "|text=" .. name .. "|skin=" .. skindata[3] .."|nolink=true|artwork=true|indicator=false}}" 
        	end
        end
    
        local count = 0 -- if other people worked on that (not relevant rn)
        for i, val in pairs (skin.splashartist) do
            if val ~= args['artist'] then
                if count == 0 then
                    s = s .. " <small>(Collaboration with "
                else
                    s = s .. ", "
                end
                s = s .. skin.splashartist[i]
                count = count + 1
            end
        end
        if count ~= 0 then
            s = s .. ")</small>"
        end
        s = s .. "\n"
    end
    s = s .. "}}"
    
    return frame:preprocess(s)
end

-- Generates a list of skins in a table.
function p.skinlist(frame)
    local lang = mw.language.new( "en" )
    local sdtable = mw.html.create('table')
    
    sdtable
        :addClass('sortable article-table nopadding sticky-header')
        :css('width','100%')
        :css('text-align','center')
        :newline()
        
        -- TABLE HEADER
        :tag('tr')
        	:tag('th')
        		:tag('span')
                	:attr('title','Mana Cost')
                	:wikitext('[[File:LoR mana icon.png|40px|link=|]]')
            	:done()
            :done()
            :tag('th')
                :wikitext('Skin')
            :done()
            :tag('th')
                :tag('span')
                    :attr('title','Availability')
                    :wikitext('[[File:Availability.png|40px|link=|]]')
                :done()
            :done()
            :tag('th')
                :tag('span')
                    :attr('title','Release')
                    :wikitext('[[File:Release.png|40px|link=|]]')
                :done()
            :done()
            :tag('th')
                :tag('span')
                    :attr('title','Cost')
                    :wikitext('[[File:Coin icon.png|40px|link=|]]')
                :done()
            :done()
            :tag('th')
                :css('width','40px')
                :tag('span')
                    :attr('title','New lv up animations')
                    :wikitext('[[File:New animations.png|40px|link=|]]')
                :done()
            :done()
            :tag('th')
                :css('width','40px')
                :tag('span')
                    :attr('title','Part of a Collection')
                    :wikitext('[[File:Set piece.png|40px|link=|]]')
                :done()
            :done()
        :done()
        :newline()

        -- TABLE ENTRIES
        local cardtable = {}
        for x in pairs(lorSkindata) do
            table.insert(cardtable, x)
        end
        table.sort(cardtable)

        local availablenode = mw.html.create('span')
        availablenode
            :css('color', 'green')
            :css('font-size', 'x-large')
            :css('vertical-align', 'text-top')
            :wikitext("✔")
            
        local legacynode = mw.html.create('span')
        legacynode
            :css('color', 'yellow')
            :css('font-size', 'x-large')
            :css('font-weight', '600')
            :css('vertical-align', 'text-top')
            :wikitext("‒")
            
        local limitednode = mw.html.create('span')
        limitednode
            :css('color', 'red')
            :css('font-size', 'x-large')
            :css('vertical-align', 'text-top')
            :wikitext("✘")
        local rarenode = mw.html.create('span')
        rarenode
            :css('color', 'orange')
            :css('font-size', 'x-large')
            :css('vertical-align', 'text-top')
            :wikitext("⭐")


        for _, cardcode in pairs(cardtable) do
            local skintable  = {}
            for cardcode in pairs(lorSkindata[cardcode]) do
                table.insert(skintable, cardcode)
            end
            table.sort(skintable)
    
            for _, skinname in pairs(skintable) do
                local t = lorSkindata[cardcode][skinname]
                local sdnode = mw.html.create('tr')
                local cardname = get.name(cardcode)
                local cardcost = get.cost(cardcode)
                
                -- Skin mana cost
                local link = nil
                local indicatorImage = 'File:LoR Champion Indicator.png'
                local indicatorWidth = 30
                local indicatorLeft = 1.5
                local indicatorLink = formatnil('[[%s|%s|link=%s]]', indicatorImage, indicatorWidth .. 'px', cardname .. ' (Legends of Runeterra)')
                
                link = '<span style="position:relative;text-indent:0;">' .. indicatorLink
                link = link .. '<span style="position:absolute;left:' .. indicatorLeft .. 'px;top:50%;user-select:none;line-height:1.5;color:white;font-weight:normal;text-align:center;width:20px;height:20px;font-size:14px;-ms-transform:translateY(-50%);transform:translateY(-50%);">' .. cardcost .. '</span>'
                link = link .. '</span>'
                sdnode
                    :tag('td')
                        :attr('data-sort-value', cardcost .. cardname)
                        :wikitext(link)
                    :done()
                
                -- Skinname
                local s = ""
                if (skinname == "Original") then
                    s = "!" .. t["release"]
                else 
                    s = t["release"]
                end
                sdnode
                    :tag('td')
                        :addClass('skin-icon')
                        :attr('data-sort-value', cardname .. s)
                        :attr('data-champion', cardname)
                        :attr('data-skin', skinname)
                        :attr('data-variant', cardcode)
                        :attr('data-game', "lor")
                        :css('text-align', 'left')
                        :wikitext(lib.ternary(t["displayname"] ~= nil, t["displayname"], skinname .. " " .. cardname))
                    :done()
                
                -- Availability
                local astring = '<span style="color: cornflowerblue;font-size: large;font-weight: 600;">⭘</span>'
                if (t["availability"] == "Available") then
                    astring = tostring(availablenode)
                end
                if (t["availability"] == "Legacy") then
                    astring = tostring(legacynode)
                end
                if (t["availability"] == "Limited") then
                    astring = tostring(limitednode)
                end
                if (t["availability"] == "Rare") then
                    astring = tostring(rarenode)
                end
                sdnode
                    :tag('td')
                        :tag('span')
                            :attr('title', t["availability"] or 'Upcoming')
                            :wikitext(astring)
                    :done()
                
                -- Release
                local y, m, d = t["release"]:match("(%d+)-(%d+)-(%d+)")
                if y == nil or m == nil or d == nil then
                    sdnode
                        :tag('td')
                            :attr('data-sort-value', t["release"])
                            :wikitext(t["release"])
                        :done()
                else
                    sdnode
                        :tag('td')
                            :attr('data-sort-value', t["release"])
                            :wikitext(lang:formatDate('d-M-Y', t["release"]))
                        :done()
                end
                
                -- Cost
                local skincolor = ""
                local image = ""
                if (tostring(t["cost"]) == "1290") then
                    skincolor = "mythic"
                end 
                if (tostring(t["cost"]) == "700") then
                    skincolor = "standard"
                end
                if (tostring(t["cost"]) == "0") then
                    skincolor = "classic"
                    image = "[[File:LoR Champion Wildcard icon.png|20px|link=]]"
                end
                sdnode
                    :tag('td')
                        :attr('data-sort-value', t["cost"])
                        :tag('span')
                            :css('color', color.skin({skincolor}))
                            :wikitext(lib.ternary(tostring(t["cost"]) == "0", image .. "1", t["cost"]))
                        :done()
                    :done()
                    
                -- Newanimations
                sdnode
                    :tag('td')
                        :attr('data-sort-value', lib.ternary(type(t["newLevelAnimation"]) ~= "nil", 1, 0))
                        :wikitext(lib.ternary(type(t["newLevelAnimation"]) ~= "nil", tostring(availablenode), ""))
                    :done()

                -- Set
                if (type(t["set"]) ~= "nil") then
                    local multiset = false
                    
                    for i, setname in pairs(t["set"]) do
                        if i == 1 then
                            s = setname
                        else
                            multiset = true
                            s = s .. ", " .. setname
                        end
                    end
                    
                    if multiset == true then
                        sdnode
                            :tag('td')
                                :attr('data-sort-value', '!Multiple')
                                :tag('span')
                                    :attr('title', s)
                                    :wikitext(tostring(availablenode))
                                :done()
                            :done()
                    else
                        sdnode
                            :tag('td')
                                :attr('data-sort-value', s)
                                :tag('span')
                                    :attr('title', s)
                                    :wikitext(tostring(availablenode))
                                :done()
                            :done()
                    end
                else
                    sdnode
                        :tag('td')
                            :attr('data-sort-value', 0)
                        :done()
                end
                
                -- Add skin row to the table
                sdtable
                    :node(sdnode)
                    :newline()
            end
        end
        -- TABLE END
    
    sdtable:allDone()
    return tostring(sdtable)
end

-------------------------
-- COMPOSITE FUNCTIONS --
-------------------------

-- Displays full artworks, either limited or all of them, with a description of the skin.
-- For patch notes and other use cases, show only 2 artworks. On the full cosmetics site, show all of them?
function p.displaySkin(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    local t, skinname, cardcode  = findSkin(args)
    
    return skinDisplay(cardcode, skinname, t, args['limit'])
end


-- Displays a link and tooltip for a given card and skin. 
function p.linkSkin(frame)
	local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
		
	local t, skinname, cardcode = findSkin(args)
	local s = "" 
	
	args['text'] = args['text'] or t.formatname or (skinname.. " " .. args['cardname'])
	if ((args['card'] == "all") and (t.associatedCards[2] ~= nil)) then -- hack to see if more than one associated card exists, #t.associatedCards doesn't work
        for i, card in ipairs(t.associatedCards) do
        	local name = card.name or get.name(card.cardcode)
        	if (i == 1) then -- first card
        		s = tostring(lorData.link{name = args['cardname'], code = card.cardcode, text = args['text'], skin = skinname}) .. "<small>("
        	else
        		s = s .. lib.ternary(i > 2, ", ", "") .. tostring(lorData.link{name=args['cardname'], code=card.cardcode, text = name, skin = skinname, indicator = false}) 
        	end
        end
        s = s .. ")</small>"
	else -- args['card'] is a number: Only link to that card
		local cardnumber = tonumber(args['card']) or 1
		if t.associatedCards[cardnumber] == nil then cardnumber = 1 end
		s = tostring(lorData.link{name = args['cardname'], code = t.associatedCards[cardnumber].cardcode, text = args['text'], skin = skinname})
	end
	
	return s
end

----------------------
-- GETTER FUNCTIONS --
----------------------

-- Returns the voiceactor of a skin, if possible
function p.getVoiceactor(frame)
	local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
        
    args['skin']	 = args['skin']     or "Original"	-- add "original" if no skin is given
    local t, skinname, cardcode = findSkin(args)
    local voiceactor = nil
    
    if t.voiceactor == nil and (t.voice == "original" or t.voice == "filtered" or t.voice == "added") then
    	voiceactor = lorSkindata[cardcode]['Original'].voiceactor -- try original VA if possible
    else
    	voiceactor = t.voiceactor
    end
    
    if voiceactor == nil then
        return "Unknown voice actor"
    end
    
    local s = ""
    for i, voiceactorname in ipairs(voiceactor) do
        if i ~= 1 then
            s = s .. ", " .. voiceactorname:gsub("% ", "&nbsp;")
        else
            s = voiceactorname
        end
    end

    return s
end

function p.getSplashartist(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
        
    args['skin']	 = args['skin']     or "Original"	-- add "original" if no skin is given
    local t, skinname, cardcode = findSkin(args)
    
    if t.splashartist == nil then
        return "Unknown artist"
    end
    
    local s = ""
    for i, splashartistname in ipairs(t.splashartist) do
        if i ~= 1 then
            s = s .. ", " .. splashartistname:gsub("% ", "&nbsp;")
        else
            s = splashartistname
        end
    end
    return s
end

-- Concatenates all set names of a skin
function p.getSet(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
        
    args['skin']	 = args['skin']     or "Original"	-- add "original" if no skin is given
    local t, skinname, cardcode = findSkin(args)
    
    if t.set == nil then
        return nil
    end
    
    local s = ""
    for i, setname in ipairs(t.set) do
        if i ~= 1 then
            s = s .. ", " .. setname:gsub("% ", "&nbsp;")
        else
            s = s .. setname
        end
    end

    return s
end

-- Returns the flavor text of all changed artworks or of one specific card.
function p.getLore(frame)
	local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
		
    args['skin']	 = args['skin']     or "Original"	-- add "original" if no skin is given
    local t, skinname, cardcode = findSkin(args)
	local s = "" 
	
	if ((args['card'] == "all") and (t.associatedCards[2] ~= nil)) then -- hack to see if more than one associated card exists, #t.associatedCards doesn't work
        for i, card in ipairs(t.associatedCards) do
        	s = s .. card.flavor .. "<br />"
        end
	else -- args['card'] is a number: Only link to that card
		local cardnumber = tonumber(args['card']) or 1
		if t.associatedCards[cardnumber] == nil then cardnumber = 1 end
		s = t.associatedCards[cardnumber].flavor
	end
	
	return s
end

-- getavailability, getprice, getartworks and other getter functions are also possible, whatever other modules / functions need

----------------------
-- HELPER FUNCTIONS --
----------------------

-- Generates the big display with all artworks of a skin.
function skinDisplay(cardcode, skin, skindata, limit)
    local lang = mw.language.new("en")
	local IL        = require('Module:ImageLink')
	local FN		= require('Module:Filename')
    local cardname	   = get.name(cardcode)
    local formatname   = skindata.formatname or skin .. ' ' .. cardname
    local rarity	   = skindata.rarity or "Base"
    local cost         = skindata.cost
    local release      = skindata.release
    local changedCards = skindata.associatedCards
    local changedCount = 0
    
    for k, v in pairs(changedCards) do
    	changedCount = changedCount + 1 -- "#"" does not work
    end
    
    
    -- really no need to validate the arguments, do it in the calling functions
    
    if release ~= "N/A" then
        release  = lang:formatDate("d-M-Y", release)
    end
    
    if type(limit) == "string" then 
    	limit = tonumber(limit)
    end
    if limit == nil then
    	limit = 99 -- some high number, I don't think any skin has 99 new artworks.
    end
    
    local s = '<div style="display:inline-block; margin:5px"><div class="skin-description" style="width: 342px;">' .. formatname .. ': ' .. rarity .. ' Skin <br />'
    
    -- begin of description div
    
    -- feature list
    if (rarity ~= "Base") then
	    s = s .. "Features: <br /><ul>"
	    s = s .. "<li> " .. changedCount .. " new artworks </li>"
	    s = s .. lib.ternary(skindata.newLevelAnimation, "<li>New level-up animation </li>", "")
	    
	    s = s .. '</ul>'
    end
    -- Availability notes: Available, Legacy, Rare, Limited, Partner or Upcoming
    if skindata.availability == "Upcoming" then
        s = s .. "This skin has been announced, but is not yet available in game."
    elseif skindata.availability == "Available" then
    	s = s .. lib.ternary(rarity == "Base", "", "This skin is available for purchase.")
    elseif skindata.availability == "Legacy" then
    	s = s .. "This legacy skin is not available for purchase."
    else
    	-- other phrases here
    end
    
    -- right-floating div
    s = s ..' <div style="float:right; line-height: 27px;">'
    
    if cost == 'N/A' then
        -- skip
    elseif cost == 0 and rarity == "Base" then -- not "for free" but a basic card skin
        s = s .. tostring(IL.basic{["link"] = "Wildcard (Legends of Runeterra)", ["text"] = 1, ["alttext"] = "1 Wildcards", ["image"] = "LoR " .. get.rarity(cardcode) .. " Wildcard icon.png", ["border"] = "false", ["labellink"] = "false"}) .. ' / ' 
    elseif cost == "special" then
        s = s .. "Special pricing" .. ' / ' 
    else
        s = s .. tostring(IL.basic{["link"] = "Coin_(Legends_of_Runeterra)", ["text"] = cost, ["alttext"] = cost .. " Coins", ["image"] = "Coin icon.png", ["border"] = "false", ["labellink"] = "false"}) .. ' / ' 
    end
    
    
    s = s .. release .. '</div>' -- end of the right-floating div 
    
    s = s .. '</div>' -- end of description div
    
    -- begin artworks
    for i, t in ipairs(changedCards) do
    	if (i <= limit and skindata.availability ~= "Upcoming") then
    		local changedName = t.name or get.name(t.cardcode)
    		s = s .. '<div class="skin-single-card" style="width: 342px; float:left; padding:10px;"><div class="skin-icon" data-game="lor" data-champion="' .. cardname .. '" data-skin="' .. skin .. '" data-variant="' .. t.cardcode .. '">[[File:' .. FN.lorskin{cardcode = t.cardcode, skin = skin}  .. '|x170px|border]]</div><div style="float:left">'.. tostring(lorData.link{code = t.cardcode, text = changedName, indicator = "false", nolink = true}) ..'<p>' .. t.flavor.. '</p></div></div>'
    	end
    end
    
	s = s .. '</div><br />' -- end of entire div
	
    return s
end

-- Finds an entry from the skindata based on various arguments. (base Card name or code) and (skinname or skin idx), also works with {"Cardname", "Skinname"}
function findSkin(args) 
	local cardcode = args['cardcode'] or nil
	if cardcode == nil then
		local cardname = args['cardname'] or args[1] or nil
		cardcode = lorData.findCardsByName{search = cardname, firstOnly = true}
		if cardcode == nil then -- couldn't find card by name
			return userError("Can't find a card this way.", "LuaError")
		end
	end
	
    if lorSkindata[cardcode] == nil then
        return userError("Card ''" ..cardcode .. "'' does not exist in Module:LoRCosmetics/skins", "LuaError")
    end
    
	local skinname = args['skin'] or args[2] or nil
	if skinname == nil then
		for k, v in pairs(lorSkindata[cardcode]) do-- check all skins for the right index
			if v.idx == args['skinindex'] then
				skinname = k
			end
		end
		if skinname == nil then
			return userError ("Can't find a skin this way.", "LuaError")	
		end
	end
	
    if lorSkindata[cardcode][skinname] == nil then
        return userError("Skin ''" .. skinname .. "'' does not exist for card ''".. get.name(cardcode) .. "'' (".. cardcode .. ")", "LuaError")
    end
    
    return lorSkindata[cardcode][skinname], skinname, cardcode -- skin data first, this is what you get returned when you use only one value
end

-- same as listing all skins, but includes a way to break the loops if the skin is found. Returns cardcode, skinname, skindata.
function findSkinForCardcode(cardcode, skin)
	local basecardcode = cardcode
	local isAssociated, wordTIndex = string.find(basecardcode, "[^A-Z]T"); -- can't look for just any T because MT
	if isAssociated then
    	basecardcode = string.sub(basecardcode, 1, wordTIndex-1) 
    end
    
    local t = lorSkindata[basecardcode]
    
    if t ~= nil then
    	for skinname, skindata in pairs(t) do
    		if (skinname == skin) then
	    		for i, card in ipairs(skindata.associatedCards) do
	    			if card.cardcode ==	cardcode then
	    				return skindata, skinname, basecardcode
	    			end
	    		end
	    		-- skinname matches but didn't find the cardcode - this is fucked up
	    		break
	    	end
    	end
    end -- end of associated card skins
    
    for associatedcard, skins in pairs(lorSkindata) do
    	if (associatedcard ~= basecardcode) then -- skip card that is already handled
    		for skinname, skindata in pairs(skins) do -- check all other skins of all other cards
	    		for i, card in ipairs(skindata.associatedCards) do
	    			if card.cardcode ==	cardcode then
	    				return skindata, skinname, associatedcard
	    			end
	    		end -- end of "all cards of this skin"
	    	end-- end of "all skins of this card"
    	end
    end -- end of "all other cards"
end

-- finds all skins that are related to the given card. First, returns the skins of the associated card (without the T), then ALL SKINS. Gonna be an expensive function to use!
-- returns a table of {skindata, skinname, associated_cardcode} if the given cardcode appears in skindata.
function findSkinsForCardcode(cardcode) 
	local basecardcode = cardcode
	local isAssociated, wordTIndex = string.find(basecardcode, "[^A-Z]T"); -- can't look for just any T because MT
	if isAssociated then
    	basecardcode = string.sub(basecardcode, 1, wordTIndex-1) 
    end
    
    local skinstable = {}
    
    local t = lorSkindata[basecardcode]
    
    if t ~= nil then
    	for skinname, skindata in pairs(t) do
    		for i, card in ipairs(skindata.associatedCards) do
    			if card.cardcode ==	cardcode then
    				table.insert(skinstable, {basecardcode, skinname, skindata})
    				break
    			end
    		end
    	end
    end -- end of associated card skins
    
    for associatedcard, skins in pairs(lorSkindata) do
    	if (associatedcard ~= basecardcode) then -- skip card that is already handled
    		for skinname, skindata in pairs(skins) do -- check all other skins of all other cards
	    		for i, card in ipairs(skindata.associatedCards) do
	    			if card.cardcode ==	cardcode then
	    				table.insert(skinstable, {associatedcard, skinname, skindata})
	    				break
	    			end
	    		end -- end of "all cards of this skin"
	    	end-- end of "all skins of this card"
    	end
    end -- end of "all other cards"
    
    return skinstable
end

function idxcomp(a, b)
    local a = a.idx or -1
    local b = b.idx or -1
    return (a < b)
end

-- "Inflates" a reduced string with space characters before Uppercase letters
function inflate(s)
	local str, number = s:gsub("%l%u", function (m) return string.sub(m,1,1) .. " " .. string.sub(m,2,2) end)
	return str
end

-- creates an iteration function based on the given skin table, sorted by skin names? I'm open to other ways of sorting as well...
function skinIter(t)
    local keys = {}
    for cardcode, skins in pairs(t) do -- this is a loop over every card in the module!
    	local cardname = get.name(cardcode)
    	for skinname, skin in pairs(skins) do
    		local sortname = skin.formatname or (skinname .. " " .. cardname)
            table.insert(keys, {sortname, cardcode, skinname})
    	end
    end

    table.sort(keys, function(a,b)
        return (a[1] < b[1])
    end)

    -- return the iterator function
    local i = 0
    return function()
        i = i + 1
        if keys[i] then
            return {keys[i][2], keys[i][3]}, t[keys[i][2]][keys[i][3]], i
        end -- t is indexed by (card code) and then (skin name) so this is what is returned as well.
    end
end

return p

-- </pre>
-- [[Category:Lua sandbox]]
Advertisement