模块

模块:卡牌

来自卡厄思梦境WIKI

律Rhyme留言 | 贡献2025年10月1日 (三) 18:34的版本

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

local p = {}

-- 安全获取嵌套表数据
local function safeGet(tbl, key, default)
    if tbl and tbl[key] ~= nil then
        return tbl[key]
    end
    return default or ""
end

-- 颜色映射表
local COLOR_MAP = {
    ["白"] = { bgColor = "#ffffff", textColor = "#333333" },
    ["蓝"] = { bgColor = "#4a90e2", textColor = "white" },
    ["紫"] = { bgColor = "#9b59b6", textColor = "white" },
    ["金"] = { bgColor = "#f1c40f", textColor = "#333333" }
}

-- 计算名称缩放比例
local function calculateNameScale(name)
    local length = mw.ustring.len(name)
    -- 基准是5个字符以内
    if length <= 5 then
        return "1"
    elseif length <= 7 then
        return "0.9"
    elseif length <= 9 then
        return "0.8"
    elseif length <= 11 then
        return "0.7"
    else
        return "0.6"
    end
end

-- 处理描述文本
local function processDescription(frame, description)
    if not description or description == "" then
        return ""
    end
    
    -- 处理换行符
    description = description:gsub("\r?\n", "<br>")
    
    -- 处理图标:{{图标名称}} 转换为对应图片
    description = description:gsub("{{(.-)}}", function(iconName)
        return string.format('[[File:icon_%s.png|16px|link=|style="vertical-align: -3px; margin: 0 1px;"]]', iconName)
    end)
    
    -- 处理高亮文本:<hl>文本</hl> 转换为金色
    description = description:gsub("<hl>(.-)</hl>", function(text)
        return string.format('<span style="color: #f1c832; font-weight: bold; text-shadow: 0 0 2px rgba(241, 200, 50, 0.5);">%s</span>', text)
    end)
    
    return frame:preprocess(description)
end

-- 生成卡牌HTML样式组件
local function buildStyleComponents(cardData, cardName, baseCardData)
    local color = safeGet(cardData, "稀有度", "白")
    local colorStyle = COLOR_MAP[color] or COLOR_MAP["白"]
    local attribute = safeGet(cardData, "属性", "虚无")
    local cardDeck = safeGet(cardData, "卡组", "起始卡牌")
    
    local ap = safeGet(cardData, "AP", "")
    local originalAP = baseCardData and safeGet(baseCardData, "AP", ap) or ap
    
    -- 决定AP的发光颜色
    local apGlowColor = "#4a90e2" -- 默认蓝色
    if baseCardData and ap ~= originalAP then
        if ap == "X" then
            apGlowColor = "#4a90e2" -- X值保持默认蓝色
        elseif tonumber(ap) and tonumber(originalAP) then
            if tonumber(ap) < tonumber(originalAP) then
                apGlowColor = "#51f651" -- 绿色(AP降低)
            else
                apGlowColor = "#f65151" -- 红色(AP增加)
            end
        end
    end
    
    -- 检查原卡牌与当前卡牌的机制变化(将"无"视为空字符串)
    local mechanism = safeGet(cardData, "机制", "")
    local originalMechanism = baseCardData and safeGet(baseCardData, "机制", "") or mechanism
    
    -- 将"无"视为空字符串
    if mechanism == "无" then mechanism = "" end
    if originalMechanism == "无" then originalMechanism = "" end
    
    local mechanismChanged = baseCardData and mechanism ~= "" and originalMechanism == ""
    
    return {
        color = color,
        colorStyle = colorStyle,
        attribute = attribute,
        ap = ap,
        apGlowColor = apGlowColor,
        cardType = safeGet(cardData, "类型", ""),
        art = safeGet(cardData, "art", "start_1041_01.png"),
        mechanism = mechanism,
        mechanismChanged = mechanismChanged
    }
end

