卡牌:修订间差异
来自卡厄思梦境WIKI
无编辑摘要 标签:已被回退 |
小 文字替换 -“塞克瑞德”替换为“赛克瑞德” |
||
| (未显示同一用户的42个中间版本) | |||
| 第1行: | 第1行: | ||
local p = {} | local p = {} | ||
-- | -- 使用官方参数解析器,自动合并父/子 frame 参数、去空白与空值 | ||
local | local getArgs = require('Module:Arguments').getArgs | ||
-- | -- 渲染模块 | ||
local | local displayModule = require('Module:卡牌/display') | ||
-- | -- 安全的错误输出 | ||
local | local function err(msg) | ||
return '<span style="color: red;">' .. mw.text.encode(msg) .. '</span>' | |||
end | end | ||
-- | -- 标题截断:超过 maxLen 个字符用 ... 截断(按 Unicode 计数,兼容中英文) | ||
local function | local function ellipsisTitle(s, maxLen) | ||
if | s = tostring(s or '') | ||
return | local ulen = mw.ustring.len(s) | ||
if ulen > (maxLen or 8) then | |||
return mw.ustring.sub(s, 1, maxLen or 8) .. '...' | |||
end | end | ||
return s | |||
end | end | ||
-- | -- 解析 dict 字段 | ||
local function | local function parseDictTokens(dictStr) | ||
if | 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 | end | ||
-- | -- 提取 {{文本|颜色|内容}} 形式的内容 token(保留为 template) | ||
for content in s:gmatch('{{%s*文本%s*|%s*[^|]+%s*|%s*([^}]+)%s*}}') do | |||
content = mw.text.trim(content or '') | |||
if content ~= '' then | |||
for | table.insert(tokens, { type = 'template', value = content }) | ||
if | |||
table.insert( | |||
end | end | ||
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 | end | ||
-- | -- 去重,保持顺序 | ||
local | local seen, out = {}, {} | ||
for _, t in ipairs(tokens) do | |||
local key = t.type .. ':' .. t.value | |||
if not seen[key] then | |||
for _, | seen[key] = true | ||
table.insert(out, t) | |||
local | |||
table.insert( | |||
end | end | ||
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 | else | ||
dictData = dictModule | |||
end | end | ||
end | 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 | 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 | ||
end | end | ||
return #entries > 0 and entries or nil | |||
end | |||
local | |||
-- 合并 base 数据与变体数据 | |||
-- | local function mergeCardData(baseData, variantData, opts) | ||
if | 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 | ||
end | end | ||
return merged | |||
return | |||
end | end | ||
-- | -- 根据 base AP 与目标 AP 文本计算发光样式 | ||
local function | -- 返回: 'blue' | 'red' | 'green' | 'gray' | ||
if not | local function calcApStyle(apText, baseApText, isVariant) | ||
return | local text = apText ~= nil and tostring(apText) or '' | ||
if not isVariant then | |||
return 'blue' | |||
end | end | ||
if text == 'X' then | |||
return 'blue' | |||
local | end | ||
local | 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 | ||
end | end | ||
-- 其他内容(非数值且非X)不发光 | |||
return 'gray' | |||
return | |||
end | end | ||
-- | -- 将单张卡牌的数据转换为 cardInfo 结构(在此完成词典解析) | ||
-- opts: | |||
-- isVariant: boolean | |||
-- baseApText: string|number | |||
-- baseDictStr: string | |||
function p.parseCardData(cardData, moduleName, cardName, moduleInfo, opts) | |||
if not cardData then | if not cardData then | ||
return | return nil | ||
end | end | ||
local ego = "" | |||
if moduleInfo and moduleInfo.ego then | |||
ego = moduleInfo.ego | |||
elseif cardData.ego then | |||
ego = cardData.ego | |||
local | |||
if | |||
elseif | |||
end | 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) | ||
local | |||
local | -- 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 | |||
-- 词典解析 | |||
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 | end | ||
else | |||
dictTokens = parseDictTokens(dictStr) | |||
dictEntries = buildDictEntries(dictTokens) | |||
end | end | ||
local cardInfo = { | |||
moduleName = moduleName or "", | |||
local | cardName = cardName or "", | ||
displayTitle = displayTitle or "", | |||
displayTitleShort = displayTitleShort or "", | |||
art = cardData.art or "", | |||
ego = ego or "", | |||
cardName 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 | |||
return | |||
end | end | ||
-- | -- 解析衍生卡牌列表(支持递归) | ||
function p.parseSubCardData(subCardNames, dataModule, moduleInfo) | |||
if not subCardNames or not dataModule then | |||
return nil | |||
end | 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 | end | ||
if #subCards == 0 then | |||
return nil | |||
end | end | ||
return subCards | |||
return | |||
end | end | ||
-- | -- 收集全部灵光一闪变体 | ||
local function | local function collectInspirationVariants(baseData, cardDataWrapper, moduleName, cardName, moduleInfo, dataModule) | ||
local | local variants = {} | ||
if not cardDataWrapper.var or not cardDataWrapper.var.inspiration then | |||
return variants | |||
end | |||
for i, inspData in ipairs(cardDataWrapper.var.inspiration) do | |||
for | local mergedData = mergeCardData(baseData, inspData, { clearSubUnlessSpecified = true }) | ||
local | if mergedData then | ||
if | 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 | ||
end | end | ||
return variants | |||
end | |||
local | |||
for | -- 收集全部神光一闪变体 | ||
table. | 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 | end | ||
return | return godGroups, groupOrder | ||
end | end | ||
-- | -- 主入口 | ||
local | function p.main(frame) | ||
local | local args = getArgs(frame, { removeBlank = true }) | ||
local moduleName = args[1] | |||
if | local cardName = args[2] | ||
return | 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 | end | ||
local finalCardData = baseData | |||
local | local isVariantFlag = false | ||
if | 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 | ||
end | end | ||
if not finalCardData then | |||
return | 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 | end | ||
-- | -- 神光一闪按钮集合 | ||
if | local godFlag = (baseData.isgod_god_inspiration == 1) or (baseData.isgod_inspiration == 1) | ||
local | 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 | end | ||
-- | -- 主卡牌的衍生牌 | ||
local | local subCardInfo = nil | ||
if finalCardData.sub then | |||
subCardInfo = p.parseSubCardData(finalCardData.sub, dataModule, moduleInfo) | |||
end | end | ||
return | return displayModule.render(cardInfo, subCardInfo) | ||
end | end | ||
function p.batch(frame) | |||
function p. | local args = getArgs(frame, { removeBlank = false }) | ||
local args = frame | local html = {} | ||
local | |||
-- | -- 开始包裹容器 | ||
table.insert(html, '<div style="display:flex;gap:10px;flex-wrap:wrap;">') | |||
-- | -- 遍历所有参数组 | ||
if cardName == | 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 | end | ||
-- | -- 结束包裹容器 | ||
table.insert(html, '</div>') | |||
return | return table.concat(html, '') | ||
end | 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 | 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