Fortnite Esports Wiki

Power Rankings of Brazil and Oceania at the moment are not updated to the last two competitive seasons, so the current positions of the players are not 100% accurate.

READ MORE

Fortnite Esports Wiki
Advertisement

Documentation for this module may be created at Module:PlayerTeamHistoryAbstract/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_math = require("Module:MathUtil")
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_time = require("Module:TimeUtil")
local util_timedelta = require("Module:TimedeltaUtil")
local util_vars = require("Module:VarsUtil")
local i18n = require('Module:i18nUtil')

local p = require('Module:GroupedRosterChangesAbstract'):extends()
local h = {}

function p:init(name)
	self:super('init', name)
	self.STATUSES_TO_IGNORE = {
		set_to_leave = true,
		opportunities = true,
	}
	self.PRELOADS_TO_IGNORE = {
		'extended', 'gcd_extended', 'remain', 'expire_notleave', 'set_to_leave', 'opportunities', 'set_to_leave_already_joined', 'gcd_expire_notleave', 're_sign', 'gcd_remove_notleave',
		leave = { 'retirement_no_team' },
		join = { 'gcd_expire_notleave_yet', },
	}
	self.INCLUDE_STATUS = false
	self.DEBUG = false
	self.CONTRACT_DATES = {}	
end

function p:run(args)
	util_cargo.setStoreNamespace('')
	h.castArgs(args)
	self:setConstants(args)
	self:setDebugIfEnabled()
	return self:super('run', args)
end

function h.castArgs(args)
	args.player = util_args.strOrTitle(args.player)
end

function p:setConstants(args)
	if util_args.castAsBool(args.debug) then
		self.DEBUG = true
	end
end

