维护提醒

BWIKI 全站将于 9 月 3 日(全天)进行维护,期间无法编辑任何页面或发布新的评论。

全站通知:

模块:Theater

来自星露谷物语维基
跳到导航 跳到搜索
[ 创建 | 刷新 ]文档页面
当前模块文档缺失,需要扩充。
local Helper = require('Module:Helper')
local NPC = require("Module:NPC")
local p = {}
local createMovieQuote
local createMovieQuotes
local moviesData = Helper.LazyLoad('Module:Theater/data/movies')
local concessionsData = Helper.LazyLoad('Module:Theater/data/concessions')
local concessionTastesData = Helper.LazyLoad('Module:Theater/data/concession tastes')
local moviesReactionsData = Helper.LazyLoad('Module:Theater/data/movies reactions')
local talkData = Helper.LazyLoad('Module:Talk/data')
local movieNames = {
    ["spring_movie_0"] = "勇敢的小树苗",
    ["summer_movie_0"] = "草原之王之旅:大电影",
    ["fall_movie_0"] = "神秘事迹",
    ["winter_movie_0"] = "冷星牧场的奇迹",
    ["spring_movie_1"] = "自然奇观:探索我们这充满活力的世界",
    ["summer_movie_1"] = "温布斯",
    ["fall_movie_1"] = "它在雨中嚎叫",
    ["winter_movie_1"] = "祖祖城特快列车"
}
local concessionNames = {
    ["0"] = "棉花糖",
    ["1"] = "茉莉花茶",
    ["2"] = "Joja 可乐",
    ["3"] = "酸味史莱姆",
    ["4"] = "个人披萨",
    ["5"] = "芝士玉米片",
    ["6"] = "鲑鱼汉堡",
    ["7"] = "冰淇淋三明治",
    ["8"] = "爆米花",
    ["9"] = "薯条",
    ["10"] = "巧克力爆米花",
    ["11"] = "黑甘草糖",
    ["12"] = "星形饼干",
    ["13"] = "大糖球",
    ["14"] = "盐渍花生",
    ["15"] = "鹰嘴豆泥小吃包",
    ["16"] = "羽衣甘蓝汁",
    ["17"] = "苹果脆片",
    ["18"] = "意式面包沙拉",
    ["19"] = "松露爆米花",
    ["20"] = "卡布奇诺慕斯蛋糕",
    ["21"] = "Joja 玉米",
    ["22"] = "星之果实冰糕",
    ["23"] = "糖冰棍"
}
local movieNamesEn = {
    ["spring_movie_0"] = "The Brave Little Sapling",
    ["summer_movie_0"] = "Journey Of The Prairie King: The Motion Picture",
    ["fall_movie_0"] = "Mysterium",
    ["winter_movie_0"] = "The Miracle At Coldstar Ranch",
    ["spring_movie_1"] = "Natural Wonders: Exploring Our Vibrant World",
    ["summer_movie_1"] = "Wumbus",
    ["fall_movie_1"] = "It Howls In The Rain",
    ["winter_movie_1"] = "The Zuzu City Express"
}
local concessionNamesEn = {
    ["0"] = "Cotton Candy",
    ["1"] = "Jasmine Tea",
    ["2"] = "Joja Cola",
    ["3"] = "Sour Slimes",
    ["4"] = "Personal Pizza",
    ["5"] = "Nachos",
    ["6"] = "Salmon Burger",
    ["7"] = "Ice Cream Sandwich",
    ["8"] = "Popcorn",
    ["9"] = "Fries",
    ["10"] = "Chocolate Popcorn",
    ["11"] = "Black Licorice",
    ["12"] = "Star Cookie",
    ["13"] = "Rock Candy",
    ["14"] = "Salted Peanuts",
    ["15"] = "Hummus Snack Pack",
    ["16"] = "Kale Smoothie",
    ["17"] = "Apple Slices",
    ["18"] = "Panzanella Salad",
    ["19"] = "Truffle Popcorn",
    ["20"] = "Cappuccino Mousse Cake",
    ["21"] = "Joja Corn",
    ["22"] = "Stardrop Sorbet",
    ["23"] = "Rock Candy"
}
local function getCharacterName(character)
    if not character then return "" end
    character = character:gsub("^%l", string.upper)
    local chineseName = NPC.getChineseName(character)
    if chineseName then return chineseName end
    return character
end
local function getMovieTags(movieId)
    for _, movie in ipairs(moviesData) do
        if movie.Id == movieId then return movie.Tags or {} end
    end
    return {}
end
local function getConcessionTags(concessionId)
    for _, concession in ipairs(concessionsData) do
        if concession.Id == concessionId then
            return concession.ItemTags or {}
        end
    end
    return {}
end
local function calculateMoviePreference(npcName, movieId)
    local movieTags = getMovieTags(movieId)
    local npcReactions = nil
    for _, npcData in ipairs(moviesReactionsData) do
        if npcData.NPCName == npcName then
            npcReactions = npcData.Reactions
            break
        end
    end
    if not npcReactions then return "like" end
    for _, reaction in ipairs(npcReactions) do
        if reaction.Tag == movieId then return reaction.Response end
    end
    for _, tag in ipairs(movieTags) do
        for _, reaction in ipairs(npcReactions) do
            if reaction.Tag == tag then return reaction.Response end
        end
    end
    for _, reaction in ipairs(npcReactions) do
        if reaction.Tag == "*" then return reaction.Response end
    end
    return "like"
