Follow us on Twitter @LiquipediaSC2 if you'd like to be kept up to date on all things StarCraft II!

Module:GroupTableLeague/new

From Liquipedia StarCraft 2 Wiki
Module documentation[view] [edit] [history] [purge]

This module generates Group Standings tables automatically, using information from LiquipediaDB

Parameters[edit]

|tournamentX=
Pagenames of tournament data (X is either blank or an integer)
|id=
ID of the MatchList that the data should only be queried from.
|sdate=
Start date for matches (inclusive)
|edate=
End date for matches (inclusive)
|pbgX=
Background colours for ranking X (X is an integer)
|playerX= |pX= // |teamX= |tX=
Players // Teams (X is an integer)
|opponentXalias=name1,name2,name3
Includes results from name1, name2 and name3 for playerX/teamX
|bgX=
Background colours associated with playerX/teamX
|temp_win_mX= |temp_tie_mX= |temp_lose_mX=
Additional series win/tie/loss awarded to playerX/teamX
|temp_win_gX= |temp_lose_gX=
Additional game win/loss awarded to playerX/teamX
|temp_pX=
Additional points awarded to playerX/teamX
|dqX=true
Disqualified parameter for playerX/teamX (sets player/team to last place and strike through player name)
|temp_tieX=
Additional hidden tiebreaker (can be used, for example, to resolve ties by additional matches that don't give any score).
Always used if specified and comes as the lowest priority tiebreaker.
|win_p=3 |tie_p=1 |lose_p=0
Amount of points awarded for each series win/tie/loss
|X-Y_p=0
Amount of points awarded for a series with score X-Y (X and Y are integers)
e.g |2-0_p=3 awards 3 points for a 2-0 win.
|countWinPoints=
If custom points per score are set the points per win (|win_p=), points per tie (|tie_p=) and points per loss (|lose_p=) are not counted unless they are set manually too or |countWinPoints=true is set.
|walkover_win=
Amount of game win/loss awarded to a player in case of walkover/forfeiture
|ties=true
Show ties
|show_p=true
Show points (also uses points as the main tiebreaker)
|show_g=false
Hide game win/loss (for solely Bo1 tournaments)
|diff=false
Hide game differences
|exclusive=false
Restricts to matches where either player is included by playerX/teamX (default is 'true' which restricts to matches where both players are included by playerX/teamX)
|type=
Switch the type from 1v1 to team (default is '1v1')
|ns=
Define from which namespace the data should be queried
0 = Main (this is the default)
2 = User
4 = Liquipedia
134 = Portal
136 = Data

Tiebreakers[edit]

|tiebreakerX=
Set the order of tiebreakers used, with X = 1 being the highest priority (order listed below is the default).
This sets the order priority for the whole table.
|tiebreakerX=points, |tiebreakerX=pts
Points calculated by win/ties/loss multiplied by point parameters
|tiebreakerX=series
Total series won followed by total series loss
|tiebreakerX=diff
Game difference calculated as games won - games loss
|tiebreakerX=h2h series
Head-to-head series won - series loss
|tiebreakerX=h2h games
Head-to-head games won - 0.01 * games loss
|tiebreakerX=games won
Total games won
|tiebreakerX=games loss
Total games loss
Also available, but not automatically used is the following:
|tiebreakerX=series%
Series win percentage calculated as series wins divided by played series.
|tiebreakerX=series diff
Series difference calculated as series won - series loss
|tiebreakerX=h2h games diff
Head-to-head game difference calculated as games won - games loss
|tiebreakerX=h2h games won
Head-to-head games won
|tiebreakerX=h2h games loss
Head-to-head games loss

Rounds[edit]

|roundtitle=Round
Title of the round. e.g. "Round 1", "Round 2", ...
|roundNedate=
End date of round N (inclusive). Automatically includes last date of matches if not added.
|roundNbgX=
Background colours associated with playerX for round N and onwards.
|roundNtemp_win_mX= |roundNtemp_tie_mX= |roundNtemp_lose_mX=
Additional series win/tie/loss awarded to playerX for round N and onwards.
|roundNtemp_win_gX= |roundNtemp_lose_gX=
Additional game win/loss awarded to playerX for round N and onwards.
|roundNtemp_pX=
Additional points awarded to playerX for round N and onwards.
|roundNdqX=true
Disqualified parameter for playerX (sets player to last place and strike through player name) for round N and onwards.
|roundNtemp_tieX=
Additional hidden tiebreaker (can be used, for example, to resolve ties by additional matches that don't give any score).
Always used if specified and comes as the lowest priority tiebreaker for round N and onwards.

Header[edit]

|title=
The first unnamed argument of the parameter list is used as the caption of the group table. Examples could be Group D or Week 3.
|width=
(optional) This argument specify the width of the table. The value can be any CSS width value.
|hide=
(optional)
|hide=false
makes table collapsible, shows it.
|hide=true
makes table collapsible, hides it.
|preview=
(Optional) Put a link to a preview here.
|lrthread=
(Optional) Put a link to the live report thread here.
|vod=
(Optional) Put a link to the VOD(s) here.
For secondary (tertiary, etc) VOD sources use vod2, vod3 etc.
|interview=
(Optional) Put a link to the interview here.
|interview2=
(Optional) Put a link to the interview here.
|interview3=
(Optional) Put a link to the interview here.
|interview4=
(Optional) Put a link to the interview here.
|recap=
(Optional) Put a link to the recap here.
|date=
(Optional) Put the date here. It will display between the title row and the first group slot.
|finished=
(Optional) Put in "true" if the group has already finished.
|stream=
(Optional) Put in the TL stream name or - if not on TL - the URL to the stream.
|location=
(Optional) Put in the location where the group is being played. For cities not added to {{Played in}} a globe icon with the city as mouseover text is displayed.

Copy/Paste[edit]

Simple 1v1[edit]

{{GroupTableLeague|title=|width=
|pbg1=up |pbg2=up |pbg3=down |pbg4=down
|bg1= |p1=
|bg2= |p2=
|bg3= |p3=
|bg4= |p4=
}}

1v1 with additional Parameters[edit]

{{GroupTableLeague|title=|width=
|tournament=
|roundtitle=
|date=
|sdate=
|edate=
|round1edate=
|twitch=
|win_p= |tie_p= |lose_p= 
|walkover_win= |ties= |show_p= |show_percentage= |show_g= |diff= |exclusive=
|tiebreaker1= |tiebreaker2= |tiebreaker3= 
|pbg1=up |pbg2=up |pbg3=down |pbg4=down
|bg1= |round1bg1= |p1= |temp_tie1= |p1alias=
|bg2= |round1bg2= |p2= |temp_tie2= |p2alias=
|bg3= |round1bg3= |p3= |temp_tie3= |p3alias=
|bg4= |round1bg4= |p4= |temp_tie4= |p4alias=
}}

Simple Team[edit]

{{GroupTableLeagueTeam|title=|width=
|pbg1=up |pbg2=up |pbg3=down |pbg4=down
|bg1= |t1=
|bg2= |t2=
|bg3= |t3=
|bg4= |t4=
}}

Team with additional Parameters[edit]

{{GroupTableLeague|title=|width=
|tournament=
|roundtitle=
|date=
|sdate=
|edate=
|round1edate=
|twitch=
|win_p= |tie_p= |lose_p= 
|walkover_win= |ties= |show_p= |show_percentage= |show_g= |diff= |exclusive=
|tiebreaker1= |tiebreaker2= |tiebreaker3= 
|pbg1=up |pbg2=up |pbg3=down |pbg4=down
|bg1= |round1bg1= |t1= |temp_tie1= |t1alias=
|bg2= |round1bg2= |t2= |temp_tie2= |t2alias=
|bg3= |round1bg3= |t3= |temp_tie3= |t3alias=
|bg4= |round1bg4= |t4= |temp_tie4= |t4alias=
}}

local Arguments = require('Module:Arguments')
local Array = require('Module:Array')
local ArrayExt = require('Module:Array/Ext')
local Countdown = require('Module:Countdown')
local DateExt = require('Module:Date/Ext')
local Flags = require('Module:Flags')
local Json = require('Module:Json')
local Logic = require('Module:Logic')
local Opponent = require('Module:Opponent')
local PageVariableNamespace = require('Module:PageVariableNamespace')
local Player = require('Module:Player')
local String = require('Module:StringUtils')
local Table = require('Module:Table')
local TeamTemplate = require('Module:TeamTemplate')
local TeamTemplates = require('Module:TeamTemplates')
local TournamentUtil = require('Module:Tournament/Util')

local globalVars = PageVariableNamespace()
local pageVars = PageVariableNamespace('GroupTableLeague')
local DISPLAY = {}
local aliaslist = {}

--[[
	GroupTableLeague for the new Bracket/Match System
	- supports both team and 1v1 matches (via switch |type=team )
	- supports queries from non main space name spaces via |ns=
		--> it needs the number of the name space the data should be queried from
				0 = Main (this is the default)
				2 = User
				4 = Liquipedia
				134 = Portal
				136 = Data
]]
local GroupTableLeague = {}

function GroupTableLeague.formatDate(date, format)
	local dateString = date or ''
	local timezone = String.split(
		String.split(dateString, 'data%-tz%=\"')[2] or '',
			'\"')[1] or String.split(
		String.split(dateString, 'data%-tz%=\'')[2] or '',
			'\'')[1] or ''
	local Date = String.explode(dateString, '<', 0):gsub('-', '')
	date = Date .. timezone
	date = mw.getContentLanguage():formatDate(format or 'r', date)

	return date
end

function GroupTableLeague.var(name, default)
	return (mw.ext.VariablesLua.var(name) ~= '') and mw.ext.VariablesLua.var(name) or default or ''
end

function GroupTableLeague.get_lpdb_results(args, tournaments, opponents, mode, id)
	local lpdb_conditions = '[[mode::' .. mode .. ']] AND '

	if id ~= '' then
		lpdb_conditions = lpdb_conditions .. '[[match2bracketid::' .. id .. ']]'
	else
		lpdb_conditions = lpdb_conditions .. '('

		if tournaments then
			for key, item in ipairs(tournaments) do
				if (key > 1) then
					lpdb_conditions = lpdb_conditions .. 'OR'
				end
				lpdb_conditions = lpdb_conditions .. '[[pagename::' .. item .. ']]'
			end
		else
			lpdb_conditions = lpdb_conditions .. '[[pagename::' .. args.tournament .. ']]'
		end
		lpdb_conditions = lpdb_conditions .. ') '
	end

	if args.user == 'true' then
		lpdb_conditions = '[[namespace::2]] AND ' .. lpdb_conditions
	elseif (tonumber(args.ns or 0) or 0) > 1 then
		lpdb_conditions = '[[namespace::' .. tonumber(args.ns) .. ']] AND ' .. lpdb_conditions
	end

	local opp_conditions = ''
	if type(opponents) == 'table' and opponents[1] then
		local opp_number = #opponents
		opp_conditions = opp_conditions .. 'AND ('
		for key = 1, opp_number - 1 do
			for key2 = key + 1, opp_number do
				if key > 1 or key2 > 2 then
					opp_conditions = opp_conditions .. ' OR '
				end
				opp_conditions = opp_conditions .. '[[opponent::' .. opponents[key] .. ']] AND [[opponent::' .. opponents[key2] .. ']]'
			end
		end
		opp_conditions = opp_conditions .. ') '
	end

	local date_condition = ''

	if (args.sdate) and args.sdate ~= '' then
		date_condition = date_condition .. 'AND ([[date::>' .. GroupTableLeague.formatDate(args.sdate) .. ']] OR [[date::' .. GroupTableLeague.formatDate(args.sdate) .. ']]) '
	end
	if (args.edate) and args.edate ~= '' then
		date_condition = date_condition .. 'AND ([[date::<' .. GroupTableLeague.formatDate(args.edate) .. ']] OR [[date::' .. GroupTableLeague.formatDate(args.edate) .. ']]) '
	end

	local output = mw.ext.LiquipediaDB.lpdb('match2', {
		limit = 1000,
		order = 'date asc',
		conditions = lpdb_conditions .. date_condition .. opp_conditions
	})

	return output, lpdb_conditions
end

local Score = {}
function Score.dq(opponent)
	return opponent.disqualified and 0 or 1
end

function Score.points(opponent)
	return opponent.tiebreaker*30 + opponent.points
end

function Score.diff(opponent)
	return opponent.tiebreaker*30 + opponent.diff
end

function Score.series(opponent)
	return opponent.tiebreaker*30 + opponent.series.won - 0.1 * opponent.series.loss
end

function Score.gamesWon(opponent)
	return opponent.tiebreaker*30 + opponent.games.won
end

function Score.gamesLoss(opponent)
	return opponent.tiebreaker*30 - 0.1 * opponent.games.loss
end

function Score.seriesPercent(opponent)
	-- TODO
end

function Score.seriesDiff(opponent)
	-- TODO
end

function Score.tempTies(opponent)
	return opponent.tiebreaker*30 + (opponent.temp.tiebreaker or 0)
end

local StatefulScore = {}
function StatefulScore.h2hSeries(results, start_index, end_index, lpdb_conditions)
	for i = start_index, end_index do
		results[i].tiebreaker = results[i].tiebreaker*30
	end
	for opponent1_index = start_index, end_index-1 do
		for opponent2_index = opponent1_index+1, end_index do
			-- opponent1 vs opponent2 / opponent2 vs opponent1
			local opp1 = GroupTableLeague.getH2HOppCond(results, opponent1_index)
			local opp2 = GroupTableLeague.getH2HOppCond(results, opponent2_index)
			local cond = ' AND ([[opponent::' .. table.concat(opp1, ']] OR [[opponent::') .. ']]) AND ([[opponent::' .. table.concat(opp2, ']] OR [[opponent::') .. ']])'
			local match = mw.ext.LiquipediaDB.lpdb('match2', {
				limit = 1000,
				order = 'date asc',
				conditions = lpdb_conditions .. cond
			})
			for _, item in ipairs(match) do
				if Table.includes(opp1, item.match2opponents[1].name) then
					if item.winner == '1' then
						results[opponent1_index].tiebreaker = results[opponent1_index].tiebreaker + 1
						results[opponent2_index].tiebreaker = results[opponent2_index].tiebreaker - 1
					elseif item.winner == '2' then
						results[opponent2_index].tiebreaker = results[opponent2_index].tiebreaker + 1
						results[opponent1_index].tiebreaker = results[opponent1_index].tiebreaker - 1
					end
				else
					if item.winner == '2' then
						results[opponent1_index].tiebreaker = results[opponent1_index].tiebreaker + 1
						results[opponent2_index].tiebreaker = results[opponent2_index].tiebreaker - 1
					elseif item.winner == '1' then
						results[opponent2_index].tiebreaker = results[opponent2_index].tiebreaker + 1
						results[opponent1_index].tiebreaker = results[opponent1_index].tiebreaker - 1
					end
				end
			end
		end
	end
end

function StatefulScore.h2hGames(results, start_index, end_index, lpdb_conditions)
	for i = start_index, end_index do
		results[i].tiebreaker = results[i].tiebreaker*30
	end
	for opponent1_index = start_index, end_index-1 do
		for opponent2_index = opponent1_index+1, end_index do
			-- opponent1 vs opponent2 / opponent2 vs opponent1
			local opp1 = GroupTableLeague.getH2HOppCond(results, opponent1_index)
			local opp2 = GroupTableLeague.getH2HOppCond(results, opponent2_index)
			local cond = ' AND ([[opponent::' .. table.concat(opp1, ']] OR [[opponent::') .. ']]) AND ([[opponent::' .. table.concat(opp2, ']] OR [[opponent::') .. ']])'
			local match = mw.ext.LiquipediaDB.lpdb('match2', {
				limit = 1000,
				order = 'date asc',
				conditions = lpdb_conditions .. cond
			})
			for _, item in ipairs(match) do
				if item.match2opponents[1].score ~= -1 and item.match2opponents[2].score ~= -1 then
					if Table.includes(opp1, item.match2opponents[1].name) then
						results[opponent1_index].tiebreaker = results[opponent1_index].tiebreaker + tonumber(item.match2opponents[1].score) - 0.01 * tonumber(item.match2opponents[2].score)
						results[opponent2_index].tiebreaker = results[opponent2_index].tiebreaker + tonumber(item.match2opponents[2].score) - 0.01 * tonumber(item.match2opponents[1].score)
					else
						results[opponent1_index].tiebreaker = results[opponent1_index].tiebreaker + tonumber(item.match2opponents[2].score) - 0.01 * tonumber(item.match2opponents[1].score)
						results[opponent2_index].tiebreaker = results[opponent2_index].tiebreaker + tonumber(item.match2opponents[1].score) - 0.01 * tonumber(item.match2opponents[2].score)
					end
				end
			end
		end
	end
end

function StatefulScore.h2hGamesDiff(results, start_index, end_index, lpdb_conditions)
	for i = start_index, end_index do
		results[i].tiebreaker = results[i].tiebreaker*30
	end
	for opponent1_index = start_index, end_index-1 do
		for opponent2_index = opponent1_index+1, end_index do
			-- opponent1 vs opponent2 / opponent2 vs opponent1
			local opp1 = GroupTableLeague.getH2HOppCond(results, opponent1_index)
			local opp2 = GroupTableLeague.getH2HOppCond(results, opponent2_index)
			local cond = ' AND ([[opponent::' .. table.concat(opp1, ']] OR [[opponent::') .. ']]) AND ([[opponent::' .. table.concat(opp2, ']] OR [[opponent::') .. ']])'
			local match = mw.ext.LiquipediaDB.lpdb('match2', {
				limit = 1000,
				order = 'date asc',
				conditions = lpdb_conditions .. cond
			})
			for _, item in ipairs(match) do
				if item.match2opponents[1].score ~= -1 and item.match2opponents[2].score ~= -1 then
					if Table.includes(opp1, item.match2opponents[1].name) then
						results[opponent1_index].tiebreaker = results[opponent1_index].tiebreaker + tonumber(item.match2opponents[1].score) - tonumber(item.match2opponents[2].score)
						results[opponent2_index].tiebreaker = results[opponent2_index].tiebreaker + tonumber(item.match2opponents[2].score) - tonumber(item.match2opponents[1].score)
					else
						results[opponent1_index].tiebreaker = results[opponent1_index].tiebreaker + tonumber(item.match2opponents[2].score) - tonumber(item.match2opponents[1].score)
						results[opponent2_index].tiebreaker = results[opponent2_index].tiebreaker + tonumber(item.match2opponents[1].score) - tonumber(item.match2opponents[2].score)
					end
				end
			end
		end
	end
end

function StatefulScore.h2hGamesWon(results, start_index, end_index, lpdb_conditions)
	for i = start_index, end_index do
		results[i].tiebreaker = results[i].tiebreaker*30
	end
	for opponent1_index = start_index, end_index-1 do
		for opponent2_index = opponent1_index+1, end_index do
			-- opponent1 vs opponent2 / opponent2 vs opponent1
			local opp1 = GroupTableLeague.getH2HOppCond(results, opponent1_index)
			local opp2 = GroupTableLeague.getH2HOppCond(results, opponent2_index)
			local cond = ' AND ([[opponent::' .. table.concat(opp1, ']] OR [[opponent::') .. ']]) AND ([[opponent::' .. table.concat(opp2, ']] OR [[opponent::') .. ']])'
			local match = mw.ext.LiquipediaDB.lpdb('match2', {
				limit = 1000,
				order = 'date asc',
				conditions = lpdb_conditions .. cond
			})
			for _, item in ipairs(match) do
				if item.match2opponents[1].score ~= -1 and item.match2opponents[2].score ~= -1 then
					if Table.includes(opp1, item.match2opponents[1].name) then
						results[opponent1_index].tiebreaker = results[opponent1_index].tiebreaker + tonumber(item.match2opponents[1].score)
						results[opponent2_index].tiebreaker = results[opponent2_index].tiebreaker + tonumber(item.match2opponents[2].score)
					else
						results[opponent1_index].tiebreaker = results[opponent1_index].tiebreaker + tonumber(item.match2opponents[2].score)
						results[opponent2_index].tiebreaker = results[opponent2_index].tiebreaker + tonumber(item.match2opponents[1].score)
					end
				end
			end
		end
	end
end

function StatefulScore.h2hGamesLoss(results, start_index, end_index, lpdb_conditions)
	for i = start_index, end_index do
		results[i].tiebreaker = results[i].tiebreaker*30
	end
	for opponent1_index = start_index, end_index-1 do
		for opponent2_index = opponent1_index+1, end_index do
			-- opponent1 vs opponent2 / opponent2 vs opponent1
			local opp1 = GroupTableLeague.getH2HOppCond(results, opponent1_index)
			local opp2 = GroupTableLeague.getH2HOppCond(results, opponent2_index)
			local cond = ' AND ([[opponent::' .. table.concat(opp1, ']] OR [[opponent::') .. ']]) AND ([[opponent::' .. table.concat(opp2, ']] OR [[opponent::') .. ']])'
			local match = mw.ext.LiquipediaDB.lpdb('match2', {
				limit = 1000,
				order = 'date asc',
				conditions = lpdb_conditions .. cond
			})
			for _, item in ipairs(match) do
				if item.match2opponents[1].score ~= -1 and item.match2opponents[2].score ~= -1 then
					if Table.includes(opp1, item.match2opponents[1].name) then
						results[opponent1_index].tiebreaker = results[opponent1_index].tiebreaker - tonumber(item.match2opponents[2].score)
						results[opponent2_index].tiebreaker = results[opponent2_index].tiebreaker - tonumber(item.match2opponents[1].score)
					else
						results[opponent1_index].tiebreaker = results[opponent1_index].tiebreaker - tonumber(item.match2opponents[1].score)
						results[opponent2_index].tiebreaker = results[opponent2_index].tiebreaker - tonumber(item.match2opponents[2].score)
					end
				end
			end
		end
	end
end

function GroupTableLeague.readScoreSpec(args)
	local spec = {}
	for i = 1, 10 do
		local key = args['tiebreaker' .. tostring(i)] or ''
		if key == 'points' or key == 'pts' then
			table.insert(spec, 'points')
		elseif key == 'series' then
			table.insert(spec, 'series')
		elseif key == 'diff' then
			table.insert(spec, 'diff')
		elseif key == 'games won' then
			table.insert(spec, 'gamesWon')
		elseif key == 'games loss' then
			table.insert(spec, 'gamesLoss')
		elseif key == 'h2h series' or key == 'head-to-head series' or key == 'head to head series' then
			table.insert(spec, 'h2hSeries')
		elseif key == 'h2h games' or key == 'head-to-head games' or key == 'head to head games' then
			table.insert(spec, 'h2hGames')
		elseif key == 'h2h games diff' or key == 'head-to-head games' or key == 'head to head games' then
			table.insert(spec, 'h2hGamesDiff')
		elseif key == 'h2h games won' or key == 'head-to-head games won' or key == 'head to head games won' then
			table.insert(spec, 'h2hGamesWon')
		elseif key == 'h2h games loss' or key == 'head-to-head games loss' or key == 'head to head games loss' then
			table.insert(spec, 'h2hGamesLoss')
		elseif key == 'series percentage' or key == 'series%' or key == 'series-percentage' then
			table.insert(spec, 'seriesPercent')
		elseif key == 'series diff' then
			table.insert(spec, 'seriesDiff')
		end
	end

	if #spec == 0 then
		spec = Array.extend(
			Logic.readBool(args.show_p) and 'points' or nil,
			'series',
			'diff',
			'h2hSeries',
			'h2hGames',
			'gamesWon',
			'gamesLoss'
		)
	end
	table.insert(spec, 1, 'dq')
	table.insert(spec, 'tempTies')

	return spec
end

function GroupTableLeague.resolve_ties(args, opponentlist, results, lpdb_conditions, date)
	local scoreSpec = GroupTableLeague.readScoreSpec(args)

	local ties_resolved = false
	local scoreSpecIndex = 1

	lpdb_conditions = lpdb_conditions .. ' AND ([[date::<' .. date .. ']] OR [[date::' .. date .. ']])'

	local statefulScoreNames = {'h2hSeries', 'h2hGames', 'h2hGamesDiff', 'h2hGamesWon', 'h2hGamesLoss'}
	repeat
		local start_index, end_index = 1, 1

		repeat
			while (end_index < #opponentlist and results[end_index].tiebreaker == results[end_index+1].tiebreaker) do
				end_index = end_index + 1
			end

			-- calculate tiebreaker value for each tied opponent
			local tied_rankings = {}

			local scoreName = scoreSpec[scoreSpecIndex]
			if end_index - start_index > 0 then
				if Table.includes(statefulScoreNames, scoreName) then
					StatefulScore[scoreName](results, start_index, end_index, lpdb_conditions)
					for i = start_index, end_index do
						table.insert(tied_rankings, results[i])
					end
				else
					for i = start_index, end_index do
						results[i].tiebreaker = Score[scoreName](results[i])
						table.insert(tied_rankings, results[i])
					end
				end
				table.sort(tied_rankings, function(item1, item2) return
					item1.tiebreaker > item2.tiebreaker or (item1.tiebreaker == item2.tiebreaker and string.lower(item1.opponent) < string.lower(item2.opponent)) end)
				for i = start_index, end_index do
					results[i] = tied_rankings[ i - start_index + 1 ]
				end
			end
			end_index = end_index + 1
			start_index = end_index
		until (end_index > #opponentlist)
		scoreSpecIndex = scoreSpecIndex + 1

		if (scoreSpecIndex > 2 and start_index == 1 and end_index == #opponentlist) then
			ties_resolved = true
		end

	until (scoreSpecIndex > #scoreSpec or ties_resolved == true)

	-- update opponentlist to match results
	for key, item in ipairs(results) do
		opponentlist[item.opponent] = key
	end
end

function GroupTableLeague.get(frame, args, data)
	local divWrapper
	local lpdb_conditions
	local opponentlist = {}
	local results = {}
	local tournaments = {}
	local custom_points = {}
	local rounds = {}
	local round_no = 1
	local opponents = {}
	local countWinPoints = 1

	if not args then
		args = Arguments.getArgs(frame)
	end

	local groupTableIndex = tonumber(pageVars:get('index'))
	pageVars:set('index', groupTableIndex and groupTableIndex + 1 or 0)

	local table_type = string.lower(args['type'] or '1v1') == 'team' and 'team' or '1v1'
	local typeparams = {'player', 'p'}
	local mode = '1_1'
	if table_type == 'team' then
		typeparams = {'team', 't'}
		mode = 'team_team'
	end

	-- parse parameters tournamentX and opponentX and X-X_p
	for key, item in pairs(args) do
		if item == '' or item == '\n' then
			args[key] = nil
		end

		if type(key) == 'string' and item ~= '' then
			-- tournamentX
			if key:match('^tournament(%d*)$') then
				table.insert(tournaments, (mw.ext.TeamLiquidIntegration.resolve_redirect(item):gsub('%s','_')) )
			end

			-- paramX associated with opponentX
			if key:match('^r?o?u?n?d?%d-([^%d]*%d*)$') then
				local round_index, param, opponent_index = key:match('^r?o?u?n?d?(%d-)([^%d]*)(%d*)$')

				if round_index == '' then
					round_index = 0
				end
				round_index, opponent_index = tonumber(round_index), tonumber(opponent_index)

				if not rounds[round_index] then
					rounds[round_index] = {
						date = nil, --today_date,
						temp = {
							series = {
								won = {},
								tied = {},
								loss = {},
							},
							games = {
								won = {},
								loss = {},
							},
							points = {},
							tiebreaker = {},
							diff = {}
						},
						params = {
							disqualified = {},
							bg = {}
						}
					}
				end

				-- opponentX
				if param == 'opponent' or param == typeparams[1] or param == typeparams[2] then
					opponentlist[opponent_index] = GroupTableLeague['get_' .. table_type .. '_displaydata'](param, opponent_index, item, args)
					opponentlist[opponent_index].note = (args[param .. opponent_index .. 'note'] or '') ~= '' and args[param .. opponent_index .. 'note'] or args['note' .. opponent_index] or ''
					opponentlist[opponentlist[opponent_index].opponent] = opponent_index
					opponents[#opponents + 1] = opponentlist[opponent_index].opponent

					for alias in mw.text.gsplit(args['opponent' .. opponent_index .. 'alias'] or '', ",") do
						if alias ~= '' then
							local alias_name = mw.ext.TeamLiquidIntegration.resolve_redirect(alias)
							aliaslist[alias_name] = opponentlist[opponent_index].opponent
							opponents[#opponents + 1] = alias_name
						end
					end

				-- rNdateX
				elseif param == 'edate' or param == 'date' or param == 'ate' then
					rounds[round_index].date = tonumber(GroupTableLeague.formatDate(item, 'U'))

				-- rNbgX
				elseif param == 'bg' then
					rounds[round_index].params.bg[opponent_index] = item

				-- temp_pX
				elseif param == 'temp_p' and opponent_index then
					rounds[round_index].temp.points[opponent_index] = item

				-- temp_tieX
				elseif param == 'temp_tie' and opponent_index then
					rounds[round_index].temp.tiebreaker[opponent_index] = item

				-- temp_diffX
				elseif param == 'temp_diff' and opponent_index then
					rounds[round_index].temp.diff[opponent_index] = item

				-- temp_win_mX
				elseif param == 'temp_win_m' and opponent_index then
					rounds[round_index].temp.series.won[opponent_index] = item
				elseif param == 'temp_tie_m' and opponent_index then
					rounds[round_index].temp.series.tied[opponent_index] = item
				elseif param == 'temp_lose_m' and opponent_index then
					rounds[round_index].temp.series.loss[opponent_index] = item
				elseif param == 'temp_win_g' and opponent_index then
					rounds[round_index].temp.games.won[opponent_index] = item
				elseif param == 'temp_lose_g' and opponent_index then
					rounds[round_index].temp.games.loss[opponent_index] = item

				-- dqX
				elseif (param == 'dq' or param == 'q') and opponent_index and item == 'true' then -- TODO: param == 'q' is a temporary fix, resolve it correctly
					rounds[round_index].params.disqualified[opponent_index] = true
				end

			-- X-Y_p
			elseif key:match('^(%d+-%d+)_p$') then
				countWinPoints = 0
				table.insert(custom_points, {score1 = key:match('^(%d+)-%d+_p'), score2 = key:match('^%d+-(%d+)_p'), points = item})
			end
		end
	end

	if args.exclusive == 'false' then
		opponents = {}
	end

	-- parse parameters
	countWinPoints = (args.countWinPoints == '1' or args.countWinPoints == 'true') and 1 or countWinPoints
	args.win_p = tonumber(args.win_p) or countWinPoints * 3
	args.tie_p = tonumber(args.tie_p) or countWinPoints * 1
	args.lose_p = tonumber(args.lose_p) or 0
	args.walkover_win = tonumber(args.walkover_win) or 0
	args.ties = args.ties or 'false'
	args.show_p = args.show_p or 'false'
	args.diff = args.diff or 'true'
	args.show_g = args.show_g or 'true'
	args.exclusive = args.exclusive or 'true'
	args.roundtitle = args.roundtitle or 'Round'
	args.roundwidth = tonumber(args.roundwidth) or 90

	if not next(tournaments) then
		tournaments[1] = string.gsub(mw.title.getCurrentTitle().text,'%s','_')
	end
	--=
	if not data then
		data, lpdb_conditions = GroupTableLeague.get_lpdb_results(args, tournaments, opponents, mode, args.id or '')
	end
	if not data[1] then
		data[1] = {
			date = '',
			finished = 0,
			match2opponents = {
				[1] = {
					name = 'Definitions',
					score = 0,
					status = 'S',
				},
				[2] = {
					name = 'Definitions',
					score = 0,
					status = 'S',
				}
			}
		}
	end

	if type(data) == 'table' then
		-- final date
		table.sort(rounds, function(item1, item2) return item1.date < item2.date end)
		local is_mid_tournament = false
		local today_date = tonumber(mw.language.getContentLanguage():formatDate('U', os.date()))
		local last_date = today_date
		if next(data) then
			last_date = tonumber(GroupTableLeague.formatDate(data[#data].date, 'U'))
			for key, item in ipairs(rounds) do
				if item.date > today_date then
					if is_mid_tournament then
						rounds[key] = nil
					else
						is_mid_tournament = true
					end
				elseif item.date > last_date then
					rounds[key] = nil
				end
			end
		end
		if not(rounds[1]) or (rounds[#rounds].date < last_date and not(is_mid_tournament)) then
			if rounds[0] then
				rounds[0].date = today_date
				rounds[(#rounds or 0)+1] = rounds[0]
			else
				rounds[(#rounds or 0)+1] = {
					date = today_date,
					temp = {
						series = {
							won = {},
							tied = {},
							loss = {},
						},
						games = {
							won = {},
							loss = {},
						},
						points = {},
						tiebreaker = {},
						diff = {}
					},
					params = {
						disqualified = {},
						bg = {}
					}
				}
			end
		end

		-- create table
		local class = 'wikitable wikitable-bordered grouptable'
		if (args.hide == 'true') then
			class = class .. 'collapsible collapsed'
		elseif (args.hide == 'false') then
			class = class .. 'collapsible'
		elseif (args.hide) then
			class = class .. args.hide
		end

		divWrapper = mw.html.create('div')
			:addClass('table-responsive toggle-area toggle-area-' .. tostring(#rounds))
			:attr('data-toggle-area',tostring(#rounds))
		local output = divWrapper:tag('table')
			:addClass(class)
			:css('width', args.width or '300px')
			:css('margin','0px')

		-- create table header
		local titleText = ''
		if args.location then
			titleText = '<span style="padding-right:3px;">' .. tostring(frame:expandTemplate{ title = 'Played in', args = {args.location} }) .. '</span>'
		end
		titleText = titleText .. (args.title or mw.title.getCurrentTitle().text)

		local title = mw.html.create('span')
			:wikitext(titleText)
		if #rounds > 1 then
			title:css('margin-left', '-70px')
				:css('vertical-align','middle')
		end

		local header_iconsWrapper = ''
		if (args.preview or args.lrthread or args.vod or args.vod1 or args.vod2 or args.vod3 or args.vod4 or args.vod5 or args.vod6 or args.vod7 or args.vod8 or args.vod9 or args.interview or args.interview2 or args.interview3 or args.interview4 or args.recap or args.review) then
			local header_icons = ''
			if args.preview then header_icons = header_icons .. '[[File:Preview_Icon32.png|link=' .. args.preview .. '|alt=preview|Preview]] ' end
		if args.lrthread then header_icons = header_icons .. '[[File:LiveReport32.png|link=' .. args.lrthread .. '|alt=lrthread|Live Report Thread]] ' end
		if args.vod then header_icons = header_icons .. '[[File:VOD Icon.png|link=' .. args.vod .. '|Watch VOD]] ' end
		if args.vod1 then header_icons = header_icons .. '[[File:VOD Icon1.png|link=' .. args.vod1 .. '|Watch Game 1]] ' end
		if args.vod2 then header_icons = header_icons .. '[[File:VOD Icon2.png|link=' .. args.vod2 .. '|Watch Game 2]] ' end
		if args.vod3 then header_icons = header_icons .. '[[File:VOD Icon3.png|link=' .. args.vod3 .. '|Watch Game 3]] ' end
		if args.vod4 then header_icons = header_icons .. '[[File:VOD Icon4.png|link=' .. args.vod4 .. '|Watch Game 4]] ' end
		if args.vod5 then header_icons = header_icons .. '[[File:VOD Icon5.png|link=' .. args.vod5 .. '|Watch Game 5]] ' end
		if args.vod6 then header_icons = header_icons .. '[[File:VOD Icon6.png|link=' .. args.vod6 .. '|Watch Game 6]] ' end
		if args.vod7 then header_icons = header_icons .. '[[File:VOD Icon7.png|link=' .. args.vod7 .. '|Watch Game 7]] ' end
		if args.vod8 then header_icons = header_icons .. '[[File:VOD Icon8.png|link=' .. args.vod8 .. '|Watch Game 8]] ' end
		if args.vod9 then header_icons = header_icons .. '[[File:VOD Icon9.png|link=' .. args.vod9 .. '|Watch Game 9]] ' end
		if args.interview then header_icons = header_icons .. '[[File:Int_Icon.png|link=' .. args.interview .. '|20px|Interview]] ' end
		if args.interview2 then header_icons = header_icons .. '[[File:Int_Icon.png|link=' .. args.interview2 .. '|20px|Interview]] ' end
		if args.interview3 then header_icons = header_icons .. '[[File:Int_Icon.png|link=' .. args.interview3 .. '|20px|Interview]] ' end
		if args.interview4 then header_icons = header_icons .. '[[File:Int_Icon.png|link=' .. args.interview4 .. '|20px|Interview]] ' end
		if args.recap then header_icons = header_icons .. '[[File:Reviews32.png|link=' .. args.recap .. '|15px|Recap]] ' end
		if args.review then header_icons = header_icons .. '[[File:Reviews32.png|link=' .. args.review .. '|15px|Recap]] ' end
			header_iconsWrapper = mw.html.create('span')
				:addClass('plainlinks vodlink')
				:css('float', 'right')
				:wikitext(header_icons)
	end

		local dropdownWrapper = mw.html.create('div')
			:addClass('dropdown-box-wrapper')
			:css('float','left')
		dropdownWrapper:tag('span')
			:addClass('dropdown-box-button btn btn-primary')
			:css('width',args.roundwidth .. 'px')
			:css('padding-top', '2px')
			:css('padding-bottom', '2px')
			:wikitext((is_mid_tournament and 'Current' or args.roundtitle .. ' ' .. tostring(#rounds)) .. ' <span class="caret"></span>')
		if #rounds <= 1 then
			dropdownWrapper:css('display','none')
		end
		local dropdownButton = dropdownWrapper:tag('div')
			:addClass('dropdown-box')
			:css('padding', '0px')
			:css('border', '0px')
		for i=1,#rounds do
			local buttonText
			if i == #rounds and is_mid_tournament then
				buttonText = 'Current'
			else
				buttonText = args.roundtitle .. ' ' .. tostring(i)
			end

			dropdownButton:tag('div')
				:addClass('toggle-area-button btn btn-primary')
				:attr('data-toggle-area-btn',tostring(i))
				:css('width',args.roundwidth .. 'px')
				:css('padding-top', '2px')
				:css('padding-bottom', '2px')
				:css('border-top-width', '0px')
				:wikitext(buttonText)
		end

		local header = output:tag('tr')
		header:tag('th')
			:attr('colspan', args.colspan or '7')
			:css('text-align', 'center')
			:wikitext(tostring(title) .. tostring(header_iconsWrapper) .. tostring(dropdownWrapper))

		-- secondary header for date
		local dateheader
		if args.date then
			dateheader = output:tag('tr')
			dateheader:tag('td')
				:attr('colspan', args.colspan or '7')
				:css('background-color', '#f2f2f2')
				:css('font-size', '85%')
				:css('line-height', '90%')
				:css('height', '13px')
				:css('text-align', 'center')
				:wikitext(Countdown._create{
					date = GroupTableLeague.formatDate(args.date, 'F j, Y - H:i') .. '<abbr data-tz="+0:00"></abbr>',
					finished = args.finished,
					stream = args.stream,
					twitch = args.twitch,
					afreeca = args.afreeca,
					afreecatv = args.afreecatv,
					dailymotion = args.dailymotion,
					douyu = args.douyu,
					smashcast = args.smashcast,
					youtube = args.youtube,
					facebook = args.facebook,
					trovo = args.trovo,
					rawdatetime = args.rawdatetime,
				})
		end

		--store (s)date as match date to be passed to the matches entered after this group table
		local storeToVarDate = args.sdate or ''
		if storeToVarDate == '' then
			storeToVarDate = args.date or ''
		end
		if storeToVarDate ~= '' then
			storeToVarDate = GroupTableLeague.formatDate(storeToVarDate, 'Y-m-d H:i:s')
			mw.ext.VariablesLua.vardefine('matchDate', storeToVarDate)
		end

		-- get list of unique opponents if no set opponents
		if not next(opponentlist) then
			local k = 0
			for _, item in ipairs(data) do
				if not opponentlist[item.match2opponents[1].name] then
					k = k + 1
					opponentlist[k] = {opponent = item.match2opponents[1].name, opponent_arg = item.match2opponents[1].name}
				end
				if not opponentlist[item.match2opponents[2].name] then
					k = k + 1
					opponentlist[k] = {opponent = item.match2opponents[2].name, opponent_arg = item.match2opponents[2].name}
				end
			end
		end
		-- additional opponent 'discard' for discarded results
		opponentlist[0] = ''

		-- initialise templates for each opponent
		local oppdate = (args.edate or '') ~= '' and args.edate or (args.date or '') ~= '' and args.date or GroupTableLeague.var('tournament_enddate', today_date)


		for key = 0,#opponentlist do
			results[key] = {
				index = key,
				opponent = opponentlist[key].opponent or opponentlist[key],
				opponent_arg = opponentlist[key].opponent_arg or opponentlist[key],
				opponent_template = DISPLAY['get_' .. table_type](opponentlist[key], oppdate) or '',
				note = opponentlist[key].note or '',
				ranking = 0,
				ranking_change = 0,
				tiebreaker = 0, -- score used to determine ranking between opponents
				points = 0,
				custom_points = 0,
				diff = 0,
				series = {
					won = 0,
					tied = 0,
					loss = 0,
				},
				games = {
					won = 0,
					loss = 0,
				},
				temp = {
					tiebreaker = 0,
					points = 0,
					diff = 0
				}
			}
		end

		-- calculations
		if not next(data) then
			GroupTableLeague.resolve_ties(args, opponentlist, results, lpdb_conditions, today_date)
			GroupTableLeague.printResults(args, header, results, rounds, round_no)
		end
		for key, item in ipairs(data) do
			local index1
			if aliaslist[item.match2opponents[1].name] then
				index1 = opponentlist[ aliaslist[item.match2opponents[1].name] ]
			else
				index1 = opponentlist[ item.match2opponents[1].name ]
			end
			local index2
			if aliaslist[item.match2opponents[2].name] then
				index2 = opponentlist[ aliaslist[item.match2opponents[2].name] ]
			else
				index2 = opponentlist[ item.match2opponents[2].name ]
			end
			local update = false

			if (args.exclusive ~= 'false' and index1 and index2) then
				update = true
			elseif (args.exclusive == 'false' and (index1 or index2)) then
				update = true
				if not index1 then
					index1 = 0 --discard results
				elseif not index2 then
					index2 = 0 --discard results
				end
			end

			if (update == true) then
				if ((item.finished == '1' or item.finished == 't') and (tonumber(item.match2opponents[1].score)>0 or tonumber(item.match2opponents[2].score)>0 or item.winner ~= '' or item.resulttype ~= '') ) then
					-- add game win/loss
					if item.resulttype ~= '' and item.resulttype ~= 'draw' then
						if item.winner == '1' then
							results[index1].games.won = results[index1].games.won + args.walkover_win
							results[index2].games.loss = results[index2].games.loss + args.walkover_win
						elseif item.winner == '2' then
							results[index2].games.won = results[index2].games.won + args.walkover_win
							results[index1].games.loss = results[index1].games.loss + args.walkover_win
						end
					else
						results[index1].games.won = results[index1].games.won + tonumber(item.match2opponents[1].score)
						results[index1].games.loss = results[index1].games.loss + tonumber(item.match2opponents[2].score)

						results[index2].games.won = results[index2].games.won + tonumber(item.match2opponents[2].score)
						results[index2].games.loss = results[index2].games.loss + tonumber(item.match2opponents[1].score)
					end

					-- add series win/loss
					if item.winner == '1' then
						results[index1].series.won = results[index1].series.won + 1
						results[index2].series.loss = results[index2].series.loss + 1
					elseif item.winner == '2' then
						results[index1].series.loss = results[index1].series.loss + 1
						results[index2].series.won = results[index2].series.won + 1
					elseif (item.match2opponents[1].score == item.match2opponents[2].score) then
						results[index1].series.tied = results[index1].series.tied + 1
						results[index2].series.tied = results[index2].series.tied + 1
						args.ties = 'true'
					end

					-- add points based on series score
					for _, item_p in pairs(custom_points) do
						if (tostring(item.match2opponents[1].score) == item_p.score1 and tostring(item.match2opponents[2].score) == item_p.score2) then
							results[index1].custom_points = results[index1].custom_points + tonumber(item_p.points)
						end
						if (tostring(item.match2opponents[2].score) == item_p.score1 and tostring(item.match2opponents[1].score) == item_p.score2) then
							results[index2].custom_points = results[index2].custom_points + tonumber(item_p.points)
						end
					end
				end
			end

			--add a catch to fix an error (ugly af!)
			local roundIsEmpty
			if not rounds[round_no] then
				roundIsEmpty = true
				rounds[round_no] = rounds[round_no - 1]
				rounds[round_no].date = 9999999999
			end

			if key == #data or tonumber(GroupTableLeague.formatDate(data[key+1].date, 'U')) > rounds[round_no].date then
				for i=1,#opponentlist do
					local opponent_index = results[i].index
					results[i].series.won = results[i].series.won + (rounds[round_no].temp.series.won[opponent_index] or 0)
					results[i].series.tied = results[i].series.tied + (rounds[round_no].temp.series.tied[opponent_index] or 0)
					results[i].series.loss = results[i].series.loss + (rounds[round_no].temp.series.loss[opponent_index] or 0)
					results[i].games.won = results[i].games.won + (rounds[round_no].temp.games.won[opponent_index] or 0)
					results[i].games.loss = results[i].games.loss + (rounds[round_no].temp.games.loss[opponent_index] or 0)

					results[i].temp.points = results[i].temp.points + (rounds[round_no].temp.points[opponent_index] or 0)
					results[i].points = results[i].temp.points + results[i].custom_points +
						args.win_p * results[i].series.won +
						args.tie_p * results[i].series.tied +
						args.lose_p * results[i].series.loss

					results[i].temp.diff = results[i].temp.diff + (rounds[round_no].temp.diff[opponent_index] or 0)
					results[i].diff = results[i].temp.diff + results[i].games.won - results[i].games.loss

					results[i].temp.tiebreaker = results[i].temp.tiebreaker + (rounds[round_no].temp.tiebreaker[opponent_index] or 0)

					if rounds[round_no].params.bg[opponent_index] then
						results[i].bg = rounds[round_no].params.bg[opponent_index]
					end
					if rounds[round_no].params.disqualified[opponent_index] or rounds[0] and rounds[0].params.disqualified[opponent_index] then
						results[i].disqualified = true
					end
				end
				-- tiebreakers
				GroupTableLeague.resolve_ties(args, opponentlist, results, lpdb_conditions, rounds[round_no].date)
				-- update rankings
				local rank = 1
				for index, oppResult in ipairs(results) do
					if oppResult.ranking ~= 0 and oppResult.ranking ~= rank and type(oppResult.ranking) == 'number' then
						oppResult.ranking_change = oppResult.ranking - rank
					else
						oppResult.ranking_change = 0
					end

					if (oppResult.disqualified == true) then
						oppResult.ranking = 'DQ'
					-- if they've played any matches, show a ranking
					elseif (oppResult.series.won + oppResult.series.tied + oppResult.series.loss ~= 0) then
						oppResult.ranking = rank
					end
					if (index == #results or results[index+1].tiebreaker ~= results[index].tiebreaker) then
						rank = index + 1
					end
					oppResult.tiebreaker = 0
				end

				if roundIsEmpty then
					rounds[round_no] = nil
				end

				if round_no == #rounds then
					-- Note: printResults mutates results, so store() has to go first
					Logic.try(function()
						GroupTableLeague.store(args, results, opponentlist, data, table_type)
					end)
						:catch(function(err)
							mw.log('GroupTableLeague: Error saving group results to data point')
							mw.log(err)
						end)
					GroupTableLeague.printResults(args, output, results, rounds, round_no, is_mid_tournament)
				else
					GroupTableLeague.printResults(args, output, results, rounds, round_no)
				end
				round_no = round_no + 1
				if round_no > #rounds or roundIsEmpty then
					break;
				end
			end
		end

	else
		error(data)
	end

	return divWrapper
end

function GroupTableLeague.printResults(args, output, results, rounds, round_no, is_mid_tournament)
	for key, item in ipairs(results) do
		local row = output:tag('tr')
			:attr('data-toggle-area-content',round_no)
		local bgclass = string.lower(item.bg or '')
		row:tag('th')
			:addClass('bg-' .. string.lower(args['pbg' .. key] or bgclass or ''))
			:css('width', '28px')
			:wikitext(item.ranking ~= 0 and (item.ranking .. '.') or '')

		local opponent_colspan = 3
		if (args.show_g == 'false') then
			opponent_colspan = opponent_colspan + 1
		end
		if (args.diff == 'false') then
			opponent_colspan = opponent_colspan + 1
		end
		if (args.show_p == 'true') then
			opponent_colspan = opponent_colspan - 1
		end

		local opponenttext = item.opponent_template
		if (item.disqualified == true) then
			opponenttext = '<s>' .. opponenttext .. '</s>'
		end
		if (item.note or '') ~= '' then
			opponenttext = opponenttext .. '&nbsp;<sup><b>' .. item.note .. '</b></sup>'
		end

		local ranking_change = item.ranking_change
		if ranking_change == 0 then
			ranking_change = ''
		elseif ranking_change > 0 then
			ranking_change = '<span style="color:green; float:right; font-size:90%;">&#x25B2;' .. ranking_change .. '</span>'
		else
			ranking_change = '<span style="color:red; float:right; font-size:90%;">&#x25BC;' .. -ranking_change .. '</span>'
		end

		row:tag('td')
			:addClass('grouptableslot')
			:addClass('bg-' .. bgclass)
			:attr('colspan', opponent_colspan)
			:attr('align', 'left')
			:wikitext(opponenttext .. ranking_change)

		local series_score
		if (args.ties == 'true') then
			series_score = item.series.won .. '-' .. item.series.tied .. '-' .. item.series.loss
		else
			series_score = item.series.won .. '-' .. item.series.loss
		end
		row:tag('td')
			:addClass('bg-' .. bgclass)
			:attr('width', '35px')
			:attr('align', 'center')
			:css('white-space', 'pre')
			:wikitext('\'\'\'' .. series_score .. '\'\'\'')

		if (args.show_g ~= 'false') then
			row:tag('td')
				:addClass('bg-' .. bgclass)
				:attr('width', '35px')
				:attr('align', 'center')
				:css('white-space', 'pre')
				:wikitext(item.games.won .. '-' .. item.games.loss)
		end

		if (args.diff ~= 'false') then
			if (item.diff > 0) then
				item.diff = '+' .. item.diff
			end
			if item.has_temp_diff then
				item.diff = item.diff .. '*'
			end
			row:tag('td')
				:addClass('bg-' .. bgclass)
				:attr('width', '35px')
				:attr('align', 'center')
				:css('white-space', 'pre')
				:wikitext('\'\'' .. item.diff .. '\'\'')
		end

		if (args.show_p == 'true') then
			row:tag('td')
				:addClass('bg-' .. bgclass)
				:attr('width', '32px')
				:attr('align', 'center')
				:css('white-space', 'pre')
				:wikitext('\'\'\'' .. item.points .. 'p\'\'\'')
		end
	end
end

function GroupTableLeague.getH2HOppCond(results, index)
	local opp = {}
	table.insert(opp, results[index].opponent)
	--aliases
	if aliaslist[results[index].opponent] then
		for _, v in pairs(aliaslist[results[index].opponent]) do
			table.insert(opp, v)
		end
	end
	return opp
end

function DISPLAY.get_1v1(opp)
	return Player._player({
		opp.opponent_arg or opp,
		link = opp.opponent,
		flag = opp.flag,
		race = opp.race,
		novar = 'true'})
end

function GroupTableLeague.get_1v1_displaydata(param, opponent_index, item, args)
	local opponent_name = mw.ext.TeamLiquidIntegration.resolve_redirect(args[param .. opponent_index .. 'link'] or GroupTableLeague.var(item .. '_page', item))
	local opponent_flag = args[param .. opponent_index .. 'flag'] or GroupTableLeague.var(item .. '_flag', '')
	local opponent_race = args[param .. opponent_index .. 'race'] or GroupTableLeague.var(item .. '_race', '')
	return {opponent = opponent_name, opponent_arg = item, flag = opponent_flag, race = opponent_race}
end

function DISPLAY.get_team(opp, date)
	return TeamTemplates._team(opp.opponent, date)
end

function GroupTableLeague.get_team_displaydata(param, opponent_index, item, args)
	local opponent_name = mw.ext.TeamLiquidIntegration.resolve_redirect(TeamTemplates._teampage((item or '') ~= '' and item or 'tbd'))
	return {opponent = opponent_name, opponent_arg = item}
end

function GroupTableLeague.store(args, results, gtlOpponents, matchRecords, tableType)
	local record = GroupTableLeague.groupTableToRecord(args, results, gtlOpponents, matchRecords, tableType)
	local groupTableIndex = record.extradata.groupTableIndex or 'unknown'
	record.extradata = Json.stringify(record.extradata)
	mw.ext.LiquipediaDB.lpdb_datapoint('GroupTableLeague_' .. groupTableIndex, record)
end

function GroupTableLeague.groupTableToRecord(args, results, gtlOpponents, matchRecords, tableType)
	local resolveDate = DateExt.toYmdInUtc(args.date)
		or TournamentUtil.getContextualDateOrNow()

	local function makeResultRecord(index)
		local result = results[index]
		local gtlOpponent = Array.find(gtlOpponents, function(gtlOpponent) return gtlOpponent.opponent == result.opponent end)

		local opponent
		if tableType == '1v1' then
			local player = {
				pageName = String.nilIfEmpty(gtlOpponent.opponent),
				displayName = String.nilIfEmpty(gtlOpponent.opponent_arg),
				race = String.nilIfEmpty(gtlOpponent.race),
				flag = String.nilIfEmpty(Flags.CountryName(gtlOpponent.flag)),
			}
			if Opponent.playerIsTbd(player) then
				player.pageName = nil
			end
			opponent = {type = 'solo', players = {player}}
		else
			local template = TeamTemplate.resolve(gtlOpponent.opponent_arg, resolveDate)
			opponent = {type = 'team', template = template}
		end

		return {
			bg = result.bg,
			customPoints = result.custom_points,
			dq = result.disqualified,
			finalTiebreak = nil,
			gameScore = {result.games.won or 0, result.games.tied or 0, result.games.loss or 0},
			matchScore = {result.series.won or 0, result.series.tied or 0, result.series.loss or 0},
			opponent = opponent,
			pbg = args['pbg' .. index],
			points = result.points,
			rank = tonumber(result.ranking),
			rankChange = -result.ranking_change,
			slotIndex = index,
		}
	end

	local roundFinished = Logic.readBool(args.finished)
		or Array.all(matchRecords, function(match) return Logic.readBool(match.finished) end)
	local bracketIndexes = Array.map(matchRecords, function(match) return tonumber((match.match2bracketdata or {}).bracketindex) end)
	local matchGroupIds = Array.map(matchRecords, function(matchRecord) return matchRecord.match2bracketid end)

	local recordDate = DateExt.readTimestamp(args.date)
	if not recordDate then
		local tournamentEnddate = TournamentUtil.getContextualDate()
		if tournamentEnddate then
			recordDate = DateExt.readTimestamp(tournamentEnddate) + 24 * 3600
		end
	end
	if not recordDate then
		recordDate = os.time()
	end

	local extradata = {
		bracketIndex = Array.min(bracketIndexes),
		groupFinished = roundFinished and true,
		groupTableIndex = tonumber(pageVars:get('index')),
		matchGroupId = ArrayExt.uniqueElement(matchGroupIds),
		metrics = nil,
		results = Array.map(Array.range(1, #results), makeResultRecord),
		roundFinished = roundFinished,
		rounds = nil,
		stageName = globalVars:get('bracket_header'),
	}
	return {
		date = DateExt.formatTimestamp('c', recordDate),
		extradata = extradata,
		name = args.title,
		type = 'GroupTableLeague',
	}
end

GroupTableLeague.perfConfig = {
	locations = {
		'Module:GroupTableLeague/new|*',
	},
}

return GroupTableLeague