Module:ScoreboardPlayerStats

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.

For the query, see Special:RunQuery/TournamentStatistics


local util_args = require('Module:ArgsUtil')
local util_cargo = require('Module:CargoUtil')
local util_esports = require('Module:EsportsUtil')
local util_form = require('Module:FormUtil')
local util_game = require('Module:GameUtil')
local util_html = require('Module:HtmlUtil')
local util_map = require('Module:MapUtil')
local util_math = require('Module:MathUtil')
local util_sort = require('Module:SortUtil')
local util_stats = require('Module:StatsUtil')
local util_table = require('Module:TableUtil')
local util_text = require('Module:TextUtil')
local util_title = require('Module:TitleUtil')
local util_vars = require('Module:VarsUtil')
local i18n = require('Module:i18nUtil')
local Sprite = require('Module:Sprite').sprite
local m_champion = require('Module:Champion')
local m_region = require('Module:Region')
local m_role = require('Module:Role')
local m_team = require('Module:Team')
local MostRecentGameIncluded = require('Module:MostRecentGameIncluded').main
local SETTINGS = require('Module:ScoreboardPlayerStats/Settings')
local PRELOAD
local MAX_SUBJECT_TEAMS = 5
local SHOWN_ITEMS_PER_LIST = { Champion = 3, Role = 5 }
local Class = require('Module:Class').Class

local s = {}
function s.ChampionSprite(id, link)
	return Sprite{
		id,
		size = 25,
		type = 'Champion',
		--nosize = true,
		notext = true,
		link = link,
	}
end
function s.RoleSprite(id, link)
	return Sprite{
		id,
		size = 20,
		type = 'Role',
		--nosize = true,
		notext = true,
		link = link,
	}
end
local h = {}
local p = Class()
h.GAMES_ALREADY_INCLUDED = {}
function p.main(frame)
	local args = util_args.merge()
	return p:_main(args)
end
function p:_main(args)
	i18n.init('ScoreboardPlayerStats')
	h.GAMES_ALREADY_INCLUDED = {}
	h.setPreloadAndValidate(args)
	local dataFromQuery = self:getData(args)
	local output = mw.html.create('div'):addClass('wide-content-scroll')
	return MostRecentGameIncluded(args, self.query),
		self:printOutputFromData(output, dataFromQuery, args)
end
function p:printOutputFromData(output, dataFromQuery, args)
	i18n.init('ScoreboardPlayerStats')
	h.GAMES_ALREADY_INCLUDED = {}
	h.setPreloadAndValidate(args)
	local dataCounted = h.countData(dataFromQuery)
	h.formatData(dataCounted, args)
	h.printOutput(output, dataCounted, PRELOAD.headings, args)
	local tournamentList = h.makeIncludedTournaments(dataCounted.total.lists.Tournament, args)
	local permalink = h.makePermalink(args)
	return output, tournamentList, permalink
end
function p:CPTMH(output, dataFromQuery, args)
	-- this only exists because it used to exist. it's kind of gross. if maintaining this makes your life impossibly hard just kill it.
	-- used by MatchHistoryPlayer
	i18n.init('ScoreboardPlayerStats')
	PRELOAD = SETTINGS.preloads.cptmh
	local cptmhArgs = h.getSetOfArgsForCPTMH(args)
	h.printLabels(output, PRELOAD.headings)
	for _, thisArgs in ipairs(cptmhArgs) do
		local thisData = self:getData(thisArgs)
		local dataCounted = {}
		h.countRowsInTotal(dataCounted, thisData)
		dataCounted.total = h.formatTotalsAsCPTMH(dataCounted.total, thisArgs)
		h.printTotalLine(output, dataCounted.total, PRELOAD.headings)
	end
end
function h.getSetOfArgsForCPTMH(args)
	local argsChampionOnly = mw.clone(args)
	local argsPlayerOnly = mw.clone(args)
	argsChampionOnly.link = nil
	argsPlayerOnly.champion = nil
	args.label = i18n.print('cptmh_total')
	argsChampionOnly.label = i18n.print('cptmh_total_champion', m_champion.rightlong(args.champion))
	argsPlayerOnly.label = i18n.print('cptmh_total_player', util_text.intLinkOrText(args.link))
	return { args, argsChampionOnly, argsPlayerOnly }
