Module:TournamentResults

local util_args = require('Module:ArgsUtil') local util_cargo = require('Module:CargoUtil') local util_esports = require('Module:EsportsUtil') local util_html = require('Module:HtmlUtil') local util_math = require('Module:MathUtil') local util_table = require('Module:TableUtil') local util_text = require('Module:TextUtil') local util_toggle = require('Module:ToggleUtil') local util_vars = require('Module:VarsUtil') local i18n = require('Module:i18nUtil') local Currency = require('Module:Currency') local m_team = require('Module:Team') local Placement = require('Module:PlacementClass') local PLAYER_SEP = '%s*;;%s*'

local ROWSPANS = { place = true, prize = true, usd = true, eur = true, prizepct = true, qual = true, points = true }

local CLASSES = { place = 'tournament-results-place', team = 'tournament-results-team', player = 'tournament-results-player', prize_display = 'tournament-results-prize', }

local CURRENCY_ORDER = { 'USD', 'Euros' }

local LINE_ARG_ORDER = { 'place', 'forcenewplace', 'sameplaces', 'date', 'prize', 'otherprize', 'prizepct', 'prizeunit', 'points', 'qual', 'quallink', 'qual2', 'qual2link', 'team', 'nocargo', 'date', 'rosterpage', 'groupstage', 'lastresult', 'lastopponent', 'lastopponentlink', 'lastopponentteam', 'lastoutcome', 'lastresultpoints', 'player', 'playerlink', 'hide', 'prpoints', 'players' }

local TOGGLES = { class = 'TRL_toggle%s', }

local PRIZE_TOGGLE_DATA = { order = {}, sep = ' &#8226; ', section = 'prizepool-togglers-currency', all = 'prizepool-currency-all', hiddenclass = 'prizepool-currency-hidden', }

local HAS_TOGGLES = false

local h = {} local p = {} function p.storeFromData(frame) i18n.init('TournamentResults') local args = util_args.merge(true) local rosterLookup = h.getRosterLookup(args) local processedArgs = h.getProcessedArgs(args) h.storeProcessedArgs(processedArgs) h.initCurrencyToggle(processedArgs) local cols = { 'place', 'prize_display', 'prizepct', 'points', 'prpoints', 'qual', 'team', 'players' } h.addNamesToCols(cols, args) local linesData = h.getLinesData(args, processedArgs, rosterLookup) h.storeCargo(linesData) return h.makeOutput(args, processedArgs, cols, linesData) end

function p.displayFromQuery(frame) i18n.init('TournamentResults') local args = util_args.merge(true) local processedArgs = h.queryProcessedArgs(args) or {} util_table.merge(processedArgs, args) processedArgs.totalprizenum = util_math.tonumber(processedArgs.totalprize) processedArgs.currency = Currency(processedArgs.prizeunit or 'USD') h.initCurrencyToggle(processedArgs) local cols = h.getCols(args, processedArgs) local linesData = h.queryLinesData(args, processedArgs) h.formatLinesDataFromCargo(linesData, processedArgs) return h.makeOutput(args, processedArgs, cols, linesData) end

function h.getRosterLookup(args) local query = h.getRosterQuery(util_esports.getOverviewPage(args.page)) return util_table.mapInPlace(util_cargo.getConstDict(query, 'UniqueLine', 'Roster'), util_text.split, '%s*;;%s*') end

function h.getRosterQuery(page) return { tables = 'TournamentRosters', where = ('_pageName="%s"'):format(page), fields = { 'UniqueLine', 'RosterLinks=Roster' }, limit = 9999, } end