-- 构建卡牌HTML
local function buildCardHTML(frame, cardName, cardData, baseCardData, characterName)
    if not cardData then
        return string.format("找不到卡牌数据: %s", cardName or "未指定")
    end
    
    local style = buildStyleComponents(cardData, cardName, baseCardData)
    local description = processDescription(frame, safeGet(cardData, "描述", ""))
    
    -- 获取衍生卡牌信息
    local derivedCards = safeGet(cardData, "衍生卡牌", "")
    local deckType = safeGet(cardData, "卡组", "")
    
    -- 生成数据属性
    local dataAttrs = string.format('data-card-name="%s" data-character="%s" data-deck-type="%s" data-derived-cards="%s"',
        cardName or "", characterName or "", deckType or "", derivedCards or "")
    
    -- 处理机制文本(当机制为"无"或空时不显示)
    local mechanismHTML = ""
    if style.mechanism and style.mechanism ~= "" then
        local mechanismColor = style.mechanismChanged and "#b5f651" or "#f6c478"
        mechanismHTML = string.format('<div style="color: %s; margin-bottom: 4px;">[%s]</div>', 
            mechanismColor, style.mechanism:gsub("、", "/"))
    end
    
    -- 计算名称缩放比例
    local nameScale = calculateNameScale(cardName)
    
    -- 使用字符串拼接优化HTML生成
    local htmlParts = {
        string.format('<div class="game-card" %s style="display: inline-block; vertical-align: top; position: relative; width: 168px; height: 230px; overflow: hidden; margin: 0px; cursor: pointer;">', dataAttrs),
    }
    
    -- 根据类型判断使用哪种布局
    if style.cardType == "状态异常" then
        -- 状态异常卡牌的特殊布局
        table.insert(htmlParts, string.format('<div style="position: absolute; top: 5px; left: 5px;">[[File:%s|150px|link=]]</div>', style.art))
        table.insert(htmlParts, string.format('<div style="position: absolute; top: 0px; left: 0px;">[[File:card_状态异常_%s.png|159px|link=]]</div>', style.attribute))
        
        -- AP值
        table.insert(htmlParts, string.format('<div style="position: absolute; left: 24px; top: 4px; color: white; font-weight: bold; text-shadow: 0 0 10px %s,0 0 20px %s, 0 0 30px %s, 0 0 40px %s; font-size: 32px;">%s</div>', 
            style.apGlowColor, style.apGlowColor, style.apGlowColor, style.apGlowColor, style.ap))
        table.insert(htmlParts, string.format('<div style="position: absolute; left: 25px; top: 34px; color: white; font-weight: bold; text-shadow: 0 0 10px %s,0 0 20px %s, 0 0 30px %s, 0 0 40px %s;">—</div>', 
            style.apGlowColor, style.apGlowColor, style.apGlowColor, style.apGlowColor))
        
        -- 卡牌名称(使用transform缩放)
        table.insert(htmlParts, string.format('<div style="position: absolute; left: 50px; top: 13px; width: 105px; transform-origin: left center; transform: scaleX(%s);"><span style="color: %s; font-size: 16px; white-space: nowrap;">%s</span></div>', 
            nameScale, style.colorStyle.textColor, cardName))
        
        -- 卡牌类型
        table.insert(htmlParts, string.format('<div style="position: absolute; left: 48px; top: 30px;">[[File:icon_card_%s.png|18px|link=]]</div>', 
            style.cardType))
        table.insert(htmlParts, string.format('<div style="position: absolute; left: 68px; top: 33px; color: white; font-size:14px">%s</div>', 
            style.cardType))
        
        -- 顶层蒙版
        table.insert(htmlParts, '<div style="position: absolute; left: 0px; bottom: 0px;">[[File:card_顶层蒙版.png|168px|link=]]</div>')
        
    else
        -- 普通卡牌的布局
        -- 背景图片层
        table.insert(htmlParts, string.format('<div style="position: absolute; top: 1px; left: 12px;">[[File:%s|151px|link=]]</div>', style.art))
        table.insert(htmlParts, '<div style="position: absolute; top: 1px; left: 12px;">[[File:card_黑色蒙版.png|151px|link=]]</div>')
        
        -- 颜色条
        table.insert(htmlParts, string.format('<div style="position: absolute; top: 21px; left: 10px; width: 148px; height: 8px; background-color: %s;"></div>', 
            style.colorStyle.bgColor))
        
        -- AP值
        table.insert(htmlParts, string.format('<div style="position: absolute; left: 24px; top: 4px; color: white; font-weight: bold; text-shadow: 0 0 10px %s,0 0 20px %s, 0 0 30px %s, 0 0 40px %s; font-size: 32px;">%s</div>', 
            style.apGlowColor, style.apGlowColor, style.apGlowColor, style.apGlowColor, style.ap))
        table.insert(htmlParts, string.format('<div style="position: absolute; left: 25px; top: 34px; color: white; font-weight: bold; text-shadow: 0 0 10px %s,0 0 20px %s, 0 0 30px %s, 0 0 40px %s;">—</div>', 
            style.apGlowColor, style.apGlowColor, style.apGlowColor, style.apGlowColor))
        
        -- 卡牌名称(使用transform缩放)
        table.insert(htmlParts, string.format('<div style="position: absolute; left: 50px; top: 13px; width: 105px; transform-origin: left center; transform: scaleX(%s);"><span style="color: %s; font-size: 16px; white-space: nowrap;">%s</span></div>', 
            nameScale, style.colorStyle.textColor, cardName))
        
        -- 卡牌类型
        table.insert(htmlParts, string.format('<div style="position: absolute; left: 48px; top: 30px;">[[File:icon_card_%s.png|18px|link=]]</div>', 
            style.cardType))
        table.insert(htmlParts, string.format('<div style="position: absolute; left: 68px; top: 33px; color: white; font-size:14px">%s</div>', 
            style.cardType))
        
        -- 边框
        table.insert(htmlParts, string.format('<div style="position: absolute; top: 0px; left: 1px;">[[File:card_属性边框_%s.png|160px|link=]]</div>', 
            style.attribute))
        -- 稀有度
        table.insert(htmlParts, string.format('<div style="position: absolute; top: 10px; left: 0px;">[[File:card_稀有度_%s.png|22px|link=]]</div>', 
            style.color))
        table.insert(htmlParts, string.format('<div style="position: absolute; top: 2px; right: 4px;">[[File:card_稀有度_边框_%s.png|11px|link=]]</div>', 
            style.color))
        
        -- 顶层蒙版
        table.insert(htmlParts, '<div style="position: absolute; left: 0px; bottom: 0px;">[[File:card_顶层蒙版.png|168px|link=]]</div>')
    end
    
    -- 描述文本(包含机制)- 对所有类型都显示
    table.insert(htmlParts, '<div style="position: absolute; bottom: 7px; left: 15px; width: 144px; height: 100px; font-size: 12px; color: white; line-height: 13px; display: flex; justify-content: center; align-items: center; text-align: center;">')
    table.insert(htmlParts, '<div style="line-height: 13px;">')
    table.insert(htmlParts, mechanismHTML)
    table.insert(htmlParts, description)
    table.insert(htmlParts, '</div>')
    table.insert(htmlParts, '</div>')
    table.insert(htmlParts, '</div>')
    
    return table.concat(htmlParts)