end
function h.setPreloadAndValidate(args)
	PRELOAD = h.getPreload(args.preload)
	util_stats.validatePreload(args, PRELOAD)
end
function h.getPreload(str)
	local preload = SETTINGS.preloads[str:lower()]
	if not preload then
		error(i18n.print('error_invalidPreload'))
	elseif type(preload) == 'string' then
		preload = SETTINGS.preloads[preload]
	end
	return preload
end
-- get dataFromQuery / cargo
function p:getData(args)
	self.query = h.getGeneralQuery(args)
	util_vars.log(self.query.where)
	util_vars.log(self.query.tables)
	util_vars.log(self.query.join)
	local dataFromQuery = util_cargo.queryAndCast(self.query)
	if PRELOAD.bans then
		dataFromQuery.bans = util_cargo.queryAndCast(h.getBanQuery(args))
	end
	return dataFromQuery
end
function h.getGeneralQuery(args)
	return {
		tables = h.getGeneralTables(args),
		join = h.getGeneralJoin(args),
		fields = h.getGeneralFields(args),
		where = h.getGeneralWhere(args),
		limit = args.limit or 9999,
		types = h.getGeneralTypes(args),
		orderBy = 'SG.DateTime_UTC ASC',
	}
end
function h.getGeneralTables(args)
	local ret = {
		'ScoreboardGame=SG',
		'Tournaments=T',
		'ScoreboardPlayer=SP',
		'ScoreboardPlayer=SPVs',
		'PlayerRedirects=PR',
		'TeamRedirects=TRed',
	}
	return ret
end
function h.getGeneralJoin(args)
	local ret = {
		'SG.UniqueGame=SP.UniqueGame',
		'T.OverviewPage=SG.OverviewPage',
		'SP.UniqueLineVs=SPVs.UniqueLine',
		'SP.Link=PR.AllName',
		'SP.Team=TRed.AllName',
	}
	return ret
end
function h.getGeneralFields(args)
	local ret = {
		'COALESCE(PR._pageName, SP.Link)=Link',
		'SP.Name=Name',
		'SP.Team=Team',
		'SP.Champion=Champion',
		'SP.Kills=K',
		'SP.Deaths=D',
		'SP.Assists=A',
		'SP.Gold=G',
		'SP.CS=CS',
		'SP.Role=Role',
		'SP.TeamKills=TeamKills',
		'SP.TeamGold=TeamGold',
		'T.StandardName=Tournament',
		'SP.PlayerWin=PlayerWin',
		'SP.UniqueGame=UniqueGame',
		'SG.Gamelength_Number=Len',
		'T.Region=Region',
		'T._pageName=_pageName',
	}
	return ret
end
function h.getGeneralTypes(args)
	local ret = {
		K = 'number',
		D = 'number',
		A = 'number',
		G = 'number',
		CS = 'number',
		PlayerWin = 'boolean',
		Len = 'number',
	}
	return ret
end
function h.getGeneralWhere(args)
	local tbl = {
		'SP._pageName IS NOT NULL',
		util_cargo.whereFromArg('(%s)', args.where),
		util_cargo.whereFromArgList('SP.OverviewPage="%s"', args.tournament, nil, util_title.target),
		h.getLinkWhereCondition(args.link),
		util_cargo.whereFromArgList('TRed._pageName="%s"', args.team,  nil, m_team.teamlinkname),
		util_cargo.whereFromArgList('SP.Champion="%s"', args.champion, nil, m_champion.championname),
		util_cargo.whereFromArgList('SP.Role="%s"', args.role, nil, m_role.storename),
		util_cargo.whereFromArgList('T.Year="%s"', args.year),
		util_cargo.whereFromArgList('T.TournamentLevel="%s"', args.tournamentlevel),
		util_cargo.whereFromArgList('T.Region="%s"', args.region, nil, m_region.long),
		util_cargo.whereFromArgList('SG.Patch="%s"', args.patch),
	}
	h.validateWhere(tbl)
	return util_cargo.concatWhere(tbl)
end
function h.getLinkWhereCondition(link)
	if not link then return nil end
	if mw.title.new(link, '').exists then
		return util_cargo.whereFromArgList('PR._pageName="%s"', link, nil, util_title.target)
	else
		return util_cargo.whereFromArgList('SP.Link="%s"', link, nil, util_title.target)
	end
