模块

卡牌:修订间差异

来自卡厄思梦境WIKI

律Rhyme留言 | 贡献
无编辑摘要
律Rhyme留言 | 贡献
无编辑摘要
第1行: 第1行:
-- 模块:卡牌
local p = {}
local p = {}


-- 加载词典/data模块
-- 使用官方参数解析器,自动合并父/子 frame 参数、去空白与空值
local dictionaryData = require("模块:词典/data")
local getArgs = require('Module:Arguments').getArgs


-- 卡牌名称底部颜色条映射表
-- 渲染模块
local COLOR_MAP = {
local displayModule = require('Module:卡牌/display')
    ["白"] = { bgColor = "rgba(249, 249, 249, 0.5)", textColor = "white" },
    ["蓝"] = { bgColor = "rgba(115, 236, 254, 0.5)", textColor = "#7de5ff" },
    ["橙"] = { bgColor = "rgba(254, 199, 109, 0.5)", textColor = "#ffee75" },
    ["彩"] = { bgColor = "rgba(201, 88, 241, 0.5)", textColor = "#eba2fc" }
}


-- 右侧词典渲染顶部边框颜色映射表
-- 安全的错误输出
local BORDER_COLOR_MAP = {
local function err(msg)
     ["白"] = "#ffffff",
     return '<span style="color: red;">' .. mw.text.encode(msg) .. '</span>'
    ["蓝"] = "#0099ff",
    ["红"] = "#ff3333",
    ["橙"] = "#f2ba02"
}
 
-- 安全获取数据字段
local function safeGet(data, field, default)
    return data and data[field] or default
end
end


-- 处理描述文本中的模板
-- 标题截断:超过 maxLen 个字符用 ... 截断(按 Unicode 计数,兼容中英文)
local function processDescription(frame, description)
local function ellipsisTitle(s, maxLen)
     if not description or description == "" then
    s = tostring(s or '')
         return description
    local ulen = mw.ustring.len(s)
     if ulen > (maxLen or 5) then
         return mw.ustring.sub(s, 1, maxLen or 5) .. '...'
     end
     end
      
     return s
    local success, result = pcall(function()
        return frame:preprocess(description)
    end)
   
    return success and result or description
end
end


-- 从处理后的文本中提取纯文本(去除HTML标签)
-- 解析 dict 字段
local function extractPlainText(text)
local function parseDictTokens(dictStr)
     if not text then return "" end
    local s = tostring(dictStr or '')
      
     if s == '' then return {} end
     -- 去除HTML标签
 
     local plainText = text:gsub("<[^>]*>", "")
     local tokens = {}
   
 
    -- 去除多余的空白字符
     -- 提取模板形式的 token:{{词典|机制}}
    plainText = mw.text.trim(plainText)
     for token in s:gmatch('{{%s*词典%s*|%s*([^}|]+)%s*[^}]*}}') do
   
        token = mw.text.trim(token or '')
     return plainText
        if token ~= '' then table.insert(tokens, token) end
end
     end


-- 安全的字符串分割函数(使用 MediaWiki 函数处理 UTF-8)
    -- 去掉模板后,解析剩余的纯文本(兼容中文分隔符为英文逗号)
local function splitString(str, delimiter)
    local plain = s:gsub('{{%s*词典%s*|%s*[^}]*}}', '')
     if not str or str == "" then
     plain = plain:gsub('、', ','):gsub(',', ',')
         return {}
    for token in plain:gmatch('([^,]+)') do
        token = mw.text.trim(token or '')
         if token ~= '' then table.insert(tokens, token) end
     end
     end
   
 
     -- 使用 mw.text.split 进行分割(UTF-8 安全)
     -- 去重,保持顺序
     local parts = mw.text.split(str, delimiter, true)
     local seen, out = {}, {}
   
     for _, t in ipairs(tokens) do
    local result = {}
         if not seen[t] then
     for _, part in ipairs(parts) do
            seen[t] = true
         -- 使用 mw.text.trim 去除空格(UTF-8 安全)
             table.insert(out, t)
        local trimmed = mw.text.trim(part)
        if trimmed ~= "" then
             table.insert(result, trimmed)
         end
         end
     end
     end
   
     return out
     return result
end
end


-- 从原始机制文本中提取机制名称列表(处理模板语法前)
-- 加载 模块:词典/data
local function extractMechanismNames(mechanismText)
local dictData = nil
    if not mechanismText or mechanismText == "" or mechanismText == "无" then
do
        return {}
     local ok, dictModule = pcall(mw.loadData, 'Module:词典/data')
    end
     if ok and type(dictModule) == 'table' then
   
         if type(dictModule.dictionary) == 'table' then
     local mechanismList = {}
             dictData = dictModule.dictionary
   
        else
    -- 匹配所有 {{词典|机制名}} 模板
            dictData = dictModule
     for mechName in mechanismText:gmatch("{{词典|([^}|]+)") do
         mechName = mw.text.trim(mechName)
        if mechName ~= "" then
             table.insert(mechanismList, mechName)
         end
         end
     end
     end
   
end
    -- 如果没有找到词典模板,尝试直接解析文本
 
     if #mechanismList == 0 then
-- 根据 tokens 生成词典条目数组 { {key=..., desc=...}, ... }
        -- 先按"、"分割
local function buildDictEntries(tokens)
        local parts1 = splitString(mechanismText, "、")
     if not tokens or #tokens == 0 then return nil end
        for _, part in ipairs(parts1) do
    local entries = {}
             -- 再按"/"分割
    for _, key in ipairs(tokens) do
             local parts2 = splitString(part, "/")
        local desc = nil
             for _, p in ipairs(parts2) do
        local d = dictData and dictData[key]
                 table.insert(mechanismList, p)
        if type(d) == 'table' then
             -- 兼容数组形式与字典形式
             if d[1] and type(d[1]) == 'table' then
                desc = d[1].desc or desc
             elseif type(d.desc) == 'string' then
                 desc = d.desc
             end
             end
         end
         end
        table.insert(entries, { key = key, desc = desc })
     end
     end
   
     return entries
     return mechanismList
