Skylords Reborn
No edit summary
No edit summary
Line 930: Line 930:
 
result = mw.html.create():tag('tr'):addClass('tr-force-show')
 
result = mw.html.create():tag('tr'):addClass('tr-force-show')
 
:tag('th'):cssText('width: 30%; text-align:left; background-color: #092949'):wikitext('Card Name'):done()
 
:tag('th'):cssText('width: 30%; text-align:left; background-color: #092949'):wikitext('Card Name'):done()
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0'):wikitext('[[File:General Booster Pack HD.png|40px|link=]]'):attr('title', 'General Booster Pack (Card Pool: '..counter[1]..')'):done()
+
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:General Booster Pack HD.png|40px|link=]]'):attr('title', 'General Booster Pack (Card Pool: '..counter[1]..')'):done()
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0'):wikitext('[[File:Fire Booster Pack HD.png|40px|link=]]'):attr('title', 'Fire Booster Pack (Card Pool: '..counter[2]..')'):done()
+
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Fire Booster Pack HD.png|40px|link=]]'):attr('title', 'Fire Booster Pack (Card Pool: '..counter[2]..')'):done()
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0'):wikitext('[[File:Frost Booster Pack HD.png|40px|link=]]'):attr('title', 'Frost Booster Pack (Card Pool: '..counter[3]..')'):done()
+
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Frost Booster Pack HD.png|40px|link=]]'):attr('title', 'Frost Booster Pack (Card Pool: '..counter[3]..')'):done()
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0'):wikitext('[[File:Nature Booster Pack HD.png|40px|link=]]'):attr('title', 'Nature Booster Pack (Card Pool: '..counter[4]..')'):done()
+
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Nature Booster Pack HD.png|40px|link=]]'):attr('title', 'Nature Booster Pack (Card Pool: '..counter[4]..')'):done()
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0'):wikitext('[[File:Shadow Booster Pack HD.png|40px|link=]]'):attr('title', 'Shadow Booster Pack (Card Pool: '..counter[5]..')'):done()
+
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Shadow Booster Pack HD.png|40px|link=]]'):attr('title', 'Shadow Booster Pack (Card Pool: '..counter[5]..')'):done()
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0'):wikitext('[[File:Amii Booster Pack HD.png|40px|link=]]'):attr('title', 'Amii Booster Pack (Card Pool: '..counter[6]..')'):done()
+
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Amii Booster Pack HD.png|40px|link=]]'):attr('title', 'Amii Booster Pack (Card Pool: '..counter[6]..')'):done()
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0'):wikitext('[[File:Bandits Booster Pack HD.png|40px|link=]]'):attr('title', 'Bandits Booster Pack (Card Pool: '..counter[7]..')'):done()
+
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Bandits Booster Pack HD.png|40px|link=]]'):attr('title', 'Bandits Booster Pack (Card Pool: '..counter[7]..')'):done()
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0'):wikitext('[[File:Fire Frost Booster Pack HD.png|40px|link=]]'):attr('title', 'Fire Frost Booster Pack (Card Pool: '..counter[8]..')'):done()
+
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Fire Frost Booster Pack HD.png|40px|link=]]'):attr('title', 'Fire Frost Booster Pack (Card Pool: '..counter[8]..')'):done()
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0'):wikitext('[[File:Lost Souls Booster Pack HD.png|40px|link=]]'):attr('title', 'Lost Souls Booster Pack (Card Pool: '..counter[9]..')'):done()
+
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Lost Souls Booster Pack HD.png|40px|link=]]'):attr('title', 'Lost Souls Booster Pack (Card Pool: '..counter[9]..')'):done()
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0'):wikitext('[[File:Stonekin Booster Pack HD.png|40px|link=]]'):attr('title', 'Stonekin Booster Pack (Card Pool: '..counter[10]..')'):done()
+
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Stonekin Booster Pack HD.png|40px|link=]]'):attr('title', 'Stonekin Booster Pack (Card Pool: '..counter[10]..')'):done()
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0'):wikitext('[[File:Twilight Booster Pack HD.png|40px|link=]]'):attr('title', 'Twilight Booster Pack (Card Pool: '..counter[11]..')'):done()
+
:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Twilight Booster Pack HD.png|40px|link=]]'):attr('title', 'Twilight Booster Pack (Card Pool: '..counter[11]..')'):done()
 
:done():node(result:done()):done()
 
:done():node(result:done()):done()
 
end
 
end

Revision as of 19:24, 11 June 2021

Edit documentation

Description

Handles the creation of the playable cards' appearences in the wiki.

See also


-- Questions? Ask them on Message_Wall:FabianLars or Discord (User:FabianLars for contact details)

local p = {}
local get = require('Module:Card/get')
local has = require('Module:Card/has')
local lib = require('Module:Shared')
local concat = lib.concat
local format = lib.format
local getArgs = lib.getArgs

--- get name, affinity and promo from given cardname
---@param n string name of card in form of 'name', 'name (affinity)' or 'name (Promo)'
---@return string, string|nil, boolean Cardname without parantheses; affinity or nil; true if promo
---@private
local function verifyName(n)
    local aff, promo = nil, false
    local name, var = string.find(n, ' (', 1, true)
    name, _ = name and string.sub(n, 1, (name or 0) - 1) or n, nil
    var, _ = string.gsub(string.sub(n, (var or 0) + 1), '%)', '')

    if var ~= name then
        if var == 'Promo' then
            promo = true
        elseif var == 'Lost Souls' or var == 'Twilight' or var == 'Superpig' then
            name = format('%s (%s)', name, var)
        elseif var == 'Fire' or var == 'Frost' or var == 'Nature' or var == 'Shadow' then
            if has.affinity(name, var) then
                aff = var
            end
        end
    end

    return name, aff, promo
end

