Call of Duty Esports Wiki
Advertisement

Documentation for this module may be created at Module:CurrentRostersPortal/doc

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_map = require("Module:MapUtil")
local util_news = require("Module:NewsUtil")
local util_source = require("Module:SourceUtil")
local util_table = require("Module:TableUtil")
local util_text = require("Module:TextUtil")
local util_title = require("Module:TitleUtil")
local util_vars = require("Module:VarsUtil")
local i18n = require('Module:i18nUtil')

local ListOfRoles = require('Module:ListOfRoles')
local OD = require('Module:OrderedDict')

local m_team = require('Module:Team')
local Role = require('Module:Role')

local PARITY
local OVER = false

local PRELOADS_TO_IGNORE = {
	'opportunities', 'set_to_leave', 'to_main_also_stay', 'to_academy_also_stay', 'gcd_remove_notleave',
	join = {},
	leave = {},
}

local h = {}

local p = {}

function p.intro(frame)
	local args = util_args.merge()
	util_vars.setVar('season1', args[1])
	util_vars.setVar('season2', args[2])
	return '<!-- -->'
end

function p.main(frame)
	local args = util_args.merge()
	i18n.init('CurrentRostersPortal')
	h.castArgs(args)
	h.setConstants(args)
	local playersBefore = util_news.getPlayersFromArg(args.players)
	h.addTagsToPlayersBefore(playersBefore)
	local processed = h.getProcessedDataFromPlayers(playersBefore)
	local listOfChanges = h.queryForChanges(args, playersBefore)
	h.formatDataFromQuery(listOfChanges)
	local netStatuses = h.computeNetStatuses(listOfChanges)
	h.addChangesToProcessedPlayersList(processed, netStatuses)
	h.moveJoiningPlayersToEmptySlots(processed)
	return h.makeOutput(processed, args)
end

function h.castArgs(args)
	args.team = m_team.teamlinkname(args.team)
	args.team2 = args.team2 and m_team.teamlinkname(args.team2)
	args.over = util_args.castAsBool(args.over)
end

function h.setConstants(args)
	OVER = args.over
end

function h.addTagsToPlayersBefore(playersBefore)
	h.mergeTagsIntoPlayersBefore(playersBefore, h.queryTagsForPlayersBefore(playersBefore))
end

function h.mergeTagsIntoPlayersBefore(playersBefore, newData)
	for _, player, playerData in ipairs(playersBefore) do
		util_table.merge(playerData, newData[mw.ustring.lower(player)])
	end
end

function h.queryTagsForPlayersBefore(playersBefore)
	return util_cargo.getRowDict(h.getBeforeTagsQuery(playersBefore), 'PlayerPageOrRedirect')
end

function h.getBeforeTagsQuery(playersBefore)
	local query = {
		tables = { 'Players=P', 'PlayerRedirects=PR' },
		join = { 'P._pageName=PR._pageName' },
		where = h.getBeforeTagsWhere(playersBefore),
		fields = {
			'Residency [region]',
			'PR.AllName=PlayerPageOrRedirect [unicodelowercase]',
		},
	}
	return query
end

function h.getBeforeTagsWhere(playersBefore)
	return playersBefore:formatAndConcatKeys(' OR ', 'PR.AllName="%s"')
end

-- args for before
function h.getProcessedDataFromPlayers(playersBefore)
	local processed = {
		playerLookup = {},
		byRole = h.initProcessedByRole()
	}
	for _, _, playerData in ipairs(playersBefore) do
		h.addBeforePlayerToRole(processed, playerData)
	end
	return processed
end

function h.initProcessedByRole()
	local ret = OD()
	for _, role in ipairs(ListOfRoles.current_rosters_list) do
		ret:set(role, {})
	end
	return ret
end