end
end


-- 生成机制说明HTML
-- 合并 base 数据与变体数据
local function generateMechanismHTML(frame, mechanism)
local function mergeCardData(baseData, variantData, opts)
     if not mechanism or mechanism == "" or mechanism == "无" then
     if not baseData then return nil end
         return ""
    local merged = {}
    -- 浅拷贝 base
    for k, v in pairs(baseData) do
        merged[k] = v
    end
    -- 清空衍生卡牌信息
    if opts and opts.clearSubUnlessSpecified then
         merged.sub = nil
     end
     end
   
     -- 覆盖变体数据
     -- 提取机制名称列表(从原始模板语法中提取)
     if variantData then
    local mechanismList = extractMechanismNames(mechanism)
        for k, v in pairs(variantData) do
   
             merged[k] = v
    local htmlParts = {}
   
     for _, mechName in ipairs(mechanismList) do
        local entries = dictionaryData.dictionary[mechName]
       
        if entries then
            -- 找到"卡牌机制"类型的条目
            local entry = nil
            for _, e in ipairs(entries) do
                if e["类型"] == "卡牌机制" then
                    entry = e
                    break
                end
            end
           
            if entry then
                local borderColor = BORDER_COLOR_MAP[entry["颜色"]] or BORDER_COLOR_MAP["白"]
                local description = entry["描述"] or "暂无说明"
                local icon = entry["icon"] or ""
               
                -- 构建图标HTML
                local iconHTML = ""
                if icon ~= "" then
                    iconHTML = '[[File:' .. icon .. '|25px|link=]] '
                end
               
                -- 构建机制显示HTML
                local mechHTML = string.format([[
                    <div class="mechanism-card" style="margin-bottom: 10px;">
                        <div style="width: 100%%; height: 2px; background-color: %s;"></div>
                        <div style="width: 100%%; background-color: #343434; color: white; padding: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); line-height: 1.6;">
                            <div style="margin-bottom: 5px; font-weight: bold;">%s%s</div>
                            <div>%s</div>
                        </div>
                    </div>
                ]], borderColor, iconHTML, mechName, description)
               
                table.insert(htmlParts, mechHTML)
             else
                -- 没有找到对应类型的条目
                table.insert(htmlParts, string.format([[
                    <div class="mechanism-card" style="margin-bottom: 10px;">
                        <div style="width: 100%%; height: 2px; background-color: #f2ba02;"></div>
                        <div style="width: 100%%; background-color: #343434; color: white; padding: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.3);">
                            <div style="margin-bottom: 5px; font-weight: bold;">%s</div>
                            <div style="color: #ccc;">暂无详细说明</div>
                        </div>
                    </div>
                ]], mechName))
            end
        else
            -- 词典中没有该词条
            table.insert(htmlParts, string.format([[
                <div class="mechanism-card" style="margin-bottom: 10px;">
                    <div style="width: 100%%; height: 2px; background-color: #f2ba02;"></div>
                    <div style="width: 100%%; background-color: #343434; color: white; padding: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.3);">
                        <div style="margin-bottom: 5px; font-weight: bold;">%s</div>
                        <div style="color: #ccc;">暂无详细说明</div>
                    </div>
                </div>
            ]], mechName))
         end
         end
     end
     end
      
     return merged
    if #htmlParts > 0 then
        -- 添加标题和容器
        return string.format([[
            <div class="mechanism-container" style="display: none;">
                %s
            </div>
        ]], table.concat(htmlParts))
    end
   
    return ""
end
end


-- 生成卡牌HTML样式组件
-- 根据 base AP 与目标 AP 文本计算发光样式
local function buildStyleComponents(cardData, cardName, baseCardData, originalCardData)
-- 返回: 'blue' | 'red' | 'green' | 'gray'
     local color = safeGet(cardData, "稀有度", "白")
local function calcApStyle(apText, baseApText, isVariant)
    local colorStyle = COLOR_MAP[color] or COLOR_MAP["白"]
     local text = apText ~= nil and tostring(apText) or ''
    local attribute = safeGet(cardData, "属性", "虚无")
     if not isVariant then
    local cardDeck = safeGet(cardData, "卡组", "起始卡牌")
        return 'blue'
   
     end
    local ap = safeGet(cardData, "AP", "")
     if text == 'X' then
    local originalAP = baseCardData and safeGet(baseCardData, "AP", ap) or ap
         return 'blue'
      
    -- 决定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
     end
   
     local n = tonumber(text)
    -- 检查原始变体卡牌数据中是否指定了机制字段
     local baseN = baseApText ~= nil and tonumber(baseApText) or nil
     local mechanism = safeGet(cardData, "机制", "")
     if n and baseN then
     local mechanismChanged = false
         if n > baseN then
   
            return 'red'
    -- 判断机制是否发生了变化(变体卡牌主动指定了机制字段)
         elseif n < baseN then
     if baseCardData and originalCardData then
             return 'green'
         -- 检查原始变体卡牌数据中是否有"机制"字段
        else
         if originalCardData["机制"] ~= nil then
             return 'blue'
             -- 变体卡牌主动指定了机制字段,无论内容是什么都标记为已更改
             mechanismChanged = true
         end
         end
     end
     end
   
     -- 其他内容(非数值且非X)不发光
     -- 将"无"视为空字符串
     return 'gray'
    if mechanism == "无" then mechanism = "" end
   
     return {
        color = color,
        colorStyle = colorStyle,
        attribute = attribute,
        ap = ap,
        apGlowColor = apGlowColor,
        cardType = safeGet(cardData, "类型", ""),
        art = safeGet(cardData, "art", ""),
        mechanism = mechanism,
        mechanismChanged = mechanismChanged
    }
