From 29f65898c93b1905f10caaae5a1ab77797f9033b Mon Sep 17 00:00:00 2001 From: Rikard Blixt Date: Tue, 17 Sep 2024 12:50:37 +0200 Subject: [PATCH 1/9] feat(match2): remove support for `pagifyPlayerNames=false` (#4750) * feat(match2): remove support for `pagifyPlayerNames=false` * remove wc3's also --- .../match2/commons/match_group_input_util.lua | 25 ++++++------------- .../match_group_input_starcraft.lua | 4 +-- .../ageofempires/match_group_input_custom.lua | 12 +++------ .../arenaofvalor/match_group_input_custom.lua | 1 - .../battlerite/match_group_input_custom.lua | 1 - .../clashofclans/match_group_input_custom.lua | 1 - .../crossfire/match_group_input_custom.lua | 1 - .../deadlock/match_group_input_custom.lua | 1 - .../wikis/dota2/match_group_input_custom.lua | 5 ++-- .../match_group_input_custom.lua | 5 ++-- .../rocketleague/match_group_input_custom.lua | 2 +- .../wikis/smite/match_group_input_custom.lua | 1 - .../warcraft/match_group_input_custom.lua | 1 - .../wildrift/match_group_input_custom.lua | 1 - 14 files changed, 17 insertions(+), 44 deletions(-) diff --git a/components/match2/commons/match_group_input_util.lua b/components/match2/commons/match_group_input_util.lua index b52b7c08594..c02f0dceca9 100644 --- a/components/match2/commons/match_group_input_util.lua +++ b/components/match2/commons/match_group_input_util.lua @@ -114,7 +114,6 @@ local contentLanguage = mw.getContentLanguage() ---@field maxNumPlayers integer? ---@field resolveRedirect boolean? ---@field pagifyTeamNames boolean? ----@field pagifyPlayerNames boolean? ---@class MatchGroupInputSubstituteInformation ---@field substitute standardPlayer @@ -236,11 +235,9 @@ function MatchGroupInputUtil.readOpponent(match, opponentIndex, options) ) end - if options.pagifyPlayerNames then - Array.forEach(opponent.players or {}, function(player) - player.pageName = Page.pageifyLink(player.pageName) - end) - end + Array.forEach(opponent.players or {}, function(player) + player.pageName = Page.pageifyLink(player.pageName) + end) local record = MatchGroupInputUtil.mergeRecordWithOpponent(opponentInput, opponent, substitutions) @@ -1134,16 +1131,9 @@ function MatchGroupInputUtil.findPlayerId(players, playerInput, playerLink) end ---@param name string ----@param options {pagifyPlayerNames: boolean?}? ---@return string -function MatchGroupInputUtil.makeLinkFromName(name, options) - local link = mw.ext.TeamLiquidIntegration.resolve_redirect(name) - - if (options or {}).pagifyPlayerNames then - link = Page.pageifyLink(link) --[[@as string]] - end - - return link +function MatchGroupInputUtil.makeLinkFromName(name) + return Page.pageifyLink(name) --[[@as string]] end ---@alias PlayerInputData {name: string?, link: string?} @@ -1151,15 +1141,14 @@ end ---@param inputPlayers table[] ---@param indexToPlayer fun(playerIndex: integer): PlayerInputData? ---@param transform fun(playerIndex: integer, playerIdData: MGIParsedPlayer?, playerInputData: PlayerInputData): table? ----@param options {pagifyPlayerNames: boolean?}? ---@return table, table -function MatchGroupInputUtil.parseParticipants(playerIds, inputPlayers, indexToPlayer, transform, options) +function MatchGroupInputUtil.parseParticipants(playerIds, inputPlayers, indexToPlayer, transform) local participants = {} local unattachedParticipants = {} local function parsePlayer(_, playerIndex) local playerInputData = indexToPlayer(playerIndex) or {} if playerInputData.name and not playerInputData.link then - playerInputData.link = MatchGroupInputUtil.makeLinkFromName(playerInputData.name, options) + playerInputData.link = MatchGroupInputUtil.makeLinkFromName(playerInputData.name) end local playerId = MatchGroupInputUtil.findPlayerId(playerIds, playerInputData.name, playerInputData.link) local toStoreData = transform(playerIndex, playerIds[playerId] or {}, playerInputData) diff --git a/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft.lua b/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft.lua index 8d05172091c..d4c08423044 100644 --- a/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft.lua +++ b/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft.lua @@ -25,7 +25,6 @@ local Streams = Lua.import('Module:Links/Stream') local OPPONENT_CONFIG = { resolveRedirect = true, pagifyTeamNames = true, - pagifyPlayerNames = true, } local TBD = 'TBD' local TBA = 'TBA' @@ -399,8 +398,7 @@ function MapFunctions.getTeamParticipants(mapInput, opponent, opponentIndex) flag = Flags.CountryName(playerIdData.flag), position = playerIndex, } - end, - OPPONENT_CONFIG + end ) Array.forEach(unattachedParticipants, function(participant) diff --git a/components/match2/wikis/ageofempires/match_group_input_custom.lua b/components/match2/wikis/ageofempires/match_group_input_custom.lua index a61557dd08c..a0f45d5ef41 100644 --- a/components/match2/wikis/ageofempires/match_group_input_custom.lua +++ b/components/match2/wikis/ageofempires/match_group_input_custom.lua @@ -27,7 +27,6 @@ local CustomMatchGroupInput = {} local OPPONENT_CONFIG = { resolveRedirect = true, pagifyTeamNames = true, - pagifyPlayerNames = false, } ---@param match table @@ -126,11 +125,9 @@ function CustomMatchGroupInput.readOpponent(match, opponentIndex, options) ) end - if options.pagifyPlayerNames then - Array.forEach(opponent.players or {}, function(player) - player.pageName = Page.pageifyLink(player.pageName) - end) - end + Array.forEach(opponent.players or {}, function(player) + player.pageName = Page.pageifyLink(player.pageName) + end) local record = MatchGroupInputUtil.mergeRecordWithOpponent(opponentInput, opponent, substitutions) @@ -325,8 +322,7 @@ function CustomMatchGroupInput._participants(opponentPlayers, map, opponentIndex pageName = playerIdData.name or playerInputData.name, flag = playerIdData.flag, } - end, - OPPONENT_CONFIG + end ) Array.forEach(unattachedParticipants, function(participant) table.insert(participants, participant) diff --git a/components/match2/wikis/arenaofvalor/match_group_input_custom.lua b/components/match2/wikis/arenaofvalor/match_group_input_custom.lua index 49983db46f0..8cdb0c69cae 100644 --- a/components/match2/wikis/arenaofvalor/match_group_input_custom.lua +++ b/components/match2/wikis/arenaofvalor/match_group_input_custom.lua @@ -26,7 +26,6 @@ local DUMMY_MAP = 'default' local OPPONENT_CONFIG = { resolveRedirect = true, pagifyTeamNames = false, - pagifyPlayerNames = true, maxNumPlayers = 5, } diff --git a/components/match2/wikis/battlerite/match_group_input_custom.lua b/components/match2/wikis/battlerite/match_group_input_custom.lua index 3d2aa7abe62..ea4c039ce2c 100644 --- a/components/match2/wikis/battlerite/match_group_input_custom.lua +++ b/components/match2/wikis/battlerite/match_group_input_custom.lua @@ -19,7 +19,6 @@ local DUMMY_MAP = 'default' -- Is set in Template:Map when |map= is empty. local OPPONENT_CONFIG = { resolveRedirect = true, pagifyTeamNames = true, - pagifyPlayerNames = true, maxNumPlayers = 15, } diff --git a/components/match2/wikis/clashofclans/match_group_input_custom.lua b/components/match2/wikis/clashofclans/match_group_input_custom.lua index 7515b0c17c8..d8c38229d9c 100644 --- a/components/match2/wikis/clashofclans/match_group_input_custom.lua +++ b/components/match2/wikis/clashofclans/match_group_input_custom.lua @@ -23,7 +23,6 @@ local DEFAULT_MODE = 'team' local OPPONENT_CONFIG = { resolveRedirect = true, pagifyTeamNames = true, - pagifyPlayerNames = true, } -- containers for process helper functions diff --git a/components/match2/wikis/crossfire/match_group_input_custom.lua b/components/match2/wikis/crossfire/match_group_input_custom.lua index 04ce90eb7cb..236b0d57978 100644 --- a/components/match2/wikis/crossfire/match_group_input_custom.lua +++ b/components/match2/wikis/crossfire/match_group_input_custom.lua @@ -22,7 +22,6 @@ local DEFAULT_MODE = 'team' local OPPONENT_CONFIG = { resolveRedirect = true, pagifyTeamNames = true, - pagifyPlayerNames = true, maxNumPlayers = 5 } diff --git a/components/match2/wikis/deadlock/match_group_input_custom.lua b/components/match2/wikis/deadlock/match_group_input_custom.lua index 7e4b88decde..c23a296bad1 100644 --- a/components/match2/wikis/deadlock/match_group_input_custom.lua +++ b/components/match2/wikis/deadlock/match_group_input_custom.lua @@ -19,7 +19,6 @@ local MatchGroupInputUtil = Lua.import('Module:MatchGroup/Input/Util') local OPPONENT_CONFIG = { resolveRedirect = true, pagifyTeamNames = true, - pagifyPlayerNames = true, maxNumPlayers = 10, } diff --git a/components/match2/wikis/dota2/match_group_input_custom.lua b/components/match2/wikis/dota2/match_group_input_custom.lua index 57d6a3010b4..adcfa4c2e73 100644 --- a/components/match2/wikis/dota2/match_group_input_custom.lua +++ b/components/match2/wikis/dota2/match_group_input_custom.lua @@ -23,7 +23,6 @@ local MatchGroupUtil = Lua.import('Module:MatchGroup/Util') local OPPONENT_CONFIG = { resolveRedirect = true, pagifyTeamNames = false, - pagifyPlayerNames = true, maxNumPlayers = 15, } local DEFAULT_MODE = 'team' @@ -280,8 +279,8 @@ function MapFunctions.getParticipants(MatchParser, map, opponents) local participant = participantList[playerIndex] participant.character = getCharacterName(participant.character) return participant - end, - OPPONENT_CONFIG) + end + ) Array.forEach(unattachedParticipants, function(participant) table.insert(participants, participant) end) diff --git a/components/match2/wikis/leagueoflegends/match_group_input_custom.lua b/components/match2/wikis/leagueoflegends/match_group_input_custom.lua index cd55e8afdb7..e86e79d7660 100644 --- a/components/match2/wikis/leagueoflegends/match_group_input_custom.lua +++ b/components/match2/wikis/leagueoflegends/match_group_input_custom.lua @@ -23,7 +23,6 @@ local MatchGroupUtil = Lua.import('Module:MatchGroup/Util') local OPPONENT_CONFIG = { resolveRedirect = true, pagifyTeamNames = false, - pagifyPlayerNames = true, maxNumPlayers = 15, } local DEFAULT_MODE = 'team' @@ -261,8 +260,8 @@ function MapFunctions.getParticipants(MatchParser, map, opponents) local participant = participantList[playerIndex] participant.character = getCharacterName(participant.character) return participant - end, - OPPONENT_CONFIG) + end + ) Array.forEach(unattachedParticipants, function(participant) table.insert(participants, participant) end) diff --git a/components/match2/wikis/rocketleague/match_group_input_custom.lua b/components/match2/wikis/rocketleague/match_group_input_custom.lua index f10ff5eeb2b..b30a73067f6 100644 --- a/components/match2/wikis/rocketleague/match_group_input_custom.lua +++ b/components/match2/wikis/rocketleague/match_group_input_custom.lua @@ -126,7 +126,7 @@ end CustomMatchGroupInput.processMap = FnUtil.identity ---@param opponent table ----@return table? +---@return table function CustomMatchGroupInput.getOpponentExtradata(opponent) if not Logic.isNumeric(opponent.score2) then return {} diff --git a/components/match2/wikis/smite/match_group_input_custom.lua b/components/match2/wikis/smite/match_group_input_custom.lua index db8b241609a..e3e7da0ae8a 100644 --- a/components/match2/wikis/smite/match_group_input_custom.lua +++ b/components/match2/wikis/smite/match_group_input_custom.lua @@ -25,7 +25,6 @@ local MAX_NUM_PLAYERS = 15 local OPPONENT_CONFIG = { resolveRedirect = true, pagifyTeamNames = true, - pagifyPlayerNames = true, maxNumPlayers = MAX_NUM_PLAYERS, } diff --git a/components/match2/wikis/warcraft/match_group_input_custom.lua b/components/match2/wikis/warcraft/match_group_input_custom.lua index 89e2f3a39d6..bea5f47f744 100644 --- a/components/match2/wikis/warcraft/match_group_input_custom.lua +++ b/components/match2/wikis/warcraft/match_group_input_custom.lua @@ -27,7 +27,6 @@ local Streams = Lua.import('Module:Links/Stream') local OPPONENT_CONFIG = { resolveRedirect = true, pagifyTeamNames = true, - pagifyPlayerNames = true, } local TBD = 'TBD' local NEUTRAL_HERO_FACTION = 'neutral' diff --git a/components/match2/wikis/wildrift/match_group_input_custom.lua b/components/match2/wikis/wildrift/match_group_input_custom.lua index c7fe36b8bc6..b6c43287b0d 100644 --- a/components/match2/wikis/wildrift/match_group_input_custom.lua +++ b/components/match2/wikis/wildrift/match_group_input_custom.lua @@ -26,7 +26,6 @@ local DUMMY_MAP = 'default' local OPPONENT_CONFIG = { resolveRedirect = true, pagifyTeamNames = false, - pagifyPlayerNames = true, } -- containers for process helper functions From 99d4c5401020fbb962ba31f91a143a4eea74c552 Mon Sep 17 00:00:00 2001 From: Rikard Blixt Date: Tue, 17 Sep 2024 13:12:09 +0200 Subject: [PATCH 2/9] refactor(widget): improve building part 3 - support children across the board (#4719) * refactor(widget): improve building part 3 - Children * unused imports * not needed anymore * adjust to the removal of double layed content in tablecell * readd this wrap * anno fixes * fixes, add a generic div widget * update golden --- components/prize_pool/commons/prize_pool.lua | 2 +- .../prize_pool/commons/prize_pool_base.lua | 27 +++++++------- components/widget/widget_all.lua | 3 ++ components/widget/widget_breakdown.lua | 10 +++--- components/widget/widget_cell.lua | 12 +++---- components/widget/widget_center.lua | 8 ++--- components/widget/widget_chronology.lua | 3 +- components/widget/widget_div.lua | 36 +++++++++++++++++++ components/widget/widget_header.lua | 3 +- components/widget/widget_highlights.lua | 8 ++--- components/widget/widget_links.lua | 3 +- components/widget/widget_table.lua | 15 ++++---- components/widget/widget_table_cell.lua | 27 +++----------- components/widget/widget_table_row.lua | 15 ++++---- components/widget/widget_title.lua | 8 ++--- spec/golden_masters/prize_pool.txt | 2 +- 16 files changed, 103 insertions(+), 79 deletions(-) create mode 100644 components/widget/widget_div.lua diff --git a/components/prize_pool/commons/prize_pool.lua b/components/prize_pool/commons/prize_pool.lua index 3753fbfa342..84bb6be8fdd 100644 --- a/components/prize_pool/commons/prize_pool.lua +++ b/components/prize_pool/commons/prize_pool.lua @@ -54,7 +54,7 @@ end ---@return WidgetTableCell function PrizePool:placeOrAwardCell(placement) local placeCell = TableCell{ - content = {{placement:getMedal() or '', NON_BREAKING_SPACE, placement:_displayPlace()}}, + content = {placement:getMedal() or '', NON_BREAKING_SPACE, placement:_displayPlace()}, css = {['font-weight'] = 'bolder'}, classes = {'prizepooltable-place'}, } diff --git a/components/prize_pool/commons/prize_pool_base.lua b/components/prize_pool/commons/prize_pool_base.lua index 7e930d1efe7..27719bc159a 100644 --- a/components/prize_pool/commons/prize_pool_base.lua +++ b/components/prize_pool/commons/prize_pool_base.lua @@ -31,6 +31,7 @@ local WidgetFactory = Lua.import('Module:Widget/Factory') local WidgetTable = Widgets.Table local TableRow = Widgets.TableRow local TableCell = Widgets.TableCell +local Div = Widgets.Div local pageVars = PageVariableNamespace('PrizePool') @@ -149,7 +150,7 @@ BasePrizePool.prizeTypes = { headerDisplay = function (data) local currencyText = Currency.display(BASE_CURRENCY) - return TableCell{content = {{currencyText}}} + return TableCell{content = {currencyText}} end, row = BASE_CURRENCY:lower() .. 'prize', @@ -188,7 +189,7 @@ BasePrizePool.prizeTypes = { } end, headerDisplay = function (data) - return TableCell{content = {{Currency.display(data.currency)}}} + return TableCell{content = {Currency.display(data.currency)}} end, row = 'localprize', @@ -226,7 +227,7 @@ BasePrizePool.prizeTypes = { return {title = 'Percentage'} end, headerDisplay = function (data) - return TableCell{content = {{data.title}}} + return TableCell{content = {data.title}} end, row = 'percentage', @@ -240,7 +241,7 @@ BasePrizePool.prizeTypes = { end, rowDisplay = function (headerData, data) if String.isNotEmpty(data) then - return TableCell{content = {{data .. '%'}}} + return TableCell{content = {data .. '%'}} end end, }, @@ -294,7 +295,7 @@ BasePrizePool.prizeTypes = { table.insert(content, '[[' .. headerData.link .. ']]') end - return TableCell{content = {content}} + return TableCell{children = {Div{children = content}}} end, mergeDisplayColumns = true, @@ -339,7 +340,7 @@ BasePrizePool.prizeTypes = { table.insert(headerDisplay, text) end - return TableCell{content = {headerDisplay}} + return TableCell{content = {table.concat(headerDisplay)}} end, row = 'points', @@ -348,7 +349,7 @@ BasePrizePool.prizeTypes = { end, rowDisplay = function (headerData, data) if data > 0 then - return TableCell{content = {{LANG:formatNum(data)}}} + return TableCell{content = {LANG:formatNum(data)}} end end, }, @@ -360,7 +361,7 @@ BasePrizePool.prizeTypes = { return {title = input} end, headerDisplay = function (data) - return TableCell{content = {{data.title}}} + return TableCell{content = {data.title}} end, row = 'freetext', @@ -369,7 +370,7 @@ BasePrizePool.prizeTypes = { end, rowDisplay = function (headerData, data) if String.isNotEmpty(data) then - return TableCell{content = {{data}}} + return TableCell{content = {data}} end end, } @@ -665,11 +666,11 @@ function BasePrizePool:_buildRows() local lastCellOfType = previousOfPrizeType[prize.type] if lastCellOfType and prizeTypeData.mergeDisplayColumns then - if Table.isNotEmpty(lastCellOfType.content) and Table.isNotEmpty(cell.content) then + if Table.isNotEmpty(lastCellOfType.children) and Table.isNotEmpty(cell.children) then lastCellOfType:addContent(tostring(mw.html.create('hr'):css('width', '100%'))) end - Array.extendWith(lastCellOfType.content, cell.content) + Array.extendWith(lastCellOfType.children, cell.children) lastCellOfType.css['flex-direction'] = 'column' return nil @@ -683,11 +684,11 @@ function BasePrizePool:_buildRows() local lastInColumn = previousOpponent[columnIndex] ---@cast prizeCell -nil - if Table.isEmpty(prizeCell.content) then + if Table.isEmpty(prizeCell.children) then prizeCell = BasePrizePool._emptyCell() end - if lastInColumn and Table.deepEquals(lastInColumn.content, prizeCell.content) then + if lastInColumn and Table.deepEquals(lastInColumn.children, prizeCell.children) then lastInColumn.rowSpan = (lastInColumn.rowSpan or 1) + 1 else previousOpponent[columnIndex] = prizeCell diff --git a/components/widget/widget_all.lua b/components/widget/widget_all.lua index 15f1a9a46c5..08ec610fa2b 100644 --- a/components/widget/widget_all.lua +++ b/components/widget/widget_all.lua @@ -24,6 +24,9 @@ Widgets.Highlights = Lua.import('Module:Widget/Highlights') Widgets.Links = Lua.import('Module:Widget/Links') Widgets.Title = Lua.import('Module:Widget/Title') +--- Generic Widgets +Widgets.Div = Lua.import('Module:Widget/Div') + --- Table Widgets (div-table) (might be removed) Widgets.Table = Lua.import('Module:Widget/Table') Widgets.TableRow = Lua.import('Module:Widget/Table/Row') diff --git a/components/widget/widget_breakdown.lua b/components/widget/widget_breakdown.lua index 019ed3baff2..34333140058 100644 --- a/components/widget/widget_breakdown.lua +++ b/components/widget/widget_breakdown.lua @@ -12,23 +12,23 @@ local Lua = require('Module:Lua') local Widget = Lua.import('Module:Widget') ---@class BreakdownWidget: Widget ----@operator call({content:(string|number)[],classes:string[],contentClasses:table}):BreakdownWidget ----@field contents (string|number)[] +---@operator call({children:(string|number)[],classes:string[],contentClasses:table}):BreakdownWidget ---@field classes string[] ---@field contentClasses table --can have gaps in the outer table local Breakdown = Class.new( Widget, function(self, input) - self.contents = input.content + self.children = input.children or input.content self.classes = input.classes self.contentClasses = input.contentClasses or {} end ) ---@param injector WidgetInjector? +---@param children string[] ---@return string? -function Breakdown:make(injector) - return Breakdown:_breakdown(self.contents, self.classes, self.contentClasses) +function Breakdown:make(injector, children) + return Breakdown:_breakdown(children, self.classes, self.contentClasses) end ---@param contents (string|number)[] diff --git a/components/widget/widget_cell.lua b/components/widget/widget_cell.lua index f598100bf79..df285ed61bc 100644 --- a/components/widget/widget_cell.lua +++ b/components/widget/widget_cell.lua @@ -19,13 +19,12 @@ local Widget = Lua.import('Module:Widget') ---@class CellWidget: Widget ---@operator call({name:string|number,content:(string|number)[],classes:string[]?,options:CellWidgetOptions}):CellWidget ---@field name string|number ----@field content (string|number)[] ---@field options CellWidgetOptions ---@field classes string[]? local Cell = Class.new(Widget, function(self, input) self.name = self:assertExistsAndCopy(input.name) - self.content = input.content + self.children = input.children or input.content or {} self.options = input.options or {} self.classes = input.classes @@ -60,7 +59,7 @@ function Cell:_class(...) return self end ----@param ... string|number +---@param ... string ---@return CellWidget function Cell:_content(...) local firstItem = select(1, ...) @@ -75,7 +74,7 @@ function Cell:_content(...) if i > 1 then self.contentDiv:wikitext('
') end - local item = select(i, ...) + local item = select(i, ...) ---@type string? if item == nil then break end @@ -90,11 +89,12 @@ function Cell:_content(...) end ---@param injector WidgetInjector? +---@param children string[] ---@return string? -function Cell:make(injector) +function Cell:make(injector, children) self:_new(self.name) self:_class(unpack(self.classes or {})) - self:_content(unpack(self.content)) + self:_content(unpack(children)) if self.contentDiv == nil then return diff --git a/components/widget/widget_center.lua b/components/widget/widget_center.lua index 42551a99ffa..1a96c26d6ad 100644 --- a/components/widget/widget_center.lua +++ b/components/widget/widget_center.lua @@ -14,20 +14,20 @@ local Widget = Lua.import('Module:Widget') ---@class CentereWidget: Widget ---@operator call({content: (string|number)[], classes: string[]}): CentereWidget ----@field content (string|number)[] ---@field classes string[] local Center = Class.new( Widget, function(self, input) - self.content = input.content + self.children = input.children or input.content or {} self.classes = input.classes end ) ---@param injector WidgetInjector? +---@param children string[] ---@return string? -function Center:make(injector) - return Center:_create(self.content, self.classes) +function Center:make(injector, children) + return Center:_create(children, self.classes) end ---@param content (string|number)[] diff --git a/components/widget/widget_chronology.lua b/components/widget/widget_chronology.lua index e7236007fd1..5866af72275 100644 --- a/components/widget/widget_chronology.lua +++ b/components/widget/widget_chronology.lua @@ -23,8 +23,9 @@ local Chronology = Class.new( ) ---@param injector WidgetInjector? +---@param children string[] ---@return string? -function Chronology:make(injector) +function Chronology:make(injector, children) return Chronology:_chronology(self.links) end diff --git a/components/widget/widget_div.lua b/components/widget/widget_div.lua new file mode 100644 index 00000000000..5d9e12790a7 --- /dev/null +++ b/components/widget/widget_div.lua @@ -0,0 +1,36 @@ +--- +-- @Liquipedia +-- wiki=commons +-- page=Module:Widget/Div +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Array = require('Module:Array') +local Class = require('Module:Class') +local FnUtil = require('Module:FnUtil') +local Lua = require('Module:Lua') + +local Widget = Lua.import('Module:Widget') + +---@class GenericDivWidget: Widget +---@operator call({children: (Widget|string)[]?}?): GenericDivWidget +---@field classes string[] +local Div = Class.new( + Widget, + function(self, input) + self.classes = input.classes or {} + end +) + +---@param injector WidgetInjector? +---@param children string[] +---@return string? +function Div:make(injector, children) + local div = mw.html.create('div') + Array.forEach(self.classes, FnUtil.curry(div.addClass, div)) + Array.forEach(children, FnUtil.curry(div.node, div)) + return tostring(div) +end + +return Div diff --git a/components/widget/widget_header.lua b/components/widget/widget_header.lua index 4a0dfb73432..276edff36dd 100644 --- a/components/widget/widget_header.lua +++ b/components/widget/widget_header.lua @@ -36,8 +36,9 @@ local Header = Class.new( ) ---@param injector WidgetInjector? +---@param children string[] ---@return string -function Header:make(injector) +function Header:make(injector, children) local header = { Header:_name(self.name), Header:_image( diff --git a/components/widget/widget_highlights.lua b/components/widget/widget_highlights.lua index 152b63e2f58..14f517888d6 100644 --- a/components/widget/widget_highlights.lua +++ b/components/widget/widget_highlights.lua @@ -14,18 +14,18 @@ local Widget = Lua.import('Module:Widget') ---@class HighlightsWidget: Widget ---@operator call({content: (string|number)[]?}):HighlightsWidget ----@field list (string|number)[]? local Highlights = Class.new( Widget, function(self, input) - self.list = input.content + self.children = input.children or input.content or {} end ) ---@param injector WidgetInjector? +---@param children string[] ---@return string? -function Highlights:make(injector) - return Highlights:_highlights(self.list) +function Highlights:make(injector, children) + return Highlights:_highlights(children) end ---@param list (string|number)[]? diff --git a/components/widget/widget_links.lua b/components/widget/widget_links.lua index 80cb8f14a93..927b953e40e 100644 --- a/components/widget/widget_links.lua +++ b/components/widget/widget_links.lua @@ -28,8 +28,9 @@ local Links = Class.new( local PRIORITY_GROUPS = Lua.import('Module:Links/PriorityGroups', {loadData = true}) ---@param injector WidgetInjector? +---@param children string[] ---@return string? -function Links:make(injector) +function Links:make(injector, children) local infoboxLinks = mw.html.create('div') infoboxLinks :addClass('infobox-center') :addClass('infobox-icons') diff --git a/components/widget/widget_table.lua b/components/widget/widget_table.lua index 832d2b50b2a..ebd5085a13b 100644 --- a/components/widget/widget_table.lua +++ b/components/widget/widget_table.lua @@ -11,7 +11,6 @@ local Class = require('Module:Class') local Lua = require('Module:Lua') local Widget = Lua.import('Module:Widget') -local WidgetFactory = Lua.import('Module:Widget/Factory') ---@class WidgetTableInput ---@field rows WidgetTableRow[]? @@ -21,14 +20,13 @@ local WidgetFactory = Lua.import('Module:Widget/Factory') ---@class WidgetTable:Widget ---@operator call(WidgetTableInput):WidgetTable ----@field rows WidgetTableRow[] ---@field classes string[] ---@field css {[string]: string|number|nil} ---@field columns integer? local Table = Class.new( Widget, function(self, input) - self.rows = input.rows or {} + self.children = input.children or input.rows or {} self.classes = input.classes or {} self.css = input.css or {} self.columns = input.columns @@ -38,7 +36,7 @@ local Table = Class.new( ---@param row WidgetTableRow? ---@return self function Table:addRow(row) - table.insert(self.rows, row) + table.insert(self.children, row) return self end @@ -50,8 +48,9 @@ function Table:addClass(class) end ---@param injector WidgetInjector? +---@param children string[] ---@return string? -function Table:make(injector) +function Table:make(injector, children) local displayTable = mw.html.create('div'):addClass('csstable-widget') displayTable:css{ ['grid-template-columns'] = 'repeat(' .. (self.columns or self:_getMaxCells()) .. ', auto)', @@ -63,8 +62,8 @@ function Table:make(injector) displayTable:css(self.css) - for _, row in ipairs(self.rows) do - displayTable:node(WidgetFactory.work(row, injector)) + for _, row in ipairs(children) do + displayTable:node(row) end return tostring(displayTable) @@ -75,7 +74,7 @@ function Table:_getMaxCells() local getNumberCells = function(row) return row:getCellCount() end - return Array.reduce(Array.map(self.rows, getNumberCells), math.max) + return Array.reduce(Array.map(self.children, getNumberCells), math.max) end return Table diff --git a/components/widget/widget_table_cell.lua b/components/widget/widget_table_cell.lua index dcec7ccbc9a..68b1015c129 100644 --- a/components/widget/widget_table_cell.lua +++ b/components/widget/widget_table_cell.lua @@ -20,7 +20,6 @@ local Widget = Lua.import('Module:Widget') ---@class WidgetTableCell:Widget ---@operator call(WidgetCellInput): WidgetTableCell ----@field content (string|number|table|Html)[] ---@field classes string[] ---@field css {[string]: string|number} ---@field rowSpan integer? @@ -28,7 +27,7 @@ local Widget = Lua.import('Module:Widget') local TableCell = Class.new( Widget, function(self, input) - self.content = input.content or {} + self.children = input.children or input.content or {} self.classes = input.classes or {} self.css = input.css or {} end @@ -37,7 +36,7 @@ local TableCell = Class.new( ---@param text string|number|table|nil ---@return self function TableCell:addContent(text) - table.insert(self.content, text) + table.insert(self.children, text) return self end @@ -57,8 +56,9 @@ function TableCell:addCss(key, value) end ---@param injector WidgetInjector? +---@param children string[] ---@return string? -function TableCell:make(injector) +function TableCell:make(injector, children) local cell = mw.html.create('div'):addClass('csstable-widget-cell') cell:css{ ['grid-row'] = self.rowSpan and 'span ' .. self.rowSpan or nil, @@ -71,26 +71,9 @@ function TableCell:make(injector) cell:css(self.css) - cell:node(self:_concatContent()) + Array.forEach(children, FnUtil.curry(cell.node, cell)) return tostring(cell) end ----@return string -function TableCell:_concatContent() - return table.concat(Array.map(self.content, function (content) - if type(content) ~= 'table' then - return content - end - - if not Array.isArray(content) then - return tostring(content) - end - - local wrapper = mw.html.create('div') - Array.forEach(content, FnUtil.curry(wrapper.node, wrapper)) - return tostring(wrapper) - end)) -end - return TableCell diff --git a/components/widget/widget_table_row.lua b/components/widget/widget_table_row.lua index fc194699d53..9004c016da7 100644 --- a/components/widget/widget_table_row.lua +++ b/components/widget/widget_table_row.lua @@ -10,7 +10,6 @@ local Class = require('Module:Class') local Lua = require('Module:Lua') local Widget = Lua.import('Module:Widget') -local WidgetFactory = Lua.import('Module:Widget/Factory') ---@class WidgetTableRowInput ---@field cells Widget[]? @@ -19,13 +18,12 @@ local WidgetFactory = Lua.import('Module:Widget/Factory') ---@class WidgetTableRow:Widget ---@operator call(WidgetTableRowInput): WidgetTableRow ----@field cells Widget[] ---@field classes string[] ---@field css {[string]: string|number|nil} local TableRow = Class.new( Widget, function(self, input) - self.cells = input.cells or {} + self.children = input.children or input.cells or {} self.classes = input.classes or {} self.css = input.css or {} end @@ -34,7 +32,7 @@ local TableRow = Class.new( ---@param cell Widget? ---@return self function TableRow:addCell(cell) - table.insert(self.cells, cell) + table.insert(self.children, cell) return self end @@ -55,12 +53,13 @@ end ---@return integer function TableRow:getCellCount() - return #self.cells + return #self.children end ---@param injector WidgetInjector? +---@param children string[] ---@return string? -function TableRow:make(injector) +function TableRow:make(injector, children) local row = mw.html.create('div'):addClass('csstable-widget-row') for _, class in ipairs(self.classes) do @@ -69,8 +68,8 @@ function TableRow:make(injector) row:css(self.css) - for _, cell in ipairs(self.cells) do - row:node(WidgetFactory.work(cell, injector)) + for _, cell in ipairs(children) do + row:node(cell) end return tostring(row) diff --git a/components/widget/widget_title.lua b/components/widget/widget_title.lua index 62b4c9320cd..c388d60cff1 100644 --- a/components/widget/widget_title.lua +++ b/components/widget/widget_title.lua @@ -13,18 +13,18 @@ local Widget = Lua.import('Module:Widget') ---@class TitleWidget: Widget ---@operator call({name: string|number|nil}): TitleWidget ----@field content string|number|nil local Title = Class.new( Widget, function(self, input) - self.content = self:assertExistsAndCopy(input.name) + self.children = {self:assertExistsAndCopy(input.children or input.name)} end ) ---@param injector WidgetInjector? +---@param children string[] ---@return string? -function Title:make(injector) - return Title:_create(self.content) +function Title:make(injector, children) + return Title:_create(children[1]) end ---@param infoDescription string|number|nil diff --git a/spec/golden_masters/prize_pool.txt b/spec/golden_masters/prize_pool.txt index 11efc41385f..129a62a579a 100644 --- a/spec/golden_masters/prize_pool.txt +++ b/spec/golden_masters/prize_pool.txt @@ -1 +1 @@ -
$TBA USD are spread among the participants as seen below:
\ No newline at end of file +
$TBA USD are spread among the participants as seen below:
\ No newline at end of file From 45b692cb7424947c7df6788d340a54a9cc9518cb Mon Sep 17 00:00:00 2001 From: Rikard Blixt Date: Tue, 17 Sep 2024 14:50:18 +0200 Subject: [PATCH 3/9] refactor(gametable): simplify not played (#4753) refactor(gametable): simplify check --- components/game_table/commons/game_table.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/game_table/commons/game_table.lua b/components/game_table/commons/game_table.lua index 82446e9741a..4728615a612 100644 --- a/components/game_table/commons/game_table.lua +++ b/components/game_table/commons/game_table.lua @@ -11,12 +11,11 @@ local Class = require('Module:Class') local Game = require('Module:Game') local Logic = require('Module:Logic') local Lua = require('Module:Lua') -local Table = require('Module:Table') local VodLink = require('Module:VodLink') local MatchTable = Lua.import('Module:MatchTable') -local NP_STATUSES = {'skip', 'np', 'canceled', 'cancelled'} +local NOT_PLAYED = 'np' local SCORE_CONCAT = ' : ' ---@class GameTableMatch: MatchTableMatch @@ -32,7 +31,7 @@ end) ---@return match2game? function GameTable:gameFromRecord(game) if self.countGames == self.config.limit then return nil end - if Table.includes(NP_STATUSES, game.resulttype) then + if game.resulttype == NOT_PLAYED then return nil end From cbebb2222a08c84b7936cd549ee858a26bdbe651 Mon Sep 17 00:00:00 2001 From: Rikard Blixt Date: Tue, 17 Sep 2024 15:00:09 +0200 Subject: [PATCH 4/9] refactor(widget): improve building part 5 - remove widget factory (#4742) * refactor(widget): improve building part 5 - remove widget factory * remove imports * these changes were for part 6 --- components/infobox/commons/infobox.lua | 5 +-- .../prize_pool/commons/prize_pool_base.lua | 3 +- components/squad/commons/squad.lua | 3 +- components/widget/widget.lua | 17 ++++------ components/widget/widget_factory.lua | 34 ------------------- 5 files changed, 10 insertions(+), 52 deletions(-) delete mode 100644 components/widget/widget_factory.lua diff --git a/components/infobox/commons/infobox.lua b/components/infobox/commons/infobox.lua index 95bfe45fc15..7b22fb4e424 100644 --- a/components/infobox/commons/infobox.lua +++ b/components/infobox/commons/infobox.lua @@ -9,12 +9,9 @@ local Array = require('Module:Array') local Class = require('Module:Class') local Logic = require('Module:Logic') -local Lua = require('Module:Lua') local Variables = require('Module:Variables') local WarningBox = require('Module:WarningBox') -local WidgetFactory = Lua.import('Module:Widget/Factory') - ---@class Infobox ---@field frame Frame? ---@field root Html? @@ -86,7 +83,7 @@ function Infobox:build(widgets) error('Infobox:build can only accept Widgets') end - self.content:node(WidgetFactory.work(widget, self.injector)) + self.content:node(widget:tryMake(self.injector)) end self.root:node(self.content) diff --git a/components/prize_pool/commons/prize_pool_base.lua b/components/prize_pool/commons/prize_pool_base.lua index 27719bc159a..5156ade16d4 100644 --- a/components/prize_pool/commons/prize_pool_base.lua +++ b/components/prize_pool/commons/prize_pool_base.lua @@ -27,7 +27,6 @@ local Opponent = OpponentLibraries.Opponent local OpponentDisplay = OpponentLibraries.OpponentDisplay local Widgets = require('Module:Widget/All') -local WidgetFactory = Lua.import('Module:Widget/Factory') local WidgetTable = Widgets.Table local TableRow = Widgets.TableRow local TableCell = Widgets.TableCell @@ -609,7 +608,7 @@ function BasePrizePool:_buildTable(isAward) end local tableNode = mw.html.create('div'):css('overflow-x', 'auto') - tableNode:node(WidgetFactory.work(tbl, self._widgetInjector)) + tableNode:node(tbl:tryMake(self._widgetInjector)) return tableNode end diff --git a/components/squad/commons/squad.lua b/components/squad/commons/squad.lua index a52215bc08d..307322b7805 100644 --- a/components/squad/commons/squad.lua +++ b/components/squad/commons/squad.lua @@ -15,7 +15,6 @@ local String = require('Module:StringUtils') local SquadUtils = Lua.import('Module:Squad/Utils') local Widget = Lua.import('Module:Widget/All') -local WidgetFactory = Lua.import('Module:Widget/Factory') ---@class Squad ---@operator call:Squad @@ -108,7 +107,7 @@ function Squad:create() classes = {'wikitable-striped', 'roster-card'}, children = self.rows, } - return WidgetFactory.work(dataTable, self.injector) + return dataTable:tryMake(self.injector) or '' end return Squad diff --git a/components/widget/widget.lua b/components/widget/widget.lua index cc694ff91ee..7919829192b 100644 --- a/components/widget/widget.lua +++ b/components/widget/widget.lua @@ -9,11 +9,8 @@ local Array = require('Module:Array') local Class = require('Module:Class') local ErrorDisplay = require('Module:Error/Display') local Logic = require('Module:Logic') -local Lua = require('Module:Lua') local String = require('Module:StringUtils') -local WidgetFactory = Lua.import('Module:Widget/Factory') - ---@class Widget: BaseClass ---@operator call({children: Widget[]?}?): Widget ---@field children (Widget|Html|string|number)[] @@ -31,18 +28,18 @@ end ---@param injector WidgetInjector? ---@param children string[] ----@return Widget[]|string|nil +---@return string|nil function Widget:make(injector, children) error('A Widget must override the make() function!') end ---@param injector WidgetInjector? ----@return Widget[]|string|nil +---@return string|nil function Widget:tryMake(injector) local processedChildren = self:tryChildren(injector) return Logic.tryOrElseLog( function() return self:make(injector, processedChildren) end, - function(error) return {ErrorDisplay.InlineError(error)} end, + function(error) return tostring(ErrorDisplay.InlineError(error)) end, function(error) error.header = 'Error occured in widget: (caught by Widget:tryMake)' return error @@ -57,12 +54,12 @@ function Widget:tryChildren(injector) if self.makeChildren then children = self:makeChildren(injector) or {} end - return Array.flatMap(children, function(child) + return Array.map(children, function(child) if type(child) == 'table' and type(child['is_a']) == 'function' and child:is_a(Widget) then ---@cast child Widget return Logic.tryOrElseLog( - function() return WidgetFactory.work(child, injector) end, - function(error) return {ErrorDisplay.InlineError(error)} end, + function() return child:tryMake(injector) end, + function(error) return tostring(ErrorDisplay.InlineError(error)) end, function(error) error.header = 'Error occured in widget: (caught by Widget:tryChildren)' return error @@ -70,7 +67,7 @@ function Widget:tryChildren(injector) ) end ---@cast child -Widget - return {tostring(child)} + return tostring(child) end) end diff --git a/components/widget/widget_factory.lua b/components/widget/widget_factory.lua deleted file mode 100644 index bf5074b64dc..00000000000 --- a/components/widget/widget_factory.lua +++ /dev/null @@ -1,34 +0,0 @@ ---- --- @Liquipedia --- wiki=commons --- page=Module:Widget/Factory --- --- Please see https://github.com/Liquipedia/Lua-Modules to contribute --- - -local Array = require('Module:Array') -local Class = require('Module:Class') - ----@class WidgetFactory -local WidgetFactory = Class.new() - ----@param widget Widget ----@param injector WidgetInjector? ----@return string -function WidgetFactory.work(widget, injector) - local children = widget:tryMake(injector) - - if not children then - return '' - end - - if type(children) == 'string' then - return children - end - - return table.concat(Array.map(children, function(child) - return WidgetFactory.work(child, injector) - end)) -end - -return WidgetFactory From 6f00fa56d78c9a7c3b40c4b55ff9530c2a858321 Mon Sep 17 00:00:00 2001 From: Rikard Blixt Date: Tue, 17 Sep 2024 15:52:40 +0200 Subject: [PATCH 5/9] refactor(widget): improve building part 6 - remove injector from make() (#4743) * refactor(widget): improve building part 6 - remove injector from make() * update widget also * needed for this one too --- components/widget/widget.lua | 5 ++--- components/widget/widget_breakdown.lua | 3 +-- components/widget/widget_builder.lua | 3 +-- components/widget/widget_cell.lua | 3 +-- components/widget/widget_center.lua | 3 +-- components/widget/widget_chronology.lua | 3 +-- components/widget/widget_customizable.lua | 3 +-- components/widget/widget_div.lua | 3 +-- components/widget/widget_header.lua | 3 +-- components/widget/widget_highlights.lua | 3 +-- components/widget/widget_links.lua | 3 +-- components/widget/widget_table.lua | 3 +-- components/widget/widget_table_cell.lua | 3 +-- components/widget/widget_table_cell_new.lua | 3 +-- components/widget/widget_table_new.lua | 3 +-- components/widget/widget_table_row.lua | 3 +-- components/widget/widget_table_row_new.lua | 3 +-- components/widget/widget_title.lua | 3 +-- 18 files changed, 19 insertions(+), 37 deletions(-) diff --git a/components/widget/widget.lua b/components/widget/widget.lua index 7919829192b..28bc9cf88d6 100644 --- a/components/widget/widget.lua +++ b/components/widget/widget.lua @@ -26,10 +26,9 @@ function Widget:assertExistsAndCopy(value) return assert(String.nilIfEmpty(value), 'Tried to set a nil value to a mandatory property') end ----@param injector WidgetInjector? ---@param children string[] ---@return string|nil -function Widget:make(injector, children) +function Widget:make(children) error('A Widget must override the make() function!') end @@ -38,7 +37,7 @@ end function Widget:tryMake(injector) local processedChildren = self:tryChildren(injector) return Logic.tryOrElseLog( - function() return self:make(injector, processedChildren) end, + function() return self:make(processedChildren) end, function(error) return tostring(ErrorDisplay.InlineError(error)) end, function(error) error.header = 'Error occured in widget: (caught by Widget:tryMake)' diff --git a/components/widget/widget_breakdown.lua b/components/widget/widget_breakdown.lua index 34333140058..9358c00c33a 100644 --- a/components/widget/widget_breakdown.lua +++ b/components/widget/widget_breakdown.lua @@ -24,10 +24,9 @@ local Breakdown = Class.new( end ) ----@param injector WidgetInjector? ---@param children string[] ---@return string? -function Breakdown:make(injector, children) +function Breakdown:make(children) return Breakdown:_breakdown(children, self.classes, self.contentClasses) end diff --git a/components/widget/widget_builder.lua b/components/widget/widget_builder.lua index a89aaa6335b..09637f7715a 100644 --- a/components/widget/widget_builder.lua +++ b/components/widget/widget_builder.lua @@ -21,10 +21,9 @@ local Builder = Class.new( end ) ----@param injector WidgetInjector? ---@param children string[] ---@return string -function Builder:make(injector, children) +function Builder:make(children) return table.concat(children) end diff --git a/components/widget/widget_cell.lua b/components/widget/widget_cell.lua index df285ed61bc..db78f3c24fa 100644 --- a/components/widget/widget_cell.lua +++ b/components/widget/widget_cell.lua @@ -88,10 +88,9 @@ function Cell:_content(...) return self end ----@param injector WidgetInjector? ---@param children string[] ---@return string? -function Cell:make(injector, children) +function Cell:make(children) self:_new(self.name) self:_class(unpack(self.classes or {})) self:_content(unpack(children)) diff --git a/components/widget/widget_center.lua b/components/widget/widget_center.lua index 1a96c26d6ad..d6cee7a9588 100644 --- a/components/widget/widget_center.lua +++ b/components/widget/widget_center.lua @@ -23,10 +23,9 @@ local Center = Class.new( end ) ----@param injector WidgetInjector? ---@param children string[] ---@return string? -function Center:make(injector, children) +function Center:make(children) return Center:_create(children, self.classes) end diff --git a/components/widget/widget_chronology.lua b/components/widget/widget_chronology.lua index 5866af72275..e7ac1698db4 100644 --- a/components/widget/widget_chronology.lua +++ b/components/widget/widget_chronology.lua @@ -22,10 +22,9 @@ local Chronology = Class.new( end ) ----@param injector WidgetInjector? ---@param children string[] ---@return string? -function Chronology:make(injector, children) +function Chronology:make(children) return Chronology:_chronology(self.links) end diff --git a/components/widget/widget_customizable.lua b/components/widget/widget_customizable.lua index e2ba33ed4ab..817a6559b30 100644 --- a/components/widget/widget_customizable.lua +++ b/components/widget/widget_customizable.lua @@ -21,10 +21,9 @@ local Customizable = Class.new( end ) ----@param injector WidgetInjector? ---@param children string[] ---@return string -function Customizable:make(injector, children) +function Customizable:make(children) return table.concat(children) end diff --git a/components/widget/widget_div.lua b/components/widget/widget_div.lua index 5d9e12790a7..8cd0dfa8095 100644 --- a/components/widget/widget_div.lua +++ b/components/widget/widget_div.lua @@ -23,10 +23,9 @@ local Div = Class.new( end ) ----@param injector WidgetInjector? ---@param children string[] ---@return string? -function Div:make(injector, children) +function Div:make(children) local div = mw.html.create('div') Array.forEach(self.classes, FnUtil.curry(div.addClass, div)) Array.forEach(children, FnUtil.curry(div.node, div)) diff --git a/components/widget/widget_header.lua b/components/widget/widget_header.lua index 276edff36dd..ba9e3473cb2 100644 --- a/components/widget/widget_header.lua +++ b/components/widget/widget_header.lua @@ -35,10 +35,9 @@ local Header = Class.new( end ) ----@param injector WidgetInjector? ---@param children string[] ---@return string -function Header:make(injector, children) +function Header:make(children) local header = { Header:_name(self.name), Header:_image( diff --git a/components/widget/widget_highlights.lua b/components/widget/widget_highlights.lua index 14f517888d6..891db587b1a 100644 --- a/components/widget/widget_highlights.lua +++ b/components/widget/widget_highlights.lua @@ -21,10 +21,9 @@ local Highlights = Class.new( end ) ----@param injector WidgetInjector? ---@param children string[] ---@return string? -function Highlights:make(injector, children) +function Highlights:make(children) return Highlights:_highlights(children) end diff --git a/components/widget/widget_links.lua b/components/widget/widget_links.lua index 927b953e40e..e9fb284fe32 100644 --- a/components/widget/widget_links.lua +++ b/components/widget/widget_links.lua @@ -27,10 +27,9 @@ local Links = Class.new( local PRIORITY_GROUPS = Lua.import('Module:Links/PriorityGroups', {loadData = true}) ----@param injector WidgetInjector? ---@param children string[] ---@return string? -function Links:make(injector, children) +function Links:make(children) local infoboxLinks = mw.html.create('div') infoboxLinks :addClass('infobox-center') :addClass('infobox-icons') diff --git a/components/widget/widget_table.lua b/components/widget/widget_table.lua index ebd5085a13b..b95ffeab429 100644 --- a/components/widget/widget_table.lua +++ b/components/widget/widget_table.lua @@ -47,10 +47,9 @@ function Table:addClass(class) return self end ----@param injector WidgetInjector? ---@param children string[] ---@return string? -function Table:make(injector, children) +function Table:make(children) local displayTable = mw.html.create('div'):addClass('csstable-widget') displayTable:css{ ['grid-template-columns'] = 'repeat(' .. (self.columns or self:_getMaxCells()) .. ', auto)', diff --git a/components/widget/widget_table_cell.lua b/components/widget/widget_table_cell.lua index 68b1015c129..97dbb885225 100644 --- a/components/widget/widget_table_cell.lua +++ b/components/widget/widget_table_cell.lua @@ -55,10 +55,9 @@ function TableCell:addCss(key, value) return self end ----@param injector WidgetInjector? ---@param children string[] ---@return string? -function TableCell:make(injector, children) +function TableCell:make(children) local cell = mw.html.create('div'):addClass('csstable-widget-cell') cell:css{ ['grid-row'] = self.rowSpan and 'span ' .. self.rowSpan or nil, diff --git a/components/widget/widget_table_cell_new.lua b/components/widget/widget_table_cell_new.lua index 66d8b836c0f..da8c61a6508 100644 --- a/components/widget/widget_table_cell_new.lua +++ b/components/widget/widget_table_cell_new.lua @@ -41,10 +41,9 @@ local TableCell = Class.new( end ) ----@param injector WidgetInjector? ---@param children string[] ---@return string? -function TableCell:make(injector, children) +function TableCell:make(children) local cell = mw.html.create(self.isHeader and 'th' or 'td') cell:attr('colspan', self.colSpan) cell:attr('rowspan', self.rowSpan) diff --git a/components/widget/widget_table_new.lua b/components/widget/widget_table_new.lua index 082cf2f53cd..1a6e95db92d 100644 --- a/components/widget/widget_table_new.lua +++ b/components/widget/widget_table_new.lua @@ -30,10 +30,9 @@ local Table = Class.new( end ) ----@param injector WidgetInjector? ---@param children string[] ---@return string? -function Table:make(injector, children) +function Table:make(children) local wrapper = mw.html.create('div'):addClass('table-responsive') local output = mw.html.create('table'):addClass('wikitable') diff --git a/components/widget/widget_table_row.lua b/components/widget/widget_table_row.lua index 9004c016da7..f20e8bdde13 100644 --- a/components/widget/widget_table_row.lua +++ b/components/widget/widget_table_row.lua @@ -56,10 +56,9 @@ function TableRow:getCellCount() return #self.children end ----@param injector WidgetInjector? ---@param children string[] ---@return string? -function TableRow:make(injector, children) +function TableRow:make(children) local row = mw.html.create('div'):addClass('csstable-widget-row') for _, class in ipairs(self.classes) do diff --git a/components/widget/widget_table_row_new.lua b/components/widget/widget_table_row_new.lua index 596fdb02482..e284e14559b 100644 --- a/components/widget/widget_table_row_new.lua +++ b/components/widget/widget_table_row_new.lua @@ -30,10 +30,9 @@ local TableRow = Class.new( end ) ----@param injector WidgetInjector? ---@param children string[] ---@return string? -function TableRow:make(injector, children) +function TableRow:make(children) local row = mw.html.create('tr') Array.forEach(self.classes, FnUtil.curry(row.addClass, row)) diff --git a/components/widget/widget_title.lua b/components/widget/widget_title.lua index c388d60cff1..69f37d22826 100644 --- a/components/widget/widget_title.lua +++ b/components/widget/widget_title.lua @@ -20,10 +20,9 @@ local Title = Class.new( end ) ----@param injector WidgetInjector? ---@param children string[] ---@return string? -function Title:make(injector, children) +function Title:make(children) return Title:_create(children[1]) end From 33af6743f4fbd03a24b34c68131c46ce086b7ce1 Mon Sep 17 00:00:00 2001 From: hjpalpha <75081997+hjpalpha@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:56:36 +0200 Subject: [PATCH 6/9] refactor(match2): stormgate MGI (#4651) * ms adjusts * player ext adjusts * input processing * linter: line is too long * as per review in warcraft * from wc review that applies here but not there * double space fix * kick unused function * fix map handling * bold submatch scores on submatch draw * #4650 * #4645 * missing faction normalization * #4752 * Update components/match2/wikis/stormgate/match_group_input_custom.lua Co-authored-by: Rikard Blixt * Update components/match2/wikis/stormgate/match_group_input_custom.lua Co-authored-by: Rikard Blixt --------- Co-authored-by: Rikard Blixt --- .../stormgate/match_group_input_custom.lua | 916 ++++++------------ .../match2/wikis/stormgate/match_summary.lua | 10 +- .../wikis/stormgate/player_ext_custom.lua | 6 +- 3 files changed, 333 insertions(+), 599 deletions(-) diff --git a/components/match2/wikis/stormgate/match_group_input_custom.lua b/components/match2/wikis/stormgate/match_group_input_custom.lua index da7408e805a..54a3a53ea22 100644 --- a/components/match2/wikis/stormgate/match_group_input_custom.lua +++ b/components/match2/wikis/stormgate/match_group_input_custom.lua @@ -10,690 +10,443 @@ local Array = require('Module:Array') local Faction = require('Module:Faction') local Flags = require('Module:Flags') local HeroData = mw.loadData('Module:HeroData') -local Json = require('Module:Json') local Logic = require('Module:Logic') local Lua = require('Module:Lua') +local Operator = require('Module:Operator') local String = require('Module:StringUtils') local Table = require('Module:Table') local Variables = require('Module:Variables') -local MatchGroupInput = Lua.import('Module:MatchGroup/Input/Util') +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 DEFAULT_LOSS_STATUSES = {'FF', 'L', 'DQ'} -local DEFAULT_WIN_STATUS = 'W' -local SCORE_STATUS = 'S' -local ALLOWED_STATUSES = Array.append(DEFAULT_LOSS_STATUSES, DEFAULT_WIN_STATUS) -local RESULT_TYPE_DEFAULT = 'default' -local RESULT_TYPE_NOT_PLAYED = 'np' -local MAX_NUM_OPPONENTS = 2 -local DEFAULT_BEST_OF = 99 -local MODE_MIXED = 'mixed' -local TBD = 'tbd' +local OPPONENT_CONFIG = { + resolveRedirect = true, + pagifyTeamNames = true, +} +local TBD = 'TBD' local DEFAULT_HERO_FACTION = HeroData.default.faction -local NOW = os.time(os.date('!*t') --[[@as osdateparam]]) +local MODE_MIXED = 'mixed' + +---@class StormgateParticipant +---@field player string +---@field faction 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, options) assert(not Logic.readBool(match.ffa), 'FFA is not yet supported in stormgate match2') - Table.mergeInto( - match, - CustomMatchGroupInput._readDate(match) - ) - CustomMatchGroupInput._getTournamentVars(match) - CustomMatchGroupInput._adjustData(match) - CustomMatchGroupInput._updateFinished(match) - match.stream = Streams.processStreams(match) - CustomMatchGroupInput._getExtraData(match) - - return match -end + Table.mergeInto(match, MatchFunctions.readDate(match)) ----@param matchArgs table ----@return table -function CustomMatchGroupInput._readDate(matchArgs) - local dateProps = MatchGroupInput.readDate(matchArgs.date, { - 'matchDate', - 'tournament_startdate', - 'tournament_enddate' - }) - - if dateProps.dateexact then - Variables.varDefine('matchDate', dateProps.date) - end - - return dateProps -end + local opponents = Array.mapIndexes(function(opponentIndex) + return MatchGroupInputUtil.readOpponent(match, opponentIndex, OPPONENT_CONFIG) + end) ----@param match table -function CustomMatchGroupInput._updateFinished(match) - match.finished = Logic.nilOr(Logic.readBoolOrNil(match.finished), Logic.isNotEmpty(match.winner)) - if match.finished then - return - end + Array.forEach(opponents, function(opponent) + opponent.extradata = opponent.extradata or {} + Table.mergeInto(opponent.extradata, MatchFunctions.getOpponentExtradata(opponent)) + -- make sure match2players is not nil to avoid indexing nil + opponent.match2players = opponent.match2players or {} + Array.forEach(opponent.match2players, function(player) + player.extradata = player.extradata or {} + player.extradata.faction = MatchFunctions.getPlayerFaction(player) + end) + end) - -- Match is automatically marked finished upon page edit after a - -- certain amount of time (depending on whether the date is exact) - local threshold = match.dateexact and 30800 or 86400 - match.finished = match.timestamp + threshold < NOW -end + local games = MatchFunctions.extractMaps(match, opponents) ----@param match table -function CustomMatchGroupInput._getTournamentVars(match) - match.cancelled = Logic.emptyOr(match.cancelled, Variables.varDefault('tournament_cancelled', 'false')) - match.publishertier = Logic.emptyOr(match.publishertier, Variables.varDefault('tournament_publishertier')) - match.bestof = tonumber(Logic.emptyOr(match.bestof, Variables.varDefault('match_bestof'))) - Variables.varDefine('match_bestof', match.bestof) + local autoScoreFunction = MatchGroupInputUtil.canUseAutoScore(match, games) + and MatchFunctions.calculateMatchScore(games, opponents) + or nil - MatchGroupInput.getCommonTournamentVars(match) -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) ----@param match table -function CustomMatchGroupInput._getExtraData(match) - match.extradata = { - casters = MatchGroupInput.readCasters(match), - ffa = 'false', - } + match.mode = MatchFunctions.getMode(opponents) - for prefix, mapVeto in Table.iter.pairsByPrefix(match, 'veto') do - match.extradata[prefix] = mapVeto and mw.ext.TeamLiquidIntegration.resolve_redirect(mapVeto) or nil - match.extradata[prefix .. 'by'] = match[prefix .. 'by'] - match.extradata[prefix .. 'displayname'] = match[prefix .. 'displayName'] + match.bestof = MatchFunctions.getBestOf(match.bestof) + local cancelled = Logic.readBool(Logic.emptyOr(match.cancelled, Variables.varDefault('tournament_cancelled'))) + if cancelled then + match.finished = match.finished or 'skip' end - Table.mergeInto(match.extradata, Table.filterByKey(match, function(key, value) - return key:match('subgroup%d+header') end)) -end + local winnerInput = match.winner --[[@as string?]] + local finishedInput = match.finished --[[@as string?]] + match.finished = MatchGroupInputUtil.matchIsFinished(match, opponents) ----@param match table -function CustomMatchGroupInput._adjustData(match) - --parse opponents + set base sumscores + set mode - CustomMatchGroupInput._opponentInput(match) - - --main processing done here - local subGroupIndex = 0 - for _, _, mapIndex in Table.iter.pairsByPrefix(match, 'map') do - subGroupIndex = CustomMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) + 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, match.resulttype) end - CustomMatchGroupInput._matchWinnerProcessing(match) -end - ----@param match table -function CustomMatchGroupInput._matchWinnerProcessing(match) - local bestof = match.bestof or DEFAULT_BEST_OF - local numberofOpponents = 0 - - Array.map(Array.range(1, MAX_NUM_OPPONENTS),function(opponentIndex) - local opponent = match['opponent' .. opponentIndex] - - if Logic.isEmpty(opponent) then return end - - numberofOpponents = numberofOpponents + 1 - - if Table.includes(ALLOWED_STATUSES, string.upper(opponent.score or '')) then - opponent.status = string.upper(opponent.score) - match.resulttype = RESULT_TYPE_DEFAULT - match.finished = true - opponent.score = -1 - - if opponent.status == DEFAULT_WIN_STATUS then - match.winner = opponentIndex - else - match.walkover = opponent.status - end - else - opponent.status = SCORE_STATUS - opponent.score = tonumber(opponent.score) or tonumber(opponent.sumscore) or -1 - if opponent.score > bestof / 2 then - match.finished = Logic.emptyOr(match.finished, true) - match.winner = tonumber(match.winner) or opponentIndex - end - end - - if Logic.readBool(match.cancelled) then - match.finished = true - if String.isEmpty(match.resulttype) and Logic.isEmpty(opponent.score) then - match.resulttype = RESULT_TYPE_NOT_PLAYED - opponent.score = opponent.score or -1 - end - end - - -- to not break the loop - return true - end) - - CustomMatchGroupInput._determineWinnerIfMissing(match) + MatchGroupInputUtil.getCommonTournamentVars(match) - for opponentIndex = 1, numberofOpponents do - local opponent = match['opponent' .. opponentIndex] - if match.winner == 'draw' or tonumber(match.winner) == 0 or - (match.opponent1.score == bestof / 2 and match.opponent1.score == match.opponent2.score) then - match.finished = true - match.winner = 0 - match.resulttype = 'draw' - end + match.stream = Streams.processStreams(match) + match.vod = Logic.nilIfEmpty(match.vod) + match.extradata = MatchFunctions.getExtraData(match, #games) - if tonumber(match.winner) == opponentIndex or - match.resulttype == 'draw' then - opponent.placement = 1 - elseif Logic.isNumeric(match.winner) then - opponent.placement = 2 - end - end -end - ----@param match table ----@return table -function CustomMatchGroupInput._determineWinnerIfMissing(match) - if Logic.readBool(match.finished) and Logic.isEmpty(match.winner) then - local scores = Array.mapIndexes(function(opponentIndex) - local opponent = match['opponent' .. opponentIndex] - if not opponent then - return nil - end - return match['opponent' .. opponentIndex].score or -1 end - ) - 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 - local maxIndexFound = false - for opponentIndex, score in pairs(scores) do - if maxIndexFound and score == maxScore then - match.winner = 0 - break - elseif score == maxScore then - maxIndexFound = true - match.winner = opponentIndex - end - end - end - end + match.games = games + match.opponents = opponents return match end ---OpponentInput functions - ----@param match table ----@return table -function CustomMatchGroupInput._opponentInput(match) - local opponentTypes = {} - - for opponentKey, opponent, opponentIndex in Table.iter.pairsByPrefix(match, 'opponent') do - opponent = Json.parseIfString(opponent) - - --Convert byes to literals - if Opponent.isBye(opponent) then - opponent = {type = Opponent.literal, name = 'BYE'} - end - - -- Opponent processing (first part) - -- Sort out extradata - opponent.extradata = { - advantage = opponent.advantage, - penalty = opponent.penalty, - score2 = opponent.score2, - } - - local partySize = Opponent.partySize(opponent.type) - if partySize then - opponent = CustomMatchGroupInput.processPartyOpponentInput(opponent, partySize) - elseif opponent.type == Opponent.team then - opponent = CustomMatchGroupInput.ProcessTeamOpponentInput(opponent, match.date) - opponent = CustomMatchGroupInput._readPlayersOfTeam(match, opponentIndex, opponent) - elseif opponent.type == Opponent.literal then - opponent = CustomMatchGroupInput.ProcessLiteralOpponentInput(opponent) - else - error('Unsupported Opponent Type "' .. (opponent.type or '') .. '"') - end - - --set initial opponent sumscore - opponent.sumscore = tonumber(opponent.extradata.advantage) or (-1 * (tonumber(opponent.extradata.penalty) or 0)) - - table.insert(opponentTypes, opponent.type) +---@param matchArgs table +---@return {date: string, dateexact: boolean, timestamp: integer, timezoneId: string?, timezoneOffset: string?} +function MatchFunctions.readDate(matchArgs) + local dateProps = MatchGroupInputUtil.readDate(matchArgs.date, { + 'matchDate', + 'tournament_startdate', + 'tournament_enddate' + }) - match[opponentKey] = opponent + if dateProps.dateexact then + Variables.varDefine('matchDate', dateProps.date) end - assert(#opponentTypes <= MAX_NUM_OPPONENTS, 'Too many opponents') - - match.mode = Array.all(opponentTypes, function(opponentType) return opponentType == opponentTypes[1] end) - and opponentTypes[1] or MODE_MIXED - - match.isTeamMatch = Array.any(opponentTypes, function(opponentType) return opponentType == Opponent.team end) - - return match + return dateProps end ----reads the players of a team from input and wiki variables ---@param match table ----@param opponentIndex integer ----@param opponent table ----@return table -function CustomMatchGroupInput._readPlayersOfTeam(match, opponentIndex, opponent) - local players = {} +---@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, subGroup, #opponents) - local teamName = opponent.name + map.participants = MapFunctions.getParticipants(mapInput, opponents) - local insertIntoPlayers = function(player) - if type(player) ~= 'table' or Logic.isEmpty(player) or Logic.isEmpty(player.name) then - return - end - - player.name = mw.ext.TeamLiquidIntegration.resolve_redirect(player.name):gsub(' ', '_') - player.flag = Flags.CountryName(player.flag) - player.displayname = Logic.emptyOr(player.displayname, player.displayName) - player.extradata = {faction = Faction.read(player.faction)} + map.mode = MapFunctions.getMode(mapInput, map.participants, opponents) - players[player.name] = players[player.name] or {} - Table.deepMergeInto(players[player.name], player) - end + Table.mergeInto(map.extradata, MapFunctions.getAdditionalExtraData(map, map.participants)) - local playerIndex = 1 - local varPrefix = teamName .. '_p' .. playerIndex - local name = Variables.varDefault(varPrefix) - while name do - insertIntoPlayers{ - name = name, - displayName = Variables.varDefault(varPrefix .. 'dn'), - faction = Variables.varDefault(varPrefix .. 'faction'), - flag = Variables.varDefault(varPrefix .. 'flag'), - } - playerIndex = playerIndex + 1 - varPrefix = teamName .. '_p' .. playerIndex - name = Variables.varDefault(varPrefix) - end + map.vod = Logic.emptyOr(mapInput.vod, match['vodgame' .. mapIndex]) - --players from manual input as `opponnetX_pY` - for _, player in Table.iter.pairsByPrefix(match, 'opponent' .. opponentIndex .. '_p') do - insertIntoPlayers(Json.parseIfString(player)) + table.insert(maps, map) + match[mapKey] = nil end - opponent.match2players = Array.extractValues(players) - --set default faction for unset factions - Array.forEach(opponent.match2players, function(player) - player.extradata.faction = player.extradata.faction or Faction.defaultFaction - end) - - return opponent + return maps end ----@param opponent table ----@return table -function CustomMatchGroupInput.ProcessLiteralOpponentInput(opponent) - local faction = opponent.faction - local flag = opponent.flag - local name = opponent.name or opponent[1] - local extradata = opponent.extradata - - local players = {} - if String.isNotEmpty(faction) or String.isNotEmpty(flag) then - players[1] = { - displayname = name, - name = TBD:upper(), - flag = Flags.CountryName(flag), - extradata = {faction = Faction.read(faction) or Faction.defaultFaction} - } - extradata.hasFactionOrFlag = true +---@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 - - return { - type = opponent.type, - name = name, - score = opponent.score, - extradata = extradata, - match2players = players - } end ---@param opponent table ----@param partySize integer ---@return table -function CustomMatchGroupInput.processPartyOpponentInput(opponent, partySize) - local players = {} - local links = {} - - for playerIndex = 1, partySize do - local name = Logic.emptyOr(opponent['p' .. playerIndex], opponent[playerIndex]) or '' - local link = mw.ext.TeamLiquidIntegration.resolve_redirect(Logic.emptyOr( - opponent['p' .. playerIndex .. 'link'], - Variables.varDefault(name .. '_page') - ) or name):gsub(' ', '_') - table.insert(links, link) - - table.insert(players, { - displayname = name, - name = link, - flag = Flags.CountryName(Logic.emptyOr( - opponent['p' .. playerIndex .. 'flag'], - Variables.varDefault(name .. '_flag') - )), - extradata = {faction = Faction.read(Logic.emptyOr( - opponent['p' .. playerIndex .. 'faction'], - Variables.varDefault(name .. '_faction') - )) or Faction.defaultFaction} - }) - end - - table.sort(links) - +function MatchFunctions.getOpponentExtradata(opponent) return { - type = opponent.type, - name = table.concat(links, ' / '), - score = opponent.score, - extradata = opponent.extradata, - match2players = players + advantage = tonumber(opponent.advantage), + penalty = tonumber(opponent.penalty), } end ----@param opponent table ----@param date string ----@return table -function CustomMatchGroupInput.ProcessTeamOpponentInput(opponent, date) - local template = string.lower(Logic.emptyOr(opponent.template, opponent[1], '')--[[@as string]]):gsub('_', ' ') - - if String.isEmpty(template) or template == 'noteam' then - opponent = Table.merge(opponent, Opponent.blank(Opponent.team)) - opponent.name = Opponent.toName(opponent) - return opponent - end +---@param player table +---@return string +function MatchFunctions.getPlayerFaction(player) + return Faction.read(player.extradata.faction) or Faction.defaultFaction +end - assert(mw.ext.TeamTemplate.teamexists(template), 'Missing team template "' .. template .. '"') +---@param opponents {type: OpponentType} +---@return string +function MatchFunctions.getMode(opponents) + local opponentTypes = Array.map(opponents, Operator.property('type')) + return #Array.unique(opponentTypes) == 1 and opponentTypes[1] or MODE_MIXED +end - local templateData = mw.ext.TeamTemplate.raw(template, date) +---@param bestofInput string|integer? +---@return integer? +function MatchFunctions.getBestOf(bestofInput) + local bestof = tonumber(bestofInput) or tonumber(Variables.varDefault('match_bestof')) - opponent.icon = templateData.image - opponent.icondark = Logic.emptyOr(templateData.imagedark, templateData.image) - opponent.name = templateData.page:gsub(' ', '_') - opponent.template = templateData.templatename or template + if bestof then + Variables.varDefine('match_bestof', bestof) + end - return opponent + return bestof end ---MapInput functions - ---@param match table ----@param mapIndex integer ----@param subGroupIndex integer ----@return integer -function CustomMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) - local map = Json.parseIfString(match['map' .. mapIndex]) - map.map = mw.ext.TeamLiquidIntegration.resolve_redirect(map.map or '') - - -- set initial extradata for maps - map.extradata = { - comment = map.comment, - header = map.header, +---@param numberOfGames integer +---@return table +function MatchFunctions.getExtraData(match, numberOfGames) + local extradata = { + casters = MatchGroupInputUtil.readCasters(match, {noSort = true}), } - -- determine score, resulttype, walkover and winner - map = CustomMatchGroupInput._mapWinnerProcessing(map) - - -- get participants data for the map + get map mode + winnerfaction and loserfaction - --(w/l faction stuff only for 1v1 maps) - CustomMatchGroupInput.ProcessPlayerMapData(map, match, 2) - - --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 - - -- handle subgroup stuff if team match - if match.isTeamMatch then - map.subgroup = tonumber(map.subgroup) or (subGroupIndex + 1) - subGroupIndex = map.subgroup + for prefix, mapVeto in Table.iter.pairsByPrefix(match, 'veto') do + extradata[prefix] = mapVeto and mw.ext.TeamLiquidIntegration.resolve_redirect(mapVeto) or nil + extradata[prefix .. 'by'] = match[prefix .. 'by'] + extradata[prefix .. 'displayname'] = match[prefix .. 'displayName'] end - match['map' .. mapIndex] = map + Table.mergeInto(extradata, Table.filterByKey(match, function(key) return key:match('subgroup%d+header') end)) - return subGroupIndex + return extradata end ----@param map table +---@param mapInput table +---@param subGroup integer +---@param opponentCount integer ---@return table -function CustomMatchGroupInput._mapWinnerProcessing(map) - map.scores = {} - local hasManualScores = false - local indexedScores = {} - for scoreIndex = 1, MAX_NUM_OPPONENTS do - -- read scores - local score = map['score' .. scoreIndex] - local obj = {} - if Logic.isNotEmpty(score) then - hasManualScores = true - score = score - if Logic.isNumeric(score) then - obj.status = 'S' - obj.score = score - elseif Table.includes(ALLOWED_STATUSES, score) then - obj.status = score - obj.score = -1 - end - table.insert(map.scores, score) - indexedScores[scoreIndex] = obj - else - break - end +---@return integer +function MapFunctions.readMap(mapInput, subGroup, opponentCount) + subGroup = tonumber(mapInput.subgroup) or (subGroup + 1) + + local mapName = mapInput.map + if mapName and mapName:upper() ~= TBD then + mapName = mw.ext.TeamLiquidIntegration.resolve_redirect(mapName) + elseif mapName then + mapName = TBD end - local winner = tonumber(map.winner) - if Logic.isNotEmpty(map.walkover) then - local walkoverInput = tonumber(map.walkover) - if walkoverInput == 1 or walkoverInput == 2 or walkoverInput == 0 then - winner = walkoverInput - end - map.walkover = Table.includes(ALLOWED_STATUSES, map.walkover) and map.walkover or 'L' - map.scores = {-1, -1} - map.resulttype = 'default' - map.winner = winner + local map = { + map = mapName, + subgroup = subGroup, + extradata = { + comment = mapInput.comment, + header = mapInput.header, + } + } - return map - end + map.finished = MatchGroupInputUtil.mapIsFinished(mapInput) + 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.winner, map.finished)) + return {score = score, status = status} + end) - if hasManualScores then - map.winner = winner or CustomMatchGroupInput._getWinner(indexedScores) + map.scores = Array.map(opponentInfo, Operator.property('score')) - return map + 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) end - if map.winner == 'skip' then - map.scores = {-1, -1} - map.resulttype = RESULT_TYPE_NOT_PLAYED - elseif winner == 1 then - map.scores = {1, 0} - elseif winner == 2 then - map.scores = {0, 1} - elseif winner == 0 or map.winner == 'draw' then - map.scores = {0.5, 0.5} - map.resulttype = 'draw' - end - - map.winner = winner + return map, subGroup +end - return map +---@param winnerInput string|integer|nil +---@param finished boolean +---@return fun(opponentIndex: integer): integer? +function MapFunctions.calculateMapScore(winnerInput, finished) + local winner = tonumber(winnerInput) + return function(opponentIndex) + -- 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 winner == opponentIndex and 1 or 0 + end end ----@param map table ----@param match table ----@param numberOfOpponents integer -function CustomMatchGroupInput.ProcessPlayerMapData(map, match, numberOfOpponents) +---@param mapInput table +---@param opponents table[] +---@return table +function MapFunctions.getParticipants(mapInput, opponents) local participants = {} - local modeParts = {} - for opponentIndex = 1, numberOfOpponents do - local opponent = match['opponent' .. opponentIndex] - local partySize = Opponent.partySize(opponent.type) - local players = opponent.match2players - if partySize then - table.insert(modeParts, partySize) - CustomMatchGroupInput._processPartyPlayerMapData(players, map, opponentIndex, participants) + Array.forEach(opponents, function(opponent, opponentIndex) + if opponent.type == Opponent.literal then + return elseif opponent.type == Opponent.team then - table.insert(modeParts, CustomMatchGroupInput._processTeamPlayerMapData(players, map, opponentIndex, participants)) - elseif opponent.type == Opponent.literal then - table.insert(modeParts, 'literal') + Table.mergeInto(participants, MapFunctions.getTeamParticipants(mapInput, opponent, opponentIndex)) + return end - end + Table.mergeInto(participants, MapFunctions.getPartyParticipants(mapInput, opponent, opponentIndex)) + end) - map.mode = table.concat(modeParts, 'v') - map.participants = participants + return participants +end - if numberOfOpponents ~= MAX_NUM_OPPONENTS or map.mode ~= '1v1' then - return - end +---@param mapInput table +---@param opponent table +---@param opponentIndex integer +---@return table +function MapFunctions.getTeamParticipants(mapInput, opponent, opponentIndex) + local players = Array.mapIndexes(function(playerIndex) + return Logic.nilIfEmpty(mapInput['t' .. opponentIndex .. 'p' .. playerIndex]) + end) - local opponentFactions, playerNameArray, heroesData - = CustomMatchGroupInput._fetchOpponentMapParticipantData(participants) - map.extradata = Table.merge(map.extradata, heroesData) - if tonumber(map.winner) == 1 then - map.extradata.winnerfaction = opponentFactions[1] - map.extradata.loserfaction = opponentFactions[2] - elseif tonumber(map.winner) == 2 then - map.extradata.winnerfaction = opponentFactions[2] - map.extradata.loserfaction = opponentFactions[1] - end - map.extradata.opponent1 = playerNameArray[1] - map.extradata.opponent2 = playerNameArray[2] -end + local participants, unattachedParticipants = MatchGroupInputUtil.parseParticipants( + opponent.match2players, + players, + function(playerIndex) + local prefix = 't' .. opponentIndex .. 'p' .. playerIndex + return { + name = mapInput[prefix], + link = Logic.nilIfEmpty(mapInput[prefix .. 'link']) or Variables.varDefault(mapInput[prefix] .. '_page'), + } + end, + function(playerIndex, playerIdData, playerInputData) + local prefix = 't' .. opponentIndex .. 'p' .. playerIndex + local faction = Faction.read(mapInput[prefix .. 'faction']) + or (playerIdData.extradata or {}).faction or Faction.defaultFaction + local link = playerIdData.name or playerInputData.link or playerInputData.name:gsub(' ', '_') + return { + faction = faction, + player = link, + flag = Flags.CountryName(playerIdData.flag), + position = playerIndex, + random = Logic.readBool(mapInput[prefix .. 'random']), + heroes = MapFunctions.readHeroes( + mapInput[prefix .. 'heroes'], + faction, + link, + Logic.readBool(mapInput[prefix .. 'noheroescheck']) + ), + } + end + ) ----@param participants table ----@return table ----@return table ----@return table -function CustomMatchGroupInput._fetchOpponentMapParticipantData(participants) - local opponentFactions, playerNameArray, heroesData = {}, {}, {} - for participantKey, participantData in pairs(participants) do - local opponentIndex = tonumber(string.sub(participantKey, 1, 1)) - -- opponentIndex can not be nil due to the format of the participants keys - ---@cast opponentIndex -nil - opponentFactions[opponentIndex] = participantData.faction - playerNameArray[opponentIndex] = participantData.player - Array.forEach(participantData.heroes or {}, function(hero, heroIndex) - heroesData['opponent' .. opponentIndex .. 'hero' .. heroIndex] = hero - end) - end + Array.forEach(unattachedParticipants, function(participant) + local name = mapInput['t' .. opponentIndex .. 'p' .. participant.position] + local nameUpper = name:upper() + local isTBD = nameUpper == TBD + + table.insert(opponent.match2players, { + name = isTBD and TBD or participant.player, + displayname = isTBD and TBD or name, + flag = participant.flag, + extradata = {faction = participant.faction}, + }) + participants[#opponent.match2players] = participant + end) - return opponentFactions, playerNameArray, heroesData + return Table.map(participants, MatchGroupInputUtil.prefixPartcipants(opponentIndex)) end ----@param players table[] ----@param map table +---@param mapInput table +---@param opponent table ---@param opponentIndex integer ----@param participants table ----@return table -function CustomMatchGroupInput._processPartyPlayerMapData(players, map, opponentIndex, participants) +---@return table +function MapFunctions.getPartyParticipants(mapInput, opponent, opponentIndex) + local players = opponent.match2players + + -- resolve the aliases in case they are used local prefix = 't' .. opponentIndex .. 'p' - for playerIndex, player in pairs(players) do - local faction = Logic.emptyOr( - map[prefix .. playerIndex .. 'faction'], - player.extradata.faction, - Faction.defaultFaction - ) - faction = Faction.read(faction) + local participants = {} + + Array.forEach(players, function(player, playerIndex) + local faction = Faction.read(mapInput['t' .. opponentIndex .. 'p' .. playerIndex .. 'faction']) + or player.extradata.faction participants[opponentIndex .. '_' .. playerIndex] = { - faction = faction, + faction = Faction.read(faction or player.extradata.faction), player = player.name, - heroes = CustomMatchGroupInput._readHeroes( - map[prefix .. playerIndex .. 'heroes'], + heroes = MapFunctions.readHeroes( + mapInput[prefix .. playerIndex .. 'heroes'], faction, player.name, - Logic.readBool(map[prefix .. playerIndex .. 'noheroescheck']) + Logic.readBool(mapInput[prefix .. playerIndex .. 'noheroescheck']) ), } - end + end) return participants end ----@param players table[] ----@param map table ----@param opponentIndex integer ----@param participants table ----@return integer -function CustomMatchGroupInput._processTeamPlayerMapData(players, map, opponentIndex, participants) - local amountOfTbds = 0 - local playerData = {} - - local numberOfPlayers = 0 - for prefix, playerInput, playerIndex in Table.iter.pairsByPrefix(map, 't' .. opponentIndex .. 'p') do - numberOfPlayers = numberOfPlayers + 1 - if playerInput:lower() == TBD then - amountOfTbds = amountOfTbds + 1 - else - local link = Logic.emptyOr(map[prefix .. 'link'], Variables.varDefault(playerInput .. '_page')) or playerInput - link = mw.ext.TeamLiquidIntegration.resolve_redirect(link):gsub(' ', '_') - - playerData[link] = { - faction = Faction.read(map[prefix .. 'faction']), - position = playerIndex, - heroes = map[prefix .. 'heroes'], - heroesCheckDisabled = Logic.readBool(map[prefix .. 'noheroescheck']), - playedRandom = Logic.readBool(map[prefix .. 'random']), - displayName = playerInput, - } - end +---@param mapInput table # the input data +---@param participants table +---@param opponents table[] +---@return string +function MapFunctions.getMode(mapInput, participants, opponents) + -- assume we have a min of 2 opponents in a game + local playerCounts = {0, 0} + for key in pairs(participants) do + local parsedOpponentIndex = key:match('(%d+)_%d+') + local opponetIndex = tonumber(parsedOpponentIndex) --[[@as integer]] + playerCounts[opponetIndex] = (playerCounts[opponetIndex] or 0) + 1 end - local addToParticipants = function(currentPlayer, player, playerIndex) - local faction = currentPlayer.faction or (player.extradata or {}).faction or Faction.defaultFaction + local modeParts = Array.map(playerCounts, function(count, opponentIndex) + if count == 0 then + return Opponent.literal + end - participants[opponentIndex .. '_' .. playerIndex] = { - faction = faction, - player = player.name, - position = currentPlayer.position, - flag = Flags.CountryName(player.flag), - heroes = CustomMatchGroupInput._readHeroes( - currentPlayer.heroes, - faction, - player.name, - currentPlayer.heroesCheckDisabled - ), - random = currentPlayer.playedRandom, - } + return count + end) + + return table.concat(modeParts, 'v') +end + +---@param map table +---@param participants table +---@return table +function MapFunctions.getAdditionalExtraData(map, participants) + if map.mode ~= '1v1' then return {} end + + local extradata = MapFunctions.getHeroesExtradata(participants) + + local players = {} + for _, player in Table.iter.spairs(participants) do + table.insert(players, player) end - Array.forEach(players, function(player, playerIndex) - local currentPlayer = playerData[player.name] - if not currentPlayer then return end + extradata.opponent1 = players[1].player + extradata.opponent2 = players[2].player - addToParticipants(currentPlayer, player, playerIndex) - playerData[player.name] = nil - end) + if map.winner ~= 1 and map.winner ~= 2 then + return extradata + end + local loser = 3 - map.winner - -- if we have players not already in the match2players insert them - -- this is to break conditional data loops between match2 and teamCard/HDB - Table.iter.forEachPair(playerData, function(playerLink, player) - local faction = player.faction or Faction.defaultFaction - table.insert(players, { - name = playerLink, - displayname = player.displayName, - extradata = {faction = faction}, - }) - addToParticipants(player, players[#players], #players) - numberOfPlayers = numberOfPlayers + 1 - end) + extradata.winnerfaction = players[map.winner].faction + extradata.loserfaction = players[loser].faction - Array.forEach(Array.range(1, amountOfTbds), function(tbdIndex) - participants[opponentIndex .. '_' .. (#players + tbdIndex)] = { - faction = Faction.defaultFaction, - player = TBD:upper(), - } - end) + return extradata +end - map.participants = participants +--- additionally store heroes in extradata so we can condition on them +---@param participants table +---@return table +function MapFunctions.getHeroesExtradata(participants) + local extradata = {} + for participantKey, participant in Table.iter.spairs(participants) do + local opponentIndex = string.match(participantKey, '^(%d+)_') + Array.forEach(participant.heroes or {}, function(hero, heroIndex) + extradata['opponent' .. opponentIndex .. 'hero' .. heroIndex] = hero + end) + end - return numberOfPlayers + return extradata end ---@param heroesInput string? @@ -701,7 +454,7 @@ end ---@param playerName string ---@param ignoreFactionHeroCheck boolean ---@return string[]? -function CustomMatchGroupInput._readHeroes(heroesInput, faction, playerName, ignoreFactionHeroCheck) +function MapFunctions.readHeroes(heroesInput, faction, playerName, ignoreFactionHeroCheck) if String.isEmpty(heroesInput) then return end @@ -715,38 +468,11 @@ function CustomMatchGroupInput._readHeroes(heroesInput, faction, playerName, ign local isCoreFaction = Table.includes(Faction.coreFactions, faction) assert(ignoreFactionHeroCheck or not isCoreFaction or faction == heroData.faction or heroData.faction == DEFAULT_HERO_FACTION, - 'Invalid hero input "' .. hero .. '" for faction "' - .. Faction.toName(faction) .. '" of player "' .. playerName .. '"') + 'Invalid hero input "' .. hero .. '" for faction "' .. Faction.toName(faction) + .. '" of player "' .. playerName .. '"') return heroData.name end) end ----@param indexedScores table ----@return integer? -function CustomMatchGroupInput._getWinner(indexedScores) - table.sort(indexedScores, CustomMatchGroupInput._mapWinnerSortFunction) - - return indexedScores[1].index -end - ----@param opponent1 table ----@param opponent2 table ----@return boolean -function CustomMatchGroupInput._mapWinnerSortFunction(opponent1, opponent2) - local opponent1Norm = opponent1.status == SCORE_STATUS - local opponent2Norm = opponent2.status == SCORE_STATUS - - if opponent1Norm and opponent2Norm then - return tonumber(opponent1.score) > tonumber(opponent2.score) - elseif opponent1Norm then return true - elseif opponent2Norm then return false - elseif opponent1.status == DEFAULT_WIN_STATUS then return true - elseif Table.includes(ALLOWED_STATUSES, opponent1.status) then return false - elseif opponent2.status == DEFAULT_WIN_STATUS then return false - elseif Table.includes(ALLOWED_STATUSES, opponent2.status) then return true - else return true - end -end - return CustomMatchGroupInput diff --git a/components/match2/wikis/stormgate/match_summary.lua b/components/match2/wikis/stormgate/match_summary.lua index fb59edff5d7..3ae5ba25147 100644 --- a/components/match2/wikis/stormgate/match_summary.lua +++ b/components/match2/wikis/stormgate/match_summary.lua @@ -366,17 +366,21 @@ function CustomMatchSummary._submatchHeader(submatch) } end + ---@param opponentIndex any + ---@return Html local createScore = function(opponentIndex) - local isWinner = opponentIndex == submatch.winner + local isWinner = opponentIndex == submatch.winner or submatch.resultType == 'draw' if submatch.resultType == 'default' then return OpponentDisplay.BlockScore{ isWinner = isWinner, - scoreText = isWinner and 'W' or submatch.walkover, + scoreText = isWinner and 'W' or string.upper(submatch.walkover), } end + + local score = submatch.resultType ~= 'np' and (submatch.scores or {})[opponentIndex] or nil return OpponentDisplay.BlockScore{ isWinner = isWinner, - scoreText = (submatch.scores or {})[opponentIndex] or '', + scoreText = score, } end diff --git a/components/opponent/wikis/stormgate/player_ext_custom.lua b/components/opponent/wikis/stormgate/player_ext_custom.lua index 2f3825d4090..e928a33e5c6 100644 --- a/components/opponent/wikis/stormgate/player_ext_custom.lua +++ b/components/opponent/wikis/stormgate/player_ext_custom.lua @@ -52,7 +52,11 @@ end) function CustomPlayerExt.fetchPlayerFaction(resolvedPageName, date) local lpdbPlayer = CustomPlayerExt.fetchPlayer(resolvedPageName) if lpdbPlayer and lpdbPlayer.factionHistory then - date = date or DateExt.getContextualDateOrNow() + local timestamp = DateExt.readTimestamp(date or DateExt.getContextualDateOrNow()) + ---@cast timestamp -nil + -- convert date to iso format to match the dates retrieved from the data points + -- need the time too so the below check remains the same as before + date = DateExt.formatTimestamp('Y-m-d H:i:s', timestamp) local entry = Array.find(lpdbPlayer.factionHistory, function(entry) return date <= entry.endDate end) return entry and Faction.read(entry.faction) else From 68428aa763a950489dac4ef88878b3e11ee6c028 Mon Sep 17 00:00:00 2001 From: hjpalpha <75081997+hjpalpha@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:08:12 +0200 Subject: [PATCH 7/9] refactor(match2): sc(2) FFA input processing (#4734) * prep * more prep * i hope this works ... * a start * unused import * extend comment * match scores and some fixes * import the correct module * catch nil * soem fixes * fixes * some more fixes * set map.scores * fix placement calculation * `|` invalid in cat name * remove the change since it was already merged in another pr * Update components/match2/commons/match_group_input_util.lua Co-authored-by: Rikard Blixt * Update components/match2/commons/starcraft_starcraft2/match_group_input_starcraft_ffa.lua Co-authored-by: Rikard Blixt * Update components/match2/commons/starcraft_starcraft2/match_group_input_starcraft_ffa.lua Co-authored-by: Rikard Blixt --------- Co-authored-by: Rikard Blixt --- .../match2/commons/match_group_input_util.lua | 1 + .../match_external_links_starcraft.lua | 136 --- .../match_group_input_starcraft.lua | 44 +- ...match_group_input_starcraft_deprecated.lua | 1064 ----------------- .../match_group_input_starcraft_ffa.lua | 823 ++++++------- .../match_summary_ffa_starcraft.lua | 19 +- .../match_summary_starcraft.lua | 3 + 7 files changed, 394 insertions(+), 1696 deletions(-) delete mode 100644 components/match2/commons/starcraft_starcraft2/match_external_links_starcraft.lua delete mode 100644 components/match2/commons/starcraft_starcraft2/match_group_input_starcraft_deprecated.lua diff --git a/components/match2/commons/match_group_input_util.lua b/components/match2/commons/match_group_input_util.lua index c02f0dceca9..db6862bb640 100644 --- a/components/match2/commons/match_group_input_util.lua +++ b/components/match2/commons/match_group_input_util.lua @@ -93,6 +93,7 @@ local ASSUME_FINISHED_AFTER = { EXACT = 30800, ESTIMATE = 86400, } +MatchGroupInputUtil.ASSUME_FINISHED_AFTER = ASSUME_FINISHED_AFTER local NOW = os.time() local contentLanguage = mw.getContentLanguage() diff --git a/components/match2/commons/starcraft_starcraft2/match_external_links_starcraft.lua b/components/match2/commons/starcraft_starcraft2/match_external_links_starcraft.lua deleted file mode 100644 index bbec1660a6e..00000000000 --- a/components/match2/commons/starcraft_starcraft2/match_external_links_starcraft.lua +++ /dev/null @@ -1,136 +0,0 @@ ---- --- @Liquipedia --- wiki=commons --- page=Module:MatchExternalLinks/Starcraft --- --- Please see https://github.com/Liquipedia/Lua-Modules to contribute --- - -local Arguments = require('Module:Arguments') -local Array = require('Module:Array') - ---[[ -Utility module for working with starcraft and starcraft2 specific external -media links of matches. This includes the preview, lrthread, vodN, interviewN, -recap, and review params of both matches and games. -]] - -local StarcraftMatchExternalLinks = {} - --- List of supported external link parameters, in display order ----@enum StarcraftMatchExternalLinkTypes -local PARAM_TYPES = { - 'preview', - 'lrthread', - 'vod', - 'vodgame', - 'interview', - 'recap', - 'review', -} -StarcraftMatchExternalLinks.paramTypes = PARAM_TYPES - -StarcraftMatchExternalLinks.paramTypeIndexes = {} -for index, paramType in ipairs(StarcraftMatchExternalLinks.paramTypes) do - StarcraftMatchExternalLinks.paramTypeIndexes[paramType] = index -end - ----Renders a single external link. ----@param props {number: number?, type: StarcraftMatchExternalLinkTypes, url: string} ----@return string -function StarcraftMatchExternalLinks.ExternalLink(props) - if props.type == 'preview' then - return '[[File:Preview_Icon32.png|link=' .. props.url .. '|alt=preview|16px|Preview]] ' - end - - if props.type == 'lrthread' then - return '[[File:LiveReport32.png|link=' .. props.url .. '|alt=lrthread|16px|Live Report Thread]] ' - end - - if props.type == 'vod' then - if props.number and 1 <= props.number and props.number <= 9 then - return '[[File:VOD Icon' .. props.number .. '.png|16px|link=' .. props.url .. '|Watch Game ' .. props.number .. ']] ' - else - return '[[File:VOD Icon.png|link=' .. props.url .. '|16px|Watch VOD]] ' - end - end - - if props.type == 'interview' then - return '[[File:Interview32.png|link=' .. props.url .. '|16px|Interview]] ' - end - - if props.type == 'recap' then - return '[[File:Reviews32.png|link=' .. props.url .. '|16px|Recap]] ' - end - if props.type == 'review' then - return '[[File:Reviews32.png|link=' .. props.url .. '|16px|Review]] ' - end - - error('Unsupported prop type ' .. tostring(props.type)) -end - ----Extracts match media link relevant args from a generic args array, and returns an array of link propss that can be ----rendered via StarcraftMatchExternalLinks.MatchExternalLinks. ----@param args table ----@return {number: number?, type: StarcraftMatchExternalLinkTypes, url: string}[] -function StarcraftMatchExternalLinks.extractFromArgs(args) - local links = {} - for paramName, url in pairs(args) do - local type, number = tostring(paramName):match('^(%a+)(%d*)$') - if StarcraftMatchExternalLinks.paramTypeIndexes[type] and url ~= '' then - table.insert(links, { - type = type, - number = tonumber(number), - url = url, - }) - end - end - - return links -end - ----@param match table ----@return {number: number?, type: StarcraftMatchExternalLinkTypes, url: string}[] -function StarcraftMatchExternalLinks.extractFromMatch(match) - local links = StarcraftMatchExternalLinks.extractFromArgs(match.links) - - if match.vod then - table.insert(links, {type = 'vod', url = match.vod}) - end - for gameIx, game in ipairs(match.games) do - if game.vod then - table.insert(links, {type = 'vod', number = gameIx, url = game.vod}) - end - end - - return links -end - ----@param props {links: {number: number?, type: StarcraftMatchExternalLinkTypes, url: string}[]} ----@return Html -function StarcraftMatchExternalLinks.MatchExternalLinks(props) - local links = Array.sortBy(props.links, function(link) - return { - StarcraftMatchExternalLinks.paramTypeIndexes[link.type], - -- Unnumbered vods appear before numbered ones - link.number or -1, - } - end) - - local list = mw.html.create('span'):addClass('starcraft-match-external-links') - for _, link in ipairs(links) do - list:node(StarcraftMatchExternalLinks.ExternalLink(link)) - end - return list -end - --- Entry point of Template:MatchExternalLink ----@param frame Frame ----@return Html -function StarcraftMatchExternalLinks.TemplateMatchExternalLink(frame) - local args = Arguments.getArgs(frame) - local links = StarcraftMatchExternalLinks.extractFromArgs(args) - return StarcraftMatchExternalLinks.MatchExternalLinks({links = links}) -end - -return StarcraftMatchExternalLinks diff --git a/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft.lua b/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft.lua index d4c08423044..43ed6661907 100644 --- a/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft.lua +++ b/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft.lua @@ -16,7 +16,6 @@ local String = require('Module:StringUtils') local Table = require('Module:Table') local Variables = require('Module:Variables') -local DeprecatedCustomMatchGroupInput = Lua.import('Module:MatchGroup/Input/Starcraft/deprecated') local MatchGroupInputUtil = Lua.import('Module:MatchGroup/Input/Util') local OpponentLibraries = require('Module:OpponentLibraries') local Opponent = OpponentLibraries.Opponent @@ -33,20 +32,23 @@ local MODE_MIXED = 'mixed' local StarcraftMatchGroupInput = {} local MatchFunctions = {} local MapFunctions = {} +-- make these available for ffa +StarcraftMatchGroupInput.MatchFunctions = MatchFunctions +StarcraftMatchGroupInput.MapFunctions = MapFunctions ---@param match table ---@param options table? ---@return table function StarcraftMatchGroupInput.processMatch(match, options) if Logic.readBool(match.ffa) then - return DeprecatedCustomMatchGroupInput.processMatch(match, options) + -- have to import here to avoid import loops + local FfaStarcraftMatchGroupInput = Lua.import('Module:MatchGroup/Input/Starcraft/Ffa') + return FfaStarcraftMatchGroupInput.processMatch(match, options) end Table.mergeInto(match, MatchFunctions.readDate(match.date)) - local opponents = Array.mapIndexes(function(opponentIndex) - return MatchGroupInputUtil.readOpponent(match, opponentIndex, OPPONENT_CONFIG) - end) + local opponents = MatchFunctions.readOpponents(match) -- TODO: check how we can get rid of this legacy stuff ... Array.forEach(opponents, function(opponent, opponentIndex) @@ -56,17 +58,6 @@ function StarcraftMatchGroupInput.processMatch(match, options) match.winner = match.winner or opponentIndex end) - Array.forEach(opponents, function(opponent) - opponent.extradata = opponent.extradata or {} - Table.mergeInto(opponent.extradata, MatchFunctions.getOpponentExtradata(opponent)) - -- make sure match2players is not nil to avoid indexing nil - opponent.match2players = opponent.match2players or {} - Array.forEach(opponent.match2players, function(player) - player.extradata = player.extradata or {} - player.extradata.faction = MatchFunctions.getPlayerFaction(player) - end) - end) - local games = MatchFunctions.extractMaps(match, opponents) local autoScoreFunction = MatchGroupInputUtil.canUseAutoScore(match, games) @@ -115,6 +106,27 @@ function StarcraftMatchGroupInput.processMatch(match, options) return match end +---@param match table +---@return table[] +function MatchFunctions.readOpponents(match) + local opponents = Array.mapIndexes(function(opponentIndex) + return MatchGroupInputUtil.readOpponent(match, opponentIndex, OPPONENT_CONFIG) + end) + + Array.forEach(opponents, function(opponent) + opponent.extradata = opponent.extradata or {} + Table.mergeInto(opponent.extradata, MatchFunctions.getOpponentExtradata(opponent)) + -- make sure match2players is not nil to avoid indexing nil + opponent.match2players = opponent.match2players or {} + Array.forEach(opponent.match2players, function(player) + player.extradata = player.extradata or {} + player.extradata.faction = MatchFunctions.getPlayerFaction(player) + end) + end) + + return opponents +end + ---@param dateInput string? ---@return {date: string, dateexact: boolean, timestamp: integer, timezoneId: string?, timezoneOffset: string?} function MatchFunctions.readDate(dateInput) diff --git a/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft_deprecated.lua b/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft_deprecated.lua deleted file mode 100644 index 18c2fe1add2..00000000000 --- a/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft_deprecated.lua +++ /dev/null @@ -1,1064 +0,0 @@ ---- --- @Liquipedia --- wiki=commons --- page=Module:MatchGroup/Input/Starcraft/deprecated --- --- Please see https://github.com/Liquipedia/Lua-Modules to contribute --- - ----@deprecated ----only used for the ffa part until that gets cleaned up too - - -local Array = require('Module:Array') -local Faction = require('Module:Faction') -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 String = require('Module:StringUtils') -local Table = require('Module:Table') -local Variables = require('Module:Variables') - -local config = Lua.requireIfExists('Module:Match/Config', {loadData = true}) or {} -local MatchGroupInputUtil = Lua.import('Module:MatchGroup/Input/Util') -local Opponent = Lua.import('Module:Opponent') -local Streams = Lua.import('Module:Links/Stream') - -local MAX_NUM_MAPS = config.MAX_NUM_MAPS or 20 -local ALLOWED_STATUSES = {'W', 'FF', 'DQ', 'L'} -local CONVERT_STATUS_INPUT = {W = 'W', FF = 'FF', L = 'L', DQ = 'DQ', ['-'] = 'L'} -local DEFAULT_LOSS_STATUSES = {'FF', 'L', 'DQ'} -local MAX_NUM_OPPONENTS = 2 -local MAX_NUM_PLAYERS = 30 -local MAX_NUM_VODGAMES = 9 -local DEFAULT_BEST_OF = 99 -local OPPONENT_MODE_TO_PARTIAL_MATCH_MODE = { - solo = '1', - duo = '2', - trio = '3', - quad = '4', - team = 'team', - literal = 'literal', -} -local NOW = os.time(os.date('!*t') --[[@as osdateparam]]) -local TBD = 'tbd' -local TBA = 'tba' - -local getStarcraftFfaInputModule = FnUtil.memoize(function() - return Lua.import('Module:MatchGroup/Input/Starcraft/Ffa') -end) - ----Module for converting input args of match group objects into LPDB records. ----This module is specific to the Starcraft and Starcraft2 wikis. -local StarcraftMatchGroupInput = {} - --- called from Module:MatchGroup ----@param match table ----@param options table? ----@return table -function StarcraftMatchGroupInput.processMatch(match, options) - Table.mergeInto( - match, - StarcraftMatchGroupInput._readDate(match) - ) - match = StarcraftMatchGroupInput._getTournamentVars(match) - if Logic.readBool(match.ffa) then - match = getStarcraftFfaInputModule().adjustData(match) - else - match = StarcraftMatchGroupInput._adjustData(match) - end - match = StarcraftMatchGroupInput._checkFinished(match) - match = StarcraftMatchGroupInput._getVodStuff(match) - match = StarcraftMatchGroupInput._getLinks(match) - match = StarcraftMatchGroupInput._getExtraData(match) - - return match -end - ----@param matchArgs table ----@return {date: string, dateexact: boolean, timestamp: integer, timezoneId: string?, timezoneOffset: string?} -function StarcraftMatchGroupInput._readDate(matchArgs) - local dateProps = MatchGroupInputUtil.readDate(matchArgs.date, { - 'matchDate', - 'tournament_startdate', - 'tournament_enddate', - }) - if dateProps.dateexact then - Variables.varDefine('matchDate', dateProps.date) - end - return dateProps -end - ----@param match table ----@return table -function StarcraftMatchGroupInput._checkFinished(match) - if Logic.readBoolOrNil(match.finished) == false then - match.finished = false - return match - elseif Logic.readBool(match.finished) then - match.finished = true - elseif Logic.isNotEmpty(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 match.finished ~= true then - local threshold = match.dateexact and 30800 or 86400 - if match.timestamp + threshold < NOW then - match.finished = true - end - end - - return match -end - ----@param match table ----@return table -function StarcraftMatchGroupInput._getTournamentVars(match) - match.cancelled = Logic.emptyOr(match.cancelled, Variables.varDefault('cancelled tournament', 'false')) - match.headtohead = Logic.emptyOr(match.headtohead, Variables.varDefault('headtohead')) - Variables.varDefine('headtohead', match.headtohead) - match.publishertier = Logic.emptyOr(match.featured, Variables.varDefault('tournament_publishertier')) - match.bestof = Logic.emptyOr(match.bestof, Variables.varDefault('bestof')) - Variables.varDefine('bestof', match.bestof) - - return MatchGroupInputUtil.getCommonTournamentVars(match) -end - ----@param match table ----@return table -function StarcraftMatchGroupInput._getVodStuff(match) - match.stream = Streams.processStreams(match) - match.vod = Logic.emptyOr(match.vod) - - return match -end - ----@param match table ----@return table -function StarcraftMatchGroupInput._getLinks(match) - match.links = { - preview = match.preview, - preview2 = match.preview2, - interview = match.interview, - interview2 = match.interview2, - review = match.review, - recap = match.recap, - lrthread = match.lrthread, - } - return match -end - ----@param match table ----@return table -function StarcraftMatchGroupInput._getExtraData(match) - local extradata - if Logic.readBool(match.ffa) then - match.extradata = getStarcraftFfaInputModule().getExtraData(match) - return match - end - - extradata = { - casters = MatchGroupInputUtil.readCasters(match, {noSort = true}), - headtohead = match.headtohead, - ffa = 'false', - } - - for prefix, vetoMap, vetoIndex in Table.iter.pairsByPrefix(match, 'veto') do - StarcraftMatchGroupInput._getVeto(extradata, vetoMap, match, prefix, vetoIndex) - end - - for subGroupIndex = 1, MAX_NUM_MAPS do - extradata['subGroup' .. subGroupIndex .. 'header'] - = StarcraftMatchGroupInput._getSubGroupHeader(subGroupIndex, match) - end - - match.extradata = extradata - - return match -end - ----@param extradata table ----@param map string ----@param match table ----@param prefix string ----@param vetoIndex integer -function StarcraftMatchGroupInput._getVeto(extradata, map, match, prefix, vetoIndex) - extradata[prefix] = map and mw.ext.TeamLiquidIntegration.resolve_redirect(map) or nil - extradata[prefix .. 'by'] = match['vetoplayer' .. vetoIndex] or match['vetoopponent' .. vetoIndex] - extradata[prefix .. 'displayname'] = match[prefix .. 'displayName'] -end - ----@param subGroupIndex integer ----@param match table ----@return string? -function StarcraftMatchGroupInput._getSubGroupHeader(subGroupIndex, match) - local header = Logic.emptyOr( - match['subGroup' .. subGroupIndex .. 'header'], - match['subgroup' .. subGroupIndex .. 'header'], - match['submatch' .. subGroupIndex .. 'header'] - ) - - return String.nilIfEmpty(header) -end - ----@param match table ----@return table -function StarcraftMatchGroupInput._adjustData(match) - --parse opponents + set base sumscores + determine match mode - match.mode = '' - match = StarcraftMatchGroupInput._opponentInput(match) - - --main processing done here - local subGroupIndex = 0 - for _, _, mapIndex in Table.iter.pairsByPrefix(match, 'map') do - match, subGroupIndex = StarcraftMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) - end - - --apply vodgames - for index = 1, MAX_NUM_VODGAMES do - local vodgame = match['vodgame' .. index] - if Logic.isNotEmpty(vodgame) and Logic.isNotEmpty(match['map' .. index]) then - match['map' .. index].vod = match['map' .. index].vod or vodgame - end - end - - match = StarcraftMatchGroupInput._matchWinnerProcessing(match) - - return match -end - ----@param match table ----@return table -function StarcraftMatchGroupInput._matchWinnerProcessing(match) - local bestof = tonumber(match.bestof) or DEFAULT_BEST_OF - local walkover = match.walkover or '' - local numberofOpponents = 0 - for opponentIndex = 1, MAX_NUM_OPPONENTS do - local opponent = match['opponent' .. opponentIndex] - if Logic.isNotEmpty(opponent) then - numberofOpponents = numberofOpponents + 1 - --determine opponent scores, status and placement - --determine MATCH winner, resulttype and walkover - --the following ignores the possibility of > 2 opponents - --as > 2 opponents is only possible in ffa - if Logic.isNotEmpty(walkover) then - if Logic.isNumeric(walkover) then - local numericWalkover = tonumber(walkover) - if numericWalkover == opponentIndex then - match.winner = opponentIndex - match.walkover = 'L' - opponent.status = 'W' - elseif numericWalkover == 0 then - match.winner = 0 - match.walkover = 'L' - opponent.status = 'L' - else - local score = string.upper(opponent.score or '') - opponent.status = CONVERT_STATUS_INPUT[score] or 'L' - end - elseif Table.includes(ALLOWED_STATUSES, string.upper(walkover)) then - if tonumber(match.winner or 0) == opponentIndex then - opponent.status = 'W' - else - opponent.status = CONVERT_STATUS_INPUT[string.upper(walkover)] or 'L' - end - else - local score = string.upper(opponent.score or '') - opponent.status = CONVERT_STATUS_INPUT[score] or 'L' - match.walkover = 'L' - end - opponent.score = -1 - match.finished = true - match.resulttype = 'default' - elseif CONVERT_STATUS_INPUT[string.upper(opponent.score or '')] then - if string.upper(opponent.score) == 'W' then - match.winner = opponentIndex - match.resulttype = 'default' - match.finished = true - opponent.score = -1 - opponent.status = 'W' - else - match.resulttype = 'default' - match.finished = true - match.walkover = CONVERT_STATUS_INPUT[string.upper(opponent.score)] - local score = string.upper(opponent.score) - opponent.status = CONVERT_STATUS_INPUT[score] - opponent.score = -1 - end - else - opponent.status = 'S' - opponent.score = tonumber(opponent.score) or - tonumber(opponent.sumscore) or -1 - if opponent.score > bestof / 2 then - match.finished = Logic.emptyOr(match.finished, true) - match.winner = tonumber(match.winner or '') or opponentIndex - end - end - - if Logic.readBool(match.cancelled) then - match.finished = true - if String.isEmpty(match.resulttype) and Logic.isEmpty(opponent.score) then - match.resulttype = 'np' - opponent.score = opponent.score or -1 - end - end - else - break - end - end - - StarcraftMatchGroupInput._determineWinnerIfMissing(match) - - for opponentIndex = 1, numberofOpponents do - local opponent = match['opponent' .. opponentIndex] - if match.winner == 'draw' or tonumber(match.winner) == 0 or - (match.opponent1.score == bestof / 2 and match.opponent1.score == match.opponent2.score) then - match.finished = true - match.winner = 0 - match.resulttype = 'draw' - end - - if tonumber(match.winner) == opponentIndex or - match.resulttype == 'draw' then - opponent.placement = 1 - elseif Logic.isNumeric(match.winner) then - opponent.placement = 2 - end - end - - return match -end - ----@param match table ----@return table -function StarcraftMatchGroupInput._determineWinnerIfMissing(match) - if Logic.readBool(match.finished) and Logic.isEmpty(match.winner) then - local scores = Array.mapIndexes(function(opponentIndex) - local opponent = match['opponent' .. opponentIndex] - if not opponent then - return nil - end - return match['opponent' .. opponentIndex].score or -1 end - ) - 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 - local maxIndexFound = false - for opponentIndex, score in pairs(scores) do - if maxIndexFound and score == maxScore then - match.winner = 0 - break - elseif score == maxScore then - maxIndexFound = true - match.winner = opponentIndex - end - end - end - end - - return match -end - ---OpponentInput functions - ----@param match table ----@return table -function StarcraftMatchGroupInput._opponentInput(match) - local opponentIndex = 1 - local opponent = match['opponent' .. opponentIndex] - - while opponentIndex <= MAX_NUM_OPPONENTS and Logic.isNotEmpty(opponent) do - opponent = Json.parseIfString(opponent) - - -- 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 - - -- Fix legacy winner - if Logic.isNotEmpty(opponent.win) then - if Logic.isEmpty(match.winner) then - match.winner = tostring(opponentIndex) - else - match.winner = '0' - end - opponent.win = nil - end - - -- Opponent processing (first part) - -- Sort out extradata - opponent.extradata = { - advantage = opponent.advantage, - penalty = opponent.penalty, - score2 = opponent.score2, - isarchon = opponent.isarchon - } - - --process input depending on type - if opponent.type == Opponent.solo then - opponent = - StarcraftMatchGroupInput.ProcessSoloOpponentInput(opponent) - elseif opponent.type == Opponent.duo then - opponent = StarcraftMatchGroupInput.ProcessDuoOpponentInput(opponent) - elseif opponent.type == Opponent.trio then - opponent = StarcraftMatchGroupInput.ProcessOpponentInput(opponent, 3) - elseif opponent.type == Opponent.quad then - opponent = StarcraftMatchGroupInput.ProcessOpponentInput(opponent, 4) - elseif opponent.type == Opponent.team then - opponent = StarcraftMatchGroupInput.ProcessTeamOpponentInput(opponent, match.date) - elseif opponent.type == Opponent.literal then - opponent = StarcraftMatchGroupInput.ProcessLiteralOpponentInput(opponent) - else - error('Unsupported Opponent Type') - end - - --set initial opponent sumscore - opponent.sumscore = - tonumber(opponent.extradata.advantage) or tonumber('-' .. (opponent.extradata.penalty or '')) - - local mode = OPPONENT_MODE_TO_PARTIAL_MATCH_MODE[opponent.type] - if mode == '2' and Logic.readBool(opponent.extradata.isarchon) then - mode = 'Archon' - end - - match.mode = match.mode .. (opponentIndex ~= 1 and '_' or '') .. mode - - match['opponent' .. opponentIndex] = opponent - - opponentIndex = opponentIndex + 1 - opponent = match['opponent' .. opponentIndex] - end - - return match -end - ----@param opponent table ----@return table -function StarcraftMatchGroupInput.ProcessSoloOpponentInput(opponent) - local name = Logic.emptyOr( - opponent.name, - opponent.p1, - opponent[1] - ) or '' - local link = Logic.emptyOr(opponent.link, Variables.varDefault(name .. '_page')) or name - link = mw.ext.TeamLiquidIntegration.resolve_redirect(link):gsub(' ', '_') - local faction = Logic.emptyOr(opponent.race, Variables.varDefault(name .. '_faction')) - local players = {} - local flag = Logic.emptyOr(opponent.flag, Variables.varDefault(name .. '_flag')) - players[1] = { - displayname = name, - name = link, - flag = Flags.CountryName(flag), - extradata = {faction = Faction.read(faction) or Faction.defaultFaction} - } - - return { - type = opponent.type, - name = link, - score = opponent.score, - extradata = opponent.extradata, - match2players = players - } -end - ----@param opponent table ----@return table -function StarcraftMatchGroupInput.ProcessDuoOpponentInput(opponent) - opponent.p1 = opponent.p1 or '' - opponent.p2 = opponent.p2 or '' - opponent.link1 = mw.ext.TeamLiquidIntegration.resolve_redirect(Logic.emptyOr( - opponent.p1link, - Variables.varDefault(opponent.p1 .. '_page') - ) or opponent.p1):gsub(' ', '_') - opponent.link2 = mw.ext.TeamLiquidIntegration.resolve_redirect(Logic.emptyOr( - opponent.p2link, - Variables.varDefault(opponent.p2 .. '_page') - ) or opponent.p2):gsub(' ', '_') - if Logic.readBool(opponent.extradata.isarchon) then - opponent.p1faction = Faction.read(opponent.race) or Faction.defaultFaction - opponent.p2faction = opponent.p1faction - else - opponent.p1faction = Faction.read(Logic.emptyOr( - opponent.p1race, - Variables.varDefault(opponent.p1 .. '_faction') - )) or Faction.defaultFaction - opponent.p2faction = Faction.read(Logic.emptyOr( - opponent.p2race, - Variables.varDefault(opponent.p2 .. '_faction') - )) or Faction.defaultFaction - end - - local players = {} - for playerIndex = 1, 2 do - local flag = Logic.emptyOr( - opponent['p' .. playerIndex .. 'flag'], - Variables.varDefault(opponent['p' .. playerIndex] .. '_flag') - ) - - players[playerIndex] = { - displayname = opponent['p' .. playerIndex], - name = opponent['link' .. playerIndex], - flag = Flags.CountryName(flag), - extradata = {faction = Faction.read(opponent['p' .. playerIndex .. 'faction']) or Faction.defaultFaction} - } - end - local name = opponent.link1 .. ' / ' .. opponent.link2 - - return { - type = opponent.type, - name = name, - score = opponent.score, - extradata = opponent.extradata, - match2players = players - } -end - ----@param opponent table ----@param playernumber integer ----@return table -function StarcraftMatchGroupInput.ProcessOpponentInput(opponent, playernumber) - local name = '' - - local players = {} - for playerIndex = 1, playernumber do - local playerName = opponent['p' .. playerIndex] or '' - local link = mw.ext.TeamLiquidIntegration.resolve_redirect(Logic.emptyOr( - opponent['p' .. playerIndex .. 'link'], - Variables.varDefault(playerName .. '_page') - ) or playerName):gsub(' ', '_') - local faction = Logic.emptyOr( - opponent['p' .. playerIndex .. 'race'], - Variables.varDefault(playerName .. '_faction'), - '' - ) - name = name .. (playerIndex ~= 1 and ' / ' or '') .. link - local flag = Logic.emptyOr( - opponent['p' .. playerIndex .. 'flag'], - Variables.varDefault((opponent['p' .. playerIndex] or '') .. '_flag') - ) - - players[playerIndex] = { - displayname = playerName, - name = link, - flag = Flags.CountryName(flag), - extradata = {faction = Faction.read(faction) or Faction.defaultFaction} - } - end - - return { - type = opponent.type, - name = name, - score = opponent.score, - extradata = opponent.extradata, - match2players = players - } -end - ----@param opponent table ----@return table -function StarcraftMatchGroupInput.ProcessLiteralOpponentInput(opponent) - local faction = opponent.race or '' - local flag = opponent.flag or '' - - local players = {} - if String.isNotEmpty(faction) or String.isNotEmpty(flag) then - players[1] = { - displayname = opponent[1], - name = '', - flag = Flags.CountryName(flag), - extradata = {faction = Faction.read(faction) or Faction.defaultFaction} - } - local extradata = opponent.extradata or {} - extradata.hasFactionOrFlag = true - end - - return { - type = opponent.type, - name = opponent[1], - score = opponent.score, - extradata = opponent.extradata, - match2players = players - } -end - ----@param playerData string|table? ----@return table -function StarcraftMatchGroupInput._getManuallyEnteredPlayers(playerData) - local players = {} - playerData = Json.parseIfString(playerData) or {} - for playerIndex = 1, MAX_NUM_PLAYERS do - local name = mw.ext.TeamLiquidIntegration.resolve_redirect(Logic.emptyOr( - playerData['p' .. playerIndex .. 'link'], - playerData['p' .. playerIndex] - ) or ''):gsub(' ', '_') - if String.isNotEmpty(name) then - players[playerIndex] = { - name = name, - displayname = playerData['p' .. playerIndex], - flag = Flags.CountryName(playerData['p' .. playerIndex .. 'flag']), - extradata = { - faction = playerData['p' .. playerIndex .. 'race'], - position = playerIndex - } - } - else - break - end - end - - return players -end - ----@param teamName string ----@return table -function StarcraftMatchGroupInput._getPlayersFromVariables(teamName) - local players = {} - for playerIndex = 1, MAX_NUM_PLAYERS do - local prefix = teamName .. '_p' .. playerIndex - local name = Variables.varDefault(prefix) - if Logic.isNotEmpty(name) then - ---@cast name -nil - local player = { - name = name:gsub(' ', '_'), - displayname = Variables.varDefault(prefix .. 'dn'), - flag = Flags.CountryName(Variables.varDefault(prefix .. 'flag')), - extradata = {faction = Variables.varDefault(prefix .. 'faction')} - } - if player.displayname then - Variables.varDefine(player.displayname .. '_page', player.name) - end - table.insert(players, player) - else - break - end - end - return players -end - ----@param opponent table ----@param date string? ----@return table -function StarcraftMatchGroupInput.ProcessTeamOpponentInput(opponent, date) - local name, icon, iconDark - - opponent.template = string.lower(Logic.emptyOr(opponent.template, opponent[1], 'tbd')--[[@as string]]) - - name, icon, iconDark, opponent.template = StarcraftMatchGroupInput._processTeamTemplateInput(opponent.template, date) - - local players = StarcraftMatchGroupInput._getManuallyEnteredPlayers(opponent.players) - if Logic.isEmpty(players) then - players = StarcraftMatchGroupInput._getPlayersFromVariables(name) - end - - return { - icon = icon, - icondark = iconDark, - template = opponent.template, - type = opponent.type, - name = name:gsub(' ', '_'), - score = opponent.score, - extradata = opponent.extradata, - match2players = players - } -end - ----@param template string? ----@param date string? ----@return string? ----@return string? ----@return string? ----@return string -function StarcraftMatchGroupInput._processTeamTemplateInput(template, date) - local icon, name, iconDark - template = string.lower(template or ''):gsub('_', ' ') - if String.isNotEmpty(template) and template ~= 'noteam' and - mw.ext.TeamTemplate.teamexists(template) then - - local templateData = mw.ext.TeamTemplate.raw(template, date) - icon = templateData.image - iconDark = templateData.imagedark - if String.isEmpty(icon) then - icon = templateData.legacyimage - end - if String.isEmpty(iconDark) then - iconDark = templateData.legacyimagedark - end - name = templateData.page - template = templateData.templatename or template - end - - return name, icon, iconDark, template -end - ---MapInput functions - ----@param match table ----@param mapIndex integer ----@param subGroupIndex integer ----@return table ----@return integer -function StarcraftMatchGroupInput._mapInput(match, mapIndex, subGroupIndex) - local map = Json.parseIfString(match['map' .. mapIndex]) - --redirect maps - if map.map ~= 'TBD' then - map.map = mw.ext.TeamLiquidIntegration.resolve_redirect(map.map or '') - end - - -- set initial extradata for maps - map.extradata = { - comment = map.comment, - displayname = map.mapDisplayName, - header = map.header, - server = map.server, - } - - -- determine score, resulttype, walkover and winner - map = StarcraftMatchGroupInput._mapWinnerProcessing(map) - - -- get participants data for the map + get map mode + winnerfaction and loserfaction - --(w/l faction stuff only for 1v1 maps) - map = StarcraftMatchGroupInput.ProcessPlayerMapData(map, match, 2) - - -- set sumscore to 0 if it isn't a number - if Logic.isEmpty(match.opponent1.sumscore) then - match.opponent1.sumscore = 0 - end - if Logic.isEmpty(match.opponent2.sumscore) then - match.opponent2.sumscore = 0 - end - - --adjust sumscore for winner opponent - if (tonumber(map.winner or 0) or 0) > 0 then - match['opponent' .. map.winner].sumscore = - match['opponent' .. map.winner].sumscore + 1 - end - - -- handle subgroup stuff if team match - if string.find(match.mode, 'team') then - map.subgroup = tonumber(map.subgroup or '') - if map.subgroup then - subGroupIndex = map.subgroup - else - subGroupIndex = subGroupIndex + 1 - map.subgroup = subGroupIndex - end - end - - match['map' .. mapIndex] = map - - return match, subGroupIndex -end - ----@param map table ----@return table -function StarcraftMatchGroupInput._mapWinnerProcessing(map) - map.scores = {} - local hasManualScores = false - local indexedScores = {} - for scoreIndex = 1, MAX_NUM_OPPONENTS do - -- read scores - local score = map['score' .. scoreIndex] - local obj = {} - if Logic.isNotEmpty(score) then - hasManualScores = true - score = CONVERT_STATUS_INPUT[score] or score - if Logic.isNumeric(score) then - obj.status = 'S' - obj.score = score - elseif Table.includes(ALLOWED_STATUSES, score) then - obj.status = score - obj.score = -1 - end - table.insert(map.scores, score) - indexedScores[scoreIndex] = obj - else - break - end - end - - if hasManualScores then - for scoreIndex, _ in Table.iter.spairs(indexedScores, StarcraftMatchGroupInput._placementSortFunction) do - if not tonumber(map.winner or '') then - map.winner = scoreIndex - else - break - end - end - else - local winnerInput = tonumber(map.winner) - if Logic.isNotEmpty(map.walkover) then - local walkoverInput = tonumber(map.walkover) - if walkoverInput == 1 then - map.winner = 1 - elseif walkoverInput == 2 then - map.winner = 2 - elseif walkoverInput == 0 then - map.winner = 0 - end - map.walkover = Table.includes(ALLOWED_STATUSES, map.walkover) and map.walkover or 'L' - map.scores = {-1, -1} - map.resulttype = 'default' - elseif map.winner == 'skip' then - map.scores = {0, 0} - map.scores = {-1, -1} - map.resulttype = 'np' - elseif winnerInput == 1 then - map.scores = {1, 0} - elseif winnerInput == 2 then - map.scores = {0, 1} - elseif winnerInput == 0 or map.winner == 'draw' then - map.scores = {0.5, 0.5} - map.resulttype = 'draw' - end - end - - return map -end - ----@param map table ----@param match table ----@param numberOfOpponents integer ----@return table -function StarcraftMatchGroupInput.ProcessPlayerMapData(map, match, numberOfOpponents) - local participants = {} - local mapMode = '' - - for opponentIndex = 1, numberOfOpponents do - local opponentMapMode - if match['opponent' .. opponentIndex].type == Opponent.team then - local players = match['opponent' .. opponentIndex].match2players - participants, opponentMapMode = StarcraftMatchGroupInput._processTeamPlayerMapData( - players or {}, - map, - opponentIndex, - participants - ) - elseif match['opponent' .. opponentIndex].type == Opponent.literal then - opponentMapMode = 'Literal' - elseif - match['opponent' .. opponentIndex].type == Opponent.duo and - Logic.readBool(match['opponent' .. opponentIndex].extradata.isarchon) - then - opponentMapMode = 'Archon' - local players = match['opponent' .. opponentIndex].match2players - if Table.isEmpty(players) then - break - else - participants = StarcraftMatchGroupInput._processArchonPlayerMapData( - players, - map, - opponentIndex, - participants - ) - end - else - opponentMapMode = tonumber(OPPONENT_MODE_TO_PARTIAL_MATCH_MODE[match['opponent' .. opponentIndex].type]) - local players = match['opponent' .. opponentIndex].match2players - if Table.isEmpty(players) then - break - else - participants = StarcraftMatchGroupInput._processDefaultPlayerMapData( - players, - map, - opponentIndex, - participants - ) - end - end - mapMode = mapMode .. (opponentIndex ~= 1 and 'v' or '') .. opponentMapMode - - if mapMode == '1v1' and numberOfOpponents == 2 then - local opponentFactions, playerNameArray = StarcraftMatchGroupInput._fetchOpponentMapFactionsAndNames(participants) - if tonumber(map.winner or 0) == 1 then - map.extradata.winnerfaction = opponentFactions[1] - map.extradata.loserfaction = opponentFactions[2] - elseif tonumber(map.winner or 0) == 2 then - map.extradata.winnerfaction = opponentFactions[2] - map.extradata.loserfaction = opponentFactions[1] - end - map.extradata.opponent1 = playerNameArray[1] - map.extradata.opponent2 = playerNameArray[2] - end - map.patch = Variables.varDefault('tournament_patch', '') - end - - map.mode = mapMode - - map.participants = participants - return map -end - ----@param participants table ----@return table ----@return table -function StarcraftMatchGroupInput._fetchOpponentMapFactionsAndNames(participants) - local opponentFactions, playerNameArray = {}, {} - for participantKey, participantData in pairs(participants) do - local opponentIndex = tonumber(string.sub(participantKey, 1, 1)) - -- opponentIx can not be nil due to the format of the participants keys - ---@cast opponentIndex -nil - opponentFactions[opponentIndex] = participantData.faction - playerNameArray[opponentIndex] = participantData.player - end - - return opponentFactions, playerNameArray -end - ----@param players table ----@param map table ----@param opponentIndex integer ----@param participants table ----@return table -function StarcraftMatchGroupInput._processDefaultPlayerMapData(players, map, opponentIndex, participants) - map['t' .. opponentIndex .. 'p1race'] = Logic.emptyOr( - map['t' .. opponentIndex .. 'p1race'], - map['race' .. opponentIndex] - ) - - for playerIndex = 1, #players do - local faction = map['t' .. opponentIndex .. 'p' .. playerIndex .. 'race'] - participants[opponentIndex .. '_' .. playerIndex] = { - faction = Faction.read(faction or players[playerIndex].extradata.faction) or Faction.defaultFaction, - player = players[playerIndex].name - } - end - - return participants -end - ----@param players table ----@param map table ----@param opponentIndex integer ----@param participants table ----@return table -function StarcraftMatchGroupInput._processArchonPlayerMapData(players, map, opponentIndex, participants) - local faction = Logic.emptyOr( - map['opponent' .. opponentIndex .. 'race'], - map['race' .. opponentIndex], - players[1].extradata.faction - ) - participants[opponentIndex .. '_1'] = { - faction = Faction.read(faction) or Faction.defaultFaction, - player = players[1].name - } - - participants[opponentIndex .. '_2'] = { - faction = Faction.read(faction) or Faction.defaultFaction, - player = players[2].name - } - - return participants -end - ----@param players table ----@param map table ----@param opponentIndex integer ----@param participants table ----@return table ----@return string|integer -function StarcraftMatchGroupInput._processTeamPlayerMapData(players, map, opponentIndex, participants) - local amountOfTbds = 0 - local playerData = {} - - local numberOfPlayers = 0 - for prefix, playerInput, playerIndex in Table.iter.pairsByPrefix(map, 't' .. opponentIndex .. 'p') do - numberOfPlayers = numberOfPlayers + 1 - if playerInput:lower() == TBD or playerInput:lower() == TBA then - amountOfTbds = amountOfTbds + 1 - else - local link = Logic.emptyOr(map[prefix .. 'link'], Variables.varDefault(playerInput .. '_page')) or playerInput - link = mw.ext.TeamLiquidIntegration.resolve_redirect(link):gsub(' ', '_') - - local faction = Logic.readBool(map['opponent' .. opponentIndex .. 'archon']) - and Logic.emptyOr(map['t' .. opponentIndex .. 'race'], map['opponent' .. opponentIndex .. 'race']) - or map[prefix .. 'race'] - - playerData[link] = { - faction = Faction.read(faction), - position = playerIndex, - displayName = playerInput, - } - end - end - - local addToParticipants = function(currentPlayer, player, playerIndex) - local faction = currentPlayer.faction or (player.extradata or {}).faction or Faction.defaultFaction - - participants[opponentIndex .. '_' .. playerIndex] = { - faction = faction, - player = player.name, - position = currentPlayer.position, - flag = Flags.CountryName(player.flag), - } - end - - Array.forEach(players, function(player, playerIndex) - local currentPlayer = playerData[player.name] - if not currentPlayer then return end - - addToParticipants(currentPlayer, player, playerIndex) - playerData[player.name] = nil - end) - - -- if we have players not already in the match2players insert them - -- this is to break conditional data loops between match2 and teamCard/HDB - Table.iter.forEachPair(playerData, function(playerLink, player) - local faction = player.faction or Faction.defaultFaction - table.insert(players, { - name = playerLink, - displayname = player.displayName, - extradata = {faction = faction}, - }) - addToParticipants(player, players[#players], #players) - end) - - Array.forEach(Array.range(1, amountOfTbds), function(tbdIndex) - participants[opponentIndex .. '_' .. (#players + tbdIndex)] = { - faction = Faction.defaultFaction, - player = TBD:upper(), - } - end) - - if numberOfPlayers == 2 and Logic.readBool(map['opponent' .. opponentIndex .. 'archon']) then - return participants, 'Archon' - elseif numberOfPlayers == 2 and Logic.readBool(map['opponent' .. opponentIndex .. 'duoSpecial']) then - return participants, '2S' - elseif numberOfPlayers == 4 and Logic.readBool(map['opponent' .. opponentIndex .. 'quadSpecial']) then - return participants, '4S' - end - - return participants, numberOfPlayers -end - --- function to sort out winner/placements ----@param tbl table ----@param key1 string|integer ----@param key2 string|integer ----@return boolean -function StarcraftMatchGroupInput._placementSortFunction(tbl, key1, key2) - local opponent1 = tbl[key1] - local opponent2 = tbl[key2] - local opponent1Norm = opponent1.status == 'S' - local opponent2Norm = opponent2.status == 'S' - if opponent1Norm then - if opponent2Norm then - return tonumber(opponent1.score) > tonumber(opponent2.score) - else return true end - else - if opponent2Norm then return false - elseif opponent1.status == 'W' then return true - elseif Table.includes(DEFAULT_LOSS_STATUSES, opponent1.status) then return false - elseif opponent2.status == 'W' then return false - elseif Table.includes(DEFAULT_LOSS_STATUSES, opponent2.status) then return true - else return true end - end -end - -return StarcraftMatchGroupInput diff --git a/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft_ffa.lua b/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft_ffa.lua index 8f033448b9e..e179dfa90a0 100644 --- a/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft_ffa.lua +++ b/components/match2/commons/starcraft_starcraft2/match_group_input_starcraft_ffa.lua @@ -6,547 +6,428 @@ -- Please see https://github.com/Liquipedia/Lua-Modules to contribute -- +local Array = require('Module:Array') +local DateExt = require('Module:Date/Ext') +local FnUtil = require('Module:FnUtil') local Logic = require('Module:Logic') local Lua = require('Module:Lua') -local String = require('Module:StringUtils') +local Operator = require('Module:Operator') local Table = require('Module:Table') - -local StarcraftMatchGroupInput = Lua.import('Module:MatchGroup/Input/Starcraft/deprecated') -local Opponent = Lua.import('Module:Opponent') - -local ALLOWED_STATUSES = {'W', 'FF', 'DQ', 'L'} -local ALLOWED_STATUSES2 = {W = 'W', FF = 'FF', L = 'L', DQ = 'DQ', ['-'] = 'L'} -local MAX_NUM_VODGAMES = 9 -local MODES2 = { - solo = '1', - duo = '2', - trio = '3', - quad = '4', - team = 'team', - literal = 'literal' -} -local ALLOWED_BG = { - up = 'up', - down = 'down', - stayup = 'stayup', - staydown = 'staydown', - stay = 'stay', - mid = 'stay', - drop = 'down', - proceed = 'up', -} -local _TBD_STRINGS = { - 'definitions', - 'tbd' +local Variables = require('Module:Variables') + +local MatchGroupInputUtil = Lua.import('Module:MatchGroup/Input/Util') +local Streams = Lua.import('Module:Links/Stream') + +local StarcraftMatchGroupInput = Lua.import('Module:MatchGroup/Input/Starcraft') +local BaseMatchFunctions = StarcraftMatchGroupInput.MatchFunctions +local BaseMapFunctions = StarcraftMatchGroupInput.MapFunctions + +local ADVANCE_BACKGROUND = 'up' +local DEFUALT_BACKGROUND = 'down' +local VALID_BACKGROUNDS = { + ADVANCE_BACKGROUND, + DEFUALT_BACKGROUND, + 'stayup', + 'staydown', + 'stay', } -local _BESTOF_DUMMY = 9999 -local _DEFAULT_WIN_SCORE_VALUE = 9999 -local _PLACEMENT_DUMMY = 99 - -local StarcraftFfaInput = {} - -function StarcraftFfaInput.adjustData(match) - local noscore = Logic.readBool(match.noscore) or Logic.readBool(match.nopoints) - match.noscore = noscore - - --process pbg entries and set them into match.pbg (will get merged into extradata later on) - match = StarcraftFfaInput._getPbg(match) - - --parse opponents + determine match mode + set initial stuff - match.mode = '' - local numberOfOpponents - match, numberOfOpponents = StarcraftFfaInput._opponentInput(match, noscore) - - --indicate it is an FFA match - match.mode = match.mode .. 'ffa' - - --main processing done here - local subgroup = 0 - for mapKey, map in Table.iter.pairsByPrefix(match, 'map') do - if - Logic.isNotEmpty(map.opponent1placement) or Logic.isNotEmpty(map.placement1) - or Logic.isNotEmpty(map.points1) or Logic.isNotEmpty(map.opponent1points) - or Logic.isNotEmpty(map.score1) or Logic.isNotEmpty(map.opponent1score) - or String.isNotEmpty(map.map) - then - match, subgroup = StarcraftFfaInput._mapInput(match, mapKey, subgroup, noscore, numberOfOpponents) - else - match[mapKey] = nil - break - end +local MODE_FFA = 'FFA' +local TBD = 'TBD' +local ASSUME_FINISHED_AFTER = MatchGroupInputUtil.ASSUME_FINISHED_AFTER +local NOW = os.time() + +local StarcraftFfaMatchGroupInput = {} +local MatchFunctions = {} +local MapFunctions = {} + +---@param match table +---@param options table? +---@return table +function StarcraftFfaMatchGroupInput.processMatch(match, options) + Table.mergeInto(match, BaseMatchFunctions.readDate(match.date)) + + match.stream = Streams.processStreams(match) + match.vod = Logic.nilIfEmpty(match.vod) + match.links = BaseMatchFunctions.getLinks(match) + + MatchGroupInputUtil.getCommonTournamentVars(match) + + local opponents = BaseMatchFunctions.readOpponents(match) + + local games = MatchFunctions.extractMaps(match, opponents) + + local finishedInput = match.finished --[[@as string?]] + match.bestof = tonumber(match.firstto) or tonumber(match.bestof) + + match.finished = MatchFunctions.isFinished(match, opponents) + match.mode = MODE_FFA + + if MatchGroupInputUtil.isNotPlayed(match.winner, finishedInput) then + match.finished = true + match.resulttype = MatchGroupInputUtil.RESULT_TYPE.NOT_PLAYED + match.extradata = {ffa = 'true'} + return match end - --apply vodgames - for index = 1, MAX_NUM_VODGAMES do - local vodgame = match['vodgame' .. index] - if Logic.isNotEmpty(vodgame) and Logic.isNotEmpty(match['map' .. index]) then - match['map' .. index].vod = match['map' .. index].vod or vodgame - end + match.pbg = MatchFunctions.getPBG(match) + + local autoScoreFunction = MatchGroupInputUtil.canUseAutoScore(match, games) + and not Logic.readBool(match.noscore) + and MatchFunctions.calculateMatchScore(games, opponents) + or nil + + 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) + + Array.forEach(opponents, function(opponent) + opponent.placement = tonumber(opponent.placement) + end) + + if match.finished then + match.resulttype = MatchGroupInputUtil.getResultType(match.winner, finishedInput, opponents) + match.walkover = MatchGroupInputUtil.getWalkover(match.resulttype, opponents) + StarcraftFfaMatchGroupInput._setPlacements(opponents) + match.winner = StarcraftFfaMatchGroupInput._getWinner(opponents, match.winner, match.resulttype) end - match = StarcraftFfaInput._matchWinnerProcessing(match, numberOfOpponents, noscore) + Array.forEach(opponents, function(opponent) + opponent.extradata = opponent.extradata or {} + opponent.extradata.noscore = Logic.readBool(match.noscore) + opponent.extradata.bg = MatchFunctions.readBg(opponent.bg) + or match.pbg[opponent.placement] + or DEFUALT_BACKGROUND + + -- todo: get rid of the damn alias ... + if Logic.isEmpty(opponent.advances) and Logic.isNotEmpty(opponent.win) then + opponent.advances = opponent.win + mw.ext.TeamLiquidIntegration.add_category('Pages with ffa matches using `win=` in opponents') + end + + opponent.extradata.advances = Logic.readBool(opponent.advances) + or (match.bestof and (opponent.score or 0) >= match.bestof) + or opponent.extradata.bg == ADVANCE_BACKGROUND + or opponent.placement == 1 + end) + + match.opponents = opponents + match.games = games + + match.extradata = MatchFunctions.getExtraData(match) return match end +---@param match table +---@param opponents {score: integer?}[] +---@return boolean +function MatchFunctions.isFinished(match, opponents) + if MatchGroupInputUtil.isNotPlayed(match.winner, match.finished) then + return true + end -function StarcraftFfaInput._getPbg(match) - local pbg = {} + local finished = Logic.readBoolOrNil(match.finished) + if finished ~= nil then + return finished + end - local advancecount = tonumber(match.advancecount or 0) or 0 - if advancecount > 0 then - for index = 1, advancecount do - pbg[index] = 'up' - end + -- If a winner has been set + if Logic.isNotEmpty(match.winner) then + return true end - local index = 1 - while StarcraftFfaInput._bgClean(match['pbg' .. index]) ~= '' do - pbg[index] = StarcraftFfaInput._bgClean(match['pbg' .. index]) - match['pbg' .. index] = nil - index = index + 1 + -- 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 + return true end - match.pbg = pbg + return MatchFunctions.placementHasBeenSet(opponents) +end - return match +---@param opponents table[] +---@return boolean +function MatchFunctions.placementHasBeenSet(opponents) + return Array.any(opponents, function(opponent) return Logic.isNumeric(opponent.placement) end) end ---helper function -function StarcraftFfaInput._bgClean(pbg) - local pbgInput = pbg - pbg = string.lower(pbg or '') - if pbg == '' then - return '' - else - pbg = ALLOWED_BG[pbg] - - if not pbg then - error('Bad bg/pbg entry "' .. pbgInput .. '"') - end +---@param maps table[] +---@param opponents table[] +---@return fun(opponentIndex: integer): integer? +function MatchFunctions.calculateMatchScore(maps, opponents) + return function(opponentIndex) + local opponent = opponents[opponentIndex] + local sum = (opponent.extradata.advantage or 0) - (opponent.extradata.penalty or 0) + Array.forEach(maps, function(map) + sum = sum + ((map.scores or {})[opponentIndex] or 0) + end) + return sum + end +end + +---@param match table +---@param opponents table[] +---@return table[] +function MatchFunctions.extractMaps(match, opponents) + local hasScores = not Logic.readBool(match.noscore) + local maps = {} + for mapKey, mapInput in Table.iter.pairsByPrefix(match, 'map', {requireIndex = true}) do + local map = MapFunctions.readMap(mapInput, #opponents, hasScores) + + map.participants = BaseMapFunctions.getParticipants(mapInput, opponents) - return pbg + map.mode = BaseMapFunctions.getMode(mapInput, map.participants, opponents) + + table.insert(maps, map) + match[mapKey] = nil end + + return maps end ---function to get extradata for storage -function StarcraftFfaInput.getExtraData(match) +---@param match table +---@return table +function MatchFunctions.getExtraData(match) local extradata = { - featured = match.featured, - veto1by = String.nilIfEmpty(match.vetoplayer1) or match.vetoopponent1, - veto1 = match.veto1, - veto2by = String.nilIfEmpty(match.vetoplayer2) or match.vetoopponent2, - veto2 = match.veto2, - veto3by = String.nilIfEmpty(match.vetoplayer3) or match.vetoopponent3, - veto3 = match.veto3, - veto4by = String.nilIfEmpty(match.vetoplayer4) or match.vetoopponent4, - veto4 = match.veto4, - veto5by = String.nilIfEmpty(match.vetoplayer5) or match.vetoopponent5, - veto5 = match.veto5, - veto6by = String.nilIfEmpty(match.vetoplayer6) or match.vetoopponent6, - veto6 = match.veto6, + casters = MatchGroupInputUtil.readCasters(match, {noSort = true}), ffa = 'true', - noscore = match.noscore, - showplacement = match.showplacement, + noscore = tostring(Logic.readBool(match.noscore)), + showplacement = Logic.readBoolOrNil(match.showplacement), } - --add the pbg stuff - for key, item in pairs(match.pbg) do - extradata['pbg' .. key] = item + for prefix, vetoMap, vetoIndex in Table.iter.pairsByPrefix(match, 'veto') do + BaseMatchFunctions.getVeto(extradata, vetoMap, match, prefix, vetoIndex) end - match.pbg = nil + + Array.forEach(match.pbg, function(value, key) extradata['pbg' .. key] = value end) return extradata end --- function to sort out placements -function StarcraftFfaInput._placementSortFunction(tbl, key1, key2) - local op1 = tbl[key1] - local op2 = tbl[key2] - return tonumber(op1) > tonumber(op2) +---@param match table +---@return table +function MatchFunctions.getPBG(match) + local advanceCount = tonumber(match.advancecount) or 0 + + return Array.mapIndexes(function(pbgIndex) + return MatchFunctions.readBg(match['pbg' .. pbgIndex]) + or (pbgIndex <= advanceCount and ADVANCE_BACKGROUND) + or nil + end) end ---[[ - -Match Winner, Walkover, Placement, Resulttype, Status functions - -]]-- -function StarcraftFfaInput._matchWinnerProcessing(match, numberOfOpponents, noscore) - local bestof = tonumber(match.firstto) or tonumber(match.bestof) or _BESTOF_DUMMY - match.bestof = bestof - local walkover = match.walkover - local IndScore = {} - for opponentIndex = 1, numberOfOpponents do - local opponent = match['opponent' .. opponentIndex] - --determine opponent scores, status - --determine MATCH winner, resulttype and walkover - if walkover then - if Logic.isNumeric(walkover) then - local numericWalkover = tonumber(walkover) - if numericWalkover == opponentIndex then - match.winner = opponentIndex - match.walkover = 'L' - opponent.status = 'W' - IndScore[opponentIndex] = _DEFAULT_WIN_SCORE_VALUE - elseif numericWalkover == 0 then - match.winner = 0 - match.walkover = 'L' - opponent.status = 'L' - IndScore[opponentIndex] = -1 - else - opponent.status = - ALLOWED_STATUSES2[string.upper(opponent.score or '')] or 'L' - IndScore[opponentIndex] = -1 - end - elseif Table.includes(ALLOWED_STATUSES, string.upper(walkover)) then - if tonumber(match.winner or 0) == opponentIndex then - IndScore[opponentIndex] = _DEFAULT_WIN_SCORE_VALUE - opponent.status = 'W' - else - IndScore[opponentIndex] = -1 - opponent.status = ALLOWED_STATUSES2[string.upper(walkover)] or 'L' - end - else - opponent.status = - ALLOWED_STATUSES2[string.upper(opponent.score or '')] or 'L' - match.walkover = 'L' - if ALLOWED_STATUSES2[string.upper(opponent.score or '')] == 'W' then - IndScore[opponentIndex] = _DEFAULT_WIN_SCORE_VALUE - else - IndScore[opponentIndex] = -1 - end - end - opponent.score = -1 - match.finished = 'true' - match.resulttype = 'default' - elseif Logic.readBool(match.cancelled) then - match.resulttype = 'np' - match.finished = 'true' - opponent.score = -1 - IndScore[opponentIndex] = -1 - elseif ALLOWED_STATUSES2[string.upper(opponent.score or '')] then - if string.upper(opponent.score) == 'W' then - match.winner = opponentIndex - match.resulttype = 'default' - match.finished = 'true' - opponent.score = -1 - opponent.status = 'W' - IndScore[opponentIndex] = _DEFAULT_WIN_SCORE_VALUE - else - match.resulttype = 'default' - match.finished = 'true' - match.walkover = ALLOWED_STATUSES2[string.upper(opponent.score)] - opponent.status = - ALLOWED_STATUSES2[string.upper(opponent.score)] - opponent.score = -1 - IndScore[opponentIndex] = -1 - end - else - opponent.status = 'S' - opponent.score = tonumber(opponent.score or '') - or tonumber(opponent.sumscore) or -1 - IndScore[opponentIndex] = opponent.score - end - end +---@param input string? +---@return string? +function MatchFunctions.readBg(input) + if Logic.isEmpty(input) then return nil end + ---@cast input -nil - match = StarcraftFfaInput._matchPlacements(match, numberOfOpponents, noscore, IndScore) + input = string.lower(input) + assert(Table.includes(VALID_BACKGROUNDS, input), 'Bad bg/pbg entry "' .. input .. '"') - return match + return input end ---determine placements and winner (if not already set) -function StarcraftFfaInput._matchPlacements(match, numberOfOpponents, noscore, IndScore) - local counter = 0 - local temp = {} - match.finished = Logic.isNotEmpty(match.finished) - and match.finished ~= 'false' and match.finished ~= '0' - and 'true' or nil - - if not noscore then - for scoreIndex, score in Table.iter.spairs(IndScore, StarcraftFfaInput._placementSortFunction) do - local opponent = match['opponent' .. scoreIndex] - counter = counter + 1 - if counter == 1 and Logic.isEmpty(match.winner) then - if match.finished or score >= match.bestof then - match.winner = scoreIndex - match.finished = 'true' - opponent.placement = tonumber(opponent.placement or '') or counter - opponent.extradata.advances = true - opponent.extradata.bg = String.nilIfEmpty(opponent.extradata.bg) - or match.pbg[opponent.placement] - or 'down' - temp.place = counter - temp.score = IndScore[scoreIndex] - else - break - end - elseif match.finished then - if temp.score == score then - opponent.placement = tonumber(opponent.placement or '') or temp.place - else - opponent.placement = tonumber(opponent.placement or '') or counter - temp.place = counter - temp.score = IndScore[scoreIndex] - end - opponent.extradata.bg = String.nilIfEmpty(opponent.extradata.bg) - or match.pbg[opponent.placement] - or 'down' - if opponent.extradata.bg == 'up' then - opponent.extradata.advances = true - end - else - break - end - end - elseif tonumber(match.winner or '') then - for oppIndex = 1, numberOfOpponents do - local opponent = match['opponent' .. oppIndex] - opponent.placement = tonumber(opponent.placement or '') or _PLACEMENT_DUMMY - if opponent.placement == _PLACEMENT_DUMMY and tonumber(match.winner) == oppIndex then - opponent.placement = 1 - end - end - else - for oppIndex = 1, numberOfOpponents do - local opponent = match['opponent' .. oppIndex] - opponent.placement = tonumber(opponent.placement or '') or _PLACEMENT_DUMMY - if opponent.placement == 1 then - match.winner = oppIndex - end - end +---@param mapInput table +---@param opponentCount integer +---@param hasScores boolean +---@return table +function MapFunctions.readMap(mapInput, opponentCount, hasScores) + local mapName = mapInput.map + if mapName and mapName:upper() ~= TBD then + mapName = mw.ext.TeamLiquidIntegration.resolve_redirect(mapInput.map) + elseif mapName then + mapName = TBD end - if match.finished then - for oppIndex = 1, numberOfOpponents do - local opponent = match['opponent' .. oppIndex] - opponent.extradata.bg = String.nilIfEmpty(opponent.extradata.bg) - or match.pbg[opponent.placement] - or 'down' - end + local map = { + map = mapName, + patch = Variables.varDefault('tournament_patch', ''), + vod = mapInput.vod, + extradata = { + comment = mapInput.comment, + displayname = mapInput.mapDisplayName, + } + } + + if MatchGroupInputUtil.isNotPlayed(mapInput.winner, mapInput.finished) then + map.finished = true + map.resulttype = MatchGroupInputUtil.RESULT_TYPE.NOT_PLAYED + map.scores = {} + map.statuses = {} + return map end - return match -end + -- todo: get rid of the damn alias ... + Array.forEach(Array.range(1, opponentCount), function(opponentIndex) + if not map['score' .. opponentIndex] and map['points' .. opponentIndex] then + mw.ext.TeamLiquidIntegration.add_category('Pages with ffa matches using `pointsX=` in maps') + map['score' .. opponentIndex] = map['points' .. opponentIndex] + end + end) ---[[ + local opponentsInfo = Array.map(Array.range(1, opponentCount), function(opponentIndex) + return MapFunctions.getOpponentInfo(mapInput, opponentIndex, hasScores) + end) -OpponentInput functions + map.scores = Array.map(opponentsInfo, Operator.property('score')) -]]-- -function StarcraftFfaInput._opponentInput(match, noscore) - local numberOfOpponents - for opponentKey, opponent, opponentIndex in Table.iter.pairsByPrefix(match, 'opponent') do - numberOfOpponents = opponentIndex + map.finished = MapFunctions.isFinished(mapInput, opponentCount, hasScores) + if map.finished then + map.resulttype = MatchGroupInputUtil.getResultType(mapInput.winner, mapInput.finished, opponentsInfo) + map.walkover = MatchGroupInputUtil.getWalkover(map.resulttype, opponentsInfo) + StarcraftFfaMatchGroupInput._setPlacements(opponentsInfo, not hasScores) + map.winner = StarcraftFfaMatchGroupInput._getWinner(opponentsInfo, mapInput.winner, map.resulttype) + end - -- 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 + Array.forEach(opponentsInfo, function(opponentInfo, opponentIndex) + map.extradata['placement' .. opponentIndex] = opponentInfo.placement + end) - local bg = StarcraftFfaInput._bgClean(opponent.bg) - opponent.bg = nil - local advances = opponent.advance - if String.isEmpty(advances) then - advances = opponent.win - if String.isEmpty(advances) then - advances = opponent.advances - end - end - advances = Logic.isNotEmpty(advances) and advances ~= 'false' and advances ~= '0' - - --opponent processing (first part) - --sort out extradata - opponent.extradata = { - advances = advances, - advantage = opponent.advantage, - bg = bg, - isarchon = opponent.isarchon, - noscore = noscore, - score2 = opponent.score2, - } + return map +end - --set initial opponent sumscore - opponent.sumscore = - tonumber(opponent.extradata.advantage or '') or '' - - -- read placement input for the opponent to overwrite - -- the one set by the default opponent input processing - -- as that sets placements assuming it is a non ffa match - local inputPlace = opponent.placement - --process input depending on type - if opponent.type == Opponent.solo then - opponent = StarcraftMatchGroupInput.ProcessSoloOpponentInput(opponent) - elseif opponent.type == Opponent.duo then - opponent = StarcraftMatchGroupInput.ProcessDuoOpponentInput(opponent) - elseif opponent.type == Opponent.trio then - opponent = StarcraftMatchGroupInput.ProcessOpponentInput(opponent, 3) - elseif opponent.type == Opponent.quad then - opponent = StarcraftMatchGroupInput.ProcessOpponentInput(opponent, 4) - elseif opponent.type == Opponent.team then - opponent = StarcraftMatchGroupInput.ProcessTeamOpponentInput(opponent, match.date) - elseif opponent.type == Opponent.literal then - opponent = StarcraftMatchGroupInput.ProcessLiteralOpponentInput(opponent) - else - error('Unsupported Opponent Type') - end - match[opponentKey] = opponent - - opponent.placement = inputPlace - - --mark match as noQuery if it contains TBD or Literal opponents - local opponentName = string.lower(opponent.name or '') - local playerName = string.lower(((opponent.match2players or {})[1] or {}).name or '') - if - opponent.type == Opponent.literal or - Table.includes(_TBD_STRINGS, opponentName) or - Table.includes(_TBD_STRINGS, playerName) - then - match.noQuery = 'true' - end +---@param mapInput table +---@param opponentCount integer +---@param hasScores boolean +---@return boolean +function MapFunctions.isFinished(mapInput, opponentCount, hasScores) + local finished = Logic.readBoolOrNil(mapInput.finished) + if finished ~= nil then + return finished + end - local mode = MODES2[opponent.type] - if mode == '2' and opponent.extradata.isarchon == 'true' then - mode = 'Archon' - end + return Array.any(Array.range(1, opponentCount), function(opponentIndex) + return Logic.isNotEmpty(mapInput['placement' .. opponentIndex]) or + (hasScores and Logic.isNotEmpty(mapInput['score' .. opponentIndex])) + end) +end - match.mode = match.mode .. mode .. '_' - end +---@param mapInput any +---@param opponentIndex any +---@param hasScores any +---@return {placement: integer?, score: integer?, status: string} +function MapFunctions.getOpponentInfo(mapInput, opponentIndex, hasScores) + local score, status = MatchGroupInputUtil.computeOpponentScore{ + walkover = mapInput.walkover, + winner = mapInput.winner, + opponentIndex = opponentIndex, + score = mapInput['score' .. opponentIndex], + } - return match, numberOfOpponents + return { + placement = tonumber(mapInput['placement' .. opponentIndex]), + score = hasScores and score or nil, + status = status, + } end ---[[ +--- helper fucntions applicable for both map and match -MapInput functions +---@param opponents {placement: integer?, score: integer?, status: string} +---@param noScores boolean? +function StarcraftFfaMatchGroupInput._setPlacements(opponents, noScores) + if noScores then return end -]]-- -function StarcraftFfaInput._mapInput(match, mapKey, subgroup, noscore, numberOfOpponents) - local map = match[mapKey] + if Array.all(opponents, function(opponent) + return Logic.isNotEmpty(opponent.placement) + end) then return end - --redirect maps - if map.map ~= 'TBD' then - map.map = mw.ext.TeamLiquidIntegration.resolve_redirect(map.map or '') + ---@param status string + ---@return string + local toSortStatus = function(status) + if status == MatchGroupInputUtil.STATUS.DEFAULT_WIN or status == MatchGroupInputUtil.STATUS.SCORE or not status then + return status + end + return MatchGroupInputUtil.STATUS.DEFAULT_LOSS end - --set initial extradata for maps - map.extradata = { - comment = map.comment or '', - header = map.header or '', - noQuery = match.noQuery, - } + local cache = {} + + ---@param status string + ---@param score integer? + ---@param manualPlacement integer? + ---@return boolean + local isNewPlacement = function(status, score, manualPlacement) + if manualPlacement then + return true + elseif cache.manualPlacement and not manualPlacement then + return true + elseif status ~= cache.status then + return true + elseif status == MatchGroupInputUtil.STATUS.SCORE and score ~= cache.score then + return true + end + return false + end - --inherit stuff from match data - map.type = match.type - map.liquipediatier = match.liquipediatier - map.liquipediatiertype = match.liquipediatiertype - map.game = match.game - map.date = match.date - - --get participants data for the map + get map mode - map = StarcraftMatchGroupInput.ProcessPlayerMapData(map, match, numberOfOpponents) - - --determine scores, resulttype, walkover and winner - map = StarcraftFfaInput._mapScoreProcessing(map, numberOfOpponents, noscore) - - --adjust sumscores if scores/points are used - if not noscore then - for j = 1, numberOfOpponents do - --set sumscore to 0 if it isn't a number - if String.isEmpty(match['opponent' .. j].sumscore) then - match['opponent' .. j].sumscore = 0 - end - match['opponent' .. j].sumscore = match['opponent' .. j].sumscore + (tonumber(map.scores[j] or 0) or 0) + cache.placement = 1 + cache.skipped = 0 + for _, opponent in Table.iter.spairs(opponents, StarcraftFfaMatchGroupInput._placementSortFunction) do + local currentStatus = toSortStatus(opponent.status) + local currentScore = opponent.score or 0 + if isNewPlacement(currentStatus, currentScore, opponent.placement) then + cache.manualPlacement = opponent.placement + cache.placement = opponent.placement or (cache.placement + cache.skipped) + cache.skipped = 0 + cache.score = currentScore + cache.status = currentStatus end + opponent.placement = cache.placement + cache.skipped = cache.skipped + 1 end +end - --subgroup handling - subgroup = tonumber(map.subgroup) or subgroup + 1 +---@param opponents {placement: integer?, score: integer?, status: string} +---@param winnerInput integer|string|nil +---@param resultType string? +---@return integer? +function StarcraftFfaMatchGroupInput._getWinner(opponents, winnerInput, resultType) + if Logic.isNumeric(winnerInput) then + return tonumber(winnerInput) + elseif resultType == MatchGroupInputUtil.RESULT_TYPE.DRAW then + return MatchGroupInputUtil.WINNER_DRAW + end + + local placements = Array.map(opponents, Operator.property('placement')) + local bestPlace = Array.min(placements) - match[mapKey] = map + local calculatedWinner = Array.indexOf(placements, FnUtil.curry(Operator.eq, bestPlace)) - return match, subgroup + return calculatedWinner ~= 0 and calculatedWinner or nil end +---@param opponents {placement: integer?, score: integer?, status: string}[] +---@param index1 integer +---@param index2 integer +---@return boolean +function StarcraftFfaMatchGroupInput._placementSortFunction(opponents, index1, index2) + local opponent1 = opponents[index1] + local opponent2 = opponents[index2] + + if opponent1.status == MatchGroupInputUtil.STATUS_INPUTS.DEFAULT_WIN then + return true + elseif Table.includes(MatchGroupInputUtil.STATUS_INPUTS, opponent1.status) then + return false + end -function StarcraftFfaInput._mapScoreProcessing(map, numberOfOpponents, noscore) - map.scores = {} - local indexedScores = {} - local hasScoreSet = false - --read scores - if not noscore then - for scoreIndex = 1, numberOfOpponents do - local score = String.nilIfEmpty(map['score' .. scoreIndex]) - or String.nilIfEmpty(map['points' .. scoreIndex]) - or '' - score = ALLOWED_STATUSES2[score] or tonumber(score) or 0 - indexedScores[scoreIndex] = score - if not Logic.isNumeric(score) then - map.resulttype = 'default' - if String.isEmpty(map.walkover) or map.walkover == 'L' then - if score == 'DQ' then - map.walkover = 'DQ' - elseif score == 'FF' then - map.walkover = 'FF' - else - map.walkover = 'L' - end - end - if score == 'W' then - indexedScores[scoreIndex] = _DEFAULT_WIN_SCORE_VALUE - else - indexedScores[scoreIndex] = -1 - end - end - - --check if any score is not 0, i.e. a score has been actually entered - if score ~= 0 then - hasScoreSet = true - end - - map.scores[scoreIndex] = score - end + if (opponent1.score or -1) ~= (opponent2.score or -1) then + return (opponent1.score or -1) > (opponent2.score or -1) + end - --determine map winner and placements from scores if not set manually - if hasScoreSet then - local counter = 0 - local temp = {} - for scoreIndex, score in Table.iter.spairs(indexedScores, StarcraftFfaInput._placementSortFunction) do - counter = counter + 1 - if counter == 1 and Logic.isEmpty(map.winner) then - map.winner = scoreIndex - map.extradata['placement' .. scoreIndex] = tonumber(map['placement' .. scoreIndex] or '') or - tonumber(map['opponent' .. scoreIndex .. 'placement'] or '') or counter - temp.place = counter - temp.score = indexedScores[scoreIndex] - elseif temp.score == score then - map.extradata['placement' .. scoreIndex] = tonumber(map['placement' .. scoreIndex] or '') or - tonumber(map['opponent' .. scoreIndex .. 'placement'] or '') or temp.place - else - map.extradata['placement' .. scoreIndex] = tonumber(map['placement' .. scoreIndex] or '') or - tonumber(map['opponent' .. scoreIndex .. 'placement'] or '') or counter - temp.place = counter - temp.score = indexedScores[scoreIndex] - end - end - end - elseif tonumber(map.winner or '') then - for oppIndex = 1, numberOfOpponents do - map.extradata['placement' .. oppIndex] = tonumber(map['placement' .. oppIndex] or '') or - tonumber(map['opponent' .. oppIndex .. 'placement'] or '') or _PLACEMENT_DUMMY - if map.extradata['placement' .. oppIndex] == _PLACEMENT_DUMMY and tonumber(map.winner) == oppIndex then - map.extradata['placement' .. oppIndex] = 1 - end - end - else - for oppIndex = 1, numberOfOpponents do - map.extradata['placement' .. oppIndex] = tonumber(map['placement' .. oppIndex] or '') or - tonumber(map['opponent' .. oppIndex .. 'placement'] or '') or _PLACEMENT_DUMMY - if map.extradata['placement' .. oppIndex] == 1 then - map.winner = oppIndex - end - end + if opponent1.placement and opponent2.placement then + return opponent1.placement < opponent2.placement + elseif opponent1.placement and not opponent2.placement then + return true + elseif opponent2.placement and not opponent1.placement then + return false end - return map + return index1 < index2 end -return StarcraftFfaInput +return StarcraftFfaMatchGroupInput diff --git a/components/match2/commons/starcraft_starcraft2/match_summary_ffa_starcraft.lua b/components/match2/commons/starcraft_starcraft2/match_summary_ffa_starcraft.lua index 95b57750bec..200fb4c79f5 100644 --- a/components/match2/commons/starcraft_starcraft2/match_summary_ffa_starcraft.lua +++ b/components/match2/commons/starcraft_starcraft2/match_summary_ffa_starcraft.lua @@ -8,9 +8,9 @@ local Lua = require('Module:Lua') local Placement = require('Module:Placement') -local StarcraftMatchExternalLinks = require('Module:MatchExternalLinks/Starcraft') local Table = require('Module:Table') +local MatchSummary = Lua.import('Module:MatchSummary/Base') local FfaMatchSummary = Lua.import('Module:MatchSummary/Ffa') local StarcraftMatchGroupUtil = Lua.import('Module:MatchGroup/Util/Starcraft') local StarcraftMatchSummary = Lua.import('Module:MatchSummary/Starcraft') @@ -69,14 +69,15 @@ function CustomFfaMatchSummary.GamePlacement(props) end function CustomFfaMatchSummary.Footer(props) - local links = StarcraftMatchExternalLinks.extractFromMatch(props.match) - if #links > 0 then - local linksNode = StarcraftMatchExternalLinks.MatchExternalLinks({links = links}) - :addClass('brkts-popup-sc-footer-links vodlink') - return mw.html.create('div'):addClass('ffa-match-summary-footer') - :node(linksNode) - else - return nil + local match = props.match + + local footer = MatchSummary.addVodsToFooter(match, MatchSummary.Footer()) + + footer:addLinks(StarcraftMatchSummary.LINKS_DATA, match.links) + + local footerDisplay = footer:create() + if footerDisplay then + return footerDisplay:addClass('ffa-match-summary-footer') end end diff --git a/components/match2/commons/starcraft_starcraft2/match_summary_starcraft.lua b/components/match2/commons/starcraft_starcraft2/match_summary_starcraft.lua index bb48e683ab7..28a1cd26fca 100644 --- a/components/match2/commons/starcraft_starcraft2/match_summary_starcraft.lua +++ b/components/match2/commons/starcraft_starcraft2/match_summary_starcraft.lua @@ -68,6 +68,9 @@ end local StarcraftMatchSummary = {} +-- make these available in FFA Matchsummary too +StarcraftMatchSummary.LINKS_DATA = LINKS_DATA + ---@param args {bracketId: string, matchId: string, config: table?} ---@return Html function StarcraftMatchSummary.MatchSummaryContainer(args) From 5ce10470e39565fe3998fe8262a1dd32ee34f7bc Mon Sep 17 00:00:00 2001 From: Rikard Blixt Date: Tue, 17 Sep 2024 16:27:02 +0200 Subject: [PATCH 8/9] refactor(standard): add non-instanced is_a/instanceOf method (#4722) * refactor(standard): add non-instanced is_a/instanceOf method * fix typo * this one can be changed too now that it exists * remove old comment --- components/infobox/commons/infobox.lua | 7 ++-- .../prize_pool/commons/prize_pool_base.lua | 4 +-- components/squad/commons/squad_row.lua | 2 +- components/widget/widget.lua | 2 +- standard/class.lua | 34 +++++++++++-------- standard/condition.lua | 5 ++- standard/error.lua | 5 +-- standard/links_stream.lua | 17 ++++------ standard/result_or_error.lua | 8 ++--- 9 files changed, 39 insertions(+), 45 deletions(-) diff --git a/components/infobox/commons/infobox.lua b/components/infobox/commons/infobox.lua index 7b22fb4e424..cc2ed123525 100644 --- a/components/infobox/commons/infobox.lua +++ b/components/infobox/commons/infobox.lua @@ -9,9 +9,12 @@ local Array = require('Module:Array') local Class = require('Module:Class') local Logic = require('Module:Logic') +local Lua = require('Module:Lua') local Variables = require('Module:Variables') local WarningBox = require('Module:WarningBox') +local Widget = Lua.import('Module:Widget') + ---@class Infobox ---@field frame Frame? ---@field root Html? @@ -79,9 +82,7 @@ end ---@return Html function Infobox:build(widgets) for _, widget in ipairs(widgets) do - if widget == nil or widget['is_a'] == nil then - error('Infobox:build can only accept Widgets') - end + assert(Class.instanceOf(widget, Widget), 'Infobox:build can only accept Widgets') self.content:node(widget:tryMake(self.injector)) end diff --git a/components/prize_pool/commons/prize_pool_base.lua b/components/prize_pool/commons/prize_pool_base.lua index 5156ade16d4..eddfccdd239 100644 --- a/components/prize_pool/commons/prize_pool_base.lua +++ b/components/prize_pool/commons/prize_pool_base.lua @@ -919,7 +919,7 @@ end ---@param widgetInjector WidgetInjector An instance of a class that implements the WidgetInjector interface ---@return self function BasePrizePool:setWidgetInjector(widgetInjector) - assert(widgetInjector:is_a(WidgetInjector), 'setWidgetInjector: Not a Widget Injector') + assert(Class.instanceOf(widgetInjector, WidgetInjector), 'setWidgetInjector: Not a Widget Injector') self._widgetInjector = widgetInjector return self end @@ -928,7 +928,7 @@ end ---@param lpdbInjector LpdbInjector An instance of a class that implements the LpdbInjector interface ---@return self function BasePrizePool:setLpdbInjector(lpdbInjector) - assert(lpdbInjector:is_a(LpdbInjector), 'setLpdbInjector: Not an LPDB Injector') + assert(Class.instanceOf(lpdbInjector, LpdbInjector), 'setLpdbInjector: Not an LPDB Injector') self._lpdbInjector = lpdbInjector return self end diff --git a/components/squad/commons/squad_row.lua b/components/squad/commons/squad_row.lua index 30f29f29a2d..0c51ad54c3b 100644 --- a/components/squad/commons/squad_row.lua +++ b/components/squad/commons/squad_row.lua @@ -22,7 +22,7 @@ local RoleIcons = { sub = Icon.makeIcon{iconName = 'substitute', hover = 'Substitute'}, } ----@class SquadRow +---@class SquadRow: BaseClass ---@operator call(ModelRow): SquadRow ---@field children Widget[] ---@field model ModelRow diff --git a/components/widget/widget.lua b/components/widget/widget.lua index 28bc9cf88d6..6ccd37b692d 100644 --- a/components/widget/widget.lua +++ b/components/widget/widget.lua @@ -54,7 +54,7 @@ function Widget:tryChildren(injector) children = self:makeChildren(injector) or {} end return Array.map(children, function(child) - if type(child) == 'table' and type(child['is_a']) == 'function' and child:is_a(Widget) then + if Class.instanceOf(child, Widget) then ---@cast child Widget return Logic.tryOrElseLog( function() return child:tryMake(injector) end, diff --git a/standard/class.lua b/standard/class.lua index bb7fc56d20d..0065995560e 100644 --- a/standard/class.lua +++ b/standard/class.lua @@ -6,10 +6,6 @@ -- Please see https://github.com/Liquipedia/Lua-Modules to contribute -- ---- --- @author Vogan for Liquipedia --- - local Arguments = require('Module:Arguments') local Class = {} @@ -18,7 +14,8 @@ Class.PRIVATE_FUNCTION_SPECIFIER = '_' ---@class BaseClass ---@operator call:self ----@field is_a fun(self, BaseClass):boolean +---@field is_a fun(self, class: BaseClass):boolean #deprecated +---@field init fun(self, ...) function Class.new(base, init) local instance = {} @@ -59,16 +56,9 @@ function Class.new(base, init) return Class.export(instance, options) end - instance.is_a = function(self, class) - local m = getmetatable(self) - while m do - if m == class then - return true - end - m = m._base - end - return false - end + ---@deprecated + instance.is_a = Class.instanceOf + setmetatable(instance, metatable) return instance end @@ -159,4 +149,18 @@ function Class._frameToArgs(frame, options) return (Table.isNotEmpty(namedArgs) and namedArgs or nil), indexedArgs end +---@param instance any +---@param class BaseClass +---@return boolean +function Class.instanceOf(instance, class) + local metatable = getmetatable(instance) + while metatable do + if metatable == class then + return true + end + metatable = metatable._base + end + return false +end + return Class diff --git a/standard/condition.lua b/standard/condition.lua index 3ff8f44371b..0ee410c05b4 100644 --- a/standard/condition.lua +++ b/standard/condition.lua @@ -34,7 +34,7 @@ local ConditionTree = Class.new(_ConditionNode, function ConditionTree:add(node) if not node then return self - elseif node.is_a ~= nil and node:is_a(_ConditionNode) then + elseif Class.instanceOf(node, _ConditionNode) then table.insert(self._nodes, node) else -- List of nodes @@ -50,7 +50,7 @@ function ConditionTree:toString() assert(self.booleanOperator ~= nil) return table.concat(Array.map(self._nodes, function(node) - if node:is_a(ConditionTree) then + if Class.instanceOf(node, ConditionTree) then return String.interpolate('(${node})', {node = node:toString()}) end @@ -66,7 +66,6 @@ end ---@field name ColumnName ---@field comparator lpdbComparator ---@field value string|number ----@field is_a function local ConditionNode = Class.new(_ConditionNode, function(self, name, comparator, value) self.name = name diff --git a/standard/error.lua b/standard/error.lua index da927d75e6d..18fe7125890 100644 --- a/standard/error.lua +++ b/standard/error.lua @@ -66,10 +66,7 @@ end) ---@param error Error ---@return boolean function Error.isError(error) - return type(error) == 'table' - and type(error.is_a) == 'function' - and error:is_a(Error) - and type(error.message) == 'string' + return Class.instanceOf(error, Error) and type(error.message) == 'string' end function Error:__tostring() diff --git a/standard/links_stream.lua b/standard/links_stream.lua index 33f97d41fa7..6da24c7a930 100644 --- a/standard/links_stream.lua +++ b/standard/links_stream.lua @@ -222,9 +222,7 @@ local StreamKey = Class.new( ) StreamLinks.StreamKey = StreamKey ----@param tbl string ----@param languageCode string ----@param index integer +---@overload fun(self, tbl: string, languageCode: string, index: integer): StreamKey ---@overload fun(self, tbl: StreamKey): StreamKey ---@overload fun(self, tbl: string): StreamKey function StreamKey:new(tbl, languageCode, index) @@ -236,6 +234,7 @@ function StreamKey:new(tbl, languageCode, index) index = tbl.index -- All three parameters are supplied elseif languageCode and index then + ---@cast tbl -StreamKey platform = tbl elseif type(tbl) == 'string' then local components = mw.text.split(tbl, '_', true) @@ -251,8 +250,8 @@ function StreamKey:new(tbl, languageCode, index) end end - self.platform = platform --[[@as string]] - self.languageCode = languageCode --[[@as string]] + self.platform = platform + self.languageCode = languageCode self.index = tonumber(index) --[[@as integer]] self:_isValid() self.languageCode = self.languageCode:lower() @@ -298,14 +297,10 @@ function StreamKey:_isValid() return true end ----@param value StreamKey ----@return true +---@overload fun(value: StreamKey): true ---@overload fun(value: any): false function StreamKey._isStreamKey(value) - if type(value) == 'table' and type(value.is_a) == 'function' and value:is_a(StreamKey) then - return true - end - return false + return Class.instanceOf(value, StreamKey) end StreamKey.__tostring = StreamKey.toString diff --git a/standard/result_or_error.lua b/standard/result_or_error.lua index cac5c5cfaf5..0043a35d3e4 100644 --- a/standard/result_or_error.lua +++ b/standard/result_or_error.lua @@ -66,12 +66,12 @@ end ---@return boolean function ResultOrError:isResult() - return self:is_a(ResultOrError.Result) + return Class.instanceOf(self, ResultOrError.Result) end ---@return boolean function ResultOrError:isError() - return self:is_a(ResultOrError.Error) + return Class.instanceOf(self, ResultOrError.Error) end --[[ @@ -136,9 +136,7 @@ function ResultOrError.try(f, originalError) xpcall( function() local result = f() - local isResultOrError = type(result) == 'table' - and type(result.is_a) == 'function' - and result:is_a(ResultOrError) + local isResultOrError = Class.instanceOf(result, ResultOrError) resultOrError = isResultOrError and result or ResultOrError.Result(result) From 6e44caba795c1dcb894f8e848f1cefc2147c5a2c Mon Sep 17 00:00:00 2001 From: Rikard Blixt Date: Tue, 17 Sep 2024 17:20:45 +0200 Subject: [PATCH 9/9] fix(match2): valorant participant issue in legacy (#4754) * fix(match2): valorant participant issue in legacy * fix format, fix typo * allow it through the clamping * allow gaps in input of players * change these to pairs too --- components/match2/commons/match.lua | 11 +++++++- .../valorant/match_group_input_custom.lua | 26 +++++++++---------- .../match2/wikis/valorant/match_legacy.lua | 2 +- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/components/match2/commons/match.lua b/components/match2/commons/match.lua index 113cccf575d..accc893b66d 100644 --- a/components/match2/commons/match.lua +++ b/components/match2/commons/match.lua @@ -164,7 +164,7 @@ end ---and removes direct references between a match record and its subobject records. ---@param match table ---@return {matchRecord: table, gameRecords: table[], opponentRecords: table[], playerRecords: table[]} ----@overload fun(match: table): {} +---@overload fun(match: any): {} function Match.splitRecordsByType(match) if match == nil or type(match) ~= 'table' then return {} @@ -385,6 +385,14 @@ end function Match._prepareGameRecordForStore(matchRecord, gameRecord) gameRecord.parent = matchRecord.parent gameRecord.tournament = matchRecord.tournament + if not gameRecord.participants and gameRecord.opponents then + gameRecord.participants = {} + for opponentId, opponent in ipairs(gameRecord.opponents) do + for playerId, player in pairs(opponent.players) do + gameRecord.participants[opponentId .. '_' .. playerId] = player + end + end + end Match.clampFields(gameRecord, Match.gameFields) end @@ -452,6 +460,7 @@ Match.gameFields = Table.map({ 'parent', 'participants', 'patch', + 'opponents', -- Not a real field yet, but will be in the future. Also for use in Match/Legacy 'resulttype', 'rounds', 'scores', diff --git a/components/match2/wikis/valorant/match_group_input_custom.lua b/components/match2/wikis/valorant/match_group_input_custom.lua index 827a615d1c7..ade2636532d 100644 --- a/components/match2/wikis/valorant/match_group_input_custom.lua +++ b/components/match2/wikis/valorant/match_group_input_custom.lua @@ -88,8 +88,8 @@ function CustomMatchGroupInput.extractMaps(match, opponents) local finishedInput = map.finished --[[@as string?]] local winnerInput = map.winner --[[@as string?]] - map.participants = MapFunctions.getParticipants(map, opponents) - map.extradata = MapFunctions.getExtraData(map, map.participants) + map.opponents = MapFunctions.getParticipants(map, opponents) + map.extradata = MapFunctions.getExtraData(map, map.opponents) map.finished = MatchGroupInputUtil.mapIsFinished(map) local opponentInfo = Array.map(opponents, function(_, opponentIndex) @@ -179,8 +179,8 @@ function MapFunctions.keepMap(map) end ---@param map table ----@param participants table ----@return table +---@param participants {players: {player: string?, agent: string?}[]}[] +---@return table function MapFunctions.getExtraData(map, participants) ---@type table local extraData = { @@ -190,10 +190,11 @@ function MapFunctions.getExtraData(map, participants) t2halfs = {atk = map.t2atk, def = map.t2def, otatk = map.t2otatk, otdef = map.t2otdef}, } - for key, participant in pairs(participants) do - local opponentIdx, playerIdx = unpack(mw.text.split(key, '_', true)) - extraData['t' .. opponentIdx .. 'p' .. playerIdx] = participant.player - extraData['t' .. opponentIdx .. 'p' .. playerIdx .. 'agent'] = participant.agent + for opponentIdx, opponent in ipairs(participants) do + for playerIdx, player in pairs(opponent.players) do + extraData['t' .. opponentIdx .. 'p' .. playerIdx] = player.player + extraData['t' .. opponentIdx .. 'p' .. playerIdx .. 'agent'] = player.agent + end end return extraData @@ -201,12 +202,11 @@ end ---@param map table ---@param opponents MGIParsedOpponent[] ----@return table +---@return {players: table[]}[] function MapFunctions.getParticipants(map, opponents) - local allParticipants = {} local getCharacterName = FnUtil.curry(MatchGroupInputUtil.getCharacterName, AgentNames) - Array.forEach(opponents, function(opponent, opponentIndex) + return Array.map(opponents, function(opponent, opponentIndex) local players = Array.mapIndexes(function(playerIndex) return opponent.match2players[playerIndex] or (map['t' .. opponentIndex .. 'p' .. playerIndex] and {}) or @@ -234,10 +234,8 @@ function MapFunctions.getParticipants(map, opponents) Array.forEach(unattachedParticipants, function(participant) table.insert(participants, participant) end) - Table.mergeInto(allParticipants, Table.map(participants, MatchGroupInputUtil.prefixPartcipants(opponentIndex))) + return {players = participants} end) - - return allParticipants end ---@param map table diff --git a/components/match2/wikis/valorant/match_legacy.lua b/components/match2/wikis/valorant/match_legacy.lua index 1705abe3723..e20b017c0c8 100644 --- a/components/match2/wikis/valorant/match_legacy.lua +++ b/components/match2/wikis/valorant/match_legacy.lua @@ -85,7 +85,7 @@ function MatchLegacy.storeGames(match, match2) end local opponents = Json.parseIfString(game2.opponents) or {} for teamId, opponent in ipairs(opponents) do - for playerId, player in ipairs(opponent.players) do + for playerId, player in pairs(opponent.players) do addPlayer(teamId, playerId, player) end end