Leaguepedia | League of Legends Esports Wiki
[checked revision][checked revision]
([ST] add to `this` instead of `total` cos we do it before we sort i guess?)
([ST] jk)
Line 337: Line 337:
 
if not tab then return end
 
if not tab then return end
 
local f_sort = util_tournament.getSortMethod(tltype)
 
local f_sort = util_tournament.getSortMethod(tltype)
h.addArgsToWeekOneTotals(tab, teamlist, args)
 
 
for _, team in ipairs(tab) do
 
for _, team in ipairs(tab) do
 
h.copyThisToTotal(tab[team].total, tab[team].this)
 
h.copyThisToTotal(tab[team].total, tab[team].this)
 
f_sort(tab[team].total)
 
f_sort(tab[team].total)
 
end
 
end
 
h.addArgsToWeekOneTotals(tab, teamlist, args)
 
end
 
end
   
Line 359: Line 359:
 
}
 
}
 
for _, teamstr in ipairs(weekOne) do
 
for _, teamstr in ipairs(weekOne) do
h.addAdjustmentsToWeekOne(weekOne[teamstr].this, teamstr, arg_adjust)
+
h.addAdjustmentsToWeekOne(weekOne[teamstr].total, teamstr, arg_adjust)
 
end
 
end
 
end
 
end

Revision as of 11:45, 14 June 2021

Documentation for this module may be created at Module:Timeline/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_html = require('Module:HtmlUtil')
local util_map = require('Module:MapUtil')
local util_math = require('Module:MathUtil')
local util_table = require('Module:TableUtil')
local util_text = require('Module:TextUtil')
local util_title = require('Module:TitleUtil')
local util_toggle = require('Module:ToggleUtil')
local util_tournament = require('Module:TournamentUtil')
local util_vars = require('Module:VarsUtil')

local m_team = require('Module:Team')

local lang = mw.getLanguage('en')

local TOGGLES = {
	order = { 'cu', 'wk' },
	displays = {
		wk = 'Weekly',
		cu = 'Cumulative'
	},
}

local DISPLAY_TYPE = {
	record = 'record',
	recordwithgames = 'record',
	points = 'points',
	recordwithpoints = 'recordwithpoints',
	bo2nopoints = 'recordbo2',
}
--[[
	Table structure:
	{
		{
			name = Tab 1,
			team1, team2, team3,
			team1 = { this = { w = , l = , p = }, total = {}, place = 1, diff = 0 }
		},
		{
			name = Tab 2,
			team1, team2, team3,
			team1 = { this = {}, total = {}, place = , diff = }
		}
	}
	
	first construct all tabs, except for standings & diffs
	then determine standings & diffs
]]

local h = {}
local p = {}
function p.fromCargo(frame)
	local args = util_args.merge()
	h.verifyParams(args)
	local tltype = lang:lc(args.orderby)
	local overviewPage = util_esports.getOverviewPage(args.page)
	local data = h.getData(overviewPage, args, tltype)
	if #data == 0 then return '' end
	return p.main(data, args, tltype)
end

function p.fromArgs(frame)
	local args = util_args.merge()
	local tltype = lang:lc(args.orderby)
	local data = h.getDataFromArgs(args, tltype)
	return p.main(data, args, tltype)
end

function p.main(data, args, tltype)
	local nTabs, nTeams = h.countParts(data)
	local colors = h.getColors(args, nTabs, nTeams)
	return h.makeOutput(data, tltype, colors)
end

function h.verifyParams(args)
	if not args.orderby then
		error('Missing |orderby=')
	end
end

function h.getData(page, args, tltype)
	local teamlist = h.getTeamlist(page, args.teamlist, args.onlygroup)
	local query = h.getQuery(page, args)
	local result = util_cargo.queryAndCast(query)
	local data = h.parseResult(result, tltype, args, teamlist)
	return data
end

function h.getTeamlist(page, teamlist, group)
	if teamlist then
		return util_map.split(teamlist,nil,m_team.teamlinkname)
	elseif group then
		return util_tournament.getGroupTeamList(page, group)
	end
	return nil