end

-- 获取基础卡牌数据(用于对比)
local function getBaseCardData(cardName)
    local data = mw.loadData("模块:卡牌数据")
    return data[cardName]
end

-- 主函数:显示卡牌
function p.displayCard(frame)
    local args = frame.args
    local cardName = args[1] or args["name"]
    local characterName = args[2] or args["character"] or ""
    local deckType = args[3] or args["deck"] or ""
    
    if not cardName then
        return "错误:未指定卡牌名称"
    end
    
    -- 优先从角色专属数据加载
    local cardData = nil
    local baseCardData = nil
    
    if characterName ~= "" then
        local charDataModule = "模块:卡牌数据/" .. characterName
        local success, charData = pcall(mw.loadData, charDataModule)
        if success then
            -- 根据卡组类型查找
            if deckType ~= "" and charData[deckType] then
                cardData = charData[deckType][cardName]
            else
                -- 遍历所有卡组查找
                for _, deck in pairs(charData) do
                    if type(deck) == "table" and deck[cardName] then
                        cardData = deck[cardName]
                        break
                    end
                end
            end
        end
        
        -- 如果是变体卡牌,获取基础卡牌数据
        if cardData then
            baseCardData = getBaseCardData(cardName)
        end
    end
    
    -- 如果没有找到专属数据,从通用数据加载
    if not cardData then
        local data = mw.loadData("模块:卡牌数据")
        cardData = data[cardName]
    end
    
    if not cardData then
        return string.format("错误:找不到卡牌 '%s' 的数据", cardName)
    end
    
    return buildCardHTML(frame, cardName, cardData, baseCardData, characterName)
end

-- 批量显示卡牌
function p.displayCards(frame)
    local args = frame.args
    local cardList = args[1] or args["cards"] or ""
    local characterName = args[2] or args["character"] or ""
    local columns = tonumber(args[3] or args["columns"]) or 5
    
    if cardList == "" then
        return "错误:未指定卡牌列表"
    end
    
    -- 解析卡牌列表
    local cards = {}
    for card in mw.text.gsplit(cardList, ",") do
        card = mw.text.trim(card)
        if card ~= "" then
            table.insert(cards, card)
        end
    end
    
    if #cards == 0 then
        return "错误:卡牌列表为空"
    end
    
    -- 构建HTML
    local html = {}
    table.insert(html, '<div style="display: flex; flex-wrap: wrap; gap: 10px;">')
    
    for i, cardName in ipairs(cards) do
        -- 获取卡牌数据
        local cardData = nil
        local baseCardData = nil
        
        if characterName ~= "" then
            local charDataModule = "模块:卡牌数据/" .. characterName
            local success, charData = pcall(mw.loadData, charDataModule)
            if success then
                for _, deck in pairs(charData) do
                    if type(deck) == "table" and deck[cardName] then
                        cardData = deck[cardName]
                        break
                    end
                end
            end
            
            if cardData then
                baseCardData = getBaseCardData(cardName)
            end
        end
        
        if not cardData then
            local data = mw.loadData("模块:卡牌数据")
            cardData = data[cardName]
        end
        
        if cardData then
            table.insert(html, buildCardHTML(frame, cardName, cardData, baseCardData, characterName))
        else
            table.insert(html, string.format('<div style="color: red;">找不到卡牌: %s</div>', cardName))
        end
        
        -- 添加换行
        if i % columns == 0 and i < #cards then
            table.insert(html, '</div><div style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px;">')
        end
    end
    
    table.insert(html, '</div>')
    
    return table.concat(html)
