跳转到内容
主菜单
主菜单
移至侧栏
隐藏
主菜单
首页
曲目列表
曲包列表
人物与装扮
列表
定数表
曲目列表
曲包列表
曲师列表
谱师列表
角色设定
玩法系统
新手入门
玩法机制
游戏界面
闪音会员卡
版本
版本与赛季
赛季通行证
盒蛋
Bleap战书
剧情模式
社区
待办项
讨论
更新日志
编辑规范
官方资讯
联系方式 / Contact
Bleap Wiki
搜索
搜索
外观
创建账号
登录
个人工具
创建账号
登录
查看“︁Module:曲目页面”︁的源代码
模块
讨论
English
阅读
查看源代码
查看历史
工具
工具
移至侧栏
隐藏
操作
阅读
查看源代码
查看历史
常规
链入页面
相关更改
页面信息
上传文件
文件列表
特殊页面
最近更改
外观
移至侧栏
隐藏
←
Module:曲目页面
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
local p = {} -- 数据页: 曲目列表 (曲绘/曲师/曲包/定数 的单一来源) local DATA_TITLE = '模板:曲目列表.json' local validator = require('模块:数据页校验') local parse = require('模块:署名解析') local ARTIST_MAP = '模板:曲师映射.json' local CHARTER_MAP = '模板:谱师映射.json' local function esc(s) return (s:gsub('|', '|')) end local function norm(s) return (s:gsub(' ', '_')) end -- Lua 中空字符串为真值, 必须显式判空 local function arg(frame, k, default) local v = frame.args[k] if v == nil or v == '' then return default end return v end local function fmt(v) if type(v) == 'number' then return string.format('%.1f', v) end return v or '' end local function loadJson(title) local t = mw.title.new(title) if not t or not t.exists then return nil end local ok, data = pcall(mw.text.jsonDecode, t:getContent()) if not ok then return nil end return data end -- 列表页里实际有小节的署名集合 local function knownCredits(data, lk, field, includeSpecial) local known = {} local function eat(list) for _, s in ipairs(list or {}) do local raw = s[field] if type(raw) == 'table' then for _, v in pairs(raw) do for _, pt in ipairs(parse.attribute(lk, v) or parse.parts(v)) do known[pt.base] = true end end else for _, pt in ipairs(parse.attribute(lk, raw) or parse.parts(raw)) do known[pt.base] = true end end end end eat(data.songs) if includeSpecial then eat(data.special) end return known end -- 署名 → 链到 [[<列表页>#主名义]](马甲/合作名义自动归并;无小节则纯文本) local function linkedCredits(raw, lk, page, known) if not raw or raw == '' then return '' end local linked = parse.linkify(raw, lk, { page = page, known = known }) if linked then return linked end local parts = parse.attribute(lk, raw) or parse.parts(raw) local out = {} for _, pt in ipairs(parts) do local text = pt.rawPart or pt.base if known and not known[pt.base] then out[#out + 1] = esc(text) else out[#out + 1] = '[[' .. page .. '#' .. esc(pt.base) .. '|' .. esc(text) .. ']]' end end -- 回退分支: -- ① 解析出多个主名义(标签式合作名义,如 rank.x / Kevin.B / ↑=192.333KB/s) -- → 「合作名义<br>主名义A链接 / 主名义B链接」 -- ② 单个主名义 → 直接显示(原名与主名义不同时补「(名义:X)」) local joined = table.concat(out, ' / ') if #parts > 1 then return esc(raw) .. '<br>' .. joined end if parse.creditNote(raw, lk) and parse.norm(raw) ~= parse.norm(parts[1].rawPart or parts[1].base) then return joined .. ' <span style="font-size:85%;color:#666">(名义:' .. esc(raw) .. ')</span>' end return joined end local function loadData() return validator.load(DATA_TITLE) end local function findSong(data, name, specialOnly) if not specialOnly then for _, s in ipairs(data.songs or {}) do if s['曲名'] == name then return s, false end end end for _, s in ipairs(data.special or {}) do if s['曲名'] == name then return s, true end end return nil, false end local function langWrapName(song) local lang = song['lang'] if lang == 'ja' then return '{{lang|ja|' .. esc(song['曲名']) .. '}}' elseif lang == 'zh-Hant' then return '{{lang|zh-Hant|' .. esc(song['曲名']) .. '}}' end return esc(song['曲名']) end local function songArt(song, isSpecial) local art = song['曲绘'] or song['曲名'] or '' if isSpecial then art = art:gsub('(石墩子模式)$', '') .. '_石墩子模式' end return norm(art) end -- 自动生成解锁方式: 按曲包类型 local function autoUnlock(pack) if pack == '' or pack == nil then return '(待补充)' end if pack == '教程' then return '游戏内教学关卡,无需解锁' elseif pack == '剧情模式' then return '剧情模式内出现' elseif pack == '免费单曲' then return '无需解锁' elseif pack == '免费单曲(商店)' then return '前往[[商店#单曲|商店]]使用[[货币#瓶盖|瓶盖]]购买单曲' elseif pack == 'CORE基础卡' then return '前往[[商店#闪音卡|商店]]购买基础卡解锁' else return '前往[[商店#曲包|商店]]购买[[曲包列表#' .. esc(pack) .. '|' .. esc(pack) .. ']]解锁' end end function p.render(frame) local name = arg(frame, '曲名', '') if name == '' then return '<span class="error">未指定 |曲名= 参数</span>' end local data, err = loadData() if not data then return '<span class="error">' .. tostring(err) .. '</span>' end local specialOnly = arg(frame, '特殊', '') == '石墩子' local song, isSpecial = findSong(data, name, specialOnly) if not song then return '<span class="error">数据页中找不到曲目: ' .. esc(name) .. '(请检查 [[模板:曲目列表.json]])</span>' end local defaultTitle = isSpecial and (langWrapName(song) .. '(石墩子模式)') or song['曲名'] local title = arg(frame, '标题', defaultTitle) -- 标题先单独展开 ({{lang}} 嵌套在信息框参数内不会被最终 preprocess 二次展开) title = frame:preprocess(title) local art = songArt(song, isSpecial) local consts = song['定数'] or {} local out = {} local tagline = arg(frame, '标语', '') if tagline ~= '' then table.insert(out, '<div class="song-tagline">' .. tagline .. '</div>') end local artistLk = parse.buildLookup(loadJson(ARTIST_MAP)) local charterLk = parse.buildLookup(loadJson(CHARTER_MAP)) local knownArtists = knownCredits(data, artistLk, '曲师', true) local knownCharters = knownCredits(data, charterLk, '谱师', false) -- 信息框: 自动填 标题/图片/曲师/曲包/定数/谱师/时长/BPM, 手工传参可覆盖 table.insert(out, '{{曲目信息框') table.insert(out, '|标题= ' .. esc(title)) local imgOverride = arg(frame, '图片', '') if imgOverride ~= '' then table.insert(out, '|图片= ' .. imgOverride) else table.insert(out, '|图片= [[文件:曲绘-' .. esc(art) .. '.png|256px]]') end table.insert(out, '|曲师= ' .. arg(frame, '曲师', linkedCredits(song['曲师'], artistLk, '曲师列表', knownArtists))) -- 曲包: 手工传参优先; 否则自动生成指向 曲包列表#<曲包名> 的链接 local packArg = frame.args['曲包'] local packText = song['曲包'] or '' if packArg ~= nil and packArg ~= '' then packText = packArg elseif packText ~= '' then packText = '[[' .. '曲包列表#' .. esc(packText) .. '|' .. esc(packText) .. ']]' end table.insert(out, '|曲包= ' .. packText) table.insert(out, '|时长= ' .. esc(arg(frame, '时长', song['时长'] or ''))) table.insert(out, '|BPM= ' .. esc(arg(frame, 'BPM', song['BPM'] or ''))) local charters = song['谱师'] or {} for _, d in ipairs({ 'CASUAL', 'TOUGH', 'INSANE', 'PRO' }) do table.insert(out, '|' .. d .. '定数= ' .. fmt(consts[d])) table.insert(out, '|' .. d .. '谱师= ' .. arg(frame, d .. '谱师', linkedCredits(charters[d], charterLk, '谱师列表', knownCharters))) table.insert(out, '|' .. d .. '连击= ' .. esc(arg(frame, d .. '连击', ''))) end table.insert(out, '}}') -- 标准段落 -- 解锁方式: 手工传参优先, 否则按曲包自动生成 local unlock = arg(frame, '解锁方式', '') if unlock == '' then unlock = autoUnlock(song['曲包'] or '') end table.insert(out, '\n== 解锁方式 ==\n' .. unlock) table.insert(out, '\n== 游戏相关 ==\n=== 定数调整历史 ===\n' .. arg(frame, '定数调整历史', '(待补充)')) -- 其他相关子章节: 新参数优先, 兼容旧参数 |游戏相关= local otherRelated = arg(frame, '其他相关', '') local legacy = arg(frame, '游戏相关', '') if otherRelated == '' then otherRelated = legacy end if otherRelated == '' then otherRelated = '(待补充)' end table.insert(out, '\n=== 其他相关 ===\n' .. otherRelated) table.insert(out, '\n== 歌词 ==\n' .. arg(frame, '歌词', '纯音乐,请欣赏')) local other = arg(frame, '其他', '') if other ~= '' then table.insert(out, '\n== 其他 ==\n' .. other) end table.insert(out, '\n[[Category:曲目]]') -- 展开 {{曲目信息框}} 等模板 return frame:preprocess(table.concat(out, '\n')) end return p
此页面嵌入的页面:
Module:曲目页面/doc
(
查看源代码
)
返回
Module:曲目页面
。
搜索
搜索
查看“︁Module:曲目页面”︁的源代码
添加话题