end
local function calculateConcessionPreference(npcName, concessionId)
    local concessionTags = getConcessionTags(concessionId)
    local concessionName = nil
    for _, concession in ipairs(concessionsData) do
        if concession.Id == concessionId then
            concessionName = concession.Name
            break
        end
    end
    local npcTastes = nil
    local universalTastes = nil
    for _, tasteData in ipairs(concessionTastesData) do
        if tasteData.Name == npcName then
            npcTastes = tasteData
        elseif tasteData.Name == "*" then
            universalTastes = tasteData
        end
    end
    local function checkPreference(tastes, tags, concessionName)
        if not tastes then return nil end
        if concessionName then
            for _, disliked in ipairs(tastes.DislikedTags or {}) do
                if disliked == concessionName then
                    return "dislike"
                end
            end
            for _, loved in ipairs(tastes.LovedTags or {}) do
                if loved == concessionName then return "love" end
            end
            for _, liked in ipairs(tastes.LikedTags or {}) do
                if liked == concessionName then return "like" end
            end
        end
        for _, tag in ipairs(tags) do
            for _, disliked in ipairs(tastes.DislikedTags or {}) do
                if disliked == tag then return "dislike" end
            end
            for _, loved in ipairs(tastes.LovedTags or {}) do
                if loved == tag then return "love" end
            end
            for _, liked in ipairs(tastes.LikedTags or {}) do
                if liked == tag then return "like" end
            end
        end
        return nil
    end
    local npcPreference = checkPreference(npcTastes, concessionTags,
                                          concessionName)
    if npcPreference then return npcPreference end
    local universalPreference = checkPreference(universalTastes, concessionTags,
                                                concessionName)
    if universalPreference then return universalPreference end
    return "like"
end
local function getMovieYearSeason(movieId)
    for _, movie in ipairs(moviesData) do
        if movie.Id == movieId then
            local yearText = ""
            local seasonText = ""
            if movie.YearRemainder == 0 then
                yearText = "第一年"
            elseif movie.YearRemainder == 1 then
                yearText = "第二年"
            else
                yearText = "第" .. (movie.YearRemainder + 1) .. "年"
            end
            if movie.Seasons then
                local seasons = {}
                for i, season in ipairs(movie.Seasons) do
                    local cleanSeason = tostring(season):gsub("^%s*(.-)%s*$",
                                                              "%1")
                    if cleanSeason then
                        table.insert(seasons,
                                     '<br><div style="white-space: nowrap;">' ..
                                         Helper.ExpandTemplate("Season",
                                                               {cleanSeason}) ..
                                         '</div>')
                    end
                end
                seasonText = table.concat(seasons, "、")
            end
            return yearText .. seasonText
        end
    end
    return ""
end
function p.getNPCMoviePreferences(frame)
    local npcName = frame.args[1] or frame.args.npc
    if not npcName then return "<!-- 错误:请提供NPC名称 -->" end
    local displayName = npcName
    local englishName = npcName
    if NPC.getEnglishName(npcName) then
        englishName = NPC.getEnglishName(npcName)
    else
        displayName = getCharacterName(npcName)
    end
    local loved = {}
    local liked = {}
    local disliked = {}
    for _, movie in ipairs(moviesData) do
        local preference = calculateMoviePreference(englishName, movie.Id)
        local movieName = movieNames[movie.Id] or movie.Id
        local yearSeason = getMovieYearSeason(movie.Id)
        local movieDisplayName = movieName .. "(" .. yearSeason .. ")"
        if preference == "love" then
            table.insert(loved, movieDisplayName)
        elseif preference == "like" then
            table.insert(liked, movieDisplayName)
        elseif preference == "dislike" then
            table.insert(disliked, movieDisplayName)
        end
    end
    local result = "=== " .. displayName .. "的电影喜好 ===\n"
    if #loved > 0 then
        result = result .. "'''最爱:'''" .. table.concat(loved, "、") ..
                     "\n\n"
    end
    if #liked > 0 then
        result = result .. "'''喜欢:'''" .. table.concat(liked, "、") ..
                     "\n\n"
    end
    if #disliked > 0 then
        result =
            result .. "'''不喜欢:'''" .. table.concat(disliked, "、") ..
                "\n\n"
    end
    return result
end
function p.getNPCConcessionPreferences(frame)
    local npcName = frame.args[1] or frame.args.npc
    if not npcName then return "<!-- 错误:请提供NPC名称 -->" end
    local displayName = npcName
    local englishName = npcName
    if NPC.getEnglishName(npcName) then
        englishName = NPC.getEnglishName(npcName)
    else
        displayName = getCharacterName(npcName)
    end
    local loved = {}
    local liked = {}
    local disliked = {}
    for _, concession in ipairs(concessionsData) do
        local preference = calculateConcessionPreference(englishName,
                                                         concession.Id)
        local concessionName = concessionNames[concession.Id] or concession.Name
        if preference == "love" then
            table.insert(loved, concessionName)
        elseif preference == "like" then
            table.insert(liked, concessionName)
        elseif preference == "dislike" then
            table.insert(disliked, concessionName)
        end
    end
    local result = "=== " .. displayName .. "的零食喜好 ===\n"
    if #loved > 0 then
        result = result .. "'''最爱:'''" .. table.concat(loved, "、") ..
                     "\n\n"
    end
    if #liked > 0 then
        result = result .. "'''喜欢:'''" .. table.concat(liked, "、") ..
                     "\n\n"
    end
    if #disliked > 0 then
        result =
            result .. "'''不喜欢:'''" .. table.concat(disliked, "、") ..
                "\n\n"
    end
    return result
