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