模块

卡牌:修订间差异

来自卡厄思梦境WIKI

律Rhyme留言 | 贡献
无编辑摘要
律Rhyme留言 | 贡献
文字替换 -“塞克瑞德”替换为“赛克瑞德”
 
(未显示同一用户的100个中间版本)
第1行: 第1行:
local p = {}
local p = {}
local mw = require('mw')


-- 颜色映射表
-- 使用官方参数解析器,自动合并父/子 frame 参数、去空白与空值
local colorMap = {
local getArgs = require('Module:Arguments').getArgs
     [""] = {
 
         bgColor = "rgba(249, 249, 249, 0.5)",
-- 渲染模块
        textColor = "white"
local displayModule = require('Module:卡牌/display')
     },
 
     ["蓝"] = {
-- 安全的错误输出
         bgColor = "rgba(115, 236, 254, 0.5)",
local function err(msg)
         textColor = "#7de5ff"
     return '<span style="color: red;">' .. mw.text.encode(msg) .. '</span>'
     },
end
     ["橙"] = {
 
         bgColor = "rgba(254, 199, 109, 0.5)",
-- 标题截断:超过 maxLen 个字符用 ... 截断(按 Unicode 计数,兼容中英文)
         textColor = "#ffee75"
local function ellipsisTitle(s, maxLen)
     },
    s = tostring(s or '')
     ["彩"] = {
    local ulen = mw.ustring.len(s)
        bgColor = "rgba(201, 88, 241, 0.5)",
    if ulen > (maxLen or 8) then
         textColor = "#eba2fc"
         return mw.ustring.sub(s, 1, maxLen or 8) .. '...'
    }
    end
}
    return s
end
 
-- 解析 dict 字段
local function parseDictTokens(dictStr)
    local s = tostring(dictStr or '')
    if s == '' then return {} end
 
    local tokens = {}
    local processedText = s
 
     -- 提取 {{词典|机制}} 形式的 token(保留为 template 类型)
     for token in s:gmatch('{{%s*词典%s*|%s*([^}|]+)%s*[^}]*}}') do
        token = mw.text.trim(token or '')
         if token ~= '' then
            table.insert(tokens, { type = 'template', value = token })
         end
    end
 
     -- 提取 {{文本|颜色|内容}} 形式的内容 token(保留为 template)
     for content in s:gmatch('{{%s*文本%s*|%s*[^|]+%s*|%s*([^}]+)%s*}}') do
        content = mw.text.trim(content or '')
         if content ~= '' then
            table.insert(tokens, { type = 'template', value = content })
         end
    end
 
    -- 移除所有模板,保留纯文本部分
     processedText = processedText:gsub('{{%s*词典%s*|%s*[^}]*}}', '')
     processedText = processedText:gsub('{{%s*文本%s*|%s*[^}]*}}', '')
 
    -- 处理剩余的纯文本(支持中文顿号和逗号)
    processedText = processedText:gsub('、', ','):gsub(',', ',')
    for token in processedText:gmatch('([^,]+)') do
        token = mw.text.trim(token or '')
         if token ~= '' then
            table.insert(tokens, { type = 'text', value = token })
        end
    end


-- 获取卡牌HTML
    -- 去重,保持顺序
local function getCardHTML(frame, cardName, cardData)
    local seen, out = {}, {}
    if not cardData then
    for _, t in ipairs(tokens) do
        return "找不到卡牌数据: " .. (cardName or "未指定")
        local key = t.type .. ':' .. t.value
        if not seen[key] then
            seen[key] = true
            table.insert(out, t)
        end
     end
     end
      
     return out
    -- 使用指定的数据条目
end
    local data = cardData
 
    local color = data["稀有度"] or "白"
-- 加载 模块:词典/data
    local colorStyle = colorMap[color] or colorMap["白"]
local dictData = nil
     local ap = data["AP"] or ""
do
     local cardType = data["类型"] or ""
     local ok, dictModule = pcall(mw.loadData, 'Module:词典/data')
    local description = data["描述"] or ""
     if ok and type(dictModule) == 'table' then
    local attribute = data["属性"] or "虚无"
        if type(dictModule.dictionary) == 'table' then
    local cardDeck = data["卡组"] or "起始卡牌"
            dictData = dictModule.dictionary
   
        else
    -- 根据卡组类型决定边框样式
            dictData = dictModule
    local borderStyle = "normal"
         end
    if cardDeck ~= "起始卡牌" then
         borderStyle = "unique"
     end
     end
      
