From a626f60e7ca9a908053ac9d9dc698a13a1c11329 Mon Sep 17 00:00:00 2001 From: Rikard Blixt Date: Thu, 12 Sep 2024 16:40:44 +0200 Subject: [PATCH] feat(match2): properly attach participants to their playerid (#4645) * feat(match2): properly attach participants to their playerid * fix variable naming * do other refactored wikis * tweak function * do starcraft also * long lines * unused import * rename to id * rename variables * from review * idea on reworking an handling unattachedplayers * rework again, apply to all * var rename, spelling * rework to be more generic * changed the rest * tweaks (not done) * typo? * maybe this? * readd annos * fix sc2 * fix sc2: use correct var * ensure `match2players` is always set --------- Co-authored-by: hjpalpha <75081997+hjpalpha@users.noreply.github.com> --- .../match2/commons/match_group_input_util.lua | 76 ++++++++++++++- .../match_group_input_starcraft.lua | 96 +++++++------------ .../brawlstars/match_group_input_custom.lua | 54 +++++++---- .../wikis/dota2/match_group_input_custom.lua | 46 +++++---- .../match_group_input_custom.lua | 46 +++++---- .../valorant/match_group_input_custom.lua | 64 ++++++++----- 6 files changed, 244 insertions(+), 138 deletions(-) diff --git a/components/match2/commons/match_group_input_util.lua b/components/match2/commons/match_group_input_util.lua index f68dde8b47f..3656d49a763 100644 --- a/components/match2/commons/match_group_input_util.lua +++ b/components/match2/commons/match_group_input_util.lua @@ -251,8 +251,8 @@ function MatchGroupInputUtil.mergeRecordWithOpponent(record, opponent, substitut record.icondark = opponent.icondark or record.icondark end - if not record.match2players and Logic.isNotEmpty(opponent.players) then - record.match2players = Array.map(opponent.players, function(player) + if not record.match2players then + record.match2players = Array.map(opponent.players or {}, function(player) return { displayname = player.displayName, flag = player.flag, @@ -1089,6 +1089,78 @@ function MatchGroupInputUtil.getCharacterName(alias, character) return (assert(alias[character:lower()], 'Invalid character:' .. character)) end +---@param players {name: string?, displayname: string?}[] +---@param playerInput string? +---@param playerLink string? +---@return integer? +function MatchGroupInputUtil.findPlayerId(players, playerInput, playerLink) + if Logic.isEmpty(playerInput) and Logic.isEmpty(playerLink) then + return + end + + local playerLinks = Array.map(players, Operator.property('name')) + local playerIndex = Array.indexOf(playerLinks, FnUtil.curry(Operator.eq, playerLink)) + if playerIndex > 0 then + return playerIndex + end + + local playerDisplayNames = Array.map(players, Operator.property('displayname')) + playerIndex = Array.indexOf(playerDisplayNames, FnUtil.curry(Operator.eq, playerInput)) + if playerIndex > 0 then + return playerIndex + end + mw.log('Player with id ' .. playerInput .. ' not found in opponent data') +end + +---@param name string +---@param options {pagifyPlayerNames: boolean?}? +---@return string +function MatchGroupInputUtil.makeLinkFromName(name, options) + local link = mw.ext.TeamLiquidIntegration.resolve_redirect(name) + + if (options or {}).pagifyPlayerNames then + link = Page.pageifyLink(link) --[[@as string]] + end + + return link +end + +---@param playerIds table[] +---@param inputPlayers table[] +---@param indexToPlayer fun(playerIndex: integer): {name: string?, link: string?}? +---@param transform fun(playerIndex: integer, playerIdData?: table, playerInputData?: table): table? +---@param options {pagifyPlayerNames: boolean?}? +---@return table, table +function MatchGroupInputUtil.parseParticipants(playerIds, inputPlayers, indexToPlayer, transform, options) + local participants = {} + local unattachedParticipants = {} + local function parsePlayer(_, playerIndex) + local playerInputData = indexToPlayer(playerIndex) or {} + if playerInputData.name and not playerInputData.link then + playerInputData.link = MatchGroupInputUtil.makeLinkFromName(playerInputData.name, options) + end + local playerId = MatchGroupInputUtil.findPlayerId(playerIds, playerInputData.name, playerInputData.link) + local toStoreData = transform(playerIndex, playerIds[playerId] or {}, playerInputData) + if playerId then + participants[playerId] = toStoreData + else + table.insert(unattachedParticipants, toStoreData) + end + end + Array.forEach(inputPlayers, parsePlayer) + + return participants, unattachedParticipants +end + +---@generic T:table +---@param opponentIndex integer +---@return fun(playerIndex: integer, data: T): string, T +function MatchGroupInputUtil.prefixPartcipants(opponentIndex) + return function(playerIndex, data) + return opponentIndex .. '_' .. playerIndex, data + end +end + --- Warning, both match and standalone match may be mutated ---@param match table ---@param standaloneMatch table diff --git a/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft.lua b/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft.lua index 8530f8c6781..8a4e66799c4 100644 --- a/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft.lua +++ b/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft.lua @@ -12,7 +12,6 @@ local Flags = require('Module:Flags') local Logic = require('Module:Logic') local Lua = require('Module:Lua') local Operator = require('Module:Operator') -local Page = require('Module:Page') local String = require('Module:StringUtils') local Table = require('Module:Table') local Variables = require('Module:Variables') @@ -371,73 +370,52 @@ end ---@param opponentIndex integer ---@return table function MapFunctions.getTeamParticipants(mapInput, opponent, opponentIndex) - local players = opponent.match2players - local archonFaction = Faction.read(mapInput['t' .. opponentIndex .. 'p1race']) or Faction.read(mapInput['opponent' .. opponentIndex .. 'race']) - or ((players[1] or {}).extradata or {}).faction + or ((opponent.match2players[1] or {}).extradata or {}).faction local isArchon = MapFunctions.isArchon(mapInput, opponent, opponentIndex) - ---@type {input: string, faction: string?, link: string?}[] - local participantsList = Array.mapIndexes(function(playerIndex) - local prefix = 't' .. opponentIndex .. 'p' .. playerIndex - - if Logic.isEmpty(mapInput[prefix]) then return end - - return { - input = mapInput[prefix], - link = Logic.nilIfEmpty(mapInput[prefix .. 'link']), - faction = isArchon and archonFaction or Faction.read(mapInput[prefix .. 'race']), - } + local players = Array.mapIndexes(function(playerIndex) + return Logic.nilIfEmpty(mapInput['t' .. opponentIndex .. 'p' .. playerIndex]) end) - local participants = {} - - Array.forEach(participantsList, function(participantInput, position) - local nameInput = participantInput.input - - local isTBD = nameInput:upper() == TBD or nameInput:upper() == TBA - - local link = participantInput.link or Variables.varDefault(nameInput .. '_page') or nameInput - link = Page.pageifyLink(link) --[[@as string -- can't be nil as input isn't nil]] - - local playerIndex = MapFunctions.getPlayerIndex(players, link, nameInput) - - -- in case we have a TBD or a player not known in match2players inster a new player in match2players - if isTBD or playerIndex == 0 then - table.insert(players, { - name = isTBD and TBD or link, - displayname = isTBD and TBD or nameInput, - extradata = {faction = participantInput.faction or Faction.defaultFaction}, - }) - playerIndex = #players - end - - local player = players[playerIndex] + local participants, unattachedParticipants = MatchGroupInputUtil.parseParticipants( + opponent.match2players, + players, + function(playerIndex) + local prefix = 't' .. opponentIndex .. 'p' .. playerIndex + return { + name = mapInput[prefix], + link = Logic.nilIfEmpty(mapInput[prefix .. 'link']) or Variables.varDefault(mapInput[prefix] .. '_page'), + faction = isArchon and archonFaction or Faction.read(mapInput[prefix .. 'race']), + } + end, + function(playerIndex, playerIdData, playerInputData) + return { + faction = playerInputData.faction or (playerIdData.extradata or {}).faction or Faction.defaultFaction, + player = playerIdData.name or playerInputData.link, + flag = Flags.CountryName(playerIdData.flag), + position = playerIndex, + } + end, + OPPONENT_CONFIG + ) - participants[opponentIndex .. '_' .. playerIndex] = { - faction = participantInput.faction or player.extradata.faction, - player = link, - position = position, - flag = Flags.CountryName(player.flag), - } + Array.forEach(unattachedParticipants, function(participant) + local name = mapInput['t' .. opponentIndex .. 'p' .. participant.position] + local nameUpper = name:upper() + local isTBD = nameUpper == TBD or nameUpper == TBA + + table.insert(opponent.match2players, { + name = isTBD and TBD or participant.player, + displayname = isTBD and TBD or name, + flag = participant.flag, + extradata = {faction = participant.faction}, + }) + participants[#opponent.match2players] = participant end) - return participants -end - ----@param players {name: string, displayname: string} ----@param name string ----@param displayName string ----@return integer -function MapFunctions.getPlayerIndex(players, name, displayName) - local playerIndex = Array.indexOf(players, function(player) return player.name == name end) - - if playerIndex ~= 0 then - return playerIndex - end - - return Array.indexOf(players, function(player) return player.displayname == displayName end) + return Table.map(participants, MatchGroupInputUtil.prefixPartcipants(opponentIndex)) end ---@param mapInput table diff --git a/components/match2/wikis/brawlstars/match_group_input_custom.lua b/components/match2/wikis/brawlstars/match_group_input_custom.lua index 54ee7907dc2..a21baa0ad0c 100644 --- a/components/match2/wikis/brawlstars/match_group_input_custom.lua +++ b/components/match2/wikis/brawlstars/match_group_input_custom.lua @@ -47,7 +47,7 @@ function CustomMatchGroupInput.processMatch(match, options) local opponents = Array.mapIndexes(function(opponentIndex) return MatchGroupInputUtil.readOpponent(match, opponentIndex, {}) end) - local games = CustomMatchGroupInput.extractMaps(match, #opponents) + local games = CustomMatchGroupInput.extractMaps(match, opponents) match.bestof = MatchFunctions.getBestOf(match) local autoScoreFunction = MatchGroupInputUtil.canUseAutoScore(match, games) @@ -84,9 +84,9 @@ function CustomMatchGroupInput.processMatch(match, options) end ---@param match table ----@param opponentCount integer +---@param opponents table[] ---@return table[] -function CustomMatchGroupInput.extractMaps(match, opponentCount) +function CustomMatchGroupInput.extractMaps(match, opponents) local maps = {} for key, map, mapIndex in Table.iter.pairsByPrefix(match, 'map', {requireIndex = true}) do local finishedInput = map.finished --[[@as string?]] @@ -94,10 +94,10 @@ function CustomMatchGroupInput.extractMaps(match, opponentCount) map.vod = map.vod or String.nilIfEmpty(match['vodgame' .. mapIndex]) map.bestof = MapFunctions.getBestOf(map) - map.participants = MapFunctions.getParticipants(map, opponentCount) - map.extradata = MapFunctions.getExtraData(map, opponentCount) + map.participants = MapFunctions.getParticipants(map, opponents) + map.extradata = MapFunctions.getExtraData(map, #opponents) - local opponentInfo = Array.map(Array.range(1, opponentCount), function(opponentIndex) + local opponentInfo = Array.map(opponents, function(_, opponentIndex) local score, status = MatchGroupInputUtil.computeOpponentScore({ walkover = map.walkover, winner = map.winner, @@ -201,23 +201,37 @@ function MapFunctions.getExtraData(map, opponentCount) end ---@param map table ----@param opponentCount integer +---@param opponents table[] ---@return table -function MapFunctions.getParticipants(map, opponentCount) - local participants = {} +function MapFunctions.getParticipants(map, opponents) + local allParticipants = {} local getCharacterName = FnUtil.curry(MatchGroupInputUtil.getCharacterName, BrawlerNames) - for opponentIndex = 1, opponentCount do - for _, player, playerIndex in Table.iter.pairsByPrefix(map, 't' .. opponentIndex .. 'p') do - participants[opponentIndex .. '_' .. playerIndex] = {player = player} - end - - for _, brawler, pickIndex in Table.iter.pairsByPrefix(map, 't' .. opponentIndex .. 'c') do - participants[opponentIndex .. '_' .. pickIndex] = participants[opponentIndex .. '_' .. pickIndex] or {} - participants[opponentIndex .. '_' .. pickIndex].brawler = getCharacterName(brawler) - end - end + Array.forEach(opponents, function(opponent, opponentIndex) + local players = Array.mapIndexes(function(playerIndex) + return opponent.match2players[playerIndex] or Logic.nilIfEmpty(map['t' .. opponentIndex .. 'c' .. playerIndex]) + end) + local participants, unattachedParticipants = MatchGroupInputUtil.parseParticipants( + opponent.match2players, + players, + function(playerIndex) + local player = map['t' .. opponentIndex .. 'p' .. playerIndex] + return player and {name = player} or nil + end, + function(playerIndex, playerIdData) + local brawler = map['t' .. opponentIndex .. 'c' .. playerIndex] + return { + player = playerIdData.name, + brawler = getCharacterName(brawler), + } + end + ) + Array.forEach(unattachedParticipants, function() + table.insert(participants, table.remove(unattachedParticipants, 1)) + end) + Table.mergeInto(allParticipants, Table.map(participants, MatchGroupInputUtil.prefixPartcipants(opponentIndex))) + end) - return participants + return allParticipants end return CustomMatchGroupInput diff --git a/components/match2/wikis/dota2/match_group_input_custom.lua b/components/match2/wikis/dota2/match_group_input_custom.lua index daf1e1e9ac9..57d6a3010b4 100644 --- a/components/match2/wikis/dota2/match_group_input_custom.lua +++ b/components/match2/wikis/dota2/match_group_input_custom.lua @@ -80,7 +80,7 @@ function CustomMatchGroupInput.processMatchWithoutStandalone(MatchParser, match) local opponents = Array.mapIndexes(function(opponentIndex) return MatchGroupInputUtil.readOpponent(match, opponentIndex, OPPONENT_CONFIG) end) - local games = MatchFunctions.extractMaps(MatchParser, match, #opponents) + local games = MatchFunctions.extractMaps(MatchParser, match, opponents) match.bestof = MatchGroupInputUtil.getBestOf(match.bestof, games) local autoScoreFunction = MatchGroupInputUtil.canUseAutoScore(match, games) @@ -120,9 +120,9 @@ end ---@param MatchParser Dota2MatchParserInterface ---@param match table ----@param opponentCount integer +---@param opponents table[] ---@return table[] -function MatchFunctions.extractMaps(MatchParser, match, opponentCount) +function MatchFunctions.extractMaps(MatchParser, match, opponents) local maps = {} for key, mapInput, mapIndex in Table.iter.pairsByPrefix(match, 'map', {requireIndex = true}) do local map = MatchParser.getMap(mapInput) @@ -136,11 +136,11 @@ function MatchFunctions.extractMaps(MatchParser, match, opponentCount) map.length = MatchParser.getLength(map) map.vod = map.vod or String.nilIfEmpty(match['vodgame' .. mapIndex]) map.publisherid = map.matchid or String.nilIfEmpty(match['matchid' .. mapIndex]) - map.participants = MapFunctions.getParticipants(MatchParser, map, opponentCount) - map.extradata = MapFunctions.getExtraData(MatchParser, map, opponentCount) + map.participants = MapFunctions.getParticipants(MatchParser, map, opponents) + map.extradata = MapFunctions.getExtraData(MatchParser, map, #opponents) map.finished = MatchGroupInputUtil.mapIsFinished(map) - local opponentInfo = Array.map(Array.range(1, opponentCount), function(opponentIndex) + local opponentInfo = Array.map(opponents, function(_, opponentIndex) local score, status = MatchGroupInputUtil.computeOpponentScore({ walkover = map.walkover, winner = map.winner, @@ -261,20 +261,34 @@ end -- Parse participant information ---@param MatchParser Dota2MatchParserInterface ---@param map table ----@param opponentCount integer +---@param opponents table[] ---@return table -function MapFunctions.getParticipants(MatchParser, map, opponentCount) - local participants = {} +function MapFunctions.getParticipants(MatchParser, map, opponents) + local allParticipants = {} local getCharacterName = FnUtil.curry(MatchGroupInputUtil.getCharacterName, HeroNames) - for opponentIndex = 1, opponentCount do - for playerIndex, participant in ipairs(MatchParser.getParticipants(map, opponentIndex) or {}) do - participant.character = getCharacterName(participant.character) - participants[opponentIndex .. '_' .. playerIndex] = participant - end - end + Array.forEach(opponents, function(opponent, opponentIndex) + local participantList = MatchParser.getParticipants(map, opponentIndex) or {} + local participants, unattachedParticipants = MatchGroupInputUtil.parseParticipants( + opponent.match2players, + participantList, + function (playerIndex) + local participant = participantList[playerIndex] + return participant and {name = participant.player} or nil + end, + function(playerIndex) + local participant = participantList[playerIndex] + participant.character = getCharacterName(participant.character) + return participant + end, + OPPONENT_CONFIG) + Array.forEach(unattachedParticipants, function(participant) + table.insert(participants, participant) + end) + Table.mergeInto(allParticipants, Table.map(participants, MatchGroupInputUtil.prefixPartcipants(opponentIndex))) + end) - return participants + return allParticipants end ---@param winnerInput string|integer|nil diff --git a/components/match2/wikis/leagueoflegends/match_group_input_custom.lua b/components/match2/wikis/leagueoflegends/match_group_input_custom.lua index f1f69ba15cf..cd55e8afdb7 100644 --- a/components/match2/wikis/leagueoflegends/match_group_input_custom.lua +++ b/components/match2/wikis/leagueoflegends/match_group_input_custom.lua @@ -80,7 +80,7 @@ function CustomMatchGroupInput.processMatchWithoutStandalone(MatchParser, match) local opponents = Array.mapIndexes(function(opponentIndex) return MatchGroupInputUtil.readOpponent(match, opponentIndex, OPPONENT_CONFIG) end) - local games = MatchFunctions.extractMaps(MatchParser, match, #opponents) + local games = MatchFunctions.extractMaps(MatchParser, match, opponents) match.bestof = MatchGroupInputUtil.getBestOf(match.bestof, games) local autoScoreFunction = MatchGroupInputUtil.canUseAutoScore(match, games) @@ -120,9 +120,9 @@ end ---@param MatchParser LeagueOfLegendsMatchParserInterface ---@param match table ----@param opponentCount integer +---@param opponents table[] ---@return table[] -function MatchFunctions.extractMaps(MatchParser, match, opponentCount) +function MatchFunctions.extractMaps(MatchParser, match, opponents) local maps = {} for key, mapInput, mapIndex in Table.iter.pairsByPrefix(match, 'map', {requireIndex = true}) do local map = MatchParser.getMap(mapInput) @@ -135,11 +135,11 @@ function MatchFunctions.extractMaps(MatchParser, match, opponentCount) map.length = MatchParser.getLength(map) map.vod = map.vod or String.nilIfEmpty(match['vodgame' .. mapIndex]) - map.participants = MapFunctions.getParticipants(MatchParser, map, opponentCount) - map.extradata = MapFunctions.getExtraData(MatchParser, map, opponentCount) + map.participants = MapFunctions.getParticipants(MatchParser, map, opponents) + map.extradata = MapFunctions.getExtraData(MatchParser, map, #opponents) map.finished = MatchGroupInputUtil.mapIsFinished(map) - local opponentInfo = Array.map(Array.range(1, opponentCount), function(opponentIndex) + local opponentInfo = Array.map(opponents, function(_, opponentIndex) local score, status = MatchGroupInputUtil.computeOpponentScore({ walkover = map.walkover, winner = map.winner, @@ -242,20 +242,34 @@ end -- Parse participant information ---@param MatchParser LeagueOfLegendsMatchParserInterface ---@param map table ----@param opponentCount integer +---@param opponents table[] ---@return table -function MapFunctions.getParticipants(MatchParser, map, opponentCount) - local participants = {} +function MapFunctions.getParticipants(MatchParser, map, opponents) + local allParticipants = {} local getCharacterName = FnUtil.curry(MatchGroupInputUtil.getCharacterName, HeroNames) - for opponentIndex = 1, opponentCount do - for playerIndex, participant in ipairs(MatchParser.getParticipants(map, opponentIndex) or {}) do - participant.character = getCharacterName(participant.character) - participants[opponentIndex .. '_' .. playerIndex] = participant - end - end + Array.forEach(opponents, function(opponent, opponentIndex) + local participantList = MatchParser.getParticipants(map, opponentIndex) or {} + local participants, unattachedParticipants = MatchGroupInputUtil.parseParticipants( + opponent.match2players, + participantList, + function (playerIndex) + local participant = participantList[playerIndex] + return participant and {name = participant.player} or nil + end, + function(playerIndex) + local participant = participantList[playerIndex] + participant.character = getCharacterName(participant.character) + return participant + end, + OPPONENT_CONFIG) + Array.forEach(unattachedParticipants, function(participant) + table.insert(participants, participant) + end) + Table.mergeInto(allParticipants, Table.map(participants, MatchGroupInputUtil.prefixPartcipants(opponentIndex))) + end) - return participants + return allParticipants end ---@param winnerInput string|integer|nil diff --git a/components/match2/wikis/valorant/match_group_input_custom.lua b/components/match2/wikis/valorant/match_group_input_custom.lua index 21ad577faa8..279c18552f0 100644 --- a/components/match2/wikis/valorant/match_group_input_custom.lua +++ b/components/match2/wikis/valorant/match_group_input_custom.lua @@ -40,7 +40,7 @@ function CustomMatchGroupInput.processMatch(match, options) local opponents = Array.mapIndexes(function(opponentIndex) return MatchGroupInputUtil.readOpponent(match, opponentIndex, {}) end) - local games = CustomMatchGroupInput.extractMaps(match, #opponents) + local games = CustomMatchGroupInput.extractMaps(match, opponents) match.bestof = MatchGroupInputUtil.getBestOf(nil, games) games = MatchFunctions.removeUnsetMaps(games) @@ -80,19 +80,19 @@ end ---@param match table ----@param opponentCount integer +---@param opponents table[] ---@return table[] -function CustomMatchGroupInput.extractMaps(match, opponentCount) +function CustomMatchGroupInput.extractMaps(match, opponents) local maps = {} for key, map in Table.iter.pairsByPrefix(match, 'map', {requireIndex = true}) do local finishedInput = map.finished --[[@as string?]] local winnerInput = map.winner --[[@as string?]] - map.participants = MapFunctions.getParticipants(map, opponentCount) + map.participants = MapFunctions.getParticipants(map, opponents) map.extradata = MapFunctions.getExtraData(map, map.participants) map.finished = MatchGroupInputUtil.mapIsFinished(map) - local opponentInfo = Array.map(Array.range(1, opponentCount), function(opponentIndex) + local opponentInfo = Array.map(opponents, function(_, opponentIndex) local score, status = MatchGroupInputUtil.computeOpponentScore({ walkover = map.walkover, winner = map.winner, @@ -200,30 +200,44 @@ function MapFunctions.getExtraData(map, participants) end ---@param map table ----@param opponentCount integer +---@param opponents table[] ---@return table -function MapFunctions.getParticipants(map, opponentCount) - local participants = {} +function MapFunctions.getParticipants(map, opponents) + local allParticipants = {} local getCharacterName = FnUtil.curry(MatchGroupInputUtil.getCharacterName, AgentNames) - for opponentIdx = 1, opponentCount do - for _, stats, playerIdx in Table.iter.pairsByPrefix(map, 't' .. opponentIdx .. 'p', {requireIndex = true}) do - stats = Json.parseIfString(stats) - - local participant = { - kills = stats.kills, - deaths = stats.deaths, - assists = stats.assists, - acs = stats.acs, - player = stats.player, - agent = getCharacterName(stats.agent), - } - - participants[opponentIdx .. '_' .. playerIdx] = participant - end - end + Array.forEach(opponents, function(opponent, opponentIndex) + local players = Array.mapIndexes(function(playerIndex) + return opponent.match2players[playerIndex] or + (map['t' .. opponentIndex .. 'p' .. playerIndex] and {}) or + nil + end) + local participants, unattachedParticipants = MatchGroupInputUtil.parseParticipants( + opponent.match2players, + players, + function(playerIndex) + local data = Json.parseIfString(map['t' .. opponentIndex .. 'p' .. playerIndex]) + return data and {name = data.player} or nil + end, + function(playerIndex, playerIdData) + local stats = Json.parseIfString(map['t'.. opponentIndex .. 'p' .. playerIndex]) + return { + kills = stats.kills, + deaths = stats.deaths, + assists = stats.assists, + acs = stats.acs, + player = playerIdData.player, + agent = getCharacterName(stats.agent), + } + end + ) + Array.forEach(unattachedParticipants, function(participant) + table.insert(participants, participant) + end) + Table.mergeInto(allParticipants, Table.map(participants, MatchGroupInputUtil.prefixPartcipants(opponentIndex))) + end) - return participants + return allParticipants end ---@param map table