function h.getProcessedArgs(args) h.checkConversions({ args.usdrate, args.eurorate }) local processedArgs = { _table = 'TournamentResultsArgs', prizeunit = args.prizeunit or 'USD', currency = Currency(args.prizeunit or 'USD'), totalprize = util_math.formatNumber(args.totalprize), totalprizenum = util_math.tonumber(args.totalprize), usdrate = tonumber(args.usdrate), eurorate = tonumber(args.eurorate), phase = args.phase, nocargo = util_args.castAsBool(args.nocargo), preload = args.preload or 'Team', showprize = util_args.castAsBool(args.prize), showusd = util_args.castAsBool(args.usdrate), showeuro = util_args.castAsBool(args.eurorate), showpoints = util_args.castAsBool(args.points), showqual = util_args.castAsBool(args.qual), showteam = util_args.castAsBool(args.showteam), showplayer = util_args.castAsBool(args.showplayer), combinequal = util_args.castAsBool(args.combinequal), }	processedArgs.hasconversion = processedArgs.usdrate or processedArgs.eurorate return processedArgs end

function h.checkConversions(tbl) for _, v in ipairs(tbl) do		if v and not tonumber(v) then error(i18n.print('invalid_currency', i18n.print('conversion_rate'))) end end end

function h.storeProcessedArgs(processedArgs) if mw.title.getCurrentTitle.nsText ~= 'Data' then return end processedArgs._table = 'TournamentResultsArgs' processedArgs.OverviewPage = util_esports.getOverviewPage util_cargo.store(processedArgs) end

function h.queryProcessedArgs(args) local query = { tables = 'TournamentResultsArgs', fields = { 'prizeunit', 'totalprize', 'totalprizenum', 'usdrate', 'eurorate', 'phase', 'nocargo', 'preload', 'showprize', 'showusd', 'showeuro', 'showpoints', 'showqual', 'showteam', 'showplayer', 'combinequal', 'hasconversion', 'showprpoints', },		where = ('OverviewPage="%s"'):format(util_esports.getOverviewPage(args.page)), types = { nocargo = 'boolean', showprpoints = 'boolean', showprize = 'boolean', showusd = 'boolean', showeuro = 'boolean', showpoints = 'boolean', showqual = 'boolean', showteam = 'boolean', showplayer = 'boolean', combinequal = 'boolean', hasconversion = 'boolean', }	}	return util_cargo.getOneRow(query) end

