diff --git a/components/match2/commons/match_group_input_util.lua b/components/match2/commons/match_group_input_util.lua index f4490b8b76e..3b0fb785de7 100644 --- a/components/match2/commons/match_group_input_util.lua +++ b/components/match2/commons/match_group_input_util.lua @@ -808,9 +808,10 @@ function MatchGroupInputUtil.placementFromWinner(status, winner, opponentIndex) end ---@param match table +---@param maps table[] ---@param opponents {score: integer?}[] ---@return boolean -function MatchGroupInputUtil.matchIsFinished(match, opponents) +function MatchGroupInputUtil.matchIsFinished(match, maps, opponents) if MatchGroupInputUtil.isNotPlayed(match.winner, match.finished) then return true end @@ -835,8 +836,14 @@ function MatchGroupInputUtil.matchIsFinished(match, opponents) end -- If enough time has passed since match started, it should be marked as finished - local threshold = match.dateexact and ASSUME_FINISHED_AFTER.EXACT or ASSUME_FINISHED_AFTER.ESTIMATE - if match.timestamp ~= DateExt.defaultTimestamp and (match.timestamp + threshold) < NOW then + local function longEnoughLiveForMap(map) + if not map.timestamp or map.timestamp == DateExt.defaultTimestamp then + return false + end + local longLiveTime = map.dateexact and ASSUME_FINISHED_AFTER.EXACT or ASSUME_FINISHED_AFTER.ESTIMATE + return NOW > (map.timestamp + longLiveTime) + end + if #maps > 0 and Array.all(maps, longEnoughLiveForMap) then return true end @@ -1046,6 +1053,14 @@ function MatchGroupInputUtil.mergeStandaloneIntoMatch(match, standaloneMatch) return match end +---@alias readDateFunction fun(match: table): { +---date: string, +---dateexact: boolean, +---timestamp: integer, +---timezoneId: string?, +---timezoneOffset:string?, +---} + ---@class MatchParserInterface ---@field extractMaps fun(match: table, opponents: table[], mapProps: any?): table[] ---@field getBestOf fun(bestOfInput: string|integer|nil, maps: table[]): integer? @@ -1055,13 +1070,7 @@ end ---@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 readDate? readDateFunction ---@field getMode? fun(opponents: table[]): string ---@field DEFAULT_MODE? string ---@field DATE_FALLBACKS? string[] @@ -1096,8 +1105,7 @@ function MatchGroupInputUtil.standardProcessMatch(match, Parser, FfaParser, mapP Parser = Parser or {} local matchInput = Table.deepCopy(match) - local dateProps = Parser.readDate and Parser.readDate(match) - or MatchGroupInputUtil.readDate(match.date, Parser.DATE_FALLBACKS) + local dateProps = MatchGroupInputUtil.getMatchDate(Parser, matchInput) Table.mergeInto(match, dateProps) local opponents = Array.mapIndexes(function(opponentIndex) @@ -1133,7 +1141,7 @@ function MatchGroupInputUtil.standardProcessMatch(match, Parser, FfaParser, mapP }, autoScoreFunction) end) - match.finished = MatchGroupInputUtil.matchIsFinished(match, opponents) + match.finished = MatchGroupInputUtil.matchIsFinished(match, games, opponents) if match.finished then match.status = MatchGroupInputUtil.getMatchStatus(matchInput.winner, matchInput.finished) @@ -1195,6 +1203,9 @@ function MatchGroupInputUtil.standardProcessMaps(match, opponents, Parser) local finishedInput = map.finished --[[@as string?]] local winnerInput = map.winner --[[@as string?]] + local dateToUse = map.date or match.date + Table.mergeInto(map, MatchGroupInputUtil.readDate(dateToUse)) + if Parser.ADD_SUB_GROUP then subGroup = tonumber(map.subgroup) or (subGroup + 1) map.subgroup = subGroup @@ -1253,7 +1264,7 @@ end ---@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 readDate? fun(match: table): table +---@field readDate? readDateFunction ---@field adjustOpponent? fun(opponent: table[], opponentIndex: integer, match: table) ---@field matchIsFinished? fun(match: table, opponents: table[]): boolean ---@field getMatchWinner? fun(status: string, winnerInput: integer|string|nil, opponents: table[]): integer? @@ -1288,8 +1299,7 @@ function MatchGroupInputUtil.standardProcessFfaMatch(match, Parser, mapProps) local finishedInput = match.finished --[[@as string?]] local winnerInput = match.winner --[[@as string?]] - local dateProps = Parser.readDate and Parser.readDate(match) - or MatchGroupInputUtil.readDate(match.date, Parser.DATE_FALLBACKS) + local dateProps = MatchGroupInputUtil.getMatchDate(Parser, match) Table.mergeInto(match, dateProps) local opponents = Array.mapIndexes(function(opponentIndex) @@ -1320,7 +1330,7 @@ function MatchGroupInputUtil.standardProcessFfaMatch(match, Parser, mapProps) end) match.finished = Parser.matchIsFinished and Parser.matchIsFinished(match, opponents) - or MatchGroupInputUtil.matchIsFinished(match, opponents) + or MatchGroupInputUtil.matchIsFinished(match, games, opponents) if match.finished then match.status = MatchGroupInputUtil.getMatchStatus(winnerInput, finishedInput) @@ -1365,7 +1375,8 @@ function MatchGroupInputUtil.standardProcessFfaMaps(match, opponents, scoreSetti local finishedInput = map.finished --[[@as string?]] local winnerInput = map.winner --[[@as string?]] - Table.mergeInto(map, MatchGroupInputUtil.readDate(map.date)) + local dateToUse = map.date or match.date + Table.mergeInto(map, MatchGroupInputUtil.readDate(dateToUse)) map.finished = MatchGroupInputUtil.mapIsFinished(map) map.opponents = Array.map(opponents, function(matchOpponent) @@ -1540,4 +1551,40 @@ function MatchGroupInputUtil.makeBattleRoyaleMapOpponentDetails(scoreDataInput, return opponent end +---@param matchParser {readDate?: readDateFunction, DATE_FALLBACKS?: string[]} +---@param matchInput table +---@return {date: string, dateexact: boolean, timestamp: integer, timezoneId: string?, timezoneOffset: string?} +function MatchGroupInputUtil.getMatchDate(matchParser, matchInput) + local defaultDateParser = function(record) + return MatchGroupInputUtil.readDate(record.date, matchParser.DATE_FALLBACKS) + end + local dateParsingFunction = matchParser.readDate or defaultDateParser + + if matchInput.date then + -- If there's a match date in the input, use it + return dateParsingFunction(match) + end + + -- Otherwise, use the date from the earliest game in the match + local easlierGameTimestamp, earliestGameDateStruct = DateExt.maxTimestamp, nil + + -- We have to loop through the maps unparsed as we haven't parsed the maps at this point yet + for _, map in Table.iter.pairsByPrefix(match, 'map', {requireIndex = true}) do + if map.date then + local gameDateStruct = dateParsingFunction(map) + if gameDateStruct.timestamp < easlierGameTimestamp then + earliestGameDateStruct = gameDateStruct + easlierGameTimestamp = gameDateStruct.timestamp + end + end + end + + -- We couldn't find game date neither, let's use the defaults for the match + if not earliestGameDateStruct then + return dateParsingFunction(match) + end + + return earliestGameDateStruct +end + return MatchGroupInputUtil diff --git a/components/match2/wikis/ageofempires/match_group_input_custom.lua b/components/match2/wikis/ageofempires/match_group_input_custom.lua index 1a64ae5caae..b11a6032388 100644 --- a/components/match2/wikis/ageofempires/match_group_input_custom.lua +++ b/components/match2/wikis/ageofempires/match_group_input_custom.lua @@ -38,7 +38,7 @@ function CustomMatchGroupInput.processMatch(match, options) Table.mergeInto(match, MatchGroupInputUtil.getTournamentContext(match)) match.game, match.mapsInfo = CustomMatchGroupInput._getMapsAndGame(match) - Table.mergeInto(match, MatchGroupInputUtil.readDate(match.date)) + Table.mergeInto(match, MatchGroupInputUtil.getMatchDate({}, match)) local opponents = Array.mapIndexes(function(opponentIndex) return CustomMatchGroupInput.readOpponent(match, opponentIndex, OPPONENT_CONFIG) @@ -64,7 +64,7 @@ function CustomMatchGroupInput.processMatch(match, options) local winnerInput = match.winner --[[@as string?]] local finishedInput = match.finished --[[@as string?]] - match.finished = MatchGroupInputUtil.matchIsFinished(match, opponents) + match.finished = MatchGroupInputUtil.matchIsFinished(match, games, opponents) if match.finished then match.status = MatchGroupInputUtil.getMatchStatus(winnerInput, finishedInput) diff --git a/components/match2/wikis/counterstrike/match_group_input_custom.lua b/components/match2/wikis/counterstrike/match_group_input_custom.lua index 582fa441ceb..380100f06dd 100644 --- a/components/match2/wikis/counterstrike/match_group_input_custom.lua +++ b/components/match2/wikis/counterstrike/match_group_input_custom.lua @@ -42,7 +42,7 @@ function CustomMatchGroupInput.processMatch(match, options) local finishedInput = Logic.nilIfEmpty(match.finished) or Variables.varDefault('tournament_status') --[[@as string?]] local winnerInput = match.winner --[[@as string?]] - Table.mergeInto(match, MatchGroupInputUtil.readDate(match.date)) + Table.mergeInto(match, MatchGroupInputUtil.getMatchDate({}, match)) local opponents = Array.mapIndexes(function(opponentIndex) return MatchGroupInputUtil.readOpponent(match, opponentIndex, OPPONENT_CONFIG) @@ -64,7 +64,7 @@ function CustomMatchGroupInput.processMatch(match, options) }, autoScoreFunction) end) - match.finished = MatchGroupInputUtil.matchIsFinished(match, opponents) + match.finished = MatchGroupInputUtil.matchIsFinished(match, games, opponents) if match.finished then match.status = MatchGroupInputUtil.getMatchStatus(winnerInput, finishedInput) diff --git a/components/match2/wikis/dota2/match_group_input_custom.lua b/components/match2/wikis/dota2/match_group_input_custom.lua index 5fab22b2f74..a500a723cb2 100644 --- a/components/match2/wikis/dota2/match_group_input_custom.lua +++ b/components/match2/wikis/dota2/match_group_input_custom.lua @@ -88,6 +88,9 @@ function MatchFunctions.extractMaps(match, opponents, MapParser) local finishedInput = map.finished --[[@as string?]] local winnerInput = map.winner --[[@as string?]] + local dateToUse = map.date or match.date + Table.mergeInto(map, MatchGroupInputUtil.readDate(dateToUse)) + map.map = MapFunctions.getMapName(map) map.length = MapParser.getLength(map) map.vod = map.vod or String.nilIfEmpty(match['vodgame' .. mapIndex]) diff --git a/components/match2/wikis/leagueoflegends/match_group_input_custom.lua b/components/match2/wikis/leagueoflegends/match_group_input_custom.lua index 3ae16244233..19a212722d2 100644 --- a/components/match2/wikis/leagueoflegends/match_group_input_custom.lua +++ b/components/match2/wikis/leagueoflegends/match_group_input_custom.lua @@ -82,6 +82,9 @@ function MatchFunctions.extractMaps(match, opponents, MapParser) local finishedInput = map.finished --[[@as string?]] local winnerInput = map.winner --[[@as string?]] + local dateToUse = map.date or match.date + Table.mergeInto(map, MatchGroupInputUtil.readDate(dateToUse)) + map.length = MapParser.getLength(map) map.vod = map.vod or String.nilIfEmpty(match['vodgame' .. mapIndex]) map.extradata = MapFunctions.getExtraData(MapParser, map, #opponents) diff --git a/components/match2/wikis/rocketleague/match_group_input_custom.lua b/components/match2/wikis/rocketleague/match_group_input_custom.lua index b3c89ba7da8..dd2066e9453 100644 --- a/components/match2/wikis/rocketleague/match_group_input_custom.lua +++ b/components/match2/wikis/rocketleague/match_group_input_custom.lua @@ -55,6 +55,9 @@ function MatchFunctions.extractMaps(match, opponents) local finishedInput = map.finished --[[@as string?]] local winnerInput = map.winner --[[@as string?]] + local dateToUse = map.date or match.date + Table.mergeInto(map, MatchGroupInputUtil.readDate(dateToUse)) + map.extradata = MapFunctions.getExtraData(map) map.finished = MatchGroupInputUtil.mapIsFinished(map)