Module:MatchSchedule

local util_args = require('Module:ArgsUtil') local util_vars = require('Module:VarsUtil') local util_cargo = require('Module:CargoUtil') local util_map = require('Module:MapUtil') local util_table = require('Module:TableUtil') local util_time = require('Module:TimeUtil') local util_text = require('Module:TextUtil') local util_esports = require('Module:EsportsUtil') local m_team = require('Module:Team') local m_map = require('Module:Map') local Patch = require('Module:PatchVariablesClass') local ScoreboardTab = require('Module:ScoreboardTabVariablesClass')

local Phase = require('Module:PhaseClass')

local i18n = require('Module:I18nUtil') local lang = mw.getLanguage('en')

local MATCH_FIELDS = { 'Round', 'Group', 'Team1', 'Team2', 'Result', 'Team1Rounds', 'Team2Rounds','Points', 'PointsTB', 'BestOf', 'DateTime_UTC', 'DateTime_PST', 'DateTime_CET', 'DateTime_KST', 'PBP', 'Color', 'MVP', 'VodInterview', 'With', 'VodHighlights', 'Recap', 'Reddit' }

local GAME_ARGS = { 'Map', 'Blue', 'Red', 'Winner', 'FF', 'Selection', 'MatchHistory', 'Recap', 'Vod', 'VodPB', 'VodGameStart', 'VodPostgame', 'VodHighlights', 'VodInterview', 'InterviewWith', 'MVP', 'MVPPoints', 'BlueFootnote', 'RedFootnote', 'Summary',

-- sides are known 'AFirst', 'DFirst', 'AFirstHalfPoints', 'DFirstHalfPoints', 'AFirstPointsTotal', 'DFirstPointsTotal', -- sides are not known 'Team1', 'Team2', 'Team1PointsTotal', 'Team2PointsTotal'

}

local GAME_FIELDS = { 'Map', 'Blue', 'Red', 'BlueFirstRounds', 'RedFirstRounds', 'BlueSecondRounds', 'RedSecondRounds', 'BlueRounds', 'RedRounds', 'Winner', 'FF', 'Selection', 'MatchHistory', 'Recap', 'Vod', 'VodPB', 'VodGameStart', 'VodPostgame', 'VodHighlights', 'VodInterview', 'InterviewWith', 'MVP', 'Summary' }

local ALLOWED_TZ = { PST = true, KST = true, CET = true }

local arg_sep = '%s*;;;%s*'

local h = {} local p = {} function p.start(frame) i18n.init('MatchSchedule') local args = util_args.merge local tbl = mw.html.create args.tab = args.tab and Phase(args.tab):name or 'Matches' tbl:tag('tr'):tag('th'):attr('colspan','50'):wikitext(args.tab) local tr = tbl:tag('tr') for _, v in ipairs(MATCH_FIELDS) do		tr:tag('th'):wikitext(i18n.print(v)) end for _, v in ipairs(GAME_FIELDS) do		tr:tag('th'):wikitext(i18n.print(v)) end h.setStartVars(args) return ' ' end

function h.setStartVars(args) util_vars.setGlobalIndex('N_TabInPage') util_vars.resetGlobalIndex('N_MatchInTab') util_vars.setVar('OverviewPage', util_esports.getOverviewPage) util_vars.setVar('TabName', args.tab) util_vars.setVarOnlyIf('Bestof', args.bestof) util_vars.setVarOnlyIf('MVPPoints', args.mvppoints) util_vars.setVarOnlyIf('shownname', args.shownname) if args.phase then local thisphase = Phase(args.phase):name local lastphase = util_vars.getVar('Phase') if lastphase ~= thisphase then util_vars.setVar('Phase', thisphase) util_vars.setGlobalIndex('Phase_N') end end end

function h.getTZData(args) if not args.date then return {} end local time = ('%s %s'):format(args.date, args.time or '23:59') if not args.timezone or not ALLOWED_TZ[args.timezone] then error('Missing or invalid timezone. Allowed values: PST, CET, KST.') end local tz = args.timezone or 'PST' local dst = util_args.norm(args.dst) return util_time.getTimezones(time, tz, dst) end

