Hello there! We are conducting a survey to better understand the user experience in making a first edit. If you have ever made an edit on Gamepedia, please fill out the survey. Thank you!

Module:Scoreboard

From Leaguepedia | League of Legends Esports Wiki
Jump to: navigation, search

To edit the documentation or categories for this module, click here. This module has an i18n file. Click here to edit it.

This module can be called via a number of templates, available at Category:Scoreboard Templates. They should (almost) never be filled in by hand, instead we have parsers that create the input.

This module runs and makes the following hooks available:
  • onScoreboardParsePlayerData
  • onScoreboardPrintPlayerName
  • onScoreboardPrintPlayerCS
  • onScoreboardPrintPlayerKDA
  • onScoreboardPrintStatsHeaders
  • onScoreboardPrintPlayerGold

local util_args = require('Module:ArgsUtil')
local util_cargo = require('Module:CargoUtil')
local util_esports = require('Module:EsportsUtil')
local util_footnote = require('Module:FootnoteUtil')
local util_html = require('Module:HTMLUtil')
local util_math = require('Module:MathUtil')
local util_table = require('Module:TableUtil')
local util_text = require('Module:TextUtil')
local util_time = require('Module:TimeUtil')
local util_title = require('Module:TitleUtil')
local util_toggle = require('Module:ToggleUtil')
local util_vars = require('Module:VarsUtil')
local m_champion = require('Module:Champion')
local m_role = require('Module:Role')
local m_team = require('Module:Team')
local i18n = require('Module:i18nUtil')
local Sprite = require('Module:Sprite').sprite
local Hook = require('Module:Hook')
local SEASON
local PLAYER_NUMBER = 5
local PLAYER_SIDES = { 'blue', 'red' } -- this is whatever the args in the template want
local TEAMS = { 'blue', 'red' } -- this is absolute for CSS classes
local PRINT_ITEMS = true
local PLAYERS = {}
local PLAYER_ARGS = { 'Champion', 'Link', 'Name', 'SummonerSpells', 'Items', 'Trinket', 'Kills', 'Deaths', 'Assists', 'Gold', 'CS', 'Keystone', 'Secondary', 'Pentakills', 'PentakillVod', 'nocargo', 'SkillLetter', 'SkillImage' }
local GAME_ARGS_TEAMS = {
	score = 'Score',
	d = 'Dragons',
	b = 'Barons',
	t = 'Towers',
	rh = 'RiftHeralds',
	i = 'Inhibitors',
	g = 'Gold',
	k = 'Kills',
}
local GAME_ARGS = {
	gamename = 'Gamename',
	patch = 'Patch',
	gamelength = 'Gamelength',
	dst = 'DST',
	statslink = 'MatchHistory',
	vodlink = 'VOD',
	lolvod = 'VOD2'
}
local TOGGLES = {
	one = {
		show_attr = '.sb-w%s-g%s',
		hide_attr = '.sb-showbutton-w%s-g%s',
		show_class = 'sb-allw sb-w%s-allg sb-w%s-g%s',
		hide_class = 'sb-showbutton-allw sb-showbutton-w%s-allg sb-showbutton-w%s-g%s',
	},
	tab = {
		show_attr = '.sb-w%s-allg',
		hide_attr = '.sb-showbutton-w%s-allg',
		show_class = 'sb-allw sb-w%s-allg',
		hide_class = 'sb-showbutton-allw sb-showbutton-w%s-allg',
		cssclass = 'expand-contract-button-no-margin',
	},
	all = {
		show_attr = '.sb-allw',
		hide_attr = '.sb-showbutton-allw',
		show_class = 'sb-allw',
		hide_class = 'sb-showbutton-allw',
		show_text = 'Show Entire Page',
		hide_text = 'Hide Entire Page',
	},
	row = 'sb-allw sb-w%s-allg sb-w%s-g%s toggle-section-hidden'
}
local FOOTER_ITEMS = { 'Towers', 'Inhibitors', 'Barons', 'Dragons', 'RiftHeralds' }
local TIMEZONES = { 'PST', 'KST', 'CET' }
local sep = ','
local arg_sep = ';;;'
local lang = mw.getLanguage('en')
local h = {}
local p = {}
local s = {}