end
end


-- 去除模板语法,计算纯文本字数
-- 将单张卡牌的数据转换为 cardInfo 结构(在此完成词典解析)
local function getPlainTextLength(text)
-- opts:
     if not text or text == "" then
--  isVariant: boolean
         return 0
--  baseApText: string|number
function p.parseCardData(cardData, moduleName, cardName, moduleInfo, opts)
     if not cardData then
        return nil
    end
 
    local ego = ""
    if moduleInfo and moduleInfo.ego then
         ego = moduleInfo.ego
    elseif cardData.ego then
        ego = cardData.ego
     end
     end
      
 
     -- 去除所有模板 {{...}}(支持嵌套)
    -- 展示名称:优先 displayname,否则使用卡牌名
     local plainText = text
     local displayTitle = (cardData.displayname and mw.text.trim(tostring(cardData.displayname)) ~= '' and cardData.displayname) or cardName or ''
     local maxIterations = 10  -- 防止无限循环
    -- 截断后的展示名称(仅用于卡牌图片上的可视显示,不影响其他逻辑/搜索)
     local iteration = 0
    local displayTitleShort = ellipsisTitle((displayTitle ~= '' and displayTitle) or cardName, 5)
      
 
     while iteration < maxIterations do
     -- AP 文本与样式
        local newText = plainText:gsub("{{[^{}]*}}", "")
    local apText = cardData.ap ~= nil and tostring(cardData.ap) or ''
        if newText == plainText then
    local isVariant = opts and opts.isVariant or false
            break  -- 没有更多模板可以移除
     local baseApText = opts and opts.baseApText or nil
     local apStyle = calcApStyle(apText, baseApText, isVariant)
 
    -- 词典解析
    local rawDictStr = cardData.dict or ""
     local dictStr = mw.text.trim(rawDictStr)
     local dictTokens
     local dictEntries
    local dictColor = "default"
 
    if isVariant then
        if dictStr == "" then
            dictTokens = {}
            dictEntries = nil
        else
            dictTokens = parseDictTokens(dictStr)
            dictEntries = buildDictEntries(dictTokens)
            if dictStr ~= "" then
                dictColor = "#b5f651" -- 变体卡牌的机制颜色变为绿色
            end
         end
         end
         plainText = newText
    else
         iteration = iteration + 1
         dictTokens = parseDictTokens(dictStr)
         dictEntries = buildDictEntries(dictTokens)
     end
     end
      
 
    -- 去除HTML标签
     local cardInfo = {
    plainText = plainText:gsub("<[^>]*>", "")
        moduleName = moduleName or "",
   
        cardName = cardName or "",
    -- 去除多余的空白字符
        displayTitle = displayTitle or "",
    plainText = plainText:gsub("%s+", "")
        displayTitleShort = displayTitleShort or "",
   
        art = cardData.art or "",
    -- 返回字符数(UTF-8字符计数)
        ego = ego or "",
    local _, count = plainText:gsub("[%z\1-\127\194-\244][\128-\191]*", "")
        rarity = cardData.rarity or "",
     return count
        apText = apText,
        apStyle = apStyle,
        cardType = cardData.type or "",
        desc = cardData.desc_global or "",
        dict = dictStr,
        dictTokens = dictTokens,
        dictEntries = dictEntries,
        dictColor = dictColor,
        group = cardData.group or "",
        tag = cardData.tag or "",
        hasInspiration = false,
        inspirationVariants = {},
        hasGodInspiration = false,
        godInspirationGroups = nil,
        godInspirationGroupOrder = nil
    }
 
     return cardInfo
end
end