local function CardClass(init)
    local self = type(init) == 'table' and init or {}
    self.scaling = self.scaling or 0.74
    self.cardupgrade = self.cardupgrade or 0
    self.applied_charges = self.applied_charges or 0
    self.notooltip = self.notooltip or false
    self.infobox = self.infobox or false
    self.orbs = self.orbs or {}

    function self.build()
        local card_container = mw.html.create('div')
            :addClass(format('hidden cv-card-container%s', self.promo and ' cv-promo' or ''))
            :cssText(format('transform: scale(%s)', self.scaling))
        if self.err then
            card_container:attr('title', self.err or 'Error: Check your input')
        end

        local card_artwork = mw.html.create('div'):addClass('cv-art'):wikitext(self.artwork_wktxt or ''):cssText(self.err and 'top: 0;' or ''):done()

        local card_background = mw.html.create('div'):addClass('cv-bg c'.. (self.is_upgrade and 'u' or '') ..'v-bg-'..(self.factionLR or 'Blank')):wikitext((self.link and self.link ~= '') and ('[['..self.link..']]') or '')
        if self.spell_background_wktxt then
            card_background:tag('div'):addClass('cv-bg-spell'):wikitext(self.spell_background_wktxt):done()
        end
        card_background:done()

        self.cardupgrade = self.cardupgrade or 0
        self.applied_charges = self.applied_charges or 0

        local upgradeLeft = mw.html.create()

        if self.infobox or self.cardupgrade == 1 then
            upgradeLeft:tag('div'):addClass('cv-upgradeparts cv-U1'):css{
                ['position'] = 'absolute',
                ['bottom'] = '168px',
                ['left'] = 0,
                ['z-index'] = 150,
                ['display'] = (self.cardupgrade == 1 and '' or 'none')
            }:wikitext(self.upgrade_left_1_wktxt or ''):done()
        end
        if self.infobox or self.cardupgrade == 2 then
            upgradeLeft:tag('div'):addClass('cv-upgradeparts cv-U2'):css{
                ['position'] = 'absolute',
                ['bottom'] = '168px',
                ['left'] = 0,
                ['z-index'] = 150,
                ['display'] = (self.cardupgrade == 2 and '' or 'none')
            }:wikitext(self.upgrade_left_2_wktxt or ''):done()
        end
        if self.infobox or self.cardupgrade == 3 then
            upgradeLeft:tag('div'):addClass('cv-upgradeparts cv-U3'):css{
                ['position'] = 'absolute',
                ['bottom'] = '168px',
                ['left'] = 0,
                ['z-index'] = 150,
                ['display'] = (self.cardupgrade == 3 and '' or 'none')
            }:wikitext(self.upgrade_left_3_wktxt or ''):done()
        end
        if self.infobox or self.cardupgrade >= 1 then
            upgradeLeft:tag('div'):addClass('cv-chargeparts cv-C1'):css{
                ['position'] = 'absolute',
                ['bottom'] = (self.factionLeft == 'Neutral' and '232px' or '233px'),
                ['left'] = (self.factionLeft == 'Neutral' and '10px' or '4px'),
                ['z-index'] = 200,
                ['display'] = ((self.applied_charges >= 1 and self.applied_charges <= self.cardupgrade) and '' or 'none')
            }:wikitext(self.charge_left_1_wktxt or ''):done()
        end
        if self.infobox or self.cardupgrade >= 2 then
            upgradeLeft:tag('div'):addClass('cv-chargeparts cv-C2'):css{
                ['position'] = 'absolute',
                ['bottom'] = (self.factionLeft == 'Neutral' and '312px' or '304px'),
                ['left'] = (self.factionLeft == 'Neutral' and '10px' or '4px'),
                ['z-index'] = 200,
                ['display'] = ((self.applied_charges >= 2 and self.applied_charges <= self.cardupgrade) and '' or 'none')
            }:wikitext(self.charge_left_2_wktxt or ''):done()
        end
        if self.infobox or self.cardupgrade >= 3 then
            upgradeLeft:tag('div'):addClass('cv-chargeparts cv-C3'):css{
                ['position'] = 'absolute',
                ['bottom'] = (self.factionLeft == 'Neutral' and '394px' or '372px'),
                ['left'] = (self.factionLeft == 'Neutral' and '10px' or '4px'),
                ['z-index'] = 200,
                ['display'] = ((self.applied_charges >= 3 and self.applied_charges <= self.cardupgrade) and '' or 'none')
            }:wikitext(self.charge_left_3_wktxt or ''):done()
        end

        if self.promo_icon_wktxt then
            upgradeLeft:tag('div'):addClass('cv-promo-icon ' .. (self.promo_icon_class or '')):wikitext(self.promo_icon_wktxt):done()
        end

        upgradeLeft:done()

        local upgradeRight = mw.html.create()

        if self.infobox or self.cardupgrade >= 1 then
            upgradeRight:tag('div'):addClass('cv-upgradeparts cv-U1'):css{
                ['position'] = 'absolute',
                ['bottom'] = '168px',
                ['right'] = 0,
                ['z-index'] = 150,
                ['display'] = (self.cardupgrade >= 1 and '' or 'none')
            }:wikitext(self.upgrade_right_1_wktxt or ''):done()
        end
        if self.infobox or self.cardupgrade >= 2 then
            upgradeRight:tag('div'):addClass('cv-upgradeparts cv-U2'):css{
                ['position'] = 'absolute',
                ['bottom'] = (self.factionRight == 'Neutral' and '248px' or '296px'),
                ['right'] = (self.factionRight == 'Neutral' and 0 or '4px'),
                ['z-index'] = 150,
                ['display'] = (self.cardupgrade >= 2 and '' or 'none')
            }:wikitext(self.upgrade_right_2_wktxt or ''):done()
        end
        if self.infobox or self.cardupgrade >= 3 then
            upgradeRight:tag('div'):addClass('cv-upgradeparts cv-U3'):css{
                ['position'] = 'absolute',
                ['bottom'] = (self.factionRight == 'Neutral' and '326px' or '362px'),
                ['right'] = (self.factionRight == 'Neutral' and 0 or '4px'),
                ['z-index'] = 150,
                ['display'] = (self.cardupgrade == 3 and '' or 'none')
            }:wikitext(self.upgrade_right_3_wktxt or ''):done()
        end
        if self.infobox or self.cardupgrade >= 1 then
            upgradeRight:tag('div'):addClass('cv-chargeparts cv-C1'):css{
                ['position'] = 'absolute',
                ['bottom'] = (self.factionRight == 'Neutral' and '232px' or '221px'),
                ['right'] = (self.factionRight == 'Neutral' and '11px' or '4px'),
                ['z-index'] = 200,
                ['display'] = ((self.applied_charges >= 1 and self.applied_charges <= self.cardupgrade) and '' or 'none')
            }:wikitext(self.charge_right_1_wktxt or ''):done()
        end
        if self.infobox or self.cardupgrade >= 2 then
            upgradeRight:tag('div'):addClass('cv-chargeparts cv-C2'):css{
                ['position'] = 'absolute',
                ['bottom'] = (self.factionRight == 'Neutral' and '312px' or '294px'),
                ['right'] = (self.factionRight == 'Neutral' and '11px' or '4px'),
                ['z-index'] = 200,
                ['display'] = ((self.applied_charges >= 2 and self.applied_charges <= self.cardupgrade) and '' or 'none')
            }:wikitext(self.charge_right_2_wktxt or ''):done()
        end
        if self.infobox or self.cardupgrade >= 3 then
            upgradeRight:tag('div'):addClass('cv-chargeparts cv-C3'):css{
                ['position'] = 'absolute',
                ['bottom'] = (self.factionRight == 'Neutral' and '390px' or '362px'),
                ['right'] = (self.factionRight == 'Neutral' and '11px' or '4px'),
                ['z-index'] = 200,
                ['display'] = ((self.applied_charges >= 3 and self.applied_charges <= self.cardupgrade) and '' or 'none')
            }:wikitext(self.charge_right_3_wktxt or ''):done()
        end
        upgradeRight:done()

        local card_name = mw.html.create('div'):addClass('cv-name'):wikitext(self.display_name or ''):done()

        local card_tokenslot = self.is_upgrade and mw.html.create() or mw.html.create('div'):addClass(format('cv-slot cv-slot-%s card-viewer-color-%s', self.factionLR == 'FireNature' and 'FireNature' or (self.factionRight or 'blank'), string.lower(self.affinity or 'blank'))):wikitext(self.affinity and '[[File:Affinity Tokenslot Overlay Blank.png|link=]]' or '')

        local orb1 = mw.html.create('div'):addClass('cv-orb1 cv-orb-'..(self.orbs[1] or 'None')):done()

        local orb2 = mw.html.create('div'):addClass('cv-orb2 cv-orb-'..(self.orbs[2] or 'None')):done()

        local orb3 = mw.html.create('div'):addClass('cv-orb3 cv-orb-'..(self.orbs[3] or 'None')):done()

        local orb4 = mw.html.create('div'):addClass('cv-orb4 cv-orb-'..(self.orbs[4] or 'None')):done()

        local card_power_cost = mw.html.create('div'):addClass('cv-cost'):attr('data-cost', self.data_cost_attr or ''):wikitext(self.power_cost_wktxt or ''):done()

        local card_charges_squadsize_class = mw.html.create('div'):addClass('cv-squad')
            :tag('span')
               :addClass('cv-charges')
               :attr('data-charges', self.data_charges_attr or '')
               :wikitext(self.charges_wktxt or ''):done()
        if self.squadsize and self.squadsize > 1 then
            card_charges_squadsize_class:wikitext(format('x%s', self.squadsize))
        end
        card_charges_squadsize_class:wikitext(format(' %s', self.class or '')):done()

        local card_affinity_icon = self.affinity
            and (mw.html.create('div'):addClass('cv-aff-icon cv-aff-icon-'..self.affinity)
            or mw.html.create()):done()

        local card_abilities = mw.html.create('div'):addClass('cv-abilities'):node(self.abilities_node or '')

        local card_weapon_type = mw.html.create('div'):addClass(format('cv-weapon card-viewer-layer-%s', string.lower(self.factionLeft or 'blank'))):wikitext(self.weapon_type_wktxt or ''):done()
        local card_counter = mw.html.create('div'):addClass(format('cv-counter card-viewer-layer-%s', string.lower(self.factionLeft or 'blank'))):wikitext(self.counter_wktxt or ''):done()

        local card_damage = mw.html.create('div'):addClass('cv-damage'):attr('data-damage', self.data_damage_attr or ''):wikitext(self.damage_wktxt or ''):done()

        local card_size_class = mw.html.create('div'):addClass('cv-size card-viewer-unit-size')
        if self.unit_size_wktxt or self.health_wktxt then
            card_size_class:addClass(format('card-viewer-layer-%s', string.lower(self.factionRight or 'blank')))
                           :wikitext('[[File:Card_Icon_HP.png||link=]]')
                           :wikitext(self.unit_size_wktxt or '')
        end

        local card_health = mw.html.create('div'):addClass('cv-health'):attr('data-health', self.data_health_attr or ''):wikitext(self.health_wktxt or ''):done()

        local card_edition_icon = mw.html.create('div'):addClass((not self.edition or not self.rarity) and 'cv-edition cv-edition-blank' or format('cv-edition cv-edition-%s-%s', string.gsub(self.edition, ' ', '-'), string.gsub(self.rarity, ' ', '-')))

        card_container
            :node(card_artwork):node(card_background):node(upgradeLeft):node(upgradeRight):node(card_name)
            :node(card_tokenslot):node(orb1):node(orb2):node(orb3):node(orb4)
            :node(card_power_cost):node(card_charges_squadsize_class):node(card_affinity_icon):node(card_abilities)
            :node(card_weapon_type):node(card_counter):node(card_damage):node(card_size_class):node(card_health):node(card_edition_icon)
            :done()

        local return_val = mw.html.create('div'):addClass((not self.notooltip and not self.err) and 'custom-tooltip' or ''):attr('data-tt-type', 'card'):attr('data-1', (self.full_name or self.display_name) .. (self.promo and ' (Promo)' or (self.affinity and ' ('..self.affinity..')' or ''))):css{
            ['width'] = format('%spx', 370 * self.scaling),
            ['height'] = format('%spx', 510 * self.scaling),
            ['display'] = 'inline-block'
        }:tag('div'):cssText('display:none'):wikitext(self.display_name and '[['..self.display_name..']]' or ''):done():node(card_container):allDone()
        
        if self.filter then
        	return_val
                :attr('data-search', self.full_name or self.display_name)
                :attr('data-rarity', self.rarity)
                :attr('data-cost', self.power_cost_wktxt)
                :attr('data-edition', self.edition)
                :attr('data-faction', self.faction)
                :attr('data-counter', self.counter)
                :attr('data-weapontype', self.weapon_type)
                :attr('data-special', (self.promo and 'Promo' or '') ..','.. (self.starter and 'Starter' or '')..','..(self.normal and 'Normal' or '')..','..((function() for _,v in ipairs(self.orbs) do if v and v~='Neutral' then return true end end end)() and 'Non-Legendary' or ''))
                :attr('data-orbs', concat(self.orbs, ','))
                :attr('data-orbsamount', #self.orbs)
                :attr('data-affinities', concat(self.affinity, ','))
                :attr('data-size', self.size)
                :attr('data-type', self.type)
        end
        
        return return_val
    end

    return self
end

local function ErrorCard(err, scaling)
    return CardClass{
        scaling = scaling,
        display_name = 'ERROR',
        artwork_wktxt = '[[File:Quests_warning.png||link=|320px]]',
        abilities_node = mw.html.create('div'):cssText('width: 320px'):wikitext(err),
        err = err,
    }.build()
end

local function chargeRules(index)
    local charge_rules = {
        [4] = { 1, 1, 1, 1 },
        [8] = { 4, 2, 1, 1 },
        [10] = { 5, 2, 2, 1 },
        [12] = { 6, 2, 2, 2 },
        [16] = { 8, 3, 3, 2 },
        [20] = { 10, 4, 3, 3 },
        [24] = { 12, 4, 4, 4 }
    }
    if index then return charge_rules[index] end
    return charge_rules
end

local function getProgressionArgs(v1, v2, v3, v4)
	if v1 and v1 ~= '' then
		return {texttip = 'true', v1, 'Base / U-0', v2, 'U-1', v3, 'U-2', v4, 'U-3'}
	elseif v2 and v2 ~= '' then
		return {texttip = 'true', v2, 'U-1', v3, 'U-2', v4, 'U-3'}
	elseif v3 and v3 ~= '' then
		return {texttip = 'true', v3, 'U-2', v4, 'U-3'}
	elseif v4 and v4 ~= '' then
		return {texttip = 'true', v4, 'U-3'}
	else return end
end

local function getAbilAvail(t)
	if type(t) ~= 'table' then return nil end
	local from, to
	for k,v in ipairs(t) do
		if v and not from then from = k-1 end
		if from and not v then to = k-2 end
	end
	if from == 3 then return 3 end
	if from == to then
		if from == 0 then return nil end
		return from
	else
		return (from or 0) .. '-' .. (to or 3)
	end
end

function p.abilities(frame)
	local args = getArgs(frame)
	local tagArgs = {}
	local getInfo = function(aData, affinity, promo)
		local res = ''
		local key = 'Z'
		for _,v in ipairs(aData or {}) do
			local text = v.description or ''
	        if v.values and v.values[1] then
	        	local temp = {}
	        	if type(v.values[1]) == 'number' or type(v.values[1]) == 'string' then
	        		temp[#temp+1] = frame:expandTemplate{title = 'p', args = getProgressionArgs(v.values[1], v.values[2], v.values[3], v.values[4])}
	        	else	
	    	        for _,vv in ipairs(v.values) do
	    	            temp[#temp+1] = frame:expandTemplate{title = 'p', args = getProgressionArgs(vv[1], vv[2], vv[3], vv[4])}
	    	        end
    	        end
    	        local temp_text,_ = mw.ustring.gsub(text, '%%([^s])', '%%%%%1')
    	        text = format(temp_text, unpack(temp))
	        end
	        local icon_name = format("%s %s Ability Icon.png", args.name or args[1], v.name)

	        res = res .. frame:expandTemplate{
	            title = 'Ability',
	            args = {
	                hotkey = (v.type == 'Active' or v.type == 'Toggle') and key or nil,
	                name = v.name,
	                type = v.type,
	                cost = type(v.cost) == 'table'
	                	and frame:expandTemplate{title = 'p', args = getProgressionArgs(v.cost[1], v.cost[2], v.cost[3], v.cost[4])}
	                	or v.cost,
	                affinity_dependency = v.affinity_dependency and affinity or nil,
	                upgrade_availability = getAbilAvail(v.upgrade_availability),
	                icon = mw.title.new( icon_name, 'File' ).exists and icon_name or nil,
	                details = args['ability_details_'..(tostring(v.name or ''):lower():gsub('%s+', '_'))] and frame:preprocess(args['ability_details_'..(tostring(v.name or ''):lower():gsub('%s+', '_'))]) or nil,
	                description = text
	            }
	        }
			
			if v.type == "Active" or v.type == "Toggle" then
				if key == 'Z' then key = 'X'
				elseif key == 'X' then key = 'C'
	            elseif key == 'C' then key = 'V'
	            end
	        end
		end
		return res
	end
	
	local promo_aff = get.affinity(args[1] .. ' (Promo)')
	
	if get.abilities(args[1]) then tagArgs[#tagArgs+1] = args[1]..'='..getInfo(get.abilities(args[1])) end
	if get.abilities(args[1]..' (Fire)') then tagArgs[#tagArgs+1] = args[1]..' (Fire)='..getInfo(get.abilities(args[1]..' (Fire)'), 'Fire') end
	if get.abilities(args[1]..' (Frost)') then tagArgs[#tagArgs+1] = args[1]..' (Frost)='..getInfo(get.abilities(args[1]..' (Frost)'), 'Frost') end
	if get.abilities(args[1]..' (Nature)') then tagArgs[#tagArgs+1] = args[1]..' (Nature)='..getInfo(get.abilities(args[1]..' (Nature)'), 'Nature') end
	if get.abilities(args[1]..' (Shadow)') then tagArgs[#tagArgs+1] = args[1]..' (Shadow)='..getInfo(get.abilities(args[1]..' (Shadow)'), 'Shadow') end
	if get.abilities(args[1]..' (Promo)') then tagArgs[#tagArgs+1] = args[1]..' (Promo)='..getInfo(get.abilities(args[1]..' (Promo)'), promo_aff, true) end
	if get.abilities(args[1]..' (Lost Souls)') then tagArgs[#tagArgs+1] = args[1]..' (Lost Souls)='..getInfo(get.abilities(args[1]..' (Lost Souls)')) end
	if get.abilities(args[1]..' (Twilight)') then tagArgs[#tagArgs+1] = args[1]..' (Twilight)='..getInfo(get.abilities(args[1]..' (Twilight)')) end
	if get.abilities(args[1]..' (Superpig)') then tagArgs[#tagArgs+1] = args[1]..' (Superpig)='..getInfo(get.abilities(args[1]..' (Superpig)')) end
	
	return (#tagArgs or 0) <= 1 and (tagArgs[1] or ''):gsub('^.-=', '', 1) or frame:preprocess('<tabber>'..concat(tagArgs, '|-|')..'</tabber>')
end

--- returns html for a card to display
---@param frame table mw.frame object
---@return string card as mw.html
---@public
function p.card(frame)
    local args = getArgs(frame)

    if not get.exists(args.name or args[1]) then
        return ErrorCard(
            format('Card or Variant "%s" not found', args.name or args[1]),
            args.scaling or args.displayscaling or 0.74
        )
    end

    local data = get.card(args.name or args[1])

    local card = CardClass()
    local charge_rules = chargeRules()
    local name = args.name or args[1] or 'Name missing'
    local display_name, aff, promo = verifyName(args.name or args[1] or 'Name missing')
    if data.affinity then aff = data.affinity end
    if data.promo then promo = data.promo end
    if data.temporary_card then promo = true end
    local scaling = args.scaling or data.scaling or args.displayscaling or data.displayscaling or 0.74
    --local err = nil
    local check_size = {
        S = true,
        M = true,
        L = true,
        XL = true,
    }

    local link = display_name
    if data.nolink or args.nolink then
        link = ''
    end
    
    local res = data.orbs and get['factions'](name, data.orbs)
    local factionLR = data.orbs and (res[1] ~= res[2] and res[1] .. res[2] or res[1]) or 'Blank'
    local factionLeft = res and res[1] or nil
    local factionRight = res and res[2] or nil

	card.filter = args.filter
    card.infobox = args.infobox
    card.link = link
    card.type = data.type
    card.faction = get.faction(name)
    card.factionLR = factionLR
    card.factionLeft = factionLeft
    card.factionRight = factionRight
    card.affinity = aff
    card.notooltip = args.notooltip or data.notooltip or false
    card.scaling = scaling
    card.artwork_wktxt = format(data.artwork and format('[[File:%s||link=|320px]]', data.artwork) or '[[File:%s%s_Card_Artwork.png||link=]]', display_name, promo and '_(Promo)' or '')
    card.spell_background_wktxt = data.type == 'Spell' and format('[[File:Spell_Card_Overlay_%s.png||link=]]', factionLR) or nil
    local cardupgrade = promo and 3 or (tonumber(data.cardupgrade) or tonumber(args.cardupgrade) or 0)
    local applied_charges = promo and 3 or (tonumber(data.applied_charges) or tonumber(args.applied_charges) or 0)
    card.cardupgrade = cardupgrade
    card.applied_charges = applied_charges
    if promo and not data.temporary_card then
    	card.promo = true
        card.promo_icon_wktxt = format('[[File:Promo_Icon_%s.png||link=]]', factionLeft)
    elseif data.starter_card and (args.infobox or (cardupgrade or 0) == 0) then
        card.promo_icon_class = 'cv-upgradeparts cv-A0'
        card.promo_icon_wktxt = format('[[File:Starter_Icon_%s.png||link=]]', factionLeft)
    elseif data.temporary_card then
        card.promo_icon_wktxt = format('[[File:Temporary_Icon_%s.png||link=]]', factionLeft)
    end
    card.upgrade_left_1_wktxt = format('[[File:%s_Upgrade_1_Left.png||link=]]', factionLeft)
    card.upgrade_left_2_wktxt = format('[[File:%s_Upgrade_2_Left.png||link=]]', factionLeft)
    card.upgrade_left_3_wktxt = format('[[File:%s_Upgrade_3_Left.png||link=]]', factionLeft)
    card.charge_left_1_wktxt =
    factionLeft == 'Neutral'
        and '[[File:Neutral_Charge_All.png||link=]]'
        or format('[[File:%s_Charge_1_Left.png||link=]]', factionLeft)
    card.charge_left_2_wktxt =
    factionLeft == 'Neutral'
        and '[[File:Neutral_Charge_All.png||link=]]'
        or format('[[File:%s_Charge_2_Left.png||link=]]', factionLeft)
    card.charge_left_3_wktxt =
    factionLeft == 'Neutral'
        and '[[File:Neutral_Charge_All.png||link=]]'
        or format('[[File:%s_Charge_3_Left.png||link=]]', factionLeft)
    card.upgrade_right_1_wktxt = format('[[File:%s_Upgrade_1_Right.png||link=]]', factionRight)
    card.upgrade_right_2_wktxt = format('[[File:%s_Upgrade_%s_Right.png||link=]]', factionRight, factionRight == 'Neutral' and '1' or '2')
    card.upgrade_right_3_wktxt = format('[[File:%s_Upgrade_%s_Right.png||link=]]', factionRight, factionRight == 'Neutral' and '1' or '3')
    card.charge_right_1_wktxt =
    factionRight == 'Neutral'
        and '[[File:Neutral_Charge_All.png||link=]]'
        or format('[[File:%s_Charge_1_Right.png||link=]]', factionRight)
    card.charge_right_2_wktxt =
    factionRight == 'Neutral'
        and '[[File:Neutral_Charge_All.png||link=]]'
        or format('[[File:%s_Charge_2_Right.png||link=]]', factionRight)
    card.charge_right_3_wktxt
    = factionRight == 'Neutral'
        and '[[File:Neutral_Charge_All.png||link=]]'
        or format('[[File:%s_Charge_3_Right.png||link=]]', factionRight)
    card.full_name = display_name
    card.display_name = display_name:gsub(' %(Lost Souls%)', ''):gsub(' %(Twilight%)', ''):gsub(' %(Superpig%)', '')
    local dataorbs = {}
    for k,v in ipairs(data.orbs or {}) do dataorbs[k] = v end
    card.orbs = dataorbs
    card.data_cost_attr = format('["%s"]', concat(data.power_cost, '","'))
    card.power_cost_wktxt = type(data.power_cost) == 'table'
        and data.power_cost[cardupgrade + 1]
        or data.power_cost
    local actualChargeRule = (promo
        and { data.charges or 4, 0, 0, 0 }
        or (type(data.charges) == table
        and data.charges
        or (charge_rules[data.charges or 4]) or charge_rules[4])
    )
    local actualCurrentCharge
    for i=0, applied_charges do
        actualCurrentCharge = (actualCurrentCharge or 0) + actualChargeRule[i+1] or 0
    end
    card.charges_wktxt = actualCurrentCharge
    card.data_charges_attr = format('["%s"]', concat(actualChargeRule or {}, '","'))
    card.squadsize = data.squadsize
    card.class = data.class
    card.weapon_type = data.weapon_type
    card.weapon_type_wktxt = (data.weapon_type and data.type ~= 'Spell')
        and format('[[File:Card_Icon_%s_Unit.png||link=]]', data.weapon_type) or ''
    
    card.counter = data.counter
    card.counter_wktxt = (data.weapon_type and data.weapon_type ~= 'Special' and data.type ~= 'Spell' and check_size[data.counter or 'Nope'])
        and format('[[File:Card_Icon_Unit_Size_%s.png|38px|link=]]', data.counter) or ''
    
    card.data_damage_attr = format('["%s"]', concat(data.damage, '","'))
    card.damage_wktxt = type(data.damage) == 'table' and data.damage[cardupgrade + 1] or data.damage
    card.unit_size = check_size[data.size or 'Nope'] and data.size or nil
    card.unit_size_wktxt = check_size[data.size or 'Nope'] and format('[[File:Card_Icon_Unit_Size_%s.png|38px|link=]]', data.size) or nil
    card.data_health_attr = format('["%s"]', concat(data.health, '","'))
    card.health_wktxt = type(data.health) == 'table' and data.health[cardupgrade + 1] or data.health
    card.edition = data.edition
    card.rarity = data.rarity

    card.abilities_node = data.abilities_done
    if not card.abilities_node then
        local abilities_node = mw.html.create()
        local ab_count = 0
        for k, v in ipairs(data.abilities or {}) do
            if ab_count < 4 and not v.hide_on_card then
            	ab_count = ab_count + 1
                local abil = mw.html.create('div')
                local avail = v.upgrade_availability
                if avail then
                	local s = ''
                	local from
                	for k,v in ipairs(avail) do
                		if v and not from then from = k-1 end
                		if v then s = s .. ' cv-A' .. (k-1) end
                	end
                    abil:addClass('cv-upgradeparts' .. s)
                    if from > cardupgrade then
                        abil:cssText('display: none;')
                    end
                end
                if v['type'] then
                	abil:addClass(format('cv-abil cv-abil-%s%s', string.lower(v['type']), v['affinity_dependency'] and '-'..string.lower(aff) or ''))
                else
                	abil:addClass('cv-abil-none')
                end
                abil:wikitext(v['name'])
                if not promo then
                    local a = 0
                    for i = 1, 3 do
                        if (v.upgrade_text and tostring(v.upgrade_text[i] or ''):len() > 0) or (type(v.cost) == 'table' and v.cost[i] ~= v.cost[i+1] or false) then
                            a = a + 1
                        end
                        if a ~= 0 then
                            abil:tag('span'):addClass(format('cv-upgradeparts cv-abil-upgrade-%s cv-A%s', a, i)):css(
                                'display',
                                (i == cardupgrade and '' or 'none')
                            )
                        end
                    end
                end
                abil:allDone()
                abilities_node:node(abil)
            end
        end
        card.abilities_node = abilities_node
    end

    return card.build()
end

function p.count(frame)
    local args = getArgs(frame)
    
    local i = 0
    local list = get.list()
    
    if args[1] ~= '' and args[2] ~= '' and args[1] ~= 'cardtype' then
        for _,k in ipairs(list) do
            if get[args[1]](k) == args[2] then
                i = i + 1
            end
        end
    else
        if (args[1] == 'cardtype' and args[2] == 'cards') or args[1] == nil or args[1] == '' then
            for _,k in ipairs(list) do
                if not k:find '(Promo)' then i = i + 1 end
            end
        elseif (args[1] == 'cardtype' and args[2] == 'promos') or args[1] == nil or args[1] == '' then
            for _,k in ipairs(list) do
                if k:find '(Promo)' then i = i + 1 end
            end
        end
    end
    
    return i
end

function p.custom_card(frame)
    local args = getArgs(frame)

    local name = args.name or args[1] or ''
    local link = args.link or ''

    if args.orbs then
        args.orbs = mw.text.split(args.orbs, '%s*,%s*')
        for k,v in ipairs(args.orbs or {}) do
            args.orbs[k] = v:gsub('^%l', string.upper)
        end
    end

    local res = args.orbs and get.factions(name, args.orbs) or {}
    local factionLR = res[1] and (res[1] ~= res[2] and res[1] .. res[2] or res[1]) or 'Blank'
    local factionLeft = res[1]
    local factionRight = res[2]

    if args.type then args.type = args.type:gsub('^%l', string.upper) end

    if args.edition then
        args.edition = args.edition:gsub('^%l', string.upper)
        if args.edition == 'Lost souls' then args.edition = 'Lost Souls' end
    end

    if args.rarity then
        args.rarity = args.rarity:gsub('^%l', string.upper)
        if args.rarity == 'Ultra rare' then args.rarity = 'Ultra Rare' end
    end

    if args.weapon_type then
        args.weapon_type = args.weapon_type:gsub('^%l', string.upper)
    end

    if string.lower(args.affinity or 'none') == 'none' then
        args.affinity = nil
    end
    if args.affinity then
        args.affinity = args.affinity:gsub('^%l', string.upper)
    end

    if args.counter then
        args.counter = string.upper(args.counter)
    end

    if args.size then
        args.size = string.upper(args.size)
    end

    local promo_txt, promo_class
    if type(args.upgrade) == 'string' then
        if args.upgrade:lower() == 'promo' then
            args.promo = true
            args.cardupgrade = 3
            args.applied_charges = 3
            promo_txt = format('[[File:Promo_Icon_%s.png||link=]]', factionLeft)
        end
        if args.upgrade:lower() == 'starter' then
            args.starter_card = true
            promo_class = 'cv-upgradeparts cv-A0'
            promo_txt = format('[[File:Starter_Icon_%s.png||link=]]', factionLeft)
        end
        if args.upgrade:lower() == 'temp'
            or args.upgrade:lower() == 'temporary'
            or args.upgrade:lower() == 'tome' then
            args.temporary_card = true
            args.cardupgrade = 3
            args.applied_charges = 3
            promo_txt = format('[[File:Temporary_Icon_%s.png||link=]]', factionLeft)
        end
    else
        args.cardupgrade = args.upgrade
    end

    local abilities_done = mw.html.create()
    for i = 1, 5 do
        local abil = mw.html.create('div')
        if args['ability_'..i..'_type'] and string.lower(args['ability_'..i..'_type']) ~= 'none' then
            abil:addClass(format('cv-abil cv-abil-%s%s',
            	string.lower(args['ability_'..i..'_type']),
            	(args['ability_'..i..'_affinity'] == true or string.lower(args['ability_'..i..'_affinity'] or '') == 'true') and ('-'..args.affinity) or ''))
        end
        abil:wikitext(args['ability_'..i..'_name'] or '')
        if tonumber(args['ability_'..i..'_upgrade'])
            and tonumber(args['ability_'..i..'_upgrade']) > 0
            and tonumber(args['ability_'..i..'_upgrade']) <= 3 then
            abil:wikitext(format('[[File:Card_Icon_Upgrade_Status_0%s.png||link=]]', args['ability_'..i..'_upgrade']))
        end

        abil:allDone()
        abilities_done:node(abil)

    end
    abilities_done:allDone()

    local check_size = {
        S = true,
        M = true,
        L = true,
        XL = true,
    }

    return CardClass{
        scaling = args.displayscaling or args.scaling,
        affinity = args.affinity,
        factionLR = factionLR,
        factionLeft = factionLeft,
        factionRight = factionRight,
        display_name = name,
        artwork_wktxt = format('[[File:%s||link=|320px]]', args.artwork),
        spell_background_wktxt = args.type == 'Spell' and format('[[File:Spell_Card_Overlay_%s.png||link=]]', factionLR) or nil,
        cardupgrade = args.cardupgrade,
        applied_charges = args.applied_charges,
        upgrade_left_1_wktxt = format('[[File:%s_Upgrade_1_Left.png||link=]]', factionLeft),
        upgrade_left_2_wktxt = format('[[File:%s_Upgrade_2_Left.png||link=]]', factionLeft),
        upgrade_left_3_wktxt = format('[[File:%s_Upgrade_3_Left.png||link=]]', factionLeft),
        charge_left_1_wktxt = factionLeft == 'Neutral' and '[[File:Neutral_Charge_All.png||link=]]' or format('[[File:%s_Charge_1_Left.png||link=]]', factionLeft),
        charge_left_2_wktxt = factionLeft == 'Neutral' and '[[File:Neutral_Charge_All.png||link=]]' or format('[[File:%s_Charge_2_Left.png||link=]]', factionLeft),
        charge_left_3_wktxt = factionLeft == 'Neutral' and '[[File:Neutral_Charge_All.png||link=]]' or format('[[File:%s_Charge_3_Left.png||link=]]', factionLeft),
        upgrade_right_1_wktxt = format('[[File:%s_Upgrade_1_Right.png||link=]]', factionRight),
        upgrade_right_2_wktxt = format('[[File:%s_Upgrade_%s_Right.png||link=]]', factionRight, factionRight == 'Neutral' and '1' or '2'),
        upgrade_right_3_wktxt = format('[[File:%s_Upgrade_%s_Right.png||link=]]', factionRight, factionRight == 'Neutral' and '1' or '3'),
        charge_right_1_wktxt = factionRight == 'Neutral' and '[[File:Neutral_Charge_All.png||link=]]' or format('[[File:%s_Charge_1_Right.png||link=]]', factionRight),
        charge_right_2_wktxt = factionRight == 'Neutral' and '[[File:Neutral_Charge_All.png||link=]]' or format('[[File:%s_Charge_2_Right.png||link=]]', factionRight),
        charge_right_3_wktxt = factionRight == 'Neutral' and '[[File:Neutral_Charge_All.png||link=]]' or format('[[File:%s_Charge_3_Right.png||link=]]', factionRight),
        promo_icon_wktxt = promo_txt,
        promo_icon_class = promo_class,
        orbs = args.orbs or {},
        power_cost_wktxt = args.power_cost,
        charges_wktxt = args.charges,
        unit_size_wktxt = check_size[args.size or 'Nope'] and format('[[File:Card_Icon_Unit_Size_%s.png||link=]]', args.size) or nil,
        squadsize = args.squadsize,
        class = args.class,
        damage_wktxt = args.damage,
        weapon_type_wktxt = (args.weapon_type and args.type ~= 'Spell') and format(
            '[[File:Card_Icon_%s%s.png||link=]]',
            args.weapon_type,
            (check_size[args.counter or 'Nope'] and args.weapon_type ~= 'Special') and format('_Unit_Countering_Size_%s', args.counter) or (args.weapon_type ~= 'Special' and '_Unit' or ''),
            link
        ) or ((not args.weapon_type and check_size[args.counter or 'Nope'] and args.type ~= 'Spell') and format('<span style="padding-left: 14px;">[[File:Card_Icon_Unit_Size_%s.png||link=]]</span>', args.counter)),
        health_wktxt = args.health,
        edition = args.edition,
        rarity = args.rarity,
        notooltip = true,
        abilities_node = abilities_done,
    }.build()
end

--- returns a list of cards formatted as a deck display
---@param frame table mw.frame object
---@return string deck as mw.html
---@public
function p.deck(frame)
    local args = getArgs(frame)

    local res = mw.html.create('div'):cssText('display:flex;flex-wrap:wrap;')
    if args.maxperrow then
        res:cssText(
            format(
                'max-width:100%%;width:calc(%spx * %s);',
                args.icononly and 82 * (tonumber(args.scaling) or tonumber(args.displayscaling) or 1) or 370 * (tonumber(args.scaling) or tonumber(args.displayscaling) or 0.23),
                args.maxperrow
            )
        )
    end
    for i = 1, 20 do
        local t = mw.text.split(args[i] or '', ';', true)
        if t[1] ~= '' then
            if args.icononly then
                res:node(
                    p.icon{
                        t[1],
                        size = format('%spx', (args.scaling or args.displayscaling or 1) * 80),
                        icononly = true,
                        nolink = args.nolink == true and true or false,
                    }
                )
            else
                res:node(
                    p.card{
                        t[1],
                        cardupgrade = tonumber(t[2] or 0),
                        applied_charges = tonumber(t[3] or 0),
                        scaling = args.scaling or args.displayscaling or '0.23',
                        nolink = args.nolink == true and true or false,
                    }
                )
            end
        end
    end

    return res
end

function p.for_each(frame)
	local args = getArgs(frame)
	local list = get.list()
	local result

	if args[1] == 'list_of_card_ids' then
		result = mw.html.create()

		for k,v in ipairs(list) do
			local id = get.card_id(v) or 0
			local ci  = p.icon{v}
			local id0 = tostring(id)
			local id1 = string.format("1%06d", id)
			local id2 = string.format("2%06d", id)
			local id3 = string.format("3%06d", id)

			result:tag('tr'):addClass('search-item'):attr('data-search', v..id0)
				:tag('td'):node(ci):done()
				:tag('td'):wikitext(id0):done()
				:tag('td'):wikitext(id1):done()
				:tag('td'):wikitext(id2):done()
				:tag('td'):wikitext(id3):done()
			:done()
		end
	elseif args[1] == 'list_of_card_upgrade_drop_locations' then
		result = mw.html.create()

		for k,v in ipairs(list) do
			local ci = p.icon{v}
			local drops = get.upgrade_locations(v)
			local ed = get.edition(v)
			local ra = get.rarity(v)
			local raso = 0
			if ra == 'Common' then raso = 1 elseif ra == 'Uncommon' then raso = 2 elseif ra == 'Rare' then raso = 3 elseif ra == 'Ultra Rare' then raso = 4 end
			if drops then
				local d1 = drops[1] or ''
				local d2 = drops[2] or ''
				local d3 = drops[3] or ''
				local dd1 = drops[1] and format('[[File:%s_Minimap.jpg|20px|link=%s]] [[%s]]', d1, d1, d1) or ''
				local dd2 = drops[2] and format('[[File:%s_Minimap.jpg|20px|link=%s]] [[%s]]', d2, d2, d2) or ''
				local dd3 = drops[3] and format('[[File:%s_Minimap.jpg|20px|link=%s]] [[%s]]', d3, d3, d3) or ''

				local search = v..d1
				if d2 ~= d1 then search = search..d2 end
				if d3 ~= d2 and d3 ~= d1 then search = search..d3 end

				result:tag('tr'):addClass('search-item'):attr('data-search', search)
					:tag('td'):node(ci):done()
					:tag('td'):attr('data-sort-value', raso):attr('title', ra):wikitext(format('[[File:Edition Icon %s %s.png|x22px|link=]]', ed, ra)):done()
					:tag('td'):wikitext(dd1):done()
					:tag('td'):wikitext(dd2):done()
					:tag('td'):wikitext(dd3):done()
				:done()
			end
		end

	elseif args[1] == 'list_of_booster_pack_contents' then
		result = mw.html.create()
		local counter = { [1] = 0, [2] = 0, [3] = 0, [4] = 0, [5] = 0, [6] = 0, [7] = 0, [8] = 0, [9] = 0, [10] = 0, [11] = 0 }

		for k,v in ipairs(list) do
			local name = v:gsub(' %(Fire%)', ''):gsub(' %(Frost%)', ''):gsub(' %(Nature%)', ''):gsub(' %(Shadow%)', '')
			local ci = p.icon{[1] = v, ['hide_affinity'] = true}
			local packs = get.boosters(v)

			local p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10 = 'X','X','X','X','X','X','X','X','X','X','X'
			local c0,c1,c2,c3,c4,c5,c6,c7,c8,c9,c10 = 'nay','nay','nay','nay','nay','nay','nay','nay','nay','nay','nay'
			local search = name

			if packs[1] then
				p0 = '✔'
				c0 = 'yea'
				counter[1] = counter[1] + 1
			else
				search = search..'None'
			end

			for _,pack in ipairs(packs) do
				search = search .. pack
				if pack == 'Fire' then
					p1 = '✔'
					c1 = 'yea'
					counter[2] = counter[2] + 1
				elseif pack == 'Frost' then
					p2 = '✔'
					c2 = 'yea'
					counter[3] = counter[3] + 1
				elseif pack == 'Nature' then
					p3 = '✔'
					c3 = 'yea'
					counter[4] = counter[4] + 1
				elseif pack == 'Shadow' then
					p4 = '✔'
					c4 = 'yea'
					counter[5] = counter[5] + 1
				elseif pack == 'Amii' then
					p5 = '✔'
					c5 = 'yea'
					counter[6] = counter[6] + 1
				elseif pack == 'Bandits' then
					p6 = '✔'
					c6 = 'yea'
					counter[7] = counter[7] + 1
				elseif pack == 'Fire/Frost' then
					p7 = '✔'
					c7 = 'yea'
					counter[8] = counter[8] + 1
				elseif pack == 'Lost Souls' then
					p8 = '✔'
					c8 = 'yea'
					counter[9] = counter[9] + 1
				elseif pack == 'Stonekin' then
					p9 = '✔'
					c9 = 'yea'
					counter[10] = counter[10] + 1
				elseif pack == 'Twilight' then
					p10 = '✔'
					c10 = 'yea'
					counter[11] = counter[11] + 1
				end
			end

			result:tag('tr'):addClass('search-item'):attr('data-search', search)
				:tag('td'):node(ci):done()
				:tag('td'):addClass(c0):wikitext(p0):done()
				:tag('td'):addClass(c1):wikitext(p1):done()
				:tag('td'):addClass(c2):wikitext(p2):done()
				:tag('td'):addClass(c3):wikitext(p3):done()
				:tag('td'):addClass(c4):wikitext(p4):done()
				:tag('td'):addClass(c5):wikitext(p5):done()
				:tag('td'):addClass(c6):wikitext(p6):done()
				:tag('td'):addClass(c7):wikitext(p7):done()
				:tag('td'):addClass(c8):wikitext(p8):done()
				:tag('td'):addClass(c9):wikitext(p9):done()
				:tag('td'):addClass(c10):wikitext(p10):done()
			:done()
		end
		result = mw.html.create():tag('tr'):addClass('tr-force-show')
			:tag('th'):cssText('width: 30%; text-align:left; background-color: #092949'):wikitext('Card Name'):done()
			:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:General Booster Pack HD.png|40px|link=]]'):attr('title', 'General Booster Pack (Card Pool: '..counter[1]..')'):done()
			:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Fire Booster Pack HD.png|40px|link=]]'):attr('title', 'Fire Booster Pack (Card Pool: '..counter[2]..')'):done()
			:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Frost Booster Pack HD.png|40px|link=]]'):attr('title', 'Frost Booster Pack (Card Pool: '..counter[3]..')'):done()
			:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Nature Booster Pack HD.png|40px|link=]]'):attr('title', 'Nature Booster Pack (Card Pool: '..counter[4]..')'):done()
			:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Shadow Booster Pack HD.png|40px|link=]]'):attr('title', 'Shadow Booster Pack (Card Pool: '..counter[5]..')'):done()
			:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Amii Booster Pack HD.png|40px|link=]]'):attr('title', 'Amii Booster Pack (Card Pool: '..counter[6]..')'):done()
			:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Bandits Booster Pack HD.png|40px|link=]]'):attr('title', 'Bandits Booster Pack (Card Pool: '..counter[7]..')'):done()
			:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Fire Frost Booster Pack HD.png|40px|link=]]'):attr('title', 'Fire Frost Booster Pack (Card Pool: '..counter[8]..')'):done()
			:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Lost Souls Booster Pack HD.png|40px|link=]]'):attr('title', 'Lost Souls Booster Pack (Card Pool: '..counter[9]..')'):done()
			:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Stonekin Booster Pack HD.png|40px|link=]]'):attr('title', 'Stonekin Booster Pack (Card Pool: '..counter[10]..')'):done()
			:tag('th'):cssText('width: 6%; background-color: #092949; padding: 0 6px; text-align: center'):wikitext('[[File:Twilight Booster Pack HD.png|40px|link=]]'):attr('title', 'Twilight Booster Pack (Card Pool: '..counter[11]..')'):done()
		:done():node(result:done()):done()
	end

	return result