end
     -- 解析描述中的模板
 
    if description ~= "" then
-- 根据 tokens 生成词典条目数组 { {key=..., desc=...}, ... }
         -- 使用frame:preprocess来解析描述中的模板
-- tokens 可以是:字符串数组 或 结构化数组 [{type="text",value="x"}, {type="template",value="y"}]
         description = frame:preprocess(description)
local function buildDictEntries(tokens)
    if not tokens or #tokens == 0 then return nil end
     local entries = {}
 
     for _, t in ipairs(tokens) do
        local key = nil
 
        -- 判断 token 是否为结构化对象(新格式)
        if type(t) == "table" and t.type and t.value then
            key = t.value
         -- 判断 token 是否为字符串(旧格式兼容)
         elseif type(t) == "string" then
            key = t
        end
 
        -- 如果 key 为 nil,跳过这个 token
        if not key then
            -- 不做任何事,跳过
        else
            -- 去空格
            key = mw.text.trim(key)
 
            -- 如果去空格后为空,也跳过
            if key ~= '' then
                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]["描述"] or d[1].desc or desc
                    elseif type(d["描述"]) == 'string' then
                        desc = d["描述"]
                    elseif type(d.desc) == 'string' then
                        desc = d.desc
                    end
                end
 
                table.insert(entries, { key = key, desc = desc })
            end
        end
     end
     end
   
 
     -- 构建卡牌HTML
     return #entries > 0 and entries or nil
    local html = '<div style="position: relative; width: 168px; height: 230px; overflow: hidden;">'
    .. '<div style="position: absolute; top: 1px; left: 12px;">[[File:start_1041_01.png|151px|link=]]</div>'
    .. '<div style="position: absolute; top: 1px; left: 12px;">[[File:card_黑色蒙版.png|151px|link=]]</div>'
    .. '<div style="position: absolute; top: 20px; left: 10px; width: 148px; height: 10px; background-color: ' .. colorStyle.bgColor .. ';"></div>'
    .. '<div style="position: absolute; left: 23px; top: 4px; color: white; font-weight: bold; text-shadow: 0 0 10px #4a90e2,0 0 20px #4a90e2, 0 0 30px #4a90e2, 0 0 40px #4a90e2; font-size: 32px;">' .. ap .. '</div>'
    .. '<div style="position: absolute; left: 25px; top: 34px; color: white; font-weight: bold; text-shadow: 0 0 10px #4a90e2,0 0 20px #4a90e2, 0 0 30px #4a90e2, 0 0 40px #4a90e2;">—</div>'
    .. '<div style="position: absolute; left: 50px; top: 13px; color: ' .. colorStyle.textColor .. '; font-size:16px">' .. cardName .. '</div>'
    .. '<div style="position: absolute; left: 48px; top: 30px; ">[[File:icon_card_' .. cardType .. '.png|18px|link=]]</div>'
    .. '<div style="position: absolute; left: 68px; top: 33px; color: white; font-size:14px">' .. cardType .. '</div>'
    .. '<div style="position: absolute; top: 0px; left: 1px;">[[File:card_属性边框_' .. attribute .. '_' .. borderStyle .. '.png|160px|link=]]</div>'
    .. '<div style="position: absolute; top: 10px; left: 0px;">[[File:card_稀有度_' .. color .. '.png|22px|link=]]</div>'
    .. '<div style="position: absolute; top: 2px; right: 4px;">[[File:card_稀有度_边框_' .. color .. '.png|11px|link=]]</div>'
    .. '<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;">'
    .. '<div>' .. description .. '</div>'
    .. '</div>'
    .. '</div>'
   
    return html
end
end


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


-- 主函数,用于处理模板调用
-- 根据 base AP 与目标 AP 文本计算发光样式
function p.main(frame)
-- 返回: 'blue' | 'red' | 'green' | 'gray'
     local args = frame.args
