模块:卡牌
来自卡厄思梦境WIKI
此模块的文档可以在模块:卡牌/doc创建
local p = {}
-- 安全获取嵌套表数据
local function safeGet(tbl, key, default)
if tbl and tbl[key] ~= nil then
return tbl[key]
end
return default or ""
end
-- 颜色映射表
local COLOR_MAP = {
["白"] = { bgColor = "#ffffff", textColor = "#333333" },
["蓝"] = { bgColor = "#4a90e2", textColor = "white" },
["紫"] = { bgColor = "#9b59b6", textColor = "white" },
["金"] = { bgColor = "#f1c40f", textColor = "#333333" }
}
-- 计算名称缩放比例
local function calculateNameScale(name)
local length = mw.ustring.len(name)
-- 基准是5个字符以内
if length <= 5 then
return "1"
elseif length <= 7 then
return "0.9"
elseif length <= 9 then
return "0.8"
elseif length <= 11 then
return "0.7"
else
return "0.6"
end
end
-- 处理描述文本
local function processDescription(frame, description)
if not description or description == "" then
return ""
end
-- 处理换行符
description = description:gsub("\r?\n", "<br>")
-- 处理图标:{{图标名称}} 转换为对应图片
description = description:gsub("{{(.-)}}", function(iconName)
return string.format('[[File:icon_%s.png|16px|link=|style="vertical-align: -3px; margin: 0 1px;"]]', iconName)
end)
-- 处理高亮文本:<hl>文本</hl> 转换为金色
description = description:gsub("<hl>(.-)</hl>", function(text)
return string.format('<span style="color: #f1c832; font-weight: bold; text-shadow: 0 0 2px rgba(241, 200, 50, 0.5);">%s</span>', text)
end)
return frame:preprocess(description)
end
-- 生成卡牌HTML样式组件
local function buildStyleComponents(cardData, cardName, baseCardData)
local color = safeGet(cardData, "稀有度", "白")
local colorStyle = COLOR_MAP[color] or COLOR_MAP["白"]
local attribute = safeGet(cardData, "属性", "虚无")
local cardDeck = safeGet(cardData, "卡组", "起始卡牌")
local ap = safeGet(cardData, "AP", "")
local originalAP = baseCardData and safeGet(baseCardData, "AP", ap) or ap
-- 决定AP的发光颜色
local apGlowColor = "#4a90e2" -- 默认蓝色
if baseCardData and ap ~= originalAP then
if ap == "X" then
apGlowColor = "#4a90e2" -- X值保持默认蓝色
elseif tonumber(ap) and tonumber(originalAP) then
if tonumber(ap) < tonumber(originalAP) then
apGlowColor = "#51f651" -- 绿色(AP降低)
else
apGlowColor = "#f65151" -- 红色(AP增加)
end
end
end
-- 检查原卡牌与当前卡牌的机制变化(将"无"视为空字符串)
local mechanism = safeGet(cardData, "机制", "")
local originalMechanism = baseCardData and safeGet(baseCardData, "机制", "") or mechanism
-- 将"无"视为空字符串
if mechanism == "无" then mechanism = "" end
if originalMechanism == "无" then originalMechanism = "" end
local mechanismChanged = baseCardData and mechanism ~= "" and originalMechanism == ""
return {
color = color,
colorStyle = colorStyle,
attribute = attribute,
ap = ap,
apGlowColor = apGlowColor,
cardType = safeGet(cardData, "类型", ""),
art = safeGet(cardData, "art", "start_1041_01.png"),
mechanism = mechanism,
mechanismChanged = mechanismChanged
}
end
-- 构建卡牌HTML
local function buildCardHTML(frame, cardName, cardData, baseCardData, characterName)
if not cardData then
return string.format("找不到卡牌数据: %s", cardName or "未指定")
end
local style = buildStyleComponents(cardData, cardName, baseCardData)
local description = processDescription(frame, safeGet(cardData, "描述", ""))
-- 获取衍生卡牌信息
local derivedCards = safeGet(cardData, "衍生卡牌", "")
local deckType = safeGet(cardData, "卡组", "")
-- 生成数据属性
local dataAttrs = string.format('data-card-name="%s" data-character="%s" data-deck-type="%s" data-derived-cards="%s"',
cardName or "", characterName or "", deckType or "", derivedCards or "")
-- 处理机制文本(当机制为"无"或空时不显示)
local mechanismHTML = ""
if style.mechanism and style.mechanism ~= "" then
local mechanismColor = style.mechanismChanged and "#b5f651" or "#f6c478"
mechanismHTML = string.format('<div style="color: %s; margin-bottom: 4px;">[%s]</div>',
mechanismColor, style.mechanism:gsub("、", "/"))
end
-- 计算名称缩放比例
local nameScale = calculateNameScale(cardName)
-- 使用字符串拼接优化HTML生成
local htmlParts = {
string.format('<div class="game-card" %s style="display: inline-block; vertical-align: top; position: relative; width: 168px; height: 230px; overflow: hidden; margin: 0px; cursor: pointer;">', dataAttrs),
}
-- 根据类型判断使用哪种布局
if style.cardType == "状态异常" then
-- 状态异常卡牌的特殊布局
table.insert(htmlParts, string.format('<div style="position: absolute; top: 5px; left: 5px;">[[File:%s|150px|link=]]</div>', style.art))
table.insert(htmlParts, string.format('<div style="position: absolute; top: 0px; left: 0px;">[[File:card_状态异常_%s.png|159px|link=]]</div>', style.attribute))
-- AP值
table.insert(htmlParts, string.format('<div style="position: absolute; left: 24px; top: 4px; color: white; font-weight: bold; text-shadow: 0 0 10px %s,0 0 20px %s, 0 0 30px %s, 0 0 40px %s; font-size: 32px;">%s</div>',
style.apGlowColor, style.apGlowColor, style.apGlowColor, style.apGlowColor, style.ap))
table.insert(htmlParts, string.format('<div style="position: absolute; left: 25px; top: 34px; color: white; font-weight: bold; text-shadow: 0 0 10px %s,0 0 20px %s, 0 0 30px %s, 0 0 40px %s;">—</div>',
style.apGlowColor, style.apGlowColor, style.apGlowColor, style.apGlowColor))
-- 卡牌名称(使用transform缩放)
table.insert(htmlParts, string.format('<div style="position: absolute; left: 50px; top: 13px; width: 105px; transform-origin: left center; transform: scaleX(%s);"><span style="color: %s; font-size: 16px; white-space: nowrap;">%s</span></div>',
nameScale, style.colorStyle.textColor, cardName))
-- 卡牌类型
table.insert(htmlParts, string.format('<div style="position: absolute; left: 48px; top: 30px;">[[File:icon_card_%s.png|18px|link=]]</div>',
style.cardType))
table.insert(htmlParts, string.format('<div style="position: absolute; left: 68px; top: 33px; color: white; font-size:14px">%s</div>',
style.cardType))
-- 顶层蒙版
table.insert(htmlParts, '<div style="position: absolute; left: 0px; bottom: 0px;">[[File:card_顶层蒙版.png|168px|link=]]</div>')
else
-- 普通卡牌的布局
-- 背景图片层
table.insert(htmlParts, string.format('<div style="position: absolute; top: 1px; left: 12px;">[[File:%s|151px|link=]]</div>', style.art))
table.insert(htmlParts, '<div style="position: absolute; top: 1px; left: 12px;">[[File:card_黑色蒙版.png|151px|link=]]</div>')
-- 颜色条
table.insert(htmlParts, string.format('<div style="position: absolute; top: 21px; left: 10px; width: 148px; height: 8px; background-color: %s;"></div>',
style.colorStyle.bgColor))
-- AP值
table.insert(htmlParts, string.format('<div style="position: absolute; left: 24px; top: 4px; color: white; font-weight: bold; text-shadow: 0 0 10px %s,0 0 20px %s, 0 0 30px %s, 0 0 40px %s; font-size: 32px;">%s</div>',
style.apGlowColor, style.apGlowColor, style.apGlowColor, style.apGlowColor, style.ap))
table.insert(htmlParts, string.format('<div style="position: absolute; left: 25px; top: 34px; color: white; font-weight: bold; text-shadow: 0 0 10px %s,0 0 20px %s, 0 0 30px %s, 0 0 40px %s;">—</div>',
style.apGlowColor, style.apGlowColor, style.apGlowColor, style.apGlowColor))
-- 卡牌名称(使用transform缩放)
table.insert(htmlParts, string.format('<div style="position: absolute; left: 50px; top: 13px; width: 105px; transform-origin: left center; transform: scaleX(%s);"><span style="color: %s; font-size: 16px; white-space: nowrap;">%s</span></div>',
nameScale, style.colorStyle.textColor, cardName))
-- 卡牌类型
table.insert(htmlParts, string.format('<div style="position: absolute; left: 48px; top: 30px;">[[File:icon_card_%s.png|18px|link=]]</div>',
style.cardType))
table.insert(htmlParts, string.format('<div style="position: absolute; left: 68px; top: 33px; color: white; font-size:14px">%s</div>',
style.cardType))
-- 边框
table.insert(htmlParts, string.format('<div style="position: absolute; top: 0px; left: 1px;">[[File:card_属性边框_%s.png|160px|link=]]</div>',
style.attribute))
-- 稀有度
table.insert(htmlParts, string.format('<div style="position: absolute; top: 10px; left: 0px;">[[File:card_稀有度_%s.png|22px|link=]]</div>',
style.color))
table.insert(htmlParts, string.format('<div style="position: absolute; top: 2px; right: 4px;">[[File:card_稀有度_边框_%s.png|11px|link=]]</div>',
style.color))
-- 顶层蒙版
table.insert(htmlParts, '<div style="position: absolute; left: 0px; bottom: 0px;">[[File:card_顶层蒙版.png|168px|link=]]</div>')
end
-- 描述文本(包含机制)- 对所有类型都显示
table.insert(htmlParts, '<div style="position: absolute; bottom: 7px; left: 15px; width: 144px; height: 100px; font-size: 12px; color: white; line-height: 13px; display: flex; justify-content: center; align-items: center; text-align: center;">')
table.insert(htmlParts, '<div style="line-height: 13px;">')
table.insert(htmlParts, mechanismHTML)
table.insert(htmlParts, description)
table.insert(htmlParts, '</div>')
table.insert(htmlParts, '</div>')
table.insert(htmlParts, '</div>')
return table.concat(htmlParts)
end
-- 获取基础卡牌数据(用于对比)
local function getBaseCardData(cardName)
local data = mw.loadData("模块:卡牌数据")
return data[cardName]
end
-- 主函数:显示卡牌
function p.displayCard(frame)
local args = frame.args
local cardName = args[1] or args["name"]
local characterName = args[2] or args["character"] or ""
local deckType = args[3] or args["deck"] or ""
if not cardName then
return "错误:未指定卡牌名称"
end
-- 优先从角色专属数据加载
local cardData = nil
local baseCardData = nil
if characterName ~= "" then
local charDataModule = "模块:卡牌数据/" .. characterName
local success, charData = pcall(mw.loadData, charDataModule)
if success then
-- 根据卡组类型查找
if deckType ~= "" and charData[deckType] then
cardData = charData[deckType][cardName]
else
-- 遍历所有卡组查找
for _, deck in pairs(charData) do
if type(deck) == "table" and deck[cardName] then
cardData = deck[cardName]
break
end
end
end
end
-- 如果是变体卡牌,获取基础卡牌数据
if cardData then
baseCardData = getBaseCardData(cardName)
end
end
-- 如果没有找到专属数据,从通用数据加载
if not cardData then
local data = mw.loadData("模块:卡牌数据")
cardData = data[cardName]
end
if not cardData then
return string.format("错误:找不到卡牌 '%s' 的数据", cardName)
end
return buildCardHTML(frame, cardName, cardData, baseCardData, characterName)
end
-- 批量显示卡牌
function p.displayCards(frame)
local args = frame.args
local cardList = args[1] or args["cards"] or ""
local characterName = args[2] or args["character"] or ""
local columns = tonumber(args[3] or args["columns"]) or 5
if cardList == "" then
return "错误:未指定卡牌列表"
end
-- 解析卡牌列表
local cards = {}
for card in mw.text.gsplit(cardList, ",") do
card = mw.text.trim(card)
if card ~= "" then
table.insert(cards, card)
end
end
if #cards == 0 then
return "错误:卡牌列表为空"
end
-- 构建HTML
local html = {}
table.insert(html, '<div style="display: flex; flex-wrap: wrap; gap: 10px;">')
for i, cardName in ipairs(cards) do
-- 获取卡牌数据
local cardData = nil
local baseCardData = nil
if characterName ~= "" then
local charDataModule = "模块:卡牌数据/" .. characterName
local success, charData = pcall(mw.loadData, charDataModule)
if success then
for _, deck in pairs(charData) do
if type(deck) == "table" and deck[cardName] then
cardData = deck[cardName]
break
end
end
end
if cardData then
baseCardData = getBaseCardData(cardName)
end
end
if not cardData then
local data = mw.loadData("模块:卡牌数据")
cardData = data[cardName]
end
if cardData then
table.insert(html, buildCardHTML(frame, cardName, cardData, baseCardData, characterName))
else
table.insert(html, string.format('<div style="color: red;">找不到卡牌: %s</div>', cardName))
end
-- 添加换行
if i % columns == 0 and i < #cards then
table.insert(html, '</div><div style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px;">')
end
end
table.insert(html, '</div>')
return table.concat(html)
end
-- 显示角色的所有卡牌
function p.displayCharacterCards(frame)
local args = frame.args
local characterName = args[1] or args["character"]
local deckType = args[2] or args["deck"] or "all"
local columns = tonumber(args[3] or args["columns"]) or 5
if not characterName then
return "错误:未指定角色名称"
end
local charDataModule = "模块:卡牌数据/" .. characterName
local success, charData = pcall(mw.loadData, charDataModule)
if not success then
return string.format("错误:找不到角色 '%s' 的卡牌数据", characterName)
end
-- 收集要显示的卡牌
local cardsToDisplay = {}
if deckType == "all" then
-- 显示所有卡组
local deckOrder = {"起始卡牌", "基础卡牌", "高级卡牌", "其他卡牌"}
for _, dName in ipairs(deckOrder) do
if charData[dName] then
for cardName, cardData in pairs(charData[dName]) do
table.insert(cardsToDisplay, {name = cardName, data = cardData, deck = dName})
end
end
end
else
-- 显示特定卡组
if charData[deckType] then
for cardName, cardData in pairs(charData[deckType]) do
table.insert(cardsToDisplay, {name = cardName, data = cardData, deck = deckType})
end
else
return string.format("错误:角色 '%s' 没有 '%s' 卡组", characterName, deckType)
end
end
-- 排序(可选:按稀有度、AP值等排序)
table.sort(cardsToDisplay, function(a, b)
return a.name < b.name
end)
-- 构建HTML
local html = {}
table.insert(html, '<div style="display: flex; flex-wrap: wrap; gap: 10px;">')
for i, card in ipairs(cardsToDisplay) do
local baseCardData = getBaseCardData(card.name)
table.insert(html, buildCardHTML(frame, card.name, card.data, baseCardData, characterName))
if i % columns == 0 and i < #cardsToDisplay then
table.insert(html, '</div><div style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px;">')
end
end
table.insert(html, '</div>')
return table.concat(html)
end
-- 搜索卡牌
function p.searchCards(frame)
local args = frame.args
local searchTerm = args[1] or args["search"] or ""
local searchType = args[2] or args["type"] or "name" -- name, description, mechanism
local columns = tonumber(args[3] or args["columns"]) or 5
if searchTerm == "" then
return "错误:未指定搜索词"
end
searchTerm = mw.ustring.lower(searchTerm)
local data = mw.loadData("模块:卡牌数据")
local results = {}
for cardName, cardData in pairs(data) do
local match = false
if searchType == "name" then
match = mw.ustring.find(mw.ustring.lower(cardName), searchTerm) ~= nil
elseif searchType == "description" then
local desc = safeGet(cardData, "描述", "")
match = mw.ustring.find(mw.ustring.lower(desc), searchTerm) ~= nil
elseif searchType == "mechanism" then
local mech = safeGet(cardData, "机制", "")
match = mw.ustring.find(mw.ustring.lower(mech), searchTerm) ~= nil
elseif searchType == "all" then
local nameMatch = mw.ustring.find(mw.ustring.lower(cardName), searchTerm) ~= nil
local descMatch = mw.ustring.find(mw.ustring.lower(safeGet(cardData, "描述", "")), searchTerm) ~= nil
local mechMatch = mw.ustring.find(mw.ustring.lower(safeGet(cardData, "机制", "")), searchTerm) ~= nil
match = nameMatch or descMatch or mechMatch
end
if match then
table.insert(results, {name = cardName, data = cardData})
end
end
if #results == 0 then
return "没有找到符合条件的卡牌"
end
-- 排序
table.sort(results, function(a, b)
return a.name < b.name
end)
-- 构建HTML
local html = {}
table.insert(html, string.format('<div>找到 %d 张卡牌:</div>', #results))
table.insert(html, '<div style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px;">')
for i, result in ipairs(results) do
table.insert(html, buildCardHTML(frame, result.name, result.data, nil, ""))
if i % columns == 0 and i < #results then
table.insert(html, '</div><div style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px;">')
end
end
table.insert(html, '</div>')
return table.concat(html)
end
return p