米斯特利亚Wiki正在建设中,本WIKI编辑权限开放!欢迎参与~!
全站通知:
模块:Npcs
刷
历
编
跳到导航
跳到搜索
此模块的文档可以在模块:Npcs/doc创建
local NpcsData = require('模块:Npcs/Data')
local Items = require('模块:Items')
local Get = require('模块:Get')
-- 模块开头
local p = {}
local function trim(s)
if type(s) ~= 'string' then return s end
return s:gsub('^%s+', ''):gsub('%s+$', '')
end
-- 构建人物肖像精灵名
-- 参数:npcId, seasonOrBeach, keyName, idx(0或1)
-- 返回:spr_portrait_<npcid>_<四季/海滩>_<portraits.键名>_<0/1>
function p._portraitSprite(npcId, seasonOrBeach, keyName, idx)
local id = npcId or ''
local sb = seasonOrBeach or 'spring'
local key = keyName or 'neutral'
local i = tostring(idx or '0')
if i ~= '1' then i = '0' end
return string.format('spr_portrait_%s_%s_%s_%s', id, sb, 'portraits.'..key, i)
end
-- 对外包装:从 frame 读取参数
-- 使用:{{#invoke:模块名|portraitSprite|npcId|season|key|0/1}}
function p.portraitSprite(frame)
local npcId = frame.args[1] or frame.args.npc or ''
local seasonOrBeach = frame.args[2] or frame.args.outfit or ''
local keyName = frame.args[3] or frame.args.key or ''
local idx = frame.args[4] or frame.args.index or '0'
return p._portraitSprite(npcId, seasonOrBeach, keyName, idx)
end
-- 根据 tags(如 vendor,dateable,child 等)筛选 NPC,返回 npcId 列表(table)
-- @param tags string|table 逗号/中文逗号分隔的字符串,或字符串数组
-- @param npcData table 可选,默认使用 NpcsData
-- @return table { npcId, ... }
function p._npcIdsByTags(tags, npcData)
local data = npcData or NpcsData
local result = {}
if type(data) ~= 'table' or not next(data) then return result end
-- 解析待匹配的标签列表
local tags_list = {}
if type(tags) == 'string' then
if tags ~= '' then
local s = tags:gsub(',', ',')
for part in mw.text.gsplit(s, ',', true) do
local trimmed_part = trim(part)
if trimmed_part ~= '' then
table.insert(tags_list, trimmed_part)
end
end
end
elseif type(tags) == 'table' then
for _, t in ipairs(tags) do
if type(t) == 'string' then
local trimmed_t = trim(t)
if trimmed_t ~= '' then table.insert(tags_list, trimmed_t) end
end
end
else
return result
end
if #tags_list == 0 then return result end
for npcId, npc in pairs(data) do
if type(npc) == 'table' and type(npc.tags) == 'table' then
local hit = false
for _, t in ipairs(tags_list) do
for _, x in ipairs(npc.tags) do
if t == x then hit = true break end
end
if hit then break end
end
if hit then table.insert(result, npcId) end
end
end
return result
end
-- 可选:对外包装,返回逗号分隔的 npcId 字符串
function p.npcIdsByTags(frame)
local arg = frame.args[1] or frame.args.tags or ''
local ids = p._npcIdsByTags(arg, NpcsData)
return table.concat(ids, ',')
end
-- 将标签渲染为:tag:<物品渲染>(逐行)
local function renderTagsWithItems(tags)
if type(tags) ~= 'table' or not next(tags) then return '' end
local parts = {}
for _, tag in ipairs(tags) do
local ids = Items._findItemByTag(tag) or {}
local rendered = Items._renderItemList(ids)
table.insert(parts, (tag .. ':' .. (rendered or '')))
end
return table.concat(parts, '<br/>')
end
-- 核心纯函数:按 NPC 输出礼物偏好
-- 返回: { loved = {itemId...}, liked = {...}, disliked = {tag...}, banned = {tag...}, hated = {itemId?} }
function p._giftByNpc(npcArg, npcData, options)
local data = npcData or NpcsData
local res = { loved = {}, liked = {}, disliked = {}, banned = {}, hated = {} }
if not npcArg or type(data) ~= 'table' then return res end
local npc = data[npcArg] or Get(data):values(function(n)
return type(n) == 'table' and (n.name == npcArg or n.name_en == npcArg)
end):one()
if type(npc) ~= 'table' then return res end
for _, v in ipairs(npc.loved_gifts or {}) do table.insert(res.loved, v) end
for _, v in ipairs(npc.liked_gifts or {}) do table.insert(res.liked, v) end
for _, v in ipairs(npc.disliked_gift_tags or {}) do table.insert(res.disliked, v) end
for _, v in ipairs(npc.banned_gift_tags or {}) do table.insert(res.banned, v) end
if npc.hated_gift and npc.hated_gift ~= '' then table.insert(res.hated, npc.hated_gift) end
if options and options.sort == 'alpha' then
table.sort(res.loved); table.sort(res.liked); table.sort(res.disliked); table.sort(res.banned); table.sort(res.hated)
end
return res
end
-- 对外包装:giftByNpc
-- 使用:{{#invoke:模块名|giftByNpc|<npcId或中文名或英文名>|translate=id|sort=alpha}}
function p.giftByNpc(frame)
local npcArg = frame.args[1] or frame.args.npc
if not npcArg or npcArg == '' then
return '<!-- npc 参数为空 -->'
end
local translate = frame.args.translate -- 'id' | 'cn'(当前实现仅透传ID,中文需后续扩展)
local sortOpt = frame.args.sort
local options = {
translate = translate == 'cn' and 'cn' or 'id',
sort = (sortOpt == 'alpha') and 'alpha' or 'none',
}
local ret = p._giftByNpc(npcArg, NpcsData, options)
local lovedStr = Items._renderItemList(ret.loved)
local likedStr = Items._renderItemList(ret.liked)
local dislikedStr = renderTagsWithItems(ret.disliked)
local bannedStr = renderTagsWithItems(ret.banned)
local hatedStr = Items._renderItemList(ret.hated)
return frame:expandTemplate{
title = 'GiftByNpc',
args = {
loved = lovedStr,
liked = likedStr,
disliked = dislikedStr,
banned = bannedStr,
hated = hatedStr,
npc = npcArg, -- 供表头可选展示
}
}
end
-- ==== 核心纯函数 ====
-- 参数:
-- itemId : string 物品ID
-- tags : string 物品标签,逗号/中文逗号分隔
-- npcData : table NPC 数据字典(root 下是 npcId -> npcAttrTable)
-- 返回:
-- { loved = {npc...}, liked = {npc...}, hated = {npc...}, disliked_by_tag = {npc...}, banned_by_tag = {npc...} }
function p._npcsByGift(itemId, tags, npcData)
local data = npcData or NpcsData
local res = { loved = {}, liked = {}, hated = {}, disliked_by_tag = {}, banned_by_tag = {} }
if type(data) ~= 'table' or type(itemId) ~= 'string' or itemId == '' then
return res
end
local tags_list = {}
if type(tags) == 'string' then
if tags ~= '' then
local s = tags:gsub(',', ',')
for part in mw.text.gsplit(s, ',', true) do
local trimmed_part = trim(part)
if trimmed_part ~= '' then
table.insert(tags_list, trimmed_part)
end
end
end
elseif type(tags) == 'table' then
for _, t in ipairs(tags) do
if type(t) == 'string' then
local trimmed_t = trim(t)
if trimmed_t ~= '' then table.insert(tags_list, trimmed_t) end
end
end
end
for _, npc in pairs(data) do
if type(npc) == 'table' then
-- loved / liked
if type(npc.loved_gifts) == 'table' then
for _, v in ipairs(npc.loved_gifts) do
if v == itemId then table.insert(res.loved, npc) break end
end
end
if type(npc.liked_gifts) == 'table' then
for _, v in ipairs(npc.liked_gifts) do
if v == itemId then table.insert(res.liked, npc) break end
end
end
-- hated (single item id)
if type(npc.hated_gift) == 'string' and npc.hated_gift == itemId then
table.insert(res.hated, npc)
end
-- tag based
if #tags_list > 0 then
if type(npc.disliked_gift_tags) == 'table' then
local hit = false
for _, t in ipairs(tags_list) do
for _, x in ipairs(npc.disliked_gift_tags) do
if t == x then hit = true break end
end
if hit then break end
end
if hit then table.insert(res.disliked_by_tag, npc) end
end
if type(npc.banned_gift_tags) == 'table' then
local hit2 = false
for _, t in ipairs(tags_list) do
for _, x in ipairs(npc.banned_gift_tags) do
if t == x then hit2 = true break end
end
if hit2 then break end
end
if hit2 then table.insert(res.banned_by_tag, npc) end
end
end
end
end
return res
end
-- ==== 对外暴露的包装函数(可选)====
-- 用法:
-- {{#invoke:Npcs|npcsByGift|golden_cookies|dessert,sweet}}
-- 也可以在别的 Lua 里直接调用 p._npcsByGift(itemId, tags, NpcsData)
function p.npcsByGift(frame)
local itemNameOrId = frame.args[1] or frame.args.id or ""
if itemNameOrId == '' then return "<!-- No item specified -->" end
local tags = frame.args[2] or frame.args.tags
if not tags or tags == '' then
tags = Items._tags(itemNameOrId)
end
-- Resolve name to ID for direct gift matching, but use original name for tag lookup.
local internalId = Items.id({ args = { itemNameOrId } })
if type(internalId) ~= 'string' or internalId:find("<!--", 1, true) then
internalId = itemNameOrId -- Fallback to original if resolution fails
end
local res = p._npcsByGift(internalId, tags, NpcsData)
local npcNumber = 0
for _, likeType in pairs(res) do
npcNumber = npcNumber + #likeType
end
if npcNumber <= 0 then
return "此物品没有NPC偏好或讨厌"
end
local function npcsToNames(list)
return Get(list):items():map(function(npc)
return npc.name or npc.name_en or ''
end):all()
end
local lovedStr = table.concat(npcsToNames(res.loved), ',')
local likedStr = table.concat(npcsToNames(res.liked), ',')
local hatedStr = table.concat(npcsToNames(res.hated), ',')
local dislikedStr = table.concat(npcsToNames(res.disliked_by_tag), ',')
local bannedStr = table.concat(npcsToNames(res.banned_by_tag), ',')
return frame:expandTemplate{
title = 'npcsByGift',
args = {
loved = lovedStr,
liked = likedStr,
hated = hatedStr,
disliked = dislikedStr,
banned = bannedStr,
item = itemNameOrId,
}
}
end
-- 根据是否可婚来获取NPC ID列表
-- @param isDatable boolean true表示可婚, false表示不可婚
-- @param npcData table, 可选, 默认使用NpcsData
-- @return table { npcId, ... }
function p._getNpcIdsByDatable(isDatable, npcData)
local data = npcData or NpcsData
local result = {}
if type(data) ~= 'table' then return result end
for npcId, npc in pairs(data) do
if type(npc) == 'table' and npc.dateable == isDatable then
table.insert(result, npcId)
end
end
return result
end
-- 渲染NPC列表(简单实现,输出名字链接)
local function renderNpcList(frame, npcIdList)
local moduleName = frame.args["模板"] or "人物"
if type(npcIdList) ~= 'table' or #npcIdList == 0 then return '' end
local parts = {}
for _, npcId in ipairs(npcIdList) do
local npc = NpcsData[npcId]
if npc and npc.name then
table.insert(parts, frame:expandTemplate{title=moduleName,args={npc.name,npc.icon_sprite}})
else
table.insert(parts, '[[' .. npcId .. ']]')
end
end
return table.concat(parts, ' ')
end
-- 外部接口:获取可婚/不可婚NPC列表(函数1)
-- @param frame.args[1] string 'true' for datable, 'false' for not datable
function p.getNpcIdsByDatable(frame)
local arg = frame.args[1]
local isDatable = (arg == 'true' or arg == true)
local ids = p._getNpcIdsByDatable(isDatable, NpcsData)
return ids
end
-- 外部接口:渲染可婚/不可婚NPC列表(函数2)
-- @param frame.args[1] string 'true' for datable, 'false' for not datable
function p.renderNpcListByDatable(frame)
local arg = frame.args[1]
local isDatable = (arg == 'true' or arg == true)
local ids = p._getNpcIdsByDatable(isDatable, NpcsData)
return renderNpcList(frame, ids)
end
--[[ 测试代码
local frame = mw.getCurrentFrame()
frame.args[1] = "巧克力"
mw.logObject(p.npcsByGift(frame))
local frame = mw.getCurrentFrame()
frame.args[1] = "red_toadstool"
frame.args[2] = "material, mushroom, mushroomy"
mw.logObject(p.npcsByGift(frame))
local frame = mw.getCurrentFrame()
frame.args[1] = "dozy"
mw.logObject(p.giftByNpc(frame))
--]]
return p