function p:setDebugIfEnabled()
	if self.DEBUG then
		self.COLUMNS[#self.COLUMNS+1] = 'KeyJoin'
		self.COLUMNS[#self.COLUMNS+1] = 'KeyLeave'
		self.COLUMNS[#self.COLUMNS+1] = 'PreloadJoin'
		self.COLUMNS[#self.COLUMNS+1] = 'PreloadLeave'
		self.COLUMNS[#self.COLUMNS+1] = 'SisterTeamPage'
		self.COLUMNS[#self.COLUMNS+1] = 'Status'
		self.COLUMNS[#self.COLUMNS+1] = 'ContractEnd'
		self.COLUMNS[#self.COLUMNS+1] = 'NameOld'
		self.COLUMNS[#self.COLUMNS+1] = 'changeIndex'
	end
end

function p:getTables()
	local ret = {
		'NewsItems__Players',
		'NewsItems=News',
		'PlayerRedirects=PR',
		'RosterChanges=RC',
		'PlayerRenames=RN',
		'ResidencyChanges=ResChange',
		'Contracts',
		'TeamRedirects=TRed1', -- join from contracts to ST1
		'TeamRedirects=TRed2', -- join from news to ST2
		'SisterTeams=ST1', -- the sister team page (if one exists) of the contract team
		'SisterTeams=ST2', -- the sister team page (if one exists) of the news team
	}
	return ret
end

function p:getJoin()
	-- remember this join is JUST for retrieving fields
	-- so we dont have to worry about one-to-many conditions really, because nothing is happening
	-- in any where conditions
	-- so all of the redirects and stuff just discovering the target
	-- not then also joining to another field making a many:many
	local ret = {
		-- joins to associate all of the relevant news in order
		'NewsItems__Players._rowID=News._ID',
		'NewsItems__Players._value=PR.AllName',
		'News.NewsId=RC.NewsId',
		'News.NewsId=RN.NewsId',
		'News.NewsId=ResChange.NewsId',
		'News.NewsId=Contracts.NewsId',
		
		-- contracts need to respect contracts from the same org
		'Contracts.Team=TRed1.AllName',
		'TRed1._pageName=ST1.Team',
		
		-- roster changes need to discover the org so we can pull contracts
		'RC.Team=TRed2.AllName',
		'TRed2._pageName=ST2.Team',
	}
	return ret
end

function p:getWhere(args)
	local tbl = {
		('PR._pageName="%s"'):format(args.player),
		util_news.getExcludedPreloadsWhereCondition(self.PRELOADS_TO_IGNORE),
	}
	util_table.mergeArrays(tbl, h.getCorrectPlayerWhereConditions())
	return util_cargo.concatWhere(tbl)
end

function h.getCorrectPlayerWhereConditions()
	local tablesNeedingConditions = { 'RC', 'Contracts' }
	return util_map.inPlace(tablesNeedingConditions, h.getOneCorrectPlayerWhereCondition)
end

function h.getOneCorrectPlayerWhereCondition(tbl)
	return ('%s.Player=NewsItems__Players._value OR %s.Player IS NULL'):format(tbl, tbl)
end

function p:getFields()
	local fields = self:super('getFields')
	local newFields = {
		'RN.OriginalName=NameOld',
		'ResChange.ResidencyOld=ResidencyOld',
		'News._pageName=ResPageName',
		'Contracts.ContractEnd',
		'Contracts.IsRemoval=IsContractRemoval [boolean]',
		'TRed2._pageName=TeamPage',
		
		-- Here's a bunch of info about the SisterTeamPage field, which is ridiculous
		
		-- priority:
		--	first, we want the Contracts SisterTeams entry if it exists
		--	if not, then just try and follow the redirect of the team
		--	if not, then the Contracts Team entry if it exists
		--	if not, we're not in a Contracts news entry anymore
		--	so next, try and grab sister teams if it exists
		--	if not, then at least try and follow the redirect of the team
		--	finally, otherwise just use the news team
		
		-- the TRed2._pageName was added because of Froggy who moved from
		-- Dashing Buffalo to Saigon Buffalo with team_rename_same_page
		-- TRed1._pageName was added anticipating a similar issue
		'COALESCE(ST1._pageName, TRed1._pageName, Contracts.Team, ST2._pageName, TRed2._pageName, RC.Team)=SisterTeamPage',
	}
	return util_table.mergeArrays(fields, newFields)
end

function p:getGroupBy()
	return 'COALESCE(RC.RosterChangeId, News.NewsId)'
end

-- format raw data rows
function p:formatOneNonRCRow(row)
	if row.IsContractRemoval then
		self.CONTRACT_DATES[row.SisterTeamPage] = nil
	end
	if not row.ContractEnd then return end
	self.CONTRACT_DATES[row.SisterTeamPage] = row.ContractEnd
end

function p:formatOneRawDataRow(row)
	self:super('formatOneRawDataRow', row)
	
	-- dealing with contracts here will bring information *forward* when a player is on same sister team
	-- and we want to preserve the fact that they had a contract already
	-- this construction is done as we sort through the changes and apply formatting
	-- later on we'll go back through the entire thing a 2nd time to do "after-the-fact" information
	-- which will apply "normal" contract information, when contract info is stored *after* the
	-- joining-the-team roster change in question
	
	-- because maps happen forward, we can just do this in order
	-- CONTRACT_DATES is the repository of info we maintain moving forward
	local potentialContractDate = self.CONTRACT_DATES[row.SisterTeamPage]
	if row.Date_SortJoin and potentialContractDate and potentialContractDate > row.Date_SortJoin then
		row.ContractEnd = potentialContractDate
	end
end

function p:getKey(row)
	local key = ('%s%s%s%s'):format(
		row.Team or '',
		row.RoleModifier or 'Normal',
		row.Role or 'Unknown',
		self:getAndSetMeaningfulStatus(row)
	)
	return key
end

function p:getAndSetMeaningfulStatus(row)
	if self.STATUSES_TO_IGNORE[row.Status] then
		row.Status = nil
	end
	return row.Status or ''
end

-- group stuff
function p:updateAncillaryInformation(changesByLine, queryRow)
	-- this is now propagating stuff backwards
	-- for each new line we add, we go back through our entire output and check if we want to update anything
	-- this has to be done in order, not all at once in the end, because information changes over time
	-- so for the most part we'll go back and say, "what teams is the player CURRENTLY on at time of news?"
	-- and then update this piece of information (e.g. residency change) on all of them
	-- but we'll leave any rows alone that already have departures
	for _, outputRow in ipairs(changesByLine) do
		h.updateRowAncillaryInformationIfNeeded(outputRow, queryRow)
	end
end

function h.updateRowAncillaryInformationIfNeeded(outputRow, queryRow)
	if not outputRow.TeamLeave then
		-- information that needs to get updated when the player is CURRENTLY on
		-- the team. This includes contract information.
		h.updateAncillaryInformationOnCurrentTeamsIfNeeded(outputRow, queryRow)
		
		-- do not continue to do the only-after-they-left stuff
		return
	end
	
	-- these are PRE-CHANGE values, so when they occur it means only apply them
	-- when you already left the team
	-- if you're still on the team, either the NEXT old one will apply, or the one in infobox will apply
	for _, v in ipairs({ 'NameOld', 'ResidencyOld', }) do
		-- comment left 2020-11-15:
		-- NameOld (a) doesn't work? and (b) isn't actually used??
		-- we actually get the name from either (1) the NameLeave if it's not
		-- a current tenure, or (2) the player's current name param from the infobox
		-- if it is a current tenure, and we did all this NameOld shit there already
		-- to figure out the correct value of this stuff there so yeah gg
		
		-- ResidencyOld on the other hand definitely DOES work and IS used!!!!!
		-- so DO NOT JUST KILL THIS LOL!!!!!!!!
		h.updateAncillaryValueInRow(outputRow, queryRow, v)
	end
end

function h.updateAncillaryInformationOnCurrentTeamsIfNeeded(outputRow, queryRow)
	-- this is part of the propagating information backward process
	if queryRow.ContractEnd and queryRow.SisterTeamPage == outputRow.SisterTeamPage then
		outputRow.ContractEnd = queryRow.ContractEnd
	end
end

function h.updateAncillaryValueInRow(outputRow, queryRow, value)
	-- don't overwrite. also it's possible value is nil but whatever.
	outputRow[value] = outputRow[value] or queryRow[value]
end

function p:isOriginalNews(changesByLine, row, index)
	if not index then return true end
	if not index then return true end
	for _, oldChangeLineKey in ipairs(index) do
		local oldChange = self:getOldChangeFromIndex(changesByLine, oldChangeLineKey)
		if h.isThisLineARepeatOfAnOldOne(row, oldChange) then
			return false
		end
	end
	return true
end

function h.isThisLineARepeatOfAnOldOne(row, oldRow)
	if row.TeamJoin and not oldRow.TeamLeave then
		return h.teamsWhenAreIdentical(row, oldRow, 'Join')
	end
	return false
end

function h.teamsWhenAreIdentical(row, oldRow, when)
	for _, v in ipairs({ 'Team', 'RoleModifier', 'RolesString', 'Status' }) do
		if row[v .. when] ~= oldRow[v .. when] then
			return false
		end
	end
	return true
end

function p:doWeNeedANewLine(changesByLine, row, index)
	if not row.TeamLeave then return true end
	return not index
end

function p:getOldChangeFromIndex(changesByLine, oldChangeLineKey)
	return changesByLine[oldChangeLineKey.changeNumber]
end

function p:getMostRecentLineNumberFromIndex(index)
	return index[#index].changeNumber
end

function p:updateIndex(changesByLine, row, index)
	index[#index+1] = { changeNumber = #changesByLine }
end

function p:addNewTeamToExistingLine(line, row)
	-- we earlier split stuff into separate start/end so we should be fine to just merge now
	if line.TeamLeave then return end
	self:super('addNewTeamToExistingLine', line, row)
end

-- formatting
function p:formatRowForOutput(row)
	row.PlayerLinked = util_esports.playerLinked(row.Player)
	row.RoleDisplay = row.Roles:images{ sep="", size=15 }
	row.TeamDisplay = self:getTeamDisplay(row)
	row.RegionDisplay = row.Region:image()
	row.DateLeaveDisplay = self:getDateDisplay(row, 'Leave')
	row.DateJoinDisplay = self:getDateDisplay(row, 'Join')
	row.DurationDisplay = util_timedelta.approxDurationDisplay(self:getDurationArgs(row))
	self:getStatusAndUpdateFlag(row)
	self:addSortKeys(row)
	h.correctMissingDates(row)
end

function p:getTeamDisplay(row) end

function p:getDateDisplay(row, when) end

function p:getDurationArgs(row)
	local tbl = {
		row.DateJoin or 'xxxx-xx-xx',
		row.DateLeave or os.date('%Y-%m-%d'),
		row.IsApproxDateJoin or row.IsApproxDateLeave,
	}
	return unpack(tbl)
end

function p:getStatusAndUpdateFlag(row) end

function h.correctMissingDates(row)
	if not row.DateJoinDisplay then
		row.DateJoinDisplay = '??? ????'
		h.addSortKey(row, 'DateJoinDisplay', -1)
	end
	if not row.DateLeaveDisplay then
		row.DateLeaveDisplay = ("''%s''"):format(i18n.print('present'))
		h.addSortKey(row, 'DateLeaveDisplay', 9999999999999)
	end
end

function p:addSortKeys(row)
	h.addSortKey(row, 'DateJoinDisplay', row.index)
	h.addSortKey(row, 'TeamDisplay', row.Team)
	h.addSortKey(
		row,
		'DateLeaveDisplay',
		util_time.unix(row.Date_SortLeave)
	)
	h.addSortKey(
		row,
		'DurationDisplay',
		util_timedelta.approxDurationSeconds(self:getDurationArgs(row))
	)
end

function h.addSortKey(row, col, sortkey)
	if not row[col] then return end
	row[col .. '_Sort'] = sortkey
end

function p:setEarlierRowNextTeamValues(changesByLine, queryRow)
	if queryRow.Direction == 'Leave' then return end
	for _, outputRow in ipairs(changesByLine) do
		if not outputRow.TeamLeave then
			-- we can't add a NextTeam if they're still on the team
		else
			-- set NextTeam if it doesn't already have a NextTeam
			outputRow.NextTeam = outputRow.NextTeam or queryRow.Team
			outputRow.changeIndexNext = 'queryRow.index'
		end
	end
end

-- cargo STORE
function p:storeCargo(changesByLine) end

-- output
function p:makeOutput(changesByLine)
	self:setColumnsBasedOnIncludedInformation()
	local output = mw.html.create()
	local tbl = output:tag('table')
		:addClass('player-team-history')
		:addClass('hoverable-rows')
		:addClass('sortable')
	self:printHeader(tbl)
	util_map.selfRowsInPlace(self, changesByLine, p.printOneRow, tbl)
	return output
end

function p:printHeader(tbl)
	local tr = tbl:tag('tr')
	for _, col in ipairs(self.COLUMNS) do
		tr:tag('th')
			:wikitext(i18n.print(col))
			:attr('data-sort-type', self.COLUMNS.sorttypes[col])
			:addClass(self.COLUMNS.colclasses[col])
	end
end

function p:setColumnsBasedOnIncludedInformation()
	if self.INCLUDE_STATUS then
		self.COLUMNS[#self.COLUMNS+1] = 'StatusDisplay'
	end
	self.COLUMNS.classes[self.COLUMNS[#self.COLUMNS]] = 'roster-portal-lastcell'
end

function p:printOneRow(row, tbl)
	local tr = tbl:tag('tr')
	for _, col in ipairs(self.COLUMNS) do
		self:printOneCell(tr, row, col, self.COLUMNS)
	end
	self:printEditButtons(util_html.lastChild(tr), row)
end

function p:printOneCell(tr, row, col)
	tr:tag('td')
		:wikitext(row[col])
		:addClass(self.COLUMNS.classes[col])
		:attr('data-sort-value', row[col .. '_Sort'])
end

function p:printEditButtons(td, row) end

return p
Advertisement