Leaguepedia | League of Legends Esports Wiki
Advertisement

Edit the documentation or categories for this module. This module has an i18n file.

In this module we do two entirely separate things based on one set of user input data. See {{RCPlayer}} for user documentation.

  • Store entries to [NewsItems] with sentences and dates etc.
  • Store entries to [RosterChanges] with data that can be used to generate tables and other structured data elements with players joining/leaving teams.

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_map = require('Module:MapUtil')
local util_news = require("Module:NewsUtil")
local util_sentence = require("Module:SentenceUtil")
local util_sort = require("Module:SortUtil")
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_title = require('Module:TitleUtil')
local util_vars = require("Module:VarsUtil")
local i18n = require('Module:I18nUtil')
local WeeklyDataPages = require('Module:WeeklyDataPages')
local OtherNewsDataSources = require('Module:OtherNewsDataSources').main
local SENTENCES = require('Module:RosterChangeData/Sentences')
local lang = mw.getLanguage('en')
local SentencePrinterAbstract = require('Module:RosterChangeSentencePrinterAbstract')
local tableErrors = require('Module:TableErrors')()

local SP = SentencePrinterAbstract:finalExtends()

local m_team = require('Module:Team')

local COLUMNS = util_news.COLUMNS

local JOIN_MODIFIERS = { 'sub', 'trainee' }

local h = {}

local p = {}
function p.start(frame)
	return WeeklyDataPages.start('Roster Change Data')
end

function p.date(frame)
	i18n.init('RosterChangeData')
	local args = util_args.merge()
	util_vars.resetGlobalIndex('N_LineInDate')
	tbl, tr = WeeklyDataPages.date(args, COLUMNS, 'roster-change-data')
	return OtherNewsDataSources(), tbl, tr
end

function p.endTable(frame)
	return '</table>'
end

