No edit summary |
No edit summary |
||
Line 266: | Line 266: | ||
--]] | --]] | ||
local function getCharacterPunishersByType(character) | local function getCharacterPunishersByType(character) | ||
local function cargoBoolToBool(str) | |||
if str == '1' then | |||
return true | |||
elseif str == '0' then | |||
return false | |||
else | |||
error('Wrong cargo bool value = ' .. str) | |||
end | |||
end | |||
local punishers = cargo.query( | local punishers = cargo.query( | ||
'Punisher', | 'Punisher', | ||
Line 277: | Line 287: | ||
local punishersByType = {} | local punishersByType = {} | ||
for _, p in ipairs(punishers) do | for _, p in ipairs(punishers) do | ||
local | local type = p.type | ||
local frames = -tonumber(p.frames) | local frames = -tonumber(p.frames) | ||
local hard = | local hard = cargoBoolToBool(p.hard) | ||
local damages = { | local damages = { |
Revision as of 19:52, 12 September 2023
Documentation for this module may be created at Module:Move7/doc
local O = require("Module:O")
local cargo = mw.ext.cargo
local tables = 'MoveDataCargoTest'
local allFields = 'id,name,input,target,damage,reach,tracksLeft,tracksRight,startup,recv,tot,crush,block,hit,ch,notes'
function maxOrNil(a, b)
if a and b then
return math.max(tonumber(a) or 0, tonumber(b) or 0)
elseif a and not b then
return a
elseif b and not a then
return b
else
return nil
end
end
local p = {};
-- Get move id for querying, which is the 1st unnamed param of a template.
-- Moves are queried like this: {{TempateName|<moveId>}}
function getQueryId(frame)
local id = assert(frame:getParent().args[1], '1st unnamed param must be move id')
return mw.text.trim(id) -- whitespace is stripped only from named params
end
function moveNotFoundMsg(id)
return "move with id = '" .. id .. "' not found"
end
p.display = function(frame)
local function appendFrom(dst, src)
for k, v in pairs(src) do
dst[k] = v
end
end
local function getLeads(parentId)
local function concatKeys(t)
local result = ''
for k, _ in pairs(t) do
if result == '' then
result = result .. k
else
result = result .. ', ' .. k
end
end
return result
end
local leads = {}
local visitedParents = {}
while parentId and parentId ~= '' do
visitedParents[parentId] = true
local parent = cargo.query(tables, 'input,target,damage,parent', { where = "id='" .. parentId .. "'" })[1]
assert(parent, 'parent = "' .. parentId .. '" not found')
parentId = parent['parent']
assert(not visitedParents[parentId], 'Found parent cycle: ' .. concatKeys(visitedParents) .. ', ' .. parentId)
parent['parent'] = nil
for k, v in pairs(parent) do
local leadName = k .. 'Lead'
if not leads[leadName] then
leads[leadName] = ''
end
leads[leadName] = v .. leads[leadName]
end
end
return leads
end
local leads = getLeads(frame:getParent().args['parent'])
if not next(leads) then
return frame:expandTemplate{ title = 'MoveDataCargoTest/Display/Impl', args = frame:getParent().args }
end
local args = {}
appendFrom(args, frame:getParent().args)
appendFrom(args, leads)
return frame:expandTemplate{ title = 'MoveDataCargoTest/Display/Impl', args = args }
end
p.inherit = function(frame)
local id = getQueryId(frame)
local result = cargo.query(tables, allFields .. ',MoveDataCargoTest._pageName=page', { where = "id = '" .. id .. "'" })[1]
assert(result, moveNotFoundMsg(id))
for k, v in pairs(result) do
local override = frame:getParent().args[k]
if override then
result[k] = override
end
end
result[1] = '_table=' .. tables
frame:callParserFunction{ name = '#cargo_store', args = result }
result[1] = nil
result['inheritedFrom'] = '[['..result['page']..'#'..id..'|'..id..']]'
return frame:expandTemplate{ title = 'MoveDataCargoTest/Display', args = result }
end
p.punisherTable = function(frame)
local function notFoundMsg(type, i, name)
return type .. 'required parameter ['..i..'].' .. name .. ' not found'
end
local punishersByType = {}
for _, type in ipairs({
'standing', 'crouching', 'backTurnedOpponent', 'groundedOpponent'
}) do
local punishersEncoded = frame:getParent().args[type]
if punishersEncoded then
local punishers = {}
for i, v in ipairs(O.decode(punishersEncoded)) do
local decoded = O.decode(v)
assert(decoded.frames, notFoundMsg(type, i, 'frames'))
assert(decoded.id, notFoundMsg(type, i, 'id'))
decoded.frames = tonumber(decoded.frames)
table.insert(punishers, decoded)
end
punishersByType[type] = punishers
end
end
return p._punisherTable(frame, punishersByType)
end
p._punisherTable = function(frame, punishersByTypeUnsorted)
local function queryMoveInfo(punishers)
for _, punisher in ipairs(punishers) do
local input = ''
local damage = 0
local hit = nil
local id = punisher.id
while id ~= '' do
local move = cargo.query(tables, 'parent,input,damage,hit', {
where = "id = '" .. id .. "'"
})[1]
assert(move, moveNotFoundMsg(id))
input = move['input'] .. input
for k, v in string.gmatch(move['damage'], "(%d+)") do
damage = damage + tonumber(k)
end
if not hit then
hit = move['hit']
end
id = move['parent']
end
punisher.input = input
punisher.damage = damage
punisher.hit = hit
end
end
local function store(punishers, type, frame)
for _, punisher in ipairs(punishers) do
frame:callParserFunction{ name = '#cargo_store', args = {
'_table=Punisher',
type = type,
frames = punisher.frames,
damage = punisher.damage,
combo = punisher.combo,
wallCombo = punisher.wallCombo,
rageCombo = punisher.rageCombo,
hard = punisher.hard,
} }
end
end
local punishersByType = {}
for type, punishers in pairs(punishersByTypeUnsorted) do
table.sort(punishers, function(l, r) return l.frames > r.frames end)
queryMoveInfo(punishers)
store(punishers, type, frame)
table.insert(punishersByType, { type = type, values = punishers })
end
local displayArgs = {}
for _, punishers in ipairs(punishersByType) do
local rowspansByFrames = {}
for _, punisher in ipairs(punishers.values) do
local count = rowspansByFrames[punisher.frames]
if count then
rowspansByFrames[punisher.frames] = count + 1
else
rowspansByFrames[punisher.frames] = 1
end
end
local rows = ''
for _, punisher in ipairs(punishers.values) do
local row = mw.html.create('tr')
local rowspan = rowspansByFrames[punisher.frames]
if rowspan then
row:tag('td'):wikitext(punisher.frames):attr('rowspan', rowspan)
rowspansByFrames[punisher.frames] = nil
end
local inputCell = row:tag('td')
if punisher.inputOverride then
inputCell:wikitext(punisher.inputOverride)
else
inputCell:wikitext(punisher.input)
end
if punisher.hard then
inputCell:tag('sup'):wikitext('[hard]')
end
local damageCell = row:tag('td')
damageCell:wikitext(punisher.damage)
if punisher.combo then
damageCell:tag('br')
damageCell:wikitext(punisher.combo .. ' with combo')
end
if punisher.wallCombo then
damageCell:tag('br')
damageCell:wikitext(punisher.wallCombo .. ' with a wall')
end
if punisher.rageCombo then
damageCell:tag('br')
damageCell:wikitext(punisher.rageCombo .. ' with Rage')
end
row:tag('td'):wikitext(punisher.hit)
rows = rows .. tostring(row)
end
displayArgs[punishers.type] = rows
end
return frame:expandTemplate{
title = 'MoveDataCargoTest/PunisherTable/Display',
args = displayArgs
}
end
p.punishment = function(frame)
local character = frame:getParent().args[1]
--[[
Returns punishers by type. In each type punishers are sorted from fastest to slowest.
Table structure:
{
Standing = {
{ frames, damages = {
regular = int,
wall = int,
rage = int,
hard = int,
} },
...
},
Crouching = {
...
},
}
--]]
local function getCharacterPunishersByType(character)
local function cargoBoolToBool(str)
if str == '1' then
return true
elseif str == '0' then
return false
else
error('Wrong cargo bool value = ' .. str)
end
end
local punishers = cargo.query(
'Punisher',
'type,frames,damage,combo,wallCombo,rageCombo,hard',
{ where =
"Punisher._pageName='User:Lume/Cargo testing/" .. character .. " punishers'"
.. " AND (type = 'standing' OR type = 'crouching')"
}
)
local punishersByType = {}
for _, p in ipairs(punishers) do
local type = p.type
local frames = -tonumber(p.frames)
local hard = cargoBoolToBool(p.hard)
local damages = {
wall = tonumber(p.wallCombo),
rage = tonumber(p.rageCombo),
}
local regular = maxOrNil(p.damage, p.combo)
if hard then
damages.hard = regular
else
damages.regular = regular
end
if not punishersByType[type] then
punishersByType[type] = {}
end
local sameFramePunisherFound = false
for _, p in ipairs(punishersByType[type]) do
if p.frames == frames then
sameFramePunisherFound = true
for dmgType, dmg in pairs(damages) do
p.damages[dmgType] = maxOrNil(p.damages[dmgType], dmg)
end
end
end
if not sameFramePunisherFound then
table.insert(punishersByType[type], { frames = frames, damages = damages })
end
end
for _, punishers in pairs(punishersByType) do
local currentFrames = {}
for _, punisher in ipairs(punishers) do
currentFrames[punisher.frames] = true
end
-- punishment chart must contain all punishers from -10 to -15
for i = 10, 15, 1 do
if not currentFrames[i] then
table.insert(punishers, { frames = i, damages = {} })
end
end
table.sort(punishers, function(l, r) return l.frames < r.frames end)
end
return punishersByType
end
local punishersByType = getCharacterPunishersByType(character)
local span = 80
for _, punishers in pairs(punishersByType) do
for _, punisher in pairs(punishers) do
for _, dmg in pairs(punisher.damages) do
if dmg then
span = math.max(span, dmg)
end
end
end
end
if span % 5 ~= 0 then
span = span + 5 - (span % 5)
end
local scale = mw.html.create('div'):addClass('punishment-scale')
for i = 0, span, 5 do
local pip = mw.html.create('div')
:addClass('punishment-scalepip')
:css("width", string.format("%.2f%%", 100 * i / span))
:node(mw.html.create('div')
:addClass('punishment-piplabel')
:wikitext(i))
if i % 50 == 0 then
pip:addClass('major')
elseif i % 25 == 0 then
pip:addClass('minor')
end
scale:node(pip)
end
local data = mw.loadData('Module:Fighter/punishment')
local root = mw.html.create('div'):addClass('punishment')
for type, punishers in pairs(punishersByType) do
local group = mw.html.create('div'):addClass('punishment-group')
root:node(group)
group:node(mw.html.create("div")
:addClass('punishment-label')
:wikitext(type .. " punishment"))
local grid = mw.html.create('div'):addClass('punishment-grid')
group:node(grid)
local dmgClasses = {
regular = 'bg-blue',
rage = 'bg-purple',
wall = 'bg-orange',
hard = 'bg-green',
}
best = {}
for _, punisher in ipairs(punishers) do
-- don't carry rage/meter punishment, makes viz. look funky
best[dmgClasses.rage] = nil
for dmgType, dmg in pairs(punisher.damages) do
local dmgClass = dmgClasses[dmgType]
if not best[dmgClass] or best[dmgClass] < dmg then
best[dmgClass] = dmg
end
end
if best[dmgClasses.regular] then
for k, v in pairs(best) do
if k ~= dmgClasses.regular and best[k] <= best[dmgClasses.regular] then
best[k] = nil
end
end
end
bestSortedKeys = {}
for k, _ in pairs(best) do
table.insert(bestSortedKeys, k)
end
-- Larger bars first so that smaller bars are actually visible
table.sort(bestSortedKeys, function(a, b) return best[a] > best[b] end)
grid:node(mw.html.create('div')
:addClass('punishment-startup')
:wikitext("-" .. punisher.frames))
local bars = mw.html.create('div'):addClass('punishment-bars')
grid:node(bars)
for i, k in ipairs(bestSortedKeys) do
local class = k
bars:node(mw.html.create('div')
:addClass('punishment-bar')
:addClass(class)
:css("width", string.format("%.2f%%", 100 * best[k] / span))
:wikitext(best[k]))
end
local oldMedianKey = {
standing = 'stand',
crouching = 'crouch',
}
local median = data.median[oldMedianKey[type]][punisher.frames]
if median then
bars:node(mw.html.create('div')
:addClass('punishment-median')
:css('width', string.format("%.2f%%", 100 * median / span)))
end
end
grid:node(mw.clone(scale))
end
return root
end
return p