维护提醒
BWIKI 全站将于 9 月 3 日(全天)进行维护,期间无法编辑任何页面或发布新的评论。
全站通知:
模块:Sprites
刷
历
编
跳到导航
跳到搜索
-- Module:SpriteData
-- 用法:
-- {{#invoke:SpriteData|jsonDataAttr|Data:Animation/Shane.json}}
-- 或指定 class/name:
-- {{#invoke:SpriteData|jsonDataAttr|Data:Animation/Shane.json|class=villiager-sprites|name=Animation.Shane}}
-- 输出: <div class="... " data-source="..." data-json-b64="..." data-img="... (preprocess output)" style="display:none;"></div>
-- 新用法:
-- {{#invoke:SpriteData|autoAnimationData|Shane}}
-- 自动输出 Shane 相关的所有动画数据(按指定顺序:无后缀 → JojaMart → Hospital → Winter → Beach)
local NPC = require("Module:NPC")
local p = {}
-- 角色动画数据映射表
local characterAnimations = {
'Abigail_Beach.json', 'Abigail_Winter.json', 'Abigail.json',
'Alex_Beach.json', 'Alex_Winter.json', 'Alex.json',
'Birdie.json',
'Caroline_Beach.json', 'Caroline_Winter.json', 'Caroline.json',
'Clint_Beach.json', 'Clint_Winter.json', 'Clint.json',
'Demetrius_Winter.json', 'Demetrius.json',
'Elliott_Beach.json', 'Elliott_Winter.json', 'Elliott.json',
'Emily_Beach.json', 'Emily_Winter.json', 'Emily.json',
'Evelyn_Winter.json', 'Evelyn.json',
'George_Winter.json', 'George.json',
'Gus_Winter.json', 'Gus.json',
'Haley_Beach.json', 'Haley_Winter.json', 'Haley.json',
'Harvey_Beach.json', 'Harvey_Winter.json', 'Harvey.json',
'Jas_Winter.json', 'Jas.json',
'Jodi_Beach.json', 'Jodi_Winter.json', 'Jodi.json',
'Kent_Winter.json', 'Kent.json',
'Leah_Beach.json', 'Leah_Winter.json', 'Leah.json',
'Lewis_Beach.json', 'Lewis_Winter.json', 'Lewis.json',
'Linus_Winter.json', 'Linus.json',
'Marcello.json',
'Marnie_Beach.json', 'Marnie_Winter.json', 'Marnie.json',
'Maru_Beach.json', 'Maru_Hospital.json', 'Maru_Winter.json', 'Maru.json',
'Morris.json',
'Pam_Beach.json', 'Pam_Winter.json', 'Pam.json',
'ParrotBoy_Winter.json', 'ParrotBoy.json',
'Penny_Beach.json', 'Penny_Winter.json', 'Penny.json',
'Pierre_Beach.json', 'Pierre_Winter.json', 'Pierre.json',
'Robin_Beach.json', 'Robin_Winter.json', 'Robin.json',
'SafariGuy.json',
'Sam_Beach.json', 'Sam_JojaMart.json', 'Sam_Winter.json', 'Sam.json',
'Sandy.json',
'Sebastian_Beach.json', 'Sebastian_Winter.json', 'Sebastian.json',
'Shane_Beach.json', 'Shane_JojaMart.json', 'Shane_Winter.json', 'Shane.json',
'Vincent_Winter.json', 'Vincent.json',
'Willy_Winter.json', 'Willy.json',
'Wizard.json'
}
-- 后缀优先级映射(数字越小优先级越高)
local suffixPriority = {
[''] = 1, -- 无后缀
['JojaMart'] = 2, -- JojaMart
['Hospital'] = 3, -- Hospital
['Winter'] = 4, -- Winter
['Beach'] = 5 -- Beach
}
local function getInternalName(name)
if name == '雷欧' then return 'ParrotBoy' end
if name == '蜗牛教授' then return 'SafariGuy' end
return NPC.getEnglishName(name)
end
-- 简单的 base64 编码函数(兼容 Lua 5.1/5.2 环境)
local function b64encode(data)
local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
return ((data:gsub('.', function(x)
local r,bits = '', x:byte()
for i=8,1,-1 do r = r .. (bits % 2 ^ i - bits % 2 ^ (i-1) > 0 and '1' or '0') end
return r
end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
if #x < 6 then return '' end
local c = 0
for i = 1,6 do c = c + (x:sub(i,i) == '1' and 2^(6-i) or 0) end
return b:sub(c+1,c+1)
end) .. ({ '', '==', '=' })[#data % 3 + 1])
end
local function getPageContent(titleText)
if not titleText or titleText:match('^%s*$') then return nil end
local titleObj = mw.title.new(titleText)
if not titleObj or not titleObj.exists then return nil end
local ok, content = pcall(function() return titleObj:getContent() end)
if not ok then return nil end
return content
end
-- 将 class 清理成安全的 class 名(空格->'-',移除非法字符)
local function sanitizeClass(s)
if not s then return '' end
s = tostring(s)
s = s:match('^%s*(.-)%s*$') -- trim
s = s:gsub('%s+', '-') -- spaces -> -
-- 允许字母数字、下划线、中划线、点、冒号(常见 class/fragment 字符)
s = s:gsub('[^%w%-%_%.:]', '')
if s == '' then return '' end
return s:sub(1, 128) -- 限长,防止太长
end
-- 根据传入的 titleText 生成 filepath 模板参数的文件名(例如: "Data:Animation/Shane.json" -> "Animation Shane.png")
local function makeFilepathName(titleText)
if not titleText or titleText:match('^%s*$') then return nil end
local s = tostring(titleText)
-- 如果输入像 "Data:Animation/Shane.json",取冒号后的部分
local after = s:match('^[^:]+:(.*)$')
if after and after ~= '' then
s = after
end
-- 去掉可能的 .json 或 .JSON 后缀
s = s:gsub('%.json$', '')
s = s:gsub('%.JSON$', '')
-- 把斜杠替换为空格,把下划线替换为空格
s = s:gsub('[/%_]+', ' ')
-- 多个空格合并为一个,去除首尾空格
s = s:gsub('%s+', ' '):match('^%s*(.-)%s*$') or s
s = s:gsub('SpritesData', 'Animation')
s = s:gsub('Sprites data', 'Animation')
if s == '' then return nil end
-- 最终文件名加 png 扩展
return s .. '.png'
end
-- 从文件名中提取后缀类型
local function getSuffixFromFilename(filename)
local baseName = filename:gsub('%.json$', '')
local suffix = baseName:match('_(.+)$')
return suffix or ''
end
-- 根据角色名称查找匹配的动画文件并按指定顺序排序
local function findAnimationFiles(characterName)
if not characterName or characterName:match('^%s*$') then return {} end
-- 清理角色名称(去除首尾空格,转换为首字母大写)
characterName = characterName:match('^%s*(.-)%s*$')
characterName = characterName:sub(1,1):upper() .. characterName:sub(2):lower()
local matchedFiles = {}
for _, filename in ipairs(characterAnimations) do
-- 提取文件名中的角色名部分(去掉 .json 后缀)
local baseName = filename:gsub('%.json$', '')
local charPart = baseName:match('^([^_]+)')
if charPart and charPart:lower() == characterName:lower() then
table.insert(matchedFiles, filename)
end
end
-- 按照指定顺序排序:无后缀 → JojaMart → Hospital → Winter → Beach
table.sort(matchedFiles, function(a, b)
local suffixA = getSuffixFromFilename(a)
local suffixB = getSuffixFromFilename(b)
local priorityA = suffixPriority[suffixA] or 999
local priorityB = suffixPriority[suffixB] or 999
return priorityA < priorityB
end)
return matchedFiles
end
-- 生成单个动画数据的 HTML div 元素
local function generateAnimationDiv(filename, frame, defaultClass)
local title = 'Module:Sprites/data/' .. filename
local content = getPageContent(title)
if not content then return '' end
local b64 = b64encode(content)
local name = 'SpritesData.' .. filename:gsub('%.json$', '')
local cls = defaultClass or 'villiager-sprites'
-- 生成 filepath 模板内的文件名,并通过 frame:preprocess 获取 {{filepath:...}} 的输出
local imgFilename = makeFilepathName(title) or ''
local imgPreprocessed = ''
if imgFilename ~= '' then
local tpl = '{{filepath:' .. imgFilename .. '}}'
local ok, res = pcall(function() return mw.getCurrentFrame():preprocess(tpl) end)
if ok and res then imgPreprocessed = res end
end
local html = mw.html.create('div')
html:attr('class', cls)
html:attr('data-source', name)
html:attr('data-json-b64', b64)
if imgPreprocessed ~= '' then
html:attr('data-img', imgPreprocessed)
end
html:attr('style', 'display:none;')
return tostring(html)
end
-- 输出为 <div class="..."> data-source="..." data-json-b64="..." data-img="...preprocess结果..." style="display:none;">
function p.jsonDataAttr(frame)
local title = frame.args[1] or ''
title = title:match('^%s*(.-)%s*$')
if title == '' then return '' end
local content = getPageContent(title)
if not content then return '' end
local b64 = b64encode(content)
local name = frame.args['name'] or title
local rawClass = frame.args['class'] or frame.args['className'] or 'villiager-sprites'
local cls = sanitizeClass(rawClass)
if cls == '' then cls = 'villiager-sprites' end
-- 生成 filepath 模板内的文件名,并通过 frame:preprocess 获取 {{filepath:...}} 的输出
local filename = makeFilepathName(title) or ''
local imgPreprocessed = ''
if filename ~= '' and frame and frame.preprocess then
-- 使用双花括号模板调用,frame:preprocess 会展开模板
local tpl = '{{filepath:' .. filename .. '}}'
-- 以防 preprocess 失败,用 pcall 包裹
local ok, res = pcall(function() return frame:preprocess(tpl) end)
if ok and res then imgPreprocessed = res end
end
local html = mw.html.create('div')
html:attr('class', cls)
html:attr('data-source', name)
html:attr('data-json-b64', b64)
-- data-img 存放 preprocess 的结果(可能含 HTML),mw.html.create:attr 会进行适当转义
if imgPreprocessed ~= '' then
html:attr('data-img', imgPreprocessed)
end
html:attr('style', 'display:none;') -- 隐藏,不影响布局
return tostring(html)
end
-- 新函数:根据页面名自动获取和输出角色的所有动画数据(按指定顺序排序)
function p.autoAnimationData(frame)
local characterName = frame.args[1] or ''
characterName = getInternalName(characterName:match('^%s*(.-)%s*$'))
-- 如果没有提供角色名,尝试从当前页面标题获取
if characterName == '' then
local currentTitle = mw.title.getCurrentTitle()
if currentTitle then
characterName = getInternalName(currentTitle.text)
end
end
if characterName == '' then return '' end
-- 获取自定义的 class 参数
local rawClass = frame.args['class'] or frame.args['className'] or 'villiager-sprites'
local cls = sanitizeClass(rawClass)
if cls == '' then cls = 'villiager-sprites' end
-- 查找匹配的动画文件(已按指定顺序排序)
local animationFiles = findAnimationFiles(characterName)
if #animationFiles == 0 then return '' end
-- 生成所有匹配文件的 HTML
local results = {}
for _, filename in ipairs(animationFiles) do
local div = generateAnimationDiv(filename, frame, cls)
if div ~= '' then
table.insert(results, div)
end
end
return table.concat(results, '\n')
end
function p.getInternalName(frame)
local characterName = frame.args[1] or ''
return getInternalName(characterName)
end
return p