维护提醒

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

全站通知:

模块:GiftTastesNPC

来自星露谷物语维基
跳到导航 跳到搜索
[ 创建 | 刷新 ]文档页面
当前模块文档缺失,需要扩充。
-- #############################################################################
-- # Module:GiftTastesNPC (V6 - Final) - 最终版 v6 (优先级修正)
-- # ---------------------------------------------------------------------------
-- # 最终版逻辑模块,能够处理数字ID、字符串ID和上下文标签。
-- # 依赖: Module:Object/data, Module:GiftTastesNPC/data
-- # 经过C#逻辑审查后修订,加入了对古物、默认规则(Edibility/Price)等的处理。
-- #############################################################################

p = {}

-- 1. 加载依赖模块
local objectData = require('Module:Object/data')
local giftTasteData = require('Module:GiftTastesNPC/data')

-- #############################################################################
-- # 内部辅助函数与数据映射
-- #############################################################################

local categoryNames = {
    [-103] = "技能书", -- skillBook_Category
    [-102] = "书", -- Book_Category
    [-100] = "服装", -- category_clothes
    [-99] = "工具", -- Tool.cs.14307
    [-97] = "鞋类", -- Boots.cs.12501
    [-96] = "戒指", -- Ring.cs.1
    [-81] = "采集品", -- Object.cs.12869
    [-80] = "花", -- Object.cs.12866
    [-79] = "水果", -- Object.cs.12854
    [-75] = "蔬菜", -- Object.cs.12851
    [-74] = "种子", -- Object.cs.12855
    [-28] = "怪物战利品", -- Object.cs.12867
    [-27] = "工匠物品", -- Object.cs.12862
    [-26] = "工匠物品", -- Object.cs.12862
    [-25] = "菜品", -- Object.cs.12853
    [-24] = "装饰", -- Object.cs.12859 / Furniture_Decoration
    [-22] = "钓具", -- Object.cs.12858
    [-21] = "鱼饵", -- Object.cs.12857
    [-20] = "垃圾", -- Object.cs.12860
    [-19] = "化肥", -- Object.cs.12856
    [-18] = "动物制品", -- Object.cs.12864
    [-16] = "资源", -- Object.cs.12868
    [-15] = "资源", -- Object.cs.12868
    [-14] = "动物制品", -- Object.cs.12864
    [-12] = "矿物", -- Object.cs.12850
    [-8] = "制造品", -- Object.cs.12863
    [-7] = "菜品", -- Object.cs.12853
    [-6] = "动物制品", -- Object.cs.12864
    [-5] = "动物制品", -- Object.cs.12864
    [-4] = "鱼", -- Object.cs.12852
    [-2] = "矿物" -- Object.cs.12850
}

local contextTagNames = {
    book_item = "所有书",
    category_trinket = "所有饰品",
    category_bait = "所有鱼饵",
    category_monster_loot = "所有怪物战利品",
}

local function _getItemInfo(itemId)
    return objectData[tostring(itemId)]
end

local function _getCategoryName(categoryId)
    return categoryNames[categoryId] or "未知分类"
end

local function _getContextTagName(tag)
    return contextTagNames[tag] or tag
end

local function _itemMatchesRule(itemInfo, ruleId)
    if not itemInfo then return false end
    if type(ruleId) == 'number' then
        if ruleId >= 0 then return false
        else return itemInfo.Category == ruleId end
    elseif type(ruleId) == 'string' then
        if objectData[ruleId] then return false
        else
            if itemInfo.ContextTags then
                for _, tag in ipairs(itemInfo.ContextTags) do
                    if tag == ruleId then return true end
                end
            end
            return false
        end
    end
    return false
end

