Skip to content

Commit

Permalink
feat(match2): standardize FFA/BR input (#5141)
Browse files Browse the repository at this point in the history
* feat(match2): standardize FFA/BR input

* fix extradata
  • Loading branch information
Rathoz authored Nov 26, 2024
1 parent f688a40 commit e49018c
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 133 deletions.
144 changes: 144 additions & 0 deletions components/match2/commons/match_group_input_util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1239,4 +1239,148 @@ 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 getExtraData? fun(match: table, games: table[], opponents: table[], settings: table): table?
---@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?
--- - getExtraData(match, games, opponents, settings): 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, settings) 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
150 changes: 17 additions & 133 deletions components/match2/wikis/apexlegends/match_group_input_custom.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,87 +8,34 @@

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 = {}

---@param match table
---@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
Expand Down Expand Up @@ -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,
Expand All @@ -226,9 +109,12 @@ function MatchFunctions.parseSetting(match)
}
end

---@param match table
---@param games table[]
---@param opponents table[]
---@param settings table
---@return table
function MatchFunctions.getExtraData(settings)
function MatchFunctions.getExtraData(match, games, opponents, settings)
return {
scoring = settings.score,
status = settings.status,
Expand All @@ -239,7 +125,6 @@ end
-- map related functions
--

-- Parse extradata information
---@param map table
---@return table
function MapFunctions.getExtraData(map)
Expand All @@ -249,7 +134,6 @@ function MapFunctions.getExtraData(map)
}
end

---Calculate Score and Winner of the map
---@param scoreDataInput table?
---@param scoreSettings table
---@return table
Expand Down

0 comments on commit e49018c

Please sign in to comment.