end

--- returns values from Card/data for a card
---@param frame table mw.frame object
---@return string value from card
---@public
function p.get(frame)
    local args = getArgs(frame)
    
    local cardname = (args[2]:find('affini')) and args[1] or get.verified_name(args[1])
    local val = args[2]
    local returntype = args[3]
    -- 'return' one of: array, count, 'index' (eg 1).
    -- nil returns default (tables as array (wikitext-string separated by ';'), single value as single value)

    if not get[val] then
        return format('No getter for %s', val)
    end

    -- if not get['card'](cardname) then return format('Card %s not found (fn 'get')', cardname) end
    --if not get[val](cardname, cardtype) then return format('Value %s not found for %s (%s)', val, cardname, get[val](cardname, cardtype)) end

    local returnval = cardname == 'list' and get.list() or get[val](cardname)

    if returnval == nil then
        return format('Card %s or value %s not found (fn "get")', cardname, val)
    end

    if returntype == 'count' then
        local i = 0
        for _ in pairs(returnval or {}) do
            i = i + 1
        end
        return i
    elseif returntype == 'array' then
        if not type(returnval) == 'table' then
            return format('Value %s not an array (lua table)', val)
        end
        return concat(returnval, '; ')
    elseif type(returntype) == 'number' and returntype == math.floor(returntype) and returntype < 10 then
        return returnval[tonumber(returntype)]
    else
        -- Something's not workin' here
        if type(returnval) == 'table' then
            return concat(returnval, '; ')
        elseif type(returnval) == 'string' or type(returnval) == 'number' or type(returnval) == 'boolean' then
            return returnval
        else
            return 'Something went wrong. Check: card, value, return and Module:Card/data'
        end
    end