function p.button(frame)
	local args = util_args.merge(true)
	if util_args.castAsBool(args.name) then
		local section = util_vars.setGlobalIndex('sb_N_TabInPage_display')
		if not util_args.castAsBool(args.notab) then
			util_vars.setGlobalIndex('sb_N_TabInPage', frame)
			util_vars.resetGlobalIndex('sb_N_GameInTab')
			util_vars.resetGlobalIndex('sb_N_MatchInTab')
			util_vars.setVar('sbTabName', args.name or section)
		end
		util_toggle.prepDataByWeek(TOGGLES.tab, section)
		TOGGLES.tab.show_text = 'Show ' .. (args.display or args.name)
		TOGGLES.tab.hide_text = 'Hide ' .. (args.display or args.name)
		return util_toggle.printSectionToggler(nil, TOGGLES.tab), util_html.clear()
	else
		return util_toggle.printSectionToggler(nil, TOGGLES.all), util_html.clear()
	end
end
function p.header(frame)
	i18n.initGlobalFromFile('Scoreboard')
	util_vars.resetGlobalIndex('sb_N_GameInMatch')
	util_vars.setGlobalIndex('sb_N_MatchInTab')
	util_vars.setGlobalIndex('sb_N_MatchInPage')
	local output = mw.html.create('div'):addClass('sb-intro'):wikitext('')
	return output
end
function p.main(frame)
	local args = util_args.merge(true)
	return p._main(args)
end
function p._main(args)
	i18n.initGlobalFromFile('Scoreboard')
	if util_args.castAsBool(args.notplayed) then
		return p._noGamePlayed(args)
	end
	SEASON = mw.loadData('Module:Scoreboard/SeasonData')[tonumber(args.season)]
	h.setSeasonVariables()
	h.initializePlayerN(args) -- in case it's not 5 players per team
	h.initializeItems(args)
	local player_data = h.getPlayerDisplayData(args)
	local game_data = h.getGameDisplayData(args)
	if h.doWeStoreCargo(args) then
		h.processCargoData(player_data, game_data, args)
		h.cargo(player_data, game_data)
	end
	return h.makeOutput(game_data, player_data)
end
function p._noGamePlayed(args)
	i18n.initGlobalFromFile('Scoreboard')
	local game_data = h.getNotPlayedGameData(args)
	h.prepToggles(game_data)
	local tbl = mw.html.create('table')
		:addClass('sb')
	h.printTeamLine(tbl, game_data)
	h.printScoreLine(tbl, game_data)
	h.printNotPlayedTitle(tbl, args)
	h.printNotPlayedReason(tbl, args)
	return tbl
end
function s.ChampionSprite(id)
	return Sprite{
		id,
		size = 60,
		type = 'Champion',
		nosize = true,
		notext = true,
		nolink = true
	}
end
function s.ChampionSprite2(id)
	local div = mw.html.create('div')
		:addClass('sb-footer-ban')
		:wikitext(Sprite{
			id,
			size = 30,
			type = 'Champion',
			nosize = true,
			notext = true,
			nolink = true
		})
	return tostring(div)
end
function s.SummonerSprite(id)
	return Sprite{
		id,
		size = 30,
		type = 'Summoner',
		notext = true,
		nolink = true
	}
end
function s.ItemSprite(id)
	return Sprite{
		id,
		size = 30,
		type = 'Item',
		nosize = true,
		notext = true,
		nolink = true
	}
end
function s.InfoSprite(id)
	return Sprite{
		id,
		size = 15,
		type = 'ScoreboardIcon',
		notext = true,
		nolink = true
	}
end
function h.setSeasonVariables()
	if not SEASON.rh then
		-- could use keyOf but meh
		table.remove(FOOTER_ITEMS, 5)
	end
end
function h.initializePlayerN(args)
	PLAYER_NUMBER = args.teamsize or PLAYER_NUMBER
end
function h.initializeItems(args)
	PRINT_ITEMS = not util_args.castAsBool(args.noitems)