end

-- cargo
function h.getQuery(page, args)
	local query = {
		tables = 'MatchSchedule',
		fields = h.getFields(args),
		where = h.getWhere(page, args),
		orderBy = 'N_Page ASC, N_TabInPage ASC, N_MatchInTab ASC',
		groupBy = 'MatchId',
	}
	return query
end

function h.getFields(args)
	local tbl = {
		'Team1',
		'Team2',
		'Team1Final',
		'Team2Final',
		'Winner [number]',
		'Team1Score [number]',
		'Team2Score [number]',
		'Team1Points [number]',
		'Team2Points [number]',
		'Team1PointsTB [number]',
		'Team2PointsTB [number]',
		'Tab',
		'N_MatchInTab [number]',
		'N_TabInPage [number]',
		'CONCAT(N_Page,"_",N_TabInPage)=Index'
	}
	if not util_args.castAsBool(args.nofootnotes) then
		util_table.mergeArrays(tbl, { 'Team1Footnote', 'Team2Footnote' })
	end
	return tbl
end

function h.getWhere(page, args)
	local tbl = {
		('OverviewPage="%s"'):format(page),
		-- we will actually prune the data later as if we had no where condition here
		-- because if data is split across pages then we might get too many things here that we don't want
		-- but certainly this is an upper bound on the amount of data we need
		-- so i'll leave this condition here because it isn't hurting anything
		-- and technically it is potentially allowing us to do less computation overall
		util_cargo.whereFromArg('N_TabInPage <="%s"', args.weeks),
		'IsTiebreaker="0"',
	}
	return util_table.concat(tbl, ' AND ')
end