end

--- check if card has value
---@param frame table mw.frame object
---@return boolean true if card/data contains value for card; else nothing
---@public
function p.has(frame)
    local args = getArgs(frame)

    if has[args[2]] and has[args[2]](args[1]) then
        return true
    end
    local cardname = args[2]:find('affini') and args[1] or get.verified_name(args[1])
    if get[args[2]] and get[args[2]](cardname) then
        return true
    end
end

--- returns the content for Template:Card_icon
---@param frame table mw.frame object
---@return string icon_text or icon as mw.html
---@public
function p.icon(frame)
    local args = getArgs(frame)

    local n, v = string.find(args[1], ' (', 1, true)
    local name, _ = (n == nil and args[1] or string.sub(args[1], 1, (n or 0) - 1)), nil
    local var, _ = string.gsub(string.sub(args[1], (v or 0) + 1), '%)', '')
    
    local ran, val_or_err = pcall(get.verified_name, format('%s%s', name, var == 'Twilight' and ' (Twilight)' or var == 'Lost Souls' and ' (Lost Souls)' or var == 'Superpig' and ' (Superpig)' or ''))
    if not ran then
        return format('<span style="cursor:help; border-bottom:1px dotted; color: red;" title="Error: Card &quot;%s&quot; not found">Error</span>', name or '')
    end

    local firstfile = ''
    if var == 'Promo' then
        firstfile = '_(Promo)'
    elseif var == 'Twilight' then
        firstfile = '_(Twilight)'
    elseif var == 'Lost Souls' then
        firstfile = '_(Lost_Souls)'
    elseif var == 'Superpig' then
        firstfile = '_(Superpig)'
    end
    local linkdest = args.nolink == true and '' or (name == 'Mo' and 'Mo#Card' or (var ~= 'Promo' and format('%s%s', name, firstfile) or name))

    local size = math.floor(string.gsub(args.size or '20px', 'px', '') + 0.5)

    local aff = false
    if not args['hide_affinity'] and (var == 'Fire' or var == 'Frost' or var == 'Nature' or var == 'Shadow') then
        aff = true
    end

    local linktext = name
    if args[2] ~= nil and args[2] ~= '' then
        linktext = args[2]
    elseif var ~= name then
        linktext = format('%s (%s)', linktext, var)
    end

    local span = mw.html.create('span'):addClass('card-icon custom-tooltip'):attr('data-tt-type', 'card'):attr('data-1', args[1])
    	
    local span_icon = mw.html.create('span'):wikitext(format('[[File:%s%s_Card_Icon.png|%spx|border|link=%s]]', name, firstfile, size, linkdest))

    if aff then
    	span_icon:tag('span'):wikitext(format('[[File:Affinity_Orb_%s.png|%spx|link=%s]]', var, math.floor(size * 0.4 + 0.5), linkdest))
	end
	
	span:node(span_icon:allDone())

    if not args.icononly then
        span:wikitext(format(' [[%s|%s]]', linkdest, linktext))
    end

    return span:done()
