此模块的文档可以在模块:FrameChart/doc创建
-- FrameChart
local p = {}
local html
local frameChartDataHtml
function p.drawFrameData( frame )
-- 开始循环,遍历包含{startup, active, recovery}的列表
-- 对于每种帧类型,从fd中获取相应的帧数数据并存储在duration中
-- 如果frame参数中有对应的帧类型字段,使用frame:preprocess方法处理该字段值
-- _, frameKind表示,忽略迭代过程中的第一个返回值(即k,键),只关心第二个返回值(即v,值,循环变量命名为frameKind)
-- inpairs保证以数组顺序(1-n)遍历数组,所以以下代码意为将frameKind依次赋值为s、a、r并进行循环体操作
for _, frameKind in ipairs({"startup", "active", "recovery", "nsc"}) do
local duration
if frame.args[frameKind] ~= nil then -- frame.args[frameKind]表示访问frame表中frameKind键对应的值
duration = frame:preprocess(frame.args[frameKind]) -- frame:preprocess的参数中如有MW模板或变量(如{{Var}}),会自动展开
end
-- 如果duration为nil,则跳过这个帧类型
if duration == nil then
-- 如果duration可以直接转化为数字,则直接赋值给frame.args[frameKind]
elseif tonumber(duration) ~= nil then
frame.args[frameKind] = duration
-- 如果不是数字,则使用对应的解析函数解析帧数字并存储于
else
local parsed = nil
if frameKind == "active" then
parsed = parseActive(duration)
elseif frameKind == "startup" then
parsed = parseStartup(duration)
elseif frameKind == "recovery" then
parsed = parseRecovery(duration)
elseif frameKind == "nsc" then
parsed = parseNSC(duration)
end
-- 如果parsed为nil,表示解析失败,报错
if parsed == nil then
return string.format('<span class="error">The number of %s frames is not a simple number (%s). Please specify explicitly.</span>', frameKind, tostring(duration))
end
-- 如果解析成功,遍历解析后的表parsed,将每个字段值赋给frame.args[k]
for k, v in pairs(parsed) do -- parsed格式会是{startup = s, active = a, recovery = r}的数据格式
frame.args[k] = v
end
end
end
-- 将此模块中p.drawFrameData的参数处理后输入给调用Module:MetaFrameChart的drawFrameData函数
return metaFrameData(frame)
end
function metaFrameData(frame)
currentFrame = 0 --跟踪当前帧数的变量
html = mw.html.create('div') --mw的html方法,创造一个div标签
html:addClass('frameChart') --给这个div标签添加frameChart类
-- 如果传递的框架frame中有title参数,则将其值赋给title临时变量
local title
if frame.args['title'] ~= nil then
title = frame.args['title']
end
-- 从传递的框架frame中获取startup、active、recovery值并赋值给同名变量
local startup = frame.args['startup']
local active = frame.args['active']
local inactive = frame.args['inactive2']
local recovery = frame.args['recovery']
local activeData = parseActive(active)
local frameAll = parseStartup(startup).startup + parseActive(active).active + parseRecovery(recovery).recovery
if inactive2 ~= nil then
local i = 2
while (frame.args['inactive' .. tostring(i)] ~= nil) do
frameAll = frameAll + frame.args['inactive' .. tostring(i)] + frame.args['active' .. tostring(i+1)]
i = i + 2
end
end
local backWidth = 22 + 12 * frameAll
local backWidthArg = 'width: ' .. (math.ceil((12 * frameAll - 60) / 60) * 60 + 82) .. 'px'
local backBlockN = math.ceil((backWidth - 82) / 60 )
local scrollbarArg = 'scrollbar-color: var(--color-base) var(--color-surface-3); scrollbar-width: thin;'
-- 如果提供title变量,则创建一个包含title的div标签,并添加到HTML结构中,添加frameChart-title类
if title ~= nil then
html:tag('div'):addClass('frameChart-title'):wikitext(title):done()
end
-- 创建div标签并添加类frameChart-data,将此帧数数据容器添加到HTML主结构中
frameChartContainer = mw.html.create('div'):addClass('frameChart-back'):attr('style', scrollbarArg .. backWidthArg):done()
html:node(frameChartContainer)
frameChartStrap = {frameChartStrap1, frameChartStrap2, frameChartStrap3, frameChartStrap4, frameChartStrap5, frameChartStrap6}
local frameChartStrapIndex = 1
while frameChartStrapIndex < 7 do
frameChartContainer:node(mw.html.create('div'):addClass('frameChart-back-strap'):addClass('frameChart-back-strap-pos-' .. frameChartStrapIndex):attr('style', backWidthArg))
frameChartStrapIndex = frameChartStrapIndex +1
end
frameChartContainer:node(mw.html.create('div'):addClass('frameChart-back-block-first'):done())
local frameChartBlockIndex = 1
while frameChartBlockIndex < (backBlockN + 1) do
local leftArg = 'left: ' .. (80 + 60 * (frameChartBlockIndex - 1) + 2) .. 'px'
frameChartContainer:node(mw.html.create('div'):addClass('frameChart-back-block'):attr('style', leftArg))
frameChartBlockIndex = frameChartBlockIndex + 1
end
frameChartContainer:node(mw.html.create('div'):addClass('frameChart-startline'):done())
frameChartBody = mw.html.create('div'):addClass('frameChart-body'):done()
frameChartContainer:node(frameChartBody)
frameChartDataHtml1 = mw.html.create('div'):addClass('frameChart-data'):done()
frameChartBody:node(frameChartDataHtml1)
-- 调用drawFrame函数,绘制第一行启动帧占位
drawFrame(startup, "placeholder", 1)
if isThrow(frame.args['properties']) then
drawFrame(active, "throw", 1)
else
drawFrame(active, "strike", 1)
end
-- 检查是否存在更多的非活跃帧,遍历处理,逻辑类似于上文的取消窗口
index = 2
while tonumber(frame.args['active' .. index]) ~= nil or tonumber(frame.args['inactive' .. index]) ~= nil do
drawFrame(frame.args['active' .. index], "strike", 1)
drawFrame(frame.args['inactive' .. index], "placeholder", 1)
index = index + 1
end
frameChartDataHtml2 = mw.html.create('div'):addClass('frameChart-data'):done()
frameChartBody:node(frameChartDataHtml2)
drawFrame(startup, "vuln", 2)
drawFrame(active, "vuln", 2)
index = 2
while tonumber(frame.args['active' .. index]) ~= nil or tonumber(frame.args['inactive' .. index]) ~= nil do
drawFrame(frame.args['active' .. index], "vuln", 2)
drawFrame(frame.args['inactive' .. index], "vuln", 2)
index = index + 1
end
drawFrame(recovery, "vuln", 2)
frameChartDataHtml3 = mw.html.create('div'):addClass('frameChart-data'):done()
frameChartBody:node(frameChartDataHtml3)
-- 调用drawFrame函数,绘制第一行启动帧占位
drawFrame(nsc, "placeholder", 3)
-- 检查是否存在更多的非活跃帧,遍历处理,逻辑类似于上文的取消窗口
index = 2
while tonumber(frame.args['nsc' .. index]) ~= nil or tonumber(frame.args['inactive' .. index]) ~= nil do
drawFrame(frame.args['nsc' .. index], "nsc", 3)
drawFrame(frame.args['nsc' .. index], "placeholder", 3)
index = index + 1
end
-- 将生成的HTML结构转换为字符串,并使用定义在Module:FrameChart/styles.css中的样式渲染
return tostring(html) .. mw.getCurrentFrame():extensionTag{
name = 'templatestyles', args = { src = 'Module:FrameChart/styles.css' }
}
end
function drawFrame(frames, frameType, line) -- frames为绘制的帧数,是数字;frameType表示帧类型,如active等
if tonumber(frames) ~= nil then -- 将frames转化为数字,如果转化成功说明其为一个有效的数字字符串
for i=1, tonumber(frames) do -- 循环从1到frames(帧数)次
--currentFrame = currentFrame + 1 -- 每次循环时更新currentFrame数
local frameDataHtml = mw.html.create('div') --创建新div标签表示单个帧(小方块)
frameDataHtml:addClass('frame-data'):addClass('frame-data-' .. frameType):done() --为其添加类frame-data和类frame-data-frameType
if tonumber(line) == 1 then
frameChartDataHtml1:node(frameDataHtml) --完成div标签列的构建并添加到frameChartDataHtml1结构中
elseif tonumber(line) == 2 then
frameChartDataHtml2:node(frameDataHtml)
elseif tonumber(line) == 3 then
frameChartDataHtml3:node(frameDataHtml)
elseif tonumber(line) == 4 then
frameChartDataHtml4:node(frameDataHtml)
end
end
end
end
-- 解析函数,类似MetaFrameChart模块中的功能
function parseActive(duration)
-- simple number
if tonumber(duration) ~= nil then
return {active = tonumber(duration)}
end
if string.find(duration, "%d+%s*,") ~= nil then
-- 1,2,3,4 format -- multihit with no gaps
local totalActive = 0
-- first match - just a number
local firstval, pos = string.match(duration, "^(%d+)%s*()")
if firstval == nil then
return nil
end
-- subsequent matches - coma, then number. Might have spaces between them
totalActive = totalActive + tonumber(firstval)
for p1, dur, p2 in string.gmatch(duration, "(),%s*(%d+)%s*()") do
if pos ~= p1 then
return nil
end
pos = p2
totalActive = totalActive + tonumber(dur)
end
if pos ~= string.len(duration)+1 then
return nil -- trailing stuff at the end
end
-- Done.
local out = {active = totalActive}
return out
elseif mw.ustring.find(duration, "^%d+[x×]%d+$") ~= nil then
-- 3x4 format -- also multihit with no gaps
local a, b = mw.ustring.match(duration, "^(%d+)[x×](%d+)$")
local out = { active = tonumber(a) * tonumber(b) }
return out
elseif string.find(duration, "^%d+%(%d+%)") ~= nil then
-- 1(2)3(4)5 format -- multihit with gaps
local out = {}
-- special handling for the first number
local firstval, pos = string.match(duration, "^(%d+)()")
out['active'] = firstval
local ordinal = 2
-- then we just have a groups of "(inactive)active"
for p1, d1, d2, p2 in string.gmatch(duration, "()%((%d+)%)(%d+)()") do
if pos ~= p1 then
return nil -- not directly following the previous match, so basically not what we expect
end
out['inactive' .. tostring(ordinal)] = d1
out['active' .. tostring(ordinal+1)] = d2
ordinal = ordinal + 2
pos = p2
end
if pos ~= string.len(duration)+1 then
return nil -- trailing stuff at the end
end
-- Done.
return out
end
-- unrecognized format
return nil
end
function parseStartup(duration)
-- simple number
if tonumber(duration) ~= nil then
return {startup = tonumber(duration)}
end
-- 1+2 -- common for supers
if string.find(duration, "^%d+%+%d+$") ~= nil then
local first, second = string.match(duration, "^(%d+)%+(%d+)$")
return {startup = tonumber(first) + tonumber(second) }
end
return nil
end
function parseRecovery(duration)
-- simple number
if tonumber(duration) ~= nil then
return {recovery = tonumber(duration)}
end
-- "Until L+10" -- aerial moves, such as grabs.
if string.find(duration, "^Until L%+%d+$") ~= nil then
local recovery = string.match(duration, "^Until L%+(%d+)$")
return {recovery=0, specialRecovery=recovery }
end
return nil
end
function isThrow(str)
if str ~= nil then
if string.find(str, "{{特性|投}}") then
return true
elseif string.find(str, "{{特性|空投}}") then
return true
elseif string.find(str, "{{特性|指令投}}") then
return true
else
return false
end
end
end
function parseNSC(duration)
-- simple number
if tonumber(duration) ~= nil then
return {nsc = tonumber(duration)}
end
if string.find(duration, "^%d+%(%d+%)") ~= nil then
-- 1(2)3(4)5 format -- multihit with gaps
local out = {}
-- special handling for the first number
local firstval, pos = string.match(duration, "^(%d+)()")
out['active'] = firstval
local ordinal = 2
-- then we just have a groups of "(inactive)active"
for p1, d1, d2, p2 in string.gmatch(duration, "()%((%d+)%)(%d+)()") do
if pos ~= p1 then
return nil -- not directly following the previous match, so basically not what we expect
end
out['nsc' .. tostring(ordinal)] = d1
out['nsc' .. tostring(ordinal+1)] = d2
ordinal = ordinal + 2
pos = p2
end
if pos ~= string.len(duration)+1 then
return nil -- trailing stuff at the end
end
return out
end
return nil
end
p.drawFrame = drawFrame
return p