-- data processing
function h.parseResult(result, tltype, args, teamlist)
	if not teamlist then
		teamlist = h.getTeamList(result)
	end
	local gamesByTab = h.splitResultByTab(result)
	local weeks = args.weeks or h.countWeeks(gamesByTab)
	local starting_week = args.starting_week and tonumber(args.starting_week) or 1
	local dataByTab = util_map.arraySafe(gamesByTab, h.parseTab)
	h.normalizeTeams(dataByTab, teamlist)
	h.addAndAdjustFirstTabTotals(dataByTab[1], tltype, teamlist, args)
	h.addRestTabTotals(dataByTab, tltype)
	for _, tab in ipairs(dataByTab) do
		h.sortTab(tab)
	end
	h.pruneExtraWeeks(dataByTab, tonumber(weeks), starting_week)
	h.sortFinalTab(dataByTab[#dataByTab], args.finalorder, args.finalplaces)
	h.addPlaceAndDiffs(dataByTab)
	util_map.rowsInPlace(dataByTab, h.addClassesToTab)
	return dataByTab
end

function h.getTeamList(result)
	local tbl = {}
	for _, row in ipairs(result) do
		if not tbl[row.Team1Final] then
			tbl[row.Team1Final] = true
		end
		if not tbl[row.Team2Final] then
			tbl[row.Team2Final] = true
		end
	end
	tbl.TBD = nil -- undefine teams that dont actually exist
	local teamlist = {}
	for team, _ in pairs(tbl) do
		teamlist[#teamlist+1] = team
	end
	return teamlist
end

function h.splitResultByTab(result)
	local gamesByTab = {}
	local lastindex = ''
	local thistab = 0
	for _, row in ipairs(result) do
		if row.Index ~= lastindex then
			thistab = thistab + 1
			lastindex = row.Index
			gamesByTab[thistab] = { name = row.Tab }
		end
		gamesByTab[thistab][#gamesByTab[thistab]+1] = row
	end
	return gamesByTab
end

function h.countWeeks(gamesByTab)
	for k, v in ipairs(gamesByTab) do
		if not v[1].Winner then
			return math.max(1, k-1)
		end
	end
	return 9999
end

function h.parseTab(tab)
	local ret = { name = tab.name }
	for _, row in ipairs(tab) do
		h.parseRow(ret, row)
	end
	return ret
end

function h.parseRow(tab, row)
	local Team1 = row.Team1Final
	local Team2 = row.Team2Final
	h.initializeTeam(tab, Team1)
	h.initializeTeam(tab, Team2)
	if row.Winner then
		h.addRowTotals(row, tab[Team1].this, tab[Team2].this)
	end
	tab[Team1].team = row.Team1
	tab[Team2].team = row.Team2
	
	tab[Team1].footnotes[#tab[Team1].footnotes+1] = row.Team1Footnote
	tab[Team2].footnotes[#tab[Team2].footnotes+1] = row.Team2Footnote
	return
end

function h.initializeTeam(tab, team)
	if tab[team] then return end
	tab[team] = {
		teamfinal = team,
		this = {
			w = 0,
			l = 0,
			t = 0,
			wg = 0,
			lg = 0,
			p = 0,
			tb = 0,
			w_unadj = 0,
			t_unadj = 0,
			l_unadj = 0,
			p_unadj = 0,
			tb_unadj = 0,
			wg_unadj = 0,
			lg_unadj = 0,
			hiddenpoints = 0,
		},
		total = {
			w = 0,
			l = 0,
			t = 0,
			wg = 0,
			lg = 0,
			p = 0,
			tb = 0,
			w_unadj = 0,
			t_unadj = 0,
			l_unadj = 0,
			p_unadj = 0,
			tb_unadj = 0,
			wg_unadj = 0,
			lg_unadj = 0,
			hiddenpoints = 0,
		},
		footnotes = {}
	}
	return
end

function h.addRowTotals(row, this1, this2)
	this1.w = this1.w + (row.Winner == 1 and 1 or 0)
	this1.l = this1.l + (row.Winner == 2 and 1 or 0)
	this1.t = this1.t + (row.Winner == 0 and 1 or 0)
	this1.p = this1.p + (row.Team1Points or 0)
	this1.wg = this1.wg + (row.Team1Score or 0)
	this1.lg = this1.lg + (row.Team2Score or 0)
	this1.tb = this1.tb + (row.Team1PointsTB or 0)
	this2.w = this2.w + (row.Winner == 2 and 1 or 0)
	this2.l = this2.l + (row.Winner == 1 and 1 or 0)
	this2.t = this2.t + (row.Winner == 0 and 1 or 0)
	this2.p = this2.p + (row.Team2Points or 0)
	this2.wg = this2.wg + (row.Team2Score or 0)
	this2.lg = this2.lg + (row.Team1Score or 0)
	this2.tb = this2.tb + (row.Team2PointsTB or 0)
end

function h.normalizeTeams(dataByTab, teamlist)
	for _, tab in ipairs(dataByTab) do
		for i, team in ipairs(teamlist) do
			tab[i] = team
			if not tab[team] then
				h.initializeTeam(tab, team)
			end
		end
	end
	h.fixRenamedTeams(dataByTab)
	return	
end

function h.fixRenamedTeams(dataByTab)
	-- we only populate teamfinal when we initialize a team
	-- so now we need to populate actually "team"
	local week = #dataByTab
	while week >= 1 do
		local tab = dataByTab[week]
		local nw = week + 1
		if dataByTab[nw] then
			for _, team in ipairs(tab) do
				if not tab[team].team then
					tab[team].team = dataByTab[nw][team].team
				end
			end
		else
			for _, team in ipairs(tab) do
				if not tab[team].team then
					tab[team].team = tab[team].teamfinal
				end
			end
		end
		week = week - 1
	end
end

function h.addAndAdjustFirstTabTotals(tab, tltype, teamlist, args)
	if not tab then return end
	local f_sort = util_tournament.getSortMethod(tltype)
	for _, team in ipairs(tab) do
		h.copyThisToTotal(tab[team].total, tab[team].this)
		f_sort(tab[team].total)
	end
	h.addArgsToWeekOneTotals(tab, teamlist, args)
end

function h.addArgsToWeekOneTotals(weekOne, teamlist, args)
	-- for GLL format, they had points count from results in the previous split.
	-- these have to be manually added by an editor via the "adjustment" parameters, same as Module:Standings
	-- here we add them ONLY to the total for week 1, and not to the individual week.
	-- this must be done after the total for week 1 is copied from 'this' but before the rest of the weeks are added
	if not weekOne then return end
	local arg_adjust = {
	    w = h.getAdjustmentArgData(args.wadjust),
	    l = h.getAdjustmentArgData(args.ladjust),
	    t = h.getAdjustmentArgData(args.tadjust),
	    wg = h.getAdjustmentArgData(args.wgadjust),
	    lg = h.getAdjustmentArgData(args.lgadjust),
	    p = h.getAdjustmentArgData(args.pointadjust),
	}
	for _, teamstr in ipairs(weekOne) do
	    h.addAdjustmentsToWeekOne(weekOne[teamstr].total, teamstr, arg_adjust)
	end 
end

function h.getAdjustmentArgData(arg)
	if not arg then
		return {}
	end
	local tbl = {}
	for val in arg:gmatch('%(%(%((.-)%)%)%)') do
		k, v = val:match('(.*)===(.*)')
		tbl[m_team.teamlinkname(k)] = v
	end
	return tbl
end

function h.addAdjustmentsToWeekOne(team, teamstr, arg_adjust)
	team.w = team.w + (tonumber(arg_adjust.w[teamstr] or 0))
	team.l = team.l + (tonumber(arg_adjust.l[teamstr] or 0))
	team.t = team.t + (tonumber(arg_adjust.t[teamstr] or 0))
	team.wg = team.wg + (tonumber(arg_adjust.wg[teamstr] or 0))
	team.lg = team.lg + (tonumber(arg_adjust.lg[teamstr] or 0))
end

function h.addRestTabTotals(dataByTab, tltype)
	local f_sort = util_tournament.getSortMethod(tltype)
	for i, tab in ipairs(dataByTab) do
		if i == 1 then
			-- pass
		else
			h.addLaterTabTotals(tab, dataByTab[i-1], f_sort)
		end
	end
end

function h.copyThisToTotal(total, this)
	total.w = this.w
	total.l = this.l
	total.t = this.t
	total.wg = this.wg
	total.lg = this.lg
	total.p = this.p
	total.tb = this.tb
end

function h.addLaterTabTotals(tab, last, f_sort)
	for _, team in ipairs(tab) do
		h.addThisAndLastAsTotal(tab[team].total, tab[team].this, last[team].total)
		f_sort(tab[team].total)
	end
end

function h.addThisAndLastAsTotal(total, this, last)
	total.w = this.w + last.w
	total.l = this.l + last.l
	total.t = this.t + last.t
	total.wg = this.wg + last.wg
	total.lg = this.lg + last.lg
	total.p = this.p + last.p
	total.tb = this.tb + last.tb
end

function h.sortTab(tab)
	table.sort(tab,
		function(a,b)
			if tab[a].total.sort == tab[b].total.sort then
				return lang:lc(a) < lang:lc(b)
			else
				return tab[a].total.sort > tab[b].total.sort
			end
		end
	)
	return
end

function h.pruneExtraWeeks(data, weeks, startingWeek)
	if not weeks then return end
	for k, _ in ipairs(data) do
		if k > weeks then
			data[k] = nil
		end
		
		-- in the event that we have a startingWeek > 1, this pushes the earlier weeks up
		-- and then sets the later weeks to nil once it gets to the end
		-- if startingWeek == 1 then this does nothing
		data[k] = data[k + startingWeek - 1]
	end
end

function h.sortFinalTab(tab, finalorder, places)
	if not finalorder then return end
	local order_tbl = util_text.split(finalorder)
	local places_tbl = places and util_text.split(places) or {}
	for k, v in ipairs(order_tbl) do
		local team = m_team.teamlinkname(v)
		tab[k] = team
		tab[team].total.sort = places_tbl[k] or k
	end
	return
end

function h.addPlaceAndDiffs(dataByTab)
	for i, tab in ipairs(dataByTab) do
		if i == 1 then
			h.addFirstTabPlaceAndDiffs(tab)
		else
			h.addLaterTabPlaceAndDiffs(tab, dataByTab[i-1])
		end
	end
	return
end

function h.addFirstTabPlaceAndDiffs(tab)
	local place = 0
	local lastsort
	for k, team in ipairs(tab) do
		if tab[team].total.sort ~= lastsort then
			place = k
			lastsort = tab[team].total.sort
		end
		tab[team].place = place
		tab[team].diff = 0
	end
end

function h.addLaterTabPlaceAndDiffs(tab, lasttab)
	local place = 0
	local lastsort
	for k, team in ipairs(tab) do
		if tab[team].total.sort ~= lastsort then
			place = k
			lastsort = tab[team].total.sort
		end
		tab[team].place = place
		-- it's better to have a lower place number
		tab[team].diff = lasttab[team].place - place
	end
end

function h.addClassesToTab(tab)
	for _, team in ipairs(tab) do
		h.addClassesToTeam(tab[team])
	end
end

function h.addClassesToTeam(team)
	if not team.classes then team.classes = {} end
	team.classes[#team.classes+1] = h.getTeamPercentageClass(team.total)
end

function h.getTeamPercentageClass(total)
	if total.w == total.l then
		return 'tl-row-equal'
	elseif total.w > total.l then
		return 'tl-row-above'
	end
	return 'tl-row-below'
end

-- from args
function h.getDataFromArgs(args, tltype)
	local dataByTab = {}
	local pointskey = tltype == 'points' and 'p' or 'tb'
	local w = 1
	local t = 1
	while args[('w%steam%s'):format(w,t)] do
		dataByTab[w] = { name = ('Week %s'):format(w) }
		while args[('w%steam%s'):format(w,t)] do
			local team = m_team.teamlinkname(args[('w%steam%s'):format(w,t)])
			dataByTab[w][t] = team
			dataByTab[w][team] = {
				team = team,
				this = {
					w = tonumber(args[('w%sw%s'):format(w,t)] or '') or 0,
					l = tonumber(args[('w%sl%s'):format(w,t)] or '') or 0,
					t = tonumber(args[('w%st%s'):format(w,t)] or '') or 0,
					wg = 0,
					lg = 0,
					p = 0,
					tb = 0,
				},
				total = { w = 0, l = 0, t = 0, p = 0, tb = 0 },
			}
			dataByTab[w][team].this[pointskey] = tonumber(args[('w%spt%s'):format(w,t)] or '') or 0
			t = t + 1
		end
		t = 1
		w = w + 1
	end
	h.addAndAdjustFirstTabTotals(dataByTab[1], tltype, teamlist, args)
	h.addRestTabTotals(dataByTab, tltype)
	if util_args.castAsBool(args.isover) then
		h.sortFinalTabFromArgs(dataByTab[#dataByTab])
	end
	h.addPlaceAndDiffs(dataByTab)
	return dataByTab
end

function h.sortFinalTabFromArgs(tab)
	for k, v in ipairs(tab) do
		tab[v].total.sort = k
	end
	return
end

-- data has been gotten !

function h.countParts(data)
	return #data, #data[1]
end

function h.getColors(args, nTabs, nTeams)
	local tbl = {
		init = args.places and util_map.split(args.places,',',h.getClassName) or {}
	}
	for i = 1, nTabs do
		tbl[i] = {}
		for j = 1, nTeams do
			local arg = args[('w%sbg%s'):format(i,j)]
			if arg then
				tbl[i][j] = h.getClassName(arg)
			end
		end
	end
	return tbl
end

function h.getClassName(class)
	-- prepend every individual "word" in the class with 'standings-'
	return class:gsub('([^ ]+)','standings-%1')
end

function h.makeOutput(data, tltype, colors)
	util_footnote.init()
	if not DISPLAY_TYPE[tltype] then
		error('Invalid orderby')
	end
	tltype = DISPLAY_TYPE[tltype]
	local output = mw.html.create('div'):addClass('timeline-section')
	h.makeToggler(output)
	local div = output:tag('div')
	for i, tab in ipairs(data) do
		h.makeTable(div, tab, tltype, colors.init, colors[i])
	end
	util_footnote.printTexts(output)
	return output
end

function h.makeTable(div, tab, tltype, colorsPlace, colorsBg)
	local tbl = div:tag('table')
		:addClass('wikitable')
		:addClass('timeline')
	h.printHeading(tbl, tab.name)
	for i, team in ipairs(tab) do
		h.printRow(tbl, tab[team], tltype, colorsPlace[i], colorsBg[i])
	end
end

function h.printHeading(tbl, name)
	tbl:tag('tr'):tag('th'):attr('colspan','4'):wikitext(name)
	return
end

function h.printRow(tbl, teamdata, tltype, colorPlace, colorBg)
	local tr = tbl
		:tag('tr')
		:addClass(colorBg)
	h.printRowClasses(tr, teamdata.classes)
	util_esports.addTeamHighlighter(tr, teamdata.teamfinal)
	tr:tag('td')
		:addClass('timeline-place')
		:addClass(colorPlace)
		:wikitext(teamdata.place)
	h.printDiff(tr, teamdata.diff)
	tr:tag('td')
		:addClass('timeline-team')
		:wikitext(m_team.onlyimage(teamdata.team,{size=45}))
		:attr('title', m_team.teamname(teamdata.team))
	h.makeCell(tr, tltype, teamdata)
	return
end

function h.printRowClasses(tr, classes)
	if not classes then return end
	for _, c in ipairs(classes) do
		tr:addClass(c)
	end
end

function h.printDiff(tr, diff)
	local td = tr:tag('td')
	util_esports.printDiff(td, diff)
	return
end

function h.makeCell(tr, tltype, data)
	local wk, cu, class
	if tltype == 'points' then
		wk = data.this.p
		cu = data.total.p
		class = 'timeline-points timeline-score'
	elseif tltype == 'record' then
		wk = ('%s-%s'):format(data.this.w, data.this.l)
		cu = ('%s-%s'):format(data.total.w, data.total.l)
		class = 'timeline-record timeline-score'
	elseif tltype == 'recordbo2' then
		wk = ('%s-%s-%s'):format(data.this.w, data.this.t, data.this.l)
		cu = ('%s-%s-%s'):format(data.total.w, data.total.t, data.total.l)
		class = 'timeline-record timeline-score'
	elseif tltype == 'recordwithpoints' then
		wk = ("%s-%s ''(%s)''"):format(data.this.w, data.this.l, util_math.printWithSign(data.this.tb))
		cu = ("%s-%s ''(%s)''"):format(data.total.w, data.total.l, util_math.printWithSign(data.total.tb))
		class = 'timeline-recordwithpoints timeline-score'
	end
	h.printCell(tr, wk, cu, class, data.footnotes)
	return
end

function h.printCell(tr, wk, cu, class, footnotes)
	local td_wk = tr:tag('td')
		:addClass(class)
		:wikitext(wk)
	util_toggle.oflCellClasses(td_wk, TOGGLES, 'wk')
	util_footnote.tag(td_wk, footnotes)
	local td_cu = tr:tag('td')
		:addClass(class)
		:wikitext(cu)
	util_toggle.oflCellClasses(td_cu, TOGGLES, 'cu')
	util_footnote.tag(td_cu, footnotes)
	return
end

function h.makeToggler(tbl)
	local div = tbl:tag('div')
		:addClass('toggle-button')
	util_toggle.printOptionFromListTogglers(div, TOGGLES)
	util_html.clear(tbl)
	return
end

return p