Skylords Reborn
Advertisement

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 ErrorCard(err, scaling)
	return require("Module:Card/class")({
		scaling = scaling,
		display_name = "ERROR",
		artwork_wktxt = "[[File:Quests_warning.png||link=|320px]]",
		abilities_node = mw.html.create("div"):cssText("width: 320px;line-height: 32px;"):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

local function general_desc(key)
	local d = get.description(key)

	if type(d) == "string" and d ~= "" then
		return format('<div class="ability">%s</div>', d)
	end
	if type(d) == "table" and lib.length(d) > 0 then
		return format('<div class="ability">%s</div>', lib.p(getProgressionArgs(d[1], d[2], d[3], d[4])))
	end

	return ""
end

function p.ability(frame)
	local args = getArgs(frame)
	if not (args[1] and args[2]) then
		return "Invalid Input"
	end

	local getAbInfo = function(v, affinity, promo)
		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 %s Ability Icon.png", args[2], promo and " (Promo)" or "", v.name)

		return frame:expandTemplate({
			title = "Ability",
			args = {
				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,
				description = frame:preprocess(text or ''), --TODO: remove preprocess once fandom gets their issues resolved
				card = args[2],
			},
		})
	end

	local exts = { "", " (Fire)", " (Frost)", " (Nature)", " (Shadow)", " (Lost Souls)", " (Twilight)", " (Superpig)" }
	local affs = { nil, "Fire", "Frost", "Nature", "Shadow" }
	local abilities = {}

	if string.find(args[2], " (Promo)", 1, true) then
		exts = { " (Promo)" }
		affs = { get.affinity(args[2] .. " (Promo)") }
	end

	-- iterate over cards
	for k, v in ipairs(exts) do
		-- iterate over abilities
		for _, vv in ipairs(get.abilities(exts[1] == " (Promo)" and args[2] or args[2] .. v) or {}) do
			if vv.name == args[1] then
				return getAbInfo(vv, affs[k], k == 6 or string.find(args[2], " (Promo)", 1, true) ~= nil)
			end
		end
	end

	return format('Ability "%s" for "%s" not found.', args[1], args[2])
end

function p.abilities(frame)
	local args = getArgs(frame)
	local tagArgs = {}
	local getInfo = function(aData, affinity, promo, special)
		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
			if text == "" then
				text = nil
			end

			local icon_tabber
			local icon_name = format(
				"%s%s %s Ability Icon.png",
				args.name or args[1],
				promo and " (Promo)" or special or "",
				v.name
			)
			local icon2_name = format(
				"%s%s %s 2 Ability Icon.png",
				args.name or args[1],
				promo and " (Promo)" or special or "",
				v.name
			)
			icon_name = mw.title.new(icon_name, "File").exists and icon_name or nil
			icon2_name = mw.title.new(icon2_name, "File").exists and icon2_name or nil

			if icon2_name then
				icon_tabber = mw.html.create("div")
					:addClass("flip-container lazyimg-wrapper")
					:cssText("margin-right:0.75rem;margin-bottom:0.25rem;width:64px;")
					:tag("div")
					:addClass("flip-content-1 flip-switch")
					:tag("div")
					:cssText("box-shadow:0 2px 5px 0 black;")
					:wikitext(format("[[File:%s|64px|link=]]", icon_name))
					:done()
					:tag("div")
					:cssText("display:flex;justify-content:center;align-items:center;")
					:tag("div")
					:cssText(
						"height:12px;width:12px;border:1px solid black;border-radius:50%;background:white;margin:5px 2px 5px 5px;box-shadow:1px 1px 3px 0 black;"
					)
					:done()
					:tag("div")
					:cssText(
						"height:12px;width:12px;border:1px solid black;border-radius:50%;background:gray;margin:5px 5px 5px 2px;box-shadow:1px 1px 3px 0 black;"
					)
					:done()
					:done()
					:done()
					:tag("div")
					:addClass("flip-content-2 flip-switch")
					:cssText("display:none")
					:tag("div")
					:cssText("box-shadow:0 2px 5px 0 black;")
					:wikitext(format("[[File:%s|64px|link=]]", icon2_name))
					:done()
					:tag("div")
					:cssText("display:flex;justify-content:center;align-items:center;")
					:tag("div")
					:cssText(
						"height:12px;width:12px;border:1px solid black;border-radius:50%;background:gray;margin:5px 2px 5px 5px;box-shadow:1px 1px 3px 0 black;"
					)
					:done()
					:tag("div")
					:cssText(
						"height:12px;width:12px;border:1px solid black;border-radius:50%;background:white;margin:5px 5px 5px 2px;box-shadow:1px 1px 3px 0 black;"
					)
					:done()
					:done()
					:done()
					:done()

				icon_name = nil
				icon2_name = nil
			end

			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 = icon_name,
						icon2 = icon2_name,
						icontabber = icon_tabber and tostring(icon_tabber) 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 = frame:preprocess(text or ""), --TODO: remove preprocess once fandom gets their issues resolved
					},
				})

			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] .. "=" .. general_desc(args[1]) .. getInfo(get.abilities(args[1]))
	end
	if get.abilities(args[1] .. " (Fire)") then
		tagArgs[#tagArgs + 1] = args[1]
			.. " (Fire)="
			.. general_desc(args[1] .. " (Fire)")
			.. getInfo(get.abilities(args[1] .. " (Fire)"), "Fire")
	end
	if get.abilities(args[1] .. " (Frost)") then
		tagArgs[#tagArgs + 1] = args[1]
			.. " (Frost)="
			.. general_desc(args[1] .. " (Frost)")
			.. getInfo(get.abilities(args[1] .. " (Frost)"), "Frost")
	end
	if get.abilities(args[1] .. " (Nature)") then
		tagArgs[#tagArgs + 1] = args[1]
			.. " (Nature)="
			.. general_desc(args[1] .. " (Nature)")
			.. getInfo(get.abilities(args[1] .. " (Nature)"), "Nature")
	end
	if get.abilities(args[1] .. " (Shadow)") then
		tagArgs[#tagArgs + 1] = args[1]
			.. " (Shadow)="
			.. general_desc(args[1] .. " (Shadow)")
			.. getInfo(get.abilities(args[1] .. " (Shadow)"), "Shadow")
	end
	if get.abilities(args[1] .. " (Promo)") then
		tagArgs[#tagArgs + 1] = args[1]
			.. " (Promo)="
			.. general_desc(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)="
			.. general_desc(args[1] .. " (Lost Souls)")
			.. getInfo(get.abilities(args[1] .. " (Lost Souls)"), nil, false, " (Lost Souls)")
	end
	if get.abilities(args[1] .. " (Twilight)") then
		tagArgs[#tagArgs + 1] = args[1]
			.. " (Twilight)="
			.. general_desc(args[1] .. " (Twilight)")
			.. getInfo(get.abilities(args[1] .. " (Twilight)"), nil, false, " (Twilight)")
	end
	if get.abilities(args[1] .. " (Superpig)") then
		tagArgs[#tagArgs + 1] = args[1]
			.. " (Superpig)="
			.. general_desc(args[1] .. " (Superpig)")
			.. getInfo(get.abilities(args[1] .. " (Superpig)"), nil, false, " (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 = require("Module:Card/class")()
	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
						local avail = type(v.upgrade_availability) ~= "table" or v.upgrade_availability[i] == true
						if
							avail
							and (
								(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)
	return require("Module:Card/custom").card(frame)
end
function p.custom_upgrade(frame)
	return require("Module:Card/custom").upgrade(frame)
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.22),
				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,
					c = tonumber(t[2] or 0),
					u = tonumber(t[3] or 0),
				}))
			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.22",
					nolink = args.nolink == true and true or false,
				}))
			end
		end
	end

	return res
end

function p.for_each(frame)
	return require("Module:Card/for_each").main(frame)
end

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

	local name = args.name or args[1]
	if not name then
		return "Error: Missing Card Name"
	end

	local artwork = "File:" .. name .. "_Card_Artwork.png"
	local art_promo = "File:" .. name .. "_(Promo)_Card_Artwork.png"
	local art_old1 = "File:" .. name .. "_Card_Artwork_Old.png"
	local art_old2 = "File:" .. name .. "_Card_Artwork_Old_2.png"

	local images = {}
	if mw.title.new(artwork).fileExists then
		images[#images + 1] = artwork .. "|Card Artwork"
	end
	if mw.title.new(art_promo).fileExists then
		images[#images + 1] = art_promo .. "|Card Artwork (Promo)"
	end
	if mw.title.new(art_old1).fileExists then
		images[#images + 1] = art_old1 .. "|Old Card Artwork - Version 1"
	end
	if mw.title.new(art_old2).fileExists then
		images[#images + 1] = art_old2 .. "|Old Card Artwork - Version 2"
	end

	local gal = frame:extensionTag(
		"gallery",
		concat(images, "\n"),
		{ type = "slideshow", widths = "420px", position = "center", hideaddbutton = "true" }
	)

	return gal
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 skip_verify = {
		affinity_variants = true,
		editions = true,
		verified_name = true,
	}

	local cardname = (args[2]:find("affini") or skip_verify[args[2]]) 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 val:lower() == 'counter' then
		if get.weapon_type(cardname) == 'Special' then return "Special" end
	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)

	mw.logObject(returnval)

	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 name, var

	if string.find(args[1], " (", 1, true) and string.find(args[1], ")", 1, true) then
		name, var = string.match(args[1], "(.+) %((.+)%)")
	else
		name = args[1]
	end

	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>[[Category:Pages with script errors]]',
			name or ""
		)
	end
	if var and not get.exists(format("%s (%s)", name, var)) then
		return format(
			'<span style="cursor:help; border-bottom:1px dotted; color: red;" title="Error: Variant &quot;%s&quot; not found.>Error</span>[[Category:Pages with script errors]]',
			var 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 then
		linktext = format("%s (%s)", linktext, var)
	end

	local u = args.u or args.upgrade or args.upgrades or args.applied_upgrade or args.applied_upgrades or 0
	local c = args.c or args.charge or args.charges or args.applied_charge or args.applied_charges or 0

	local span = mw.html.create("span")
		:addClass("card-icon custom-tooltip ci")
		:attr("data-tt-type", args.show_upgrade and "upgrade" or "card")
		:attr("data-1", args[1])
		:attr("data-2", u)
		:attr("data-3", c)

	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 == "bandit" 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

		-- omg the formatter f'ed up
		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 mapData = mw.loadData("Module:Map/data")

	local res = get.map_drops(args[1], args[2])
	if args[3] == "list" then
		local d = ({ "standard", "advanced", "expert" })[args[2]]
		local dt = mapData[args[1]].difficulties or {}
		if #res == 0 then
			if dt[d] then
				return format("No card upgrades as reward for this scenario on %s difficulty.", d)
			else
				return format("Scenario not available on %s difficulty.", d)
			end
		elseif not dt[d] then
			return format(
				'<span style="color:red;">Error: %s rewards found in [[Module:Card/data]], but %s isn\'t available on %s difficulty ([[Module:Map/data]]).</span>',
				#res,
				args[1],
				d
			)
		end
		local res2 = mw.html.create("ul")
		for i = 1, #res do
			res2:tag("li"):node(p.icon({ res[i], show_upgrade = true, upgrade = args[2] })):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 incrH = (
				frame:getParent()["args"] and frame:getParent()["args"]["mainpage"]
				or frame:getParent()["args"]["increased-height"]
			)
			and true
		or false
	local height = incrH and "400px" or "230px"
	local grid = mw.html.create("div")
		:attr("id", "card-grid")
		:css({ ["max-height"] = height })
		:addClass("lazyimg-wrapper collapsed" .. (incrH and " increased-height" or ""))

	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

			local special = { get.gender(v) or "None" }
			if has.promo(name) then
				special[#special + 1] = "Promo"
			end
			if has.starter_card(name) then
				special[#special + 1] = "Starter"
			end
			if has.normal(name) then
				special[#special + 1] = "Normal"
			end
			if non_legendary then
				special[#special + 1] = "Non-Legendary"
			end

			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", concat(get.editions(name), ","))
				:attr("data-faction", faction)
				:attr("data-counter", get.counter(v))
				:attr("data-weapontype", get.weapon_type(v))
				:attr("data-special", concat(special, ","))
				: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")
		:allDone()
	-- minus QueekQueek (Superpig)

	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
			local avai = type(v.upgrade_availability) == "table" and v.upgrade_availability or { true, true, true }
			if type(v.upgrade_text) == "table" or type(cost) == "table" then
				if
					upgrade >= 1
					and (
						avai[1] == true
						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 (
						avai[2] == true
						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 (
						avai[3] == true
						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 require("Module:Card/class")({
		is_upgrade = true,
		scaling = args.scaling or args.displayscaling or nil,
		--TODO: notooltip = args.notooltip or false,
		notooltip = true,
		link = args.nolink and "" 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():tag('div'):cssText('display:none'):node(description):done()
end

return p
Advertisement