end
function h.validateWhere(tbl)
	local total = 0
	for _, v in pairs(tbl) do
		if v then total = total + 1 end
	end
	if total == 1 then error(i18n.print('error_NoWhere')) end
end
function h.getBanQuery(args)
	return {
		tables = { 'ScoreboardGame=SG', 'Tournaments=T' },
		join = 'SG.OverviewPage=T.OverviewPage',
		fields = h.getBanFields(args),
		where = h.getBanWhere(args),
		limit = args.limit or 9999
	}
end
function h.getBanFields(args)
	local ret = {
		'SG.Team1Bans__full=Team1Bans',
		'SG.Team2Bans__full=Team2Bans',
		'SG.UniqueGame=UniqueGame',
		'T.Region=Region',
		'T.StandardName=Tournament',
	}
	return ret
end
function h.getBanWhere(args)
	if args.role or args.link then
		error(i18n.print('invalidBanArg'))
	end
	local tbl = {
		'SG._pageName IS NOT NULL',
		util_cargo.whereFromArg('(%s)', args.where),
		util_cargo.whereFromArgList('SG.OverviewPage="%s"', args.tournament, nil, util_title.target),
		util_cargo.whereFromArgList('T.Year="%s"', args.year),
		util_cargo.whereFromArgList('T.TournamentLevel="%s"', args.tournamentlevel),
		util_cargo.whereFromArgList('T.Region="%s"', args.region, nil, m_region.long),
		util_cargo.whereFromArgList('SG.Patch="%s"', args.patch),
		h.getChampionBanWhere(args.champion),
	}
	return util_cargo.concatWhere(tbl)
end
function h.getChampionBanWhere(champion)
	if not champion then return nil end
	local tbl = {
		util_cargo.fakeHolds('SG.Team1Bans', champion),
		util_cargo.fakeHolds('SG.Team2Bans', champion),
	}
	return util_cargo.concatWhereOr(tbl)
end
-- count data
function h.countData(dataFromQuery)
	local dataCounted = {}
	h.countRowsByGroup(dataCounted, dataFromQuery)
	h.countRowsInTotal(dataCounted, dataFromQuery)
	dataCounted.playerLinks = h.getPlayerLinks(dataFromQuery)
	if PRELOAD.bans then
		h.addBanDataToData(dataCounted, dataFromQuery.bans)
	end
	if PRELOAD.count_bans_by_group then
		h.addBanCountsToGames(dataCounted.total.lists.Tournament, dataFromQuery.bans)
	end
	dataCounted.count = h.getCounts(dataFromQuery, dataCounted)
	return dataCounted
end
function h.countRowsByGroup(dataCounted, dataFromQuery)
	for _, row in ipairs(dataFromQuery) do
		h.countRow(dataCounted, row)
	end
end
function h.countRowsInTotal(dataCounted, dataFromQuery)
	dataCounted.total = h.getTableOfZeroes()
	for _, row in ipairs(dataFromQuery) do
		h.updateCountsFromDataRow(dataCounted.total, row)
		h.GAMES_ALREADY_INCLUDED[row.UniqueGame] = true
	end
end
function h.countRow(dataCounted, row)
	h.verifyRow(row)
	h.initRowCount(dataCounted, row[PRELOAD.groupby])
	h.updateCountsFromDataRow(dataCounted[row[PRELOAD.groupby]], row)
end
function h.verifyRow(row)
	if not row[PRELOAD.groupby] then
		error(i18n.print('error_missing_key', PRELOAD.groupby, row._pageName))
	end