end
function p.getMovieNPCPreferences(frame)
    local movieId = frame.args[1] or frame.args.movie
    if not movieId then return "<!-- 错误:请提供电影ID -->" end
    local movieName = movieNames[movieId] or movieId
    local yearSeason = getMovieYearSeason(movieId)
    local loved = {}
    local liked = {}
    local disliked = {}
    local allNPCs = {}
    for _, npcData in ipairs(moviesReactionsData) do
        table.insert(allNPCs, npcData.NPCName)
    end
    for _, npcName in ipairs(allNPCs) do
        local preference = calculateMoviePreference(npcName, movieId)
        if preference == "love" then
            table.insert(loved, npcName)
        elseif preference == "like" then
            table.insert(liked, npcName)
        elseif preference == "dislike" then
            table.insert(disliked, npcName)
        end
    end
    local result = "=== 《" .. movieName .. "》(" .. yearSeason ..
                       ")的观众喜好 ===\n"
    if #loved > 0 then
        result = result .. "'''最爱:'''" .. table.concat(loved, "、") ..
                     "\n\n"
    end
    if #liked > 0 then
        result = result .. "'''喜欢:'''" .. table.concat(liked, "、") ..
                     "\n\n"
    end
    if #disliked > 0 then
        result =
            result .. "'''不喜欢:'''" .. table.concat(disliked, "、") ..
                "\n\n"
    end
    return result
end
function p.getConcessionNPCPreferences(frame)
    local concessionId = frame.args[1] or frame.args.concession
    if not concessionId then return "<!-- 错误:请提供零食ID -->" end
    local concessionName = concessionNames[concessionId] or concessionId
    local loved = {}
    local liked = {}
    local disliked = {}
    local allNPCs = {}
    for _, tasteData in ipairs(concessionTastesData) do
        if tasteData.Name ~= "*" then
            table.insert(allNPCs, tasteData.Name)
        end
    end
    for _, npcName in ipairs(allNPCs) do
        local preference = calculateConcessionPreference(npcName, concessionId)
        if preference == "love" then
            table.insert(loved, npcName)
        elseif preference == "like" then
            table.insert(liked, npcName)
        elseif preference == "dislike" then
            table.insert(disliked, npcName)
        end
    end
    local result = "=== " .. concessionName .. "的受欢迎程度 ===\n"
    if #loved > 0 then
        result = result .. "'''最爱:'''" .. table.concat(loved, "、") ..
                     "\n\n"
    end
    if #liked > 0 then
        result = result .. "'''喜欢:'''" .. table.concat(liked, "、") ..
                     "\n\n"
    end
    if #disliked > 0 then
        result =
            result .. "'''不喜欢:'''" .. table.concat(disliked, "、") ..
                "\n\n"
    end
    return result
end
function p.getConcessionPrice(frame)
    local concessionId = frame.args[1] or frame.args.concession
    if not concessionId then return "<!-- 错误:请提供零食ID -->" end
    for _, concession in ipairs(concessionsData) do
        if concession.Id == concessionId then
            local concessionName = concessionNames[concessionId] or
                                       concession.Name
            return concessionName .. "的价格:" ..
                       Helper.ExpandTemplate("price", {concession.Price})
        end
    end
    return "<!-- 错误:未找到零食ID " .. concessionId .. " -->"
end
function p.getMovieCranePrizes(frame)
    local movieId = frame.args[1] or frame.args.movie
    if not movieId then return "<!-- 错误:请提供电影ID -->" end
    local movieName = movieNames[movieId] or movieId
    local yearSeason = getMovieYearSeason(movieId)
    for _, movie in ipairs(moviesData) do
        if movie.Id == movieId then
            local result = "=== 《" .. movieName .. "》(" .. yearSeason ..
                               ")上映期间的娃娃机特殊物品 ===\n"
            if movie.CranePrizes and #movie.CranePrizes > 0 then
                for _, prize in ipairs(movie.CranePrizes) do
                    local rarity = ""
                    if prize.Rarity == 1 then
                        rarity = "(普通)"
                    elseif prize.Rarity == 2 then
                        rarity = "(稀有)"
                    elseif prize.Rarity == 3 then
                        rarity = "(豪华)"
                    end
                    local itemId = prize.ItemId
                    if itemId and itemId:match("%(F%)(%d+)") then
                        local furnitureId = itemId:match("%(F%)(%d+)")
                        result = result .. "* " ..
                                     Helper.ExpandTemplate("Name", {furnitureId}) ..
                                     rarity .. "\n"
                    else
                        result =
                            result .. "* " .. (prize.Id or "未知物品") ..
                                rarity .. "\n"
                    end
                end
            else
                result = result ..
                             "该电影上映期间没有特殊物品。\n"
            end
            return result
        end
    end
    return "<!-- 错误:未找到电影ID " .. movieId .. " -->"
end
local function extractScriptDialogue(script)
    if not script then return nil end
    local messageDialogue = script:match('/message%s+"([^"]+)"')
    if messageDialogue then return messageDialogue end
    local complexMessage = script:match('/emote%s+[^/]+/message%s+"([^"]+)"')
    if complexMessage then return complexMessage end
    return nil
end
local function getMovieDialogueTexts(translationKey, script)
    local textDialogues = {}
    local scriptDialogues = {}
    local processedKeys = {}
    if translationKey and translationKey ~= "" then
        if talkData then
            local key = translationKey:match(
                            "%[LocalizedText Strings\\MovieReactions:(.+)%]")
            if key and talkData.MovieReactions then
                processedKeys[key] = true
                if talkData.MovieReactions[key] then
                    table.insert(textDialogues, talkData.MovieReactions[key])
                end
            else
                table.insert(textDialogues, translationKey)
            end
        else
            table.insert(textDialogues, translationKey)
        end
    end
    if script then
        local scriptDialogue = extractScriptDialogue(script)
        if scriptDialogue then
            if scriptDialogue:match("%[LocalizedText") then
                local scriptKey = scriptDialogue:match(
                                      "%[LocalizedText Strings\\MovieReactions:(.+)%]")
                if scriptKey and not processedKeys[scriptKey] then
                    local scriptTexts = getMovieDialogueTexts(scriptDialogue,
                                                              nil)
                    for _, text in ipairs(scriptTexts.textDialogues or {}) do
                        table.insert(scriptDialogues, text)
                    end
                end
            else
                table.insert(scriptDialogues, scriptDialogue)
            end
        end
    end
    return {textDialogues = textDialogues, scriptDialogues = scriptDialogues}