end

function p.infobox(frame)
    local args = getArgs(frame)

    local vname, _, _ = get.verified_name(args[1])

    return frame:expandTemplate{
        title = 'Infobox card',
        args = {
            name         = args[1],
            artwork      = format('[[File:%s_Card_Artwork.png|link=]]', args[1]),
            card_type    = get.type(vname) or nil,
            faction      = get.faction(vname) == 'Neutral' and 'None (Neutral / Legendary)' or get.faction(vname),
            class        = get.class(vname) or nil,
            has_normal   = has.normal(args[1]) or nil,
            has_promo    = has.promo(args[1]) or nil,
            affinity1    = has.affinities(args[1]) and get.affinity_variants(args[1])[1] or nil,
            affinity2    = has.affinities(args[1]) and get.affinity_variants(args[1])[2] or nil,
            power_cost   = type(get.power_cost(vname)) == 'table' and frame:expandTemplate{title = 'pu', args={get.power_cost(vname)[1], get.power_cost(vname)[2], get.power_cost(vname)[3], get.power_cost(vname)[4]}} or get.power_cost(vname),
            orbs         = get.orbs(vname) and concat(get.orbs(vname), ', ') or nil,
            charges      = has.nonpromo(vname) and frame:expandTemplate{title = 'pc', args={get.charges(vname)}} or get.charges(vname),
            squadsize    = get.squadsize(vname),
            damage_bonus = get.counter(vname),
            damage       = type(get.damage(vname)) == 'table' and frame:expandTemplate{title = 'pu', args={get.damage(vname)[1], get.damage(vname)[2], get.damage(vname)[3], get.damage(vname)[4]}} or get.damage(vname),
            size         = get.size(vname),
            gender       = get.gender(vname),
            booster      = concat(get.boosters(vname), ', '),
            card_id      = get.card_id(args[1]),
            card_id_fire = get.card_id(args[1]..' (Fire)'),
            card_id_frost= get.card_id(args[1]..' (Frost)'),
            card_id_nature= get.card_id(args[1]..' (Nature)'),
            card_id_shadow= get.card_id(args[1]..' (Shadow)'),
            card_id_promo= get.card_id(args[1]..' (Promo)'),
            movement_speed = get.type(vname) == 'Unit' and (type(get.movement_speed(vname)) == 'table' and frame:expandTemplate{title = 'pu', args={get.movement_speed(vname)[1], get.movement_speed(vname)[2], get.movement_speed(vname)[3], get.movement_speed(vname)[4]}} or get.movement_speed(vname)) or nil,
            construction_time = get.type(vname) == 'Building' and (type(get.construction_time(vname)) == 'table' and frame:expandTemplate{title = 'pu', args={get.construction_time(vname)[1], get.construction_time(vname)[2], get.construction_time(vname)[3], get.construction_time(vname)[4]}} or get.construction_time(vname)) or nil,
            health       = type(get.health(vname)) == 'table' and frame:expandTemplate{title = 'pu', args={get.health(vname)[1], get.health(vname)[2], get.health(vname)[3], get.health(vname)[4]}} or get.health(vname),
            edition      = get.edition(vname),
            rarity       = get.rarity(vname)
        }
    }