end
function h.getPlayerDisplayData(args)
	local data = {}
	for i, side in ipairs(PLAYER_SIDES) do
		data[i] = {}
		for j = 1, PLAYER_NUMBER do
			local row = h.splitPlayerDataAndErrorcheck(args, side, i, j)
			data[i][j] = h.parsePlayerData(row, i, j)
		end
	end
	return data
end
function h.splitPlayerDataAndErrorcheck(args, side, i, j)
	if not args[side .. j] then
		error(i18n.print('no_player_data', i, j))
	end
	local row = util_args.splitArgs(args[side .. j], PLAYER_ARGS, arg_sep)
	return row
end
function h.parsePlayerData(row, i, j)
	row.Link = lang:ucfirst(row.Link or row.Name or '')
	row.Side = i
	row.Role_Number = j
	row.Role = m_role.rolename('scoreboard' .. j)
	row.Items = util_text.split(row.Items or '')
	row.SummonerSpells = util_text.split(row.SummonerSpells)
	row.Champion = m_champion.championname(row.Champion)
	Hook.run('onScoreboardParsePlayerData', row, i, j)
	return row
end
function h.getGameDisplayData(args)
	local game_data = {}
	h.setVariables(game_data, args)
	h.getWinnerAndLoser(game_data, args)
	h.getGameDataByRenamingArgs(game_data, args)
	h.getGamelengthNumber(game_data)
	h.getTeamDisplayData(game_data, args)
	h.getDateAndTime(game_data, args)
	h.getGameFootnotes(game_data, args)
	return game_data
end
function h.setVariables(game_data, args)
	game_data.N_MatchInTab = util_vars.getGlobalIndex('sb_N_MatchInTab')
	game_data.N_MatchInPage = util_vars.getGlobalIndex('sb_N_MatchInPage')
	game_data.N_GameInMatch = util_vars.setGlobalIndex('sb_N_GameInMatch')
	util_vars.setGlobalIndex('sb_N_GameInTab')
	game_data.Tournament = args.tournament
end
function h.getWinnerAndLoser(game_data, args)
	if args.winner and not tonumber(args.winner) then
		error('Invalid winner')
	elseif not args.winner then
		return
	end
	local winner = tonumber(args.winner)
	local loser = util_esports.otherTeamN(winner)
	game_data.Winner = winner
	game_data.WinTeam = m_team.teamlinkname(args['team' .. winner])
	game_data.LossTeam = m_team.teamlinkname(args['team' .. loser])
end
function h.getGameDataByRenamingArgs(game_data, args)
	for k, v in pairs(GAME_ARGS) do
		game_data[v] = args[k]
	end
	game_data.VOD = game_data.VOD or game_data.VOD2
end
function h.getGamelengthNumber(game_data)
	if not game_data.Gamelength then return end
	local m, s = game_data.Gamelength:match('(%d+):(%d+)')
	m = tonumber(m)
	s = tonumber(s)
	if not m then
		error("Can't find minutes in game length")
	elseif not s then
		error("Can't find seconds in game length")
	end
	game_data.Gamelength_Number = m + s / 60
end
function h.getTeamDisplayData(game_data, args)
	for i, s in ipairs(PLAYER_SIDES) do
		local key = 'Team' .. i
		local arg = 'team' .. i
		game_data[key] = m_team.teamlinkname(args[arg])
		game_data[key .. 'Bans'] = h.getBans(args, arg)
		h.getTeamDataByRenamingArgs(game_data, args, key, arg)
	end
end
function h.getBans(args, arg)
	local bans = util_args.numberedArgsToTable(args, arg .. 'ban', true, SEASON.max_bans) or {}
	util_table.mapInPlace(bans, m_champion.championname)
	util_table.padFalseEntries(bans, SEASON.max_bans, 'Loss of Ban')
	return bans
end
function h.getTeamDataByRenamingArgs(game_data, args, key, arg)
	for k, v in pairs(GAME_ARGS_TEAMS) do
		game_data[key .. v] = args[arg .. k]
	end
end
function h.getDateAndTime(game_data, args)
	game_data.tz = h.parseTimeData(args)