function h.makeCargoMatchFields(args) local tzdata = h.getTZData(args) local tbl = { _table = 'MatchSchedule', Team1 = m_team.teamlinkname(args.team1), Team2 = m_team.teamlinkname(args.team2), Team1Points = args.team1points, Team2Points = args.team2points, Team1PointsTB = args.team1pointstb, Team2PointsTB = args.team2pointstb, DateTime_UTC = tzdata.UTC, HasTime = util_args.isDefined(args.time), OverviewPage = util_vars.getVar('OverviewPage'), Winner = args.winner, Team1Score = args.team1score, Team2Score = args.team2score, Team1Rounds = args.team1rounds, Team2Rounds = args.team2rounds, FF = args.ff, DST = args.dst, IsFlexibleStart = util_args.castAsBool(args.flex), BestOf = args.bestof or util_vars.getVar('Bestof'), ShownRound = args.round or util_vars.getVar('Round'), Phase = util_vars.getVar('Phase'), N_MatchInPage = util_vars.setGlobalIndex('MS_Overall'), Tab = util_vars.getVar('TabName'), N_MatchInTab = util_vars.setGlobalIndex('N_MatchInTab'), N_TabInPage = util_vars.getGlobalIndex('N_TabInPage'), N_Page = util_vars.getVar('N_Page'), Player1 = args.player1, Player2 = args.player2, Patch = Patch:_get('patch'), Disabled = Patch:_get('disabled'), Hotfix = Patch:_get('hotfix'), PatchFootnote = Patch:_get('footnote'), IsTiebreaker = util_args.castAsBool(args.istb), OverrideAllowPredictions = util_args.castAsBool(args.predictionoverride), Stream = args.stream, StreamDisplay = args.streamdisplay, Venue = args.venue, CastersPBP = args.pbp, CastersColor = args.color, Casters = h.getAllCasters(args.pbp, args.color), MVP = args.mvp, MVPPoints = args.mvppoints or util_vars.getVar('MVPPoints'), VodInterview = args.vodinterview, VodHighlights = args.vodhl, InterviewWith = args.with, Recap = args.recap, Reddit = args.reddit, Team1Footnote = args.team1footnote, Team2Footnote = args.team2footnote, Footnote = args.footnote, }	tbl.InitialN_MatchInTab = args.initialorder or tbl.N_MatchInTab tbl.InitialPageAndTab = args.initialtab local title = mw.title.getCurrentTitle.text tbl.UniqueMatch = title .. '_' .. tbl.Tab .. '_' .. tbl.N_MatchInTab tbl.ShownName = args.shownname or util_vars.getVar('shownname') or tbl.OverviewPage tbl.Team1Final = args.team1final and m_team.teamlinkname(args.team1final) or tbl.Team1 tbl.Team2Final = args.team2final and m_team.teamlinkname(args.team2final) or tbl.Team2 tbl.PageAndTeam1 = tbl.OverviewPage .. '_' .. tbl.Team1 tbl.PageAndTeam2 = tbl.OverviewPage .. '_' .. tbl.Team2 h.getMatchDay(tbl, tzdata.UTC, args.matchday) return tbl end

