From 752504be9afae09c975115160cfcb51c56221da3 Mon Sep 17 00:00:00 2001 From: Rikard Blixt Date: Mon, 25 Nov 2024 17:18:44 +0100 Subject: [PATCH] feat(match2): standardize FFA/BR input --- .../match2/commons/match_group_input_util.lua | 160 ++++++++++++++++++ .../apexlegends/match_group_input_custom.lua | 145 ++-------------- 2 files changed, 173 insertions(+), 132 deletions(-) diff --git a/components/match2/commons/match_group_input_util.lua b/components/match2/commons/match_group_input_util.lua index a07c269b3a0..7a432c67609 100644 --- a/components/match2/commons/match_group_input_util.lua +++ b/components/match2/commons/match_group_input_util.lua @@ -1239,4 +1239,164 @@ function MatchGroupInputUtil.standardProcessMaps(match, opponents, Parser) return maps end +---@class FfaMatchParserInterface +---@field extractMaps fun(match: table, opponents: table[], mapProps: any?): table[] +---@field parseSettings fun(match: table): table +---@field calculateMatchScore? fun(maps: table[], opponents: table[]): fun(opponentIndex: integer): integer? +---@field removeUnsetMaps? fun(maps: table[]): table[] +---@field getExtraData? fun(match: table, games: table[], opponents: table[]): table? +---@field adjustOpponent? fun(opponent: MGIParsedOpponent, opponentIndex: integer) +---@field getLinks? fun(match: table, games: table[]): table +---@field getHeadToHeadLink? fun(match: table, opponents: table[]): string? +---@field readDate? fun(match: table): { +---date: string, +---dateexact: boolean, +---timestamp: integer, +---timezoneId: string?, +---timezoneOffset:string?, +---} +---@field getMode? fun(opponents: table[]): string +---@field DEFAULT_MODE? string +---@field DATE_FALLBACKS? string[] +---@field OPPONENT_CONFIG? readOpponentOptions + +--- The standard way to process a match input. +--- +--- The Parser injection must have the following functions: +--- - extractMaps(match, opponents, mapProps): table[] +--- - parseSettings(match): table +--- +--- It may optionally have the following functions: +--- - calculateMatchScore(maps, opponents): fun(opponentIndex): integer? +--- - removeUnsetMaps(maps): table[] +--- - getExtraData(match, games, opponents): table? +--- - adjustOpponent(opponent, opponentIndex) +--- - getLinks(match, games): table? +--- - getHeadToHeadLink(match, opponents): string? +--- - readDate(match): table +--- - getMode(opponents): string? +--- +--- Additionally, the Parser may have the following properties: +--- - DEFAULT_MODE: string +--- - DATE_FALLBACKS: string[] +--- - OPPONENT_CONFIG: table +---@param match table +---@param Parser FfaMatchParserInterface +---@param mapProps any? +---@return table +function MatchGroupInputUtil.standardProcessFfaMatch(match, Parser, mapProps) + local finishedInput = match.finished --[[@as string?]] + local winnerInput = match.winner --[[@as string?]] + + local settings = Parser.parseSettings(match) + + Table.mergeInto(match, MatchGroupInputUtil.readDate(match.date)) + + local opponents = Array.mapIndexes(function(opponentIndex) + return MatchGroupInputUtil.readOpponent(match, opponentIndex, Parser.OPPONENT_CONFIG) + end) + + local games = Parser.extractMaps(match, opponents, settings.score) + + local autoScoreFunction = Parser.calculateMatchScore and MatchGroupInputUtil.canUseAutoScore(match, games) + and Parser.calculateMatchScore(opponents, games) + or nil + Array.forEach(opponents, function(opponent, opponentIndex) + opponent.extradata = opponent.extradata or {} + opponent.extradata.startingpoints = tonumber(opponent.pointmodifier) + opponent.placement = tonumber(opponent.placement) + + opponent.score, opponent.status = MatchGroupInputUtil.computeOpponentScore({ + walkover = match.walkover, + winner = match.winner, + opponentIndex = opponentIndex, + score = opponent.score, + }, autoScoreFunction) + end) + + match.finished = MatchGroupInputUtil.matchIsFinished(match, opponents) + + if match.finished then + match.status = MatchGroupInputUtil.getMatchStatus(winnerInput, finishedInput) + match.winner = MatchGroupInputUtil.getWinner(match.status, winnerInput, opponents) + + local placementOfOpponents = MatchGroupInputUtil.calculatePlacementOfOpponents(opponents) + Array.forEach(opponents, function(opponent, opponentIndex) + opponent.placement = placementOfOpponents[opponentIndex] + opponent.extradata.bg = settings.status[opponent.placement] + end) + end + + match.mode = Parser.getMode and Parser.getMode(opponents) + or Logic.emptyOr(match.mode, globalVars:get('tournament_mode'), Parser.DEFAULT_MODE) + Table.mergeInto(match, MatchGroupInputUtil.getTournamentContext(match)) + + match.stream = Streams.processStreams(match) + match.extradata = Parser.getExtraData and Parser.getExtraData(match, games, opponents) or {} + + match.games = games + match.opponents = opponents + + return match +end + +---@param opponents table[] +---@return integer[] +function MatchGroupInputUtil.calculatePlacementOfOpponents(opponents) + local usedPlacements = Array.map(opponents, function() + return 0 + end) + Array.forEach(opponents, function(opponent) + if opponent.placement then + usedPlacements[opponent.placement] = usedPlacements[opponent.placement] + 1 + end + end) + -- Spread out placements if there are duplicates placements + -- For example 2 placement at 4 means 5 is also taken and the next available is 6 + Array.forEach(usedPlacements, function(count, placement) + if count > 1 then + usedPlacements[placement + 1] = usedPlacements[placement + 1] + (count - 1) + usedPlacements[placement] = 1 + end + end) + + local placementCount = #usedPlacements + local function findNextSlot(placement) + if usedPlacements[placement] == 0 or placement > placementCount then + return placement + end + return findNextSlot(placement + 1) + end + + local placementOfTeams = {} + local lastScore + local lastPlacement = 0 + + local function scoreSorter(tbl, key1, key2) + local value1 = tonumber(tbl[key1].score) or -math.huge + local value2 = tonumber(tbl[key2].score) or -math.huge + return value1 > value2 + end + + for opponentIdx, opp in Table.iter.spairs(opponents, scoreSorter) do + local placement = opp.placement + if not placement then + local thisPlacement = findNextSlot(lastPlacement) + usedPlacements[thisPlacement] = 1 + if lastScore and opp.score == lastScore then + placement = lastPlacement + else + placement = thisPlacement + end + end + placementOfTeams[opponentIdx] = placement + + lastPlacement = placement + lastScore = opp.score + end + + return placementOfTeams +end + + return MatchGroupInputUtil diff --git a/components/match2/wikis/apexlegends/match_group_input_custom.lua b/components/match2/wikis/apexlegends/match_group_input_custom.lua index f3b22e9214c..9e8fe0aa5b1 100644 --- a/components/match2/wikis/apexlegends/match_group_input_custom.lua +++ b/components/match2/wikis/apexlegends/match_group_input_custom.lua @@ -8,25 +8,21 @@ local Array = require('Module:Array') local Json = require('Module:Json') -local Logic = require('Module:Logic') local Lua = require('Module:Lua') local Operator = require('Module:Operator') -local Streams = require('Module:Links/Stream') local Table = require('Module:Table') -local Variables = require('Module:Variables') local MatchGroupInputUtil = Lua.import('Module:MatchGroup/Input/Util') -local DEFAULT_MODE = 'team' - -local OPPONENT_CONFIG = { - resolveRedirect = true, - applyUnderScores = true, - maxNumPlayers = 3, -} - -local MatchFunctions = {} local MapFunctions = {} +local MatchFunctions = { + OPPONENT_CONFIG = { + resolveRedirect = true, + applyUnderScores = true, + maxNumPlayers = 3, + }, + DEFAULT_MODE = 'team' +} local CustomMatchGroupInput = {} @@ -34,61 +30,12 @@ local CustomMatchGroupInput = {} ---@param options table? ---@return table function CustomMatchGroupInput.processMatch(match, options) - local finishedInput = match.finished --[[@as string?]] - local winnerInput = match.winner --[[@as string?]] - - local settings = MatchFunctions.parseSetting(match) - - Table.mergeInto(match, MatchGroupInputUtil.readDate(match.date)) - - local opponents = Array.mapIndexes(function(opponentIndex) - return MatchGroupInputUtil.readOpponent(match, opponentIndex, OPPONENT_CONFIG) - end) - - local games = MatchFunctions.extractMaps(match, opponents, settings.score) - - local autoScoreFunction = MatchGroupInputUtil.canUseAutoScore(match, games) - and MatchFunctions.calculateMatchScore(opponents, games) - or nil - Array.forEach(opponents, function(opponent, opponentIndex) - opponent.extradata = opponent.extradata or {} - opponent.extradata.startingpoints = tonumber(opponent.pointmodifier) - opponent.placement = tonumber(opponent.placement) - - opponent.score, opponent.status = MatchGroupInputUtil.computeOpponentScore({ - walkover = match.walkover, - winner = match.winner, - opponentIndex = opponentIndex, - score = opponent.score, - }, autoScoreFunction) - end) - - match.finished = MatchGroupInputUtil.matchIsFinished(match, opponents) - - if match.finished then - match.status = MatchGroupInputUtil.getMatchStatus(winnerInput, finishedInput) - match.winner = MatchGroupInputUtil.getWinner(match.status, winnerInput, opponents) - - local placementOfOpponents = CustomMatchGroupInput.calculatePlacementOfOpponents(opponents) - Array.forEach(opponents, function(opponent, opponentIndex) - opponent.placement = placementOfOpponents[opponentIndex] - opponent.extradata.bg = settings.status[opponent.placement] - end) - end - - match.mode = Logic.emptyOr(match.mode, Variables.varDefault('tournament_mode', DEFAULT_MODE)) - Table.mergeInto(match, MatchGroupInputUtil.getTournamentContext(match)) - - match.stream = Streams.processStreams(match) - - match.games = games - match.opponents = opponents - - match.extradata = MatchFunctions.getExtraData(settings) - - return match + return MatchGroupInputUtil.standardProcessFfaMatch(match, MatchFunctions) end +-- +-- match related functions +-- ---@param match table ---@param opponents table[] ---@param scoreSettings table @@ -133,73 +80,9 @@ function MatchFunctions.calculateMatchScore(opponents, maps) end end ----@param opponents table[] ----@return integer[] -function CustomMatchGroupInput.calculatePlacementOfOpponents(opponents) - local usedPlacements = Array.map(opponents, function() - return 0 - end) - Array.forEach(opponents, function(opponent) - if opponent.placement then - usedPlacements[opponent.placement] = usedPlacements[opponent.placement] + 1 - end - end) - -- Spread out placements if there are duplicates placements - -- For example 2 placement at 4 means 5 is also taken and the next available is 6 - Array.forEach(usedPlacements, function(count, placement) - if count > 1 then - usedPlacements[placement+1] = usedPlacements[placement + 1] + (count - 1) - usedPlacements[placement] = 1 - end - end) - - local placementCount = #usedPlacements - local function findNextSlot(placement) - if usedPlacements[placement] == 0 or placement > placementCount then - return placement - end - return findNextSlot(placement + 1) - end - - local placementOfTeams = {} - local lastScore - local lastPlacement = 0 - for opponentIdx, opp in Table.iter.spairs(opponents, CustomMatchGroupInput.scoreSorter) do - local placement = opp.placement - if not placement then - local thisPlacement = findNextSlot(lastPlacement) - usedPlacements[thisPlacement] = 1 - if lastScore and opp.score == lastScore then - placement = lastPlacement - else - placement = thisPlacement - end - end - placementOfTeams[opponentIdx] = placement - - lastPlacement = placement - lastScore = opp.score - end - - return placementOfTeams -end - ----@param tbl table ----@param key1 string|number ----@param key2 string|number ----@return boolean -function CustomMatchGroupInput.scoreSorter(tbl, key1, key2) - local value1 = tonumber(tbl[key1].score) or -math.huge - local value2 = tonumber(tbl[key2].score) or -math.huge - return value1 > value2 -end - --- --- match related functions --- ---@param match table ---@return {score: table, status: table} -function MatchFunctions.parseSetting(match) +function MatchFunctions.parseSettings(match) -- Score Settings local scoreSettings = { kill = tonumber(match.p_kill) or 1, @@ -239,7 +122,6 @@ end -- map related functions -- --- Parse extradata information ---@param map table ---@return table function MapFunctions.getExtraData(map) @@ -249,7 +131,6 @@ function MapFunctions.getExtraData(map) } end ----Calculate Score and Winner of the map ---@param scoreDataInput table? ---@param scoreSettings table ---@return table