local function calcApStyle(apText, baseApText, isVariant)
     local characterName = args[1] or ""
     local text = apText ~= nil and tostring(apText) or ''
     local cardName = args[2] or ""
    if not isVariant then
     local deckFilter = args[3] or ""
        return 'blue'
     local cardIndex = tonumber(args[4]) or 0
    end
      
     if text == 'X' then
     -- 动态加载战斗员模块
        return 'blue'
     local characterModule = nil
     end
    local success = pcall(function()  
     local n = tonumber(text)
         characterModule = require("模块:卡牌/" .. characterName)
     local baseN = baseApText ~= nil and tonumber(baseApText) or nil
     end)
    if n and baseN then
      
        if n > baseN then
     if not success or not characterModule then
            return 'red'
         return "找不到战斗员卡牌数据模块: 模块:卡牌/" .. characterName
        elseif n < baseN then
            return 'green'
        else
            return 'blue'
        end
     end
     -- 其他内容(非数值且非X)不发光
     return 'gray'
end
 
-- 将单张卡牌的数据转换为 cardInfo 结构(在此完成词典解析)
-- opts:
--  isVariant: boolean
--  baseApText: string|number
--  baseDictStr: string
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 cards = characterModule.card or {}
    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)
     if cardName == "" then
 
         local html = ""
    -- 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
        -- 获取base的dict用于比较
         local baseDictStr = opts and opts.baseDictStr or ""
        baseDictStr = mw.text.trim(baseDictStr)
          
          
         -- 显示起始卡牌
         if dictStr == "空" then
         for name, cardArray in pairs(cards) do
            -- 显式设置为"空",说明是变体修改了
            for _, card in ipairs(cardArray) do
            dictTokens = {}
                if card["卡组"] == "起始卡牌" then
            dictEntries = nil
                     html = html .. getCardHTML(frame, name, card) .. "<br><br>"
            dictColor = "green"
         elseif dictStr ~= "" and dictStr ~= baseDictStr then
            -- dict不为空,且与base不同,说明变体修改了
            dictTokens = parseDictTokens(dictStr)
            dictEntries = buildDictEntries(dictTokens)
            dictColor = "green"
        else
            -- dict为空或与base相同,说明是继承的
            dictTokens = parseDictTokens(dictStr)
            dictEntries = buildDictEntries(dictTokens)
            dictColor = "default"
        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 parsedCard then
                    if subCardData.base.sub then
                        parsedCard.subCards = p.parseSubCardData(subCardData.base.sub, dataModule, moduleInfo)
                    end
                     table.insert(subCards, parsedCard)
                end
            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 })
        if mergedData then
            local variantInfo = p.parseCardData(mergedData, moduleName, cardName, moduleInfo, {
                isVariant = true,
                baseApText = baseData.ap,
                baseDictStr = baseData.dict or ""
            })
            if variantInfo then
                if mergedData.sub then
                    variantInfo.subCards = p.parseSubCardData(mergedData.sub, dataModule, moduleInfo)
                 end
                 end
                table.insert(variants, variantInfo)
             end
             end
         end
         end
       
    end
        -- 显示第一张独特卡牌
    return variants
         for name, cardArray in pairs(cards) do
end
             for _, card in ipairs(cardArray) do
 
                 if card["卡组"] == "独特卡牌" then
-- 收集全部神光一闪变体
                    html = html .. getCardHTML(frame, name, card)
local function collectGodInspirationVariants(baseData, cardDataWrapper, moduleName, cardName, moduleInfo, dataModule)
                     return html
    local godGroups = {}
    local groupOrder = {}
 
    if not cardDataWrapper.var or not cardDataWrapper.var.god_inspiration then
         return godGroups, groupOrder
    end
 
    local fixedOrder = { "凯尔肯", "戴奥斯", "尼希隆", "赛克瑞德", "维托" }
 
    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 })
                 if mergedData then
                    local variantInfo = p.parseCardData(mergedData, moduleName, cardName, moduleInfo, {
                        isVariant = true,
                        baseApText = baseData.ap,
                        baseDictStr = baseData.dict or ""
                    })
                    if variantInfo then
                        if mergedData.sub then
                            variantInfo.subCards = p.parseSubCardData(mergedData.sub, dataModule, moduleInfo)
                        end
                        table.insert(list, variantInfo)
                     end
                 end
                 end
            end
            if #list > 0 then
                godGroups[groupName] = list
                table.insert(groupOrder, groupName)
             end
             end
         end
         end
       
        return html
     end
     end
      
 
     local cardData = cards[cardName]
     return godGroups, groupOrder
     if not cardData then
