Module:Card

Revision as of 15:43, 2 May 2026 by Eleiyas (talk | contribs) (I'm going to see what or if this breaks anything.)
Module documentation
This Lua module is used primarily on pages in the Main namespace. It can be found on 2,430 pages, or on about 60.63% 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 link_label = require('Module:Link_Label')._main
local namespace = require('Module:Namespace_detect')._main
local getTemplateArgs = require('Module:Parser').getTemplateArgs
local array = require('Module:Array')

local allData = {
	{'Agent', mw.loadData('Module:CentralData/Agents')},
	{'W-Engine', mw.loadData('Module:CentralData/W-Engines')},
	{'Drive Disc', mw.loadData('Module:CentralData/Drive Discs')},
	{'Bangboo', mw.loadData('Module:CentralData/Bangboo')},
	{'Enemy', mw.loadData('Module:CentralData/Enemies')},
	{'Resonium', mw.loadData('Module:CentralData/Resonia')},
	{'Item', mw.loadData('Module:CentralData/Items')},
	{'Companion', mw.loadData('Module:CentralData/Companions')},
	{'Outfit', mw.loadData('Module:CentralData/Outfits')},	
}

local COLON = ':'

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

function p._main(args)
	local item = args[1] or args.name or 'Unknown'
	local label = args[2] or args.label or args.text or args.count or args.amount or args.x or nil
	
	-- Reset agent-specific data to prevent carry-over from previous cards
	local agRank, agAttribute, agSpecialty, bbRank, weRank, weSpecialty, outRank, notice = nil, nil, nil, nil, nil, nil, nil, nil
	
	-- Caption Params:
	local caption = args.caption or nil
	local caption_width = args.caption_width or args.captionWidth or nil
	
    -- Image File Params:
	local type, data = p.getTypeAndData(item, args.type)
	local suffix = args.suffix or ''
	local imageSize = args.image_size or args.imageSize or lib.ternary(args.mini == '1', 48, 70)
	local imageExt = args.image_ext or args.imageExt or 'png'
	
	-- Link Params:
	local link = args.link or lib.ternary(item == 'Unknown', '', item)
	local link_suffix = args.link_suffix or ''
	
	-- Rank Param:
	local rank = args.rank or args.quality or args.rarity or nil
	
	-- Try to get Rank From Data
	if lib.isEmpty(rank) and data then
		if data.rank then
			rank = data.rank
		end
	end
	
	-- If rank is still empty, set it to "C"
	if lib.isEmpty(rank) then
		rank = 'C' 
	end
	
	-- If rank contains a semicolon, then split and get first
	if string.find(rank, ";") then
		rank = array._main({
			rank,
			';',
			firstItemOnly = 1
		})
	end
	
	-- Make sure rank is uppercase
	rank = rank:upper()
	
	-- Modifiers:
	local isFirst = args.first or false
	local isUp = args.UP or args.up or false
	
	-- Attribute/specialty
	local attribute = args.attr or args.attribute or nil
	local specialty = args.spec or args.fightSpec or args.fs or nil
	
	if lib.isEmpty(suffix) then
		local lcType = type:lower()
		if (lcType == 'agent') then
			suffix = ' Icon'
			--Grabs Agent Rank, Attribute and Specialty from Data to display icons
			agRank = data['rank']
			agAttribute = data['attribute']
			agSpecialty = data['specialty']
			
			--In Full-Size Card-Mode, if there is no specified text, Agents will default to using their full name displayed underneath.
			if args.mini ~= "1" then
				if (label == nil) then label = item end
			end
			-- In Mini-Size Card-Mode, if there is no specified text, Agents will use their "shortName" pulled from /data as the displayed text.
			-- In cases where Agents do not have a "shortName", their full name will be displayed instead.
			if args.mini == "1" then
				if (label == nil) and data['shortName'] ~= nil then label = data['shortName']
				elseif (label == nil) and data['shortName'] == nil then label = item
				end
			end
			
			-- hackish workaround to account for the slight transparent borders on hoyolab agent icons
			if (tonumber(imageSize) ~= nil) then
				imageSize = tonumber(imageSize) + 2
			end
			
		elseif (lcType == 'bangboo') then
			suffix = ' Icon'
			--Grabs Bangboo Rank from Data to display icons
			bbRank = data['rank']
			
		elseif (lcType == 'w-engine') then
			suffix = ' Icon'
			--Grabs W-Engine Rank and Specialty from Data to display icons
			weRank = data['rank']
			weSpecialty = data['specialty']
			
		elseif (lcType == 'enemy') then
			suffix = '' --icon
			enemyType = data and data['type'] or ''
			enemyTier = data and data['tier'] or ''
			enemyWeakness = data and data['weakness'] or ''
			imageSize = lib.ternary(args.mini == '1', 48, 70)
			
		elseif (lcType == 'drive disc') then 
			suffix = ' Icon' 
			
		elseif (lcType == 'npc') then
			suffix = ' Icon'
			
		elseif (lcType == 'outfit') then
			suffix = ' Icon'
			--Grabs Outfit Rank from Data to display icons
			outRank = data['rank']	
		end
	end

	local final_file = args.image or type .. ' ' .. lib.sanitizeFileName(item) .. suffix .. '.' .. imageExt
	
	local final_link = link .. link_suffix
	
	local file = '[[File:' .. final_file .. '|' .. imageSize .. 'px|alt=' .. item .. '|link=' .. final_link .. ']]'
	
	local result = mw.html.create()
	
	local card_container = result:tag('div')
		:addClass('card-container')
		:addClass('card-rank-' .. rank)
		
	if args.mini == "1" then
		card_container:addClass('mini-card')
	end
		
	local card_wrapper = card_container:tag('div')
		:addClass('card-wrapper')
		
	local card_body = card_wrapper:tag('div')
		:addClass('card-body')
		
	local card_link = card_wrapper:tag('div')
		:addClass('card-link')
		:wikitext('[[' .. final_link .. ']]')
	
	local card_image = card_body:tag('div')
		:addClass('card-image-container')
		:wikitext(file)

--[[if label and not (args.label == "0" or args.text == "0") then --]]
    if (label or args.show_text) and (args.label ~= "0" or args.text ~= "0") then 
		if args.show_label == "1" or args.show_text == "1" then
			label = data and data.name or item
		end 
	
		local card_label = card_container:tag('div')
			:addClass('card-label')
			:wikitext(label)
	end 	

	if (caption or args.show_caption) and args.show_caption ~= "0" then
		if args.show_caption == "1" then
			caption = (data and data.name) or item
		end
		local card_caption = card_container:tag('div')
			:addClass('card-caption')
			:wikitext('[[', final_link, '|', caption, ']]')
			
		if caption_width == 'auto' or lib.isEmpty(caption_width) then
			card_caption:addClass('auto-width')
		else
			card_caption:css('width', caption_width)
		end
	end
	
	if (isFirst) then
		local card_mod_first = card_wrapper:tag('div')
			:addClass('card-modifier card-modifier-first')
			:wikitext('[[File:Icon Item First.png|x25px]]')
	end
	
	if (isUp) then
		local card_mod_up = card_wrapper:tag('div')
			:addClass('card-modifier card-modifier-up')
			:wikitext('[[File:Icon Item UP.png|x25px]]')
	end
	
	-- Attribute icon, applied automatically for agents or manually specified for anything else
	if args.mini ~= "1" then  -- Only display the icons if mini is not set to "1"
		if ne(agRank) then
			card_body:addClass('card-rank-' .. string.lower(agRank))
			card_body:tag('span')
				:addClass('card-icon-rank')
				:wikitext('[[File:Icon AgentRank ', agRank, '.png|alt=', agRank, '|link=|20px]]')
		end
		if ne(bbRank) then
			card_body:addClass('card-rank-' .. string.lower(bbRank))
			card_body:tag('span')
				:addClass('card-icon-rank')
				:wikitext('[[File:Icon Bangboo Rank ', bbRank, '.png|alt=', bbRank, '|link=|20px]]')
		end
		if ne(weRank) then
			card_body:addClass('card-rank-' .. string.lower(weRank))
			card_body:tag('span')
				:addClass('card-icon-rank')
				:wikitext('[[File:Icon AgentRank ', weRank, '.png|alt=', weRank, '|link=|20px]]')
		end
		if ne(agAttribute) then
			card_body:addClass('card-attribute-' .. string.lower(agAttribute))
			card_body:tag('span')
				:addClass('card-icon-attribute')
				:wikitext('[[File:Icon ', agAttribute, '.png|alt=', agAttribute, '|link=', agAttribute, '|20px]]')
		end
		if ne(agSpecialty) then
			card_body:addClass('card-specialty-' .. string.lower(agSpecialty))
			card_body:tag('span')
				:addClass('card-icon-specialty')
				:wikitext('[[File:Icon ', agSpecialty, '.png|alt=', agSpecialty, '|link=', agSpecialty, '|20px]]')
		end
		if ne(weSpecialty) then
			card_body:addClass('card-specialty-' .. string.lower(weSpecialty))
			card_body:tag('span')
				:addClass('card-icon-specialty')
				:wikitext('[[File:Icon ', weSpecialty, '.png|alt=', weSpecialty, '|link=', weSpecialty, '|20px]]')
		end
		if ne(outRank) then
			card_body:addClass('card-rank-' .. string.lower(outRank))
			card_body:tag('span')
				:addClass('card-icon-rank')
				:wikitext('[[File:Icon Item Rank ', outRank, '.png|alt=', outRank, '|link=|20px]]')
		end
		local cinema = args.cinema or args.c or nil
		if ne(cinema) then
			card_body:tag('span')
				:addClass('card-cinema')
				:addClass('cinema-' .. cinema)
				:wikitext(' C', cinema)
			card_body:addClass('card-with-cinema')
		end
		local phase = args.phase or args.p or nil
		if ne(phase) then
			card_body:tag('span')
				:addClass('card-phase')
				:addClass('phase-' .. phase)
				:wikitext(' P', phase)
			card_body:addClass('card-with-phase')
		end
	    local notice = args.notice or nil
	    if ne(notice) then
		    card_body:tag('span')
				:addClass('card-notice-label')
				:wikitext(notice)
	    end
		if ne(enemyType) then
			card_body:tag('span')
				:addClass('card-icon-enemy-type')
				:wikitext('[[File:', enemyType, ' Icon.png|alt=', enemyType, '|link=', enemyType, '|28px]]')
		end
		if ne(enemyWeakness) then
			local weaknessContainer = card_body:tag('span')
				:addClass('card-enemy-weaknesses')
			
			for weakness in lib.gsplit(enemyWeakness, ';') do
				weaknessContainer:wikitext('[[File:Icon ', weakness, '.png|16px|link=', weakness, '|Weak to ', weakness, ']]')
			end
		end
	end
	
	if ne(enemyTier) then
		card_body:addClass('card-enemy card-enemy-' .. enemyTier:gsub(' ', ''))
	end
	
	return tostring(result)
end

function p.getTypeAndData(name, type)
	local img_type = type or 'Item'
	local img_data = nil
	
	for i, v in ipairs(allData) do
		-- Only if the type was either not provided in the card args or if the provided type from card args is equal
		if lib.isEmpty(type) or v[1] == type then
			local data = v[2][name]
			if data then
				img_type = v[1]
				img_data = data
				break
			end
		end
	end
	
	return img_type, img_data
end

return p