end
local function cleanMovieText(text)
    if not text or type(text) ~= "string" then return text, nil end
    local emotion = nil
    emotion = text:match("%$([hslua])$")
    if not emotion then
        emotion = text:match("%$(%d+)$")
        if emotion then
            local num = tonumber(emotion)
            if not num or num < 0 or num > 15 then emotion = nil end
        end
    end
    text = text:gsub("%$[hslua]$", "")
    text = text:gsub("%$%d+$", "")
    text = text:gsub("%$%{([^%^}]+)%^([^}]+)%}", "%1/%2")
    text = text:gsub("\\\"", "\"")
    text = text:gsub("\\n", "\n")
    text = text:gsub("@", '<span class="player-name">玩家名</span>')
    local has0 = text:find("{0}") ~= nil
    local has2 = text:find("{2}") ~= nil
    if has0 and has2 then
        text = text:gsub("{0}", '<span class="player-name">电影名称</span>')
        text = text:gsub("{2}", '<span class="player-name">人物名称</span>')
    elseif has0 then
        text = text:gsub("{0}", '<span class="player-name">_____</span>')
    elseif has2 then
        text = text:gsub("{2}", '<span class="player-name">_____</span>')
    end
    text = text:gsub("%%noturn",
                     '<span class="trigger-chance">(不转身)</span>')
    text = text:gsub("%$[hbslaue]", "")
    text = text:gsub("%$%d+", "")
    text = text:gsub("##", "\n")
    text = text:gsub("%s*%[%s*%d+%s*%]",
                     '<span class="refuse-item">(给予物品)</span>')
    if text:match("^[^。!?]*在[^。!?]*。?%s*$") then
        text = '<span class="action-description">' .. text .. '</span>'
    end
    if emotion then
        if emotion == "h" then
            emotion = "1"
        elseif emotion == "s" then
            emotion = "2"
        elseif emotion == "u" then
            emotion = "3"
        elseif emotion == "l" then
            emotion = "4"
        elseif emotion == "a" then
            emotion = "5"
        end
    end
    return text, emotion
end
local function processMovieGenderDialogue(text, characterName, isFromScript)
    if not text or type(text) ~= "string" then return text end
    local hasInlineGender = text:find("%$%{[^%^}]+%^[^}]+%}%$")
    if hasInlineGender then
        local maleText = text:gsub("%$%{([^%^}]+)%^[^}]+%}%$", "%1")
        local femaleText = text:gsub("%$%{[^%^}]+%^([^}]+)%}%$", "%1")
        local maleResult = createMovieQuote(maleText ..
                                                '<span class="trigger-chance">(男)</span>',
                                            characterName, isFromScript)
        local femaleResult = createMovieQuote(femaleText ..
                                                  '<span class="trigger-chance">(女)</span>',
                                              characterName, isFromScript)
        return maleResult .. "\n" .. femaleResult
    end
    if not text:find("%^") then
        return createMovieQuote(text, characterName, isFromScript)
    end
    local caretPos = text:find("%^")
    local beforeCaret = text:sub(1, caretPos - 1)
    local afterCaret = text:sub(caretPos + 1)
    local maleText = beforeCaret:match("^%s*(.-)%s*$") or ""
    local femaleText = afterCaret:match("^%s*(.-)%s*$") or ""
    local maleResult = createMovieQuote(maleText ..
                                            '<span class="trigger-chance">(男)</span>',
                                        characterName, isFromScript)
    local femaleResult = createMovieQuote(femaleText ..
                                              '<span class="trigger-chance">(女)</span>',
                                          characterName, isFromScript)
    return maleResult .. "\n" .. femaleResult
end
-- 处理单个对话片段(不包含分隔符)
local createMovieQuoteSingle = function(text, characterName, isFromScript)
    if not text then return "" end
    local cleanedText, emotion = cleanMovieText(text)
    if isFromScript then
        return Helper.ExpandTemplate("Say", {"voiceover", cleanedText})
    end
    local chineseName = getCharacterName(characterName)
    local portrait = characterName
    if portrait then portrait = portrait:lower() end
    if not emotion and characterName then emotion = "0" end
    if emotion and portrait then
        return Helper.ExpandTemplate("Say", {
            "left", chineseName, cleanedText, emotion, portrait
        })
    else
        return Helper.ExpandTemplate("Say", {"left", chineseName, cleanedText})
    end
end

-- 处理包含分隔符的电影对话文本,分割为独立对话
local function processMovieDialogueWithSeparators(text, characterName, isFromScript)
    if not text or type(text) ~= "string" then
        return ""
    end

    -- 分割对话:#$b# 和 #$e# 都作为分隔符
    local parts = {}
    local currentPart = ""
    local i = 1

    while i <= #text do
        if text:sub(i, i + 3) == "#$b#" then
            if currentPart ~= "" then
                table.insert(parts, currentPart)
                currentPart = ""
            end
            i = i + 4
        elseif text:sub(i, i + 3) == "#$e#" then
            if currentPart ~= "" then
                table.insert(parts, currentPart)
                currentPart = ""
            end
            i = i + 4
        else
            currentPart = currentPart .. text:sub(i, i)
            i = i + 1
        end
    end

    -- 添加最后一部分
    if currentPart ~= "" then
        table.insert(parts, currentPart)
    end

    -- 如果没有分隔符,直接处理整个文本
    if #parts <= 1 then
        return createMovieQuoteSingle(text, characterName, isFromScript)
    end

    -- 处理每个独立的对话片段
    local results = {}
    for _, part in ipairs(parts) do
        local trimmed = part:match("^%s*(.-)%s*$")
        if trimmed and trimmed ~= "" then
            -- 检查这个片段是否包含性别分支
            if trimmed:find("%^") or trimmed:find("%$%{[^%^}]+%^[^}]+%}%$") then
                -- 有性别分支,调用性别处理函数
                table.insert(results, processMovieGenderDialogue(trimmed, characterName, isFromScript))
            else
                -- 没有性别分支,直接处理
                table.insert(results, createMovieQuoteSingle(trimmed, characterName, isFromScript))
            end
        end
    end

    return table.concat(results, "\n")