end
         return "找不到卡牌: " .. cardName
 
-- 主入口
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
 
    if not finalCardData then
        return err("错误: 最终卡牌数据为空")
    end
 
    local cardInfo = p.parseCardData(finalCardData, moduleName, cardName, moduleInfo, {
        isVariant = isVariantFlag,
        baseApText = baseData and baseData.ap or nil,
        baseDictStr = baseData and baseData.dict or ""
    })
    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 and next(godGroups) 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
     end
    return displayModule.render(cardInfo, subCardInfo)
end
function p.batch(frame)
    local args = getArgs(frame, { removeBlank = false })
    local html = {}
      
      
     -- 获取基础卡牌数据(第一个条目)
     -- 开始包裹容器
     local baseCard = cardData[1]
     table.insert(html, '<div style="display:flex;gap:10px;flex-wrap:wrap;">')
      
      
     -- 如果指定了卡组筛选
     -- 遍历所有参数组
     if deckFilter ~= "" then
     local i = 1
         local filteredCards = {}
    while args[i] do
         for _, card in ipairs(cardData) do
         local moduleName = args[i]
            if card["卡组"] == deckFilter then
         local cardName = args[i+1]
                table.insert(filteredCards, card)
        local variantType = args[i+2] or nil
            end
        local variantParam = args[i+3] or nil
         end
         local variantIndex = args[i+4] or nil
          
          
         -- 如果没有找到符合条件的卡牌
         if moduleName and cardName then
        if #filteredCards == 0 then
            -- 构造子 frame 调用 p.main
             return "找不到卡组 '" .. deckFilter .. "' 的卡牌: " .. cardName
            local subFrame = {
                args = {moduleName, cardName, variantType, variantParam, variantIndex},
                getParent = function() return frame end
            }
             table.insert(html, p.main(subFrame))
         end
         end
          
          
         -- 如果指定了索引
         i = i + 5  -- 每组5个参数(模块名,卡牌名,变体类型,变体参数,变体索引)
        if cardIndex > 0 and cardIndex <= #filteredCards then
            local mergedCard = mergeCardData(baseCard, filteredCards[cardIndex])
            return getCardHTML(frame, cardName, mergedCard)
        else
            -- 显示该卡组的所有卡牌
            local html = ""
            for i, card in ipairs(filteredCards) do
                local mergedCard = mergeCardData(baseCard, card)
                html = html .. getCardHTML(frame, cardName, mergedCard)
                if i < #filteredCards then
                    html = html .. "<br><br>"
                end
            end
            return html
        end
    else
        -- 如果没有指定卡组,就返回基础卡牌
        return getCardHTML(frame, cardName, baseCard)
     end
     end
   
    -- 结束包裹容器
    table.insert(html, '</div>')
   
    return table.concat(html, '')
end
end


-- 为每个战斗员创建一个直接调用方法
function p.batch(frame)
setmetatable(p, {
    local args = getArgs(frame, { removeBlank = false })
     __index = function(t, characterName)
     local html = {}
        return function(frame)
   
            local args = frame.args
    -- 开始包裹容器
            local cardName = args[1] or ""
    table.insert(html, '<div style="display:flex;gap:10px;flex-wrap:wrap;">')
            local deckFilter = args[2] or ""
   
            local cardIndex = tonumber(args[3]) or 0
    -- 遍历所有参数组
    local i = 1
    while args[i] do
        local moduleName = args[i]
        local cardName = args[i+1]
        local variantType = args[i+2]
        local variantParam = args[i+3]
        local variantIndex = args[i+4]
       
        if moduleName and cardName then
            -- 清理空字符串为 nil
            if variantType == "" then variantType = nil end
            if variantParam == "" then variantParam = nil end
            if variantIndex == "" then variantIndex = nil end
              
              
             -- 创建新的参数表来传递给 main 函数
             -- 构造子 frame 调用 p.main
             local newArgs = {
             local subArgs = {moduleName, cardName}
                [1] = characterName,
                [2] = cardName,
                [3] = deckFilter,
                [4] = cardIndex
            }
              
              
             -- 将其他参数也传递过去
             -- 只有在变体类型非空时才添加变体参数
             for k, v in pairs(args) do
             if variantType then
                if not newArgs[k] then
                table.insert(subArgs, variantType)
                     newArgs[k] = v
                if variantParam then
                    table.insert(subArgs, variantParam)
                    if variantIndex then
                        table.insert(subArgs, variantIndex)
                     end
                 end
                 end
             end
             end
              
              
            -- 调用主函数
             local subFrame = {
             local newFrame = {
                 args = subArgs,
                 args = newArgs,
                 getParent = function() return frame end
                 preprocess = frame.preprocess
             }
             }
              
             table.insert(html, p.main(subFrame))
            return p.main(newFrame)
         end
         end
       
        i = i + 5  -- 每组5个参数(模块名,卡牌名,变体类型,变体参数,变体索引)
     end
     end
})
   
    -- 结束包裹容器
    table.insert(html, '</div>')
   
    return table.concat(html, '')