-- 构建卡牌HTML
-- 解析衍生卡牌列表(支持递归)
local function buildCardHTML(frame, cardName, cardData, baseCardData, characterName, originalCardData)
function p.parseSubCardData(subCardNames, dataModule, moduleInfo)
     if not cardData then
     if not subCardNames or not dataModule then
         return string.format("找不到卡牌数据: %s", cardName or "未指定")
         return nil
     end
     end
   
     local subCards = {}
    -- 获取显示名称,如果存在则使用,否则使用默认卡牌名称
     for subName in string.gmatch(subCardNames, '([^,]+)') do
     local displayName = safeGet(cardData, "显示名称", cardName)
        subName = mw.text.trim(subName)
      
        if subName ~= "" then
    local style = buildStyleComponents(cardData, cardName, baseCardData, originalCardData)
            local subCardData = dataModule[subName]
    local description = processDescription(frame, safeGet(cardData, "描述", ""))
            if subCardData and subCardData.base then
   
                local parsedCard = p.parseCardData(subCardData.base, "", subName, moduleInfo, { isVariant = false })
    -- 获取原始机制字段(未处理的)
                if subCardData.base.sub then
    local rawMechanism = safeGet(cardData, "机制", "")
                    parsedCard.subCards = p.parseSubCardData(subCardData.base.sub, dataModule, moduleInfo)
   
                 end
    -- 处理机制文本显示
                 table.insert(subCards, parsedCard)
    local mechanismDisplayHTML = ""
    if rawMechanism and rawMechanism ~= "" and rawMechanism ~= "无" then
        -- 处理机制字段中的模板(让词典模板正常渲染)
        local processedMechanism = processDescription(frame, rawMechanism)
       
        -- 将""替换为"/"
        processedMechanism = processedMechanism:gsub("、", "/")
       
        if processedMechanism and processedMechanism ~= "" then
            -- 如果是变体卡牌且主动指定了机制字段,整体使用绿色
            if style.mechanismChanged then
                mechanismDisplayHTML = string.format('<div style="color: #b4f352;">[%s]</div>', processedMechanism)
            else
                 -- 普通机制:使用橙色显示,但保留词典模板的样式
                 mechanismDisplayHTML = string.format('<div style="color: #f2ba02;">[%s]</div>', processedMechanism)
             end
             end
         end
         end
     end
     end
   
     if #subCards == 0 then
    -- 组合完整的描述内容
         return nil
    local fullDescription = mechanismDisplayHTML .. description
   
    -- 检测是否需要滚动(去除模板后计算字数)
    local plainTextLength = getPlainTextLength(fullDescription)
    local needScroll = plainTextLength > 50  -- 超过50个字符才启用滚动
    local scrollClass = needScroll and " card-description-scrollable" or ""
   
    -- 获取衍生卡牌信息
    local derivedCards = safeGet(cardData, "衍生卡牌", "")
    local deckType = safeGet(cardData, "卡组", "")
   
    -- 生成机制HTML(用于右侧词条展示,传入原始未处理的机制字段)
    local mechanismHTML = generateMechanismHTML(frame, rawMechanism)
   
    -- 生成数据属性
    local hasMechanism = (rawMechanism ~= "" and rawMechanism ~= "无") and "true" or "false"
    local dataAttrs = string.format('data-card-name="%s" data-character="%s" data-deck-type="%s" data-derived-cards="%s" data-has-mechanism="%s"',
        cardName or "", characterName or "", deckType or "", derivedCards or "", hasMechanism)
   
    -- 检查是否是自我意识技能
     if deckType == "自我意识技能" then
        -- 自我意识技能的特殊布局
        local attribute = safeGet(cardData, "属性", "虚无")
        local cost = safeGet(cardData, "AP", "1")
        local art = safeGet(cardData, "art", "")
       
        local htmlParts = {
            string.format('<div class="game-card" %s style="display: inline-block; vertical-align: top; position: relative; width: 155px; height: 235px; overflow: hidden; margin: 0px; cursor: pointer;">', dataAttrs),
            string.format('<div style="position: absolute; top: 0px; left: 4px; pointer-events: none;">[[File:%s|150px|link=]]</div>', art),
            string.format('<div style="position: absolute; top: 0px; left: 0px; pointer-events: none;">[[File:tp_%s.png|154px|link=]]</div>', attribute),
            string.format('<div style="position: absolute; top: 0px; left: 0px; pointer-events: none;">[[File:tp_cost_%s.png|154px|link=]]</div>', cost),
            string.format('<div style="position: absolute; top: 105px; left: 17px; color: white; font-size: 13px; user-select: text; cursor: text;">%s</div>', displayName),
            string.format('<div style="position: absolute; top: 138px; left: 17px; width: 135px; height: 90px; color: white; font-size: 13px; line-height: 14px; user-select: text; cursor: text;">%s</div>', description),
            '<div style="position: absolute; top: 0px; left: 0px; pointer-events: none;">[[File:tp_顶层蒙版.png|154px|link=]]</div>',
            '</div>'
        }
       
        -- 在卡牌div之后添加隐藏的机制说明
        local result = table.concat(htmlParts)
        if mechanismHTML ~= "" then
            result = result .. mechanismHTML
        end
       
         return result
     end
     end
      
     return subCards
    -- 使用字符串拼接优化HTML生成
end
    local htmlParts = {
 
        string.format('<div class="game-card%s" %s style="display: inline-block; vertical-align: top; position: relative; width: 168px; height: 230px; overflow: hidden; margin: 0px; cursor: pointer;">',
-- 收集全部灵光一闪变体
            scrollClass, dataAttrs),
local function collectInspirationVariants(baseData, cardDataWrapper, moduleName, cardName, moduleInfo, dataModule)
    }
    local variants = {}
   
     if not cardDataWrapper.var or not cardDataWrapper.var.inspiration then
    -- 根据类型判断使用哪种布局
         return variants
    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))
       
        -- 卡牌名称和类型(使用显示名称)
        table.insert(htmlParts, string.format('<div style="position: absolute; left: 50px; top: 13px; color: %s; font-size:16px">%s</div>',
            style.colorStyle.textColor, displayName))
        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))
       
        -- 卡牌名称和类型(使用显示名称)
        table.insert(htmlParts, string.format('<div style="position: absolute; left: 50px; top: 13px; color: %s; font-size:16px">%s</div>',
            style.colorStyle.textColor, displayName))
        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
     end
      
     for i, inspData in ipairs(cardDataWrapper.var.inspiration) do
    -- 描述文本 - 根据是否需要滚动使用不同结构
        local mergedData = mergeCardData(baseData, inspData, { clearSubUnlessSpecified = true })
    table.insert(htmlParts, string.format([[
        local variantInfo = p.parseCardData(mergedData, moduleName, cardName, moduleInfo, {
<div class="card-description-scroll">
            isVariant = true,
    <div class="card-description-scroll-inner">
            baseApText = baseData.ap
         <div class="card-description-text">%s</div>
         })
    </div>
        if mergedData.sub then
</div>
            variantInfo.subCards = p.parseSubCardData(mergedData.sub, dataModule, moduleInfo)
]], fullDescription))
        end
   
         table.insert(variants, variantInfo)
    table.insert(htmlParts, '</div>')
   
    -- 在卡牌div之后添加隐藏的机制说明
    if mechanismHTML ~= "" then
         table.insert(htmlParts, mechanismHTML)
     end
     end
   
     return variants
     return table.concat(htmlParts)
end
end


-- 合并卡牌数据
-- 收集全部神光一闪变体
local function mergeCardData(baseData, overrideData)
local function collectGodInspirationVariants(baseData, cardDataWrapper, moduleName, cardName, moduleInfo, dataModule)
     local result = {}
     local godGroups = {}
     -- 复制基础卡牌的所有属性
     local groupOrder = {}
     for k, v in pairs(baseData or {}) do
 
         result[k] = v
     if not cardDataWrapper.var or not cardDataWrapper.var.god_inspiration then
         return nil, nil
     end
     end
   
    -- 清空衍生卡牌属性
    if overrideData and next(overrideData) ~= nil then
        result["衍生卡牌"] = nil
    end
   
    -- 应用覆盖数据
    for k, v in pairs(overrideData or {}) do
        result[k] = v
    end
   
    return result