function p.line(frame)
	local args = util_args.merge()
	tableErrors:setIntroText(('Error in entry for team %s (input %s), region %s'):format(
		m_team.teamlinkname(args.team or ''),
		args.team or '',
		args.region or ''
	))
	util_cargo.setStoreNamespace('Data')
	h.validateArgs(args)
	util_news.setId()
	i18n.init('RosterChangeData')
	local data = h.getDataFromArgs(args)
	if not tableErrors:hasErrors() then
		h.storeRosterChangeData(data)
	end
	local newsData = h.getNewsData(args, data)
	if tableErrors:hasErrors() then
		return tableErrors:output(#COLUMNS)
	end
	util_cargo.store(newsData)
	return util_news.makeSentenceOutput(args, newsData), h.makeRowOutput(data)
end

function h.validateArgs(args)
	if not args.team then
		tableErrors:report(i18n.print('error_missingTeam'))
	end
	if args.leave_date then
		tableErrors:report(i18n.print('error_leaveDate'))
	end
end

function h.getDataFromArgs(args)
	local pre = util_news.getPlayersFromArg(args.pre)
	local post = util_news.getPlayersFromArg(args.post)
	local data = h.joinPlayerData(pre, post, args)
	return data
end	

function h.joinPlayerData(pre, post, args)
	local ret = {}
	for _, player, data in ipairs(pre) do
		ret[#ret+1] = h.joinOnePlayerData(player, data, post:get(player) or {}, args)
		post:removeKey(player)
	end
	for _, player, data in ipairs(post) do
		ret[#ret+1] = h.joinOnePlayerData(player, {}, data, args)
	end
	return ret
end

function h.joinOnePlayerData(player, pre, post, args)
	local ret = {
		Player = player,
		Leave = pre,
		Join = post,
		TeamStart = pre.Player and m_team.teamlinkname(args.team),
		RoleStart = pre.Role,
		IsSubStart = pre.IsSub,
		IsTraineeStart = pre.IsTrainee,
		TeamEnd = post.Player and m_team.teamlinkname(args.team),
		RoleEnd = post.Role,
		IsSubEnd = post.IsSub,
		IsTraineeEnd = post.IsTrainee,
		LoanedTo = post.LoanedTo or pre.LoanedTo,
		LoanedFrom = post.LoanedFrom or pre.LoanedFrom,
		EventLink = util_title.target(post.Event or pre.Event or args.event),
		Event = post.Event or pre.Event or args.event,
		Reason = post.Reason or pre.Reason,
		Replacing = post.Replacing or pre.Replacing,
		Phase = post.Phase or pre.Phase,
		contract_until = post.ContractUntil or pre.ContractUntil,
		date = post.Date,
		assistance = util_args.castAsBool(post.Assistance),
		StatusStart = pre.Status and pre.Status:lower(),
		StatusEnd = post.Status and post.Status:lower(),
		pre_preload = pre.MoveType and pre.MoveType:lower(),
		post_preload = post.MoveType and post.MoveType:lower(),
		custom_text = post.Custom or pre.Custom,
		rejoin = util_args.castAsBool(post.Rejoin),
		remain_for = post.RemainFor or pre.RemainFor,
		remain_for_link = post.RemainForLink or pre.RemainForLink,
		sub = h.getNetRoleModifierStatus(pre, post, 'Sub'),
		player = player, -- for util_esports.playerWithRole
		trainee = h.getNetRoleModifierStatus(pre, post, 'Trainee'),
		unlinked = pre.Unlinked or post.Unlinked,
		AlreadyJoined = pre.AlreadyJoined or post.AlreadyJoined,
		IsGCD = util_source.isGCD(args.source),
		SisterTeam = pre.SisterTeam or post.SisterTeam,
	}
	ret.IsLinked = not ret.unlinked
	ret.isLoan = ret.LoanedFrom or ret.LoanedTo
	ret.Team = ret.TeamStart or ret.TeamEnd
	ret.role = ret.Join.RoleSet or ret.Leave.RoleSet
	ret.Roles = post.Roles or pre.Roles
	ret.RolesStaff = post.RolesStaff or pre.RolesStaff
	ret.RoleDisplayStart = pre.RoleDisplay
	ret.RoleDisplayEnd = post.RoleDisplay
	ret.RolesIngameStart = pre.RolesIngame
	ret.RolesIngameEnd = post.RolesIngame
	ret.RolesStaffStart = pre.RolesStaff
	ret.RolesStaffEnd = post.RolesStaff
	ret.RolesStart = pre.Roles
	ret.RolesEnd = post.Roles
	ret.RoleDisplay = post.RoleDisplay or pre.RoleDisplay
	ret.RoleModifierStart = h.getRoleModifier(ret, 'Start')
	ret.RoleModifierEnd = h.getRoleModifier(ret, 'End')
	ret.move_type = ret.post_preload or ret.pre_preload
	h.addConstantsToRow(ret, args, post)
	ret.Preload = h.guessNewsPreload(ret)
	ret.PreloadSortNumber = SENTENCES.priority[ret.Preload]
	ret.sort_key_role = h.getRoleSortKey(ret, pre, post)
	ret.sort_key_player = mw.ustring.lower(ret.Player)
	ret.sort_key_sentence = h.getSentenceSortKey(ret, pre, post)
	ret.sentence_group = post.SentenceGroup or pre.SentenceGroup
	return ret
end

function h.getNetRoleModifierStatus(pre, post, statustype)
	-- only allow sub/trainee status pulled from pre if he left the team
	if post.Player then return util_args.castAsBool(post[statustype]) end
	return util_args.castAsBool(pre[statustype])
end

function h.getRoleModifier(ret, when)
	-- to editors we want only trainee/sub available, but in the database its much more
	-- long-term convenient to treat this as its own freeform column that can take
	-- any arbitrary value
	
	-- when updating, remember to edit Module:NewsUtil.getRoleModifierFromArgs() as well
	
	if ret['IsTrainee' .. when] then
		return 'Trainee'
	elseif ret['IsSub' .. when] then
		return 'Sub'
	end
	return nil
end

function h.addConstantsToRow(row, args, post)
	util_table.mergeDontOverwrite(row, util_news.getRCFieldsFromPlayerAndArgs(post, args))
	row.source_display = util_source.makePopupRef(args.source)
end

function h.getRoleSortKey(playerVariables, pre, post)
	-- put all maually specified order things first
	-- then fall back to the role's sort number
	if post.Order then return tonumber(post.Order) end
	if pre.Order then return tonumber(pre.Order) end
	return (playerVariables.role:sortnumber() or 1000) * 1000
end


function h.getSentenceSortKey(ret, pre, post)
	if post.Order then return tonumber(post.Order) end
	if pre.Order then return tonumber(pre.Order) end
	return SENTENCES.priority[ret.Preload]
end

function h.guessNewsPreload(info)
	local gcd_prefix = ''
	if info.move_type == 'confirm' then
		gcd_prefix = 'confirm_'
	end
	if info.IsGCD then
		gcd_prefix = 'gcd_'
	end
	if info.move_type == 'expire' or info.move_type == 'expire_will_leave' then
		return gcd_prefix .. h.determineExpirationMoveType(info)
	elseif info.move_type == 'to_academy' then
		return gcd_prefix .. h.determineToAcademyMoveType(info)
	elseif info.move_type == 'to_main' then
		return gcd_prefix .. h.determineToMainMoveType(info)
	
	elseif info.move_type == 'from_academy' then
		return gcd_prefix .. h.determineFromAcacdemyMoveType(info)
	elseif info.move_type == 'from_main' then
		return gcd_prefix .. h.determineFromMainMoveType(info)
	elseif info.move_type == 'from_sister' then
		return gcd_prefix .. h.determineFromSisterMoveType(info)
	
	elseif h.isAllowedMoveType(info, info.move_type, gcd_prefix) then
		return gcd_prefix .. info.move_type
	elseif info.contract_until and info.TeamStart and info.TeamEnd then
		return gcd_prefix .. 'extended'
	end
	local preload = h.guessNewsPreloadWithoutPrefix(info)
	if preload then
		return gcd_prefix .. preload
	end
	return 'unknown'
end

function h.determineExpirationMoveType(info)
	if info.StatusStart == 'draft_pick' then
		return 'draft_pick_expire'
	elseif info.move_type == 'expire_will_leave' then
		return 'expire_notleave_yet'
	elseif info.TeamEnd then
		return 'expire_notleave'
	end
	return 'expire'
end

function h.determineToAcademyMoveType(info)
	if info.StatusStart == 'draft_pick' then
		return 'draft_pick_to_academy'
	elseif info.TeamEnd then
		return 'to_academy_also_stay'
	end
	return 'to_academy'
end

function h.determineToMainMoveType(info)
	if info.TeamEnd then
		return 'to_main_also_stay'
	end
	return 'to_main'
end

function h.determineFromAcacdemyMoveType(info)
	if info.StatusEnd == 'temp_sub' then return 'from_academy_as_temp_sub' end
	if info.StatusStart == 'temp_sub' then return 'leave_academy_as_temp_sub' end
	return 'from_academy'
end

function h.determineFromMainMoveType(info)
	if info.StatusEnd == 'temp_sub' then return 'from_main_as_temp_sub' end
	if info.StatusStart == 'temp_sub' then return 'leave_main_as_temp_sub' end
	return 'from_main'
end

function h.determineFromSisterMoveType(info)
	if info.StatusEnd == 'temp_sub' then return 'from_sister_as_temp_sub' end
	if info.StatusStart == 'temp_sub' then return 'leave_sister_as_temp_sub' end
	return 'from_sister'
end

function h.isAllowedMoveType(info, move_type, gcd_prefix)
	-- move_type, if legally specified, will usually be a literal preload to use
	-- we'll overload "confirm" though for editor convenience
	if not move_type then return false end
	if move_type == 'confirm' then return false end
	local move_type_lookup = gcd_prefix .. move_type
	if not SENTENCES.lookup[move_type_lookup] then
		tableErrors:report(i18n.print('error_unknownPreload', move_type_lookup))
		return false
	end
	if SENTENCES.lookup[move_type_lookup].auto then
		tableErrors:report(i18n.print('error_forbiddenPreload', move_type_lookup, 'RCInfo'))
		return false
	end
	h.validateMoveType(info, move_type, gcd_prefix)
	return true
end

function h.validateMoveType(info, move_type, gcd_prefix)
	-- can throw errors here if needed
end

function h.guessNewsPreloadWithoutPrefix(info)
	if info.isLoan then
		return h.guessLoanMoveType(info)
	elseif h.isEverythingTheSame(info) then
		return 'remain'
	elseif info.TeamStart and info.TeamEnd and info.StatusEnd and info.StatusEnd:gsub('_', ' ') == 'set to leave' then
		return h.guessSetToLeaveStatus(info)
	elseif info.TeamStart and not info.TeamEnd and info.StatusStart == 'draft_pick' then
		return 'draft_pick_leave'
	elseif info.TeamEnd and not info.TeamStart and info.StatusEnd == 'draft_pick' then
		return 'draft_pick_start'
	elseif info.TeamStart and info.TeamEnd and info.StatusStart == 'draft_pick' then
		return h.guessDraftPickStayWithTeamMoveType(info)
	elseif not info.TeamStart and info.StatusEnd == 'temp_sub' then
		return 'join_as_temp_sub'
	elseif not info.TeamEnd and info.StatusStart == 'temp_sub' then
		return 'leave_as_temp_sub'
	elseif not info.TeamEnd and info.StatusStart == 'trial' then
		return 'leave_after_trial'
	elseif info.TeamStart and not info.TeamEnd then
		return 'leave'
	elseif info.TeamEnd and info.StatusStart == 'trial' then
		return 'join_after_trial'
	elseif info.TeamEnd and info.StatusEnd == 'main_or_acad' then
		return 'join_main_or_acad'
	elseif info.TeamEnd and info.StatusEnd == 'set_to_join' then
		return 'set_to_join'
	elseif info.TeamEnd and not info.TeamStart then
		return 'join'
	elseif info.RoleStart ~= info.RoleEnd then
		return h.guessRoleSwapType(info)
	elseif info.StatusEnd == 'official_sub' and info.StatusStart ~= 'official_sub' then
		return 'to_official_sub'
	elseif info.StatusStart == 'official_sub' and info.StatusEnd ~= 'official_sub' then
		return 'end_official_sub'
	elseif info.IsSubStart and not info.IsTraineeStart and info.IsTraineeEnd and not info.IsSubEnd then
		return 'sub_to_trainee'
	elseif not info.IsTraineeStart and info.IsTraineeEnd then
		return 'to_trainee'
	elseif not info.IsSubStart and info.IsSubEnd then
		return 'to_sub'
	elseif (info.IsTraineeStart and not info.IsTraineeEnd ) or (info.IsSubStart and not info.IsSubEnd) then
		return 'to_starting'
	elseif info.StatusStart == 'inactive' then
		return 'to_active'
	elseif info.StatusEnd == 'inactive' then
		return 'to_inactive'
	elseif info.StatusEnd == 'opportunities' then
		return 'opportunities'
	end
end

function h.guessLoanMoveType(info)
	if info.LoanedTo and info.StatusStart == "loaned_out" and info.StatusEnd ~= 'loaned_out' then
		-- a loan ends, this is the team they were loaned FROM
		if info.TeamEnd then
			return 'loan_return'
		end
		return 'loan_return_and_leave'
	elseif info.LoanedTo and info.StatusEnd == 'loaned_out' and info.StatusStart ~= 'loaned_out' then
		-- a loan starts, this is the team they were loaned FROM
		if info.TeamStart ~= info.TeamEnd then
			tableErrors:report(i18n.print('error_missingLoanTagging'))
		end
		return 'loaned_to'
	elseif info.LoanedFrom and info.StatusStart == 'on_loan' and info.StatusEnd ~= 'on_loan' then
		-- a loan ends, this is the team they were loaned TO
		if info.TeamEnd then
			return 'loan_end_and_join'
		end
		return 'loan_end'
	elseif info.LoanedFrom and info.StatusEnd == 'on_loan' and info.StatusStart ~= 'on_loan' then
		-- a loan starts, this is the team they were loaned TO
		return 'loaned_from'
	end
end

function h.isEverythingTheSame(info)
	for _, v in ipairs(util_news.ALL_POSSIBLE_CHANGES) do
		if info[v .. 'Start'] ~= info[v .. 'End'] then
			return false
		end
	end
	return true
end

function h.guessSetToLeaveStatus(info)
	if info.AlreadyJoined then
		return 'set_to_leave_already_joined'
	end
	return 'set_to_leave'
end

function h.guessDraftPickStayWithTeamMoveType(info)
	-- this only handles cases where the player stays with the main team
	-- leaving, expiring is handled elsewhere
	-- moves to academy are handled elsewhere
	if info.IsTraineeEnd then
		return 'draft_pick_to_trainee'
	elseif info.IsSubEnd then
		return 'draft_pick_to_sub'
	end
	return 'draft_pick_signed'
end

function h.guessRoleSwapType(info)
	-- we might need to further break this up into 4 types:
	-- ingame only -> ingame + staff
	-- staff only -> ingame + staff
	-- ingame + staff -> ingame only
	-- ingame + staff -> staff only
	-- that setup would allow complete discrimination in what we accept if we wanna
	-- be able to show a brief period that someone was also on staff but then stopped being
	-- while they were, that entire time, also a player.
	
	-- currently since we aren't really doing anything of an organization query of Tenures table
	-- this is not needed; and we might NEVER do that, so we shouldn't prematurely complicate
	-- but we might need it later so just keep that in mind.
	
	-- these extra preloads are NOT ignored by TenuresUnbroken, whereas role_swap IS
	-- so we'll properly create our current & former stuff
	-- see Brolia's tenures on 5 Ronin for an example requiring these extra preloads
	if info.Leave.RoleSet:hasIngame() and not info.Join.RoleSet:hasIngame() then
		return 'role_swap_from_ingame'
	end
	if not info.Leave.RoleSet:hasIngame() and info.Join.RoleSet:hasIngame() then
		return 'role_swap_to_ingame'
	end
	return 'role_swap'
end

-- done guessing, output time!
function h.makeRowOutput(data)
	local output = mw.html.create()
	for _, row in ipairs(data) do
		local tr = output:tag('tr')
		util_html.printRowByList(tr, row, COLUMNS)
	end
	return output
end

-- store roster change cargo
function h.storeRosterChangeData(data)
	local N_LineInNews = 1
	for _, row in ipairs(data) do
		row.N_LineInNews = N_LineInNews
		if not row.TeamStart or not row.TeamEnd then
			h.storeEntireRow(row)
			N_LineInNews = N_LineInNews + 1
		else
			h.storeTwoRosterChangeRowsFromOneLine(row)
			N_LineInNews = N_LineInNews + 2
		end
	end
end

function h.storeEntireRow(row)
	local rowCopy = mw.clone(row)
	h.copyWhenArgsToRowCopy(rowCopy, row.TeamStart and 'Start' or 'End')
	util_news.storeRosterChangesRow(rowCopy)
end

function h.copyWhenArgsToRowCopy(rowCopy, when)
	rowCopy.Direction = h.getRosterChangeDirection(when)
	for _, v in ipairs({ 'RoleDisplay', 'RoleModifier', 'Status', 'Role', 'RolesIngame', 'RolesStaff', 'Roles' }) do
		rowCopy[v] = rowCopy[v .. when]
	end
end

function h.getRosterChangeDirection(when)
	return when == 'Start' and 'Leave' or 'Join'
end

function h.storeTwoRosterChangeRowsFromOneLine(row)
	-- Start is a leave (on the team at the start)
	-- End is a join (on the team at the end)
	
	h.storeOneRosterChangeRow(mw.clone(row), 'Start', 'End')
	row.N_LineInNews = row.N_LineInNews + 1
	h.storeOneRosterChangeRow(mw.clone(row), 'End', 'Start')
end

function h.storeOneRosterChangeRow(rowCopy, when, notWhen)
	for i, v in ipairs(util_news.PLAYER_STATUSES) do
		-- legacy value-for-each-part
		rowCopy[v .. notWhen] = nil
	end
	-- one value, and direction says leave or join
	h.copyWhenArgsToRowCopy(rowCopy, when)
	util_news.storeRosterChangesRow(rowCopy)
end

---------------------------------------------------
-- News Sentence Creation & Printing
---------------------------------------------------
function h.getNewsData(args, listOfPlayers)
	local newsSentencePrinter = SP(args)
	util_table.merge(args, newsSentencePrinter:run(args, listOfPlayers))
	local ret = util_table.merge(
		util_news.getNewsCargoFieldsFromArgs(args),
		h.getSpecializedNewsData(args.team, listOfPlayers)
	)
	return ret
end

function h.getSpecializedNewsData(team, listOfPlayers)
	local ret = {
		Players = util_table.concat(util_table.extractValueToList(listOfPlayers, 'Player')),
		Subject = m_team.teamlinkname(team),
		SubjectType = 'Team',
	}
	return ret
end

function SP:groupPlayersByPreload(listOfPlayers)
	local ret = {}
	for _, player in ipairs(listOfPlayers) do
		h.addPlayerToPreloadOutput(ret, player.Preload or 'unknown', player)
	end
	util_sort.dictByKeys(ret, 'sort_key_sentence', true)
	h.sortEachPreload(ret)
	return ret
end

function h.addPlayerToPreloadOutput(ret, preload, player)
	local key = h.getCustomKey(preload, player)
	util_table.initDict(ret, key)
	ret[key].preload = preload
	ret[key].sort_key_sentence = h.getSortKeySentence(ret, player)
	ret[key].custom_text = player.custom_text
	ret[key].class = SENTENCES.lookup[preload or 'unknown'].class
	util_table.initDict(ret[key], player.Player, player)
end

function h.getCustomKey(preload, player)
	-- modify the key to add any information that requires different information so that
	-- we never have anything problematically grouped together
	local key = player.custom_text or preload
	if player.sentence_group then key = player.sentence_group .. key end
	if not SENTENCES.lookup[preload or 'unknown'] then
		tableErrors:report(i18n.print('error_invalidPreloadComputed', preload))
	end
	key = key .. (player.LoanedTo or player.LoanedFrom or '')
	if preload == 'extended' then
		key = key .. (player.contract_until or '')
	end
	if not SENTENCES.lookup[preload or 'unknown'].respect_joining_key then
		return key
	end
	for _, modifier in ipairs { 'sub', 'trainee', 'rejoin' } do
		if player[modifier] then
			key = key .. modifier
		end
	end
	return key
end

function h.getSortKeySentence(sentenceData, player)
	if not sentenceData.sort_key_sentence then
		return player.sort_key_sentence
	end
	return math.min(player.sort_key_sentence, sentenceData.sort_key_sentence)
end

function h.sortEachPreload(playersByPreload)
	for _, preload in ipairs(playersByPreload) do
		util_sort.dictByKeys(
			playersByPreload[preload],
			{ 'sort_key_role', 'sort_key_player' },
			{ true, true }
		)
	end
end

function SP:getTextForReplacements(row, args)
	if args.custom then return args.custom end
	if row.custom_text then return row.custom_text end
	local preload = args.sentencetype or row.preload
	if not SENTENCES.lookup[preload or 'unknown'] then
		tableErrors:report(i18n.print('error_unknownPreload', row.preload))
	end
	return SENTENCES.lookup[preload or 'unknown'].sentence
end

function SP:getReplacements(players, args)
	local firstPlayer = players[players[1]]
	local ret = {
		PLAYERS = util_sentence.players(players),
		PLAYERS_POSS = util_sentence.playersPossessive(players),
		JOIN = h.getJoinText(players, ''),
		JOINING = h.getJoinText(players, 'ing'),
		ADDED = h.getAddedText(firstPlayer),
		AS = h.getJoinSuffix(firstPlayer),
		PLAYERS_WITH_SWAP = util_sentence.playersWithSwap(players),
		PLAYERS_POSS_WITH_SWAP = util_sentence.playersPossessiveWithSwap(players),
		LOANED_TO = m_team.mediumplainlinked(firstPlayer.LoanedTo),
		LOANED_FROM = m_team.mediumplainlinked(firstPlayer.LoanedFrom),
		CONTRACT_EXTEND_UNTIL = h.getUntilDateSpecific(firstPlayer, 'contractUntil'),
		DRAFT_WINDOW_UNTIL = h.getUntilDateSpecific(firstPlayer, 'draftWindowUntil'),
		ASSISTANCE = firstPlayer.assistance and ' with assistance finding a new team' or '',
		EVENT = firstPlayer.Event and util_text.intLink(firstPlayer.EventLink, firstPlayer.Event),
		AT_EVENT = h.getAtEventText(firstPlayer),
		REASON = h.getReason(firstPlayer.Reason),
		REPLACING = h.getReplacing(players),
		REMAIN_FOR = h.getRemainFor(firstPlayer),
		BECAUSE_COMMA = h.getBecauseComma(players, firstPlayer.Reason),
		PHASE = h.getPhase(firstPlayer.Phase),
		LEAVE_DATE = h.getDate(firstPlayer.date, 'leave'),
		JOIN_DATE = h.getDate(firstPlayer.date, 'join'),
		ALREADY_JOINED = h.getAlreadyJoined(firstPlayer.AlreadyJoined),
		SISTER_TEAM = m_team.rightmediumlinked(firstPlayer.SisterTeam),
	}
	util_table.merge(
		ret,
		util_map.arrayToLookupSafe(
			SENTENCES.to_conjugate,
			util_sentence.getConjugation,
			#players)
		)
	return ret
end

function h.getJoinText(players, ing)
	return h.getJoinWord(players, ing) .. h.getJoinSuffix(players[players[1]])
end

function h.getJoinWord(players, ing)
	if players[players[1]].rejoin then
		return util_sentence.getConjugation('rejoin' .. ing, #players)
	end
	return util_sentence.getConjugation('join' .. ing, #players)
end

function h.getAddedText(firstPlayer)
	if firstPlayer.rejoin then
		return i18n.default('readded')
	end
	return i18n.default('added')
end

function h.getUntilDateSpecific(firstPlayer, formatKey)
	if not firstPlayer.contract_until then return '' end
	if firstPlayer.contract_until:find('^%d%d%d%d$') then
		return i18n.default('extendThrough', firstPlayer.contract_until)
	end
	return i18n.default(
		formatKey,
		util_time.strToDateStrFuzzy(firstPlayer.contract_until)
	)
end

function h.getReplacing(players)
	local replacing = util_table.extractValueFromDictToList(players, 'Replacing')
	if #replacing == 0 then return '' end
	return (', replacing %s%s'):format(
		util_table.printList(
			util_map.inPlace(replacing, util_esports.playerLinked)
		),
		#players > 1 and ', respectively' or ''
	)
end

function h.getRemainFor(firstPlayer)
	if not firstPlayer.remain_for then return '' end
	return i18n.default(
		'remainFor',
		util_text.intLinkOrText(firstPlayer.remain_for_link, firstPlayer.remain_for)
	)
end

function h.getAtEventText(firstPlayer)
	if not firstPlayer.Event then return '' end
	return i18n.default(
		'atEvent',
		util_text.intLink(firstPlayer.EventLink, firstPlayer.Event)
	)
end

function h.getReason(reason)
	if not reason then return '' end
	return (' because %s'):format(reason)
end

function h.getBecauseComma(players, reason)
	return reason and #players > 1 and ',' or ''
end

function h.getPhase(phase)
	if not phase then return '' end
	return ('%s of '):format(phase)
end

function h.getDate(date, direction)
	if not date then return '' end
	return i18n.default(('%sDate'):format(direction), lang:formatDate('j F Y', date))
end

function h.getAlreadyJoined(team)
	if not team then return '' end
	return m_team.rightmediumlinked(team)
end

function h.getJoinSuffix(player)
	return h.getJoinRoleModifier(player) .. h.getJoinStatusModifier(player)
end

function h.getJoinRoleModifier(player)
	for _, v in ipairs(JOIN_MODIFIERS) do
		if player[v] then
			return i18n.default(('store_joins_as_%s'):format(v))
		end
	end
	return ''
end

function h.getJoinStatusModifier(player)
	if player.Status == 'trial' then
		return i18n.default('store_joins_on_trial')
	end
	return ''
end

return p
Advertisement