function h.getAllCasters(pbp, color) local tbl = {} tbl[#tbl+1] = pbp tbl[#tbl+1] = color return util_table.concat(tbl, ',') end

function h.getMatchDay(tbl, tzdata, gameday) local thistime = tonumber(util_time.unix(tzdata)) local lasttime = tonumber(util_vars.getVar('MatchTime') or '') local lastday = tonumber(util_vars.getVar('MatchDay') or '') util_vars.setVar('MatchTime', thistime) if gameday then tbl.MatchDay = gameday util_vars.setVar('MatchDay', gameday) elseif not lasttime then tbl.MatchDay = 1 util_vars.setVar('MatchDay', 1) elseif (thistime - lasttime) > 43200 then tbl.MatchDay = lastday + 1 util_vars.setVar('MatchDay', lastday + 1) else tbl.MatchDay = lastday util_vars.setVar('MatchDay', lastday) end end

function h.makeCargoGameFields(args, match) local tbl = {} for i, game in ipairs(args) do		tbl[i] = mw.clone(game) thisgame = tbl[i] -- alias for convenience thisgame._table = 'MatchScheduleGame' thisgame.Blue = game.Blue and m_team.teamlinkname(game.Blue) thisgame.Red = game.Red and m_team.teamlinkname(game.Red) thisgame.Map = game.Map and m_map.maplinkname(game.Map) thisgame.MVPPoints = thisgame.MVPPoints or match.MVPPoints thisgame.OverviewPage = match.OverviewPage thisgame.N_GameInMatch = i		thisgame.UniqueMatch = match.UniqueMatch thisgame.N_MatchInTab = match.N_MatchInTab thisgame.N_TabInPage = match.N_TabInPage thisgame.N_Page = match.N_Page thisgame.WrittenSummary = game.Summary h.setGameIds(thisgame, match) thisgame.UniqueLine = match.UniqueMatch .. '_' .. i		if thisgame.Selection then h.sideSelection(thisgame) end local allowed_teams = { [match.Team1] = true, [match.Team2] = true } if thisgame.Blue and not allowed_teams[thisgame.Blue] then error(('%s: Invalid blue team (%s) in match %s game %s'):format(match.Tab, thisgame.Blue, thisgame.N_MatchInTab, i)) end if thisgame.Red and not allowed_teams[thisgame.Red] then error(('%s: Invalid red team (%s) in game %s in match %s'):format(match.Tab, thisgame.Red, thisgame.N_MatchInTab, i)) end end return tbl end

function h.setGameIds(thisgame, match) thisgame.GameID_Wiki = ('%s_%s_%s_%s'):format(		match.OverviewPage,		match.Tab,		thisgame.N_MatchInTab,		thisgame.N_GameInMatch	) thisgame.ScoreboardID_Wiki = ('%s_%s_%s_%s'):format(		match.OverviewPage,		ScoreboardTab:_get('tab') or match.Tab,		ScoreboardTab:match(thisgame.N_GameInMatch) or thisgame.N_MatchInTab,		thisgame.N_GameInMatch	) end

function h.sideSelection(thisgame) if thisgame.Selection == '1' or thisgame.Selection == '2' then h.selectionFromNumber(thisgame, match) else thisgame.Selection = m_team.teamlinkname(thisgame.Selection) end end

function h.selectionFromNumber(thisgame) if thisgame.Selection == '1' then thisgame.Selection = thisgame.Blue elseif thisgame.Selection == '2' then thisgame.Selection = thisgame.Red end end

function h.makeMatchDisplay(tbl, args) local display = mw.clone(tbl) local tzdata = h.getTZData(args) display.Result = ("%s - %s"):format(		tbl.Winner == '1' and ("%s"):format(tbl.Team1Score or ) or tbl.Team1Score or ,		tbl.Winner == '2' and ("%s"):format(tbl.Team2Score or ) or tbl.Team2Score or 	) display.Team1 = m_team.plainlinked(tbl.Team1) display.Team2 = m_team.plainlinked(tbl.Team2) display.DateTime_UTC = tbl.DateTime_UTC and lang:formatDate('D Y-m-d H:i', tbl.DateTime_UTC) display.DateTime_PST = h.getTZDisplay(tzdata.PST) display.DateTime_CET = h.getTZDisplay(tzdata.CET) display.DateTime_KST = h.getTZDisplay(tzdata.KST) display.Points = (tbl.Team1Points or tbl.Team2Points) and ('%s - %s'):format(tbl.Team1Points or , tbl.Team2Points or ) display.PointsTB = (tbl.Team1PointsTB or tbl.Team2PointsTB) and ('%s - %s'):format(tbl.Team1PointsTB or , tbl.Team2PointsTB or ) display.PBP = util_esports.playersLinked(args.pbp) display.Color = util_esports.playersLinked(args.color) display.MVP = util_esports.playersLinked(args.mvp) display.With = util_esports.playersLinked(args.with) display.Reddit = args.reddit and ('[%s]'):format(args.reddit) display.Recap = args.recap and ('[%s]'):format(args.recap) display.VodInterview = args.vodinterview and ('[%s]'):format(args.vodinterview) display.VodHighlights = args.vodhl and ('[%s]'):format(args.vodhl) return display end

function h.getTZDisplay(str) if not str then return nil end return str:match('(%d%d:%d%d)') end

function h.makeGameDisplay(game) local display = mw.clone(game) display.Blue = game.Blue and m_team.short(game.Blue) or '' display.Red = game.Red and m_team.short(game.Red) or '' display.Map = m_map.linkedDisplay(game.Map) or '' display.Vod = h.printExternalLink(game.Vod) display.VodPB = h.printExternalLink(game.VodPB) display.VodGameStart = h.printExternalLink(game.VodGameStart) display.VodPostgame = h.printExternalLink(game.VodPostgame) display.VodHighlights = h.printExternalLink(game.VodHighlights) display.Recap = h.printExternalLink(game.recap) display.VodInterview = h.printExternalLink(game.VodInterview) display.MatchHistory = h.printExternalLink(game.MatchHistory) display.MVP = util_esports.playersLinked(game.MVP) display.InterviewWith = util_esports.playersLinked(game.InterviewWith) display.Summary = game.Summary and 'Y' or '' return display end

function h.printExternalLink(url) if not url then return nil end return ('[%s]'):format(url) end

function h.splitGameArgs(row) return util_args.splitArgs(row, GAME_ARGS, arg_sep) end

function h.castTeamsToBlueAndRed(row) -- we store as Blue and Red, regardless of whether we know who was AFirst -- and then we'll just save a flag saying "is Blue/Red random or actually known" if not (row.AFirst or row.Team1) then return end if not (row.DFirst or row.Team2) then return end row.Blue = m_team.teamlinkname(row.AFirst or row.Team1) row.Red = m_team.teamlinkname(row.DFirst or row.Team2) if not row.AFirst or not row.DFirst then -- in this case blue/red are random -- for now let's assume we can't know halftime points unless we also know sides each half -- string not bool cos Cargo row.IsBlueAttackerFirst = 'No' row.BlueRounds = row.Team1PointsTotal row.RedRounds = row.Team2PointsTotal return end -- now we know that blue is attacker first, and red is defender first row.IsBlueAttackerFirst = 'Yes' -- get totals if the user entered only stuff for each of the halves h.calculateAndAddSecondHalfPoints(row, 'AFirst') h.calculateAndAddSecondHalfPoints(row, 'DFirst') -- now we translate to blue and red (remember flag was saved a couple lines ago) row.BlueRounds = row.AFirstPointsTotal row.RedRounds = row.DFirstPointsTotal row.BlueFirstRounds = row.AFirstHalfPoints row.RedFirstRounds = row.DFirstHalfPoints row.BlueSecondRounds = row.AFirstSecondHalfPoints row.RedSecondRounds = row.DFirstSecondHalfPoints end

function h.calculateAndAddSecondHalfPoints(row, this) if not row[this .. 'HalfPoints'] or not row[this .. 'PointsTotal'] then return end row[this .. 'SecondHalfPoints'] = row[this .. 'PointsTotal'] - row[this .. 'HalfPoints'] end

function h.addGameTotalScoreToMatchAndValidate(match_tbl, games_cargo, ignorewarnings) local team1 = match_tbl.Team1 local team2 = match_tbl.Team2 local matchScores = { [team1] = match_tbl.Team1Score, [team2] = match_tbl.Team2Score, }	local gameScores = { [team1] = 0, [team2] = 0, hasTotals = false, }	h.countGameScores(gameScores, games_cargo) if match_tbl.Winner then h.validateTotalScores(matchScores, gameScores, match_tbl, ignorewarnings) elseif gameScores.hasTotals then match_tbl.Team1Score = gameScores[team1] match_tbl.Team2Score = gameScores[team2] end end

function h.countGameScores(gameScores, games_cargo) for _, game in ipairs(games_cargo) do		local winKey = (game.Winner == '1' and 'Blue') or (game.Winner == '2' and 'Red') if winKey then gameScores.hasTotals = true local winTeam = game[winKey] gameScores[winTeam] = (gameScores[winTeam] or 0) + 1 end end end

function h.validateTotalScores(matchScores, gameScores, match_tbl, ignorewarnings) if not gameScores.hasTotals then return end if ignorewarnings then return end if match_tbl.FF then return end for team, v in pairs(matchScores) do		v = v and tonumber(v) if gameScores[team] ~= v then error('Non-matching match & game scores for team ' .. team) end end end

function h.addGameRoundStuffToMatch(match_tbl, games_cargo) -- follow structure of h.addGameTotalScoreToMatchAndValidate, but with rounds -- this code is NOT reflected on LoL wiki local team1 = match_tbl.Team1 local team2 = match_tbl.Team2 local roundTotals = { [team1] = 0, [team2] = 0, }	h.countGameMatches(roundTotals, games_cargo) if roundTotals[team1] + roundTotals[team2] > 0 then match_tbl.Team1Rounds = roundTotals[team1] match_tbl.Team2Rounds = roundTotals[team2] end end

function h.countGameMatches(roundTotals, games_cargo) for _, game in ipairs(games_cargo) do		if game.Blue then roundTotals[game.Blue] = roundTotals[game.Blue] + (game.BlueRounds or 0) end if game.Red then roundTotals[game.Red] = roundTotals[game.Red] + (game.RedRounds or 0) end end end

function h.printMatchRow(display_match, games) if not games then games = { {} } end local ngames = #games local tbl = mw.html.create for i, game in ipairs(games) do		local tr = tbl:tag('tr') if i == 1 then for _, v in ipairs(MATCH_FIELDS) do				tr:tag('td'):attr('rowspan',ngames):wikitext(display_match[v]) end end h.printGameRow(tr, game) end return tbl end

function h.printGameRow(tr, game) for _, v in ipairs(GAME_FIELDS) do		tr:tag('td'):wikitext(game[v]) end return end

return p