end


-- 获取并显示所有默认卡牌
     local fixedOrder = { "circen", "diallos", "nihilum", "secred", "vitor" }
local function displayAllDefaultCards(frame, characterModule, characterName)
 
     local cards = characterModule.card or {}
     for _, groupName in ipairs(fixedOrder) do
    local cardOrder = characterModule.cardOrder or {}
         local groupList = cardDataWrapper.var.god_inspiration[groupName]
    local displayedCards = {}
         if type(groupList) == "table" and #groupList > 0 then
   
             local list = {}
    -- 按cardOrder顺序遍历
             for _, godVar in ipairs(groupList) do
     for orderIndex, name in ipairs(cardOrder) do
                local mergedData = mergeCardData(baseData, godVar, { clearSubUnlessSpecified = true })
         local cardArray = cards[name]
                local variantInfo = p.parseCardData(mergedData, moduleName, cardName, moduleInfo, {
         if cardArray and #cardArray > 0 then
                    isVariant = true,
             -- 只显示第一张卡牌
                    baseApText = baseData.ap
             table.insert(displayedCards, {
                 })
                name = name,
                if mergedData.sub then
                 data = cardArray[1],
                    variantInfo.subCards = p.parseSubCardData(mergedData.sub, dataModule, moduleInfo)
                 order = orderIndex
                 end
             })
                table.insert(list, variantInfo)
            end
            godGroups[groupName] = list
             table.insert(groupOrder, groupName)
         end
         end
     end
     end
   
 
     -- 生成HTML
     return godGroups, groupOrder
    local htmlParts = {}
    for i, cardInfo in ipairs(displayedCards) do
        table.insert(htmlParts, buildCardHTML(frame, cardInfo.name, cardInfo.data, nil, characterName, nil))
    end
   
    return table.concat(htmlParts)
end
end


-- 处理特定卡牌请求
-- 主入口
local function handleSpecificCard(frame, cardName, cardData, deckFilter, cardIndex, characterName)
function p.main(frame)
     local baseCard = cardData[1]
    local args = getArgs(frame, { removeBlank = true })
      
 
     -- 如果没有指定卡组筛选,返回基础卡牌
     local moduleName = args[1]
     if deckFilter == "" then
     local cardName = args[2]
         return buildCardHTML(frame, cardName, baseCard, nil, characterName, nil)
     local variantType = args[3]
    local variantParam = args[4]
    local variantIndex = args[5]
 
     if not moduleName or moduleName == "" then
         return err("错误: 未指定模块名")
     end
     end
      
     if not cardName or cardName == "" then
    -- 筛选指定卡组的卡牌
        return err("错误: 未指定卡牌名")
    local filteredCards = {}
    for _, card in ipairs(cardData) do
        if card["卡组"] == deckFilter then
            table.insert(filteredCards, card)
        end
     end
     end
      
 
     if #filteredCards == 0 then
     local success, dataModule = pcall(require, 'Module:卡牌/' .. moduleName)
         return string.format("找不到卡组 '%s' 的卡牌: %s", deckFilter, cardName)
     if not success or not dataModule then
         return err('错误: 找不到模块 "Module:卡牌/' .. moduleName .. '"')
     end
     end
   
 
     -- 如果指定了索引,返回特定卡牌
     local moduleInfo = dataModule.info or {}
     if cardIndex > 0 and cardIndex <= #filteredCards then
     local cardDataWrapper = dataModule[cardName]
        local originalCard = filteredCards[cardIndex] -- 保存原始变体卡牌数据
    if not cardDataWrapper then
        local mergedCard = mergeCardData(baseCard, originalCard)
         return err('错误: 在模块 "' .. moduleName .. '" 中找不到卡牌 "' .. cardName .. '"')
         return buildCardHTML(frame, cardName, mergedCard, baseCard, characterName, originalCard)
     end
     end
   
 
    -- 否则返回该卡组的所有卡牌
     local baseData = cardDataWrapper.base
     local htmlParts = {}
     if not baseData then
     for i, card in ipairs(filteredCards) do
         return err('错误: 卡牌 "' .. cardName .. '" 没有 base 数据')
        local originalCard = card  -- 保存原始变体卡牌数据
         local mergedCard = mergeCardData(baseCard, originalCard)
        table.insert(htmlParts, buildCardHTML(frame, cardName, mergedCard, baseCard, characterName, originalCard))
     end
     end
   
    return table.concat(htmlParts)
end


-- 主函数
    local finalCardData = baseData
