Leaguepedia | League of Legends Esports Wiki

Documentation for this module may be created at Module:ScoreboardAbstract/doc

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_map = require('Module:MapUtil')
local util_html = require('Module:HtmlUtil')
local util_math = require('Module:MathUtil')
local util_riot = require("Module:RiotUtil")
local util_scoreboard = require("Module:ScoreboardUtil")
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 Role = require('Module:Role')
local m_team = require('Module:Team')
local Item = require('Module:Item')
local ItemList = require('Module:ItemList')
local Champion = require('Module:Champion')
local ChampionList = require('Module:ChampionList')
local Keystone = require('Module:Keystone')
local LCS = require('Module:LuaClassSystem')
local i18n = require('Module:i18nUtil')
local Sprite = require('Module:Sprite').sprite
local RunesReforged = require('Module:Scoreboard/RunesReforged')
local TabVariables = require('Module:TabVariables')
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 TEAMS_DISPLAY = { 'Blue', 'Red' } -- this is for Cargo
local PRINT_ITEMS = true
local PLAYERS = {}
local PLAYER_ARGS = { 'Champion', 'Link', 'Name', 'SummonerSpells', 'Items', 'Trinket', 'Kills', 'Deaths', 'Assists', 'Gold', 'CS', 'Keystone', 'PrimaryTree', 'SecondaryTree', 'Pentakills', 'PentakillVod', 'nocargo', 'SkillLetter', 'SkillImage', 'AllRunes', 'IngameRole', 'DamageToChampions', 'VisionScore', }
local GAME_ARGS_TEAMS = {
	score = 'Score',
	d = 'Dragons',
	b = 'Barons',
	t = 'Towers',
	rh = 'RiftHeralds',
	i = 'Inhibitors',
	g = 'Gold',
	k = 'Kills',
	vg = 'VoidGrubs',
	at = 'Atakhans'
}
local GAME_ARGS = {
	gamename = 'Gamename',
	gamelength = 'Gamelength',
	dst = 'DST',
	statslink = 'MatchHistory',
	vodlink = 'VOD',
	lolvod = 'VOD2',
	patch = 'LegacyPatch'
}
local FOOTER_ITEMS = { 'Towers', 'Inhibitors', 'Barons', 'Dragons', 'RiftHeralds', 'VoidGrubs', 'Atakhans' }
local TIMEZONES = { 'PST', 'KST', 'CET' }
local summonerspell_sep = ','
local item_sep = ';'
local arg_sep = ';;;'
local lang = mw.getLanguage('en')
local h = {}
local p = LCS.class.abstract()
local s = {}

function p:init(args)
	i18n.init('Scoreboard')
	if util_args.castAsBool(args.notplayed) then
		util_scoreboard.storeCounterCargo()
		return self:_noGamePlayed(args)
	end
	SEASON = mw.loadData('Module:Scoreboard/SeasonData')[tonumber(args.season)]
	h.setSeasonVariables(args)
	h.initializePlayerN(args) -- in case it's not 5 players per team
	h.initializeItems(args)
	local player_data = self:getPlayerDisplayData(args)
	local game_data = h.getGameDisplayData(args)
	self:cargo(args, player_data, game_data)
	return self:makeOutput(game_data, player_data)
