Jump to content

Module:Card

From The Petit Planet Wiki
Revision as of 11:16, 8 May 2026 by Eleiyas (talk | contribs)
Module documentation
This Lua module is used primarily on pages in the Main namespace. It can be found on 2,428 pages, or on about 60.61% of those pages.
Edits to this Lua module will be widely noticed. To avoid large-scale disruption and unnecessary server load, any changes to this Lua module should be tested in its sandbox or testcases subpages. The tested changes can then be added to this page in one single edit.
Please consider discussing any changes in the wiki's Discord.

This module implements {{Card}}.

To update, see: Module:CentralData.

local p = {}

local lib = require('Module:Feature')
local ne = lib.isNotEmpty
local TemplateData = require('Module:TemplateData')
local array = require('Module:Array')

local allData = {
--Neighbor
	{'Neighbor', mw.loadData('Module:CentralData/Neighbors')},
--Creatures
	{'Fish', mw.loadData('Module:CentralData/Fish')},
	{'Insect', mw.loadData('Module:CentralData/Insects')},
	{'Shore-Dweller', mw.loadData('Module:CentralData/Shore-Dwellers')},
--Furniture
	{'Furniture', mw.loadData('Module:CentralData/Furniture')},
--Items
	{'Item', mw.loadData('Module:CentralData/Items')},
	{'Dish', mw.loadData('Module:CentralData/Dishes')},	
	{'Crop', mw.loadData('Module:CentralData/Crops')},	
	{'Tool', mw.loadData('Module:CentralData/Tools')},	
--Clothing
	{'Clothing', mw.loadData('Module:CentralData/Clothing')},
	{'Styling', mw.loadData('Module:CentralData/Styling')},	
--Other
	{'Title', mw.loadData('Module:CentralData/Titles')},
	{'Reaction', mw.loadData('Module:CentralData/Reactions')},
	{'Set', mw.loadData('Module:CentralData/Sets')},
--Redirects (Things that don't need their own pages)
	{'Redirect', mw.loadData('Module:CentralData/Redirects')},
}

local CARD_ARGS = {
	{
		name = 'name',
		alias = 1,
		default = 'Unknown',
	},
	{
		name = 'label',
		alias = {2, 'text', 'count', 'amount', 'x'},
	},
	{
		name = 'show_label',
		alias = 'show_text',
		type = '1',
	},
	{
		name = 'quality',
		alias = 'rarity',
		default = 'Gray',
	},
	{
		name = 'type',
	},
	{
		name = 'suffix',
		default = '',
	},
	{
		name = 'image',
	},
	{
		name = 'image_size',
		alias = {'imageSize', 'size'},
	},
	{
		name = 'image_ext',
		alias = {'imageExt', 'ext', 'extension'},
		default = 'png',
	},
	{
		name = 'link',
	},
	{
		name = 'link_suffix',
		default = '',
	},
	{
		name = 'no_link',
		type = '1',
	},
	{
		name = 'caption',
	},
	{
		name = 'show_caption',
		type = '1',
	},
	{
		name = 'caption_width',
		alias = 'captionWidth',
	},
	{
		name = 'mini',
		type = '1',
	},
	{
		name = 'first',
		type = '1',
	},
	{
		name = 'up',
		alias = 'UP',
		type = '1',
	},
	{
		name = 'galaxy',
	},
	{
		name = 'notice',
	},
}

function p.main(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		wrapper = { 'Template:Card' },
		parentOnly = false
	})

	return p._main(args)
end

function p.getTypeAndData(name, requestedType)
	local img_type = requestedType or 'Item'
	local img_data = nil

	for _, v in ipairs(allData) do
		local dataType = v[1]
		local dataTable = v[2]

		if lib.isEmpty(requestedType) or dataType == requestedType then
			local data = dataTable[name]

			if data then
				img_type = dataType
				img_data = data
				break
			end
		end
	end

	return img_type, img_data
end

local function normalizeBool(value)
	return value == true or value == '1' or value == 1 or value == 'yes'
end

local function normalizeClassName(value)
	return tostring(value or '')
		:lower()
		:gsub('%s+', '-')
end

local function normalizeQuality(quality)
	quality = tostring(quality or '')

	if quality:find(';', 1, true) then
		quality = array._main({
			quality,
			';',
			firstItemOnly = 1
		})
	end

	if lib.isEmpty(quality) then
		quality = 'Gray'
	end

	return normalizeClassName(quality)
end

local function stripLinkPrefix(value)
	value = tostring(value or '')

	-- Converts "Luca: Myriad Meadows" to "Myriad Meadows"
	-- Only affects the link target, not the displayed card name.
	return mw.text.trim(value:gsub('^.-:%s*', '', 1))
end

local function setCaptionWidth(node, width)
	if width == 'auto' or lib.isEmpty(width) then
		node:addClass('auto-width')
	else
		node:css('width', width)
	end
end

