|
ย |
(92 intermediate revisions by 2 users not shown) |
Line 1: |
Line 1: |
| local O = require("Module:O") | | local p = require("Module:Move") |
| local cargo = mw.ext.cargo
| | p.game = require("Module:Game").Tekken7 |
| ย | |
| 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
| |
| ย | |
| function cargoBoolToBool(str)
| |
| if str == '1' then
| |
| return true
| |
| elseif str == '0' then
| |
| return false
| |
| elseif str then
| |
| error('Wrong cargo bool value = ' .. str)
| |
| else
| |
| error('Cargo bool value is nil')
| |
| end
| |
| end
| |
| ย | |
| function boolToCargoBool(b)
| |
| if b == true then
| |
| return '1'
| |
| elseif b == false then
| |
| return '0'
| |
| else
| |
| error('bool is 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)
| |
| if decoded.hard == 'true' then
| |
| decoded.hard = false
| |
| else
| |
| decoded.hard = true
| |
| end
| |
|
| |
| 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 = boolToCargoBool(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 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 | | return p |