end
function p:_noGamePlayed(args)
	i18n.init('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.setScores(game_data, args)
	h.printScoreLine(tbl, game_data)
	h.printNotPlayedTitle(tbl, args)
	h.printNotPlayedReason(tbl, args)
	util_scoreboard.storeCounterCargo(args)
	return tbl
end
function s.SummonerSprite(id)
	return Sprite{
		id,
		size = 30,
		type = 'Summoner',
		notext = true,
		nolink = true
	}
end
function s.InfoSprite(id)
	return Sprite{
		id,
		size = 15,
		type = 'ScoreboardIcon',
		notext = true,
		nolink = true
	}
end
function h.setSeasonVariables(args)
	if not SEASON.rh then
		-- could use keyOf but meh
		util_table.removeValue(FOOTER_ITEMS, "RiftHeralds")
	end
	if not args.team1vg and not args.team2vg then
		util_table.removeValue(FOOTER_ITEMS, "VoidGrubs")
	end
	if not args.team1at and not args.team2at then
		util_table.removeValue(FOOTER_ITEMS, "Atakhans")
	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 p: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] = self:parsePlayerData(row, i, j, args)
		end
		data[i].rolemap = h.getRolemap(data[i])
		data[i].rolemapHash = util_table.hash(data[i].rolemap)
	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 p:parsePlayerData(row, i, j, args)
	row.Name = util_esports.playerDisplay(row.Link or row.Name or '')
	row.Link = row.Link or row.Name
	row.Side = i
	row.Role_Number = j
	-- ill substitute all ",,," by ";" because we want to store to cargo using that delimiter
	-- i cant use the ; delimiter in the Scoreboard/Player template
	row.Items = ItemList(row.Items:gsub(",,,", ";"), {patch = args.patch_sort or args.patch, sep = item_sep})
	row.Trinket = Item(row.Trinket)
	row.SummonerSpells = util_text.split(row.SummonerSpells)
	row.Champion = Champion(row.Champion)
	row.AllRunes = RunesReforged.cast(row.AllRunes)
	row.IngameRole = Role(row.IngameRole or tostring(row.Role_Number))
	row.IngameRoleNumber = row.IngameRole:get('sortnumber')
	return row
end
function h.getRolemap(players)
	local ret = {}
	for i, row in ipairs(players) do
		-- the ingame role number is the proper final position of this row
		-- so when we look up, what row to use at this final position? we want to get this row
		ret[row.IngameRoleNumber] = i
	end
	return ret
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)
	h.setScores(game_data, args)
	game_data.version = util_math.tonumber(args.version)
	game_data.Patch = util_esports.getPatchFromLegacyPatch(args.patch)
	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.padFalseEntries(bans, SEASON.max_bans, 'None')
	bans = ChampionList(bans)
	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
function h.setScores(game_data, args)
	if args.team1score and args.team2score then return end
	if not game_data.WinTeam then return end
	util_scoreboard.incrementScore(game_data.WinTeam)
	game_data.Team1Score = util_scoreboard.getScore(game_data.Team1)
	game_data.Team2Score = util_scoreboard.getScore(game_data.Team2)
end

-- cargo data
function p:cargo(args, player_data, game_data)
	util_scoreboard.storeCounterCargo()
	if not h.doWeStoreCargo(args) then return end
	self:processCargoData(args, player_data, game_data)
	self:storeCargo(args, player_data, game_data)
end
function h.doWeStoreCargo(args)
	if util_args.castAsBool(args.nocargo) then
		return false
	end
	return mw.title.getCurrentTitle().nsText == ''
end
function p:processCargoData(args, player_data, game_data)
	game_data.cargo = h.initGameCargoFromData(game_data)
	game_data.cargo.OverviewPage = util_esports.getOverviewPage(args.page)
	game_data.cargo.N_Page = TabVariables.getIndex()
	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.GameId = h.getIdWiki(game_data)
	game_data.cargo.MatchId = h.getMatchId(game_data)
	game_data.cargo.DateTime_UTC = game_data.tz.UTC
	
	game_data.cargo.RiotPlatformGameId = args.rpgid
	game_data.cargo.Version = args.version
	
	util_riot.setMhIdFields(game_data.cargo, game_data.cargo.MatchHistory)
	
	game_data.cargo.Gamename = game_data.cargo.Gamename or i18n.default('gamename', game_data.N_GameInMatch)
	-- TODO: Add list of picks to game maybe?
	-- add actual values to what's nil above
	h.gatherPlayerCargoForGameFields(game_data.cargo, player_data)
	
	game_data.team_cargo = {}
	for t, team in ipairs(player_data) do
		game_data.team_cargo[t] = h.getTeamCargo(game_data, player_data, t)
		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.GameTeamId = game_data.team_cargo[t].GameTeamId
			player_data.cargo.Runes = RunesReforged.store(player_data.AllRunes)
			player_data.cargo.Side = t
			player_data.cargo.Name = util_esports.playerDisplay(player_data.Link, player_data.Name)
			
			-- primary key as well as a self-foreign key for joining
			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)
			
			-- for ingame roles we will want to join on the ingame role, not the position
			-- within the scoreboard. let's keep the position within the scoreboard as the
			-- primary key, but for stats calculations we'll just use UniqueRole and
			-- UniqueRoleVs.
			local ingameRoleNumber = player_data.IngameRole:get('sortnumber') or playerN
			player_data.cargo.UniqueRole = util_cargo.getUniqueLine(game_data.N_MatchInPage, game_data.N_GameInMatch, t, ingameRoleNumber)
			player_data.cargo.UniqueRoleVs = util_cargo.getUniqueLine(game_data.N_MatchInPage, game_data.N_GameInMatch, util_esports.otherTeamN(t), ingameRoleNumber)
			
			player_data.cargo.GameRoleId = util_cargo.getUniqueLine(game_data.N_MatchInPage, game_data.N_GameInMatch, t, ingameRoleNumber)
			player_data.cargo.GameRoleIdVs = util_cargo.getUniqueLine(game_data.N_MatchInPage, game_data.N_GameInMatch, util_esports.otherTeamN(t), ingameRoleNumber)
			
			player_data.cargoPentakills = h.getPlayerPentakillCargo(player_data.cargo, player_data)
			
			-- this field is just for an external python script
			player_data.cargo.StatsPage = util_title.concatSubpageParts(player_data.Link, 'Statistics', util_vars.getVar('sbYear'))
		end
	end