function p.main(frame)
    local isVariantFlag = false
    local args = frame.args
 
    local characterName = args[1] or ""
    if variantType then
    local cardName = args[2] or ""
        variantType = mw.text.trim(variantType)
    local deckFilter = args[3] or ""
        if variantType == "灵光一闪" then
    local cardIndex = tonumber(args[4]) or 0
            if not cardDataWrapper.var or not cardDataWrapper.var.inspiration then
   
                return err('错误: 卡牌 "' .. cardName .. '" 没有灵光一闪变体')
    -- 加载战斗员模块
            end
    local success, characterModule = pcall(require, "模块:卡牌/" .. characterName)
            local inspIndex = tonumber(variantParam)
    if not success or not characterModule then
            if not inspIndex or inspIndex < 1 or inspIndex > #cardDataWrapper.var.inspiration then
        return string.format("找不到战斗员卡牌数据模块: 模块:卡牌/%s", characterName)
                return err('错误: 灵光一闪变体索引 "' .. tostring(variantParam) .. '" 无效')
            end
            finalCardData = mergeCardData(baseData, cardDataWrapper.var.inspiration[inspIndex], { clearSubUnlessSpecified = true })
            isVariantFlag = true
 
        elseif variantType == "神光一闪" then
            if not cardDataWrapper.var or not cardDataWrapper.var.god_inspiration then
                return err('错误: 卡牌 "' .. cardName .. '" 没有神光一闪变体')
            end
            local characterName = variantParam
            if not characterName or characterName == "" then
                return err("错误: 未指定战斗员")
            end
            local godInspData = cardDataWrapper.var.god_inspiration[characterName]
            if not godInspData then
                return err('错误: 找不到战斗员 "' .. characterName .. '" 的神光一闪')
            end
            local godIndex = tonumber(variantIndex)
            if not godIndex or godIndex < 1 or godIndex > #godInspData then
                return err('错误: 神光一闪变体索引 "' .. tostring(variantIndex) .. '" 无效')
            end
            finalCardData = mergeCardData(baseData, godInspData[godIndex], { clearSubUnlessSpecified = true })
            isVariantFlag = true
 
        else
            return err('错误: 未知变体类型 "' .. variantType .. '"')
        end
     end
     end
      
 
     -- 如果没有指定卡牌名,显示所有默认卡牌
     local cardInfo = p.parseCardData(finalCardData, moduleName, cardName, moduleInfo, {
     if cardName == "" then
        isVariant = isVariantFlag,
         return displayAllDefaultCards(frame, characterModule, characterName)
        baseApText = baseData and baseData.ap or nil
     })
     if not cardInfo then
         return err("错误: 解析卡牌数据失败")
     end
     end
   
 
     -- 获取指定卡牌数据
     -- 灵光一闪按钮集合
     local cardData = (characterModule.card or {})[cardName]
     if not variantType and cardDataWrapper.var and cardDataWrapper.var.inspiration and (baseData.isinspiration == 1) then
    if not cardData then
        cardInfo.hasInspiration = true
         return string.format("找不到卡牌: %s", cardName)
         cardInfo.inspirationVariants = collectInspirationVariants(baseData, cardDataWrapper, moduleName, cardName, moduleInfo, dataModule)
     end
     end
   
    return handleSpecificCard(frame, cardName, cardData, deckFilter, cardIndex, characterName)
end


-- 元表设置,支持直接调用战斗员名称作为方法
    -- 神光一闪按钮集合
setmetatable(p, {
     local godFlag = (baseData.isgod_god_inspiration == 1) or (baseData.isgod_inspiration == 1)
     __index = function(t, characterName)
    if not variantType and godFlag and cardDataWrapper.var and cardDataWrapper.var.god_inspiration then
        return function(frame)
        local godGroups, order = collectGodInspirationVariants(baseData, cardDataWrapper, moduleName, cardName, moduleInfo, dataModule)
            frame.args = {
        if godGroups and order then
                [1] = characterName,
            cardInfo.hasGodInspiration = true
                [2] = frame.args[1] or "",
             cardInfo.godInspirationGroups = godGroups
                [3] = frame.args[2] or "",
             cardInfo.godInspirationGroupOrder = order
                [4] = frame.args[3] or 0
             }
             return p.main(frame)
         end
         end
     end
     end
})
 
    -- 主卡牌的衍生牌
    local subCardInfo = nil
    if finalCardData.sub then
        subCardInfo = p.parseSubCardData(finalCardData.sub, dataModule, moduleInfo)
    end
 
    return displayModule.render(cardInfo, subCardInfo)
end


return p
return p

2025年10月15日 (三) 17:13的版本

此模块的文档可以在模块:卡牌/doc创建

local p = {}

-- 使用官方参数解析器,自动合并父/子 frame 参数、去空白与空值
local getArgs = require('Module:Arguments').getArgs

-- 渲染模块
local displayModule = require('Module:卡牌/display')

-- 安全的错误输出
local function err(msg)
    return '<span style="color: red;">' .. mw.text.encode(msg) .. '</span>'
end

-- 标题截断:超过 maxLen 个字符用 ... 截断(按 Unicode 计数,兼容中英文)
local function ellipsisTitle(s, maxLen)
    s = tostring(s or '')
    local ulen = mw.ustring.len(s)
    if ulen > (maxLen or 5) then
        return mw.ustring.sub(s, 1, maxLen or 5) .. '...'
    end
    return s
end

-- 解析 dict 字段
local function parseDictTokens(dictStr)
    local s = tostring(dictStr or '')
    if s == '' then return {} end

    local tokens = {}

    -- 提取模板形式的 token:{{词典|机制}}
    for token in s:gmatch('{{%s*词典%s*|%s*([^}|]+)%s*[^}]*}}') do
        token = mw.text.trim(token or '')
        if token ~= '' then table.insert(tokens, token) end
    end

    -- 去掉模板后,解析剩余的纯文本(兼容中文分隔符为英文逗号)
    local plain = s:gsub('{{%s*词典%s*|%s*[^}]*}}', '')
    plain = plain:gsub('、', ','):gsub(',', ',')
    for token in plain:gmatch('([^,]+)') do
        token = mw.text.trim(token or '')
        if token ~= '' then table.insert(tokens, token) end
    end

    -- 去重,保持顺序
    local seen, out = {}, {}
    for _, t in ipairs(tokens) do
        if not seen[t] then
            seen[t] = true
            table.insert(out, t)
        end
    end
    return out
end

-- 加载 模块:词典/data
local dictData = nil
do
    local ok, dictModule = pcall(mw.loadData, 'Module:词典/data')
    if ok and type(dictModule) == 'table' then
        if type(dictModule.dictionary) == 'table' then
            dictData = dictModule.dictionary
        else
            dictData = dictModule
        end
    end
end