end


return p
return p

2025年10月29日 (三) 09:07的最新版本

此模块的文档可以在模块:卡牌/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 8) then
        return mw.ustring.sub(s, 1, maxLen or 8) .. '...'
    end
    return s
end

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

    local tokens = {}
    local processedText = s

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

    -- 提取 {{文本|颜色|内容}} 形式的内容 token(保留为 template)
    for content in s:gmatch('{{%s*文本%s*|%s*[^|]+%s*|%s*([^}]+)%s*}}') do
        content = mw.text.trim(content or '')
        if content ~= '' then
            table.insert(tokens, { type = 'template', value = content })
        end
    end

    -- 移除所有模板,保留纯文本部分
    processedText = processedText:gsub('{{%s*词典%s*|%s*[^}]*}}', '')
    processedText = processedText:gsub('{{%s*文本%s*|%s*[^}]*}}', '')

    -- 处理剩余的纯文本(支持中文顿号和逗号)
    processedText = processedText:gsub('、', ','):gsub(',', ',')
    for token in processedText:gmatch('([^,]+)') do
        token = mw.text.trim(token or '')
        if token ~= '' then
            table.insert(tokens, { type = 'text', value = token })
        end
    end

    -- 去重,保持顺序
    local seen, out = {}, {}
    for _, t in ipairs(tokens) do
        local key = t.type .. ':' .. t.value
        if not seen[key] then
            seen[key] = 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=...}, ... }
-- tokens 可以是:字符串数组 或 结构化数组 [{type="text",value="x"}, {type="template",value="y"}]
local function buildDictEntries(tokens)
    if not tokens or #tokens == 0 then return nil end
    local entries = {}

    for _, t in ipairs(tokens) do
        local key = nil

        -- 判断 token 是否为结构化对象(新格式)
        if type(t) == "table" and t.type and t.value then
            key = t.value
        -- 判断 token 是否为字符串(旧格式兼容)
        elseif type(t) == "string" then
            key = t
        end

        -- 如果 key 为 nil,跳过这个 token
        if not key then
            -- 不做任何事,跳过
        else
            -- 去空格
            key = mw.text.trim(key)

            -- 如果去空格后为空,也跳过
            if key ~= '' then
                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]["描述"] or d[1].desc or desc
                    elseif type(d["描述"]) == 'string' then
                        desc = d["描述"]
                    elseif type(d.desc) == 'string' then
                        desc = d.desc
                    end
                end

                table.insert(entries, { key = key, desc = desc })
            end
        end
    end

    return #entries > 0 and entries or nil
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
--   baseDictStr: string
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
        -- 获取base的dict用于比较
        local baseDictStr = opts and opts.baseDictStr or ""
        baseDictStr = mw.text.trim(baseDictStr)
        
        if dictStr == "空" then
            -- 显式设置为"空",说明是变体修改了
            dictTokens = {}
            dictEntries = nil
            dictColor = "green"
        elseif dictStr ~= "" and dictStr ~= baseDictStr then
            -- dict不为空,且与base不同,说明变体修改了
            dictTokens = parseDictTokens(dictStr)
            dictEntries = buildDictEntries(dictTokens)
            dictColor = "green"
        else
            -- dict为空或与base相同,说明是继承的
            dictTokens = parseDictTokens(dictStr)
            dictEntries = buildDictEntries(dictTokens)
            dictColor = "default"
        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 parsedCard then
                    if subCardData.base.sub then
                        parsedCard.subCards = p.parseSubCardData(subCardData.base.sub, dataModule, moduleInfo)
                    end
                    table.insert(subCards, parsedCard)
                end
            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 })
        if mergedData then
            local variantInfo = p.parseCardData(mergedData, moduleName, cardName, moduleInfo, {
                isVariant = true,
                baseApText = baseData.ap,
                baseDictStr = baseData.dict or ""
            })
            if variantInfo then
                if mergedData.sub then
                    variantInfo.subCards = p.parseSubCardData(mergedData.sub, dataModule, moduleInfo)
                end
                table.insert(variants, variantInfo)
            end
        end
    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 godGroups, groupOrder
    end

    local fixedOrder = { "凯尔肯", "戴奥斯", "尼希隆", "赛克瑞德", "维托" }

    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 })
                if mergedData then
                    local variantInfo = p.parseCardData(mergedData, moduleName, cardName, moduleInfo, {
                        isVariant = true,
                        baseApText = baseData.ap,
                        baseDictStr = baseData.dict or ""
                    })
                    if variantInfo then
                        if mergedData.sub then
                            variantInfo.subCards = p.parseSubCardData(mergedData.sub, dataModule, moduleInfo)
                        end
                        table.insert(list, variantInfo)
                    end
                end
            end
            if #list > 0 then
                godGroups[groupName] = list
                table.insert(groupOrder, groupName)
            end
        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

    if not finalCardData then
        return err("错误: 最终卡牌数据为空")
    end

    local cardInfo = p.parseCardData(finalCardData, moduleName, cardName, moduleInfo, {
        isVariant = isVariantFlag,
        baseApText = baseData and baseData.ap or nil,
        baseDictStr = baseData and baseData.dict or ""
    })
    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 and next(godGroups) 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