end
-- get for entire game
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', 'Team1VoidGrubs', 'Team2VoidGrubs', 'Team1Atakhans', 'Team2Atakhans', 'Team1Inhibitors', 'Team2Inhibitors', 'Patch', 'LegacyPatch', 'MatchHistory', 'Gamename', 'N_GameInMatch', 'N_MatchInPage', 'VOD' }
	local ret = {
		_table = 'ScoreboardGames',
	}
	for _, v in ipairs(fields) do
		ret[v] = game_data[v]
	end
	return ret
end
function h.getIdWiki(game_data)
	return ('%s_%s_%s_%s'):format(
		game_data.cargo.OverviewPage,
		util_scoreboard.getTabName(),
		util_vars.getGlobalIndex('sb_N_MatchInTab'),
		game_data.N_GameInMatch
	)
end
function h.getMatchId(game_data)
	return ('%s_%s_%s'):format(
		game_data.cargo.OverviewPage,
		util_scoreboard.getTabName(),
		util_vars.getGlobalIndex('sb_N_MatchInTab')
	)
end
function h.gatherPlayerCargoForGameFields(cargo, player_data)
	cargo.Team1Picks = h.gatherOnePlayerDatapointForGameFields(player_data[1], 'Champion')
	cargo.Team2Picks = h.gatherOnePlayerDatapointForGameFields(player_data[2], 'Champion')
	cargo.Team1Players = h.gatherOnePlayerDatapointForGameFields(player_data[1], 'Link')
	cargo.Team2Players = h.gatherOnePlayerDatapointForGameFields(player_data[2], 'Link')
end
function h.gatherOnePlayerDatapointForGameFields(team, datapoint)
	local tbl = {}
	for i, player in ipairs(team) do
		tbl[team.rolemapHash[i]] = tostring(player[datapoint])
	end
	return util_table.concat(tbl, ',')
end
-- get for one team
function h.getTeamCargo(game_data, player_data, teamIndex)
	-- table with each row a team
	-- this is a way better data structure than having Team1... etc
	-- and all queries should be updated to use this, then the game table have
	-- team specific stuff removed from it
	local ret = {
		_table = 'ScoreboardTeams',
		OverviewPage = game_data.cargo.OverviewPage,
		Side = TEAMS_DISPLAY[teamIndex],
		Number = teamIndex,
		UniqueGame = game_data.cargo.UniqueGame,
		MatchId = game_data.cargo.MatchId,
		GameId = game_data.cargo.GameId,
		UniqueTeam = game_data.cargo.UniqueGame .. '_Team' .. teamIndex,
		GameTeamId = game_data.cargo.UniqueGame .. '_Team' .. teamIndex,
	}
	util_table.merge(ret, h.getOneTeamFieldsFromGame(game_data.cargo, teamIndex))
	ret.IsWinner = game_data.WinTeam == ret.Team
	ret.StatsPage = util_title.concatSubpageParts(ret.Team, 'Statistics', util_vars.getVar('sbYear'))
	return ret