end
function h.parseTimeData(args)
	local dst = util_args.norm(args.dst)
	if args.time then
		return util_time.getTimezones(args.date .. ' ' .. args.time, args.timezone, dst)
	end
	return h.getTZDataFromOldParamStyle(args, dst) or { UTC = 'Undefined' }
end
function h.getTZDataFromOldParamStyle(args, dst)
	for _, tz in ipairs(TIMEZONES) do
		if args[tz] then
			return util_time.getTimezones(args.date .. ' ' .. args[tz], tz, dst)
		end
	end
	return nil
end
function h.getGameFootnotes(game_data, args)
	game_data.footnote = args.footnote
end

-- cargo data
function h.processCargoData(player_data, game_data, args)
	game_data.cargo = h.initGameCargoFromData(game_data)
	game_data.cargo.OverviewPage = util_esports.getOverviewPage(args.page)
	game_data.cargo.N_MatchInTab = nil
	game_data.cargo.UniqueGame = util_cargo.getUniqueLine(game_data.N_MatchInPage, game_data.N_GameInMatch)
	game_data.cargo.UniqueLine = util_cargo.getUniqueLine(game_data.N_MatchInPage, game_data.N_GameInMatch)
	game_data.cargo.ScoreboardID_Wiki = h.getIdWiki(game_data)
	game_data.cargo.DateTime_UTC = game_data.tz.UTC
	-- TODO: Add list of picks to game maybe?
	-- add actual values to what's nil above
	h.addTeamCargoFieldsToGame(game_data.cargo, player_data)
	for t, team in ipairs(player_data) do
		for playerN, player_data in ipairs(team) do
			player_data.cargo = h.initPlayerCargoFromData(player_data)
			h.addPlayerCargoDataFromGame(player_data.cargo, game_data.cargo, t)
			h.addPlayerCargoRoleData(player_data.cargo, playerN)
			h.addPlayerCargoKeystoneData(player_data.cargo, player_data)
			player_data.cargo.Side = t
			player_data.cargo.UniqueLine = util_cargo.getUniqueLine(game_data.N_MatchInPage, game_data.N_GameInMatch, t, playerN)
			player_data.cargo.UniqueLineVs = util_cargo.getUniqueLine(game_data.N_MatchInPage, game_data.N_GameInMatch, util_esports.otherTeamN(t), playerN)
			player_data.cargoPentakill = h.getPlayerPentakillCargo(player_data.cargo, player_data)
			player_data.cargo.StatsPage = util_title.concatSubpageParts(player_data.Link, 'Statistics', util_vars.getVar('sbYear'))
		end
	end
end
function h.initGameCargoFromData(game_data)
	local fields = { 'Tournament', 'Team1', 'Team2', 'WinTeam', 'LossTeam', 'DST', 'Team1Score', 'Team2Score', 'Winner', 'Gamelength', 'Gamelength_Number', 'Team1Bans', 'Team2Bans', 'Team1Dragons', 'Team2Dragons', 'Team1Barons', 'Team2Barons', 'Team1Towers', 'Team2Towers', 'Team1Gold', 'Team2Gold', 'Team1Kills', 'Team2Kills', 'Team1RiftHeralds', 'Team2RiftHeralds', 'Team1Inhibitors', 'Team2Inhibitors', 'Patch', 'MatchHistory', 'Gamename', 'N_GameInMatch', 'N_MatchInPage', 'VOD' }
	local ret = { _table = 'ScoreboardGame' }
	for _, v in ipairs(fields) do
		ret[v] = h.castForCargo(game_data[v])
	end
	return ret
end
function h.getIdWiki(game_data)
	return ('%s_%s_%s_%s'):format(
		game_data.cargo.OverviewPage,
		util_vars.getVar('sbTabName') or util_vars.getVar('thisTab') or 'Unknown Tab',
		util_vars.getGlobalIndex('sb_N_MatchInTab'),
		game_data.N_GameInMatch
	)