end

createMovieQuote = function(text, characterName, isFromScript)
    if not text then return "" end

    -- 检查是否包含分隔符
    if text:find("#%$[be]#") then
        return processMovieDialogueWithSeparators(text, characterName, isFromScript)
    else
        return createMovieQuoteSingle(text, characterName, isFromScript)
    end
end
createMovieQuotes = function(dialogues, characterName, isFromScript)
    if not dialogues or #dialogues == 0 then return "" end
    local result = {}
    for _, dialogue in ipairs(dialogues) do
        if dialogue:find("%^") or dialogue:find("%$%{[^%^}]+%^[^}]+%}%$") then
            local quote = processMovieGenderDialogue(dialogue, characterName,
                                                     isFromScript)
            if quote and quote ~= "" then table.insert(result, quote) end
        else
            local quote =
                createMovieQuote(dialogue, characterName, isFromScript)
            if quote and quote ~= "" then table.insert(result, quote) end
        end
    end
    return table.concat(result, "\n")
end
local function processDialogueCategory(title, dialogues, englishName)
    if not dialogues then return "" end
    local dialogueContent = {}
    if dialogues.BeforeMovie then
        local result = getMovieDialogueTexts(dialogues.BeforeMovie.Text,
                                             dialogues.BeforeMovie.Script)
        local allQuotes = {}
        if #result.textDialogues > 0 then
            table.insert(allQuotes, createMovieQuotes(result.textDialogues,
                                                      englishName, false))
        end
        if #result.scriptDialogues > 0 then
            table.insert(allQuotes, createMovieQuotes(result.scriptDialogues,
                                                      englishName, true))
        end
        if #allQuotes > 0 then
            table.insert(dialogueContent, table.concat(allQuotes, "\n"))
        end
    end
    if dialogues.DuringMovie then
        local result = getMovieDialogueTexts(dialogues.DuringMovie.Text,
                                             dialogues.DuringMovie.Script)
        local allQuotes = {}
        if #result.textDialogues > 0 then
            table.insert(allQuotes, createMovieQuotes(result.textDialogues,
                                                      englishName, false))
        end
        if #result.scriptDialogues > 0 then
            table.insert(allQuotes, createMovieQuotes(result.scriptDialogues,
                                                      englishName, true))
        end
        if #allQuotes > 0 then
            table.insert(dialogueContent, table.concat(allQuotes, "\n"))
        end
    end
    if dialogues.AfterMovie then
        local result = getMovieDialogueTexts(dialogues.AfterMovie.Text,
                                             dialogues.AfterMovie.Script)
        local allQuotes = {}
        if #result.textDialogues > 0 then
            table.insert(allQuotes, createMovieQuotes(result.textDialogues,
                                                      englishName, false))
        end
        if #result.scriptDialogues > 0 then
            table.insert(allQuotes, createMovieQuotes(result.scriptDialogues,
                                                      englishName, true))
        end
        if #allQuotes > 0 then
            table.insert(dialogueContent, table.concat(allQuotes, "\n"))
        end
    end
    if #dialogueContent > 0 then
        local eventLabelContent =
            '<div class="event-label show" style="display: block;">\n' ..
                table.concat(dialogueContent, "\n") .. '\n</div>'
        return Helper.ExpandTemplate("Collapse",
                                     {title, content = eventLabelContent})
    end
    return ""