function h.addBeforePlayerToRole(processed, playerData)
	if not processed.byRole:get(playerData.Role) then
		h.initProcessedByRoleForUntrackedRole(processed.byRole, playerData.Role)
	end
	local roleData = processed.byRole:get(playerData.Role)
	roleData[#roleData+1] = { before = playerData }
	processed.playerLookup[playerData.PlayerLink] = {
		role = playerData.Role,
		i = #roleData,
		Residency = playerData.Residency
	}
end

function h.initProcessedByRoleForUntrackedRole(byRole, role)
	byRole:set(role, { untracked = true })
end

-- query for after
function h.queryForChanges(args, playersBefore)
	return util_cargo.queryAndCast(h.getChangesQuery(args, playersBefore))
end

function h.getChangesQuery(args, playersBefore)
	local query = {
		fields = {
			'RC.Date_Sort',
			'RC.Player',
			'PR._pageName=PlayerLink',
			'RC.Team',
			'RC.Role',
			'RC.Source',
			'RC.Preload',
			'RC.Direction',
			'News.Date_Display',
			'News.Region',
			'News._pageName',
			'News.NewsId',
			'P.Residency [region]',
		},
		tables = {
			'RosterChanges=RC',
			'NewsItems=News',
			'PlayerRedirects=PR',
			'Players=P',
			'RosterChangePortalDates=Dates',
		},
		join = {
			'RC.NewsId=News.NewsId',
			'RC.Player=PR.AllName',
			'PR._pageName=P._pageName',
			'News.Region=Dates.Region'
		},
		where = h.getChangesWhere(args, playersBefore),
		orderBy = 'RC.Date_Sort ASC, News.N_LineInDate ASC, RC.N_LineInNews ASC',
	}
	return query
end

function h.getChangesWhere(args, playersBefore)
	local tbl = {
		h.getTeamWhereCondition(args),
		'PR._pageName IS NOT NULL',
		'News.ExcludePortal IS NULL OR News.ExcludePortal != "1"',
		-- disallow trainees here. TODO: put this into PRELOADS_TO_IGNORE
		'RC.RoleModifier != "Trainee" OR RC.RoleModifier IS NULL',
		util_news.getExcludedPreloadsWhereCondition(PRELOADS_TO_IGNORE),
	}
	util_table.mergeArrays(tbl, h.getDateWhereCondition(args))
	return util_cargo.concatWhere(tbl)
end

function h.getTeamWhereCondition(args)
	local tbl = {
		('RC.Team="%s"'):format(args.team),
		util_cargo.whereFromArg('RC.Team="%s"', args.team2)
	}
	return util_cargo.concatWhereOr(tbl)
end

function h.getDateWhereCondition(args)
	local ret = {
		('Dates.PeriodName="%s"'):format(args.period),
		'RC.Date_Sort > Dates.DateStart',
		'RC.Date_Sort < Dates.DateEnd',
	}
	return ret
end

function h.formatDataFromQuery(listOfChanges)
	util_map.rowsInPlace(listOfChanges, h.formatOneRowOfDataFromQuery)
end

function h.formatOneRowOfDataFromQuery(row)
	if not row.Direction then
		error(i18n.print('error_missingDirection', row.NewsId))
	end
	for _, v in ipairs({ 'Role', 'Team' }) do
		row[v .. row.Direction] = row[v]
	end
end

function h.computeNetStatuses(listOfChanges)
	local currentPlayerStatuses = {}
	for _, row in ipairs(listOfChanges) do
		currentPlayerStatuses[row.PlayerLink] = row
	end
	return currentPlayerStatuses
end

-- concat before & after data
function h.addChangesToProcessedPlayersList(processed, netStatuses)
	for player, status in pairs(netStatuses) do
		h.updatePlayerWithStatus(processed, player, status)
	end
end

function h.updatePlayerWithStatus(processed, player, status)
	local oldIndex = processed.playerLookup[player]
	if not oldIndex then
		h.addNewAfterPlayer(processed.byRole, status, 'join')
		return
	end
	h.updateOldLocation(
		processed.byRole,
		processed.byRole:get(oldIndex.role)[oldIndex.i],
		status
	)
end

function h.addNewAfterPlayer(byRole, status, statusAfter)
	-- only actually add something if the player is on the team at the end
	if not status.TeamJoin then return end
	h.addNewAfterPlayerToLocation(byRole:get(status.RoleLeave or status.RoleJoin), status, statusAfter)
end

function h.addNewAfterPlayerToLocation(location, status, statusAfter)
	if not location then return end
	location[#location+1] = { after = status, statusAfter = statusAfter }
end

function h.updateOldLocation(byRole, row, status)
	if not status.TeamJoin or status.Preload == 'loaned_to' then
		row.statusBefore = 'leave'
		return
	end
	if status.RoleJoin ~= row.before.Role then
		row.statusBefore = 'roleswap'
		h.addNewAfterPlayer(byRole, status, 'roleswap')
		return
	end
	row.after = status
	row.skip = true
	row.statusBefore = 'stay'
end

function h.moveJoiningPlayersToEmptySlots(processed)
	for _, role, roleData in ipairs(processed.byRole) do
		h.moveJoiningPlayersToEmptySlotsInRole(roleData)
	end
end

function h.moveJoiningPlayersToEmptySlotsInRole(roleData)
	local emptySlots = h.getListOfEmptySlots(roleData)
	local rowsToRemove = {}
	for i, row in ipairs(roleData) do
		if not row.before and next(emptySlots) then
			rowsToRemove[#rowsToRemove+1] = i
			local oldSlot = table.remove(emptySlots, 1)
			util_table.merge(roleData[oldSlot], row)
		end
	end
	h.removeConsolidatedRows(roleData, rowsToRemove)
end

function h.getListOfEmptySlots(roleData)
	local emptySlots = {}
	for i, row in ipairs(roleData) do
		if row.statusBefore == 'leave' or row.statusBefore == 'roleswap' then
			emptySlots[#emptySlots+1] = i
		end
	end
	return emptySlots
end

function h.removeConsolidatedRows(roleData, rowsToRemove)
	for i = #rowsToRemove, 1, -1 do
		table.remove(roleData, rowsToRemove[i])
	end
end

-- print
function h.makeOutput(processed, args)
	local output = mw.html.create()
	local tbl = output:tag('table')
		:addClass('wikitable2')
		:addClass('rosterswap-current')
	h.printHeading(tbl, args)
	h.printRoles(tbl, processed)
	return output
end

function h.printHeading(tbl, args)
	h.printTopLevelHeading(tbl, args)
	h.printSeasons(tbl)
end

function h.printTopLevelHeading(tbl, args)
	if args.team2 then
		h.printHeadingTwoTeams(tbl, args)
		return
	end
	h.printHeadingOneTeam(tbl, args)
end

function h.printHeadingTwoTeams(tbl, args)
	local tr = tbl:tag('tr')
	tr:tag('th'):attr('rowspan',2)
		:addClass('rosterswap-current-teamlogo-corner')
	tr:tag('th')
		:wikitext(m_team.onlyimagelinked(args.team))
		:addClass('rosterswap-current-old')
	tr:tag('th')
		:wikitext(m_team.onlyimagelinked(args.team2))
		:addClass('rosterswap-current-new')
end

function h.printHeadingOneTeam(tbl, args)
	local tr = tbl:tag('tr')
	tr:tag('th'):attr('rowspan',2):wikitext(m_team.onlyimagelinked(args.team))
		:addClass('rosterswap-current-teamlogo-corner')
	tr:tag('th'):attr('colspan',2):wikitext(m_team.mediumplainlinked(args.team))
end

function h.printSeasons(tbl)
	local tr = tbl:tag('tr')
	tr:tag('th')
		:addClass('rosterswap-current-season')
		:wikitext(util_vars.getVar('season1') or i18n.print('before'))
	tr:tag('th')
		:addClass('rosterswap-current-season')
		:addClass('rosterswap-current-new')
		:wikitext(util_vars.getVar('season2') or i18n.print('after'))
end

function h.printRoles(tbl, processed)
	for i, role, data in ipairs(processed.byRole) do
		PARITY = i % 2
		h.printOneRole(tbl, role, data)
	end
end

function h.printOneRole(tbl, role, data)
	if data.untracked then return end
	local tr = tbl:tag('tr')
		:addClass('rosterswap-current-firstline')
	h.printRowParityClass(tr)
	h.printRowLabel(tr, role, data)
	h.printRolePlayers(tbl, tr, data)
end

function h.printRowParityClass(tr)
	tr:addClass('rosterswap-current-parity-' .. PARITY)
end

function h.printRowLabel(tr, role, data)
	local td = tr:tag('td')
		:wikitext(Role(role):get('portal'))
	if #data == 0 then return end
	td:attr('rowspan', #data)
	td:addClass('rosterswap-current-rolename')
end

function h.printRolePlayers(tbl, tr, data)
	if #data == 0 then
		h.printEmptyRow(tr)
		return
	end
	h.printNonemptyRow(tbl, tr, data)
end

function h.printEmptyRow(tr)
	h.printOneRolePlayer(tr, {})
end

function h.printNonemptyRow(tbl, tr, data)
	for i, row in ipairs(data) do
		if i == 1 then
			h.printOneRolePlayer(tr, row)
		else
			tr = tbl:tag('tr')
			h.printRowParityClass(tr)
			h.printOneRolePlayer(tr, row)
		end
	end
end

function h.printOneRolePlayer(tr, row)
	h.printBefore(tr, row, row.statusBefore, row.before)
	h.printAfter(tr, row, row.statusAfter, row.after)
end

function h.printBefore(tr, row, status, data)
	local td = tr:tag('td')
	:addClass('rosterswap-current-old')
	if not data then return end
	td:addClass(h.getStatusClass(status))
	h.setCellAsStayIfNeeded(td, status, row)
	h.printPlayer(td, status, data)
end

function h.getStatusClass(status)
	if not status then return nil end
	return ('rosterswap-current-%s'):format(status)
end

function h.setCellAsStayIfNeeded(td, status, row)
	if status == 'leave' then return end
	if status == 'stay' or (OVER and not row.after) then
		h.setCellAsStay(td)
	end
end

function h.setCellAsStay(td)
	td:attr('colspan', 2)
end

function h.printAfter(tr, row, status, data)
	if h.doWeSkipAfter(row, data) then return end
	local td = tr:tag('td')
		:addClass('rosterswap-current-new')
	if not data then return end
	h.printPlayer(td, status, data)
end

function h.doWeSkipAfter(row, data)
	if row.skip then return true end
	if data then return false end
	if row.status == 'leave' then return false end
	return OVER
end

function h.printPlayer(td, status, playerData)
	td:addClass(h.getStatusClass(status))
	if playerData.Residency then
		td:wikitext(playerData.Residency:image())
	end
	td:wikitext(util_esports.playerLinked(playerData.Player))
end

return p
Advertisement