function p._processArgs(args)
	local out = TemplateData.processArgs(args, CARD_ARGS)

	-- Basic identity
	out.name = out.name or 'Unknown'

	-- Use only a manually supplied type for lookup.
	-- This allows {{Card|Sockeye Salmon}} to search Creature, Item, etc.
	local requestedType = out.nonDefaults and out.nonDefaults.type or nil
	out.type, out.data = p.getTypeAndData(out.name, requestedType)

	-- Boolean params
	out.mini = normalizeBool(out.mini)
	out.first = normalizeBool(out.first)
	out.up = normalizeBool(out.up)
	out.no_link = normalizeBool(out.no_link)
	out.show_label = normalizeBool(out.show_label)
	out.show_caption = normalizeBool(out.show_caption)

	-- Image defaults
	out.suffix = out.suffix or ''
	out.image_ext = out.image_ext or 'png'

	if lib.isEmpty(out.image_size) then
		out.image_size = out.mini and 48 or 70
	end

	if lib.isEmpty(out.suffix) then
		out.suffix = ' Icon'
	end

	-- Data-driven defaults
	if out.data then
		if out.data.name then
			out.defaults.label = out.data.name
			out.defaults.caption = out.data.name
		end

		if out.data.title then
			out.defaults.caption = out.data.title
		end

		if out.data.quality then
			out.defaults.quality = out.data.quality

			-- Safety fallback in case TemplateData does not re-resolve
			-- defaults after processArgs.
			if not (out.nonDefaults and out.nonDefaults.quality) then
				out.quality = out.data.quality
			end
		end

		if out.data.galaxy then
			out.defaults.galaxy = out.data.galaxy

			if not (out.nonDefaults and out.nonDefaults.galaxy) then
				out.galaxy = out.data.galaxy
			end
		end
	end

	-- Quality / rarity
	out.quality = normalizeQuality(out.quality)

	-- Label/text handling
	if out.show_label then
		out.label = out.data and (out.data.name or out.data.title) or out.name
	end

	if out.label == '0' then
		out.label = nil
	end

	-- Type-specific behavior
	local lcType = tostring(out.type or ''):lower()
	
	if lcType == 'neighbor' then
		if lib.isEmpty(out.label) then
			if out.mini and out.data and out.data.shortName then
				out.label = out.data.shortName
			elseif not out.mini then
				out.label = out.name
			end
		end
	
		if tonumber(out.image_size) then
			out.image_size = tonumber(out.image_size) + 2
		end
	end
	
	if lcType == 'redirect' then
		out.defaults.link = stripLinkPrefix(out.name)
	
		-- Safety fallback in case TemplateData does not re-resolve defaults.
		if not (out.nonDefaults and out.nonDefaults.link) then
			out.link = stripLinkPrefix(out.name)
		end
	end

	-- Caption handling
	if out.show_caption and lib.isEmpty(out.caption) then
		if out.data then
			out.caption = out.data.title or out.data.name or out.name
		else
			out.caption = out.name
		end
	end

	-- Link handling
	if out.link == nil then
		out.link = lib.ternary(out.name == 'Unknown', '', out.name)
	end

	out.link_suffix = out.link_suffix or ''
	out.final_link = out.link .. out.link_suffix

	if out.no_link then
		out.final_link = ''
	end

	-- File name
	if ne(out.image) then
		out.final_file = out.image
	else
		out.final_file = lib.sanitizeFileName(out.name) .. out.suffix .. '.' .. out.image_ext
	end

	-- Main image wikitext
	local linkPart = out.no_link and '|link=' or ('|link=' .. out.final_link)

	out.image_wikitext = table.concat({
		'[[File:',
		out.final_file,
		'|',
		out.image_size,
		'px|alt=',
		out.name,
		linkPart,
		']]'
	})

	return out
end

function p._main(args)
	local a = p._processArgs(args)

	local node_container = mw.html.create('div')
		:addClass('card-container')

	if a.mini then
		node_container:addClass('mini-card')
	end

	-- Old-style wrapper/body structure
	local node_card = node_container:tag('span')
		:addClass('card-wrapper')
		:tag('span')
			:addClass('card-body')

	-- Galaxy class on body
	if not a.mini and ne(a.galaxy) then
		node_card:addClass('card-galaxy-' .. normalizeClassName(a.galaxy))
	end

	-- Main image
	node_card:tag('span')
		:addClass('card-image-container')
		:addClass('card-quality-' .. a.quality)
		:wikitext(a.image_wikitext)

	-- Galaxy icon
	if not a.mini and ne(a.galaxy) then
		node_card:tag('span')
			:addClass('card-icon-galaxy')
			:wikitext(
				'[[File:Icon ' ..
				a.galaxy ..
				'.png|alt=' ..
				a.galaxy ..
				'|link=' ..
				a.galaxy ..
				'|20px]]'
			)
	end

	-- Notice label
	if not a.mini and ne(a.notice) then
		node_card:tag('span')
			:addClass('card-notice-label')
			:wikitext(a.notice)
	end

	-- First modifier
	if a.first then
		node_card:tag('span')
			:addClass('card-modifier')
			:addClass('card-modifier-first')
			:wikitext('[[File:Icon Item First.png|x25px|link=|alt=First]]')
	end

	-- UP modifier
	if a.up then
		node_card:tag('span')
			:addClass('card-modifier')
			:addClass('card-modifier-up')
			:wikitext('[[File:Icon Item UP.png|x25px|link=|alt=UP]]')
	end

	-- Label/text below card image
	if ne(a.label) then
		node_container:tag('span')
			:addClass('card-label')
			:wikitext(a.label)
	end

	-- Caption below card
	if a.show_caption and ne(a.caption) then
		local node_caption = node_container:tag('span')
			:addClass('card-caption')

		if a.no_link or lib.isEmpty(a.final_link) then
			node_caption:wikitext(a.caption)
		else
			node_caption:wikitext('[[', a.final_link, '|', a.caption, ']]')
		end

		setCaptionWidth(node_caption, a.caption_width)
	end

	return tostring(node_container)
end

function p.templateData(frame)
	return TemplateData.templateData(CARD_ARGS, {
		description = 'Creates an item-style card with image, quality, label, caption, modifiers, and optional data-driven icons.',
		format = 'inline'
	}, frame)
end

function p.syntax(frame)
	return TemplateData.syntax(CARD_ARGS, frame)
end

return p