全站通知:
模块:GiftTastesNPC
刷
历
编
跳到导航
跳到搜索
-- #############################################################################
-- # Module:GiftTastesNPC (V7 - 伪代码重构版) - 按照伪代码逻辑重构
-- # ---------------------------------------------------------------------------
-- # 按照提供的伪代码逻辑重新实现的模块,确保正确的优先级处理。
-- # 依赖: Module:Object/data, Module:GiftTastesNPC/data
-- #############################################################################
local p = {}
-- 1. 加载依赖模块
local utils = require("Module:Utils")
local NPC = require('Module:NPC')
local ItemNames = require('Module:ItemNames')
local objectData = utils.lazyload('Module:Object/data')
local giftTasteData = utils.lazyload('Module:GiftTastesNPC/data')
-- 物品名称覆盖表:处理重复名称的情况
local nameOverrides = {
["诡异玩偶(绿)"] = "126",
["诡异玩偶(黄)"] = "127",
["Joja可乐"] = "167",
["垃圾(物品)"] = "168",
["破损的CD"] = "171",
["鱼饵(物品)"] = "685",
["针对性鱼饵"] = "SpecificBait",
["熏鱼"] = "Smoked",
["果干"] = "DriedFruit",
["蘑菇干"] = "DriedMushrooms",
["青蛙蛋"] = "FrogEgg",
["史莱姆蛋"] = "610",
}
-- 特殊物品配置表(非Objects类物品)
local specialItems = {
["FrogEgg"] = {
chineseName = "青蛙蛋",
fullId = "(TR)FrogEgg",
wikiPage = "青蛙蛋"
}
}
-- 不可赠送物品黑名单
local blacklistedItems = {
-- stones
["2"] = true, ["4"] = true, ["6"] = true, ["8"] = true, ["10"] = true,
["12"] = true, ["14"] = true, ["25"] = true, ["32"] = true, ["34"] = true,
["36"] = true, ["38"] = true, ["40"] = true, ["42"] = true, ["44"] = true,
["46"] = true, ["48"] = true, ["50"] = true, ["52"] = true, ["54"] = true,
["56"] = true, ["58"] = true, ["75"] = true, ["76"] = true, ["77"] = true,
["95"] = true, ["290"] = true, ["343"] = true, ["450"] = true, ["668"] = true,
["670"] = true, ["751"] = true, ["760"] = true, ["762"] = true, ["764"] = true,
["765"] = true, ["816"] = true, ["817"] = true, ["818"] = true, ["819"] = true,
["843"] = true, ["844"] = true, ["845"] = true, ["846"] = true, ["847"] = true,
["849"] = true, ["850"] = true, ["BasicCoalNode0"] = true, ["BasicCoalNode1"] = true,
["CalicoEggStone_0"] = true, ["CalicoEggStone_1"] = true, ["CalicoEggStone_2"] = true,
["PotOfGold"] = true, ["VolcanoGoldNode"] = true, ["VolcanoCoalNode0"] = true, ["VolcanoCoalNode1"] = true,
-- weeds
["0"] = true, ["313"] = true, ["314"] = true, ["315"] = true, ["316"] = true,
["317"] = true, ["318"] = true, ["319"] = true, ["320"] = true, ["321"] = true,
["452"] = true, ["674"] = true, ["675"] = true, ["676"] = true, ["677"] = true,
["678"] = true, ["679"] = true, ["750"] = true, ["784"] = true, ["785"] = true,
["786"] = true, ["792"] = true, ["793"] = true, ["794"] = true, ["882"] = true,
["883"] = true, ["884"] = true, ["GreenRainWeeds0"] = true, ["GreenRainWeeds1"] = true,
["GreenRainWeeds2"] = true, ["GreenRainWeeds3"] = true, ["GreenRainWeeds4"] = true,
["GreenRainWeeds5"] = true, ["GreenRainWeeds6"] = true, ["GreenRainWeeds7"] = true,
-- twigs
["294"] = true, ["295"] = true,
-- quest items
["71"] = true, ["191"] = true, ["742"] = true, ["788"] = true, ["790"] = true,
["864"] = true, ["865"] = true, ["866"] = true, ["867"] = true, ["868"] = true,
["869"] = true, ["870"] = true, ["875"] = true, ["876"] = true, ["897"] = true,
["GoldenBobber"] = true,
-- unstorable items (used on pickup)
["73"] = true, ["434"] = true, ["803"] = true, ["858"] = true, ["930"] = true,
["GoldCoin"] = true,
-- supply crates
["922"] = true, ["923"] = true, ["924"] = true, ["925"] = true,
-- secret notes
["79"] = true, ["842"] = true,
-- others
["277"] = true, ["458"] = true, ["460"] = true, ["808"] = true, ["809"] = true,
-- rings
["516"] = true, ["517"] = true, ["518"] = true, ["519"] = true, ["520"] = true,
["521"] = true, ["522"] = true, ["523"] = true, ["524"] = true, ["525"] = true,
["526"] = true, ["527"] = true, ["529"] = true, ["530"] = true, ["531"] = true,
["532"] = true, ["533"] = true, ["534"] = true, ["801"] = true, ["810"] = true,
["811"] = true, ["839"] = true, ["859"] = true, ["860"] = true, ["861"] = true,
["862"] = true, ["863"] = true, ["880"] = true, ["887"] = true, ["888"] = true,
-- unobtainable
["30"] = true, ["94"] = true, ["102"] = true, ["449"] = true, ["461"] = true,
["528"] = true, ["590"] = true, ["892"] = true, ["927"] = true, ["929"] = true,
["SeedSpot"] = true,
-- other ungiftable
["911"] = true, ["CalicoEgg"] = true, ["FarAwayStone"] = true, ["PrizeTicket"] = true, ["Lantern"] = true,
-- 特殊物品(史莱姆蛋)
["413"] = true, ["437"] = true, ["439"] = true, ["857"] = true
}
-- 检查物品是否在黑名单中(不可赠送)
local function _isBlacklisted(itemId)
local id = tostring(itemId)
return blacklistedItems[id] == true
end
-- 全局名称到ID映射表
local nameToIdMap = {}
-- 初始化名称映射表
local function _initializeNameMap()
if next(nameToIdMap) ~= nil then
return -- 已经初始化过了
end
mw.log("开始初始化物品名称映射表...")
-- 首先处理覆盖表中的项目
for chineseName, itemId in pairs(nameOverrides) do
nameToIdMap[chineseName] = itemId
mw.log("添加覆盖映射: " .. chineseName .. " -> " .. itemId)
end
-- 然后处理所有物品数据
for itemId, itemInfo in pairs(objectData) do
if not _isBlacklisted(itemId) then
local englishName = ItemNames.getEnglishName("(O)" .. itemId)
if englishName and englishName ~= "" then
-- 如果覆盖表中没有这个名称,才添加到映射表
if not nameToIdMap[englishName] then
nameToIdMap[englishName] = itemId
else
mw.log("跳过重复名称: " .. englishName .. " (原有ID: " .. nameToIdMap[englishName] .. ", 新ID: " .. itemId .. ")")
end
end
end
end
mw.log("名称映射表初始化完成,共 " .. tostring(#nameToIdMap) .. " 个条目")
end
-- #############################################################################
-- # UTF-8安全字符串处理函数
-- #############################################################################
-- 返回当前字符实际占用的字节数
local function _getUTF8ByteCount(str, index)
local curByte = string.byte(str, index)
local byteCount = 1
if curByte == nil then
byteCount = 0
elseif curByte > 0 and curByte <= 127 then
byteCount = 1
elseif curByte >= 192 and curByte <= 223 then
byteCount = 2
elseif curByte >= 224 and curByte <= 239 then
byteCount = 3
elseif curByte >= 240 and curByte <= 247 then
byteCount = 4
end
return byteCount
end
-- 获取UTF-8字符串的真实字符数量
local function _getUTF8Length(str)
local curIndex = 0
local i = 1
local lastCount = 1
repeat
lastCount = _getUTF8ByteCount(str, i)
i = i + lastCount
curIndex = curIndex + 1
until (lastCount == 0)
return curIndex - 1
end
-- 获取UTF-8字符串中指定字符索引的真实字节位置
local function _getUTF8TrueIndex(str, index)
local curIndex = 0
local i = 1
local lastCount = 1
repeat
lastCount = _getUTF8ByteCount(str, i)
i = i + lastCount
curIndex = curIndex + 1
until (curIndex >= index)
return i - lastCount
end
-- 安全截取UTF-8字符串,支持中英混合
local function _subStringUTF8(str, startIndex, endIndex)
if startIndex < 0 then
startIndex = _getUTF8Length(str) + startIndex + 1
end
if endIndex ~= nil and endIndex < 0 then
endIndex = _getUTF8Length(str) + endIndex + 1
end
if endIndex == nil then
return string.sub(str, _getUTF8TrueIndex(str, startIndex))
else
return string.sub(str, _getUTF8TrueIndex(str, startIndex), _getUTF8TrueIndex(str, endIndex + 1) - 1)
end
end
-- 安全查找UTF-8字符串中的子串位置(返回字符位置,不是字节位置)
local function _findUTF8(str, pattern, init, plain)
init = init or 1
local byteInit = _getUTF8TrueIndex(str, init)
local byteStart, byteEnd = string.find(str, pattern, byteInit, plain)
if not byteStart then
return nil
end
-- 将字节位置转换为字符位置
local charStart = 1
local i = 1
while i < byteStart do
local byteCount = _getUTF8ByteCount(str, i)
if byteCount == 0 then break end
i = i + byteCount
charStart = charStart + 1
end
local charEnd = charStart
while i <= byteEnd do
local byteCount = _getUTF8ByteCount(str, i)
if byteCount == 0 then break end
i = i + byteCount
charEnd = charEnd + 1
end
charEnd = charEnd - 1
return charStart, charEnd
end
-- #############################################################################
-- # 内部辅助函数与数据映射
-- #############################################################################
-- 不可赠送分类黑名单
local blacklistedCategories = {
[-96] = true, -- 戒指
[-99] = true, -- 工具
[-97] = true, -- 鞋类
[-24] = true, -- 装饰
}
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 = "所有怪物战利品",
ancient_item = "所有古物", -- 添加ancient_item的中文名称
}
-- 分类名称到维基链接的映射
local categoryLinks = {
["动物制品"] = ":分类:动物制品",
["矿物"] = "矿物",
["鱼"] = "鱼",
["蔬菜"] = "蔬菜",
["水果"] = "水果",
["花"] = "花",
["采集品"] = "采集#可采集物品",
["制造品"] = "打造#可打造物品",
["工匠物品"] = "工匠物品",
["菜品"] = ":分类:烹饪", -- 分类:菜品
["资源"] = ":分类:资源",
["化肥"] = "肥料", -- 化肥
["种子"] = ":分类:种子",
["垃圾"] = "垃圾",
["鱼饵"] = "鱼饵",
["钓具"] = "渔具",
-- ["装饰"] = "装饰",
["怪物战利品"] = "怪物战利品",
["书"] = "书",
["技能书"] = "书#技能之书",
["古物"] = "古物",
["树液"] = "树液采集器#产品"
}
-- 普遍类分类到友谊链接的映射
local universalCategoryLinks = {
-- ["普遍最爱的礼物"] = "友谊#最爱的礼物",
-- ["普遍喜欢的礼物"] = "友谊#喜欢的礼物",
-- ["普遍一般的礼物"] = "友谊#一般的礼物",
-- ["普遍不喜欢的礼物"] = "友谊#不喜欢的礼物",
-- ["普遍讨厌的礼物"] = "友谊#讨厌的礼物"
}
-- 格式化分类文本,处理普遍类加粗和分类链接
local function _formatCategoryText(categoryName)
-- 检查是否是普遍类分类
if _findUTF8(categoryName, "普遍", 1, true) then
-- 普遍类分类加粗并添加友谊链接
local linkTarget = universalCategoryLinks[categoryName]
if linkTarget then
return "所有'''[[" .. linkTarget .. "|" .. categoryName .. "]]'''"
else
-- 没有对应友谊链接,只加粗
return "所有'''" .. categoryName .. "'''"
end
else
-- 检查是否有对应的维基链接
local linkTarget = categoryLinks[categoryName]
if linkTarget then
if linkTarget == categoryName then
-- 链接名称与分类名称相同
return "所有[[" .. linkTarget .. "]]"
else
-- 链接名称与分类名称不同
return "所有[[" .. linkTarget .. "|" .. categoryName .. "]]"
end
else
-- 没有对应链接,直接返回
return "所有" .. categoryName
end
end
end
-- 特殊类型映射(根据Type字段)
local typeNames = {
Arch = "古物",
asdf = "书" -- 添加书籍类型映射
}
local function _getItemInfo(itemId)
-- 优先检查是否为特殊物品
if specialItems[itemId] then
return specialItems[itemId]
end
return objectData[tostring(itemId)]
end
-- 通过物品ID获取显示名称(优先使用覆盖表中的中文名称,否则使用英文名称)
local function _getItemDisplayName(itemId)
-- 优先检查是否为特殊物品
if specialItems[itemId] then
return specialItems[itemId].chineseName
end
-- 首先检查覆盖表中是否有对应的中文名称
for chineseName, overrideId in pairs(nameOverrides) do
if tostring(overrideId) == tostring(itemId) then
return chineseName
end
end
-- 如果覆盖表中没有,使用 ItemNames 模块获取英文名称
local englishName = ItemNames.getEnglishName("(O)" .. itemId)
if englishName and englishName ~= "" then
return englishName
end
-- 如果获取失败,返回原始ID
return tostring(itemId)
end
-- 通过物品ID获取中文名称(优先使用覆盖表中的中文名称,否则使用中文名称),和上面的功能不同
local function _getItemChineseName(itemId)
-- 优先检查是否为特殊物品
if specialItems[itemId] then
return specialItems[itemId].chineseName
end
-- 首先检查覆盖表中是否有对应的中文名称
for chineseName, overrideId in pairs(nameOverrides) do
if tostring(overrideId) == tostring(itemId) then
return chineseName
end
end
-- 如果覆盖表中没有,使用 ItemNames 模块获取英文名称
local chineseName = ItemNames.getChineseName("(O)" .. itemId)
if chineseName and chineseName ~= "" then
return chineseName
end
-- 如果获取失败,返回原始ID
return tostring(itemId)
end
-- 通过物品名称获取物品ID(支持中文名称和英文名称)
local function _getItemIdByName(itemName)
_initializeNameMap() -- 确保映射表已初始化
local result = nameToIdMap[itemName]
return result
end
-- Name 模板特殊规则配置
local nameTemplateRules = {
-- 特殊链接规则
links = {
["Green Slime Egg"] = "史莱姆蛋"
},
-- 文本处理规则
textProcessors = {
-- 去掉英语冒号
function(name)
if _findUTF8(name, ":", 1, true) then
return name:gsub(":", "")
end
return name
end
}
}
-- 添加更多特殊链接
-- nameTemplateRules.links["Another Item"] = "另一个物品"
-- 添加更多文本处理器
-- table.insert(nameTemplateRules.textProcessors, function(name)
-- 新的处理逻辑
-- return processedName
-- end)
-- 展开 Name 模板并应用特殊规则
local function _expandNameTemplate(itemName, templateParams)
-- 应用物品名称特殊规则
local processedName = itemName
local extraParams = {}
-- 应用特殊链接规则
if nameTemplateRules.links[itemName] then
extraParams.link = nameTemplateRules.links[itemName]
end
-- 应用文本处理规则
for _, processor in ipairs(nameTemplateRules.textProcessors) do
processedName = processor(processedName)
end
-- 构建完整的模板参数
local finalParams = {processedName}
-- 添加原有参数
if templateParams then
for key, value in pairs(templateParams) do
if type(key) == "number" then
-- 数字索引参数,追加到位置参数
table.insert(finalParams, value)
else
-- 命名参数
finalParams[key] = value
end
end
end
-- 添加额外参数
for key, value in pairs(extraParams) do
finalParams[key] = value
end
return utils.expandTemplate("Name", finalParams)
end
local function _getCategoryName(categoryId)
return categoryNames[categoryId] or "未知分类"
end
local function _getTypeName(typeName)
return typeNames[typeName] or typeName
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
-- 字符串规则:包含下划线的为contexttag,不包含下划线的为物品ID
if string.find(ruleId, '_') then
-- 包含下划线,作为contexttag处理
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
else
-- 不包含下划线,作为物品ID处理
return false -- 这里应该返回false,因为此函数检查分类匹配,而物品ID不应该在这里匹配
end
end
return false
end
-- 检查分类是否在黑名单中(不可赠送的分类)
local function _isCategoryBlacklisted(category)
return blacklistedCategories[category] == true
end
-- 检查物品是否因分类而被黑名单(组合检查)
local function _isItemBlacklistedByCategory(itemInfo)
if not itemInfo or not itemInfo.Category then return false end
return _isCategoryBlacklisted(itemInfo.Category)
end
-- 按照分类、类型、价格排序物品名称列表
local function _sortItemNames(itemNames)
_initializeNameMap() -- 确保映射表已初始化
local itemData = {}
-- 为每个物品名称找到对应的详细信息
for _, itemName in ipairs(itemNames) do
local itemId = _getItemIdByName(itemName)
local itemInfo = nil
if itemId then
itemInfo = objectData[tostring(itemId)]
end
if not itemInfo then
mw.log("警告: 无法找到物品信息: " .. itemName)
end
table.insert(itemData, {
name = itemName,
category = itemInfo and itemInfo.Category or 99999,
type = itemInfo and itemInfo.Type or "unknown",
price = itemInfo and itemInfo.Price or 0
})
end
-- 按照规则排序
table.sort(itemData, function(a, b)
if a.category ~= b.category then
return a.category < b.category
end
if a.type ~= b.type then
return a.type < b.type
end
return a.price < b.price
end)
-- 返回排序后的名称列表
local sortedNames = {}
for _, data in ipairs(itemData) do
table.insert(sortedNames, data.name)
end
return sortedNames
end
-- 格式化除外物品列表,暂时不展开模板,在最终输出时再展开
local function _formatExceptionItems(exceptions)
if #exceptions == 0 then
return ""
end
return "(" .. table.concat(exceptions, "、") .. "除外)"
end
-- 在最终输出时展开除外物品的 Name 模板
local function _expandExceptionItems(text)
-- 查找所有"(...除外)"的模式并展开Name模板
return string.gsub(text, "((.-)除外)", function(exceptions)
local formattedExceptions = {}
-- 使用UTF-8安全的字符串分割方法
local totalLen = _getUTF8Length(exceptions)
local pos = 1
while pos <= totalLen do
local nextPos = _findUTF8(exceptions, "、", pos, true)
local itemName
if nextPos then
itemName = _subStringUTF8(exceptions, pos, nextPos - 1)
pos = nextPos + 1 -- "、"是1个UTF-8字符
else
itemName = _subStringUTF8(exceptions, pos)
pos = totalLen + 1
end
if itemName and itemName ~= "" then
local nameTemplate = _expandNameTemplate(itemName, {
class = "inline"
})
table.insert(formattedExceptions, nameTemplate)
end
end
return "(" .. table.concat(formattedExceptions, "、") .. "除外)"
end)
end
-- #############################################################################
-- # 核心逻辑函数 - 按照伪代码实现
-- #############################################################################
local done = false
-- 根据伪代码实现的礼物偏好计算逻辑
local function _getTasteForItem(itemId, itemInfo, npcName, personalTastes, universalTastes)
if not itemInfo then return "neutral" end
local TASTE = "neutral"
local HAS_UNIVERSAL_ID = false
local HAS_UNIVERSAL_NEUTRAL_ID = false
local HAS_UNIVERSAL_CATEGORY = false
-- Part I: universal taste by category
if itemInfo.Category then
for _, ruleId in ipairs(universalTastes.love or {}) do
if _itemMatchesRule(itemInfo, ruleId) then
TASTE = "love"
HAS_UNIVERSAL_CATEGORY = true
break
end
end
if TASTE == "neutral" then
for _, ruleId in ipairs(universalTastes.hate or {}) do
if _itemMatchesRule(itemInfo, ruleId) then
TASTE = "hate"
HAS_UNIVERSAL_CATEGORY = true
break
end
end
end
if TASTE == "neutral" then
for _, ruleId in ipairs(universalTastes.like or {}) do
if _itemMatchesRule(itemInfo, ruleId) then
TASTE = "like"
HAS_UNIVERSAL_CATEGORY = true
break
end
end
end
if TASTE == "neutral" then
for _, ruleId in ipairs(universalTastes.dislike or {}) do
if _itemMatchesRule(itemInfo, ruleId) then
TASTE = "dislike"
HAS_UNIVERSAL_CATEGORY = true
break
end
end
end
end
-- Part II: universal taste by item ID
local itemIdStr = tostring(itemId)
for _, ruleId in ipairs(universalTastes.love or {}) do
if tostring(ruleId) == itemIdStr then
TASTE = "love"
HAS_UNIVERSAL_ID = true
break
end
end
if not HAS_UNIVERSAL_ID then
for _, ruleId in ipairs(universalTastes.hate or {}) do
if tostring(ruleId) == itemIdStr then
TASTE = "hate"
HAS_UNIVERSAL_ID = true
break
end
end
end
if not HAS_UNIVERSAL_ID then
for _, ruleId in ipairs(universalTastes.like or {}) do
if tostring(ruleId) == itemIdStr then
TASTE = "like"
HAS_UNIVERSAL_ID = true
break
end
end
end
if not HAS_UNIVERSAL_ID then
for _, ruleId in ipairs(universalTastes.dislike or {}) do
if tostring(ruleId) == itemIdStr then
TASTE = "dislike"
HAS_UNIVERSAL_ID = true
break
end
end
end
if not HAS_UNIVERSAL_ID then
for _, ruleId in ipairs(universalTastes.neutral or {}) do
if tostring(ruleId) == itemIdStr then
TASTE = "neutral"
HAS_UNIVERSAL_ID = true
HAS_UNIVERSAL_NEUTRAL_ID = true
break
end
end
end
-- Part III: override neutral if it's from universal category
if TASTE == "neutral" and not HAS_UNIVERSAL_NEUTRAL_ID then
if itemInfo.Edibility and itemInfo.Edibility < 0 and itemInfo.Edibility ~= -300 then
TASTE = "hate"
elseif itemInfo.Price and itemInfo.Price < 20 then
TASTE = "dislike"
end
end
-- Special rule: Artifacts (古物规则)
if itemInfo.Type == "Arch" then
if npcName == "Penny" or npcName == "Dwarf" then
-- Check if personally overridden
local personallyOverridden = false
for _, level in ipairs({"love", "hate", "dislike", "neutral"}) do
local items = (personalTastes[level] and personalTastes[level].items) or {}
for _, rule in ipairs(items) do
if tostring(rule) == itemIdStr or _itemMatchesRule(itemInfo, rule) then
personallyOverridden = true
break
end
end
if personallyOverridden then break end
end
if not personallyOverridden and not HAS_UNIVERSAL_ID then
TASTE = "like"
end
else
-- Other NPCs dislike artifacts by default
local personallyOverridden = false
for _, level in ipairs({"love", "like", "hate", "neutral"}) do
local items = (personalTastes[level] and personalTastes[level].items) or {}
for _, rule in ipairs(items) do
if tostring(rule) == itemIdStr or _itemMatchesRule(itemInfo, rule) then
personallyOverridden = true
break
end
end
if personallyOverridden then break end
end
if not personallyOverridden and not HAS_UNIVERSAL_ID then
TASTE = "dislike"
end
end
end
-- Part IV: override with personal tastes
local function checkPersonalOverride(level)
local items = (personalTastes[level] and personalTastes[level].items) or {}
for _, rule in ipairs(items) do
if tostring(rule) == itemIdStr then
-- Direct item match always overrides everything
return true
elseif itemInfo.Category and _itemMatchesRule(itemInfo, rule) then
-- Context tag match always overrides (no wasIndividualUniversal check for context tags)
-- Category match only overrides if not affected by universal individual rules
if type(rule) == 'string' and not objectData[rule] then
-- This is a context tag, always override
return true
elseif not HAS_UNIVERSAL_ID then
-- This is a category, only override if no universal individual rules
return true
end
end
end
return false
end
if checkPersonalOverride("love") then
return "love"
end
if checkPersonalOverride("hate") then
return "hate"
end
if checkPersonalOverride("like") then
return "like"
end
if checkPersonalOverride("dislike") then
return "dislike"
end
if checkPersonalOverride("neutral") then
return "neutral"
end
-- Part V: return taste if not overridden
return TASTE
end
-- #############################################################################
-- # 主要外部函数
-- #############################################################################
-- 处理分类规则的通用函数
local function _processCategoryRule(ruleId, ruleSource, targetLevel, npcName, personalTastes, universalTastes, seenRules, outputPhrases, personalSpecialItems, addPersonalSpecialItem)
if seenRules[tostring(ruleId)] or (type(ruleId) == 'number' and _isCategoryBlacklisted(ruleId)) then
return
end
-- 特殊处理ancient_item:如果已经有Arch规则,则跳过ancient_item
if ruleId == "ancient_item" and seenRules["Arch_implicit"] then
mw.log("跳过ancient_item规则,因为已经有Arch隐性分类规则")
return
end
local ruleName
if type(ruleId) == 'number' and ruleId < 0 then
ruleName = "所有" .. _getCategoryName(ruleId)
elseif type(ruleId) == 'string' then
-- 字符串规则:包含下划线的为contexttag,不包含下划线的为物品
if string.find(ruleId, '_') then
ruleName = _getContextTagName(ruleId)
else
-- 不包含下划线,作为物品名称处理
if objectData[ruleId] then
ruleName = objectData[ruleId].name
else
ruleName = ruleId -- 使用原始名称
end
end
end
if not ruleName then return end
mw.log("处理" .. ruleSource .. "分类规则 " .. tostring(ruleId) .. " -> " .. ruleName .. " (targetLevel: " .. targetLevel .. ")")
local exceptions = {}
local seenExceptions = {}
local matchedItems = 0 -- 添加计数器
-- 遍历所有物品,找到匹配这个规则的物品
for itemId, itemInfo in pairs(objectData) do
if itemInfo.Category ~= -999 and itemInfo.CanBeGivenAsGift and _itemMatchesRule(itemInfo, ruleId) and not _isBlacklisted(itemId) and not _isItemBlacklistedByCategory(itemInfo) then
matchedItems = matchedItems + 1
local actualTaste = _getTasteForItem(itemId, itemInfo, npcName, personalTastes, universalTastes)
local chineseName = _getItemDisplayName(itemId)
if actualTaste == targetLevel and chineseName and addPersonalSpecialItem then
-- 符合个人分类规则的物品,添加到个人特殊喜好
addPersonalSpecialItem(chineseName)
elseif actualTaste ~= targetLevel and chineseName and not seenExceptions[chineseName] then
table.insert(exceptions, chineseName)
seenExceptions[chineseName] = true
end
end
end
mw.log("规则 " .. tostring(ruleId) .. " 匹配了 " .. matchedItems .. " 个物品,除外 " .. #exceptions .. " 个")
-- 检查除外是否过多,如果除外超过匹配项的50%,则不创建分类规则
local validItems = 0
for itemId, itemInfo in pairs(objectData) do
if itemInfo.Category ~= -999 and itemInfo.CanBeGivenAsGift and _itemMatchesRule(itemInfo, ruleId) and not _isBlacklisted(itemId) and not _isItemBlacklistedByCategory(itemInfo) then
local actualTaste = _getTasteForItem(itemId, itemInfo, npcName, personalTastes, universalTastes)
if actualTaste == targetLevel then
validItems = validItems + 1
end
end
end
if #exceptions > validItems * 0.5 and validItems > 0 then
mw.log("分类规则" .. ruleName .. "除外项过多(" .. #exceptions .. "/" .. validItems .. "),不创建分类")
return
end
local text = ruleName
if #exceptions > 0 then
exceptions = _sortItemNames(exceptions)
text = text .. _formatExceptionItems(exceptions)
end
mw.log("添加分类规则: " .. text)
table.insert(outputPhrases, text)
seenRules[tostring(ruleId)] = true
end
-- 处理个人偏好规则
local function _processPersonalPreferences(personalTastes, targetLevel, npcName, universalTastes, seenRules, outputPhrases, personalSpecialItems, seenPersonalItems)
local personalIds = (personalTastes[targetLevel] and personalTastes[targetLevel].items) or {}
local function addPersonalSpecialItem(name)
if name and not seenPersonalItems[name] then
table.insert(personalSpecialItems, name)
seenPersonalItems[name] = true
end
end
for _, id in ipairs(personalIds) do
if type(id) == 'number' and id >= 0 or (type(id) == 'string' and objectData[id] or id == "FrogEgg") then
-- 具体物品
local itemInfo = _getItemInfo(id)
local chineseName = _getItemDisplayName(id)
if itemInfo and chineseName and not _isBlacklisted(id) and not _isItemBlacklistedByCategory(itemInfo) then
-- 验证这个物品确实应该是这个偏好等级
local actualTaste = _getTasteForItem(id, itemInfo, npcName, personalTastes, universalTastes)
if actualTaste == targetLevel then
addPersonalSpecialItem(chineseName)
end
end
else
-- 分类或标签规则 - 不添加物品到个人特殊喜好列表,因为会被分类规则覆盖
_processCategoryRule(id, "个人偏好", targetLevel, npcName, personalTastes, universalTastes, seenRules, outputPhrases, personalSpecialItems, nil)
end
end
end
-- 处理通用偏好规则
local function _processUniversalPreferences(universalTastes, targetLevel, npcName, personalTastes, seenRules, outputPhrases, universalRuleExceptions)
local universalTargetRules = universalTastes[targetLevel] or {}
-- 首先检查是否有通用具体物品,如果有,创建"所有普遍XX的礼物"分类
local hasUniversalItems = false
for _, u_rule in ipairs(universalTargetRules) do
if type(u_rule) == 'number' and u_rule >= 0 or (type(u_rule) == 'string' and objectData[u_rule]) then
hasUniversalItems = true
break
end
end
if hasUniversalItems then
local levelNames = {
love = "最爱",
like = "喜欢",
neutral = "一般",
dislike = "不喜欢",
hate = "讨厌"
}
local ruleName = "所有普遍" .. (levelNames[targetLevel] or targetLevel) .. "的礼物"
mw.log("处理通用偏好物品规则 -> " .. ruleName .. " (targetLevel: " .. targetLevel .. ")")
local exceptions = {}
local seenExceptions = {}
-- 查找被个人偏好覆盖的通用物品
for _, u_rule in ipairs(universalTargetRules) do
if type(u_rule) == 'number' and u_rule >= 0 or (type(u_rule) == 'string' and objectData[u_rule]) then
local u_info = _getItemInfo(u_rule)
local chineseName = _getItemDisplayName(u_rule)
if u_info and chineseName and not _isBlacklisted(u_rule) and not _isItemBlacklistedByCategory(u_info) then
local actualTaste = _getTasteForItem(u_rule, u_info, npcName, personalTastes, universalTastes)
if actualTaste ~= targetLevel and not seenExceptions[chineseName] then
table.insert(exceptions, chineseName)
seenExceptions[chineseName] = true
end
end
end
end
local text = ruleName
if #exceptions > 0 then
exceptions = _sortItemNames(exceptions)
text = text .. _formatExceptionItems(exceptions)
-- 存储除外信息到全局变量
universalRuleExceptions[targetLevel] = exceptions
end
mw.log("添加通用物品分类规则: " .. text)
table.insert(outputPhrases, text)
seenRules["universal_items_" .. targetLevel] = true
end
-- 然后处理通用分类规则
for _, u_rule in ipairs(universalTargetRules) do
if not (type(u_rule) == 'number' and u_rule >= 0 or (type(u_rule) == 'string' and objectData[u_rule])) then
-- 检查这个通用规则是否已经被个人偏好定义为其他等级
local isPersonallyOverridden = false
for _, level in ipairs({"love", "like", "dislike", "hate"}) do
if level ~= targetLevel then -- 检查其他等级
local personalItems = (personalTastes[level] and personalTastes[level].items) or {}
for _, rule in ipairs(personalItems) do
if tostring(rule) == tostring(u_rule) then
isPersonallyOverridden = true
mw.log("跳过通用" .. targetLevel .. "规则 " .. tostring(u_rule) .. ",因为被个人" .. level .. "覆盖")
break
end
end
if isPersonallyOverridden then break end
end
end
-- 只有当没有被个人偏好覆盖时,才处理这个通用分类规则
if not isPersonallyOverridden then
_processCategoryRule(u_rule, "通用偏好", targetLevel, npcName, personalTastes, universalTastes, seenRules, outputPhrases, nil, nil)
end
end
end
end
-- 检查物品是否被规则覆盖
local function _isItemCoveredByRules(itemId, itemInfo, seenRules, universalTastes, targetLevel, personalTastes)
-- 检查是否被通用物品规则覆盖
local universalItemIds = universalTastes[targetLevel] or {}
for _, u_rule in ipairs(universalItemIds) do
if type(u_rule) == 'number' and u_rule >= 0 or (type(u_rule) == 'string' and objectData[u_rule]) then
if tostring(u_rule) == tostring(itemId) then
return true
end
end
end
-- 检查其他规则覆盖
for ruleKey, _ in pairs(seenRules) do
local rule = tonumber(ruleKey) or ruleKey
if _itemMatchesRule(itemInfo, rule) or tostring(rule) == tostring(itemId) then
-- 添加调试日志来跟踪哪些物品被哪些规则覆盖
local chineseName = _getItemDisplayName(itemId)
mw.log("物品 " .. (chineseName or "unknown") .. " (ID: " .. itemId .. ") 被规则 " .. tostring(rule) .. " 覆盖")
return true
end
end
-- 检查个人规则覆盖
for _, level in ipairs({"love", "like", "neutral", "dislike", "hate"}) do
local items = (personalTastes[level] and personalTastes[level].items) or {}
for _, rule in ipairs(items) do
if tostring(rule) == tostring(itemId) or _itemMatchesRule(itemInfo, rule) then
return true
end
end
end
return false
end
-- 处理默认规则产生的物品
local function _processDefaultItems(targetLevel, npcName, personalTastes, universalTastes, seenRules, outputItems, seenItems)
local function addItem(name)
if name and not seenItems[name] then
table.insert(outputItems, name)
seenItems[name] = true
end
end
for itemId, itemInfo in pairs(objectData) do
if itemInfo.Category ~= -999 and itemInfo.CanBeGivenAsGift and itemInfo.Price ~= 0 and not _isBlacklisted(itemId) and not _isItemBlacklistedByCategory(itemInfo) then
local actualTaste = _getTasteForItem(itemId, itemInfo, npcName, personalTastes, universalTastes)
if actualTaste == targetLevel then
local coveredByRule = _isItemCoveredByRules(itemId, itemInfo, seenRules, universalTastes, targetLevel, personalTastes)
local chineseName = _getItemDisplayName(itemId)
if not coveredByRule and chineseName then
addItem(chineseName)
end
end
end
end
end
-- 处理隐性分类(如古物)
local function _processImplicitCategories(targetLevel, npcName, personalTastes, universalTastes, seenRules, outputPhrases, outputItems)
local typeGroups = {}
-- 收集当前找到的所有具体物品,按类型分组
for itemId, itemInfo in pairs(objectData) do
if itemInfo.Category ~= -999 and itemInfo.CanBeGivenAsGift and not _isBlacklisted(itemId) and not _isItemBlacklistedByCategory(itemInfo) then
local actualTaste = _getTasteForItem(itemId, itemInfo, npcName, personalTastes, universalTastes)
if actualTaste == targetLevel then
if itemInfo.Type and typeNames[itemInfo.Type] then
if not typeGroups[itemInfo.Type] then
typeGroups[itemInfo.Type] = {total = 0, covered = 0, items = {}}
end
typeGroups[itemInfo.Type].total = typeGroups[itemInfo.Type].total + 1
typeGroups[itemInfo.Type].items[itemId] = _getItemDisplayName(itemId)
local coveredByRule = _isItemCoveredByRules(itemId, itemInfo, seenRules, universalTastes, targetLevel, personalTastes)
if not coveredByRule then
typeGroups[itemInfo.Type].covered = typeGroups[itemInfo.Type].covered + 1
end
end
end
end
end
-- 对于每个类型,如果大部分物品都是目标偏好等级,则创建分类规则
for typeName, group in pairs(typeGroups) do
-- 跳过书籍类型,因为书籍已经通过 book_item 标签处理了
if typeName == "asdf" and seenRules["book_item"] then
mw.log("跳过书籍类型的隐性分类,因为已经有 book_item 规则")
elseif group.covered >= 3 and group.covered / group.total >= 0.8 then
local exceptions = {}
local seenExceptions = {}
-- 找到不是目标偏好等级的物品
for itemId, itemInfo in pairs(objectData) do
if itemInfo.Type == typeName and itemInfo.Category ~= -999 and itemInfo.CanBeGivenAsGift and not _isBlacklisted(itemId) and not _isItemBlacklistedByCategory(itemInfo) then
local actualTaste = _getTasteForItem(itemId, itemInfo, npcName, personalTastes, universalTastes)
local chineseName = _getItemDisplayName(itemId)
if actualTaste ~= targetLevel and chineseName and not seenExceptions[chineseName] then
table.insert(exceptions, chineseName)
seenExceptions[chineseName] = true
end
end
end
-- 如果除外项过多,则不创建分类
if #exceptions > group.covered * 0.5 then
mw.log("隐性分类" .. typeName .. "除外项过多(" .. #exceptions .. "/" .. group.covered .. "),不创建分类")
else
local text = "所有" .. _getTypeName(typeName)
if #exceptions > 0 then
exceptions = _sortItemNames(exceptions)
text = text .. _formatExceptionItems(exceptions)
end
mw.log("添加隐性分类规则: " .. text)
table.insert(outputPhrases, text)
-- 如果是Arch类型,标记为已处理,以便跳过ancient_item规则
if typeName == "Arch" then
seenRules["Arch_implicit"] = true
mw.log("标记Arch隐性分类已处理,将跳过后续的ancient_item规则")
end
-- 从个别物品列表中移除这个类型的物品
local newOutputItems = {}
for _, itemName in ipairs(outputItems) do
local shouldRemove = false
for itemId, itemInfo in pairs(objectData) do
if _getItemDisplayName(itemId) == itemName and itemInfo.Type == typeName then
shouldRemove = true
break
end
end
if not shouldRemove then
table.insert(newOutputItems, itemName)
end
end
for i = 1, #outputItems do outputItems[i] = nil end
for _, item in ipairs(newOutputItems) do
table.insert(outputItems, item)
end
end
end
end
end
-- 处理中立查询的默认分类
local function _processNeutralDefaults(targetLevel, npcName, personalTastes, universalTastes, seenRules, outputPhrases)
if targetLevel ~= "neutral" then return end
local giftableCategoryIds = { -2, -4, -5, -6, -12, -75, -79, -80, -81 }
for _, catId in ipairs(giftableCategoryIds) do
if not seenRules[tostring(catId)] then
-- 检查这个分类是否有任何规则定义
local hasRule = false
for _, level in ipairs({"love", "like", "dislike", "hate"}) do
-- 检查个人规则
local personalItems = (personalTastes[level] and personalTastes[level].items) or {}
for _, rule in ipairs(personalItems) do
if tostring(rule) == tostring(catId) then
hasRule = true
break
end
end
if hasRule then break end
-- 检查通用规则
local universalItems = universalTastes[level] or {}
for _, rule in ipairs(universalItems) do
if tostring(rule) == tostring(catId) then
hasRule = true
break
end
end
if hasRule then break end
end
if not hasRule then
local neutralItems = 0
local totalItems = 0
local exceptions = {}
local seenExceptions = {}
for itemId, itemInfo in pairs(objectData) do
if itemInfo.Category == catId and itemInfo.CanBeGivenAsGift and not _isBlacklisted(itemId) and not _isItemBlacklistedByCategory(itemInfo) then
totalItems = totalItems + 1
local actualTaste = _getTasteForItem(itemId, itemInfo, npcName, personalTastes, universalTastes)
local chineseName = _getItemDisplayName(itemId)
if actualTaste == "neutral" then
neutralItems = neutralItems + 1
elseif actualTaste ~= "neutral" and chineseName and not seenExceptions[chineseName] then
table.insert(exceptions, chineseName)
seenExceptions[chineseName] = true
end
end
end
-- 只有当中立物品占大多数(超过80%)且总数超过3个时,才创建分类规则
if totalItems >= 3 and neutralItems >= totalItems * 0.8 then
local text = "所有" .. _getCategoryName(catId)
if #exceptions > 0 then
exceptions = _sortItemNames(exceptions)
text = text .. _formatExceptionItems(exceptions)
end
mw.log("添加中立分类规则: " .. text .. " (中立: " .. neutralItems .. "/" .. totalItems .. ")")
table.insert(outputPhrases, text)
seenRules[tostring(catId)] = true
else
mw.log("跳过中立分类规则 " .. _getCategoryName(catId) .. " (中立: " .. neutralItems .. "/" .. totalItems .. ", 比例不足)")
end
end
end
end
end
-- 合并和排序最终输出
local function _buildFinalOutput(outputPhrases, personalSpecialItems, outputItems)
-- 预解析所有分类规则,避免重复字符串切片操作
local parsedCategoryRules = {} -- 存储解析后的分类规则信息
local categoryMap = {}
local phraseData = {}
-- 解析分类规则的安全函数
local function parsePhrase(phrase)
local categoryName = nil
local exceptions = {}
local exceptionSet = {}
-- 使用UTF-8安全函数查找除外开始位置
local exceptionStart = _findUTF8(phrase, "(", 1, true)
if exceptionStart then
-- 有除外的情况
categoryName = _subStringUTF8(phrase, 3, exceptionStart - 1) -- "所有"是2个UTF-8字符
local exceptionEnd = _findUTF8(phrase, "除外)", exceptionStart, true)
if exceptionEnd then
local exceptionsStr = _subStringUTF8(phrase, exceptionStart + 1, exceptionEnd - 1)
-- 使用UTF-8安全的字符串分割
local totalLen = _getUTF8Length(exceptionsStr)
local pos = 1
while pos <= totalLen do
local nextPos = _findUTF8(exceptionsStr, "、", pos, true)
local exception
if nextPos then
exception = _subStringUTF8(exceptionsStr, pos, nextPos - 1)
pos = nextPos + 1 -- "、"是1个UTF-8字符
else
exception = _subStringUTF8(exceptionsStr, pos)
pos = totalLen + 1
end
if exception and exception ~= "" then
table.insert(exceptions, exception)
exceptionSet[exception] = true
end
end
end
else
-- 没有除外的情况
categoryName = _subStringUTF8(phrase, 3) -- "所有"是2个UTF-8字符
end
return categoryName, exceptions, exceptionSet
end
-- 解析所有分类规则
for _, phrase in ipairs(outputPhrases) do
if _subStringUTF8(phrase, 1, 2) == "所有" then
local categoryName, exceptions, exceptionSet = parsePhrase(phrase)
if categoryName then
-- 存储解析后的规则信息,用于后续快速查找
parsedCategoryRules[phrase] = {
categoryName = categoryName,
exceptions = exceptions,
exceptionSet = exceptionSet
}
-- 维护categoryMap用于合并逻辑
if not categoryMap[categoryName] then
categoryMap[categoryName] = {}
end
for _, exception in ipairs(exceptions) do
categoryMap[categoryName][exception] = true
end
end
else
table.insert(phraseData, {
text = phrase,
type = "other",
isUniversal = false,
category = 99999,
price = 0
})
end
end
-- 重建分类规则并分离可合并的分类
local mergableCategories = {}
local standaloneCategories = {}
for categoryName, exceptionsMap in pairs(categoryMap) do
local exceptions = {}
for exception, _ in pairs(exceptionsMap) do
table.insert(exceptions, exception)
end
-- 使用格式化函数生成基础文本
local text = _formatCategoryText(categoryName)
if #exceptions > 0 then
exceptions = _sortItemNames(exceptions)
text = text .. _formatExceptionItems(exceptions)
end
local isUniversal = _findUTF8(categoryName, "普遍", 1, true) ~= nil
local hasExceptions = #exceptions > 0
if isUniversal or hasExceptions then
table.insert(standaloneCategories, {
text = text,
type = "category",
isUniversal = isUniversal,
category = isUniversal and 1 or 2,
price = 0
})
else
table.insert(mergableCategories, categoryName)
end
end
-- 处理可合并的分类
if #mergableCategories > 0 then
local mergedText = ""
if #mergableCategories == 1 then
-- 单个分类,直接使用格式化函数
mergedText = _formatCategoryText(mergableCategories[1])
else
-- 多个分类合并,需要特殊处理链接
local formattedParts = {}
if #mergableCategories == 2 then
-- 两个分类
for _, categoryName in ipairs(mergableCategories) do
local linkTarget = categoryLinks[categoryName]
if linkTarget then
if linkTarget == categoryName then
table.insert(formattedParts, "[[" .. linkTarget .. "]]")
else
table.insert(formattedParts, "[[" .. linkTarget .. "|" .. categoryName .. "]]")
end
else
table.insert(formattedParts, categoryName)
end
end
mergedText = "所有" .. formattedParts[1] .. "和" .. formattedParts[2]
else
-- 三个或以上分类
for i = 1, #mergableCategories do
local categoryName = mergableCategories[i]
local linkTarget = categoryLinks[categoryName]
if linkTarget then
if linkTarget == categoryName then
table.insert(formattedParts, "[[" .. linkTarget .. "]]")
else
table.insert(formattedParts, "[[" .. linkTarget .. "|" .. categoryName .. "]]")
end
else
table.insert(formattedParts, categoryName)
end
end
local parts = {}
for i = 1, #formattedParts - 1 do
table.insert(parts, formattedParts[i])
end
mergedText = "所有" .. table.concat(parts, "、") .. "和" .. formattedParts[#formattedParts]
end
end
table.insert(standaloneCategories, {
text = mergedText,
type = "category",
isUniversal = false,
category = 3,
price = 0
})
end
-- 添加所有分类到phraseData并排序
for _, data in ipairs(standaloneCategories) do
table.insert(phraseData, data)
end
table.sort(phraseData, function(a, b)
if a.isUniversal ~= b.isUniversal then
return a.isUniversal
end
if a.category ~= b.category then
return a.category < b.category
end
if a.type ~= b.type then
return a.type < b.type
end
return a.text < b.text
end)
-- 物品数据创建函数
local function createItemData(itemNames, itemType)
_initializeNameMap()
local itemData = {}
for _, itemName in ipairs(itemNames) do
local itemId = _getItemIdByName(itemName)
if not _isBlacklisted(itemId) then
local itemInfo = itemId and objectData[tostring(itemId)]
table.insert(itemData, {
text = itemName,
type = itemType,
isUniversal = false,
category = itemInfo and itemInfo.Category or 0,
price = itemInfo and itemInfo.Price or 0
})
end
end
table.sort(itemData, function(a, b)
if a.category ~= b.category then
return a.category < b.category
end
if a.type ~= b.type then
return a.type < b.type
end
return a.price < b.price
end)
return itemData
end
local personalItemData = createItemData(personalSpecialItems, "personal")
-- 高效的物品覆盖检查函数
local function isItemCoveredByPhrases(itemName)
local itemId = _getItemIdByName(itemName)
if not itemId then return false end
local itemInfo = objectData[tostring(itemId)]
if not itemInfo then return false end
-- 使用预解析的规则信息进行快速检查
for phrase, ruleInfo in pairs(parsedCategoryRules) do
local categoryName = ruleInfo.categoryName
local isMatched = false
-- 检查分类匹配
if categoryNames then
for catId, catName in pairs(categoryNames) do
if catName == categoryName and itemInfo.Category == catId then
isMatched = true
break
end
end
end
-- 检查Context Tag匹配
if not isMatched and contextTagNames then
for tag, tagName in pairs(contextTagNames) do
if tagName == categoryName and itemInfo.ContextTags then
for _, itemTag in ipairs(itemInfo.ContextTags) do
if itemTag == tag then
isMatched = true
break
end
end
if isMatched then break end
end
end
end
-- 检查Type匹配
if not isMatched and typeNames then
for typeName, typeDisplayName in pairs(typeNames) do
if typeDisplayName == categoryName and itemInfo.Type == typeName then
isMatched = true
break
end
end
end
-- 如果匹配,检查是否在除外列表中
if isMatched then
if not ruleInfo.exceptionSet[itemName] then
return true -- 不在除外列表中,被覆盖
end
end
end
return false
end
-- 过滤其他物品
local otherItems = {}
local personalItemsMap = {}
for _, item in ipairs(personalSpecialItems) do
personalItemsMap[item] = true
end
for _, itemName in ipairs(outputItems) do
if not personalItemsMap[itemName] and not isItemCoveredByPhrases(itemName) then
table.insert(otherItems, itemName)
end
end
local otherItemData = createItemData(otherItems, "other")
-- 构建最终输出
local finalOutput = {}
for _, data in ipairs(phraseData) do
table.insert(finalOutput, data.text)
end
for _, data in ipairs(personalItemData) do
table.insert(finalOutput, data.text)
end
for _, data in ipairs(otherItemData) do
table.insert(finalOutput, data.text)
end
return finalOutput, phraseData, personalItemData, otherItemData
end
function p.get(frame)
-- 在主函数开始时初始化映射表
_initializeNameMap()
-- 全局临时变量存储通用规则的除外信息
local universalRuleExceptions = {}
local args = frame.args
local npcName = args.npc or args[1]
npcName = NPC.getEnglishName(npcName)
local targetLevel = args.taste or args[2]
if not npcName or not targetLevel then
return ''
end
npcName = mw.text.trim(npcName)
targetLevel = mw.text.trim(targetLevel)
-- 中文到英文的偏好等级映射
local chineseToEnglish = {
["最爱"] = "love",
["喜欢"] = "like",
["一般"] = "neutral",
["中立"] = "neutral", -- 保留中立作为一般的同义词
["普通"] = "neutral",
["不喜欢"] = "dislike",
["讨厌"] = "hate"
}
-- 如果是中文输入,转换为英文
if chineseToEnglish[targetLevel] then
targetLevel = chineseToEnglish[targetLevel]
else
targetLevel = targetLevel:lower()
end
local personalTastes = giftTasteData.npcs[npcName]
if not personalTastes then
return ''
end
local universalTastes = giftTasteData.universal
-- 初始化数据结构
local outputPhrases = {}
local outputItems = {}
local personalSpecialItems = {}
local seenItems = {}
local seenPersonalItems = {}
local seenRules = {}
-- 1. 处理个人偏好规则
_processPersonalPreferences(personalTastes, targetLevel, npcName, universalTastes, seenRules, outputPhrases, personalSpecialItems, seenPersonalItems)
-- 2. 处理通用偏好规则
_processUniversalPreferences(universalTastes, targetLevel, npcName, personalTastes, seenRules, outputPhrases, universalRuleExceptions)
-- 3. 处理默认规则产生的物品
_processDefaultItems(targetLevel, npcName, personalTastes, universalTastes, seenRules, outputItems, seenItems)
-- 4. 检测隐性分类
_processImplicitCategories(targetLevel, npcName, personalTastes, universalTastes, seenRules, outputPhrases, outputItems)
-- 5. 对于中立查询,添加默认中立的分类
_processNeutralDefaults(targetLevel, npcName, personalTastes, universalTastes, seenRules, outputPhrases)
-- 6. 合并和排序最终输出
local finalOutput, phraseData, personalItemData, otherItemData = _buildFinalOutput(outputPhrases, personalSpecialItems, outputItems)
-- 分类规则
local phraseTexts = {}
for _, data in ipairs(phraseData) do
table.insert(phraseTexts, data.text)
end
-- 个人特殊喜好物品
local personalTexts = {}
for _, data in ipairs(personalItemData) do
table.insert(personalTexts, data.text)
end
-- 其他具体物品
local otherTexts = {}
for _, data in ipairs(otherItemData) do
table.insert(otherTexts, data.text)
end
-- 设置临时变量缓存
local levelNames = {
love = "最爱",
like = "喜欢",
neutral = "一般",
dislike = "不喜欢",
hate = "讨厌"
}
local chineseLevelName = levelNames[targetLevel] or targetLevel
local universalTargetRules = universalTastes[targetLevel] or {}
-- 获取存储的除外信息
local universalExceptions = universalRuleExceptions[targetLevel] or {}
-- 最终输出
if #finalOutput == 0 then
return ""
end
-- 构建最终输出的 wikitext
local resultParts = {}
-- 1. 展开 Gifts head 模板
local universalText = ""
if #phraseTexts > 0 then
-- 为每个条目添加 <li> 标签
local liWrappedTexts = {}
for _, text in ipairs(phraseTexts) do
table.insert(liWrappedTexts, "<li>" .. text .. "</li>")
end
-- 先连接文本,然后展开除外物品的 Name 模板
local rawUniversalText = "<ul>\n" .. table.concat(liWrappedTexts, "\n") .. "\n</ul>"
universalText = _expandExceptionItems(rawUniversalText)
end
local headTemplate = utils.expandTemplate(
"Gifts",
{
"head",
villager = npcName,
type = targetLevel,
universal = universalText
}
)
table.insert(resultParts, headTemplate)
local hasPersonalHeader = false
-- 2. 展开个人特殊喜好物品
for _, itemName in ipairs(personalTexts) do
if not hasPersonalHeader then
hasPersonalHeader = true
table.insert(resultParts, "<tr>")
table.insert(resultParts, '<th colspan="4">个人喜好</th>')
table.insert(resultParts, "</tr>")
end
-- 获取中文物品名称
local itemId = _getItemIdByName(itemName)
local chineseName = itemId and _getItemChineseName(itemId) or itemName
local giftRowTemplate = utils.expandTemplate(
":" .. chineseName,
{"GiftRow"}
)
table.insert(resultParts, giftRowTemplate)
end
-- 3. 输出分隔行和其他具体物品
-- 获取通用偏好中的具体物品(非分类物品),并排除除外物品
local universalItemLinks = {}
for _, ruleId in ipairs(universalTargetRules) do
if type(ruleId) == 'number' and ruleId >= 0 then
-- 这是具体物品ID
local englishName = ItemNames.getEnglishName("(O)" .. ruleId)
if englishName and englishName ~= "" then
-- 检查是否在除外列表中,如果是则跳过
local isException = false
local displayName = _getItemDisplayName(ruleId)
for _, exceptionName in ipairs(universalExceptions) do
if displayName == exceptionName or englishName == exceptionName then
isException = true
break
end
end
if not isException then
local nameTemplate = _expandNameTemplate(englishName, {
class = "inline"
})
table.insert(universalItemLinks, nameTemplate)
end
end
end
end
-- 只有当有通用物品或其他物品时才输出这个部分
if #universalItemLinks > 0 or #otherTexts > 0 then
table.insert(resultParts, "<tr>")
table.insert(resultParts, '<th colspan="4">普遍' .. chineseLevelName .. '</th>')
table.insert(resultParts, "</tr>")
table.insert(resultParts, "<tr>")
table.insert(resultParts, '<td colspan="4" style="line-break: anywhere;">')
-- 构建其他物品的内联链接
local otherItemLinks = {}
for _, itemName in ipairs(otherTexts) do
local nameTemplate = _expandNameTemplate(itemName, {
class = "inline"
})
table.insert(otherItemLinks, nameTemplate)
end
-- 合并通用物品和其他物品,通用物品在前
local allItemLinks = {}
for _, link in ipairs(universalItemLinks) do
table.insert(allItemLinks, '<li>' .. link .. '</li>')
end
for _, link in ipairs(otherItemLinks) do
table.insert(allItemLinks, '<li>' .. link .. '</li>')
end
-- 连接所有物品
if #allItemLinks > 0 then
table.insert(resultParts, '<ul style="column-width: 120px; column-gap: 0.25em; list-style: none; margin: 0 !important;">' .. table.concat(allItemLinks, "") .. '</ul>')
-- table.insert(resultParts, table.concat(allItemLinks, " • "))
end
table.insert(resultParts, "</td>")
table.insert(resultParts, "</tr>")
end
-- 4. 展开 Gifts foot 模板
local footTemplate = utils.expandTemplate(
"Gifts",
{"foot"}
)
table.insert(resultParts, footTemplate)
return table.concat(resultParts, "\n")
end
return p