end
function p.getNPCMovieDialogue(frame)
    local npcName = frame.args[1] or frame.args.npc
    if not npcName then return "<!-- 错误:请提供NPC名称 -->" end
    local displayName = npcName
    local englishName = npcName
    if NPC.getEnglishName(npcName) then
        englishName = NPC.getEnglishName(npcName)
    else
        displayName = getCharacterName(npcName)
    end

    -- 收集电影邀约对话
    local inviteDialogues = {}

    -- 1. 从NPC个人数据中获取MovieInvitation
    if talkData and talkData[englishName] and talkData[englishName].MovieInvitation then
        table.insert(inviteDialogues, {
            type = "regular",
            dialogue = talkData[englishName].MovieInvitation
        })
    end

    -- 2. 从Strings/Characters获取婚后对话
    if talkData and talkData["Strings/Characters"] then
        local stringsData = talkData["Strings/Characters"]
        local spouseKey = "MovieInvite_Spouse_" .. englishName
        if stringsData[spouseKey] then
            table.insert(inviteDialogues, {
                type = "spouse",
                dialogue = stringsData[spouseKey]
            })
        end

        -- 3. 获取通用邀约对话(如果没有个人对话且没有婚后对话)
        local hasRegularDialogue = false
        for _, invite in ipairs(inviteDialogues) do
            if invite.type == "regular" then
                hasRegularDialogue = true
                break
            end
        end

        if not hasRegularDialogue then
            -- 定义 child 和 rude NPC 列表
            local childNPCs = {"Vincent", "Jas"}
            local rudeNPCs = {"Abigail", "Alex", "Clint", "George", "Haley", "Pam", "Sebastian", "Shane", "Wizard"}

            -- 检查是否为 child
            local isChild = false
            for _, child in ipairs(childNPCs) do
                if englishName == child then
                    isChild = true
                    break
                end
            end

            -- 检查是否为 rude
            local isRude = false
            for _, rude in ipairs(rudeNPCs) do
                if englishName == rude then
                    isRude = true
                    break
                end
            end

            -- 按优先级查找通用对话
            local fallbackKeys = {
                "MovieInvite_Invited_" .. englishName
            }

            -- 根据 NPC 类型添加相应的回退对话
            if isChild then
                table.insert(fallbackKeys, "MovieInvite_Invited_Child")
            elseif isRude then
                table.insert(fallbackKeys, "MovieInvite_Invited_Rude")
            else
                table.insert(fallbackKeys, "MovieInvite_Invited")
            end

            for _, key in ipairs(fallbackKeys) do
                if stringsData[key] then
                    table.insert(inviteDialogues, {
                        type = "regular",
                        dialogue = stringsData[key]
                    })
                    break
                end
            end
        end
    end

    local result = ""

    -- 处理邀约对话
    if #inviteDialogues > 0 then
        local inviteContent = {}
        local regularDialogues = {}
        local spouseDialogues = {}

        for _, invite in ipairs(inviteDialogues) do
            if invite.type == "spouse" then
                table.insert(spouseDialogues, invite.dialogue)
            else
                table.insert(regularDialogues, invite.dialogue)
            end
        end

        -- 先输出常规对话
        if #regularDialogues > 0 then
            for _, dialogue in ipairs(regularDialogues) do
                local quote = createMovieQuote(dialogue, englishName, false)
                if quote and quote ~= "" then
                    table.insert(inviteContent, quote)
                end
            end
        end

        -- 再输出婚后对话
        if #spouseDialogues > 0 then
            table.insert(inviteContent, "'''婚后'''")
            for _, dialogue in ipairs(spouseDialogues) do
                local quote = createMovieQuote(dialogue, englishName, false)
                if quote and quote ~= "" then
                    table.insert(inviteContent, quote)
                end
            end
        end

        if #inviteContent > 0 then
            local eventLabelContent =
                '<div class="event-label show" style="display: block;">\n' ..
                    table.concat(inviteContent, "\n") .. '\n</div>'
            result = result .. Helper.ExpandTemplate("Collapse",
                                         {"发起邀约", content = eventLabelContent}) .. "\n"
        end
    end

    local npcReactions = nil
    for _, npcData in ipairs(moviesReactionsData) do
        if npcData.NPCName == englishName then
            npcReactions = npcData.Reactions
            break
        end
    end
    if not npcReactions then
        return result .. "<!-- 错误:未找到NPC " .. displayName ..
                   " 的电影反应数据 -->"
    end
    local movieDialogues = {}
    local reactionDialogues = {}
    local genreDialogues = {}
    local generalDialogues = {}
    for _, reaction in ipairs(npcReactions) do
        if reaction.SpecialResponses then
            local tag = reaction.Tag
            local movieName = movieNames[tag]
            if movieName then
                movieDialogues[movieName] = reaction.SpecialResponses
            elseif tag == "*" then
                generalDialogues["通用"] = reaction.SpecialResponses
            elseif tag == "love" or tag == "like" or tag == "dislike" or tag ==
                "seen_love" or tag == "seen_like" or tag == "seen_dislike" then
                local categoryTitle = ""
                if tag == "love" or tag == "seen_love" then
                    categoryTitle = "最爱的电影"
                elseif tag == "dislike" or tag == "seen_dislike" then
                    categoryTitle = "不喜欢的电影"
                elseif tag == "like" or tag == "seen_like" then
                    categoryTitle = "喜欢的电影"
                end
                if tag:match("^seen_") then
                    categoryTitle = categoryTitle .. "(已看过)"
                end
                reactionDialogues[categoryTitle] = reaction.SpecialResponses
            else
                local categoryTitle = ""
                if tag == "family" then
                    categoryTitle = "家庭类电影"
                elseif tag == "comedy" then
                    categoryTitle = "喜剧类电影"
                elseif tag == "horror" then
                    categoryTitle = "恐怖类电影"
                elseif tag == "action" then
                    categoryTitle = "动作类电影"
                elseif tag == "sci-fi" then
                    categoryTitle = "科幻类电影"
                elseif tag == "classic" then
                    categoryTitle = "经典类电影"
                elseif tag == "romance" then
                    categoryTitle = "浪漫类电影"
                elseif tag == "documentary" then
                    categoryTitle = "纪录片类电影"
                elseif tag == "art" then
                    categoryTitle = "艺术类电影"
                else
                    categoryTitle = tag .. "类电影"
                end
                genreDialogues[categoryTitle] = reaction.SpecialResponses
            end
        end
    end
    local outputTemplates = {}
    for movieName, dialogues in pairs(movieDialogues) do
        local collapseTemplate = processDialogueCategory(movieName, dialogues,
                                                         englishName)
        if collapseTemplate ~= "" then
            table.insert(outputTemplates, collapseTemplate)
        end
    end
    for categoryTitle, dialogues in pairs(genreDialogues) do
        local collapseTemplate = processDialogueCategory(categoryTitle,
                                                         dialogues, englishName)
        if collapseTemplate ~= "" then
            table.insert(outputTemplates, collapseTemplate)
        end
    end
    local reactionOrder = {
        "最爱的电影", "最爱的电影(已看过)", "喜欢的电影",
        "喜欢的电影(已看过)", "不喜欢的电影",
        "不喜欢的电影(已看过)"
    }
    for _, categoryTitle in ipairs(reactionOrder) do
        if reactionDialogues[categoryTitle] then
            local collapseTemplate = processDialogueCategory(categoryTitle,
                                                             reactionDialogues[categoryTitle],
                                                             englishName)
            if collapseTemplate ~= "" then
                table.insert(outputTemplates, collapseTemplate)
            end
        end
    end
    for dialogueType, dialogues in pairs(generalDialogues) do
        local collapseTemplate = processDialogueCategory("通用及特殊",
                                                         dialogues, englishName)
        if collapseTemplate ~= "" then
            table.insert(outputTemplates, collapseTemplate)
        end
    end
    if #outputTemplates > 0 then
        result = result .. "\n" .. table.concat(outputTemplates, "\n\n")
    end
    return result