end
function h.getOneTeamFieldsFromGame(gameCargo, teamIndex)
	-- copy paste from the game data to one specific team
	-- this will need to be rewritten if we stop storing team-specific stuff in the game table
	-- but for now this was a quick way to implement this
	local ret = {}
	local fields = { '', 'Score', 'Bans', 'Picks', 'Players', 'Dragons', 'Barons', 'Towers', 'Gold', 'Kills', 'RiftHeralds', 'VoidGrubs', 'Atakhans', 'Inhibitors' }
	local field_aliases = {
		[''] = 'Team',
		Players = 'Roster',
	}
	for _, field in ipairs(fields) do
		ret[field_aliases[field] or field] = gameCargo['Team' .. teamIndex .. field]
	end
	return ret
end
-- get for individual player
function h.initPlayerCargoFromData(player_data)
	local fields = {
		'Link',
		'Kills',
		'Deaths',
		'Assists',
		'SummonerSpells',
		'Gold',
		'CS',
		'DamageToChampions',
		'VisionScore',
		'Champion',
		'Items',
		'Trinket',
		'IngameRole',
		'PrimaryTree',
		'SecondaryTree',
	}
	local ret = { _table = 'ScoreboardPlayers' }
	for _, v in ipairs(fields) do
		ret[v] = player_data[v]
	end
	ret.SummonerSpells = util_table.concat(player_data.SummonerSpells)
	return ret
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.GameId = game.GameId
	player.MatchId = game.MatchId
	player.PlayerWin = game.Winner == team
end
function h.addPlayerCargoRoleData(cargo, playerN)
	cargo.Role = Role(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
	if not player_data.PentakillVod then
		return { h.getOnePlayerPentakillCargo(nil, playerCargo) }
	end
	return util_map.split(player_data.PentakillVod, nil, h.getOnePlayerPentakillCargo, playerCargo)
end
function h.getOnePlayerPentakillCargo(vod, playerCargo)
	local pentakill = {
		_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 = vod
	}
	return pentakill
end

-- cargo
function p:storeCargo(args, player_data, game_data)
	util_cargo.store(game_data.cargo)
	for i, team in ipairs(player_data) do
		util_cargo.store(game_data.team_cargo[i])
		for _, player in ipairs(team) do
			util_cargo.store(player.cargo)
			util_map.rowsInPlace(player.cargoPentakills, util_cargo.storeAndAttach)
		end
	end
end
-- output
function p: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)
	self:printLine(tbl, game_data, 'th', self.printKey)
	self:printLine(tbl, player_data, 'td', self.printPlayers)
	h.printDateTimeAndLinks(tbl, game_data)
	self:printLine(tbl, game_data, 'td', self.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(util_scoreboard.TOGGLES.one, section, n)
	util_scoreboard.TOGGLES.row = util_scoreboard.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, util_scoreboard.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(util_scoreboard.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
	util_scoreboard.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 p:printLine(tbl, data, celltype, f)
	local tr = tbl:tag('tr')
		:addClass(util_scoreboard.TOGGLES.row)
	for i, side in ipairs(TEAMS) do
		local td = tr:tag(celltype)
			:attr('colspan',2)
			:addClass('side-' .. side)
		f(self, td, data, i)
	end
end
function p: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')
	util_scoreboard.div(outer, 'key-champion', 'Champ')
	util_scoreboard.div(outer, 'key-summoners', 'SS')
	if SEASON.runes then
		util_scoreboard.div(outer, 'key-runes', 'R')
	end
	local info = outer:tag('div'):addClass('sb-key-info')
	local stats = info:tag('div'):addClass('sb-key-stats')
	self:printStatsHeaders(stats)
	if SEASON.trinket then
		util_scoreboard.div(stats, 'key-trinket', 'T')
	end
	if PRINT_ITEMS then
		util_scoreboard.div(outer, 'key-items', 'Items')
	end
end
function p:printStatsHeaders(stats)
	util_scoreboard.div(stats, 'key-stat', 'KDA', 'kda')
	util_scoreboard.div(stats, 'key-stat', 'CS', 'cs')
	util_scoreboard.div(stats, 'key-stat', 'Gold', 'gold')
end
function p:printPlayers(td, player_data, i)
	local data = player_data[i]
	for _, row in ipairs(data) do
		self:printPlayer(td, row)
	end
end
function p:printPlayer(td, row)
	local outer = td:tag('div'):addClass('sb-p')
	util_scoreboard.div(outer, 'p-champion', row.Champion:image{size=60, nosize=true})
	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)
	self: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')
	util_scoreboard.div(ss, 'p-sum', s.SummonerSprite(spells[1]))
	util_scoreboard.div(ss, 'p-sum', s.SummonerSprite(spells[2]))
end
function h.printPlayerRunes(outer, row)
	local runes = outer:tag('div'):addClass('sb-p-runes')
	h.printPlayerKeystone(runes, row.Keystone or 'EmptySummoner')
	RunesReforged.display(runes, row.AllRunes)
	h.printPlayerRune(runes, row.SecondaryTree or 'EmptySummoner')
end
function h.printPlayerKeystone(runes_div, rune)
	util_scoreboard.div(
		runes_div,
		'p-rune',
		Keystone(rune):image({ size = 30 })
	)
end
function h.printPlayerRune(runes_div, rune)
	util_scoreboard.div(
		runes_div,
		'p-rune',
		('[[File:Rune %s.png|30px|link=]]'):format(rune)
	)
end
function h.printPlayerName(info, row)
	util_scoreboard.div(info, 'p-name', util_esports.playerLinked(row.Link, row.Name))
end
function h.printTrinketAndMastery(outer, row)
	local inner = outer:tag('div'):addClass('sb-p-masteryandtrinket')
	util_scoreboard.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)
	util_scoreboard.div(div, 'p-trinket', trinket:image({ size=30, nosize=true }))