local function _isItemCoveredBySpecificTaste(itemInfo, itemId, personalTastes, universalTastes)
    local allTasteLevels = {"love", "like", "neutral", "dislike", "hate"}
    itemId = tostring(itemId)
    if not personalTastes then return false end

    for _, level in ipairs(allTasteLevels) do
        local tasteList = (personalTastes[level] and personalTastes[level].items) or {}
        for _, rule in ipairs(tasteList) do
            if tostring(rule) == itemId or _itemMatchesRule(itemInfo, rule) then
                return true
            end
        end
    end

    if itemInfo.Type == "Arch" then return true end

    for _, level in ipairs(allTasteLevels) do
        local tasteList = universalTastes[level] or {}
        for _, rule in ipairs(tasteList) do
            if tostring(rule) == itemId or _itemMatchesRule(itemInfo, rule) then
                return true
            end
        end
    end

    return false
end

-- #############################################################################
-- # 主要外部函数
-- #############################################################################

function p.describeTastes(frame)
    local args = frame.args
    local npcName = args.npc or args[1]
    local targetLevel = args.taste or args[2]

    if not npcName or not targetLevel then
        return '<strong class="error">错误:必须提供 "npc" 和 "taste" 参数。</strong>'
    end

    npcName = mw.text.trim(npcName)
    targetLevel = mw.text.trim(targetLevel):lower()

    local personalTastes = giftTasteData.npcs[npcName]
    if not personalTastes then
        return string.format('<strong class="error">错误:未找到NPC "%s"。</strong>', npcName)
    end
    local universalTastes = giftTasteData.universal
    local tasteLevels = {"love", "like", "neutral", "dislike", "hate"}

    local outputPhrases = {}
    local outputItems = {}
    local seenItems = {}

    local function addItem(name)
        if name and not seenItems[name] then
            table.insert(outputItems, name)
            seenItems[name] = true
        end
    end

    -- 1. 处理个人偏好
    local personalIds = (personalTastes[targetLevel] and personalTastes[targetLevel].items) or {}
    for _, id in ipairs(personalIds) do
        if type(id) == 'number' and id >= 0 or (type(id) == 'string' and objectData[id]) then
            local itemInfo = _getItemInfo(id)
            if itemInfo and itemInfo.Name then addItem(itemInfo.Name) end
        else
            local ruleName
            if type(id) == 'number' and id < 0 then ruleName = "所有" .. _getCategoryName(id)
            elseif type(id) == 'string' then ruleName = _getContextTagName(id) end

            if ruleName then
                local exceptions = {}
                local seenExceptions = {}
                local function addException(name)
                    if name and not seenExceptions[name] then
                        table.insert(exceptions, name)
                        seenExceptions[name] = true
                    end
                end
                for _, level in ipairs(tasteLevels) do
                    if level ~= targetLevel then
                        local levelItems = (personalTastes[level] and personalTastes[level].items) or {}
                        for _, exceptionId in ipairs(levelItems) do
                            local itemInfo = _getItemInfo(exceptionId)
                            if _itemMatchesRule(itemInfo, id) then addException(itemInfo.Name) end
                        end
                    end
                end
                local universalOverrides = {"love", "like"}
                for _, uni_level in ipairs(universalOverrides) do
                    local universalItems = universalTastes[uni_level] or {}
                    for _, universalId in ipairs(universalItems) do
                        if type(universalId) == 'number' and universalId >= 0 or (type(universalId) == 'string' and objectData[universalId]) then
                            local itemInfo = _getItemInfo(universalId)
                            if _itemMatchesRule(itemInfo, id) then addException(itemInfo.Name) end
                        end
                    end
                end
                local text = ruleName
                if #exceptions > 0 then text = text .. "(" .. table.concat(exceptions, "、") .. "除外)" end
                table.insert(outputPhrases, text)
            end
        end
    end

    -- 2. 处理古物 (Arch/Artifact) 的硬编码规则
    local function processArchRule(likeRule)
        local exceptions = {}
        local seenExceptions = {}
        for _, level in ipairs(tasteLevels) do
            if (likeRule and level ~= "like") or (not likeRule and level ~= "dislike") then
                local levelItems = (personalTastes[level] and personalTastes[level].items) or {}
                for _, exceptionId in ipairs(levelItems) do
                    local itemInfo = _getItemInfo(exceptionId)
                    if itemInfo and itemInfo.Type == "Arch" and not seenExceptions[itemInfo.Name] then
                        table.insert(exceptions, itemInfo.Name)
                        seenExceptions[itemInfo.Name] = true
                    end
                end
            end
        end
        local text = "所有古物"
        if #exceptions > 0 then text = text .. "(" .. table.concat(exceptions, "、") .. "除外)" end
        table.insert(outputPhrases, text)
    end

    if targetLevel == "like" and (npcName == "Penny" or npcName == "Dwarf") then
        processArchRule(true)
    elseif targetLevel == "dislike" and not (npcName == "Penny" or npcName == "Dwarf") then
        processArchRule(false)
    end

    -- 3. 处理默认规则 (Edibility / Price)
    if targetLevel == "hate" then
        for itemId, itemInfo in pairs(objectData) do
        	if itemInfo.Category ~= -999 and itemInfo.CanBeGivenAsGift and itemInfo.Price ~= 0 then
	            if itemInfo.Edibility and itemInfo.Edibility < 0 and itemInfo.Edibility ~= -300 then
	                if not _isItemCoveredBySpecificTaste(itemInfo, itemId, personalTastes, universalTastes) then
	                    addItem(itemInfo.Name)
	                end
	            end
	        end
        end
    elseif targetLevel == "dislike" then
        for itemId, itemInfo in pairs(objectData) do
        	if itemInfo.Category ~= -999 and itemInfo.CanBeGivenAsGift and itemInfo.Price ~= 0 then
	            if itemInfo.Price and itemInfo.Price < 20 then
	                if not _isItemCoveredBySpecificTaste(itemInfo, itemId, personalTastes, universalTastes) then
	                    addItem(itemInfo.Name)
	                end
	            end
	        end
        end
    end

    -- 4. 处理通用偏好并寻找个人例外
    local universalExceptions = {}
    local seenUniversalExceptions = {}
    local function addUniversalException(name) if name and not seenUniversalExceptions[name] then table.insert(universalExceptions, name); seenUniversalExceptions[name] = true end end

    local universalTargetRules = universalTastes[targetLevel] or {}
    for _, u_rule in ipairs(universalTargetRules) do
        local u_info = _getItemInfo(u_rule)
        local u_is_specific_item = u_info ~= nil
        
        for _, level in ipairs(tasteLevels) do
            if level ~= targetLevel then
                local personalRules = (personalTastes[level] and personalTastes[level].items) or {}
                for _, p_rule in ipairs(personalRules) do
                    local p_is_specific_item = _getItemInfo(p_rule) ~= nil

                    -- 检查是否存在冲突
                    local conflict = false
                    if u_is_specific_item and _itemMatchesRule(u_info, p_rule) then -- 通用物品 vs 个人分类/标签
                        conflict = true
                    elseif not u_is_specific_item and p_is_specific_item and _itemMatchesRule(_getItemInfo(p_rule), u_rule) then -- 通用分类/标签 vs 个人物品
                        conflict = true
                    elseif tostring(u_rule) == tostring(p_rule) then -- 规则完全相同
                        conflict = true
                    end

                    if conflict then
                        local exceptionName = nil
                        if u_is_specific_item then -- 如果通用规则是具体物品,那么例外就是这个物品
                            -- 优先级判断:只有当个人规则也是具体物品时,才能覆盖通用的具体物品规则
                            if p_is_specific_item or (type(p_rule) == 'string' and not objectData[p_rule]) then
                                exceptionName = u_info.Name
                            end
                        else -- 如果通用规则是分类/标签
                            if p_is_specific_item then -- 而个人规则是具体物品,那么例外就是这个个人物品
                                exceptionName = _getItemInfo(p_rule).Name
                            else -- 两者都是分类/标签,则例外是这个分类/标签
                                if type(u_rule) == 'number' then exceptionName = "所有" .. _getCategoryName(u_rule) else exceptionName = _getContextTagName(u_rule) end
                            end
                        end
                        if exceptionName then addUniversalException(exceptionName) end
                    end
                end
            end
        end
    end

    local universalTextMapping = { love = "最爱", like = "喜欢", neutral = "一般", dislike = "不喜欢", hate = "讨厌" }
    local universalText = "所有普遍" .. (universalTextMapping[targetLevel] or targetLevel) .. "的礼物"
    if #universalExceptions > 0 then
        table.sort(universalExceptions)
        universalText = universalText .. "(" .. table.concat(universalExceptions, "、") .. "除外)"
    end
    table.insert(outputPhrases, universalText)

    -- 5. 如果查询的是中立,则全面检查并补充说明“默认中立”的物品和分类
    if targetLevel == "neutral" then
        local defaultNeutralPhrases = {}
        
        local function isRuleDefined(ruleId)
            for _, level in ipairs(tasteLevels) do
                for _, p_rule in ipairs((personalTastes[level] and personalTastes[level].items) or {}) do
                    if tostring(p_rule) == tostring(ruleId) then return true end
                end
                for _, u_rule in ipairs(universalTastes[level] or {}) do
                    if tostring(u_rule) == tostring(ruleId) then return true end
                end
            end
            return false
        end

        -- 5a. 动态检查所有通用可赠送分类
        local giftableCategoryIds = { -2, -4, -5, -6, -12, -75, -79, -80, -81 }
        local handledCategories = {} -- 记录已被“所有XX”描述覆盖的分类
        
        for _, catId in ipairs(giftableCategoryIds) do
            if not isRuleDefined(catId) then
                handledCategories[catId] = true -- 将此分类标记为已处理
                local exceptions = {}
                local seenExceptions = {}
                local function findExceptionsInList(list) for _, rule in ipairs(list) do local itemInfo = _getItemInfo(rule) if itemInfo and itemInfo.Category == catId and not seenExceptions[itemInfo.Name] then table.insert(exceptions, itemInfo.Name); seenExceptions[itemInfo.Name] = true end end end
                for _, level in ipairs(tasteLevels) do findExceptionsInList((personalTastes[level] and personalTastes[level].items) or {}); findExceptionsInList(universalTastes[level] or {}) end
                
                local text = "所有" .. _getCategoryName(catId)
                if #exceptions > 0 then table.sort(exceptions); text = text .. "(" .. table.concat(exceptions, "、") .. "除外)" end
                table.insert(defaultNeutralPhrases, text)
            end
        end

        -- 5b. 动态遍历所有物品,筛选出未被分类覆盖的、值得关注的独立中立物品
        for itemId, itemInfo in pairs(objectData) do
            -- 条件1: 物品本身是“默认中立”的
            if not isRuleDefined(itemId) then
                -- 条件2: 物品不属于任何一个已被“所有XX”覆盖的分类
                if not handledCategories[itemInfo.Category] then
                    -- 条件3: 物品是“值得关注的”
                    -- local isNotable = (itemInfo.Category == 0) -- (分类为0)
                    local isNotable = false
                    if not isNotable and itemInfo.ContextTags then
                        for _, tag in ipairs(itemInfo.ContextTags) do
                            -- if tag == 'flower_item' then -- (花)
                            --     isNotable = true; break
                            -- end
                            if tag == 'forage_item' then -- (采集品)
                                isNotable = true; break
                            end
                        end
                    end
                    
                    if isNotable then
                        addItem(itemInfo.Name)
                    end
                end
            end
        end
        
        if #defaultNeutralPhrases > 0 then
            table.sort(defaultNeutralPhrases)
            for _, phrase in ipairs(defaultNeutralPhrases) do
                table.insert(outputPhrases, phrase)
            end
        end
    end

    -- 6. 格式化并返回
    local finalOutput = {}
    for _, phrase in ipairs(outputPhrases) do table.insert(finalOutput, phrase) end
    table.sort(outputItems)
    for _, itemName in ipairs(outputItems) do table.insert(finalOutput, itemName) end
    if #finalOutput == 0 then return "" end
    return mw.text.listToText(finalOutput)
end

return p