From 231ed812376f87718bbfdd0f06f6d2dd4f2cba9b Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Fri, 7 Jun 2024 12:03:22 -0700 Subject: [PATCH 01/31] Adding MVPs to Clash Royale. Also adding misisng file to the repo --- .../wikis/clashroyale/match_summary.lua | 15 + .../clashroyale/matchgroup_input_custom.lua | 747 ++++++++++++++++++ 2 files changed, 762 insertions(+) create mode 100644 components/match2/wikis/clashroyale/matchgroup_input_custom.lua diff --git a/components/match2/wikis/clashroyale/match_summary.lua b/components/match2/wikis/clashroyale/match_summary.lua index 2ad27b964d3..683d64ac2ec 100644 --- a/components/match2/wikis/clashroyale/match_summary.lua +++ b/components/match2/wikis/clashroyale/match_summary.lua @@ -136,6 +136,21 @@ function CustomMatchSummary._createGame(game, gameIndex, date) date = date, }) + -- Add Match MVP(s) + if match.extradata.mvp then + local mvpData = match.extradata.mvp + if not Table.isEmpty(mvpData) and mvpData.players then + local mvp = MatchSummary.Mvp() + for _, player in ipairs(mvpData.players) do + mvp:addPlayer(player) + end + mvp:setPoints(mvpData.points) + + body:addRow(mvp) + end + + end + -- Add Comment if not Logic.isEmpty(game.comment) then row:addElement(MatchSummary.Break():create()) diff --git a/components/match2/wikis/clashroyale/matchgroup_input_custom.lua b/components/match2/wikis/clashroyale/matchgroup_input_custom.lua new file mode 100644 index 00000000000..3cd3d776151 --- /dev/null +++ b/components/match2/wikis/clashroyale/matchgroup_input_custom.lua @@ -0,0 +1,747 @@ +--- +-- @Liquipedia +-- wiki=clashroyale +-- page=Module:MatchGroup/Input/Custom +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Array = require('Module:Array') +local DateExt = require('Module:Date/Ext') +local Flags = require('Module:Flags') +local Json = require('Module:Json') +local Logic = require('Module:Logic') +local Lua = require('Module:Lua') +local String = require('Module:StringUtils') +local Table = require('Module:Table') +local CardNames = mw.loadData('Module:CardNames') +local Variables = require('Module:Variables') + +local MatchGroupInput = Lua.import('Module:MatchGroup/Input', {requireDevIfEnabled = true}) +local Streams = Lua.import('Module:Links/Stream', {requireDevIfEnabled = true}) + +local Opponent = require('Module:OpponentLibraries').Opponent + +local UNKNOWN_REASON_LOSS_STATUS = 'L' +local DEFAULT_WIN_STATUS = 'W' +local DEFAULT_WIN_RESULTTYPE = 'default' +local NO_SCORE = -1 +local SCORE_STATUS = 'S' +local ALLOWED_STATUSES = {DEFAULT_WIN_STATUS, 'FF', 'DQ', UNKNOWN_REASON_LOSS_STATUS} +local MAX_NUM_OPPONENTS = 2 +local DEFAULT_BEST_OF = 99 +local EPOCH_TIME_EXTENDED = '1970-01-01T00:00:00+00:00' +local NOW = os.time(os.date('!*t')) +local ROYALE_API_PREFIX = 'https://royaleapi.com/' +local MAX_NUM_PLAYERS_PER_MAP = 2 +local TBD = 'tbd' +local TBA = 'tba' +local MAX_NUM_MAPS = 30 + +local CustomMatchGroupInput = {} + +CustomMatchGroupInput.walkoverProcessing = {} +local walkoverProcessing = CustomMatchGroupInput.walkoverProcessing + +-- called from Module:MatchGroup +function CustomMatchGroupInput.processMatch(match) + Table.mergeInto( + match, + CustomMatchGroupInput._readDate(match) + ) + match = CustomMatchGroupInput._getExtraData(match) + match = CustomMatchGroupInput._getTournamentVars(match) + match = CustomMatchGroupInput._adjustData(match) + match = CustomMatchGroupInput._getVodStuff(match) + match = CustomMatchGroupInput._getLinks(match) + + return match +end + +function CustomMatchGroupInput._readDate(matchArgs) + if matchArgs.date then + return MatchGroupInput.readDate(matchArgs.date) + else + return { + date = EPOCH_TIME_EXTENDED, + dateexact = false, + timestamp = DateExt.epochZero, + } + end +end + +function CustomMatchGroupInput._getTournamentVars(match) + match.mode = Logic.emptyOr(match.mode, Variables.varDefault('tournament_mode', 'solo')) + match.publishertier = Logic.emptyOr(match.publishertier, Variables.varDefault('tournament_publishertier')) + return MatchGroupInput.getCommonTournamentVars(match) +end + +function CustomMatchGroupInput._getVodStuff(match) + match.stream = Streams.processStreams(match) + match.vod = Logic.emptyOr(match.vod) + + return match +end + +function CustomMatchGroupInput._getLinks(match) + match.links = { + royaleapi = match.royaleapi and (ROYALE_API_PREFIX .. match.royaleapi) or nil, + } + return match +end + +function CustomMatchGroupInput._getExtraData(match) + match.extradata = { + mvp = MatchGroupInput.readMvp(match), + mvpteam = match.mvpteam or match.winner, + casters = match.casters, + t1bans = CustomMatchGroupInput._readBans(match.t1bans), + t2bans = CustomMatchGroupInput._readBans(match.t2bans), + } + + for subGroupIndex = 1, MAX_NUM_MAPS do + local prefix = 'subgroup' .. subGroupIndex + + match.extradata[prefix .. 'header'] = CustomMatchGroupInput._getSubGroupHeader(subGroupIndex, match) + match.extradata[prefix .. 'iskoth'] = Logic.readBool(match[prefix .. 'iskoth']) or nil + match.extradata[prefix .. 't1bans'] = CustomMatchGroupInput._readBans(match[prefix .. 't1bans']) + match.extradata[prefix .. 't2bans'] = CustomMatchGroupInput._readBans(match[prefix .. 't2bans']) + end + + return match +end + +function CustomMatchGroupInput._getSubGroupHeader(subGroupIndex, match) + local header = match['subgroup' .. subGroupIndex .. 'header'] + + return String.isNotEmpty(header) and header or nil +end + +function CustomMatchGroupInput._readBans(bansInput) + local bans = CustomMatchGroupInput._readCards(bansInput) + + return Table.isNotEmpty(bans) and bans or nil +end + +function CustomMatchGroupInput._adjustData(match) + --parse opponents + set base sumscores + match = CustomMatchGroupInput._opponentInput(match) + + --main processing done here + local subGroupIndex = 0 + for _, _, mapIndex in Table.iter.pairsByPrefix(match, 'map') do + match, subGroupIndex = CustomMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) + end + + match = CustomMatchGroupInput._matchWinnerProcessing(match) + + CustomMatchGroupInput._setPlacements(match) + + if CustomMatchGroupInput._hasTeamOpponent(match) then + match = CustomMatchGroupInput._subMatchStructure(match) + else + mw.logObject(match) + end + + if Logic.isNumeric(match.winner) then + match.finished = true + end + + return match +end + +function CustomMatchGroupInput._matchWinnerProcessing(match) + local bestof = tonumber(match.bestof) or Variables.varDefault('bestof', DEFAULT_BEST_OF) + match.bestof = bestof + Variables.varDefine('bestof', bestof) + + local scores = Array.map(Array.range(1, MAX_NUM_OPPONENTS), function(opponentIndex) + local opponent = match['opponent' .. opponentIndex] + if not opponent then + return NO_SCORE + end + + -- set the score either from manual input or sumscore + opponent.score = Table.includes(ALLOWED_STATUSES, string.upper(opponent.score or '')) + and opponent.score:upper() + or tonumber(opponent.score) or tonumber(opponent.sumscore) or NO_SCORE + + return opponent.score + end) + + walkoverProcessing.walkover(match, scores) + + if match.resulttype == DEFAULT_WIN_RESULTTYPE then + walkoverProcessing.applyMatchWalkoverToOpponents(match) + return match + end + + if match.winner == 'draw' then + match.winner = 0 + end + + for opponentIndex = 1, MAX_NUM_OPPONENTS do + local opponent = match['opponent' .. opponentIndex] + if Logic.isNotEmpty(opponent) then + opponent.status = SCORE_STATUS + if opponent.score > bestof / 2 then + match.finished = Logic.emptyOr(match.finished, true) + match.winner = tonumber(match.winner) or opponentIndex + elseif match.winner == 0 or (opponent.score == bestof / 2 and match.opponent1.score == match.opponent2.score) then + match.finished = Logic.emptyOr(match.finished, true) + match.winner = 0 + match.resulttype = 'draw' + end + end + end + + match.winner = tonumber(match.winner) + + CustomMatchGroupInput._checkFinished(match) + + if match.finished and not match.winner then + CustomMatchGroupInput._determineWinnerIfMissing(match, scores) + end + + return match +end + +function CustomMatchGroupInput._checkFinished(match) + if Logic.readBoolOrNil(match.finished) == false then + match.finished = false + elseif Logic.readBool(match.finished) or match.winner then + match.finished = true + end + + -- Match is automatically marked finished upon page edit after a + -- certain amount of time (depending on whether the date is exact) + if not match.finished and match.timestamp > DateExt.epochZero then + local threshold = match.dateexact and 30800 or 86400 + if match.timestamp + threshold < NOW then + match.finished = true + end + end +end + +function CustomMatchGroupInput._determineWinnerIfMissing(match, scores) + local maxScore = math.max(unpack(scores) or 0) + -- if we have a positive score and the match is finished we also have a winner + if maxScore > 0 then + if Array.all(scores, function(score) return score == maxScore end) then + match.winner = 0 + return + end + + for opponentIndex, score in pairs(scores) do + if score == maxScore then + match.winner = opponentIndex + return + end + end + end +end + +function CustomMatchGroupInput._setPlacements(match) + for opponentIndex = 1, MAX_NUM_OPPONENTS do + local opponent = match['opponent' .. opponentIndex] + + if match.winner == opponentIndex or match.winner == 0 then + opponent.placement = 1 + elseif match.winner then + opponent.placement = 2 + end + end +end + +function CustomMatchGroupInput._subMatchStructure(match) + local subMatches = {} + + local currentSubGroup = 0 + for _, map in Table.iter.pairsByPrefix(match, 'map') do + local subGroupIndex = tonumber(map.subgroup) + if subGroupIndex then + currentSubGroup = subGroupIndex + else + currentSubGroup = currentSubGroup + 1 + subGroupIndex = currentSubGroup + end + + if not subMatches[subGroupIndex] then + subMatches[subGroupIndex] = {scores = {0, 0}} + end + + local winner = tonumber(map.winner) + if winner and subMatches[subGroupIndex].scores[winner] then + subMatches[subGroupIndex].scores[winner] = subMatches[subGroupIndex].scores[winner] + 1 + end + end + + for subMatchIndex, subMatch in ipairs(subMatches) do + -- get winner if the submatch is finished + -- submatch is finished if the next submatch has a score or if the complete match is finished + local nextSubMatch = subMatches[subMatchIndex + 1] + if Logic.readBool(match.finished) or (nextSubMatch and nextSubMatch.scores[1] + nextSubMatch.scores[2] > 0) then + if subMatch.scores[1] > subMatch.scores[2] then + subMatch.winner = 1 + elseif subMatch.scores[2] > subMatch.scores[1] then + subMatch.winner = 2 + end + end + end + + match.extradata.submatches = subMatches + + return match +end + +--[[ + +OpponentInput functions + +]]-- +function CustomMatchGroupInput._opponentInput(match) + local opponentIndex = 1 + local opponent = match['opponent' .. opponentIndex] + + while opponentIndex <= MAX_NUM_OPPONENTS and Logic.isNotEmpty(opponent) do + opponent = Json.parseIfString(opponent) or Opponent.blank() + + -- Convert byes to literals + if + string.lower(opponent.template or '') == 'bye' + or string.lower(opponent.name or '') == 'bye' + then + opponent = {type = Opponent.literal, name = 'BYE'} + end + + --process input + if opponent.type == Opponent.team or opponent.type == Opponent.solo or + opponent.type == Opponent.duo or opponent.type == Opponent.literal then + + opponent = CustomMatchGroupInput.processOpponent(opponent, match.timestamp) + else + error('Unsupported Opponent Type') + end + + --set initial opponent sumscore + opponent.sumscore = 0 + + match['opponent' .. opponentIndex] = opponent + + opponentIndex = opponentIndex + 1 + opponent = match['opponent' .. opponentIndex] + end + + return match +end + +function CustomMatchGroupInput.processOpponent(record, timestamp) + local opponent = Opponent.readOpponentArgs(record) + or Opponent.blank() + + local teamTemplateDate = timestamp + -- If date is epoch, resolve using tournament dates instead + -- Epoch indicates that the match is missing a date + -- In order to get correct child team template, we will use an approximately date and not 1970-01-01 + if teamTemplateDate == DateExt.epochZero then + teamTemplateDate = Variables.varDefaultMulti( + 'tournament_enddate', + 'tournament_startdate', + NOW + ) + end + + Opponent.resolve(opponent, teamTemplatedate, {syncPlayer=true}) + + MatchGroupInput.mergeRecordWithOpponent(record, opponent) + + for _, player in pairs(record.players or {}) do + player.name = player.name:gsub(' ', '_') + end + + if record.name then + record.name = record.name:gsub(' ', '_') + end + + if record.type == Opponent.team then + record.icon, record.icondark = CustomMatchGroupInput.getIcon(opponent.template) + record.match2players = CustomMatchGroupInput._readTeamPlayers(record, record.players) + end + + return record +end + +function CustomMatchGroupInput._readTeamPlayers(opponent, playerData) + local players = CustomMatchGroupInput._getManuallyEnteredPlayers(playerData) + + if Table.isEmpty(players) then + players = CustomMatchGroupInput._getPlayersFromVariables(opponent.name) + end + + return players +end + +function CustomMatchGroupInput._getManuallyEnteredPlayers(playerData) + local players = {} + playerData = Json.parseIfString(playerData) or {} + + for prefix, displayName in Table.iter.pairsByPrefix(playerData, 'p') do + local name = mw.ext.TeamLiquidIntegration.resolve_redirect(Logic.emptyOr( + playerData[prefix .. 'link'], + displayName + )):gsub(' ', '_') + + table.insert(players, { + name = name, + displayname = displayName, + flag = Flags.CountryName(playerData[prefix .. 'flag']), + }) + end + + return players +end + +function CustomMatchGroupInput._getPlayersFromVariables(teamName) + teamName = teamName:gsub(' ', '_') + local teamNameWithSpaces = teamName:gsub('_', ' ') + local players = {} + + local playerIndex = 1 + while true do + local prefix = teamName .. '_p' .. playerIndex + local prefixWithSpaces = teamNameWithSpaces .. '_p' .. playerIndex + + local playerName = Variables.varDefault(prefix) or Variables.varDefault(prefixWithSpaces) + if String.isEmpty(playerName) then + break + end + table.insert(players, { + name = playerName:gsub(' ', '_'), + displayname = Variables.varDefault(prefix .. 'dn', + Variables.varDefault(prefixWithSpaces .. 'dn', playerName:gsub('_', ' '))), + flag = Flags.CountryName(Variables.varDefault(prefix .. 'flag', Variables.varDefault(prefixWithSpaces .. 'flag'))), + }) + playerIndex = playerIndex + 1 + end + + return players +end + +--[[ + +MapInput functions + +]]-- +function CustomMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) + local map = Json.parseIfString(match['map' .. mapIndex]) or {} + + if Table.isEmpty(map) then + match['map' .. mapIndex] = nil + return match, subGroupIndex + end + + -- CR has no map names, use generic one instead + map.map = 'Set ' .. mapIndex + + -- set initial extradata for maps + map.extradata = { + comment = map.comment, + header = map.header, + } + + -- inherit stuff from match data + map.type = Logic.emptyOr(map.type, match.type) + map.liquipediatier = match.liquipediatier + map.liquipediatiertype = match.liquipediatiertype + map.game = Logic.emptyOr(map.game, match.game) + map.date = Logic.emptyOr(map.date, match.date) + + -- determine score, resulttype, walkover and winner + map = CustomMatchGroupInput._mapWinnerProcessing(map) + + -- get participants data for the map + get map mode + map = CustomMatchGroupInput._processPlayerMapData(map, match) + + -- set sumscore to 0 if it isn't a number + if String.isEmpty(match.opponent1.sumscore) then + match.opponent1.sumscore = 0 + end + if String.isEmpty(match.opponent2.sumscore) then + match.opponent2.sumscore = 0 + end + + --adjust sumscore for winner opponent + if (tonumber(map.winner) or 0) > 0 then + match['opponent' .. map.winner].sumscore = + match['opponent' .. map.winner].sumscore + 1 + end + + match['map' .. mapIndex] = map + + return match, subGroupIndex +end + +function CustomMatchGroupInput._mapWinnerProcessing(map) + if map.winner == 'skip' then + map.scores = {NO_SCORE, NO_SCORE} + map.resulttype = 'np' + + return map + end + + map.scores = {} + local hasManualScores = false + + local scores = Array.map(Array.range(1, MAX_NUM_OPPONENTS), function(opponentIndex) + local score = map['score' .. opponentIndex] + map.scores[opponentIndex] = tonumber(score) or NO_SCORE + + if String.isEmpty(score) then + hasManualScores = true + end + + return Table.includes(ALLOWED_STATUSES, string.upper(score or '')) + and score:upper() + or map.scores[opponentIndex] + end) + + if not hasManualScores then + local winnerInput = tonumber(map.winner) + if winnerInput == 1 then + map.scores = {1, 0} + elseif winnerInput == 2 then + map.scores = {0, 1} + end + + return map + end + + walkoverProcessing.walkover(map, scores) + + return map +end + +function CustomMatchGroupInput._processPlayerMapData(map, match) + local participants = {} + + for opponentIndex = 1, MAX_NUM_OPPONENTS do + local opponent = match['opponent' .. opponentIndex] + if Opponent.typeIsParty(opponent.type) then + CustomMatchGroupInput._processDefaultPlayerMapData( + opponent.match2players or {}, + opponentIndex, + map, + participants + ) + elseif opponent.type == Opponent.team then + CustomMatchGroupInput._processTeamPlayerMapData( + opponent.match2players or {}, + opponentIndex, + map, + participants + ) + end + end + + map.mode = Opponent.toMode(match.opponent1.type, match.opponent2.type) + + map.participants = participants + + return map +end + +function CustomMatchGroupInput._processDefaultPlayerMapData(players, opponentIndex, map, participants) + for playerIndex = 1, #players do + participants[opponentIndex .. '_' .. playerIndex] = { + played = true, + cards = CustomMatchGroupInput._readCards(map['t' .. opponentIndex .. 'p' .. playerIndex .. 'c']), + } + end +end + +function CustomMatchGroupInput._processTeamPlayerMapData(players, opponentIndex, map, participants) + local tbdIndex = 0 + local appendIndex = #players + 1 + + local playerIndex = 1 + local playerKey = 't' .. opponentIndex .. 'p' .. playerIndex + while playerIndex <= MAX_NUM_PLAYERS_PER_MAP and (String.isNotEmpty(map[playerKey]) or + String.isNotEmpty(map[playerKey .. 'link']) or String.isNotEmpty(map[playerKey .. 'c'])) do + + local player = map[playerKey .. 'link'] or map[playerKey] + if String.isEmpty(player) or Table.includes({TBD, TBA}, player:lower()) then + tbdIndex = tbdIndex + 1 + player = TBD .. tbdIndex + else + -- allows fetching the link of the player from preset wiki vars + player = mw.ext.TeamLiquidIntegration.resolve_redirect( + map[playerKey .. 'link'] or Variables.varDefault(map[playerKey] .. '_page') or map[playerKey] + ) + end + + local playerData = { + played = true, + cards = CustomMatchGroupInput._readCards(map[playerKey .. 'c']), + } + + local match2playerIndex = CustomMatchGroupInput._fetchMatch2PlayerIndexOfPlayer(players, player) + + -- if we have the player not present in match2player add basic data here + if not match2playerIndex then + match2playerIndex = appendIndex + playerData = Table.merge(playerData, {name = player:gsub(' ', '_'), displayname = map[playerKey] or player}) + + appendIndex = appendIndex + 1 + end + + participants[opponentIndex .. '_' .. match2playerIndex] = playerData + + playerIndex = playerIndex + 1 + playerKey = 't' .. opponentIndex .. 'p' .. playerIndex + end + + return playerIndex - 1 +end + +function CustomMatchGroupInput._fetchMatch2PlayerIndexOfPlayer(players, player) + local displayNameIndex + local displayNameFoundTwice = false + + for match2playerIndex, match2player in pairs(players) do + local playerWithUnderscores = player:gsub(' ', '_') + if match2player and match2player.name == playerWithUnderscores then + return match2playerIndex + elseif not displayNameIndex and match2player and match2player.displayname == playerWithUnderscores then + displayNameIndex = match2playerIndex + elseif match2player and match2player.displayname == playerWithUnderscores then + displayNameFoundTwice = true + end + end + + if not displayNameFoundTwice then + return displayNameIndex + end +end + +function CustomMatchGroupInput._readCards(input) + local cards = Json.parseIfString(input) or {} + + for cardIndex, card in pairs(cards) do + if not CardNames[card:lower()] then + error('Invalid Card "' .. card .. '"') + end + cards[cardIndex] = CardNames[card] + end + + return cards +end + +function CustomMatchGroupInput.getIcon(template) + local raw = mw.ext.TeamTemplate.raw(template) + if raw then + local icon = Logic.emptyOr(raw.image, raw.legacyimage) + local iconDark = Logic.emptyOr(raw.imagedark, raw.legacyimagedark) + return icon, iconDark + end +end + +function CustomMatchGroupInput._hasTeamOpponent(match) + return match.opponent1.type == Opponent.team or match.opponent2.type == Opponent.team +end + + + +function walkoverProcessing.walkover(obj, scores) + local walkover = obj.walkover + + if Logic.isNumeric(walkover) then + walkoverProcessing.numericWalkover(obj, walkover) + elseif walkover then + walkoverProcessing.nonNumericWalkover(obj, walkover) + elseif #scores ~=2 then -- since we always have 2 opponents + error('Unexpected number of opponents when calculating winner') + elseif Array.all(scores, function(score) + return Table.includes(ALLOWED_STATUSES, score) and score ~= DEFAULT_WIN_STATUS + end) then + + walkoverProcessing.scoreDoubleWalkover(obj, scores) + elseif Array.any(scores, function(score) return Table.includes(ALLOWED_STATUSES, score) end) then + walkoverProcessing.scoreWalkover(obj, scores) + end +end + +function walkoverProcessing.numericWalkover(obj, walkover) + local winner = tonumber(walkover) + + obj.winner = winner + obj.finished = true + obj.walkover = UNKNOWN_REASON_LOSS_STATUS + obj.resulttype = DEFAULT_WIN_RESULTTYPE +end + +function walkoverProcessing.nonNumericWalkover(obj, walkover) + if not Table.includes(ALLOWED_STATUSES, string.upper(walkover)) then + error('Invalid walkover "' .. walkover .. '"') + elseif not Logic.isNumeric(obj.winner) then + error('Walkover without winner specified') + end + + obj.winner = tonumber(obj.winner) + obj.finished = true + obj.walkover = walkover:upper() + obj.resulttype = DEFAULT_WIN_RESULTTYPE +end + +function walkoverProcessing.scoreDoubleWalkover(obj, scores) + obj.winner = -1 + obj.finished = true + obj.walkover = scores[1] + obj.resulttype = DEFAULT_WIN_RESULTTYPE +end + +function walkoverProcessing.scoreWalkover(obj, scores) + local winner, status + + for scoreIndex, score in pairs(scores) do + score = string.upper(score) + if score == DEFAULT_WIN_STATUS then + winner = scoreIndex + elseif Table.includes(ALLOWED_STATUSES, score) then + status = score + else + status = UNKNOWN_REASON_LOSS_STATUS + end + end + + if not winner then + error('Invalid score combination "{' .. scores[1] .. ', ' .. scores[2] .. '}"') + end + + obj.winner = winner + obj.finished = true + obj.walkover = status + obj.resulttype = DEFAULT_WIN_RESULTTYPE +end + +function walkoverProcessing.applyMatchWalkoverToOpponents(match) + for opponentIndex = 1, MAX_NUM_OPPONENTS do + local score = match['opponent' .. opponentIndex].score + + if Logic.isNumeric(score) or String.isEmpty(score) then + match['opponent' .. opponentIndex].score = String.isNotEmpty(score) and score or NO_SCORE + match['opponent' .. opponentIndex].status = match.walkover + elseif score and Table.includes(ALLOWED_STATUSES, score:upper()) then + match['opponent' .. opponentIndex].score = NO_SCORE + match['opponent' .. opponentIndex].status = score + else + error('Invalid score "' .. score .. '"') + end + end + + -- neither match.opponent0 nor match['opponent-1'] does exist hence the if + if match['opponent' .. match.winner] then + match['opponent' .. match.winner].status = DEFAULT_WIN_STATUS + end +end + +return CustomMatchGroupInput \ No newline at end of file From 0ec03743dc8359d22603df88143b7a06dddef54f Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Fri, 7 Jun 2024 12:34:40 -0700 Subject: [PATCH 02/31] Minor Linter fixes, and renaming file --- ...ustom.lua => match_group_input_custom.lua} | 309 ++++++++---------- .../wikis/clashroyale/match_summary.lua | 28 +- 2 files changed, 157 insertions(+), 180 deletions(-) rename components/match2/wikis/clashroyale/{matchgroup_input_custom.lua => match_group_input_custom.lua} (95%) diff --git a/components/match2/wikis/clashroyale/matchgroup_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua similarity index 95% rename from components/match2/wikis/clashroyale/matchgroup_input_custom.lua rename to components/match2/wikis/clashroyale/match_group_input_custom.lua index 3cd3d776151..4f65c875149 100644 --- a/components/match2/wikis/clashroyale/matchgroup_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -17,8 +17,8 @@ local Table = require('Module:Table') local CardNames = mw.loadData('Module:CardNames') local Variables = require('Module:Variables') -local MatchGroupInput = Lua.import('Module:MatchGroup/Input', {requireDevIfEnabled = true}) -local Streams = Lua.import('Module:Links/Stream', {requireDevIfEnabled = true}) +local MatchGroupInput = Lua.import('Module:MatchGroup/Input') +local Streams = Lua.import('Module:Links/Stream') local Opponent = require('Module:OpponentLibraries').Opponent @@ -30,8 +30,7 @@ local SCORE_STATUS = 'S' local ALLOWED_STATUSES = {DEFAULT_WIN_STATUS, 'FF', 'DQ', UNKNOWN_REASON_LOSS_STATUS} local MAX_NUM_OPPONENTS = 2 local DEFAULT_BEST_OF = 99 -local EPOCH_TIME_EXTENDED = '1970-01-01T00:00:00+00:00' -local NOW = os.time(os.date('!*t')) +local NOW = os.time(os.date('!*t')) --[[@as osdateparam]] local ROYALE_API_PREFIX = 'https://royaleapi.com/' local MAX_NUM_PLAYERS_PER_MAP = 2 local TBD = 'tbd' @@ -44,11 +43,10 @@ CustomMatchGroupInput.walkoverProcessing = {} local walkoverProcessing = CustomMatchGroupInput.walkoverProcessing -- called from Module:MatchGroup +---@param match table +---@return table function CustomMatchGroupInput.processMatch(match) - Table.mergeInto( - match, - CustomMatchGroupInput._readDate(match) - ) + Table.mergeInto(match, MatchGroupInput.readDate(match.date)) match = CustomMatchGroupInput._getExtraData(match) match = CustomMatchGroupInput._getTournamentVars(match) match = CustomMatchGroupInput._adjustData(match) @@ -58,16 +56,143 @@ function CustomMatchGroupInput.processMatch(match) return match end -function CustomMatchGroupInput._readDate(matchArgs) - if matchArgs.date then - return MatchGroupInput.readDate(matchArgs.date) - else - return { - date = EPOCH_TIME_EXTENDED, - dateexact = false, - timestamp = DateExt.epochZero, - } +function CustomMatchGroupInput.getIcon(template) + local raw = mw.ext.TeamTemplate.raw(template) + if raw then + local icon = Logic.emptyOr(raw.image, raw.legacyimage) + local iconDark = Logic.emptyOr(raw.imagedark, raw.legacyimagedark) + return icon, iconDark + end +end + +function CustomMatchGroupInput.processOpponent(record, timestamp) + local opponent = Opponent.readOpponentArgs(record) + or Opponent.blank() + + ---@type number|string + local teamTemplateDate = timestamp + -- If date is epoch, resolve using tournament dates instead + -- Epoch indicates that the match is missing a date + -- In order to get correct child team template, we will use an approximately date and not default date + if teamTemplateDate == DateExt.defaultTimestamp then + teamTemplateDate = Variables.varDefaultMulti('tournament_enddate', 'tournament_startdate', NOW) + end + + Opponent.resolve(opponent, teamTemplatedate)---, {syncPlayer=true}) + + MatchGroupInput.mergeRecordWithOpponent(record, opponent) + + for _, player in pairs(record.players or {}) do + player.name = player.name:gsub(' ', '_') + end + + if record.name then + record.name = record.name:gsub(' ', '_') + end + + if record.type == Opponent.team then + record.icon, record.icondark = CustomMatchGroupInput.getIcon(opponent.template) + record.match2players = CustomMatchGroupInput._readTeamPlayers(record, record.players) + end + + return record +end + +function walkoverProcessing.walkover(obj, scores) + local walkover = obj.walkover + + if Logic.isNumeric(walkover) then + walkoverProcessing.numericWalkover(obj, walkover) + elseif walkover then + walkoverProcessing.nonNumericWalkover(obj, walkover) + elseif #scores ~=2 then -- since we always have 2 opponents + error('Unexpected number of opponents when calculating winner') + elseif Array.all(scores, function(score) + return Table.includes(ALLOWED_STATUSES, score) and score ~= DEFAULT_WIN_STATUS + end) then + + walkoverProcessing.scoreDoubleWalkover(obj, scores) + elseif Array.any(scores, function(score) return Table.includes(ALLOWED_STATUSES, score) end) then + walkoverProcessing.scoreWalkover(obj, scores) + end +end + +function walkoverProcessing.numericWalkover(obj, walkover) + local winner = tonumber(walkover) + + obj.winner = winner + obj.finished = true + obj.walkover = UNKNOWN_REASON_LOSS_STATUS + obj.resulttype = DEFAULT_WIN_RESULTTYPE +end + +function walkoverProcessing.nonNumericWalkover(obj, walkover) + if not Table.includes(ALLOWED_STATUSES, string.upper(walkover)) then + error('Invalid walkover "' .. walkover .. '"') + elseif not Logic.isNumeric(obj.winner) then + error('Walkover without winner specified') end + + obj.winner = tonumber(obj.winner) + obj.finished = true + obj.walkover = walkover:upper() + obj.resulttype = DEFAULT_WIN_RESULTTYPE +end + +function walkoverProcessing.scoreDoubleWalkover(obj, scores) + obj.winner = -1 + obj.finished = true + obj.walkover = scores[1] + obj.resulttype = DEFAULT_WIN_RESULTTYPE +end + +function walkoverProcessing.scoreWalkover(obj, scores) + local winner, status + + for scoreIndex, score in pairs(scores) do + score = string.upper(score) + if score == DEFAULT_WIN_STATUS then + winner = scoreIndex + elseif Table.includes(ALLOWED_STATUSES, score) then + status = score + else + status = UNKNOWN_REASON_LOSS_STATUS + end + end + + if not winner then + error('Invalid score combination "{' .. scores[1] .. ', ' .. scores[2] .. '}"') + end + + obj.winner = winner + obj.finished = true + obj.walkover = status + obj.resulttype = DEFAULT_WIN_RESULTTYPE +end + +function walkoverProcessing.applyMatchWalkoverToOpponents(match) + for opponentIndex = 1, MAX_NUM_OPPONENTS do + local score = match['opponent' .. opponentIndex].score + + if Logic.isNumeric(score) or String.isEmpty(score) then + match['opponent' .. opponentIndex].score = String.isNotEmpty(score) and score or NO_SCORE + match['opponent' .. opponentIndex].status = match.walkover + elseif score and Table.includes(ALLOWED_STATUSES, score:upper()) then + match['opponent' .. opponentIndex].score = NO_SCORE + match['opponent' .. opponentIndex].status = score + else + error('Invalid score "' .. score .. '"') + end + end + + -- neither match.opponent0 nor match['opponent-1'] does exist hence the if + if match['opponent' .. match.winner] then + match['opponent' .. match.winner].status = DEFAULT_WIN_STATUS + end +end + +function CustomMatchGroupInput._hasTeamOpponent(match) + return match.opponent1.type == Opponent.team or match.opponent2.type == Opponent.team end function CustomMatchGroupInput._getTournamentVars(match) @@ -139,8 +264,6 @@ function CustomMatchGroupInput._adjustData(match) if CustomMatchGroupInput._hasTeamOpponent(match) then match = CustomMatchGroupInput._subMatchStructure(match) - else - mw.logObject(match) end if Logic.isNumeric(match.winner) then @@ -215,7 +338,7 @@ function CustomMatchGroupInput._checkFinished(match) -- Match is automatically marked finished upon page edit after a -- certain amount of time (depending on whether the date is exact) - if not match.finished and match.timestamp > DateExt.epochZero then + if not match.finished and match.timestamp > DateExt.defaultTimestamp then local threshold = match.dateexact and 30800 or 86400 if match.timestamp + threshold < NOW then match.finished = true @@ -335,42 +458,6 @@ function CustomMatchGroupInput._opponentInput(match) return match end -function CustomMatchGroupInput.processOpponent(record, timestamp) - local opponent = Opponent.readOpponentArgs(record) - or Opponent.blank() - - local teamTemplateDate = timestamp - -- If date is epoch, resolve using tournament dates instead - -- Epoch indicates that the match is missing a date - -- In order to get correct child team template, we will use an approximately date and not 1970-01-01 - if teamTemplateDate == DateExt.epochZero then - teamTemplateDate = Variables.varDefaultMulti( - 'tournament_enddate', - 'tournament_startdate', - NOW - ) - end - - Opponent.resolve(opponent, teamTemplatedate, {syncPlayer=true}) - - MatchGroupInput.mergeRecordWithOpponent(record, opponent) - - for _, player in pairs(record.players or {}) do - player.name = player.name:gsub(' ', '_') - end - - if record.name then - record.name = record.name:gsub(' ', '_') - end - - if record.type == Opponent.team then - record.icon, record.icondark = CustomMatchGroupInput.getIcon(opponent.template) - record.match2players = CustomMatchGroupInput._readTeamPlayers(record, record.players) - end - - return record -end - function CustomMatchGroupInput._readTeamPlayers(opponent, playerData) local players = CustomMatchGroupInput._getManuallyEnteredPlayers(playerData) @@ -636,112 +723,4 @@ function CustomMatchGroupInput._readCards(input) return cards end -function CustomMatchGroupInput.getIcon(template) - local raw = mw.ext.TeamTemplate.raw(template) - if raw then - local icon = Logic.emptyOr(raw.image, raw.legacyimage) - local iconDark = Logic.emptyOr(raw.imagedark, raw.legacyimagedark) - return icon, iconDark - end -end - -function CustomMatchGroupInput._hasTeamOpponent(match) - return match.opponent1.type == Opponent.team or match.opponent2.type == Opponent.team -end - - - -function walkoverProcessing.walkover(obj, scores) - local walkover = obj.walkover - - if Logic.isNumeric(walkover) then - walkoverProcessing.numericWalkover(obj, walkover) - elseif walkover then - walkoverProcessing.nonNumericWalkover(obj, walkover) - elseif #scores ~=2 then -- since we always have 2 opponents - error('Unexpected number of opponents when calculating winner') - elseif Array.all(scores, function(score) - return Table.includes(ALLOWED_STATUSES, score) and score ~= DEFAULT_WIN_STATUS - end) then - - walkoverProcessing.scoreDoubleWalkover(obj, scores) - elseif Array.any(scores, function(score) return Table.includes(ALLOWED_STATUSES, score) end) then - walkoverProcessing.scoreWalkover(obj, scores) - end -end - -function walkoverProcessing.numericWalkover(obj, walkover) - local winner = tonumber(walkover) - - obj.winner = winner - obj.finished = true - obj.walkover = UNKNOWN_REASON_LOSS_STATUS - obj.resulttype = DEFAULT_WIN_RESULTTYPE -end - -function walkoverProcessing.nonNumericWalkover(obj, walkover) - if not Table.includes(ALLOWED_STATUSES, string.upper(walkover)) then - error('Invalid walkover "' .. walkover .. '"') - elseif not Logic.isNumeric(obj.winner) then - error('Walkover without winner specified') - end - - obj.winner = tonumber(obj.winner) - obj.finished = true - obj.walkover = walkover:upper() - obj.resulttype = DEFAULT_WIN_RESULTTYPE -end - -function walkoverProcessing.scoreDoubleWalkover(obj, scores) - obj.winner = -1 - obj.finished = true - obj.walkover = scores[1] - obj.resulttype = DEFAULT_WIN_RESULTTYPE -end - -function walkoverProcessing.scoreWalkover(obj, scores) - local winner, status - - for scoreIndex, score in pairs(scores) do - score = string.upper(score) - if score == DEFAULT_WIN_STATUS then - winner = scoreIndex - elseif Table.includes(ALLOWED_STATUSES, score) then - status = score - else - status = UNKNOWN_REASON_LOSS_STATUS - end - end - - if not winner then - error('Invalid score combination "{' .. scores[1] .. ', ' .. scores[2] .. '}"') - end - - obj.winner = winner - obj.finished = true - obj.walkover = status - obj.resulttype = DEFAULT_WIN_RESULTTYPE -end - -function walkoverProcessing.applyMatchWalkoverToOpponents(match) - for opponentIndex = 1, MAX_NUM_OPPONENTS do - local score = match['opponent' .. opponentIndex].score - - if Logic.isNumeric(score) or String.isEmpty(score) then - match['opponent' .. opponentIndex].score = String.isNotEmpty(score) and score or NO_SCORE - match['opponent' .. opponentIndex].status = match.walkover - elseif score and Table.includes(ALLOWED_STATUSES, score:upper()) then - match['opponent' .. opponentIndex].score = NO_SCORE - match['opponent' .. opponentIndex].status = score - else - error('Invalid score "' .. score .. '"') - end - end - - -- neither match.opponent0 nor match['opponent-1'] does exist hence the if - if match['opponent' .. match.winner] then - match['opponent' .. match.winner].status = DEFAULT_WIN_STATUS - end -end - -return CustomMatchGroupInput \ No newline at end of file +return CustomMatchGroupInput diff --git a/components/match2/wikis/clashroyale/match_summary.lua b/components/match2/wikis/clashroyale/match_summary.lua index 683d64ac2ec..21f777a1051 100644 --- a/components/match2/wikis/clashroyale/match_summary.lua +++ b/components/match2/wikis/clashroyale/match_summary.lua @@ -84,6 +84,19 @@ function CustomMatchSummary.createBody(match) body:addRow(CustomMatchSummary._banRow(extradata.t1bans, extradata.t2bans, match.date)) end + -- Add Match MVP(s) + if match.extradata.mvp then + local mvpData = match.extradata.mvp + if not Table.isEmpty(mvpData) and mvpData.players then + local mvp = MatchSummary.Mvp() + for _, player in ipairs(mvpData.players) do + mvp:addPlayer(player) + end + mvp:setPoints(mvpData.points) + body:addRow(mvp) + end + end + return body end @@ -136,21 +149,6 @@ function CustomMatchSummary._createGame(game, gameIndex, date) date = date, }) - -- Add Match MVP(s) - if match.extradata.mvp then - local mvpData = match.extradata.mvp - if not Table.isEmpty(mvpData) and mvpData.players then - local mvp = MatchSummary.Mvp() - for _, player in ipairs(mvpData.players) do - mvp:addPlayer(player) - end - mvp:setPoints(mvpData.points) - - body:addRow(mvp) - end - - end - -- Add Comment if not Logic.isEmpty(game.comment) then row:addElement(MatchSummary.Break():create()) From 8dea5c8badc2322abd40937fd216baa1652dc56a Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Fri, 7 Jun 2024 12:56:37 -0700 Subject: [PATCH 03/31] MGIC linter fixes and minor refactors --- .../clashroyale/match_group_input_custom.lua | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index 4f65c875149..595a6d95f95 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -37,6 +37,9 @@ local TBD = 'tbd' local TBA = 'tba' local MAX_NUM_MAPS = 30 +-- containers for process helper functions +local matchFunctions = {} + local CustomMatchGroupInput = {} CustomMatchGroupInput.walkoverProcessing = {} @@ -47,11 +50,10 @@ local walkoverProcessing = CustomMatchGroupInput.walkoverProcessing ---@return table function CustomMatchGroupInput.processMatch(match) Table.mergeInto(match, MatchGroupInput.readDate(match.date)) - match = CustomMatchGroupInput._getExtraData(match) + match = matchFunctions.getExtraData(match) match = CustomMatchGroupInput._getTournamentVars(match) match = CustomMatchGroupInput._adjustData(match) - match = CustomMatchGroupInput._getVodStuff(match) - match = CustomMatchGroupInput._getLinks(match) + match = matchFunctions.getVodStuff(match) return match end @@ -201,21 +203,20 @@ function CustomMatchGroupInput._getTournamentVars(match) return MatchGroupInput.getCommonTournamentVars(match) end -function CustomMatchGroupInput._getVodStuff(match) +function matchFunctions.getVodStuff(match) match.stream = Streams.processStreams(match) - match.vod = Logic.emptyOr(match.vod) + match.vod = Logic.nilIfEmpty(match.vod) - return match -end - -function CustomMatchGroupInput._getLinks(match) match.links = { royaleapi = match.royaleapi and (ROYALE_API_PREFIX .. match.royaleapi) or nil, } + return match end -function CustomMatchGroupInput._getExtraData(match) +---@param match table +---@return table +function matchFunctions.getExtraData(match) match.extradata = { mvp = MatchGroupInput.readMvp(match), mvpteam = match.mvpteam or match.winner, @@ -227,7 +228,7 @@ function CustomMatchGroupInput._getExtraData(match) for subGroupIndex = 1, MAX_NUM_MAPS do local prefix = 'subgroup' .. subGroupIndex - match.extradata[prefix .. 'header'] = CustomMatchGroupInput._getSubGroupHeader(subGroupIndex, match) + match.extradata[prefix .. 'header'] = String.nilIfEmpty(match['subgroup' .. subGroupIndex .. 'header']) match.extradata[prefix .. 'iskoth'] = Logic.readBool(match[prefix .. 'iskoth']) or nil match.extradata[prefix .. 't1bans'] = CustomMatchGroupInput._readBans(match[prefix .. 't1bans']) match.extradata[prefix .. 't2bans'] = CustomMatchGroupInput._readBans(match[prefix .. 't2bans']) @@ -236,16 +237,10 @@ function CustomMatchGroupInput._getExtraData(match) return match end -function CustomMatchGroupInput._getSubGroupHeader(subGroupIndex, match) - local header = match['subgroup' .. subGroupIndex .. 'header'] - - return String.isNotEmpty(header) and header or nil -end - function CustomMatchGroupInput._readBans(bansInput) local bans = CustomMatchGroupInput._readCards(bansInput) - return Table.isNotEmpty(bans) and bans or nil + return Logic.nilIfEmpty(bans) end function CustomMatchGroupInput._adjustData(match) From 7d6172c37b654310641f2955981f55ef790f3869 Mon Sep 17 00:00:00 2001 From: Cameron <58013431+FenrirWulf@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:00:40 -0700 Subject: [PATCH 04/31] EOF empty line, since VSC doesnt seem to want to push it --- components/match2/wikis/clashroyale/match_group_input_custom.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index 595a6d95f95..28ced472d51 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -719,3 +719,4 @@ function CustomMatchGroupInput._readCards(input) end return CustomMatchGroupInput + From aac21e4593c1370381cf4206bf3198561020a6d3 Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Sun, 9 Jun 2024 16:17:45 -0700 Subject: [PATCH 05/31] FIxing typo --- .../clashroyale/match_group_input_custom.lua | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index 595a6d95f95..86522f15c44 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -20,7 +20,8 @@ local Variables = require('Module:Variables') local MatchGroupInput = Lua.import('Module:MatchGroup/Input') local Streams = Lua.import('Module:Links/Stream') -local Opponent = require('Module:OpponentLibraries').Opponent +local OpponentLibrary = require('Module:OpponentLibraries') +local Opponent = OpponentLibrary.Opponent local UNKNOWN_REASON_LOSS_STATUS = 'L' local DEFAULT_WIN_STATUS = 'W' @@ -30,7 +31,7 @@ local SCORE_STATUS = 'S' local ALLOWED_STATUSES = {DEFAULT_WIN_STATUS, 'FF', 'DQ', UNKNOWN_REASON_LOSS_STATUS} local MAX_NUM_OPPONENTS = 2 local DEFAULT_BEST_OF = 99 -local NOW = os.time(os.date('!*t')) --[[@as osdateparam]] +local NOW = os.time(os.date('!*t') --[[@as osdateparam]]) local ROYALE_API_PREFIX = 'https://royaleapi.com/' local MAX_NUM_PLAYERS_PER_MAP = 2 local TBD = 'tbd' @@ -58,29 +59,23 @@ function CustomMatchGroupInput.processMatch(match) return match end -function CustomMatchGroupInput.getIcon(template) - local raw = mw.ext.TeamTemplate.raw(template) - if raw then - local icon = Logic.emptyOr(raw.image, raw.legacyimage) - local iconDark = Logic.emptyOr(raw.imagedark, raw.legacyimagedark) - return icon, iconDark - end -end - +---@param record table +---@param timestamp integer +---@return table function CustomMatchGroupInput.processOpponent(record, timestamp) local opponent = Opponent.readOpponentArgs(record) or Opponent.blank() ---@type number|string local teamTemplateDate = timestamp - -- If date is epoch, resolve using tournament dates instead - -- Epoch indicates that the match is missing a date - -- In order to get correct child team template, we will use an approximately date and not default date + -- If date is default date, resolve using tournament dates instead + -- default date indicates that the match is missing a date + -- In order to get correct child team template, we will use an approximately date and not the default date if teamTemplateDate == DateExt.defaultTimestamp then teamTemplateDate = Variables.varDefaultMulti('tournament_enddate', 'tournament_startdate', NOW) end - Opponent.resolve(opponent, teamTemplatedate)---, {syncPlayer=true}) + Opponent.resolve(opponent, teamTemplateDate, {syncPlayer=true}) MatchGroupInput.mergeRecordWithOpponent(record, opponent) @@ -100,6 +95,15 @@ function CustomMatchGroupInput.processOpponent(record, timestamp) return record end +function CustomMatchGroupInput.getIcon(template) + local raw = mw.ext.TeamTemplate.raw(template) + if raw then + local icon = Logic.emptyOr(raw.image, raw.legacyimage) + local iconDark = Logic.emptyOr(raw.imagedark, raw.legacyimagedark) + return icon, iconDark + end +end + function walkoverProcessing.walkover(obj, scores) local walkover = obj.walkover @@ -219,7 +223,6 @@ end function matchFunctions.getExtraData(match) match.extradata = { mvp = MatchGroupInput.readMvp(match), - mvpteam = match.mvpteam or match.winner, casters = match.casters, t1bans = CustomMatchGroupInput._readBans(match.t1bans), t2bans = CustomMatchGroupInput._readBans(match.t2bans), From 490bc98502a3553bc11e100c4c606ad7c50b0c3d Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Mon, 10 Jun 2024 17:33:45 -0700 Subject: [PATCH 06/31] Code Cleanup --- .../clashroyale/match_group_input_custom.lua | 91 ++++--------------- 1 file changed, 16 insertions(+), 75 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index 4333e41dfe7..77b70319c1a 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -40,12 +40,10 @@ local MAX_NUM_MAPS = 30 -- containers for process helper functions local matchFunctions = {} +local walkoverProcessing = {} local CustomMatchGroupInput = {} -CustomMatchGroupInput.walkoverProcessing = {} -local walkoverProcessing = CustomMatchGroupInput.walkoverProcessing - -- called from Module:MatchGroup ---@param match table ---@return table @@ -72,10 +70,10 @@ function CustomMatchGroupInput.processOpponent(record, timestamp) -- default date indicates that the match is missing a date -- In order to get correct child team template, we will use an approximately date and not the default date if teamTemplateDate == DateExt.defaultTimestamp then - teamTemplateDate = Variables.varDefaultMulti('tournament_enddate', 'tournament_startdate', NOW) + teamTemplateDate = DateExt.getContextualDateOrNow() end - Opponent.resolve(opponent, teamTemplateDate, {syncPlayer=true}) + Opponent.resolve(opponent, teamTemplateDate, {syncPlayer = true}) MatchGroupInput.mergeRecordWithOpponent(record, opponent) @@ -89,7 +87,7 @@ function CustomMatchGroupInput.processOpponent(record, timestamp) if record.type == Opponent.team then record.icon, record.icondark = CustomMatchGroupInput.getIcon(opponent.template) - record.match2players = CustomMatchGroupInput._readTeamPlayers(record, record.players) + record.match2players = MatchGroupInput.readPlayersOfTeam(record, record.players, opponent.template) end return record @@ -111,7 +109,7 @@ function walkoverProcessing.walkover(obj, scores) walkoverProcessing.numericWalkover(obj, walkover) elseif walkover then walkoverProcessing.nonNumericWalkover(obj, walkover) - elseif #scores ~=2 then -- since we always have 2 opponents + elseif #scores ~= 2 then -- since we always have 2 opponents error('Unexpected number of opponents when calculating winner') elseif Array.all(scores, function(score) return Table.includes(ALLOWED_STATUSES, score) and score ~= DEFAULT_WIN_STATUS @@ -301,20 +299,19 @@ function CustomMatchGroupInput._matchWinnerProcessing(match) match.winner = 0 end - for opponentIndex = 1, MAX_NUM_OPPONENTS do + Array.forEach(Array.range(1, MAX_NUM_OPPONENTS), function(opponentIndex) local opponent = match['opponent' .. opponentIndex] - if Logic.isNotEmpty(opponent) then - opponent.status = SCORE_STATUS - if opponent.score > bestof / 2 then - match.finished = Logic.emptyOr(match.finished, true) - match.winner = tonumber(match.winner) or opponentIndex - elseif match.winner == 0 or (opponent.score == bestof / 2 and match.opponent1.score == match.opponent2.score) then - match.finished = Logic.emptyOr(match.finished, true) - match.winner = 0 - match.resulttype = 'draw' - end + if Logic.isEmpty(opponent) then return end + opponent.status = SCORE_STATUS + if opponent.score > bestof / 2 then + match.finished = Logic.emptyOr(match.finished, true) + match.winner = tonumber(match.winner) or opponentIndex + elseif match.winner == 0 or (opponent.score == bestof / 2 and match.opponent1.score == match.opponent2.score) then + match.finished = Logic.emptyOr(Logic.readBoolOrNil(match.finished), true) + match.winner = 0 + match.resulttype = 'draw' end - end + end) match.winner = tonumber(match.winner) @@ -456,62 +453,6 @@ function CustomMatchGroupInput._opponentInput(match) return match end -function CustomMatchGroupInput._readTeamPlayers(opponent, playerData) - local players = CustomMatchGroupInput._getManuallyEnteredPlayers(playerData) - - if Table.isEmpty(players) then - players = CustomMatchGroupInput._getPlayersFromVariables(opponent.name) - end - - return players -end - -function CustomMatchGroupInput._getManuallyEnteredPlayers(playerData) - local players = {} - playerData = Json.parseIfString(playerData) or {} - - for prefix, displayName in Table.iter.pairsByPrefix(playerData, 'p') do - local name = mw.ext.TeamLiquidIntegration.resolve_redirect(Logic.emptyOr( - playerData[prefix .. 'link'], - displayName - )):gsub(' ', '_') - - table.insert(players, { - name = name, - displayname = displayName, - flag = Flags.CountryName(playerData[prefix .. 'flag']), - }) - end - - return players -end - -function CustomMatchGroupInput._getPlayersFromVariables(teamName) - teamName = teamName:gsub(' ', '_') - local teamNameWithSpaces = teamName:gsub('_', ' ') - local players = {} - - local playerIndex = 1 - while true do - local prefix = teamName .. '_p' .. playerIndex - local prefixWithSpaces = teamNameWithSpaces .. '_p' .. playerIndex - - local playerName = Variables.varDefault(prefix) or Variables.varDefault(prefixWithSpaces) - if String.isEmpty(playerName) then - break - end - table.insert(players, { - name = playerName:gsub(' ', '_'), - displayname = Variables.varDefault(prefix .. 'dn', - Variables.varDefault(prefixWithSpaces .. 'dn', playerName:gsub('_', ' '))), - flag = Flags.CountryName(Variables.varDefault(prefix .. 'flag', Variables.varDefault(prefixWithSpaces .. 'flag'))), - }) - playerIndex = playerIndex + 1 - end - - return players -end - --[[ MapInput functions From 1668045cfdda3a56f98133b200c8f857554023c4 Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Fri, 14 Jun 2024 11:33:39 -0700 Subject: [PATCH 07/31] Using commons version --- .../match2/wikis/clashroyale/match_group_input_custom.lua | 8 -------- 1 file changed, 8 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index 77b70319c1a..92ef0b28301 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -8,7 +8,6 @@ local Array = require('Module:Array') local DateExt = require('Module:Date/Ext') -local Flags = require('Module:Flags') local Json = require('Module:Json') local Logic = require('Module:Logic') local Lua = require('Module:Lua') @@ -475,13 +474,6 @@ function CustomMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) header = map.header, } - -- inherit stuff from match data - map.type = Logic.emptyOr(map.type, match.type) - map.liquipediatier = match.liquipediatier - map.liquipediatiertype = match.liquipediatiertype - map.game = Logic.emptyOr(map.game, match.game) - map.date = Logic.emptyOr(map.date, match.date) - -- determine score, resulttype, walkover and winner map = CustomMatchGroupInput._mapWinnerProcessing(map) From 3b87147e436e19f043454c97f86ca15d30eae8e8 Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Fri, 14 Jun 2024 11:47:41 -0700 Subject: [PATCH 08/31] Adding annotations --- .../clashroyale/match_group_input_custom.lua | 68 ++++++++++++++++--- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index 92ef0b28301..b4419348804 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -92,6 +92,9 @@ function CustomMatchGroupInput.processOpponent(record, timestamp) return record end +---@param template string +---@return string|nil icon +---@return string|nil iconDark function CustomMatchGroupInput.getIcon(template) local raw = mw.ext.TeamTemplate.raw(template) if raw then @@ -101,6 +104,8 @@ function CustomMatchGroupInput.getIcon(template) end end +---@param obj table +---@param scores table function walkoverProcessing.walkover(obj, scores) local walkover = obj.walkover @@ -120,6 +125,8 @@ function walkoverProcessing.walkover(obj, scores) end end +---@param obj table +---@param walkover number function walkoverProcessing.numericWalkover(obj, walkover) local winner = tonumber(walkover) @@ -129,6 +136,8 @@ function walkoverProcessing.numericWalkover(obj, walkover) obj.resulttype = DEFAULT_WIN_RESULTTYPE end +---@param obj table +---@param walkover string function walkoverProcessing.nonNumericWalkover(obj, walkover) if not Table.includes(ALLOWED_STATUSES, string.upper(walkover)) then error('Invalid walkover "' .. walkover .. '"') @@ -142,6 +151,8 @@ function walkoverProcessing.nonNumericWalkover(obj, walkover) obj.resulttype = DEFAULT_WIN_RESULTTYPE end +---@param obj table +---@param scores table function walkoverProcessing.scoreDoubleWalkover(obj, scores) obj.winner = -1 obj.finished = true @@ -149,6 +160,8 @@ function walkoverProcessing.scoreDoubleWalkover(obj, scores) obj.resulttype = DEFAULT_WIN_RESULTTYPE end +---@param obj table +---@param scores table function walkoverProcessing.scoreWalkover(obj, scores) local winner, status @@ -173,6 +186,7 @@ function walkoverProcessing.scoreWalkover(obj, scores) obj.resulttype = DEFAULT_WIN_RESULTTYPE end +---@param match table function walkoverProcessing.applyMatchWalkoverToOpponents(match) for opponentIndex = 1, MAX_NUM_OPPONENTS do local score = match['opponent' .. opponentIndex].score @@ -194,16 +208,22 @@ function walkoverProcessing.applyMatchWalkoverToOpponents(match) end end +---@param match table +---@return boolean True function CustomMatchGroupInput._hasTeamOpponent(match) return match.opponent1.type == Opponent.team or match.opponent2.type == Opponent.team end +---@param match table +---@return table function CustomMatchGroupInput._getTournamentVars(match) match.mode = Logic.emptyOr(match.mode, Variables.varDefault('tournament_mode', 'solo')) match.publishertier = Logic.emptyOr(match.publishertier, Variables.varDefault('tournament_publishertier')) return MatchGroupInput.getCommonTournamentVars(match) end +---@param match table +---@return table function matchFunctions.getVodStuff(match) match.stream = Streams.processStreams(match) match.vod = Logic.nilIfEmpty(match.vod) @@ -237,12 +257,16 @@ function matchFunctions.getExtraData(match) return match end +---@param bansInput string +---@return table function CustomMatchGroupInput._readBans(bansInput) local bans = CustomMatchGroupInput._readCards(bansInput) return Logic.nilIfEmpty(bans) end +---@param match table +---@return table function CustomMatchGroupInput._adjustData(match) --parse opponents + set base sumscores match = CustomMatchGroupInput._opponentInput(match) @@ -268,6 +292,8 @@ function CustomMatchGroupInput._adjustData(match) return match end +---@param match table +---@return table function CustomMatchGroupInput._matchWinnerProcessing(match) local bestof = tonumber(match.bestof) or Variables.varDefault('bestof', DEFAULT_BEST_OF) match.bestof = bestof @@ -323,6 +349,7 @@ function CustomMatchGroupInput._matchWinnerProcessing(match) return match end +---@param match table function CustomMatchGroupInput._checkFinished(match) if Logic.readBoolOrNil(match.finished) == false then match.finished = false @@ -340,6 +367,8 @@ function CustomMatchGroupInput._checkFinished(match) end end +---@param match table +---@param scores table function CustomMatchGroupInput._determineWinnerIfMissing(match, scores) local maxScore = math.max(unpack(scores) or 0) -- if we have a positive score and the match is finished we also have a winner @@ -358,6 +387,7 @@ function CustomMatchGroupInput._determineWinnerIfMissing(match, scores) end end +---@param match table function CustomMatchGroupInput._setPlacements(match) for opponentIndex = 1, MAX_NUM_OPPONENTS do local opponent = match['opponent' .. opponentIndex] @@ -370,6 +400,8 @@ function CustomMatchGroupInput._setPlacements(match) end end +---@param match table +---@return table function CustomMatchGroupInput._subMatchStructure(match) local subMatches = {} @@ -411,11 +443,8 @@ function CustomMatchGroupInput._subMatchStructure(match) return match end ---[[ - -OpponentInput functions - -]]-- +---@param match table +---@return table function CustomMatchGroupInput._opponentInput(match) local opponentIndex = 1 local opponent = match['opponent' .. opponentIndex] @@ -452,11 +481,10 @@ function CustomMatchGroupInput._opponentInput(match) return match end ---[[ - -MapInput functions - -]]-- +---@param match table +---@param mapIndex integer +---@param subGroupIndex integer +---@return table, integer function CustomMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) local map = Json.parseIfString(match['map' .. mapIndex]) or {} @@ -499,6 +527,8 @@ function CustomMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) return match, subGroupIndex end +---@param map table +---@return table function CustomMatchGroupInput._mapWinnerProcessing(map) if map.winner == 'skip' then map.scores = {NO_SCORE, NO_SCORE} @@ -539,6 +569,9 @@ function CustomMatchGroupInput._mapWinnerProcessing(map) return map end +---@param map table +---@param match table +---@return table function CustomMatchGroupInput._processPlayerMapData(map, match) local participants = {} @@ -568,6 +601,10 @@ function CustomMatchGroupInput._processPlayerMapData(map, match) return map end +---@param players table +---@param opponentIndex integer +---@param map table +---@param participants table function CustomMatchGroupInput._processDefaultPlayerMapData(players, opponentIndex, map, participants) for playerIndex = 1, #players do participants[opponentIndex .. '_' .. playerIndex] = { @@ -577,6 +614,11 @@ function CustomMatchGroupInput._processDefaultPlayerMapData(players, opponentInd end end +---@param players table +---@param opponentIndex integer +---@param map table +---@param participants table +---@return integer function CustomMatchGroupInput._processTeamPlayerMapData(players, opponentIndex, map, participants) local tbdIndex = 0 local appendIndex = #players + 1 @@ -621,6 +663,9 @@ function CustomMatchGroupInput._processTeamPlayerMapData(players, opponentIndex, return playerIndex - 1 end +---@param players table +---@param player string +---@return integer|nil function CustomMatchGroupInput._fetchMatch2PlayerIndexOfPlayer(players, player) local displayNameIndex local displayNameFoundTwice = false @@ -641,6 +686,8 @@ function CustomMatchGroupInput._fetchMatch2PlayerIndexOfPlayer(players, player) end end +---@param input string +---@return table function CustomMatchGroupInput._readCards(input) local cards = Json.parseIfString(input) or {} @@ -655,4 +702,3 @@ function CustomMatchGroupInput._readCards(input) end return CustomMatchGroupInput - From c9b6fe6d8cd782a8b086293a0d2e778455e83105 Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Fri, 14 Jun 2024 11:48:43 -0700 Subject: [PATCH 09/31] Trailing whitespace --- .../match2/wikis/clashroyale/match_group_input_custom.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index b4419348804..d6fbaa9edd1 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -481,9 +481,9 @@ function CustomMatchGroupInput._opponentInput(match) return match end ----@param match table ----@param mapIndex integer ----@param subGroupIndex integer +---@param match table +---@param mapIndex integer +---@param subGroupIndex integer ---@return table, integer function CustomMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) local map = Json.parseIfString(match['map' .. mapIndex]) or {} From 8842e6b6410d4083c37cb109c91d25552de8621d Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Sun, 30 Jun 2024 16:47:37 -0700 Subject: [PATCH 10/31] Converting to array --- .../match2/wikis/clashroyale/match_group_input_custom.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index d6fbaa9edd1..7cb4ddaa88a 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -76,9 +76,9 @@ function CustomMatchGroupInput.processOpponent(record, timestamp) MatchGroupInput.mergeRecordWithOpponent(record, opponent) - for _, player in pairs(record.players or {}) do + Array.forEach(record.players or {}, function (player) player.name = player.name:gsub(' ', '_') - end + end) if record.name then record.name = record.name:gsub(' ', '_') From 90a27a8226297b7beccc3da7108390f1d625367d Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Thu, 4 Jul 2024 10:11:53 -0700 Subject: [PATCH 11/31] Minor rewrites for Summary --- components/match2/wikis/clashroyale/match_summary.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_summary.lua b/components/match2/wikis/clashroyale/match_summary.lua index 21f777a1051..1cccebb7d49 100644 --- a/components/match2/wikis/clashroyale/match_summary.lua +++ b/components/match2/wikis/clashroyale/match_summary.lua @@ -87,11 +87,11 @@ function CustomMatchSummary.createBody(match) -- Add Match MVP(s) if match.extradata.mvp then local mvpData = match.extradata.mvp - if not Table.isEmpty(mvpData) and mvpData.players then + if Table.isNotEmpty(mvpData) and mvpData.players then local mvp = MatchSummary.Mvp() - for _, player in ipairs(mvpData.players) do + Array.forEach(mvpData.players, function(player) mvp:addPlayer(player) - end + end) mvp:setPoints(mvpData.points) body:addRow(mvp) end From 702199db9c1f43ec393407994323097deee6ae41 Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Thu, 4 Jul 2024 10:28:55 -0700 Subject: [PATCH 12/31] Batch changes --- .../clashroyale/match_group_input_custom.lua | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index 7cb4ddaa88a..a4ad5679d80 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -243,7 +243,7 @@ function matchFunctions.getExtraData(match) casters = match.casters, t1bans = CustomMatchGroupInput._readBans(match.t1bans), t2bans = CustomMatchGroupInput._readBans(match.t2bans), - } + } --[[@as table]] for subGroupIndex = 1, MAX_NUM_MAPS do local prefix = 'subgroup' .. subGroupIndex @@ -307,7 +307,7 @@ function CustomMatchGroupInput._matchWinnerProcessing(match) -- set the score either from manual input or sumscore opponent.score = Table.includes(ALLOWED_STATUSES, string.upper(opponent.score or '')) - and opponent.score:upper() + and string.upper(opponent.score) or tonumber(opponent.score) or tonumber(opponent.sumscore) or NO_SCORE return opponent.score @@ -493,6 +493,8 @@ function CustomMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) return match, subGroupIndex end + map = MatchGroupInput.getCommonTournamentVars(map, match) + -- CR has no map names, use generic one instead map.map = 'Set ' .. mapIndex @@ -509,10 +511,10 @@ function CustomMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) map = CustomMatchGroupInput._processPlayerMapData(map, match) -- set sumscore to 0 if it isn't a number - if String.isEmpty(match.opponent1.sumscore) then + if Logic.isEmpty(match.opponent1.sumscore) then match.opponent1.sumscore = 0 end - if String.isEmpty(match.opponent2.sumscore) then + if Logic.isEmpty(match.opponent2.sumscore) then match.opponent2.sumscore = 0 end @@ -689,16 +691,13 @@ end ---@param input string ---@return table function CustomMatchGroupInput._readCards(input) - local cards = Json.parseIfString(input) or {} - - for cardIndex, card in pairs(cards) do - if not CardNames[card:lower()] then - error('Invalid Card "' .. card .. '"') - end - cards[cardIndex] = CardNames[card] - end - return cards + return Array.map(Json.parseIfString(input) or {}, function(card) + if String.isEmpty(card) then return EMPTY_CARD end + local cleanedCard = CardNames[card] + assert(cleanedCard, 'Invalid Card "' .. card .. '"') + return cleanedCard + end) end return CustomMatchGroupInput From f250ec535ffda55def6aa60e194c70482b023182 Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Thu, 4 Jul 2024 10:53:51 -0700 Subject: [PATCH 13/31] Casters & Array stuff --- .../match2/wikis/clashroyale/match_group_input_custom.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index a4ad5679d80..1735d4904ea 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -211,7 +211,8 @@ end ---@param match table ---@return boolean True function CustomMatchGroupInput._hasTeamOpponent(match) - return match.opponent1.type == Opponent.team or match.opponent2.type == Opponent.team + return Array.any(match.opponent1.type, Opponent.team) or Array.any(match.opponent2.type, Opponent.team) + --return match.opponent1.type == Opponent.team or match.opponent2.type == Opponent.team end ---@param match table @@ -240,7 +241,6 @@ end function matchFunctions.getExtraData(match) match.extradata = { mvp = MatchGroupInput.readMvp(match), - casters = match.casters, t1bans = CustomMatchGroupInput._readBans(match.t1bans), t2bans = CustomMatchGroupInput._readBans(match.t2bans), } --[[@as table]] From 05bb294d94bfb4e5c4df95a47378133575162b77 Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Sat, 13 Jul 2024 12:45:41 -0700 Subject: [PATCH 14/31] Removing MVPs for new PR --- .../wikis/clashroyale/match_group_input_custom.lua | 1 - .../match2/wikis/clashroyale/match_summary.lua | 13 ------------- 2 files changed, 14 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index 1735d4904ea..d530b5521f9 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -240,7 +240,6 @@ end ---@return table function matchFunctions.getExtraData(match) match.extradata = { - mvp = MatchGroupInput.readMvp(match), t1bans = CustomMatchGroupInput._readBans(match.t1bans), t2bans = CustomMatchGroupInput._readBans(match.t2bans), } --[[@as table]] diff --git a/components/match2/wikis/clashroyale/match_summary.lua b/components/match2/wikis/clashroyale/match_summary.lua index 1cccebb7d49..2ad27b964d3 100644 --- a/components/match2/wikis/clashroyale/match_summary.lua +++ b/components/match2/wikis/clashroyale/match_summary.lua @@ -84,19 +84,6 @@ function CustomMatchSummary.createBody(match) body:addRow(CustomMatchSummary._banRow(extradata.t1bans, extradata.t2bans, match.date)) end - -- Add Match MVP(s) - if match.extradata.mvp then - local mvpData = match.extradata.mvp - if Table.isNotEmpty(mvpData) and mvpData.players then - local mvp = MatchSummary.Mvp() - Array.forEach(mvpData.players, function(player) - mvp:addPlayer(player) - end) - mvp:setPoints(mvpData.points) - body:addRow(mvp) - end - end - return body end From 2fe15381cd4367f88c9b5206764518a5212bfbe3 Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Tue, 16 Jul 2024 13:17:33 -0700 Subject: [PATCH 15/31] Adjusting for invalid card input --- .../match2/wikis/clashroyale/match_group_input_custom.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index d530b5521f9..026b0a55a0d 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -692,7 +692,7 @@ end function CustomMatchGroupInput._readCards(input) return Array.map(Json.parseIfString(input) or {}, function(card) - if String.isEmpty(card) then return EMPTY_CARD end + if String.isEmpty(card) then return '' end local cleanedCard = CardNames[card] assert(cleanedCard, 'Invalid Card "' .. card .. '"') return cleanedCard From 36c6fff06a7ec5f329e1a44d407c4ec2f29bbb6d Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Wed, 17 Jul 2024 14:23:26 -0700 Subject: [PATCH 16/31] Code cleanup --- .../clashroyale/match_group_input_custom.lua | 73 ++++++------------- 1 file changed, 24 insertions(+), 49 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index 026b0a55a0d..a950264a998 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -452,21 +452,13 @@ function CustomMatchGroupInput._opponentInput(match) opponent = Json.parseIfString(opponent) or Opponent.blank() -- Convert byes to literals - if - string.lower(opponent.template or '') == 'bye' - or string.lower(opponent.name or '') == 'bye' - then + if Opponent.isBye(opponent) then opponent = {type = Opponent.literal, name = 'BYE'} end --process input - if opponent.type == Opponent.team or opponent.type == Opponent.solo or - opponent.type == Opponent.duo or opponent.type == Opponent.literal then - - opponent = CustomMatchGroupInput.processOpponent(opponent, match.timestamp) - else - error('Unsupported Opponent Type') - end + assert(Opponent.isType(opponent.type), 'Unsupported Opponent Type') + opponent = CustomMatchGroupInput.processOpponent(opponent, match.timestamp) --set initial opponent sumscore opponent.sumscore = 0 @@ -510,10 +502,10 @@ function CustomMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) map = CustomMatchGroupInput._processPlayerMapData(map, match) -- set sumscore to 0 if it isn't a number - if Logic.isEmpty(match.opponent1.sumscore) then + if Logic.emptyOr(match.opponent1.sumscore) then match.opponent1.sumscore = 0 end - if Logic.isEmpty(match.opponent2.sumscore) then + if Logic.emptyOr(match.opponent2.sumscore) then match.opponent2.sumscore = 0 end @@ -607,12 +599,12 @@ end ---@param map table ---@param participants table function CustomMatchGroupInput._processDefaultPlayerMapData(players, opponentIndex, map, participants) - for playerIndex = 1, #players do + Array.forEach(Array.range(1, #players), function(playerIndex) participants[opponentIndex .. '_' .. playerIndex] = { played = true, cards = CustomMatchGroupInput._readCards(map['t' .. opponentIndex .. 'p' .. playerIndex .. 'c']), } - end + end) end ---@param players table @@ -621,47 +613,33 @@ end ---@param participants table ---@return integer function CustomMatchGroupInput._processTeamPlayerMapData(players, opponentIndex, map, participants) - local tbdIndex = 0 - local appendIndex = #players + 1 - - local playerIndex = 1 - local playerKey = 't' .. opponentIndex .. 'p' .. playerIndex - while playerIndex <= MAX_NUM_PLAYERS_PER_MAP and (String.isNotEmpty(map[playerKey]) or - String.isNotEmpty(map[playerKey .. 'link']) or String.isNotEmpty(map[playerKey .. 'c'])) do - - local player = map[playerKey .. 'link'] or map[playerKey] - if String.isEmpty(player) or Table.includes({TBD, TBA}, player:lower()) then - tbdIndex = tbdIndex + 1 - player = TBD .. tbdIndex - else - -- allows fetching the link of the player from preset wiki vars - player = mw.ext.TeamLiquidIntegration.resolve_redirect( - map[playerKey .. 'link'] or Variables.varDefault(map[playerKey] .. '_page') or map[playerKey] - ) - end + local numberOfPlayersInMap = 0 + for prefix, player, index in Table.iter.pairsByPrefix(map, 't' .. opponentIndex .. 'p') do + numberOfPlayersInMap = numberOfPlayersInMap + 1 + local link = mw.ext.TeamLiquidIntegration.resolve_redirect( + map[prefix .. 'link'] or Variables.varDefault(player .. '_page') or player + ) local playerData = { played = true, - cards = CustomMatchGroupInput._readCards(map[playerKey .. 'c']), + cards = CustomMatchGroupInput._readCards(map[prefix .. 'c']), } - local match2playerIndex = CustomMatchGroupInput._fetchMatch2PlayerIndexOfPlayer(players, player) + local match2playerIndex = CustomMatchGroupInput._fetchMatch2PlayerIndexOfPlayer(players, link) - -- if we have the player not present in match2player add basic data here if not match2playerIndex then - match2playerIndex = appendIndex - playerData = Table.merge(playerData, {name = player:gsub(' ', '_'), displayname = map[playerKey] or player}) - - appendIndex = appendIndex + 1 + table.insert(players, { + --check the format in the existing players field please and adjust accordingly + name = link:gsub(' ', '_'), + displayname = player, + }) + match2playerIndex = #players end participants[opponentIndex .. '_' .. match2playerIndex] = playerData - - playerIndex = playerIndex + 1 - playerKey = 't' .. opponentIndex .. 'p' .. playerIndex end - return playerIndex - 1 + return numberOfPlayersInMap end ---@param players table @@ -690,12 +668,9 @@ end ---@param input string ---@return table function CustomMatchGroupInput._readCards(input) - return Array.map(Json.parseIfString(input) or {}, function(card) - if String.isEmpty(card) then return '' end - local cleanedCard = CardNames[card] - assert(cleanedCard, 'Invalid Card "' .. card .. '"') - return cleanedCard + if String.isEmpty(card) then return nil end + return (assert(CardNames[card], 'Invalid Card "' .. card .. '"')) end) end From ef2b8aa391300a7f98bf0dc6ab3beb99879a6dbf Mon Sep 17 00:00:00 2001 From: FenrirWulf Date: Sun, 21 Jul 2024 21:41:33 -0700 Subject: [PATCH 17/31] minor code cleanup --- .../match2/wikis/clashroyale/match_group_input_custom.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index a950264a998..13420bffa3d 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -32,9 +32,6 @@ local MAX_NUM_OPPONENTS = 2 local DEFAULT_BEST_OF = 99 local NOW = os.time(os.date('!*t') --[[@as osdateparam]]) local ROYALE_API_PREFIX = 'https://royaleapi.com/' -local MAX_NUM_PLAYERS_PER_MAP = 2 -local TBD = 'tbd' -local TBA = 'tba' local MAX_NUM_MAPS = 30 -- containers for process helper functions @@ -614,7 +611,7 @@ end ---@return integer function CustomMatchGroupInput._processTeamPlayerMapData(players, opponentIndex, map, participants) local numberOfPlayersInMap = 0 - for prefix, player, index in Table.iter.pairsByPrefix(map, 't' .. opponentIndex .. 'p') do + for prefix, player in Table.iter.pairsByPrefix(map, 't' .. opponentIndex .. 'p') do numberOfPlayersInMap = numberOfPlayersInMap + 1 local link = mw.ext.TeamLiquidIntegration.resolve_redirect( map[prefix .. 'link'] or Variables.varDefault(player .. '_page') or player From 0354b69bdfebe23da9f217b61c969c0261ed8dbd Mon Sep 17 00:00:00 2001 From: hjpalpha <75081997+hjpalpha@users.noreply.github.com> Date: Thu, 1 Aug 2024 18:34:33 +0200 Subject: [PATCH 18/31] some cleanup --- .../clashroyale/match_group_input_custom.lua | 156 ++++++++---------- 1 file changed, 72 insertions(+), 84 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index 13420bffa3d..1e6916f94fd 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -8,9 +8,11 @@ local Array = require('Module:Array') local DateExt = require('Module:Date/Ext') +local FnUtil = require('Module:FnUtil') local Json = require('Module:Json') local Logic = require('Module:Logic') local Lua = require('Module:Lua') +local Page = require('Module:Page') local String = require('Module:StringUtils') local Table = require('Module:Table') local CardNames = mw.loadData('Module:CardNames') @@ -53,42 +55,6 @@ function CustomMatchGroupInput.processMatch(match) return match end ----@param record table ----@param timestamp integer ----@return table -function CustomMatchGroupInput.processOpponent(record, timestamp) - local opponent = Opponent.readOpponentArgs(record) - or Opponent.blank() - - ---@type number|string - local teamTemplateDate = timestamp - -- If date is default date, resolve using tournament dates instead - -- default date indicates that the match is missing a date - -- In order to get correct child team template, we will use an approximately date and not the default date - if teamTemplateDate == DateExt.defaultTimestamp then - teamTemplateDate = DateExt.getContextualDateOrNow() - end - - Opponent.resolve(opponent, teamTemplateDate, {syncPlayer = true}) - - MatchGroupInput.mergeRecordWithOpponent(record, opponent) - - Array.forEach(record.players or {}, function (player) - player.name = player.name:gsub(' ', '_') - end) - - if record.name then - record.name = record.name:gsub(' ', '_') - end - - if record.type == Opponent.team then - record.icon, record.icondark = CustomMatchGroupInput.getIcon(opponent.template) - record.match2players = MatchGroupInput.readPlayersOfTeam(record, record.players, opponent.template) - end - - return record -end - ---@param template string ---@return string|nil icon ---@return string|nil iconDark @@ -208,8 +174,7 @@ end ---@param match table ---@return boolean True function CustomMatchGroupInput._hasTeamOpponent(match) - return Array.any(match.opponent1.type, Opponent.team) or Array.any(match.opponent2.type, Opponent.team) - --return match.opponent1.type == Opponent.team or match.opponent2.type == Opponent.team + return match.opponent1.type == Opponent.team or match.opponent2.type == Opponent.team end ---@param match table @@ -239,6 +204,7 @@ function matchFunctions.getExtraData(match) match.extradata = { t1bans = CustomMatchGroupInput._readBans(match.t1bans), t2bans = CustomMatchGroupInput._readBans(match.t2bans), + mvp = MatchGroupInput.readMvp(match), } --[[@as table]] for subGroupIndex = 1, MAX_NUM_MAPS do @@ -368,17 +334,17 @@ end function CustomMatchGroupInput._determineWinnerIfMissing(match, scores) local maxScore = math.max(unpack(scores) or 0) -- if we have a positive score and the match is finished we also have a winner - if maxScore > 0 then - if Array.all(scores, function(score) return score == maxScore end) then - match.winner = 0 - return - end + if maxScore <= 0 then return end + + if Array.all(scores, function(score) return score == maxScore end) then + match.winner = 0 + return + end - for opponentIndex, score in pairs(scores) do - if score == maxScore then - match.winner = opponentIndex - return - end + for opponentIndex, score in pairs(scores) do + if score == maxScore then + match.winner = opponentIndex + return end end end @@ -421,18 +387,20 @@ function CustomMatchGroupInput._subMatchStructure(match) end end - for subMatchIndex, subMatch in ipairs(subMatches) do + Array.forEach(subMatches, function(subMatch, subMatchIndex) -- get winner if the submatch is finished -- submatch is finished if the next submatch has a score or if the complete match is finished - local nextSubMatch = subMatches[subMatchIndex + 1] - if Logic.readBool(match.finished) or (nextSubMatch and nextSubMatch.scores[1] + nextSubMatch.scores[2] > 0) then - if subMatch.scores[1] > subMatch.scores[2] then - subMatch.winner = 1 - elseif subMatch.scores[2] > subMatch.scores[1] then - subMatch.winner = 2 - end + local nextSubMatch = subMatches[subMatchIndex + 1] or {scores = {0, 0}} + + if not Logic.readBool(match.finished) and (nextSubMatch.scores[1] + nextSubMatch.scores[2] <= 0) then + return end - end + if subMatch.scores[1] > subMatch.scores[2] then + subMatch.winner = 1 + elseif subMatch.scores[2] > subMatch.scores[1] then + subMatch.winner = 2 + end + end) match.extradata.submatches = subMatches @@ -442,31 +410,58 @@ end ---@param match table ---@return table function CustomMatchGroupInput._opponentInput(match) - local opponentIndex = 1 - local opponent = match['opponent' .. opponentIndex] + Array.forEach(Array.range(1, MAX_NUM_OPPONENTS), FnUtil.curry(CustomMatchGroupInput.processOpponent, match)) - while opponentIndex <= MAX_NUM_OPPONENTS and Logic.isNotEmpty(opponent) do - opponent = Json.parseIfString(opponent) or Opponent.blank() + return match +end - -- Convert byes to literals - if Opponent.isBye(opponent) then - opponent = {type = Opponent.literal, name = 'BYE'} - end +---@param match table +---@param opponentIndex integer +function CustomMatchGroupInput.processOpponent(match, opponentIndex) + local record = Json.parseIfString(match['opponent' .. opponentIndex]) + assert(Opponent.isType(record.type), 'Unsupported Opponent Type') - --process input - assert(Opponent.isType(opponent.type), 'Unsupported Opponent Type') - opponent = CustomMatchGroupInput.processOpponent(opponent, match.timestamp) + local opponent = Opponent.readOpponentArgs(record) or Opponent.blank() - --set initial opponent sumscore - opponent.sumscore = 0 + -- Convert byes to literals + if Opponent.isBye(opponent) then + opponent = {type = Opponent.literal, name = 'BYE'} + end - match['opponent' .. opponentIndex] = opponent + ---@type number|string + local teamTemplateDate = match.timestamp + -- If date is default date, resolve using tournament dates instead + -- default date indicates that the match is missing a date + -- In order to get correct child team template, we will use an approximately date and not the default date + if teamTemplateDate == DateExt.defaultTimestamp then + teamTemplateDate = DateExt.getContextualDateOrNow() + end - opponentIndex = opponentIndex + 1 - opponent = match['opponent' .. opponentIndex] + Opponent.resolve(opponent, teamTemplateDate, {syncPlayer = true}) + + MatchGroupInput.mergeRecordWithOpponent(record, opponent) + + --set initial opponent sumscore + record.sumscore = 0 + + record.name = Page.pageifyLink(record.name) + + if record.type == Opponent.team then + record.icon, record.icondark = CustomMatchGroupInput.getIcon(opponent.template) end - return match + match['opponent' .. opponentIndex] = record + + if record.type == Opponent.team then + MatchGroupInput.readPlayersOfTeam(match, opponentIndex, record.name, { + resolveRedirect = true, + applyUnderScores = true, + }) + else + Array.forEach(record.players or {}, function(player) + player.name = Page.pageifyLink(player.name) + end) + end end ---@param match table @@ -499,12 +494,8 @@ function CustomMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) map = CustomMatchGroupInput._processPlayerMapData(map, match) -- set sumscore to 0 if it isn't a number - if Logic.emptyOr(match.opponent1.sumscore) then - match.opponent1.sumscore = 0 - end - if Logic.emptyOr(match.opponent2.sumscore) then - match.opponent2.sumscore = 0 - end + match.opponent1.sumscore = Logic.emptyOr(match.opponent1.sumscore, 0) + match.opponent2.sumscore = Logic.emptyOr(match.opponent2.sumscore, 0) --adjust sumscore for winner opponent if (tonumber(map.winner) or 0) > 0 then @@ -665,10 +656,7 @@ end ---@param input string ---@return table function CustomMatchGroupInput._readCards(input) - return Array.map(Json.parseIfString(input) or {}, function(card) - if String.isEmpty(card) then return nil end - return (assert(CardNames[card], 'Invalid Card "' .. card .. '"')) - end) + return Array.map(Json.parseIfString(input) or {}, FnUtil.curry(MatchGroupInput.getCharacterName, CardNames)) end return CustomMatchGroupInput From f438a4be043f6908a9b12835bdd6450b31d2ddc6 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Fri, 2 Aug 2024 13:38:14 +0200 Subject: [PATCH 19/31] kick trailing white space --- .../match2/wikis/clashroyale/match_group_input_custom.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index 1e6916f94fd..baffcec206b 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -391,7 +391,7 @@ function CustomMatchGroupInput._subMatchStructure(match) -- get winner if the submatch is finished -- submatch is finished if the next submatch has a score or if the complete match is finished local nextSubMatch = subMatches[subMatchIndex + 1] or {scores = {0, 0}} - + if not Logic.readBool(match.finished) and (nextSubMatch.scores[1] + nextSubMatch.scores[2] <= 0) then return end From a2731c7ce256fa673fd6411bf555d78621012a01 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Fri, 2 Aug 2024 13:39:30 +0200 Subject: [PATCH 20/31] only allow walkovers via score inputs --- .../clashroyale/match_group_input_custom.lua | 34 +------------------ 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index baffcec206b..c89cc397760 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -70,13 +70,7 @@ end ---@param obj table ---@param scores table function walkoverProcessing.walkover(obj, scores) - local walkover = obj.walkover - - if Logic.isNumeric(walkover) then - walkoverProcessing.numericWalkover(obj, walkover) - elseif walkover then - walkoverProcessing.nonNumericWalkover(obj, walkover) - elseif #scores ~= 2 then -- since we always have 2 opponents + if #scores ~= 2 then -- since we always have 2 opponents error('Unexpected number of opponents when calculating winner') elseif Array.all(scores, function(score) return Table.includes(ALLOWED_STATUSES, score) and score ~= DEFAULT_WIN_STATUS @@ -88,32 +82,6 @@ function walkoverProcessing.walkover(obj, scores) end end ----@param obj table ----@param walkover number -function walkoverProcessing.numericWalkover(obj, walkover) - local winner = tonumber(walkover) - - obj.winner = winner - obj.finished = true - obj.walkover = UNKNOWN_REASON_LOSS_STATUS - obj.resulttype = DEFAULT_WIN_RESULTTYPE -end - ----@param obj table ----@param walkover string -function walkoverProcessing.nonNumericWalkover(obj, walkover) - if not Table.includes(ALLOWED_STATUSES, string.upper(walkover)) then - error('Invalid walkover "' .. walkover .. '"') - elseif not Logic.isNumeric(obj.winner) then - error('Walkover without winner specified') - end - - obj.winner = tonumber(obj.winner) - obj.finished = true - obj.walkover = walkover:upper() - obj.resulttype = DEFAULT_WIN_RESULTTYPE -end - ---@param obj table ---@param scores table function walkoverProcessing.scoreDoubleWalkover(obj, scores) From cb464a0221bd4dfad38a9c6aca94a79989ced2b7 Mon Sep 17 00:00:00 2001 From: hjpalpha <75081997+hjpalpha@users.noreply.github.com> Date: Sat, 3 Aug 2024 07:34:42 +0200 Subject: [PATCH 21/31] adjust for stupid bs tower cards --- .../match2/wikis/clashroyale/match_group_input_custom.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index c89cc397760..a161912da85 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -624,7 +624,11 @@ end ---@param input string ---@return table function CustomMatchGroupInput._readCards(input) - return Array.map(Json.parseIfString(input) or {}, FnUtil.curry(MatchGroupInput.getCharacterName, CardNames)) + local cleanCard = FnUtil.curry(MatchGroupInput.getCharacterName, CardNames) + + return Table.map(Json.parseIfString(input) or {}, function(key, card) + return key, cleanCard(card) + end) end return CustomMatchGroupInput From 847f767552cf481954d16b2ce1dc6d1b08327ed9 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Mon, 5 Aug 2024 16:03:38 +0200 Subject: [PATCH 22/31] fix: do the pagify after retrieving players --- .../match2/wikis/clashroyale/match_group_input_custom.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index a161912da85..ab7605a3d75 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -412,8 +412,6 @@ function CustomMatchGroupInput.processOpponent(match, opponentIndex) --set initial opponent sumscore record.sumscore = 0 - record.name = Page.pageifyLink(record.name) - if record.type == Opponent.team then record.icon, record.icondark = CustomMatchGroupInput.getIcon(opponent.template) end @@ -430,6 +428,8 @@ function CustomMatchGroupInput.processOpponent(match, opponentIndex) player.name = Page.pageifyLink(player.name) end) end + + record.name = Page.pageifyLink(record.name) end ---@param match table From 94918d35630131dd07aa29d7c2d1e246cd6f12c8 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 3 Sep 2024 18:09:56 +0200 Subject: [PATCH 23/31] refactor mgi --- .../clashroyale/match_group_input_custom.lua | 759 ++++++------------ 1 file changed, 248 insertions(+), 511 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index ab7605a3d75..0fc1cd94c22 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -7,628 +7,365 @@ -- local Array = require('Module:Array') -local DateExt = require('Module:Date/Ext') +local CardNames = mw.loadData('Module:CardNames') +local Flags = require('Module:Flags') local FnUtil = require('Module:FnUtil') local Json = require('Module:Json') 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 CardNames = mw.loadData('Module:CardNames') local Variables = require('Module:Variables') -local MatchGroupInput = Lua.import('Module:MatchGroup/Input') +local MatchGroupInputUtil = Lua.import('Module:MatchGroup/Input/Util') +local OpponentLibraries = require('Module:OpponentLibraries') +local Opponent = OpponentLibraries.Opponent local Streams = Lua.import('Module:Links/Stream') -local OpponentLibrary = require('Module:OpponentLibraries') -local Opponent = OpponentLibrary.Opponent - -local UNKNOWN_REASON_LOSS_STATUS = 'L' -local DEFAULT_WIN_STATUS = 'W' -local DEFAULT_WIN_RESULTTYPE = 'default' -local NO_SCORE = -1 -local SCORE_STATUS = 'S' -local ALLOWED_STATUSES = {DEFAULT_WIN_STATUS, 'FF', 'DQ', UNKNOWN_REASON_LOSS_STATUS} -local MAX_NUM_OPPONENTS = 2 -local DEFAULT_BEST_OF = 99 -local NOW = os.time(os.date('!*t') --[[@as osdateparam]]) +local DEFAULT_MODE = 'solo' +local OPPONENT_CONFIG = { + resolveRedirect = true, + pagifyTeamNames = true, + pagifyPlayerNames = true, +} +local TBD = 'TBD' local ROYALE_API_PREFIX = 'https://royaleapi.com/' -local MAX_NUM_MAPS = 30 --- containers for process helper functions -local matchFunctions = {} -local walkoverProcessing = {} +---@class CRParticipant +---@field player string +---@field heroes string[]? +---@field position integer? +---@field flag string? +---@field random boolean? local CustomMatchGroupInput = {} +local MatchFunctions = {} +local MapFunctions = {} --- called from Module:MatchGroup ---@param match table +---@param options table? ---@return table -function CustomMatchGroupInput.processMatch(match) - Table.mergeInto(match, MatchGroupInput.readDate(match.date)) - match = matchFunctions.getExtraData(match) - match = CustomMatchGroupInput._getTournamentVars(match) - match = CustomMatchGroupInput._adjustData(match) - match = matchFunctions.getVodStuff(match) - - return match -end +function CustomMatchGroupInput.processMatch(match, options) + Table.mergeInto(match, MatchFunctions.readDate(match)) ----@param template string ----@return string|nil icon ----@return string|nil iconDark -function CustomMatchGroupInput.getIcon(template) - local raw = mw.ext.TeamTemplate.raw(template) - if raw then - local icon = Logic.emptyOr(raw.image, raw.legacyimage) - local iconDark = Logic.emptyOr(raw.imagedark, raw.legacyimagedark) - return icon, iconDark - end -end + local opponents = Array.mapIndexes(function(opponentIndex) + return MatchGroupInputUtil.readOpponent(match, opponentIndex, OPPONENT_CONFIG) + end) ----@param obj table ----@param scores table -function walkoverProcessing.walkover(obj, scores) - if #scores ~= 2 then -- since we always have 2 opponents - error('Unexpected number of opponents when calculating winner') - elseif Array.all(scores, function(score) - return Table.includes(ALLOWED_STATUSES, score) and score ~= DEFAULT_WIN_STATUS - end) then - - walkoverProcessing.scoreDoubleWalkover(obj, scores) - elseif Array.any(scores, function(score) return Table.includes(ALLOWED_STATUSES, score) end) then - walkoverProcessing.scoreWalkover(obj, scores) - end -end + local games = MatchFunctions.extractMaps(match, opponents) ----@param obj table ----@param scores table -function walkoverProcessing.scoreDoubleWalkover(obj, scores) - obj.winner = -1 - obj.finished = true - obj.walkover = scores[1] - obj.resulttype = DEFAULT_WIN_RESULTTYPE -end + local autoScoreFunction = MatchGroupInputUtil.canUseAutoScore(match, games) + and MatchFunctions.calculateMatchScore(games, opponents) + or nil ----@param obj table ----@param scores table -function walkoverProcessing.scoreWalkover(obj, scores) - local winner, status - - for scoreIndex, score in pairs(scores) do - score = string.upper(score) - if score == DEFAULT_WIN_STATUS then - winner = scoreIndex - elseif Table.includes(ALLOWED_STATUSES, score) then - status = score - else - status = UNKNOWN_REASON_LOSS_STATUS - end - end + Array.forEach(opponents, function(opponent, opponentIndex) + opponent.score, opponent.status = MatchGroupInputUtil.computeOpponentScore({ + walkover = match.walkover, + winner = match.winner, + opponentIndex = opponentIndex, + score = opponent.score, + }, autoScoreFunction) + end) - if not winner then - error('Invalid score combination "{' .. scores[1] .. ', ' .. scores[2] .. '}"') - end + match.bestof = MatchFunctions.getBestOf(match.bestof) - obj.winner = winner - obj.finished = true - obj.walkover = status - obj.resulttype = DEFAULT_WIN_RESULTTYPE -end + local winnerInput = match.winner --[[@as string?]] + local finishedInput = match.finished --[[@as string?]] + match.finished = MatchGroupInputUtil.matchIsFinished(match, opponents) ----@param match table -function walkoverProcessing.applyMatchWalkoverToOpponents(match) - for opponentIndex = 1, MAX_NUM_OPPONENTS do - local score = match['opponent' .. opponentIndex].score - - if Logic.isNumeric(score) or String.isEmpty(score) then - match['opponent' .. opponentIndex].score = String.isNotEmpty(score) and score or NO_SCORE - match['opponent' .. opponentIndex].status = match.walkover - elseif score and Table.includes(ALLOWED_STATUSES, score:upper()) then - match['opponent' .. opponentIndex].score = NO_SCORE - match['opponent' .. opponentIndex].status = score - else - error('Invalid score "' .. score .. '"') - end + if match.finished then + match.resulttype = MatchGroupInputUtil.getResultType(winnerInput, finishedInput, opponents) + match.walkover = MatchGroupInputUtil.getWalkover(match.resulttype, opponents) + match.winner = MatchGroupInputUtil.getWinner(match.resulttype, winnerInput, opponents) + MatchGroupInputUtil.setPlacement(opponents, match.winner, 1, 2) + elseif MatchGroupInputUtil.isNotPlayed(winnerInput, finishedInput) then + match.resulttype = MatchGroupInputUtil.getResultType(winnerInput, finishedInput, opponents) + match.winner = nil end - -- neither match.opponent0 nor match['opponent-1'] does exist hence the if - if match['opponent' .. match.winner] then - match['opponent' .. match.winner].status = DEFAULT_WIN_STATUS - end -end + MatchFunctions.getTournamentVars(match) ----@param match table ----@return boolean True -function CustomMatchGroupInput._hasTeamOpponent(match) - return match.opponent1.type == Opponent.team or match.opponent2.type == Opponent.team -end - ----@param match table ----@return table -function CustomMatchGroupInput._getTournamentVars(match) - match.mode = Logic.emptyOr(match.mode, Variables.varDefault('tournament_mode', 'solo')) - match.publishertier = Logic.emptyOr(match.publishertier, Variables.varDefault('tournament_publishertier')) - return MatchGroupInput.getCommonTournamentVars(match) -end - ----@param match table ----@return table -function matchFunctions.getVodStuff(match) match.stream = Streams.processStreams(match) + match.links = MatchFunctions.getLinks(match) match.vod = Logic.nilIfEmpty(match.vod) + match.extradata = MatchFunctions.getExtraData(match, #games) - match.links = { - royaleapi = match.royaleapi and (ROYALE_API_PREFIX .. match.royaleapi) or nil, - } + match.games = games + match.opponents = opponents return match end ---@param match table ---@return table -function matchFunctions.getExtraData(match) - match.extradata = { - t1bans = CustomMatchGroupInput._readBans(match.t1bans), - t2bans = CustomMatchGroupInput._readBans(match.t2bans), - mvp = MatchGroupInput.readMvp(match), - } --[[@as table]] - - for subGroupIndex = 1, MAX_NUM_MAPS do - local prefix = 'subgroup' .. subGroupIndex - - match.extradata[prefix .. 'header'] = String.nilIfEmpty(match['subgroup' .. subGroupIndex .. 'header']) - match.extradata[prefix .. 'iskoth'] = Logic.readBool(match[prefix .. 'iskoth']) or nil - match.extradata[prefix .. 't1bans'] = CustomMatchGroupInput._readBans(match[prefix .. 't1bans']) - match.extradata[prefix .. 't2bans'] = CustomMatchGroupInput._readBans(match[prefix .. 't2bans']) - end - - return match -end - ----@param bansInput string ----@return table -function CustomMatchGroupInput._readBans(bansInput) - local bans = CustomMatchGroupInput._readCards(bansInput) - - return Logic.nilIfEmpty(bans) +function MatchFunctions.getLinks(match) + return { + royaleapi = match.royaleapi and (ROYALE_API_PREFIX .. match.royaleapi) or nil, + } end ---@param match table ---@return table -function CustomMatchGroupInput._adjustData(match) - --parse opponents + set base sumscores - match = CustomMatchGroupInput._opponentInput(match) - - --main processing done here - local subGroupIndex = 0 - for _, _, mapIndex in Table.iter.pairsByPrefix(match, 'map') do - match, subGroupIndex = CustomMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) - end - - match = CustomMatchGroupInput._matchWinnerProcessing(match) - - CustomMatchGroupInput._setPlacements(match) - - if CustomMatchGroupInput._hasTeamOpponent(match) then - match = CustomMatchGroupInput._subMatchStructure(match) - end - - if Logic.isNumeric(match.winner) then - match.finished = true - end - - return match +function MatchFunctions.getTournamentVars(match) + match.mode = Logic.emptyOr(match.mode, Variables.varDefault('tournament_mode', DEFAULT_MODE)) + return MatchGroupInputUtil.getCommonTournamentVars(match) end ---@param match table ----@return table -function CustomMatchGroupInput._matchWinnerProcessing(match) - local bestof = tonumber(match.bestof) or Variables.varDefault('bestof', DEFAULT_BEST_OF) - match.bestof = bestof - Variables.varDefine('bestof', bestof) - - local scores = Array.map(Array.range(1, MAX_NUM_OPPONENTS), function(opponentIndex) - local opponent = match['opponent' .. opponentIndex] - if not opponent then - return NO_SCORE - end - - -- set the score either from manual input or sumscore - opponent.score = Table.includes(ALLOWED_STATUSES, string.upper(opponent.score or '')) - and string.upper(opponent.score) - or tonumber(opponent.score) or tonumber(opponent.sumscore) or NO_SCORE - - return opponent.score - end) - - walkoverProcessing.walkover(match, scores) - - if match.resulttype == DEFAULT_WIN_RESULTTYPE then - walkoverProcessing.applyMatchWalkoverToOpponents(match) - return match +---@param opponents table[] +---@return table[] +function MatchFunctions.extractMaps(match, opponents) + local maps = {} + local subGroup = 0 + for mapKey, mapInput, mapIndex in Table.iter.pairsByPrefix(match, 'map', {requireIndex = true}) do + local map + map, subGroup = MapFunctions.readMap(mapInput, mapIndex, subGroup, #opponents) + + map.participants = MapFunctions.getParticipants(mapInput, opponents) + map.extradata = MapFunctions.getExtraData(mapInput, map.participants) + + map.vod = Logic.emptyOr(mapInput.vod, match['vodgame' .. mapIndex]) + + table.insert(maps, map) + match[mapKey] = nil end - if match.winner == 'draw' then - match.winner = 0 - end - - Array.forEach(Array.range(1, MAX_NUM_OPPONENTS), function(opponentIndex) - local opponent = match['opponent' .. opponentIndex] - if Logic.isEmpty(opponent) then return end - opponent.status = SCORE_STATUS - if opponent.score > bestof / 2 then - match.finished = Logic.emptyOr(match.finished, true) - match.winner = tonumber(match.winner) or opponentIndex - elseif match.winner == 0 or (opponent.score == bestof / 2 and match.opponent1.score == match.opponent2.score) then - match.finished = Logic.emptyOr(Logic.readBoolOrNil(match.finished), true) - match.winner = 0 - match.resulttype = 'draw' - end - end) - - match.winner = tonumber(match.winner) - - CustomMatchGroupInput._checkFinished(match) - - if match.finished and not match.winner then - CustomMatchGroupInput._determineWinnerIfMissing(match, scores) - end - - return match + return maps end ----@param match table -function CustomMatchGroupInput._checkFinished(match) - if Logic.readBoolOrNil(match.finished) == false then - match.finished = false - elseif Logic.readBool(match.finished) or match.winner then - match.finished = true - end - - -- Match is automatically marked finished upon page edit after a - -- certain amount of time (depending on whether the date is exact) - if not match.finished and match.timestamp > DateExt.defaultTimestamp then - local threshold = match.dateexact and 30800 or 86400 - if match.timestamp + threshold < NOW then - match.finished = true - end +---@param maps table[] +---@param opponents table[] +---@return fun(opponentIndex: integer): integer? +function MatchFunctions.calculateMatchScore(maps, opponents) + return function(opponentIndex) + local calculatedScore = MatchGroupInputUtil.computeMatchScoreFromMapWinners(maps, opponentIndex) + if not calculatedScore then return end + local opponent = opponents[opponentIndex] + return calculatedScore + (opponent.extradata.advantage or 0) - (opponent.extradata.penalty or 0) end end ----@param match table ----@param scores table -function CustomMatchGroupInput._determineWinnerIfMissing(match, scores) - local maxScore = math.max(unpack(scores) or 0) - -- if we have a positive score and the match is finished we also have a winner - if maxScore <= 0 then return end - - if Array.all(scores, function(score) return score == maxScore end) then - match.winner = 0 - return - end +---@param bestofInput string|integer? +---@return integer? +function MatchFunctions.getBestOf(bestofInput) + local bestof = tonumber(bestofInput) or tonumber(Variables.varDefault('match_bestof')) - for opponentIndex, score in pairs(scores) do - if score == maxScore then - match.winner = opponentIndex - return - end + if bestof then + Variables.varDefine('match_bestof', bestof) end -end ----@param match table -function CustomMatchGroupInput._setPlacements(match) - for opponentIndex = 1, MAX_NUM_OPPONENTS do - local opponent = match['opponent' .. opponentIndex] - - if match.winner == opponentIndex or match.winner == 0 then - opponent.placement = 1 - elseif match.winner then - opponent.placement = 2 - end - end + return bestof end ---@param match table +---@param numberOfGames integer ---@return table -function CustomMatchGroupInput._subMatchStructure(match) - local subMatches = {} - - local currentSubGroup = 0 - for _, map in Table.iter.pairsByPrefix(match, 'map') do - local subGroupIndex = tonumber(map.subgroup) - if subGroupIndex then - currentSubGroup = subGroupIndex - else - currentSubGroup = currentSubGroup + 1 - subGroupIndex = currentSubGroup - end - - if not subMatches[subGroupIndex] then - subMatches[subGroupIndex] = {scores = {0, 0}} - end - - local winner = tonumber(map.winner) - if winner and subMatches[subGroupIndex].scores[winner] then - subMatches[subGroupIndex].scores[winner] = subMatches[subGroupIndex].scores[winner] + 1 - end - end +function MatchFunctions.getExtraData(match, numberOfGames) + local extradata = { + t1bans = MatchFunctions.readBans(match.t1bans), + t2bans = MatchFunctions.readBans(match.t2bans), + mvp = MatchGroupInputUtil.readMvp(match), + } - Array.forEach(subMatches, function(subMatch, subMatchIndex) - -- get winner if the submatch is finished - -- submatch is finished if the next submatch has a score or if the complete match is finished - local nextSubMatch = subMatches[subMatchIndex + 1] or {scores = {0, 0}} + local prefix = 'subgroup%d+' + Table.mergeInto(extradata, Table.filterByKey(match, function(key) return key:match(prefix .. 'header') end)) + Table.mergeInto(extradata, Table.filterByKey(match, function(key) return key:match(prefix .. 'iskoth') end)) - if not Logic.readBool(match.finished) and (nextSubMatch.scores[1] + nextSubMatch.scores[2] <= 0) then - return - end - if subMatch.scores[1] > subMatch.scores[2] then - subMatch.winner = 1 - elseif subMatch.scores[2] > subMatch.scores[1] then - subMatch.winner = 2 - end - end) + local t1Bans = Table.filterByKey(match, function(key) return key:match(prefix .. 't1bans') end) + Table.mergeInto(extradata, Table.mapValues(t1Bans, MatchFunctions.readBans)) - match.extradata.submatches = subMatches + local t2Bans = Table.filterByKey(match, function(key) return key:match(prefix .. 't2bans') end) + Table.mergeInto(extradata, Table.mapValues(t2Bans, MatchFunctions.readBans)) - return match + return extradata end ----@param match table +---@param bansInput string ---@return table -function CustomMatchGroupInput._opponentInput(match) - Array.forEach(Array.range(1, MAX_NUM_OPPONENTS), FnUtil.curry(CustomMatchGroupInput.processOpponent, match)) - - return match -end - ----@param match table ----@param opponentIndex integer -function CustomMatchGroupInput.processOpponent(match, opponentIndex) - local record = Json.parseIfString(match['opponent' .. opponentIndex]) - assert(Opponent.isType(record.type), 'Unsupported Opponent Type') - - local opponent = Opponent.readOpponentArgs(record) or Opponent.blank() - - -- Convert byes to literals - if Opponent.isBye(opponent) then - opponent = {type = Opponent.literal, name = 'BYE'} - end - - ---@type number|string - local teamTemplateDate = match.timestamp - -- If date is default date, resolve using tournament dates instead - -- default date indicates that the match is missing a date - -- In order to get correct child team template, we will use an approximately date and not the default date - if teamTemplateDate == DateExt.defaultTimestamp then - teamTemplateDate = DateExt.getContextualDateOrNow() - end - - Opponent.resolve(opponent, teamTemplateDate, {syncPlayer = true}) - - MatchGroupInput.mergeRecordWithOpponent(record, opponent) - - --set initial opponent sumscore - record.sumscore = 0 - - if record.type == Opponent.team then - record.icon, record.icondark = CustomMatchGroupInput.getIcon(opponent.template) - end - - match['opponent' .. opponentIndex] = record - - if record.type == Opponent.team then - MatchGroupInput.readPlayersOfTeam(match, opponentIndex, record.name, { - resolveRedirect = true, - applyUnderScores = true, - }) - else - Array.forEach(record.players or {}, function(player) - player.name = Page.pageifyLink(player.name) - end) - end +function MatchFunctions.readBans(bansInput) + local bans = CustomMatchGroupInput._readCards(bansInput) - record.name = Page.pageifyLink(record.name) + return Logic.nilIfEmpty(bans) end ----@param match table +---@param mapInput table ---@param mapIndex integer ----@param subGroupIndex integer ----@return table, integer -function CustomMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) - local map = Json.parseIfString(match['map' .. mapIndex]) or {} - - if Table.isEmpty(map) then - match['map' .. mapIndex] = nil - return match, subGroupIndex - end - - map = MatchGroupInput.getCommonTournamentVars(map, match) - - -- CR has no map names, use generic one instead - map.map = 'Set ' .. mapIndex +---@param subGroup integer +---@param opponentCount integer +---@return table +---@return integer +function MapFunctions.readMap(mapInput, mapIndex, subGroup, opponentCount) + subGroup = tonumber(mapInput.subgroup) or (subGroup + 1) - -- set initial extradata for maps - map.extradata = { - comment = map.comment, - header = map.header, + local map = { + map = 'Set ' .. mapIndex, -- CR has no map names, use generic one instead + subgroup = subGroup, } - -- determine score, resulttype, walkover and winner - map = CustomMatchGroupInput._mapWinnerProcessing(map) - - -- get participants data for the map + get map mode - map = CustomMatchGroupInput._processPlayerMapData(map, match) + map.finished = MatchGroupInputUtil.mapIsFinished(map) + local opponentInfo = Array.map(Array.range(1, opponentCount), function(opponentIndex) + local score, status = MatchGroupInputUtil.computeOpponentScore({ + walkover = mapInput.walkover, + winner = mapInput.winner, + opponentIndex = opponentIndex, + score = mapInput['score' .. opponentIndex], + }, MapFunctions.calculateMapScore(mapInput, map.finished)) + return {score = score, status = status} + end) - -- set sumscore to 0 if it isn't a number - match.opponent1.sumscore = Logic.emptyOr(match.opponent1.sumscore, 0) - match.opponent2.sumscore = Logic.emptyOr(match.opponent2.sumscore, 0) + map.scores = Array.map(opponentInfo, Operator.property('score')) - --adjust sumscore for winner opponent - if (tonumber(map.winner) or 0) > 0 then - match['opponent' .. map.winner].sumscore = - match['opponent' .. map.winner].sumscore + 1 + if map.finished or MatchGroupInputUtil.isNotPlayed(map.winner, mapInput.finished) then + map.resulttype = MatchGroupInputUtil.getResultType(mapInput.winner, mapInput.finished, opponentInfo) + map.walkover = MatchGroupInputUtil.getWalkover(map.resulttype, opponentInfo) + map.winner = MatchGroupInputUtil.getWinner(map.resulttype, mapInput.winner, opponentInfo) end - match['map' .. mapIndex] = map - - return match, subGroupIndex + return map, subGroup end ----@param map table ----@return table -function CustomMatchGroupInput._mapWinnerProcessing(map) - if map.winner == 'skip' then - map.scores = {NO_SCORE, NO_SCORE} - map.resulttype = 'np' - - return map - end - - map.scores = {} - local hasManualScores = false - - local scores = Array.map(Array.range(1, MAX_NUM_OPPONENTS), function(opponentIndex) - local score = map['score' .. opponentIndex] - map.scores[opponentIndex] = tonumber(score) or NO_SCORE - - if String.isEmpty(score) then - hasManualScores = true +---@param mapInput table +---@param finished boolean +---@return fun(opponentIndex: integer): integer? +function MapFunctions.calculateMapScore(mapInput, finished) + local winner = tonumber(mapInput.winner) + return function(opponentIndex) + local manualScore = tonumber(mapInput['score' .. opponentIndex]) + if manualScore then return + manualScore end - return Table.includes(ALLOWED_STATUSES, string.upper(score or '')) - and score:upper() - or map.scores[opponentIndex] - end) - - if not hasManualScores then - local winnerInput = tonumber(map.winner) - if winnerInput == 1 then - map.scores = {1, 0} - elseif winnerInput == 2 then - map.scores = {0, 1} + -- TODO Better to check if map has started, rather than finished, for a more correct handling + if not winner and not finished then + return end - return map + return winner == opponentIndex and 1 or 0 end - - walkoverProcessing.walkover(map, scores) - - return map end ----@param map table ----@param match table ----@return table -function CustomMatchGroupInput._processPlayerMapData(map, match) +---@param mapInput table +---@param opponents table[] +---@return table +function MapFunctions.getParticipants(mapInput, opponents) local participants = {} - - for opponentIndex = 1, MAX_NUM_OPPONENTS do - local opponent = match['opponent' .. opponentIndex] - if Opponent.typeIsParty(opponent.type) then - CustomMatchGroupInput._processDefaultPlayerMapData( - opponent.match2players or {}, - opponentIndex, - map, - participants - ) + Array.forEach(opponents, function(opponent, opponentIndex) + if opponent.type == Opponent.literal then + return elseif opponent.type == Opponent.team then - CustomMatchGroupInput._processTeamPlayerMapData( - opponent.match2players or {}, - opponentIndex, - map, - participants - ) + Table.mergeInto(participants, MapFunctions.getTeamParticipants(mapInput, opponent, opponentIndex)) + return end - end - - map.mode = Opponent.toMode(match.opponent1.type, match.opponent2.type) - - map.participants = participants + Table.mergeInto(participants, MapFunctions.getPartyParticipants(mapInput, opponent, opponentIndex)) + end) - return map + return participants end ----@param players table +---@param mapInput table +---@param opponent table ---@param opponentIndex integer ----@param map table ----@param participants table -function CustomMatchGroupInput._processDefaultPlayerMapData(players, opponentIndex, map, participants) - Array.forEach(Array.range(1, #players), function(playerIndex) - participants[opponentIndex .. '_' .. playerIndex] = { - played = true, - cards = CustomMatchGroupInput._readCards(map['t' .. opponentIndex .. 'p' .. playerIndex .. 'c']), - } +---@return table +function MapFunctions.getTeamParticipants(mapInput, opponent, opponentIndex) + local players = Array.mapIndexes(function(playerIndex) + return Logic.nilIfEmpty(mapInput['t' .. opponentIndex .. 'p' .. playerIndex]) + end) + + local participants, unattachedParticipants = MatchGroupInputUtil.parseParticipants( + opponent.match2players, + players, + function(playerIndex) + local prefix = 'o' .. opponentIndex .. 'p' .. playerIndex + return { + name = mapInput[prefix], + link = Logic.nilIfEmpty(mapInput[prefix .. 'link']) or Variables.varDefault(mapInput[prefix] .. '_page'), + cards = CustomMatchGroupInput._readCards(mapInput[prefix .. 'c']), + } + end, + function(playerIndex, playerIdData, playerInputData) + return { + played = true, + player = playerIdData.name or playerInputData.link, + cards = playerInputData.cards, + } + end + ) + + Array.forEach(unattachedParticipants, function(participant) + table.insert(opponent.match2players, { + name = participant.player, + displayname = participant.player, + }) + participants[#opponent.match2players] = participant end) + + return Table.map(participants, MatchGroupInputUtil.prefixPartcipants(opponentIndex)) end ----@param players table +---@param mapInput table +---@param opponent table ---@param opponentIndex integer ----@param map table ----@param participants table ----@return integer -function CustomMatchGroupInput._processTeamPlayerMapData(players, opponentIndex, map, participants) - local numberOfPlayersInMap = 0 - for prefix, player in Table.iter.pairsByPrefix(map, 't' .. opponentIndex .. 'p') do - numberOfPlayersInMap = numberOfPlayersInMap + 1 - local link = mw.ext.TeamLiquidIntegration.resolve_redirect( - map[prefix .. 'link'] or Variables.varDefault(player .. '_page') or player - ) - - local playerData = { +---@return table +function MapFunctions.getPartyParticipants(mapInput, opponent, opponentIndex) + local players = opponent.match2players + + local prefix = 't' .. opponentIndex .. 'p' + + local participants = {} + + Array.forEach(players, function(player, playerIndex) + participants[opponentIndex .. '_' .. playerIndex] = { played = true, - cards = CustomMatchGroupInput._readCards(map[prefix .. 'c']), + player = player.name, + cards = CustomMatchGroupInput._readCards(mapInput[prefix .. playerIndex .. 'c']), } + end) - local match2playerIndex = CustomMatchGroupInput._fetchMatch2PlayerIndexOfPlayer(players, link) - - if not match2playerIndex then - table.insert(players, { - --check the format in the existing players field please and adjust accordingly - name = link:gsub(' ', '_'), - displayname = player, - }) - match2playerIndex = #players - end + return participants +end - participants[opponentIndex .. '_' .. match2playerIndex] = playerData - end +---@param mapInput table +---@param participants table +---@return table +function MapFunctions.getExtraData(mapInput, participants) + local extradata = { + comment = mapInput.comment, + header = mapInput.header, + } - return numberOfPlayersInMap + return Table.merge(extradata, MapFunctions.getCardsExtradata(participants)) end ----@param players table ----@param player string ----@return integer|nil -function CustomMatchGroupInput._fetchMatch2PlayerIndexOfPlayer(players, player) - local displayNameIndex - local displayNameFoundTwice = false - - for match2playerIndex, match2player in pairs(players) do - local playerWithUnderscores = player:gsub(' ', '_') - if match2player and match2player.name == playerWithUnderscores then - return match2playerIndex - elseif not displayNameIndex and match2player and match2player.displayname == playerWithUnderscores then - displayNameIndex = match2playerIndex - elseif match2player and match2player.displayname == playerWithUnderscores then - displayNameFoundTwice = true +--- additionally store cards info in extradata so we can condition on them +---@param participants table +---@return table +function MapFunctions.getCardsExtradata(participants) + local extradata = {} + local playerCount = {} + for participantKey, participant in Table.iter.spairs(participants) do + local opponentIndex = string.match(participantKey, '^(%d+)_') + + playerCount[opponentIndex] = (playerCount[opponentIndex] or 0) + 1 + + local prefix = 't' .. opponentIndex .. 'p' .. playerCount[opponentIndex] + extradata[prefix .. 'tower'] = participant.cards.tower + -- participant.cards is an array plus the tower value .... + for cardIndex, card in ipairs(participant.cards) do + extradata[prefix .. 'c' .. cardIndex] = card end end - if not displayNameFoundTwice then - return displayNameIndex - end + return extradata end ---@param input string ---@return table function CustomMatchGroupInput._readCards(input) - local cleanCard = FnUtil.curry(MatchGroupInput.getCharacterName, CardNames) + local cleanCard = FnUtil.curry(MatchGroupInputUtil.getCharacterName, CardNames) - return Table.map(Json.parseIfString(input) or {}, function(key, card) - return key, cleanCard(card) - end) + return Table.mapValues(Json.parseIfString(input) or {}, cleanCard) end -return CustomMatchGroupInput +return CustomMatchGroupInput \ No newline at end of file From eeca984bbdb5f90d502203c6e4b7c77100be397f Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 3 Sep 2024 18:18:42 +0200 Subject: [PATCH 24/31] kick unused --- .../match2/wikis/clashroyale/match_group_input_custom.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index 0fc1cd94c22..a82e5133434 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -8,14 +8,11 @@ local Array = require('Module:Array') local CardNames = mw.loadData('Module:CardNames') -local Flags = require('Module:Flags') local FnUtil = require('Module:FnUtil') local Json = require('Module:Json') 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') @@ -30,7 +27,6 @@ local OPPONENT_CONFIG = { pagifyTeamNames = true, pagifyPlayerNames = true, } -local TBD = 'TBD' local ROYALE_API_PREFIX = 'https://royaleapi.com/' ---@class CRParticipant From 7c1503a3139134d3d02c8f07a878f3e65df62ace Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 4 Sep 2024 12:02:57 +0200 Subject: [PATCH 25/31] fix --- .../wikis/clashroyale/match_group_input_custom.lua | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index a82e5133434..ec111172075 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -29,13 +29,6 @@ local OPPONENT_CONFIG = { } local ROYALE_API_PREFIX = 'https://royaleapi.com/' ----@class CRParticipant ----@field player string ----@field heroes string[]? ----@field position integer? ----@field flag string? ----@field random boolean? - local CustomMatchGroupInput = {} local MatchFunctions = {} local MapFunctions = {} @@ -44,7 +37,7 @@ local MapFunctions = {} ---@param options table? ---@return table function CustomMatchGroupInput.processMatch(match, options) - Table.mergeInto(match, MatchFunctions.readDate(match)) + Table.mergeInto(match, MatchGroupInputUtil.readDate(match.date)) local opponents = Array.mapIndexes(function(opponentIndex) return MatchGroupInputUtil.readOpponent(match, opponentIndex, OPPONENT_CONFIG) From b210691615a74ec4b5c2be97a7ec9b939b78bd0b Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 4 Sep 2024 12:32:58 +0200 Subject: [PATCH 26/31] pass correct var to `mapIsFinished` --- .../match2/wikis/clashroyale/match_group_input_custom.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index ec111172075..a0e6fce98d7 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -193,7 +193,7 @@ function MapFunctions.readMap(mapInput, mapIndex, subGroup, opponentCount) subgroup = subGroup, } - map.finished = MatchGroupInputUtil.mapIsFinished(map) + map.finished = MatchGroupInputUtil.mapIsFinished(mapInput) local opponentInfo = Array.map(Array.range(1, opponentCount), function(opponentIndex) local score, status = MatchGroupInputUtil.computeOpponentScore({ walkover = mapInput.walkover, From dc6aa5089016a12f6343e5941c99676ff25bd15b Mon Sep 17 00:00:00 2001 From: hjpalpha <75081997+hjpalpha@users.noreply.github.com> Date: Mon, 9 Sep 2024 13:59:25 +0200 Subject: [PATCH 27/31] Update components/match2/wikis/clashroyale/match_group_input_custom.lua Co-authored-by: Rikard Blixt --- .../match2/wikis/clashroyale/match_group_input_custom.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index a0e6fce98d7..fb18d5f70e9 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -222,8 +222,8 @@ function MapFunctions.calculateMapScore(mapInput, finished) local winner = tonumber(mapInput.winner) return function(opponentIndex) local manualScore = tonumber(mapInput['score' .. opponentIndex]) - if manualScore then return - manualScore + if manualScore then + return manualScore end -- TODO Better to check if map has started, rather than finished, for a more correct handling From 211319977afb4bb15a6e9ae1c08b3bf35827e16e Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 10 Sep 2024 08:01:02 +0200 Subject: [PATCH 28/31] #4650 --- .../match2/wikis/clashroyale/match_group_input_custom.lua | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index fb18d5f70e9..96102f79fd4 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -68,10 +68,7 @@ function CustomMatchGroupInput.processMatch(match, options) match.resulttype = MatchGroupInputUtil.getResultType(winnerInput, finishedInput, opponents) match.walkover = MatchGroupInputUtil.getWalkover(match.resulttype, opponents) match.winner = MatchGroupInputUtil.getWinner(match.resulttype, winnerInput, opponents) - MatchGroupInputUtil.setPlacement(opponents, match.winner, 1, 2) - elseif MatchGroupInputUtil.isNotPlayed(winnerInput, finishedInput) then - match.resulttype = MatchGroupInputUtil.getResultType(winnerInput, finishedInput, opponents) - match.winner = nil + MatchGroupInputUtil.setPlacement(opponents, match.winner, 1, 2, match.resulttype) end MatchFunctions.getTournamentVars(match) @@ -206,7 +203,7 @@ function MapFunctions.readMap(mapInput, mapIndex, subGroup, opponentCount) map.scores = Array.map(opponentInfo, Operator.property('score')) - if map.finished or MatchGroupInputUtil.isNotPlayed(map.winner, mapInput.finished) then + if map.finished then map.resulttype = MatchGroupInputUtil.getResultType(mapInput.winner, mapInput.finished, opponentInfo) map.walkover = MatchGroupInputUtil.getWalkover(map.resulttype, opponentInfo) map.winner = MatchGroupInputUtil.getWinner(map.resulttype, mapInput.winner, opponentInfo) From fe67332946b0c856a20da3bc87019b484670b64d Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 18 Sep 2024 13:09:17 +0200 Subject: [PATCH 29/31] from review --- .../wikis/clashroyale/match_group_input_custom.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index 96102f79fd4..8741e8a174d 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -25,7 +25,6 @@ local DEFAULT_MODE = 'solo' local OPPONENT_CONFIG = { resolveRedirect = true, pagifyTeamNames = true, - pagifyPlayerNames = true, } local ROYALE_API_PREFIX = 'https://royaleapi.com/' @@ -169,7 +168,7 @@ function MatchFunctions.getExtraData(match, numberOfGames) end ---@param bansInput string ----@return table +---@return table? function MatchFunctions.readBans(bansInput) local bans = CustomMatchGroupInput._readCards(bansInput) @@ -267,14 +266,14 @@ function MapFunctions.getTeamParticipants(mapInput, opponent, opponentIndex) return { name = mapInput[prefix], link = Logic.nilIfEmpty(mapInput[prefix .. 'link']) or Variables.varDefault(mapInput[prefix] .. '_page'), - cards = CustomMatchGroupInput._readCards(mapInput[prefix .. 'c']), } end, function(playerIndex, playerIdData, playerInputData) + local prefix = 'o' .. opponentIndex .. 'p' .. playerIndex return { played = true, player = playerIdData.name or playerInputData.link, - cards = playerInputData.cards, + cards = CustomMatchGroupInput._readCards(mapInput[prefix .. 'c']), } end ) @@ -354,4 +353,4 @@ function CustomMatchGroupInput._readCards(input) return Table.mapValues(Json.parseIfString(input) or {}, cleanCard) end -return CustomMatchGroupInput \ No newline at end of file +return CustomMatchGroupInput From 3a5d138392b947159c60d29f133a03264d8519bc Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 18 Sep 2024 13:13:05 +0200 Subject: [PATCH 30/31] not needed --- components/match2/wikis/clashroyale/match_group_input_custom.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index 8741e8a174d..b474ce620df 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -185,7 +185,6 @@ function MapFunctions.readMap(mapInput, mapIndex, subGroup, opponentCount) subGroup = tonumber(mapInput.subgroup) or (subGroup + 1) local map = { - map = 'Set ' .. mapIndex, -- CR has no map names, use generic one instead subgroup = subGroup, } From 31dcf371bc9fa515f1ae27956d2d9e4782fe2202 Mon Sep 17 00:00:00 2001 From: hjpalpha <75081997+hjpalpha@users.noreply.github.com> Date: Wed, 18 Sep 2024 18:49:25 +0200 Subject: [PATCH 31/31] Update components/match2/wikis/clashroyale/match_group_input_custom.lua --- .../match2/wikis/clashroyale/match_group_input_custom.lua | 5 ----- 1 file changed, 5 deletions(-) diff --git a/components/match2/wikis/clashroyale/match_group_input_custom.lua b/components/match2/wikis/clashroyale/match_group_input_custom.lua index b474ce620df..d1f54a3c90f 100644 --- a/components/match2/wikis/clashroyale/match_group_input_custom.lua +++ b/components/match2/wikis/clashroyale/match_group_input_custom.lua @@ -216,11 +216,6 @@ end function MapFunctions.calculateMapScore(mapInput, finished) local winner = tonumber(mapInput.winner) return function(opponentIndex) - local manualScore = tonumber(mapInput['score' .. opponentIndex]) - if manualScore then - return manualScore - end - -- TODO Better to check if map has started, rather than finished, for a more correct handling if not winner and not finished then return