-- 根据 tokens 生成词典条目数组 { {key=..., desc=...}, ... }
local function buildDictEntries(tokens)
    if not tokens or #tokens == 0 then return nil end
    local entries = {}
    for _, key in ipairs(tokens) do
        local desc = nil
        local d = dictData and dictData[key]
        if type(d) == 'table' then
            -- 兼容数组形式与字典形式
            if d[1] and type(d[1]) == 'table' then
                desc = d[1].desc or desc
            elseif type(d.desc) == 'string' then
                desc = d.desc
            end
        end
        table.insert(entries, { key = key, desc = desc })
    end
    return entries
end

-- 合并 base 数据与变体数据
local function mergeCardData(baseData, variantData, opts)
    if not baseData then return nil end
    local merged = {}
    -- 浅拷贝 base
    for k, v in pairs(baseData) do
        merged[k] = v
    end
    -- 清空衍生卡牌信息
    if opts and opts.clearSubUnlessSpecified then
        merged.sub = nil
    end
    -- 覆盖变体数据
    if variantData then
        for k, v in pairs(variantData) do
            merged[k] = v
        end
    end
    return merged
end

-- 根据 base AP 与目标 AP 文本计算发光样式
-- 返回: 'blue' | 'red' | 'green' | 'gray'
local function calcApStyle(apText, baseApText, isVariant)
    local text = apText ~= nil and tostring(apText) or ''
    if not isVariant then
        return 'blue'
    end
    if text == 'X' then
        return 'blue'
    end
    local n = tonumber(text)
    local baseN = baseApText ~= nil and tonumber(baseApText) or nil
    if n and baseN then
        if n > baseN then
            return 'red'
        elseif n < baseN then
            return 'green'
        else
            return 'blue'
        end
    end
    -- 其他内容(非数值且非X)不发光
    return 'gray'
end

-- 将单张卡牌的数据转换为 cardInfo 结构(在此完成词典解析)
-- opts:
--   isVariant: boolean
--   baseApText: string|number
function p.parseCardData(cardData, moduleName, cardName, moduleInfo, opts)
    if not cardData then
        return nil
    end

    local ego = ""
    if moduleInfo and moduleInfo.ego then
        ego = moduleInfo.ego
    elseif cardData.ego then
        ego = cardData.ego
    end

    -- 展示名称:优先 displayname,否则使用卡牌名
    local displayTitle = (cardData.displayname and mw.text.trim(tostring(cardData.displayname)) ~= '' and cardData.displayname) or cardName or ''
    -- 截断后的展示名称(仅用于卡牌图片上的可视显示,不影响其他逻辑/搜索)
    local displayTitleShort = ellipsisTitle((displayTitle ~= '' and displayTitle) or cardName, 5)

    -- AP 文本与样式
    local apText = cardData.ap ~= nil and tostring(cardData.ap) or ''
    local isVariant = opts and opts.isVariant or false
    local baseApText = opts and opts.baseApText or nil
    local apStyle = calcApStyle(apText, baseApText, isVariant)

    -- 词典解析
    local rawDictStr = cardData.dict or ""
    local dictStr = mw.text.trim(rawDictStr)
    local dictTokens
    local dictEntries
    local dictColor = "default"

    if isVariant then
        if dictStr == "空" then
            dictTokens = {}
            dictEntries = nil
        else
            dictTokens = parseDictTokens(dictStr)
            dictEntries = buildDictEntries(dictTokens)
            if dictStr ~= "" then
                dictColor = "#b5f651" -- 变体卡牌的机制颜色变为绿色
            end
        end
    else
        dictTokens = parseDictTokens(dictStr)
        dictEntries = buildDictEntries(dictTokens)
    end

    local cardInfo = {
        moduleName = moduleName or "",
        cardName = cardName or "",
        displayTitle = displayTitle or "",
        displayTitleShort = displayTitleShort or "",
        art = cardData.art or "",
        ego = ego or "",
        rarity = cardData.rarity or "",
        apText = apText,
        apStyle = apStyle,
        cardType = cardData.type or "",
        desc = cardData.desc_global or "",
        dict = dictStr,
        dictTokens = dictTokens,
        dictEntries = dictEntries,
        dictColor = dictColor,
        group = cardData.group or "",
        tag = cardData.tag or "",
        hasInspiration = false,
        inspirationVariants = {},
        hasGodInspiration = false,
        godInspirationGroups = nil,
        godInspirationGroupOrder = nil
    }

    return cardInfo
end

-- 解析衍生卡牌列表(支持递归)
function p.parseSubCardData(subCardNames, dataModule, moduleInfo)
    if not subCardNames or not dataModule then
        return nil
    end
    local subCards = {}
    for subName in string.gmatch(subCardNames, '([^,]+)') do
        subName = mw.text.trim(subName)
        if subName ~= "" then
            local subCardData = dataModule[subName]
            if subCardData and subCardData.base then
                local parsedCard = p.parseCardData(subCardData.base, "", subName, moduleInfo, { isVariant = false })
                if subCardData.base.sub then
                    parsedCard.subCards = p.parseSubCardData(subCardData.base.sub, dataModule, moduleInfo)
                end
                table.insert(subCards, parsedCard)
            end
        end
    end
    if #subCards == 0 then
        return nil
    end
    return subCards
end

-- 收集全部灵光一闪变体
local function collectInspirationVariants(baseData, cardDataWrapper, moduleName, cardName, moduleInfo, dataModule)
    local variants = {}
    if not cardDataWrapper.var or not cardDataWrapper.var.inspiration then
        return variants
    end
    for i, inspData in ipairs(cardDataWrapper.var.inspiration) do
        local mergedData = mergeCardData(baseData, inspData, { clearSubUnlessSpecified = true })
        local variantInfo = p.parseCardData(mergedData, moduleName, cardName, moduleInfo, {
            isVariant = true,
            baseApText = baseData.ap
        })
        if mergedData.sub then
            variantInfo.subCards = p.parseSubCardData(mergedData.sub, dataModule, moduleInfo)
        end
        table.insert(variants, variantInfo)
    end
    return variants
