维护提醒

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