end
function p.getAllMoviesInfo(frame)
    local result = "=== 所有电影信息 ===\n"
    for _, movie in ipairs(moviesData) do
        local movieName = movieNames[movie.Id] or movie.Id
        local yearSeason = getMovieYearSeason(movie.Id)
        result = result .. "\n==== " .. movieName .. "(" .. yearSeason ..
                     ") ====\n"
        result = result .. "* '''ID:'''" .. movie.Id .. "\n"
        result = result .. "* '''季节:'''" ..
                     table.concat(movie.Seasons or {}, "、") .. "\n"
        if movie.YearModulus and movie.YearRemainder then
            local yearDesc = ""
            if movie.YearModulus == 2 then
                if movie.YearRemainder == 0 then
                    yearDesc = "第一年"
                else
                    yearDesc = "第二年"
                end
            else
                yearDesc = "每" .. movie.YearModulus .. "年,余数" ..
                               movie.YearRemainder
            end
            result = result .. "* '''年份:'''" .. yearDesc .. "\n"
        end
        if movie.Tags and #movie.Tags > 0 then
            local tagNames = {}
            for _, tag in ipairs(movie.Tags) do
                local tagName = tag
                if tag == "family" then
                    tagName = "家庭"
                elseif tag == "comedy" then
                    tagName = "喜剧"
                elseif tag == "horror" then
                    tagName = "恐怖"
                elseif tag == "action" then
                    tagName = "动作"
                elseif tag == "sci-fi" then
                    tagName = "科幻"
                elseif tag == "classic" then
                    tagName = "经典"
                elseif tag == "romance" then
                    tagName = "浪漫"
                elseif tag == "documentary" then
                    tagName = "纪录片"
                elseif tag == "art" then
                    tagName = "艺术"
                end
                table.insert(tagNames, tagName)
            end
            result = result .. "* '''类型:'''" ..
                         table.concat(tagNames, "、") .. "\n"
        end
        if movie.CranePrizes and #movie.CranePrizes > 0 then
            result = result .. "* '''娃娃机特殊物品:'''" ..
                         #movie.CranePrizes .. "种\n"
        end
    end
    return result