end

-- 收集全部神光一闪变体
local function collectGodInspirationVariants(baseData, cardDataWrapper, moduleName, cardName, moduleInfo, dataModule)
    local godGroups = {}
    local groupOrder = {}

    if not cardDataWrapper.var or not cardDataWrapper.var.god_inspiration then
        return nil, nil
    end

    local fixedOrder = { "circen", "diallos", "nihilum", "secred", "vitor" }

    for _, groupName in ipairs(fixedOrder) do
        local groupList = cardDataWrapper.var.god_inspiration[groupName]
        if type(groupList) == "table" and #groupList > 0 then
            local list = {}
            for _, godVar in ipairs(groupList) do
                local mergedData = mergeCardData(baseData, godVar, { clearSubUnlessSpecified = true })
                local variantInfo = p.parseCardData(mergedData, moduleName, cardName, moduleInfo, {
                    isVariant = true,
                    baseApText = baseData.ap
                })
                if mergedData.sub then
                    variantInfo.subCards = p.parseSubCardData(mergedData.sub, dataModule, moduleInfo)
                end
                table.insert(list, variantInfo)
            end
            godGroups[groupName] = list
            table.insert(groupOrder, groupName)
        end
    end

    return godGroups, groupOrder
end

-- 主入口
function p.main(frame)
    local args = getArgs(frame, { removeBlank = true })

    local moduleName = args[1]
    local cardName = args[2]
    local variantType = args[3]
    local variantParam = args[4]
    local variantIndex = args[5]

    if not moduleName or moduleName == "" then
        return err("错误: 未指定模块名")
    end
    if not cardName or cardName == "" then
        return err("错误: 未指定卡牌名")
    end

    local success, dataModule = pcall(require, 'Module:卡牌/' .. moduleName)
    if not success or not dataModule then
        return err('错误: 找不到模块 "Module:卡牌/' .. moduleName .. '"')
    end

    local moduleInfo = dataModule.info or {}
    local cardDataWrapper = dataModule[cardName]
    if not cardDataWrapper then
        return err('错误: 在模块 "' .. moduleName .. '" 中找不到卡牌 "' .. cardName .. '"')
    end

    local baseData = cardDataWrapper.base
    if not baseData then
        return err('错误: 卡牌 "' .. cardName .. '" 没有 base 数据')
    end

    local finalCardData = baseData
    local isVariantFlag = false

    if variantType then
        variantType = mw.text.trim(variantType)
        if variantType == "灵光一闪" then
            if not cardDataWrapper.var or not cardDataWrapper.var.inspiration then
                return err('错误: 卡牌 "' .. cardName .. '" 没有灵光一闪变体')
            end
            local inspIndex = tonumber(variantParam)
            if not inspIndex or inspIndex < 1 or inspIndex > #cardDataWrapper.var.inspiration then
                return err('错误: 灵光一闪变体索引 "' .. tostring(variantParam) .. '" 无效')
            end
            finalCardData = mergeCardData(baseData, cardDataWrapper.var.inspiration[inspIndex], { clearSubUnlessSpecified = true })
            isVariantFlag = true

        elseif variantType == "神光一闪" then
            if not cardDataWrapper.var or not cardDataWrapper.var.god_inspiration then
                return err('错误: 卡牌 "' .. cardName .. '" 没有神光一闪变体')
            end
            local characterName = variantParam
            if not characterName or characterName == "" then
                return err("错误: 未指定战斗员")
            end
            local godInspData = cardDataWrapper.var.god_inspiration[characterName]
            if not godInspData then
                return err('错误: 找不到战斗员 "' .. characterName .. '" 的神光一闪')
            end
            local godIndex = tonumber(variantIndex)
            if not godIndex or godIndex < 1 or godIndex > #godInspData then
                return err('错误: 神光一闪变体索引 "' .. tostring(variantIndex) .. '" 无效')
            end
            finalCardData = mergeCardData(baseData, godInspData[godIndex], { clearSubUnlessSpecified = true })
            isVariantFlag = true

        else
            return err('错误: 未知变体类型 "' .. variantType .. '"')
        end
    end

    local cardInfo = p.parseCardData(finalCardData, moduleName, cardName, moduleInfo, {
        isVariant = isVariantFlag,
        baseApText = baseData and baseData.ap or nil
    })
    if not cardInfo then
        return err("错误: 解析卡牌数据失败")
    end

    -- 灵光一闪按钮集合
    if not variantType and cardDataWrapper.var and cardDataWrapper.var.inspiration and (baseData.isinspiration == 1) then
        cardInfo.hasInspiration = true
        cardInfo.inspirationVariants = collectInspirationVariants(baseData, cardDataWrapper, moduleName, cardName, moduleInfo, dataModule)
    end

    -- 神光一闪按钮集合
    local godFlag = (baseData.isgod_god_inspiration == 1) or (baseData.isgod_inspiration == 1)
    if not variantType and godFlag and cardDataWrapper.var and cardDataWrapper.var.god_inspiration then
        local godGroups, order = collectGodInspirationVariants(baseData, cardDataWrapper, moduleName, cardName, moduleInfo, dataModule)
        if godGroups and order then
            cardInfo.hasGodInspiration = true
            cardInfo.godInspirationGroups = godGroups
            cardInfo.godInspirationGroupOrder = order
        end
    end

    -- 主卡牌的衍生牌
    local subCardInfo = nil
    if finalCardData.sub then
        subCardInfo = p.parseSubCardData(finalCardData.sub, dataModule, moduleInfo)
    end

    return displayModule.render(cardInfo, subCardInfo)
end

return p