卡牌:修订间差异
来自卡厄思梦境WIKI
无编辑摘要 标签:已被回退 |
无编辑摘要 标签:已被回退 |
||
| 第1行: | 第1行: | ||
-- 卡牌 | -- Module:卡牌 | ||
local p = {} | local p = {} | ||
| 第11行: | 第11行: | ||
local function err(msg) | local function err(msg) | ||
return '<span style="color: red;">' .. mw.text.encode(msg) .. '</span>' | return '<span style="color: red;">' .. mw.text.encode(msg) .. '</span>' | ||
end | |||
-- 保证字符串为有效 UTF-8;若无效则仅保留 ASCII 字节,避免 gsub/find 报错 | |||
local function ensureUtf8(s) | |||
s = tostring(s or '') | |||
local ok = pcall(function() return mw.ustring.len(s) end) | |||
if ok then return s end | |||
local out = {} | |||
for i = 1, #s do | |||
local b = string.byte(s, i) | |||
if b and b <= 0x7F then | |||
out[#out+1] = string.char(b) | |||
end | |||
end | |||
return table.concat(out) | |||
end | end | ||
| 第25行: | 第40行: | ||
-- 解析纯文本片段中的词条(支持中文/英文常见分隔符) | -- 解析纯文本片段中的词条(支持中文/英文常见分隔符) | ||
local function parsePlainTokensFromSegment(seg) | local function parsePlainTokensFromSegment(seg) | ||
local s = mw.text.trim( | local s = ensureUtf8(seg or '') | ||
s = mw.text.trim(s) | |||
if s == '' then return {} end | if s == '' then return {} end | ||
s = s:gsub('[、,;;//|]', ',') | s = s:gsub('[、,;;//|]', ',') | ||
local tokens = {} | local tokens = {} | ||
| 第42行: | 第57行: | ||
-- 示例:dict = "无法使用、{{词典|蒸发}}" → tokens = {"无法使用", "蒸发"} | -- 示例:dict = "无法使用、{{词典|蒸发}}" → tokens = {"无法使用", "蒸发"} | ||
local function parseDictTokens(dictStr) | local function parseDictTokens(dictStr) | ||
local s = | local s = ensureUtf8(dictStr or '') | ||
if mw.text.trim(s) == '' then return {} end | if mw.text.trim(s) == '' then return {} end | ||
| 第48行: | 第63行: | ||
local pos = 1 | local pos = 1 | ||
while true do | while true do | ||
local startPos, endPos, inner = s:find('{{%s*词典%s*|%s*(.-)%s*}}', pos) | local startPos, endPos, inner = s:find('{{%s*词典%s*|%s*(.-)%s*}}', pos) | ||
if startPos then | if startPos then | ||
local plainSeg = s:sub(pos, startPos - 1) | local plainSeg = s:sub(pos, startPos - 1) | ||
local plainTokens = parsePlainTokensFromSegment(plainSeg) | local plainTokens = parsePlainTokensFromSegment(plainSeg) | ||
| 第57行: | 第70行: | ||
table.insert(tokens, t) | table.insert(tokens, t) | ||
end | end | ||
local key = inner and inner:match('([^|]+)') or inner | local key = inner and inner:match('([^|]+)') or inner | ||
key = mw.text.trim(key or '') | key = mw.text.trim(key or '') | ||
| 第64行: | 第75行: | ||
table.insert(tokens, key) | table.insert(tokens, key) | ||
end | end | ||
pos = endPos + 1 | pos = endPos + 1 | ||
else | else | ||
local plainSeg = s:sub(pos) | local plainSeg = s:sub(pos) | ||
local plainTokens = parsePlainTokensFromSegment(plainSeg) | local plainTokens = parsePlainTokensFromSegment(plainSeg) | ||
| 第109行: | 第118行: | ||
local d = dictData and dictData[key] | local d = dictData and dictData[key] | ||
if type(d) == 'table' then | if type(d) == 'table' then | ||
if d[1] and type(d[1]) == 'table' then | if d[1] and type(d[1]) == 'table' then | ||
desc = d[1].desc or desc | desc = d[1].desc or desc | ||
| 第125行: | 第133行: | ||
if not baseData then return nil end | if not baseData then return nil end | ||
local merged = {} | local merged = {} | ||
for k, v in pairs(baseData) do | for k, v in pairs(baseData) do | ||
merged[k] = v | merged[k] = v | ||
end | end | ||
if opts and opts.clearSubUnlessSpecified then | if opts and opts.clearSubUnlessSpecified then | ||
merged.sub = nil | merged.sub = nil | ||
end | end | ||
if variantData then | if variantData then | ||
for k, v in pairs(variantData) do | for k, v in pairs(variantData) do | ||
| 第143行: | 第148行: | ||
-- 根据 base AP 与目标 AP 文本计算发光样式 | -- 根据 base AP 与目标 AP 文本计算发光样式 | ||
local function calcApStyle(apText, baseApText, isVariant) | local function calcApStyle(apText, baseApText, isVariant) | ||
local text = apText ~= nil and tostring(apText) or '' | local text = apText ~= nil and tostring(apText) or '' | ||
| 第163行: | 第167行: | ||
end | end | ||
end | end | ||
return 'gray' | return 'gray' | ||
end | end | ||
-- 将单张卡牌的数据转换为 cardInfo 结构(在此完成词典解析) | -- 将单张卡牌的数据转换为 cardInfo 结构(在此完成词典解析) | ||
function p.parseCardData(cardData, moduleName, cardName, moduleInfo, opts) | function p.parseCardData(cardData, moduleName, cardName, moduleInfo, opts) | ||
if not cardData then | if not cardData then | ||
| 第183行: | 第183行: | ||
end | end | ||
local displayTitle = (cardData.displayname and mw.text.trim(tostring(cardData.displayname)) ~= '' and cardData.displayname) or cardName 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) | local displayTitleShort = ellipsisTitle((displayTitle ~= '' and displayTitle) or cardName, 5) | ||
local apText = cardData.ap ~= nil and tostring(cardData.ap) or '' | local apText = cardData.ap ~= nil and tostring(cardData.ap) or '' | ||
local isVariant = opts and opts.isVariant or false | local isVariant = opts and opts.isVariant or false | ||
| 第194行: | 第191行: | ||
local apStyle = calcApStyle(apText, baseApText, isVariant) | local apStyle = calcApStyle(apText, baseApText, isVariant) | ||
local rawDictStr = cardData.dict or "" | local rawDictStr = cardData.dict or "" | ||
local dictStr = mw.text.trim(rawDictStr) | local dictStr = mw.text.trim(ensureUtf8(rawDictStr)) | ||
local dictTokens | local dictTokens | ||
local dictEntries | local dictEntries | ||
| 第421行: | 第417行: | ||
end | end | ||
if not variantType and cardDataWrapper.var and cardDataWrapper.var.inspiration and (baseData.isinspiration == 1) then | if not variantType and cardDataWrapper.var and cardDataWrapper.var.inspiration and (baseData.isinspiration == 1) then | ||
cardInfo.hasInspiration = true | cardInfo.hasInspiration = true | ||
| 第427行: | 第422行: | ||
end | end | ||
local godFlag = (baseData.isgod_god_inspiration == 1) or (baseData.isgod_inspiration == 1) | 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 | if not variantType and godFlag and cardDataWrapper.var and cardDataWrapper.var.god_inspiration then | ||
| 第438行: | 第432行: | ||
end | end | ||
local subCardInfo = nil | local subCardInfo = nil | ||
if finalCardData.sub then | if finalCardData.sub then | ||
| 第450行: | 第443行: | ||
local args = getArgs(frame, { removeBlank = false }) | local args = getArgs(frame, { removeBlank = false }) | ||
local html = {} | local html = {} | ||
table.insert(html, '<div style="display:flex;gap:10px;flex-wrap:wrap;">') | table.insert(html, '<div style="display:flex;gap:10px;flex-wrap:wrap;">') | ||
local i = 1 | local i = 1 | ||
while args[i] do | while args[i] do | ||
| 第464行: | 第453行: | ||
if moduleName and cardName then | if moduleName and cardName then | ||
local subFrame = { | local subFrame = { | ||
args = {moduleName, cardName, variantType, variantParam, variantIndex}, | args = {moduleName, cardName, variantType, variantParam, variantIndex}, | ||
| 第472行: | 第460行: | ||
end | end | ||
i = i + 5 | i = i + 5 | ||
end | end | ||
table.insert(html, '</div>') | table.insert(html, '</div>') | ||
return table.concat(html, '') | return table.concat(html, '') | ||
end | end | ||
return p | return p | ||
2025年10月16日 (四) 13:33的版本
此模块的文档可以在模块:卡牌/doc创建
-- Module:卡牌
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
-- 保证字符串为有效 UTF-8;若无效则仅保留 ASCII 字节,避免 gsub/find 报错
local function ensureUtf8(s)
s = tostring(s or '')
local ok = pcall(function() return mw.ustring.len(s) end)
if ok then return s end
local out = {}
for i = 1, #s do
local b = string.byte(s, i)
if b and b <= 0x7F then
out[#out+1] = string.char(b)
end
end
return table.concat(out)
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
-- 解析纯文本片段中的词条(支持中文/英文常见分隔符)
local function parsePlainTokensFromSegment(seg)
local s = ensureUtf8(seg or '')
s = mw.text.trim(s)
if s == '' then return {} end
s = s:gsub('[、,;;//|]', ',')
local tokens = {}
for token in s:gmatch('([^,]+)') do
token = mw.text.trim(token or '')
if token ~= '' then
table.insert(tokens, token)
end
end
return tokens
end
-- 解析 dict 字段(保持原始顺序,模板与纯文本混合)
-- 示例:dict = "无法使用、{{词典|蒸发}}" → tokens = {"无法使用", "蒸发"}
local function parseDictTokens(dictStr)
local s = ensureUtf8(dictStr or '')
if mw.text.trim(s) == '' then return {} end
local tokens = {}
local pos = 1
while true do
local startPos, endPos, inner = s:find('{{%s*词典%s*|%s*(.-)%s*}}', pos)
if startPos then
local plainSeg = s:sub(pos, startPos - 1)
local plainTokens = parsePlainTokensFromSegment(plainSeg)
for _, t in ipairs(plainTokens) do
table.insert(tokens, t)
end
local key = inner and inner:match('([^|]+)') or inner
key = mw.text.trim(key or '')
if key ~= '' then
table.insert(tokens, key)
end
pos = endPos + 1
else
local plainSeg = s:sub(pos)
local plainTokens = parsePlainTokensFromSegment(plainSeg)
for _, t in ipairs(plainTokens) do
table.insert(tokens, t)
end
break
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 = {}
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 文本计算发光样式
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
return 'gray'
end
-- 将单张卡牌的数据转换为 cardInfo 结构(在此完成词典解析)
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
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 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(ensureUtf8(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 = "green"
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 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
})
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 = { "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 })
if mergedData then
local variantInfo = p.parseCardData(mergedData, moduleName, cardName, moduleInfo, {
isVariant = true,
baseApText = baseData.ap
})
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
})
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
local subFrame = {
args = {moduleName, cardName, variantType, variantParam, variantIndex},
getParent = function() return frame end
}
table.insert(html, p.main(subFrame))
end
i = i + 5
end
table.insert(html, '</div>')
return table.concat(html, '')
end
return p