end
function h.initRowCount(dataCounted, groupName)
	if dataCounted[groupName] then return end
	dataCounted[#dataCounted+1] = groupName
	dataCounted[groupName] = h.getTableOfZeroes(groupName)
end
function h.getTableOfZeroes(groupName)
	return {
		Group = groupName,
		Games = 0,
		Contests = 0,
		Wins = 0,
		K = 0,
		D = 0,
		A = 0,
		CS = 0,
		G = 0,
		Len = 0,
		TeamKills = 0,
		TeamGold = 0,
		lists = h.initRowCountLists()
	}
end
function h.initRowCountLists()
	local ret = {}
	for _, v in ipairs(SETTINGS.lists) do
		ret[v] = {}
	end
	return ret
end
function h.updateCountsFromDataRow(countedData, row)
	h.incrementGames(countedData, h.getWinValue(row.PlayerWin))
	h.updateRunningTotals(countedData, row)
	h.updateLists(countedData.lists, row)
end
function h.incrementGames(dataRow, winValue)
	dataRow.Games = dataRow.Games + 1
	dataRow.Contests = dataRow.Contests + 1
	dataRow.Wins = dataRow.Wins + winValue
end
function h.getWinValue(playerWin)
	return playerWin and 1 or 0
end
function h.updateRunningTotals(groupedData, row)
	for _, v in ipairs({'K', 'D', 'A', 'CS', 'G', 'Len', 'TeamKills', 'TeamGold'}) do
		if groupedData[v] and row[v] then
			groupedData[v] = groupedData[v] + row[v]
		else
			groupedData[v] = nil
		end
	end
	groupedData.Team = row.Team
	groupedData.Region = row.Region
	groupedData.Tournament = row.Tournament
end
function h.updateLists(lists, row)
	if h.GAMES_ALREADY_INCLUDED[row.UniqueGame] then return end
	for _, v in ipairs(SETTINGS.lists) do
		h.updateOneList(lists[v], row[v])
	end
end
function h.updateOneList(list, val)
	if not val then return end
	h.initRowGroupCount(list, val)
	list[val] = list[val] + 1
end
function h.initRowGroupCount(keyList, rowValue)
	if not keyList[rowValue] then
		keyList[#keyList+1] = rowValue
		keyList[rowValue] = 0
	end
end
function h.addBanCountsToGames(list, banDataFromQuery)
	for _, row in ipairs(banDataFromQuery) do
		if not h.GAMES_ALREADY_INCLUDED[row.UniqueGame] then
			h.updateOneList(list, row.Tournament)
		end
		h.GAMES_ALREADY_INCLUDED[row.UniqueGame] = true
	end
end
function h.getCounts(dataFromQuery, dataCounted)
	return {
		lines = #dataCounted,
		games = util_table.generalLength(h.GAMES_ALREADY_INCLUDED),
		cargo_results = #dataFromQuery,
	}
end
function h.getPlayerLinks(dataFromQuery)
	local ret = {}
	for _, row in ipairs(dataFromQuery) do
		if row.Link then
			ret[row.Link] = util_text.intLink(row.Link, row.Name)
		end
	end
	return ret
end
function h.addBanDataToData(dataCounted, banDataFromQuery)
	local bansByCriteria = h.countBanData(banDataFromQuery)
	bansByCriteria.champion["Loss of Ban"] = nil
	bansByCriteria.champion["None"] = nil
	h.addCountedBanDataToData(dataCounted, bansByCriteria)
end
function h.countBanData(banDataFromQuery)
	if not banDataFromQuery then return {} end
	local bansByCriteria = {
		champion = {},
		group = {},
		groupConstants = {},
	}
	for _, row in ipairs(banDataFromQuery) do
		h.incrementBansByChampion(bansByCriteria.champion, row.Team1Bans)
		h.incrementBansByChampion(bansByCriteria.champion, row.Team2Bans)
		h.incrementBansByGroup(bansByCriteria.group, row)
		h.setGroupConstants(bansByCriteria.groupConstants, row)
	end
	return bansByCriteria
end
function h.incrementBansByChampion(bansByChampion, teamBans)
	for _, champ in ipairs(util_text.split(teamBans)) do
		h.initializeAndIncrement(bansByChampion, champ)
	end
end
function h.initializeAndIncrement(arr, key)
	arr[key] = 1 + (arr[key] or 0)
end
function h.incrementBansByGroup(bansByGroup, row)
	if not PRELOAD.count_bans_by_group then return end
	h.initializeAndIncrement(bansByGroup, row[PRELOAD.groupby])
end
function h.setGroupConstants(groupConstants, row)
	util_table.initTable(groupConstants, row.Tournament, {
		Region = row.Region,
		Tournament = row.Tournament,
		Team = row.Team,
	})
end
function h.addCountedBanDataToData(dataCounted, bansByCriteria)
	if PRELOAD.count_bans_by_group then
		h.addCountedGroupBanDataToData(dataCounted, bansByCriteria.group, bansByCriteria)
	else
		h.addCountedChampionBanDataToData(dataCounted, bansByCriteria.champion)
	end
end
function h.addCountedGroupBanDataToData(dataCounted, groupBans, bansByCriteria)
	for groupName, numberOfBans in pairs(groupBans) do
		h.initRowCountOnlyBans(dataCounted, groupName)
		h.addBanTotalsToRow(dataCounted[groupName], numberOfBans)
		h.setGroupBanConstants(dataCounted[groupName], bansByCriteria.groupConstants[groupName])
	end
end
function h.addCountedChampionBanDataToData(dataCounted, championBans)
	for champion, numberOfBans in pairs(championBans) do
		h.initRowCountOnlyBans(dataCounted, champion)
		h.addBanTotalsToRow(dataCounted[champion], numberOfBans)
	end
end
function h.initRowCountOnlyBans(dataCounted, groupName)
	if dataCounted[groupName] then return end
	dataCounted[#dataCounted+1] = groupName
	dataCounted[groupName] = {
		Group = groupName,
		Games = 0,
		Contests = 0,
		Wins = 0,
		onlybans = true,
		lists = {
			Champion = {},
			Link = {},
			Role = {},
			Team = {},
			Region = {},
			Tournament = {}
		}
	}
end
function h.addBanTotalsToRow(row, bans)
	row.Bans = bans
	row.Contests = row.Contests + bans
end
function h.setGroupBanConstants(group, constants)
	group.Team = constants.Team
	group.Region = constants.Region
	group.Tournament = constants.Tournament
end
-- make things pretty and get totals for printing
function h.formatData(dataCounted, args)
	local format = util_args.castAsBool(args.shownet) and h.formatRowForNetDisplay or h.formatRow
	util_map.dictInPlace(dataCounted, format, dataCounted, args)
	util_sort.dictByKeys(dataCounted, PRELOAD.sort.fields, PRELOAD.sort.increasing)
	dataCounted.overall = h.formatTotalsAsOverall(dataCounted.total, dataCounted, args)
	dataCounted.total = h.formatTotalsAsTotals(dataCounted.total, args)
	dataCounted.totalGamesExtraInfo = h.getTotalGamesExtraInfo(dataCounted, args)
end
function h.formatRow(row, dataCounted, args)
	local newRow = {}
	row[PRELOAD.groupby] = row.Group
	h.copyConstants(row, newRow)
	newRow.ContestRate = util_math.percent(row.Contests / dataCounted.count.games, 0.1)
	if row.onlybans then
		h.addBanConstants(row, newRow)
	else
		h.calcPerGameTotals(row, newRow)
		newRow.GamesDisplay = h.gamesMatchHistoryQuery(row, args)
		newRow.KDA = util_esports.calculateKDA(row.K, row.D, row.A)
		newRow.CSPM = row.CS and row.Len and util_math.roundnum(row.CS / row.Len, .01)
		newRow.Tournament = util_stats.tournamentAndRegion(row)
		h.calcLossesAndWinrate(row, newRow)
		h.calcTeamKillTotals(row, newRow)
		h.calcGoldTotalsAsPerGame(row, newRow)
		h.calcSecondaryTotals(row, newRow, args)
	end
	h.formatForDisplay(newRow, dataCounted.playerLinks)
	return newRow
end
function h.formatRowForNetDisplay(row, dataCounted, args)
	local newRow = mw.clone(row)
	newRow[PRELOAD.groupby] = row.Group
	newRow.ContestRate = util_math.percent(row.Contests / dataCounted.count.games, 0.1)
	if row.onlybans then
		h.addBanConstants(row, newRow)
	else
		newRow.GamesDisplay = h.gamesMatchHistoryQuery(row, args)
		newRow.KDA = util_esports.calculateKDA(row.K, row.D, row.A)
		newRow.CSPM = row.CS and row.Len and util_math.roundnum(row.CS / row.Len, .01)
		newRow.Tournament = util_stats.tournamentAndRegion(row)
		h.calcLossesAndWinrate(row, newRow)
		h.calcTeamKillTotals(row, newRow)
		h.calcGoldTotalsAsTotals(row, newRow)
		h.calcSecondaryTotals(row, newRow, args)
	end
	h.formatForDisplay(newRow, dataCounted.playerLinks)
	return newRow
end
function h.formatTotalsAsOverall(row, dataCounted, args)
	local newRow = {}
	h.calcPerGameTotals(row, newRow)
	newRow.KDA = util_esports.calculateKDA(row.K, row.D, row.A)
	newRow.CSPM = row.CS and row.Len and util_math.roundnum(row.CS / row.Len, .01)
	h.calcTeamKillTotals(row, newRow)
	h.calcGoldTotalsAsPerGame(row, newRow)
	newRow[PRELOAD.groupby] = i18n.print('OverallLabel')
	return newRow
end
function h.formatTotalsAsTotals(row, args)
	local newRow = mw.clone(row)
	newRow.GamesDisplay = row.Games
	h.calcLossesAndWinrate(row, newRow)
	newRow[PRELOAD.groupby] = i18n.print('TotalLabel')
	newRow.G = util_esports.roundedGold(row.G)
	newRow.Team = nil
	return newRow
end
function h.formatTotalsAsCPTMH(row, args)
	local newRow = {}
	newRow.GamesDisplay = row.Games
	newRow.Wins = row.Wins
	h.calcLossesAndWinrate(row, newRow)
	h.calcPerGameTotals(row, newRow)
	newRow.KDA = util_esports.calculateKDA(row.K, row.D, row.A)
	newRow.CSPM = row.CS and row.Len and util_math.roundnum(row.CS / row.Len, .01)
	h.calcTeamKillTotals(row, newRow)
	h.calcGoldTotalsAsPerGame(row, newRow)
	newRow.Label = args.label
	return newRow
end
function h.copyConstants(row, newRow)
	for _, v in ipairs({ 'Link', 'Champion', 'Team', 'Tournament', 'Contests', 'Games', 'Wins', 'Bans' }) do
		newRow[v] = row[v]
	end
end
function h.calcPerGameTotals(row, newRow)
	for _, v in ipairs({ 'K', 'D', 'A', 'CS' }) do
		if row[v] then
			newRow[v] = util_math.roundnum(row[v] / row.Games, 0.01)
		end
	end
end
function h.gamesMatchHistoryQuery(row, args)
	local formArgs = util_stats.initFormArgs(args)
	formArgs.preload = PRELOAD.mh_preload
	formArgs[PRELOAD.groupby:lower()] = row.Group
	local link = util_form.fullURL(SETTINGS.form_info_mh, formArgs)
	return util_text.extLink(link, row.Games)
end
function h.calcLossesAndWinrate(row, newRow)
	newRow.Losses = row.Games - row.Wins
	newRow.Winrate = util_math.percent(row.Wins / row.Games, 0.1)
end
function h.calcTeamKillTotals(row, newRow)
	if not row.TeamKills then return end
	newRow.KPAR = util_math.percent((row.K + row.A) / row.TeamKills, .1)
	newRow.KS = util_math.percent(row.K / row.TeamKills, .1)
end
function h.calcGoldTotalsAsPerGame(row, newRow)
	if not row.G then return end
	newRow.G = h.makeGoldSpan(row)
	newRow.GPM = row.Len and util_math.roundnum(row.G / row.Len, 1)
	newRow.GS = row.TeamGold and util_math.percent(row.G / row.TeamGold, .1)
end
function h.makeGoldSpan(row)
	local output = mw.html.create('span')
		:addClass('gold-as-thousands')
		:wikitext(util_math.roundnum(row.G / (1000 * row.Games), .1))
	return tostring(output)
end
function h.calcGoldTotalsAsTotals(row, newRow)
	if not row.G then return end
	newRow.G = util_esports.roundedGold(row.G)
	newRow.GPM = row.Len and util_math.roundnum(row.G / row.Len, 1)
	newRow.GS = row.TeamGold and util_math.percent(row.G / row.TeamGold, .1)
end
function h.calcSecondaryTotals(row, newRow, args)
	-- only support champion lists until the OO formatting is done
	for k, v in pairs(row.lists) do
		local extraFormArgs = h.totalsExtraFormArgs(row.Group)
		newRow[k .. 'N'] = util_text.extLinkOrText(h.getSecondaryForm(k, 'N', args, extraFormArgs), #v)
	end
	newRow.ChampionList = h.getSecondaryTotalsList('Champion', row, args)
	newRow.RoleList = h.getSecondaryTotalsList('Role', row, args)
end
function h.totalsExtraFormArgs(restriction)
	return { [PRELOAD.groupby:lower()] = restriction }
end
function h.getSecondaryForm(k, preloadSuffix, args, extraArgs)
	local formPreload = PRELOAD.outlinks and PRELOAD.outlinks[k .. preloadSuffix]
	if not formPreload then return end
	local formArgs = util_stats.initFormArgs(args)
	util_table.merge(formArgs, formPreload.args)
	util_table.merge(formArgs, extraArgs)
	return util_form.fullURL(formPreload.form or SETTINGS.form_info, formArgs)
end
function h.getSecondaryTotalsList(key, row, args)
	util_sort.sortConstantDictionary(row.lists[key])
	local function getFormLink(str)
		local extraFormArgs = h.listExtraFormArgs(row.Group, key, str)
		local link = h.getSecondaryForm(key, 'List', args, extraFormArgs)
		return h.pickSecondaryMarkupByType(key, str, link)
	end
	return util_table.concat(util_table.slice(row.lists[key], 1, SHOWN_ITEMS_PER_LIST[key]), '', getFormLink)
end
function h.pickSecondaryMarkupByType(key, str, link)
	if key == 'Champion' then
		return s.ChampionSprite(str, link)
	elseif key == 'Role' then
		return s.RoleSprite(str, link)
	end
end
function h.listExtraFormArgs(restriction, key, str)
	local ret =  {
		[PRELOAD.groupby:lower()] = restriction,
		[key:lower()] = str,
	}
	return ret
end
function h.addBanConstants(row, newRow)
	newRow.Wins = 0
	newRow.Losses = 0
	newRow.Tournament = util_stats.tournamentAndRegion(row)
end
function h.formatForDisplay(newRow, playerLinks)
	if PRELOAD.groupby == 'Champion' then
		newRow.Champion = m_champion.rightlong(newRow.Champion)
	elseif PRELOAD.groupby == 'Link' then
		newRow.Team = m_team.onlyimagelinked(newRow.Team)
		newRow.Link = playerLinks[newRow.Link]
	end
end
function h.getTotalGamesExtraInfo(dataCounted, args)
	if PRELOAD.subject == 'Player' then
		return i18n.print('listOfPlayerTeams', h.subjectTeams(dataCounted.total.lists.Team, args))
	elseif PRELOAD.subject == 'Champion' then
		return i18n.print('listOfChampionRoles', h.subjectRoles(dataCounted.total.lists.Role, args))
	end
end
function h.subjectTeams(list, args)
	local function formatTeam(team)
		local formArgs = util_stats.initFormArgs(args)
		formArgs.team = team
		local formLink = util_form.fullURL(SETTINGS.form_info, formArgs)
		return m_team.rightshort(team, {link = formLink})
	end
	return util_table.concat(util_table.slice(list, 1, MAX_SUBJECT_TEAMS), '', formatTeam) .. h.tooManyTeams(list)
end
function h.tooManyTeams(list)
	if #list <= MAX_SUBJECT_TEAMS then return '' end
	return '&ensp;' .. i18n.print('extraTeams', MAX_SUBJECT_TEAMS)
end
function h.subjectRoles(list, args)
	local function formatRole(role)
		local formArgs = util_stats.initFormArgs(args)
		formArgs.role = role
		local formLink = util_form.fullURL(SETTINGS.form_info, formArgs)
		return s.RoleSprite(role, formLink)
	end
	return util_table.concat(list, '', formatRole)
end
-- print output (html object was pre-created in case adding to a MH)
function h.printOutput(output, dataCounted, headings, args)
	h.printWarningsIfNeeded(output, dataCounted)
	local tbl = output:tag('table')
		:addClass('wikitable sortable spstats plainlinks hoverable-rows')
		:addClass(args.class)
	h.printHeadingCell(tbl, args, #headings, 'ScoreboardPlayerStats')
	h.printTotalGamesRow(tbl, dataCounted.count, #headings, dataCounted.totalGamesExtraInfo)
	h.printRoleLinksRow(tbl, #headings, args)
	h.printLabels(tbl, headings)
	--util_html.printEmptySortRow(tbl, #headings)
	h.printRows(tbl, dataCounted, headings)
	h.printTotals(tbl, dataCounted, headings, args)
end
function h.printWarningsIfNeeded(output, dataCounted)
	if dataCounted.count.cargo_results == 5000 then
		output:tag('div'):addClass('big'):wikitext(i18n.print('tooManyGames'))
	end
end
function h.printHeadingCell(tbl, args, colspan)
	local displayTbl = {
		util_stats.heading(args, 'ScoreboardPlayerStats'),
		i18n.print(util_args.castAsBool(args.shownet) and 'shownet' or 'showgame'),
		util_stats.openAsQueryLink(SETTINGS.form_info, args)
	}
	util_html.printColspanHeader(tbl, util_table.concat(displayTbl, ' - '), colspan)
end
function h.printTotalGamesRow(tbl, count, colspan, totalGamesExtraInfo)
	local tr = tbl:tag('tr')
	local tbl = {
		i18n.print(PRELOAD.type .. '_heading', count.games, count.lines) or i18n.print('heading'),
		totalGamesExtraInfo
	}
	tr:tag('th')
		:attr('colspan', colspan)
		:wikitext(util_table.concat(tbl, ' '))
end
function h.printRoleLinksRow(tbl, colspan, args)
	if not PRELOAD.rolelinks then return end
	local tr = util_html.innerColspanTable(tbl, colspan):tag('tr')
	local th = tr:tag('th')
	th:addClass('spstats-rolelinks-title')
		:wikitext(i18n.print('RoleRestrict'))
	h.printRoleLinkCells(tr, args)
end
function h.printRoleLinkCells(tr, args)
	for i = 1, util_game.players_per_team do
		local role = m_role.storename(tostring(i))
		tr:tag('td')
			:addClass('spstats-rolelinks-cell')
			:wikitext(util_text.extLink(h.getRoleFormLink(role, args), role))
	end
end
function h.getRoleFormLink(role, args)
	local formArgs = util_stats.initFormArgs(args)
	formArgs.role = role
	formArgs.preload = args.preload .. 'Role'
	return util_form.fullURL(SETTINGS.form_info, formArgs)
end
function h.printLabels(tbl, headings)
	local tr = tbl:tag('tr')
	for _, v in ipairs(headings) do
		local th = tr:tag('th')
			:wikitext(h.printI18n(v, PRELOAD.type))
		h.printLabelAttrs(th, v)
	end
end
function h.printLabelAttrs(th, v)
	if not SETTINGS.attrs then return end
	for attr, val in pairs(SETTINGS.attrs[v] or {}) do
		th:attr(attr, val)
	end
end
function h.printI18n(key)
	return i18n.print(PRELOAD.type .. '_' .. key) or i18n.print(key)
end
function h.printRows(tbl, dataCounted, headings)
	for _, key in ipairs(dataCounted) do
		local tr = tbl:tag('tr')
		h.printOneRow(tr, dataCounted[key], headings)
	end
end
function h.printOneRow(tr, row, headings)
	for _, col in ipairs(headings) do
		tr:tag('td')
			:wikitext(row[col] or '-')
			:addClass(h.getClass(col))
	end
end
function h.getClass(col)
	local class = SETTINGS.classes[col]
	if not class then return nil end
	return 'spstats-' .. class
end
function h.printTotals(tbl, dataCounted, headings, args)
	if not PRELOAD.totals then return end
	for _, line in ipairs({ 'overall', 'total' }) do
		h.printTotalLine(tbl, dataCounted[line], headings)
	end
end
function h.printTotalLine(tbl, row, headings)
	local tr = tbl:tag('tr')
	for _, col in ipairs(headings) do
		tr:tag('th')
			:wikitext(row[col] or '-')
	end
end
function h.makeIncludedTournaments(tournamentData, args)
	if util_args.castAsBool(args.hidetournamentlist) or PRELOAD.hidetournamentlist then return end
	local output = mw.html.create()
	if args.tournament then return end
	output:tag('span'):addClass('big'):wikitext(i18n.print('IncludedTournaments'))
	local ul = output:tag('ul')
	for _, v in ipairs(tournamentData) do
		ul:tag('li')
			:wikitext(util_text.intLinkOrText(v))
			:wikitext(' - ')
			:wikitext(i18n.print('NumberOfGames', tournamentData[v]))
	end
	return output
end
function h.makePermalink(args)
	if not util_args.castAsBool(args.spl) then return end
	local output = mw.html.create()
	output:tag('hr')
	util_form.permalink(output, args, SETTINGS.form_info)
	output:tag('hr')
	output:wikitext(i18n.print('debugInfo'))
	output:wikitext(h.getGeneralWhere(args))
	if PRELOAD.bans then
		output:wikitext('<br>', h.getBanWhere(args))
	end
	util_form.printLog(output)
	return output
end
return p