end
function p:printPlayerStats(info, row)
	local stats = info:tag('div'):addClass('sb-p-stats')
	self:printPlayerKDA(stats, row)
	self:printPlayerCS(stats, row)
	self:printPlayerGold(stats, row)
	if SEASON.trinket and not SEASON.mastery then
		h.printTrinket(stats, row.Trinket)
	end
end
function p:printPlayerKDA(stats, row)
	util_scoreboard.div(stats, 'p-stat', util_esports.KDA(row.Kills or '', row.Deaths or '', row.Assists or ''), 'kda')
end
function p:printPlayerCS(stats, row)
	util_scoreboard.div(stats, 'p-stat', row.CS, 'cs')
end
function p:printPlayerGold(stats, row)
	util_scoreboard.div(stats, 'p-stat', util_esports.roundedGold(row.Gold), 'gold')
end
function h.printPlayerItems(outer, items)
	util_scoreboard.div(outer, 'p-items', items:images({ size=30, nosize=true }))
end
function h.printDateTimeAndLinks(tbl, game_data)
	local tr = tbl:tag('tr')
		:addClass(util_scoreboard.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)
	if game_data.version ~= 5 then
		h.printMH(div, game_data.MatchHistory)
	end
	h.printVOD(div, game_data.VOD)
	h.printStatsPopup(div, game_data)
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.printStatsPopup(div, game_data)
	if game_data.cargo == nil then return end
	local popupWrapper = div:tag("div")
		:addClass("sbes-popup-wrapper")
		:wikitext("Stat Graphs ")
	local popup = util_toggle.popupButtonLazy(popupWrapper, "sbes-lazyloaded")
	popup.button:attr("data-parse-text", "ScoreboardExtraStats|id=" .. game_data.cargo.GameId)
	popup.tbl:addClass('sb-runes-popup-container')
	popup.wrapper:addClass('sbes-wrapper')
	popup.inner:addClass('sbes-inner')
	return popup.tbl
end
function h.printDateTimeItem(div, class, str, title)
	div:tag('div')
		:addClass('sb-datetime-' .. class)
		:wikitext(str)
		:attr('title', title)
end
function p: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')
	util_scoreboard.div(bans, 'footer-ban-header', nil)
	util_scoreboard.div(bans, 'footer-bans', game_data[key .. 'Bans']:images{size=30, nosize=true})
	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(util_scoreboard.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(util_scoreboard.TOGGLES.row)
		:attr('colspan', 4)
		:wikitext(args.reason or i18n.print('no_reason'))
end
return p