function p.batch(frame)
    local args = getArgs(frame, { removeBlank = false })
    local html = {}
    
    -- 开始包裹容器
    table.insert(html, '<div style="display:flex;gap:10px;flex-wrap:wrap;">')
    
    -- 遍历所有参数组
    local i = 1
    while args[i] do
        local moduleName = args[i]
        local cardName = args[i+1]
        local variantType = args[i+2] or nil
        local variantParam = args[i+3] or nil
        local variantIndex = args[i+4] or nil
        
        if moduleName and cardName then
            -- 构造子 frame 调用 p.main
            local subFrame = {
                args = {moduleName, cardName, variantType, variantParam, variantIndex},
                getParent = function() return frame end
            }
            table.insert(html, p.main(subFrame))
        end
        
        i = i + 5  -- 每组5个参数(模块名,卡牌名,变体类型,变体参数,变体索引)
    end
    
    -- 结束包裹容器
    table.insert(html, '</div>')
    
    return table.concat(html, '')
end

function p.batch(frame)
    local args = getArgs(frame, { removeBlank = false })
    local html = {}
    
    -- 开始包裹容器
    table.insert(html, '<div style="display:flex;gap:10px;flex-wrap:wrap;">')
    
    -- 遍历所有参数组
    local i = 1
    while args[i] do
        local moduleName = args[i]
        local cardName = args[i+1]
        local variantType = args[i+2]
        local variantParam = args[i+3]
        local variantIndex = args[i+4]
        
        if moduleName and cardName then
            -- 清理空字符串为 nil
            if variantType == "" then variantType = nil end
            if variantParam == "" then variantParam = nil end
            if variantIndex == "" then variantIndex = nil end
            
            -- 构造子 frame 调用 p.main
            local subArgs = {moduleName, cardName}
            
            -- 只有在变体类型非空时才添加变体参数
            if variantType then
                table.insert(subArgs, variantType)
                if variantParam then
                    table.insert(subArgs, variantParam)
                    if variantIndex then
                        table.insert(subArgs, variantIndex)
                    end
                end
            end
            
            local subFrame = {
                args = subArgs,
                getParent = function() return frame end
            }
            table.insert(html, p.main(subFrame))
        end
        
        i = i + 5  -- 每组5个参数(模块名,卡牌名,变体类型,变体参数,变体索引)
    end
    
    -- 结束包裹容器
    table.insert(html, '</div>')
    
    return table.concat(html, '')
end

return p