end

--- returns a filtered list of cards
---@param frame table mw.frame object
---@return string list of cards as mw.html
---@public
function p.list(frame)
    local args = getArgs(frame)

    local list = get.list()
    local cardcount = 0
    local div = args.ci == true and mw.html.create() or mw.html.create('div'):addClass('list-of-cards'..(args.filter and ' collapsed' or '')):attr('id', args.filter and 'card-grid' or ''):css{
        ['display'] = 'flex',
        ['flex-wrap'] = 'wrap',
        ['justify-content'] = 'center',
        ['overflow'] = 'hidden',
        ['max-height'] = args.filter and '500px' or nil,
        ['margin'] = '0 -1px',
    }

    if args[1] ~= '' and args[2] ~= '' and args[1] ~= 'cardtype' then
        for i,k in ipairs(list) do
            if get[args[1]](k) == args[2] then
            	cardcount = cardcount + 1
                if args.ci == true then
                    div:wikitext('\n* '):node(p.icon{k})
                else
                    div:node(p.card{k, filter=args.filter, scaling=0.5487})
                end
            end
        end
    else
        if (args[1] == 'cardtype' and args[2] == 'all') or args[1] == nil or args[1] == '' then
            for _,k in ipairs(list) do
            	cardcount = cardcount + 1
                div:node(p.card{k, filter=args.filter, scaling=0.5487})
            end
        elseif (args[1] == 'cardtype' and args[2] == 'cards') or args[1] == nil or args[1] == '' then
            for _,k in ipairs(list) do
                if not k:find '(Promo)' then
                	cardcount = cardcount + 1
                	div:node(p.card{k, filter=args.filter, scaling=0.5487})
                end
            end
        elseif (args[1] == 'cardtype' and args[2] == 'promos') or args[1] == nil or args[1] == '' then
            for _,k in ipairs(list) do
                if k:find '(Promo)' then
                	cardcount = cardcount + 1
                	div:node(p.card{k, filter=args.filter, scaling=0.5487})
                end
            end
        end
    end
    
    if args.filter then
    	local filterContainer = mw.html.create('div'):attr('id', 'grid-filter-container'):cssText('background-color:#060e1f;padding:0.5rem')
    	local arg1 = tostring(args[1] or ''):lower()
    	local arg2 = tostring(args[2] or ''):lower()
    	
		filterContainer:addClass('hide-'..arg1)
		
    	if arg1 == 'faction' then
    		if arg2 == 'fire' then
    			filterContainer:addClass('hide-frost hide-nature hide-shadow')
    		elseif arg2 == 'frost' then
    			filterContainer:addClass('hide-fire hide-nature hide-shadow')
			elseif arg2 == 'nature' then
				filterContainer:addClass('hide-fire hide-frost hide-shadow')
			elseif arg2 == 'shadow' then
				filterContainer:addClass('hide-fire hide-frost hide-nature')
			elseif arg2 == 'neutral' or arg2 == 'legendary' then
				filterContainer:addClass('hide-fire hide-frost hide-nature hide-shadow')
			elseif arg2 == 'amii' then
    			filterContainer:addClass('hide-fire hide-frost')
			elseif arg2 == 'bandits' then
				filterContainer:addClass('hide-nature hide-shadow')
			elseif arg2 == 'lost souls' then
				filterContainer:addClass('hide-fire hide-nature')
			elseif arg2 == 'stonekin' then
				filterContainer:addClass('hide-fire hide-shadow')
			elseif arg2 == 'twilight' then
    			filterContainer:addClass('hide-frost hide-shadow')
			end
    	end
    	
    	div:attr('data-card-count', tostring(cardcount))
    		:tag('div')
    		:attr('id', 'grid-matches')
    		:attr('title', 'If applicable, this count includes affinity and promo variants.')
    		:wikitext((cardcount)..' matching cards')
    		:allDone()
    		
    	return filterContainer
			:tag('div'):attr('id', 'grid-filter-buttons')
			:tag('div'):attr('id', 'grid-filter-orbs-wrapper')
			:tag('div'):cssText('font-size: 15px; font-weight: 700'):wikitext('Orbs'):done()
			:tag('div'):attr('id', 'grid-filter-orbs'):cssText('display: flex'):done()
			:done()
			:tag('div'):attr('id', 'grid-filter-orbsamount-wrapper')
			:tag('div'):cssText('font-size: 15px; font-weight: 700'):wikitext('Number of Orbs'):done()
			:tag('div'):attr('id', 'grid-filter-orbsamount'):cssText('display: flex'):done()
			:done()
			:tag('div'):attr('id', 'grid-filter-type-wrapper')
			:tag('div'):cssText('font-size: 15px; font-weight: 700'):wikitext('Type'):done()
			:tag('div'):attr('id', 'grid-filter-type'):cssText('display: flex'):done()
			:done()
			:tag('div'):attr('id', 'grid-filter-rarity-wrapper')
			:tag('div'):cssText('font-size: 15px; font-weight: 700'):wikitext('Rarity'):done()
			:tag('div'):attr('id', 'grid-filter-rarity'):cssText('display: flex'):done()
			:done()
			:done()
			:tag('div'):attr('id', 'grid-filter-dropdowns')
			:tag('div'):attr('id', 'grid-filter-search'):attr('data-placeholder', 'Search...'):done()
			:tag('div'):attr('id', 'grid-filter-affinities'):done()
			:tag('div'):attr('id', 'grid-filter-edition'):done()
			:tag('div'):attr('id', 'grid-filter-faction'):done()
			:tag('div'):attr('id', 'grid-filter-counter'):done()
			:tag('div'):attr('id', 'grid-filter-size'):done()
			:tag('div'):attr('id', 'grid-filter-weapontype'):done()
			:tag('div'):attr('id', 'grid-filter-special'):done()
			:tag('div'):attr('id', 'grid-filter-sort'):done()
			:tag('div'):attr('id', 'grid-filter-reset'):done()
			:done()
			:node(div)
			:tag('div'):attr('id', 'grid-collapse')
			:allDone()
             
    else
    	return div:done()
    end