end

-- 显示角色的所有卡牌
function p.displayCharacterCards(frame)
    local args = frame.args
    local characterName = args[1] or args["character"]
    local deckType = args[2] or args["deck"] or "all"
    local columns = tonumber(args[3] or args["columns"]) or 5
    
    if not characterName then
        return "错误:未指定角色名称"
    end
    
    local charDataModule = "模块:卡牌数据/" .. characterName
    local success, charData = pcall(mw.loadData, charDataModule)
    
    if not success then
        return string.format("错误:找不到角色 '%s' 的卡牌数据", characterName)
    end
    
    -- 收集要显示的卡牌
    local cardsToDisplay = {}
    
    if deckType == "all" then
        -- 显示所有卡组
        local deckOrder = {"起始卡牌", "基础卡牌", "高级卡牌", "其他卡牌"}
        for _, dName in ipairs(deckOrder) do
            if charData[dName] then
                for cardName, cardData in pairs(charData[dName]) do
                    table.insert(cardsToDisplay, {name = cardName, data = cardData, deck = dName})
                end
            end
        end
    else
        -- 显示特定卡组
        if charData[deckType] then
            for cardName, cardData in pairs(charData[deckType]) do
                table.insert(cardsToDisplay, {name = cardName, data = cardData, deck = deckType})
            end
        else
            return string.format("错误:角色 '%s' 没有 '%s' 卡组", characterName, deckType)
        end
    end
    
    -- 排序(可选:按稀有度、AP值等排序)
    table.sort(cardsToDisplay, function(a, b)
        return a.name < b.name
    end)
    
    -- 构建HTML
    local html = {}
    table.insert(html, '<div style="display: flex; flex-wrap: wrap; gap: 10px;">')
    
    for i, card in ipairs(cardsToDisplay) do
        local baseCardData = getBaseCardData(card.name)
        table.insert(html, buildCardHTML(frame, card.name, card.data, baseCardData, characterName))
        
        if i % columns == 0 and i < #cardsToDisplay then
            table.insert(html, '</div><div style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px;">')
        end
    end
    
    table.insert(html, '</div>')
    
    return table.concat(html)
end

-- 搜索卡牌
function p.searchCards(frame)
    local args = frame.args
    local searchTerm = args[1] or args["search"] or ""
    local searchType = args[2] or args["type"] or "name" -- name, description, mechanism
    local columns = tonumber(args[3] or args["columns"]) or 5
    
    if searchTerm == "" then
        return "错误:未指定搜索词"
    end
    
    searchTerm = mw.ustring.lower(searchTerm)
    
    local data = mw.loadData("模块:卡牌数据")
    local results = {}
    
    for cardName, cardData in pairs(data) do
        local match = false
        
        if searchType == "name" then
            match = mw.ustring.find(mw.ustring.lower(cardName), searchTerm) ~= nil
        elseif searchType == "description" then
            local desc = safeGet(cardData, "描述", "")
            match = mw.ustring.find(mw.ustring.lower(desc), searchTerm) ~= nil
        elseif searchType == "mechanism" then
            local mech = safeGet(cardData, "机制", "")
            match = mw.ustring.find(mw.ustring.lower(mech), searchTerm) ~= nil
        elseif searchType == "all" then
            local nameMatch = mw.ustring.find(mw.ustring.lower(cardName), searchTerm) ~= nil
            local descMatch = mw.ustring.find(mw.ustring.lower(safeGet(cardData, "描述", "")), searchTerm) ~= nil
            local mechMatch = mw.ustring.find(mw.ustring.lower(safeGet(cardData, "机制", "")), searchTerm) ~= nil
            match = nameMatch or descMatch or mechMatch
        end
        
        if match then
            table.insert(results, {name = cardName, data = cardData})
        end
    end
    
    if #results == 0 then
        return "没有找到符合条件的卡牌"
    end
    
    -- 排序
    table.sort(results, function(a, b)
        return a.name < b.name
    end)
    
    -- 构建HTML
    local html = {}
    table.insert(html, string.format('<div>找到 %d 张卡牌:</div>', #results))
    table.insert(html, '<div style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px;">')
    
    for i, result in ipairs(results) do
        table.insert(html, buildCardHTML(frame, result.name, result.data, nil, ""))
        
        if i % columns == 0 and i < #results then
            table.insert(html, '</div><div style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px;">')
        end
    end
    
    table.insert(html, '</div>')
    
    return table.concat(html)
end

return p