end
function p.getNPCCinemaTable(frame)
    local npcName = frame.args[1] or frame.args.npc
    if not npcName then return "<!-- 错误:请提供NPC名称 -->" end
    local displayName = npcName
    local englishName = npcName
    if NPC.getEnglishName(npcName) then
        englishName = NPC.getEnglishName(npcName)
    else
        displayName = getCharacterName(npcName)
    end
    local moviePreferences = {loved = {}, liked = {}, disliked = {}}
    for _, movie in ipairs(moviesData) do
        local preference = calculateMoviePreference(englishName, movie.Id)
        local movieName = movieNames[movie.Id] or movie.Id
        local yearSeason = getMovieYearSeason(movie.Id)
        local movieDisplayName = movieName .. "(" .. yearSeason .. ")"
        local movieEnglishName = movieNamesEn[movie.Id]:gsub(':', '')
        local movieImageName = ""
        if movieEnglishName then
            movieImageName = "'" .. movieEnglishName .. "'.png"
        end
        local movieEntry = {
            image = movieImageName,
            name = movieName,
            season = yearSeason,
            yearRemainder = movie.YearRemainder or 0,
            seasons = movie.Seasons or {}
        }
        if preference == "love" then
            table.insert(moviePreferences.loved, movieEntry)
        elseif preference == "like" then
            table.insert(moviePreferences.liked, movieEntry)
        elseif preference == "dislike" then
            table.insert(moviePreferences.disliked, movieEntry)
        end
    end
    local function sortMovies(movieList)
        table.sort(movieList, function(a, b)
            if a.yearRemainder ~= b.yearRemainder then
                return a.yearRemainder < b.yearRemainder
            end
            local seasonOrder = {
                spring = 0,
                summer = 1,
                fall = 2,
                winter = 3,
                Spring = 0,
                Summer = 1,
                Fall = 2,
                Winter = 3
            }
            local aMinSeason = 4
            local bMinSeason = 4
            for _, season in ipairs(a.seasons) do
                local seasonValue = seasonOrder[season]
                if seasonValue and seasonValue < aMinSeason then
                    aMinSeason = seasonValue
                end
            end
            for _, season in ipairs(b.seasons) do
                local seasonValue = seasonOrder[season]
                if seasonValue and seasonValue < bMinSeason then
                    bMinSeason = seasonValue
                end
            end
            if aMinSeason == bMinSeason then return a.name < b.name end
            return aMinSeason < bMinSeason
        end)
    end
    sortMovies(moviePreferences.loved)
    sortMovies(moviePreferences.liked)
    sortMovies(moviePreferences.disliked)
    local concessionPreferences = {loved = {}, liked = {}, disliked = {}}
    for _, concession in ipairs(concessionsData) do
        local preference = calculateConcessionPreference(englishName,
                                                         concession.Id)
        local concessionName = concessionNames[concession.Id] or concession.Name
        local concessionImageName = concession.Name .. ".png"
        if concession.Name == "Joja Cola" then
            concessionImageName = "Joja Cola (large).png"
        end
        local concessionEntry = {
            image = concessionImageName,
            name = concessionName
        }
        if preference == "love" then
            table.insert(concessionPreferences.loved, concessionEntry)
        elseif preference == "like" then
            table.insert(concessionPreferences.liked, concessionEntry)
        elseif preference == "dislike" then
            table.insert(concessionPreferences.disliked, concessionEntry)
        end
    end
    local movieTableHTML =
        '<table class="wikitable" style="margin: 0; margin-top: 1em;">\n'
    if #moviePreferences.loved > 0 then
        movieTableHTML = movieTableHTML ..
                             '<tr><th colspan="2">最爱 <span style="font-size:smaller;">+200</span></th></tr>\n'
        for _, movie in ipairs(moviePreferences.loved) do
            movieTableHTML =
                movieTableHTML .. '<tr><td>[[File:' .. movie.image ..
                    '|24px|link=]] ' .. movie.name ..
                    '</td><td style="text-align: center; padding: 2px 16px;">' ..
                    movie.season .. '</td></tr>\n'
        end
    end
    if #moviePreferences.liked > 0 then
        movieTableHTML = movieTableHTML ..
                             '<tr><th colspan="2">喜欢 <span style="font-size:smaller;">+100</span></th></tr>\n'
        for _, movie in ipairs(moviePreferences.liked) do
            movieTableHTML =
                movieTableHTML .. '<tr><td>[[File:' .. movie.image ..
                    '|24px|link=]] ' .. movie.name ..
                    '</td><td style="text-align: center; padding: 2px 16px;">' ..
                    movie.season .. '</td></tr>\n'
        end
    end
    if #moviePreferences.disliked > 0 then
        movieTableHTML = movieTableHTML ..
                             '<tr><th colspan="2">不喜欢<span style="font-size:smaller;"></span></th></tr>\n'
        for _, movie in ipairs(moviePreferences.disliked) do
            movieTableHTML =
                movieTableHTML .. '<tr><td>[[File:' .. movie.image ..
                    '|24px|link=]] ' .. movie.name ..
                    '</td><td style="text-align: center; padding: 2px 16px;">' ..
                    movie.season .. '</td></tr>\n'
        end
    end
    movieTableHTML = movieTableHTML .. '</table>'
    local lovedItems = {}
    local likedItems = {}
    local dislikedItems = {}
    for i, concession in ipairs(concessionPreferences.loved) do
        table.insert(lovedItems, '[[File:' .. concession.image ..
                         '|24px|link=]] ' .. concession.name .. '')
    end
    for i, concession in ipairs(concessionPreferences.liked) do
        table.insert(likedItems, '[[File:' .. concession.image ..
                         '|24px|link=]] ' .. concession.name .. '')
    end
    for i, concession in ipairs(concessionPreferences.disliked) do
        table.insert(dislikedItems, '[[File:' .. concession.image ..
                         '|24px|link=]] ' .. concession.name .. '')
    end
    local concessionTableHTML =
        '<table class="wikitable" style="margin: 0; margin-top: 1em;">\n'
    local likedCount = #likedItems
    local dislikedCount = #dislikedItems
    local leftBottomTitle, leftBottomItems, leftBottomIcon, leftBottomScore
    local rightTitle, rightItems, rightIcon, rightScore
    if likedCount <= dislikedCount then
        leftBottomTitle = "喜欢"
        leftBottomItems = likedItems
        leftBottomIcon = "ConcessionLike.png"
        leftBottomScore = "+25"
        rightTitle = "不喜欢"
        rightItems = dislikedItems
        rightIcon = "ConcessionDislike.png"
        rightScore = ""
    else
        leftBottomTitle = "不喜欢"
        leftBottomItems = dislikedItems
        leftBottomIcon = "ConcessionDislike.png"
        leftBottomScore = ""
        rightTitle = "喜欢"
        rightItems = likedItems
        rightIcon = "ConcessionLike.png"
        rightScore = "+25"
    end
    concessionTableHTML = concessionTableHTML .. '<tr>\n'
    concessionTableHTML = concessionTableHTML ..
                              '<th>[[File:ConcessionLove.png|24px|link=]] 最爱 <span style="font-size:smaller;">+50</span></th>\n'
    concessionTableHTML = concessionTableHTML ..
                              '<th rowspan="1" style="vertical-align: top;">[[File:' ..
                              rightIcon .. '|24px|link=]] ' .. rightTitle ..
                              ' <span style="font-size:smaller;">' .. rightScore ..
                              '</span></th>\n'
    concessionTableHTML = concessionTableHTML .. '</tr>\n'
    concessionTableHTML = concessionTableHTML ..
                              '<tr style="vertical-align: top;">\n'
    concessionTableHTML = concessionTableHTML ..
                              '<td style="padding: 4px 6px;">' ..
                              table.concat(lovedItems, '<br>') .. '</td>\n'
    concessionTableHTML = concessionTableHTML ..
                              '<td rowspan="3" style="padding: 4px 6px; vertical-align: top;">' ..
                              table.concat(rightItems, '<br>') .. '</td>\n'
    concessionTableHTML = concessionTableHTML .. '</tr>\n'
    concessionTableHTML = concessionTableHTML .. '<tr>\n'
    concessionTableHTML = concessionTableHTML ..
                              '<th style="height: 30px;">[[File:' ..
                              leftBottomIcon .. '|24px|link=]] ' ..
                              leftBottomTitle ..
                              ' <span style="font-size:smaller;">' ..
                              leftBottomScore .. '</span></th>\n'
    concessionTableHTML = concessionTableHTML .. '</tr>\n'
    concessionTableHTML = concessionTableHTML ..
                              '<tr style="vertical-align: top;">\n'
    concessionTableHTML = concessionTableHTML ..
                              '<td style="padding: 4px 6px;">' ..
                              table.concat(leftBottomItems, '<br>') .. '</td>\n'
    concessionTableHTML = concessionTableHTML .. '</tr>\n'
    concessionTableHTML = concessionTableHTML .. '</table>'
    local cinemaTableHTML =
        '<div style="display: flex; flex-wrap: wrap; gap: 16px;">\n'
    cinemaTableHTML = cinemaTableHTML .. '<div>\n' .. movieTableHTML ..
                          '\n</div>\n'
    cinemaTableHTML = cinemaTableHTML .. '<div>\n' .. concessionTableHTML ..
                          '\n</div>\n'
    cinemaTableHTML = cinemaTableHTML .. '</div>'
    return cinemaTableHTML
end
return p