end

function p.map_drops(frame)
    local args = getArgs(frame)

    local res = get.map_drops(args[1], args[2])
    if args[3] == 'list' then
        local res2 = mw.html.create('ul')
        for i=1, #res do
            res2:tag('li'):node(p.icon{res[i]}):done()
        end
        return res2
    elseif args[3] == 'count' then
        return #res
    else
        return concat(res, ';')
    end
end

function p.navigation(frame)
    local list = get.list()
    local cardcount = 0
    local previous_card
    local height = '230px'
    if frame:getParent()['args'] and frame:getParent()['args']['mainpage'] then
        height = '400px'
    end
    local grid = mw.html.create('div'):attr('id', 'card-grid'):css{['max-height'] = height}:addClass('lazyimg-wrapper collapsed')

    for k,v in ipairs(list) do
    	cardcount = k
        local name, _ = v:gsub(' %(Fire%)', ''):gsub(' %(Frost%)', ''):gsub(' %(Nature%)', ''):gsub(' %(Shadow%)', ''):gsub(' %(Promo%)', ''):gsub(' %(Superpig%)', '')

        if name ~= previous_card then
            local data_orbs = get.orbs(v)
            local orbs = { data_orbs[1], data_orbs[2], data_orbs[3], data_orbs[4] }
            local non_legendary = (function() for _,v in ipairs(orbs) do if v and v~='Neutral' then return true end end end)()
            
            local affinities = get.affinity_variants(name) or {}
            if affinities[1] then
            	affinities[#affinities+1] = 'All'
            else
            	affinities[1] = 'None'
            end

            local cost = get.power_cost(v)
            local faction = get.faction(v)
            local faction = faction == 'Neutral' and 'Legendary' or faction

            grid:tag('span')
                :addClass('grid-icon custom-tooltip')
                :attr('data-tt-type', 'card')
                :attr('data-1', name)
                :attr('data-search', name)
                :attr('data-rarity', get.rarity(v))
                :attr('data-cost', type(cost) == 'table' and cost[1] or cost or 0)
                :attr('data-edition', get.edition(v))
                :attr('data-faction', faction)
                :attr('data-counter', get.counter(v))
                :attr('data-weapontype', get.weapon_type(v))
                :attr('data-special', (has.promo(name) and 'Promo' or '') ..','.. (has.starter_card(name) and 'Starter' or '')..','..(has.normal(name) and 'Normal' or '')..','..(non_legendary and 'Non-Legendary' or ''))
                :attr('data-orbs', concat(orbs, ','))
                :attr('data-orbsamount', #orbs)
                :attr('data-affinities', concat(affinities, ','))
                :attr('data-size', get.size(v))
                :attr('data-type', get.type(v))
                :wikitext(format('[[File:%s_Card_Icon.png|56px|alt=%s|link=%s]]', name, name, name))
                :done()
        end

        previous_card = name
    end
    
    grid:attr('data-card-count', tostring(cardcount))
    	:tag('div')
    		:attr('id', 'grid-matches')
    		:attr('title', 'If applicable, this count includes affinity and promo variants.')
    		:wikitext((cardcount - 1)..' matching cards')-- minus QueekQueek (Superpig)
    		:allDone()

    return mw.html.create('div'):attr('id', 'grid-filter-container')
             :tag('div'):attr('id', 'grid-filter-buttons')
             :tag('div')
             :tag('div'):cssText('font-size: 15px; font-weight: 700'):wikitext('Orbs'):done()
             :tag('div'):attr('id', 'grid-filter-orbs'):cssText('display: flex'):done()
             :done()
             :tag('div')
             :tag('div'):cssText('font-size: 15px; font-weight: 700'):wikitext('Number of Orbs'):done()
             :tag('div'):attr('id', 'grid-filter-orbsamount'):cssText('display: flex'):done()
             :done()
             :tag('div')
             :tag('div'):cssText('font-size: 15px; font-weight: 700'):wikitext('Type'):done()
             :tag('div'):attr('id', 'grid-filter-type'):cssText('display: flex'):done()
             :done()
             :tag('div')
             :tag('div'):cssText('font-size: 15px; font-weight: 700'):wikitext('Rarity'):done()
             :tag('div'):attr('id', 'grid-filter-rarity'):cssText('display: flex'):done()
             :done()
             :done()
             :tag('div'):attr('id', 'grid-filter-dropdowns')
             :tag('div'):attr('id', 'grid-filter-search'):attr('data-placeholder', 'Search...'):done()
             :tag('div'):attr('id', 'grid-filter-affinities'):done()
             :tag('div'):attr('id', 'grid-filter-edition'):done()
             :tag('div'):attr('id', 'grid-filter-faction'):done()
             :tag('div'):attr('id', 'grid-filter-counter'):done()
             :tag('div'):attr('id', 'grid-filter-size'):done()
             :tag('div'):attr('id', 'grid-filter-weapontype'):done()
             :tag('div'):attr('id', 'grid-filter-special'):done()
             :tag('div'):attr('id', 'grid-filter-sort'):done()
             :tag('div'):attr('id', 'grid-filter-reset'):done()
             :done()
             :node(grid)
             :tag('div'):attr('id', 'grid-collapse')
             :allDone()
end

function p.progression_charges(frame)
    local args = getArgs(frame)
    if not args.index and not args[1] then return 'Missing max charge value' end
    local rule = chargeRules(tonumber(args.index or args[1]))
    if not rule then return 'Value '..(args.index or args[1])..' not found in ruleset'  end
    return frame:expandTemplate{title = 'p', args = {
        [1] = rule[1],
        [2] = 'No extra charges applied',
        [3] = rule[1] + rule[2],
        [4] = '1 extra charge applied',
        [5] = rule[1] + rule[2] + rule[3],
        [6] = '2 extra charges applied',
        [7] = rule[1] + rule[2] + rule[3] + rule[4],
        [8] = '3 extra charges applied',
        texttip = 'true'
    }}
end

function p.upgrade(frame)
	local args = getArgs(frame)
	local upgrade = args.upgrade or args.cardupgrade or 0
	if upgrade == 0 or upgrade > 3 then return 'upgrade needs to be 1, 2 or 3' end
	local name = args.name or args[1] or 'Name missing'
    local display_name, aff, promo = verifyName(args.name or args[1] or 'Name missing')

    local res = get.orbs(name) and get.factions(name, get.orbs(name)) or nil
    local factionLR = res and (res[1] ~= res[2] and res[1] .. res[2] or res[1]) or 'Blank'
    local factionLeft = res and res[1] or nil
    local factionRight = res and res[2] or nil

    local description = mw.html.create('div')

    local general = get.general_upgrade_text(name)
    if general and (general[upgrade] or '') ~= '' then
    	description:tag('div'):cssText('margin-left:5px;margin-right:10px;line-height:25px;white-space:pre-line'):wikitext(general[upgrade]):done()
    end
    
    local abilities = get.abilities(name)
    local has_autocast = false
    for _,v in ipairs(abilities) do
    	if v.type == 'Autocast' then has_autocast = true end
    end

    local cost, dmg, hp = get.power_cost(name), get.damage(name), get.health(name)

    if type(cost) == 'table' and cost[upgrade] ~= cost[upgrade+1] then
    	description:tag('div'):cssText('margin-left:5px;margin-right:10px;line-height:25px'):wikitext((cost[upgrade+1] > cost[upgrade] and '+' or '-') .. math.abs(cost[upgrade+1] - cost[upgrade]) .. ' power cost'):done()
    end
    if not has_autocast and type(dmg) == 'table' and dmg[upgrade] ~= dmg[upgrade+1] then
    	local s = get.squadsize(name) or 1
    	local u = s ~= 1 and (tostring(math.abs(dmg[upgrade+1] - dmg[upgrade]) / s) .. 'x' .. tostring(s)) or math.abs(dmg[upgrade+1] - dmg[upgrade])
    	description:tag('div'):cssText('margin-left:5px;margin-right:10px;line-height:25px'):wikitext('Damage ' .. (dmg[upgrade+1] > dmg[upgrade] and '+' or '-') .. u):done()
    end
    if type(hp) == 'table' and hp[upgrade] ~= hp[upgrade+1] then
    	local s = get.squadsize(name) or 1
    	local u = s ~= 1 and (tostring(math.abs(hp[upgrade+1] - hp[upgrade]) / s) .. 'x' .. tostring(s)) or math.abs(hp[upgrade+1] - hp[upgrade])
    	description:tag('div'):cssText('margin-left:5px;margin-right:10px;line-height:25px'):wikitext('Lifepoints ' .. (hp[upgrade+1] > hp[upgrade] and '+' or '-') .. u):done()
    end

    for _,v in ipairs(abilities) do

    	local from, to = 0, 9
    	local at = v.upgrade_availability or {}
		if at[4] then from = 3 end
		if at[3] then from = 2 end
		if at[2] then from = 1 end

		if at[3] and not at[4] then to = 3 end
		if at[2] and not at[3] then to = 2 end
		if at[1] and not at[2] then to = 1 end

		local learned = from > 0 and upgrade == from
		local unlearned = upgrade == to

		local cost_change
		if not learned and not unlearned and type(v.cost) == 'table' then
			if v.cost[upgrade] ~= v.cost[upgrade+1] then
				cost_change = (v.cost[upgrade+1] < v.cost[upgrade] and '-' or '+') .. math.abs(v.cost[upgrade+1] - v.cost[upgrade])
			end
		end

    	if (v.upgrade_text and (v.upgrade_text[upgrade] or '') ~= '') or learned or unlearned or cost_change then
    		local abil = mw.html.create('div'):cssText('line-height:25px')
    		local upgrade_count = 0
    		local cost = v.cost
    		if type(v.upgrade_text) == 'table' or type(cost) == 'table' then
	    		if upgrade >= 1 and ((type(v.upgrade_text) == 'table' and (v.upgrade_text[1] or '') ~= '') or (type(cost) == 'table' and cost[1] ~= cost[2])) then
	    			upgrade_count = upgrade_count + 1
	    		end
	    		if upgrade >= 2 and ((type(v.upgrade_text) == 'table' and (v.upgrade_text[2] or '') ~= '') or (type(cost) == 'table' and cost[2] ~= cost[3])) then
	    			upgrade_count = upgrade_count + 1
	    		end
	    		if upgrade >= 3 and ((type(v.upgrade_text) == 'table' and (v.upgrade_text[3] or '') ~= '') or (type(cost) == 'table' and cost[3] ~= cost[4])) then
	    			upgrade_count = upgrade_count + 1
	    		end
	    	end

    		if v.type then
            	abil:addClass(format('cv-abil cv-abil-%s%s', string.lower(v.type), v.affinity_dependency and '-'..string.lower(aff) or ''))
            else
            	abil:addClass('cv-abil-none')
            end
            abil:wikitext(v.name)
            if from < upgrade and not (learned or unlearned) then
            	abil:tag('span')
            		:cssText('margin-top:3px')
		            :addClass(format('cv-upgradeparts cv-abil-upgrade-%s', upgrade_count))
		            :done()
	        end

	        description:node(abil:done())

            if learned or unlearned then
            	description:tag('div'):cssText('margin-left:5px;margin-right:10px;line-height:25px'):wikitext((learned and '+' or '-')..v.name):done()
            end
            
            if cost_change then
    			description:tag('div'):cssText('margin-left:5px;margin-right:10px;line-height:25px'):wikitext(cost_change .. ' power cost'):done()
    		end

    		if v.upgrade_text and (v.upgrade_text[upgrade] or '') ~= '' then
    			description:tag('div'):cssText('margin-left:5px;margin-right:10px;line-height:25px;white-space:pre-line'):wikitext(v.upgrade_text[upgrade]):done()
    		end
    	end
    end

	return CardClass{
		is_upgrade = true,
		scaling = args.scaling or args.displayscaling or nil,
		--TODO: notooltip = args.notooltip or false,
		notooltip = true,
		link = (args.nolink or link == '') and nil or display_name,
		full_name = display_name,
		display_name = display_name:gsub(' %(Lost Souls%)', ''):gsub(' %(Twilight%)', ''):gsub(' %(Superpig%)', ''),
		artwork_wktxt = format('[[File:%s_Card_Artwork.png||link=]]', display_name),
		factionLR = factionLR,
		factionLeft = factionLeft,
		factionRight = factionRight,
		affinity = aff,
		abilities_node = description:allDone(),
		cardupgrade = upgrade,
		upgrade_left_1_wktxt = format('[[File:%s_Upgrade_1_Left.png||link=]]', factionLeft),
    	upgrade_left_2_wktxt = format('[[File:%s_Upgrade_2_Left.png||link=]]', factionLeft),
    	upgrade_left_3_wktxt = format('[[File:%s_Upgrade_3_Left.png||link=]]', factionLeft),
    	upgrade_right_1_wktxt = format('[[File:%s_Upgrade_1_Right.png||link=]]', factionRight),
	    upgrade_right_2_wktxt = format('[[File:%s_Upgrade_%s_Right.png||link=]]', factionRight, factionRight == 'Neutral' and '1' or '2'),
	    upgrade_right_3_wktxt = format('[[File:%s_Upgrade_%s_Right.png||link=]]', factionRight, factionRight == 'Neutral' and '1' or '3'),
	}.build()
end

return p