end
function h.addTeamCargoFieldsToGame(cargo, player_data)
	cargo.Team1Picks = h.getTeamDataForCargo(player_data[1], 'Champion')
	cargo.Team2Picks = h.getTeamDataForCargo(player_data[2], 'Champion')
	cargo.Team1Links = h.getTeamDataForCargo(player_data[1], 'Link')
	cargo.Team2Links = h.getTeamDataForCargo(player_data[2], 'Link')
	cargo.Team1Names = h.getTeamDataForCargo(player_data[1], 'Name')
	cargo.Team2Names = h.getTeamDataForCargo(player_data[2], 'Name')
end
function h.getTeamDataForCargo(team, datapoint)
	local tbl = {}
	for _, player in ipairs(team) do
		tbl[#tbl+1] = player[datapoint]
	end
	return util_table.concat(tbl, ',')
end
function h.initPlayerCargoFromData(player_data)
	local fields = { 'Name', 'Link', 'Champion', 'Kills', 'Deaths', 'Assists', 'SummonerSpells', 'Gold', 'CS', 'Items', 'Trinket' }
	local ret = { _table = 'ScoreboardPlayer' }
	for _, v in ipairs(fields) do
		ret[v] = h.castForCargo(player_data[v])
	end
	return ret
end
function h.castForCargo(v)
	if type(v) == 'table' then
		return util_table.concat(v)
	end
	return v
end
function h.addPlayerCargoDataFromGame(player, game, team)
	player.Team = game['Team' .. team]
	player.TeamVs = game['Team' .. util_esports.otherTeamN(team)]
	player.TeamGold = game['Team' .. team .. 'Gold']
	player.TeamKills = game['Team' .. team .. 'Kills']
	player.DateTime_UTC = game.DateTime_UTC
	player.OverviewPage = game.OverviewPage
	player.UniqueGame = game.UniqueGame
	player.PlayerWin = game.Winner == team
end
function h.addPlayerCargoRoleData(cargo, playerN)
	cargo.Role = m_role.storename('scoreboard' .. playerN)
	cargo.Role_Number = playerN
end
function h.addPlayerCargoKeystoneData(cargo, player_data)
	if not SEASON.keystone then return end
	if SEASON.keystone == 'Mastery' then
		cargo.KeystoneMastery = player_data.Keystone
	elseif SEASON.keystone == 'Rune' then
		cargo.KeystoneRune = player_data.Keystone
	end
end
function h.getPlayerPentakillCargo(playerCargo, player_data)
	if tonumber(player_data.Pentakills or 0) < 1 then return end
	local ret = {
		_table = 'Pentakills',
		DateDisplay = util_time.strToDateStr(playerCargo.DateTime_UTC),
		DateSort = playerCargo.DateTime_UTC,
		OverviewPage = util_esports.getOverviewPage(),
		Team = playerCargo.Team,
		TeamVs = playerCargo.TeamVs,
		Name = playerCargo.Name,
		Link = playerCargo.Link,
		Champion = playerCargo.Champion,
		Role = playerCargo.Role,
		Win = playerCargo.PlayerWin,
		Kills = playerCargo.Kills,
		Deaths = playerCargo.Deaths,
		Assists = playerCargo.Assists,
		ScoreboardLink = mw.title.getCurrentTitle().text,
		Vod = player_data.PentakillVod
	}
	return ret
end

-- cargo
function h.doWeStoreCargo(args)
	if util_args.castAsBool(args.nocargo) then
		return false
	end
	return mw.title.getCurrentTitle().nsText == ''
end
function h.cargo(player_data, game_data)
	util_cargo.store(game_data.cargo)
	for _, team in ipairs(player_data) do
		for _, player in ipairs(team) do
			util_cargo.store(player.cargo)
			util_cargo.store(player.cargoPentakill)
		end
	end
end
-- output
function h.makeOutput(game_data, player_data)
	h.prepToggles()
	local tbl = mw.html.create('table')
		:addClass('sb')
	h.printTeamLine(tbl, game_data)
	h.printScoreLine(tbl, game_data)
	h.printHeaderLine(tbl, game_data)
	h.printLine(tbl, game_data, 'th', h.printKey)
	h.printLine(tbl, player_data, 'td', h.printPlayers)
	h.printDateTimeAndLinks(tbl, game_data)
	h.printLine(tbl, game_data, 'td', h.printFooter)
	return tbl
end
function h.prepToggles()
	local n = util_vars.getGlobalIndex('sb_N_GameInTab')
	local section = util_vars.getGlobalIndex('sb_N_TabInPage_display')
	util_toggle.prepDataByWeekAndGame(TOGGLES.one, section, n)
	TOGGLES.row = TOGGLES.row:format(section, section, n)
end
function h.printTeamLine(tbl, game_data)
	local tr = tbl:tag('tr')
	local th1 = tr:tag('th')
		:addClass('sb-teamname')
		:wikitext(m_team.rightmediumlinked(game_data.Team1))
	h.printVs(tr, game_data)
	local th2 = tr:tag('th')
		:addClass('sb-teamname')
		:wikitext(m_team.leftmediumlinked(game_data.Team2))
end
function h.printVs(tr, game_data)
	local thVs = tr:tag('th')
		:addClass('sb-teamname-vs')
		:attr('colspan',2)
		:wikitext('vs')
	util_footnote.tagFootnotePlain(thVs, game_data.footnote)
end
function h.printScoreLine(tbl, game_data)
	local tr = tbl:tag('tr')
	h.printScore(tr, game_data.Team1Score, 'blue', game_data.Winner == 1)
	local th = tr:tag('th'):attr('colspan','2')
	util_toggle.printToggleButton(th, TOGGLES.one)
	h.printScore(tr, game_data.Team2Score, 'red', game_data.Winner == 2)
end
function h.printScore(tr, score, side, isWinner)
	local th = tr:tag('th')
		:addClass('side-' .. side)
		:wikitext(score)
	if isWinner then
		th:addClass('sb-score-winner')
	end
end
function h.printHeaderLine(tbl, game_data)
	local tr = tbl:tag('tr')
		:addClass(TOGGLES.row)
	h.printHeaderCell(tr, game_data, 1)
	h.printGameLength(tr, game_data)
	h.printHeaderCell(tr, game_data, 2)
end
function h.printHeaderCell(tr, game_data, i)
	local td = tr:tag('th')
	if i == 2 then
		td:addClass('side-red')
	end
	local div = td:tag('div')
		:addClass('sb-header')
	local verdict
	if game_data.Winner == i then
		verdict = 'Victory'
	elseif game_data.Winner == util_esports.otherTeamN(i) then
		verdict = 'Defeat'
	end
	h.div(div, 'header-vertict', verdict)
	h.printHeaderInfo(div, 'Gold', util_esports.roundedGold(game_data['Team' .. i .. 'Gold']))
	h.printHeaderInfo(div, 'Kills', game_data['Team' .. i .. 'Kills'])
end
function h.printHeaderInfo(parent, name, wikitext)
	local class = ('%s'):format(lang:lc(name))
	parent:tag('div')
		:addClass('sb-header-' .. name)
		:attr('title', i18n.print(name))
		:wikitext(s.InfoSprite(name), ' ', wikitext)
end
function h.printGameLength(tr, game_data)
	local th = tr:tag('th')
		:attr('colspan', 2)
	th:wikitext(game_data.Gamelength)
end
function h.printLine(tbl, data, celltype, f)
	local tr = tbl:tag('tr')
		:addClass(TOGGLES.row)
	for i, side in ipairs(TEAMS) do
		local td = tr:tag(celltype)
			:attr('colspan',2)
			:addClass('side-' .. side)
		f(td, data, i)
	end
end
function h.div(parent, class, wikitext, extra)
	local div = parent:tag('div'):addClass('sb-' .. class):wikitext(wikitext)
	if extra then
		div:addClass('sb-' .. class .. '-' .. extra)
	end
end
function h.printKey(td)
	-- honestly it's just not worth it to write any kind of loop for this
	local outer = td:tag('div'):addClass('sb-key')
	h.div(outer, 'key-champion', 'Champ')
	h.div(outer, 'key-summoners', 'SS')
	if SEASON.runes then
		h.div(outer, 'key-runes', 'R')
	end
	local info = outer:tag('div'):addClass('sb-key-info')
	local stats = info:tag('div'):addClass('sb-key-stats')
	h.printStatsHeaders(stats)
	if SEASON.trinket then
		h.div(stats, 'key-trinket', 'T')
	end
	if PRINT_ITEMS then
		h.div(outer, 'key-items', 'Items')
	end
end
function h.printStatsHeaders(stats)
	if not Hook.run('onScoreboardPrintStatsHeaders', stats, h.div) then
		return
	end
	h.div(stats, 'key-stat', 'KDA', 'kda')
	h.div(stats, 'key-stat', 'CS', 'cs')
	h.div(stats, 'key-stat', 'Gold', 'gold')
end
function h.printPlayers(td, player_data, i)
	local data = player_data[i]
	for _, row in ipairs(data) do
		h.printPlayer(td, row)
	end
end
function h.printPlayer(td, row)
	local outer = td:tag('div'):addClass('sb-p')
	h.div(outer, 'p-champion', s.ChampionSprite(row.Champion))
	h.printPlayerSummoners(outer, row.SummonerSpells)
	if SEASON.runes then
		h.printPlayerRunes(outer, row)
	end
	local info = outer:tag('div'):addClass('sb-p-info')
	h.printPlayerName(info, row)
	h.printPlayerStats(info, row)
	if SEASON.mastery then
		h.printTrinketAndMastery(outer, row)
	end
	if PRINT_ITEMS then
		h.printPlayerItems(outer, row.Items)
	end
end
function h.printPlayerSummoners(outer, spells)
	local ss = outer:tag('div'):addClass('sb-p-summoners')
	h.div(ss, 'p-sum', s.SummonerSprite(spells[1]))
	h.div(ss, 'p-sum', s.SummonerSprite(spells[2]))
end
function h.printPlayerRunes(outer, row)
	local runes = outer:tag('div'):addClass('sb-p-runes')
	h.printPlayerRune(runes, row.Keystone or 'EmptySummoner')
	h.printPlayerRune(runes, row.Secondary or 'EmptySummoner')
end
function h.printPlayerRune(runes_div, rune)
	h.div(runes_div, 'p-rune', ('[[File:Rune %s.png|30px|link=]]'):format(rune))
end
function h.printPlayerName(info, row)
	if not Hook.run('onScoreboardPrintPlayerName', info, row) then
		return
	end
	h.div(info, 'p-name', util_text.intLink(row.Link, row.Name))
end
function h.printTrinketAndMastery(outer, row)
	local inner = outer:tag('div'):addClass('sb-p-masteryandtrinket')
	h.div(inner, 'p-trinket', ('[[File:Mastery %s.png|30px|link=]]'):format(row.Keystone or 'None'))
	h.printTrinket(inner, row.Trinket)
end
function h.printTrinket(div, trinket)
	h.div(div, 'p-trinket', s.ItemSprite(trinket))
end
function h.printPlayerStats(info, row)
	local stats = info:tag('div'):addClass('sb-p-stats')
	h.printPlayerKDA(stats, row)
	h.printPlayerCS(stats, row)
	h.printPlayerGold(stats, row)
	if SEASON.trinket and not SEASON.mastery then
		h.printTrinket(stats, row.Trinket)
	end
end
function h.printPlayerKDA(stats, row)
	if not Hook.run('onScoreboardPrintPlayerKDA', stats, row, h.div) then
		return
	end
	h.div(stats, 'p-stat', util_esports.KDA(row.Kills or '', row.Deaths or '', row.Assists or ''), 'kda')
end
function h.printPlayerCS(stats, row)
	if not Hook.run('onScoreboardPrintPlayerCS', stats, row, h.div) then
		return
	end
	h.div(stats, 'p-stat', row.CS, 'cs')
end
function h.printPlayerGold(stats, row)
	if not Hook.run('onScoreboardPrintPlayerGold', stats, row, h.div) then
		return
	end
	h.div(stats, 'p-stat', util_esports.roundedGold(row.Gold), 'gold')
end
function h.printPlayerItems(outer, items)
	h.div(outer, 'p-items', util_table.concat(items, '', s.ItemSprite))
end
function h.printDateTimeAndLinks(tbl, game_data)
	local tr = tbl:tag('tr')
		:addClass(TOGGLES.row)
	local td = tr:tag('td')
		:attr('colspan',4)
		:addClass('sb-datetime-outer')
		:addClass('plainlinks')
	local div = td:tag('div')
		:addClass('sb-datetime')
	h.printDateAndTime(div, game_data.tz.UTC)
	h.printPatch(div, game_data.Patch)
	h.printMH(div, game_data.MatchHistory)
	h.printVOD(div, game_data.VOD)
end
function h.printDateAndTime(div, utc)
	local date = util_time.dateInLocal(utc)
	local time = util_time.timeInLocal(utc)
	local display = date .. ' ' .. time
	h.printDateTimeItem(div, 'date', display, 'Date & Time of the match in your local time zone')
end
function h.printPatch(div, patch)
	local display = patch and util_text.intLinkOrText('Patch ' .. patch) or ''
	h.printDateTimeItem(div, 'patch', display, 'Patch')
end
function h.printMH(div, mh)
	local display = i18n.print('match_history') .. (util_text.extLink(mh, 'Link') or 'N/A')
	h.printDateTimeItem(div, 'mh', display)
end
function h.printVOD(div, vod)
	local display = i18n.print('vod') .. (util_text.extLink(vod, 'Link') or 'N/A')
	h.printDateTimeItem(div, 'vod', display)
end
function h.printDateTimeItem(div, class, str, title)
	div:tag('div')
		:addClass('sb-datetime-' .. class)
		:wikitext(str)
		:attr('title', title)
end
function h.printFooter(td, game_data, i)
	local key = 'Team' .. i
	local div = td:tag('div'):addClass('sb-footer')
	local bans = div:tag('div'):addClass('sb-footer-bans')
	h.div(bans, 'footer-ban-header', nil)
	h.div(bans, 'footer-bans', util_table.concat(game_data[key .. 'Bans'], '', s.ChampionSprite2))
	local stats = div:tag('div'):addClass('sb-footer-stats')
	for _, v in ipairs(FOOTER_ITEMS) do
		h.printFooterInfo(stats, game_data, key, v, 'footer-item')
	end
end
function h.printFooterInfo(parent, data, key, datapoint, parentclass)
	local fullkey = key .. datapoint
	local class2 = ('%s'):format(lang:lc(datapoint))
	parent:tag('div')
		:addClass('sb-' .. parentclass)
		:addClass('sb-' .. parentclass .. '-' .. class2)
		:wikitext(s.InfoSprite(fullkey), ' ', data[fullkey])
		:attr('title', i18n.print(datapoint))
end
-- if game wasn't played
function h.getNotPlayedGameData(args)
	local game_data = {}
	h.setVariables(game_data, args)
	h.getWinnerAndLoser(game_data, args)
	h.getGameDataByRenamingArgs(game_data, args)
	h.getNotPlayedTeamDisplayData(game_data, args)
	h.getDateAndTime(game_data, args)
	h.getGameFootnotes(game_data, args)
	return game_data
end
function h.getNotPlayedTeamDisplayData(game_data, args)
	for i, s in ipairs(PLAYER_SIDES) do
		local key = 'Team' .. i
		local arg = 'team' .. i
		game_data[key] = m_team.teamlinkname(args[arg])
		h.getTeamDataByRenamingArgs(game_data, args, key, arg)
	end
end
function h.printNotPlayedTitle(tbl, args)
	local tr = tbl:tag('tr')
	tr:tag('td')
		:addClass('sb-notplayed-header')
		:addClass(TOGGLES.row)
		:attr('colspan', 4)
		:wikitext(args.notplayed)
end
function h.printNotPlayedReason(tbl, args)
	local tr = tbl:tag('tr')
	tr:tag('td')
		:addClass('sb-notplayed')
		:addClass(TOGGLES.row)
		:attr('colspan', 4)
		:wikitext(args.reason or i18n.print('no_reason'))
end
return p