function h.initCurrencyToggle(processedArgs) local rates = { USD = processedArgs.usdrate, Euros = processedArgs.eurorate, }	for _, v in ipairs(CURRENCY_ORDER) do		if rates[v] then PRIZE_TOGGLE_DATA.order[#PRIZE_TOGGLE_DATA.order+1] = v		end end if next(PRIZE_TOGGLE_DATA.order) then table.insert(PRIZE_TOGGLE_DATA.order, 1, 'prize') HAS_TOGGLES = true end util_toggle.oflInit(PRIZE_TOGGLE_DATA) end

function h.getCols(args, processedArgs) local cols = { 'place', processedArgs.showprize and 'prize_display', h.pctCol(processedArgs), processedArgs.showpoints and 'points', processedArgs.showprpoints and 'prpoints', processedArgs.showqual and 'qual', (processedArgs.showteam or processedArgs.preload == 'Team') and 'team', 'players', }	util_table.removeFalseEntries(cols) h.addNamesToCols(cols, args) return cols end

function h.pctCol(processedArgs) if processedArgs.showprize and not processedArgs.nomoney then return 'prizepct' else return false end end

function h.addNamesToCols(cols, args) local names = h.getNames(args) cols.displays = {} for i, v in ipairs(cols) do cols.displays[i] = names[v] or i18n.print('col_' .. v)	end end

function h.getNames(args) return { points = args.pointstitle, team = args.teamtitle } end

function h.getLinesData(args, processedArgs, rosterLookup) local rowData = {} for i, v in ipairs(args) do		rowData[i] = h.processArgRow(v, processedArgs, rosterLookup) end h.determineRowspans(rowData) return rowData end

function h.processArgRow(str, processedArgs, rosterLookup) local thisLine = util_args.splitArgs(str, LINE_ARG_ORDER) local lineDisplay = h.makeDisplayLineFromArgs(thisLine, processedArgs, rosterLookup) lineDisplay.cargo = h.argsToCargoLine(thisLine, processedArgs, lineDisplay, rosterLookup) lineDisplay.playerCargo = h.argsToPlayerCargo(thisLine, processedArgs, lineDisplay, rosterLookup) return lineDisplay end

function h.makeDisplayLineFromArgs(thisLine, processedArgs, rosterLookup) util_vars.setGlobalIndex('TRL_line') thisLine.UniqueLine = util_cargo.getUniqueLine('TRL_line') thisLine.placeObject = Placement(thisLine.place) local lineDisplay = { place = Placement(thisLine.place):flair, placeraw = thisLine.place, team = thisLine.team and m_team.rightmediumlinked(thisLine.team), points = thisLine.points, qual = h.getQual(thisLine), forcenewplace = util_args.castAsBool(thisLine.forcenewplace), hide = util_args.castAsBool(thisLine.hide), prpoints = h.updatePRPoints(thisLine.prpoints), }	-- players can be split up like ID|Display;;ID|Display;;ID|Display -- so we need to split and create a list of just players and a list of just ids local players = h.splitPlayers(thisLine.players) util_vars.log(players.players) lineDisplay.players = h.getPlayerDisplay(		players.players,		rosterLookup[thisLine.UniqueLine]	) lineDisplay.ids = util_table.concat(players.ids, PLAYER_SEP) local prizes = h.getPrizeDisplay(thisLine, processedArgs) prizes.prize_display = prizes.prize_toggle or prizes.prize util_table.merge(lineDisplay, prizes) if processedArgs.combinequal then lineDisplay.points = lineDisplay.points or lineDisplay.qual or '' end return lineDisplay end

function h.updatePRPoints(points) if points then return util_vars.setVar('tr_prpoints', points) else return util_vars.getVar('tr_prpoints') end end

function h.splitPlayers(players) local splitPlayers = util_text.split(players, ';;') local ret = { players = {}, ids = {} } for i, playerInput in ipairs(splitPlayers) do		local id, player = playerInput:match('^%s*(.-)%s*%|%s*(.-)%s*$') if id then ret.players[i] = player ret.ids[i] = id		else ret.players[i] = playerInput ret.ids[i] = playerInput end end return ret end

function h.getPlayerDisplay(players, lookupRoster) if players then return util_table.concat(players, ', ', util_esports.playerLinked) end if not lookupRoster then return nil end return util_table.concat(lookupRoster, ', ', util_esports.playerLinked) end

function h.getQual(thisLine) local quals = util_args.numberedArgsToTable(thisLine, 'qual') or {} thisLine.qual1link = thisLine.quallink for i, qual in ipairs(quals) do local link = thisLine['qual' .. i .. 'link'] quals[i] = util_text.intLink(link, qual) end return util_table.concat(quals,' ') end

function h.getPrizeDisplay(thisLine, processedArgs) if not (thisLine.prize or thisLine.prizepct) then return { prize = thisLine.otherprize } end local prizeNum = h.getPrizeNum(thisLine, processedArgs) if not prizeNum then return { prize = thisLine.otherprize, prizepct = thisLine.prizepct .. '%' }	end local prizes = { prize = h.getPrize(prizeNum, thisLine.otherprize, processedArgs.currency), -- usd_number and eur_number will be stored in Cargo -- and therefore must be pure number values -- below, USD and Euros params are for display usd_number = h.getPrizeConverted(prizeNum, processedArgs.prizeunit, processedArgs.usdrate, 'USD'), eur_number = h.getPrizeConverted(prizeNum, processedArgs.prizeunit, processedArgs.eurorate, 'Euro'), prizepct = h.getPrizePct(prizeNum, thisLine, processedArgs), }	prizes.USD = h.getPrize(prizes.usd_number, thisLine.otherprize, Currency('usd')) prizes.Euros = h.getPrize(prizes.eur_number, thisLine.otherprize, Currency('euro')) prizes.prize_toggle = h.getPrizeToggle(prizes, processedArgs.prizeunit) return prizes end

function h.getPrizeNum(thisLine, processedArgs) local prize = thisLine.prize if prize then if not util_math.tonumber(prize) then error(i18n.print('invalid_currency',i18n.print('prize'))) end return util_math.tonumber(prize) end local prizepct = thisLine.prizepct local totalprize = processedArgs.totalprizenum if totalprize then return util_math.roundnum(prizepct / 100 * totalprize, .01) end return nil end

function h.getPrize(prizeNum, otherprize, prizeunit) -- concatenate the number along with the otherprize -- this is generic and can be used for "real" prize or a converted currency local fullPrize = { prizeunit:short(prizeNum), otherprize }	return util_table.concat(fullPrize, ' + ',nil,2) end

function h.getPrizeConverted(prize, prizeunit, rate, unitToGet) if prizeunit == unitToGet then return prize elseif prize and rate then return util_math.roundnum(prize * tonumber(rate), .01) else return nil end end

function h.getPrizePct(prizeNum, thisLine, processedArgs) if thisLine.prizepct then return thisLine.prizepct .. '%'	elseif processedArgs.totalprizenum then return util_math.roundnum((prizeNum or 0) / processedArgs.totalprizenum * 100, .01) .. '%'	else return nil end end

function h.getPrizeToggle(prizes, prizeunit) if not HAS_TOGGLES then return nil end local tbl = mw.html.create for _, v in ipairs(PRIZE_TOGGLE_DATA.order) do		util_toggle.oflCellClasses(tbl:tag('span'):wikitext(prizes[v]), PRIZE_TOGGLE_DATA, v)	end return tostring(tbl) end

function h.argsToCargoLine(thisLine, processedArgs, lineDisplay, rosterLookup) if util_args.castAsBool(processedArgs.nocargo) or util_args.castAsBool(thisLine.nocargo) then return end local cargoData = { _table = 'TournamentResults', Event = util_vars.getVar('Event Name'), Tier = util_vars.getVar('Event Tier'), Date = thisLine.date or util_vars.getVar('Event Date'), Place = util_math.deserialize(thisLine.place) or thisLine.place, Place_Number = thisLine.placeObject:number, ForceNewPlace = thisLine.forcenewplace, Hide = thisLine.hide, Prize = thisLine.prize, PrizeUnit = processedArgs.prizeunit, Prize_Markup = thisLine.prize and processedArgs.currency:short(thisLine.prize), Prize_USD = lineDisplay.usd_number, Prize_Euro = lineDisplay.eur_number, PrizeOther = thisLine.otherprize, Team = thisLine.team and m_team.teamlinkname(thisLine.team), UniqueLine = thisLine.UniqueLine, Phase = processedArgs.phase, OverviewPage = util_esports.getOverviewPage, PRPoints = lineDisplay.prpoints, Points = thisLine.points, }	util_table.merge(cargoData, h.getMedalData(cargoData.Place_Number)) if cargoData.Team then cargoData.PageAndTeam = h.getRosterPage(thisLine.rosterpage) .. '_' .. cargoData.Team end local players = h.getCargoPlayers(thisLine, rosterLookup) util_table.merge(cargoData, players) -- return a table containing the table because maybe we want 2 dif cargo tables -- storing per line, e.g. TournamentResults & TournamentRosters return { cargoData } end

function h.getMedalData(Place_Number) local places = { 'Gold', 'Silver', 'Bronze', 'Copper' } if places[tonumber(Place_Number) or ''] then return { [places[tonumber(Place_Number)]] = 1 } end return {} end

function h.getRosterPage(rosterpage) return rosterpage or util_esports.getOverviewPage end

function h.getCargoPlayers(thisLine, rosterLookup) local players = h.pickCargoPlayers(thisLine.players, rosterLookup[thisLine.UniqueLine]) if not players then return {} end local ret = { Roster = util_table.concat(players.players, ';;'), RosterLinks = util_table.concat(players.players, ';;', util_esports.playerLink), RosterIds = util_table.concat(players.ids, ';;', util_esports.playerLink), }	return ret end

function h.pickCargoPlayers(fromHere, fromCargo) if fromHere then return h.splitPlayers(fromHere) end return fromCargo end

function h.argsToPlayerCargo(thisLine, processedArgs, lineDisplay, rosterLookup) -- this table isn't intended to be used on-wiki anywhere, but it's needed -- for external scripts to be remotely sane, because list-type fields aren't	-- sufficiently powerful to allow for querying & working with player objects. -- in theory it COULD be used for on-wiki stuff as well at some point, but -- that would require a pretty substantial rewrite of a lot of code, and that's	-- not really planned. but the important part is that even if you don't see this -- used anywhere on-wiki, it's still super important to maintain this for -- off-wiki scripts that update/sync fortnite IDs on player pages. -- let this table be a lesson that list-type fields are REALLY REALLY REALLY never -- going to be powerful enough for whatever it is that you want to do, and as soon -- as you have more than one list-type field pertaining to the same object in a	-- "parent" table, you'd better refactor everything and create a for-realsies -- child table, or future you will be stuck doing that. because now I have two -- completely different data models both in production, and both of them are -- going to have to be maintained for virtually forever. kinda sucks. local ret = {} local players = h.pickCargoPlayers(thisLine.players, rosterLookup[thisLine.UniqueLine]) if not players then return {} end if not players.players then return {} end local team = thisLine.team and m_team.teamlinkname(thisLine.team) if team then ret.PageAndTeam = h.getRosterPage(thisLine.rosterpage) .. '_' .. team end ret.OverviewPage = util_esports.getOverviewPage for i, v in ipairs(players.players) do		ret[#ret+1] = { OverviewPage = ret.OverviewPage, PageAndTeam = ret.PageAndTeam, Player = v,			FortniteId = players.ids[i], UniqueResult = thisLine.UniqueLine, _table = 'TournamentResultsPlayers', }	end return ret end

function h.determineRowspans(rowData) local curPlace, curRow, count for _, row in ipairs(rowData) do		if curPlace ~= row.placeraw or row.forcenewplace then if curRow then curRow.rowspan = count end curRow = row count = 1 curPlace = row.placeraw else count = count + 1 end end if count or 0 > 1 then curRow.rowspan = count end end

-- cargo function h.storeCargo(linesData) if h.doWeStoreCargo then for _, line in ipairs(linesData) do			if line.cargo then for _, tbl in ipairs(line.cargo) do					util_cargo.store(tbl) for _, playerCargo in ipairs(line.playerCargo) do						util_cargo.store(playerCargo) end end end end end end

function h.doWeStoreCargo return mw.title.getCurrentTitle.nsText == 'Data' end

-- cargo query linesData function h.queryLinesData(args, processedArgs) local query = { tables = 'TournamentResults', where = ('OverviewPage="%s"'):format(util_esports.getOverviewPage(args.page)), fields = { 'Place', 'Place=placeraw', 'ForceNewPlace=forcenewplace', 'Prize=prize_number', 'Prize_Markup=prize', 'Prize_USD=usd_number', 'Prize_Euro=eur_number', 'Team', 'Hide', 'Points=points', 'PRPoints=prpoints', 'Roster=players', },		orderBy = 'Place_Number', types = { Hide = 'boolean', }	}	return util_cargo.queryAndCast(query) end

function h.formatLinesDataFromCargo(linesData, processedArgs) h.determineRowspans(linesData) for _, row in ipairs(linesData) do		h.formatOneLineFromCargo(row, processedArgs) end end

function h.formatOneLineFromCargo(row, processedArgs) row.place = Placement(row.Place):flair row.team = row.Team and m_team.rightmediumlinked(row.Team) row.hide = row.Hide row.USD = processedArgs.currency:short(row.usd_number) row.Euros = processedArgs.currency:short(row.eur_number) row.prize_toggle = h.getPrizeToggle(row, processedArgs.prizeunit) row.prize_display = row.prize_toggle row.prizepct = h.getPrizePct(row.prize_number, row, processedArgs) row.players = util_args.splitMapConcat(row.players, PLAYER_SEP, util_esports.playerLinked,', ') end

-- make output function h.makeOutput(args, processedArgs, cols, linesData) h.initializeToggleData local output = mw.html.create output:wikitext(h.introSentence(args, processedArgs)) h.printCurrencyToggles(output) h.printTable(output, cols, linesData) h.creditCurrencyRates(output, args, processedArgs) return output end

function h.initializeToggleData local i = util_vars.setGlobalIndex('TRL_i') TOGGLES.class = TOGGLES.class:format(i) end

function h.introSentence(args, processedArgs) if not args.totalprize then return '' end local tempdate = util_args.nilToFalse(args.tempprizedate) local sentenceParts = { processedArgs.currency:long(processedArgs.totalprize), tempdate and i18n.print('temp_date', tempdate), i18n.print('intro'), }	return util_table.concat(sentenceParts, ' ') end

function h.printCurrencyToggles(output) if not HAS_TOGGLES then return end local div = output:tag('div'):addClass('toggle-button') util_toggle.printOptionFromListTogglers(div, PRIZE_TOGGLE_DATA) end

function h.printTable(output, cols, linesData) local tbl = output:tag('table') :addClass('wikitable2') :addClass('tournament-results') :addClass('zebra-manual') h.printStartCols(tbl, cols) local hidden = h.printLines(tbl, cols, linesData) if hidden then h.endHiddenResults(tbl, #cols) end end

function h.printStartCols(tbl, cols) local tr = tbl:tag('tr') for i, v in ipairs(cols) do		tr:tag('th') :wikitext(cols.displays[i]) end end

function h.printLines(tbl, cols, linesData) local hidden = false local rowspanLeft = 0 local odd = false for _, row in ipairs(linesData) do		if row.hide then hidden = true h.printShowButton(tbl, #cols) end local tr = tbl:tag('tr') if hidden then util_toggle.sepCellClasses(tr, TOGGLES) end local isFullRow = h.printOneLine(tr, cols, row, rowspanLeft, odd) if rowspanLeft == 1 then odd = not odd end if odd then tr:addClass('zebra-manual-odd') end if row.rowspan then rowspanLeft = row.rowspan else rowspanLeft = rowspanLeft - 1 end end return hidden end

function h.printShowButton(tbl, colspan) local tr = tbl:tag('tr') local th = tr:tag('th') :attr('colspan', colspan) util_toggle.printSepToggler(th, TOGGLES) util_toggle.sepCellClasses(tr, TOGGLES, true) end

function h.printOneLine(tr, cols, lineDisplay, rowspanLeft, odd) local isFullRow = false for _, col in ipairs(cols) do		if rowspanLeft > 1 and ROWSPANS[col] then -- pass else local td = tr:tag('td') :wikitext(lineDisplay[col]) :addClass(CLASSES[col]) if ROWSPANS[col] then td:attr('rowspan', lineDisplay.rowspan) end end end end

function h.endHiddenResults(tbl, colspan) local tr = tbl:tag('tr') local th = tr:tag('th') :attr('colspan', colspan) util_toggle.sepCellClasses(tr, TOGGLES, false) util_toggle.printSepToggler(th, TOGGLES, true) end

function h.creditCurrencyRates(output, args, processedArgs) if not (args.conversionsource and processedArgs.hasconversion) then return nil end local source = args.conversionsource:lower local date = args.conversiondate local rates = { USD = processedArgs.usdrate, Euros = processedArgs.eurorate, }	local originalUnit = processedArgs.currency output:wikitext(i18n.print('credit_' .. source,date or i18n.print('unknown_date'))) local ul = output:tag('ul') for _, v in ipairs(CURRENCY_ORDER) do		if rates[v] then ul:tag('li') :wikitext(originalUnit:long(1)) :wikitext(' = ') :wikitext(Currency(v):long(rates[v])) end end return output end

return p