diff --git a/components/match2/wikis/criticalops/match_summary.lua b/components/match2/wikis/criticalops/match_summary.lua new file mode 100644 index 00000000000..e4d8592868b --- /dev/null +++ b/components/match2/wikis/criticalops/match_summary.lua @@ -0,0 +1,394 @@ +--- +-- @Liquipedia +-- wiki=criticalops +-- page=Module:MatchSummary +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Class = require('Module:Class') +local Logic = require('Module:Logic') +local Lua = require('Module:Lua') + +local DisplayHelper = Lua.import('Module:MatchGroup/Display/Helper', {requireDevIfEnabled = true}) +local MatchSummary = Lua.import('Module:MatchSummary/Base', {requireDevIfEnabled = true}) + +local GREEN_CHECK = '[[File:GreenCheck.png|14x14px|link=]]' +local NO_CHECK = '[[File:NoCheck.png|link=]]' + +local ARROW_LEFT = '[[File:Arrow sans left.svg|15x15px|link=|Left team starts]]' +local ARROW_RIGHT = '[[File:Arrow sans right.svg|15x15px|link=|Right team starts]]' + +local EPOCH_TIME = '1970-01-01 00:00:00' +local EPOCH_TIME_EXTENDED = '1970-01-01T00:00:00+00:00' + +local TBD = 'TBD' + +-- Score Class +---@class CriticalopsScore +---@operator call(string|number|nil): CriticalopsScore +---@field root Html +---@field table Html +---@field top Html +---@field bottom Html +local Score = Class.new( + function(self, direction) + self.root = mw.html.create('div') + :css('width','70px') + :css('text-align', 'center') + :css('direction', direction) + self.table = self.root:tag('table'):css('line-height', '29px') + self.top = mw.html.create('tr') + self.bottom = mw.html.create('tr') + end +) + +---@return CriticalopsScore +function Score:setLeft() + self.table:css('float', 'left') + return self +end + +---@return CriticalopsScore +function Score:setRight() + self.table:css('float', 'right') + return self +end + +---@param score string|number|nil +---@return CriticalopsScore +function Score:setMapScore(score) + local mapScore = mw.html.create('td') + mapScore + :attr('rowspan', 2) + :css('font-size', '16px') + :css('width', '25px') + :wikitext(score or '') + + self.top:node(mapScore) + + return self +end + +---@param side string +---@param score number +---@return CriticalopsScore +function Score:setFirstHalfScore(score, side) + local halfScore = mw.html.create('td') + halfScore + :addClass('brkts-popup-body-match-sidewins') + :addClass('brkts-cs-score-color-' .. side) + :wikitext(score) + + self.top:node(halfScore) + return self +end + +---@param side string +---@param score number +---@return CriticalopsScore +function Score:setSecondHalfScore(score, side) + local halfScore = mw.html.create('td') + halfScore + :addClass('brkts-popup-body-match-sidewins') + :addClass('brkts-cs-score-color-' .. side) + :wikitext(score) + + self.bottom:node(halfScore) + return self +end + +---@return Html +function Score:create() + self.table:node(self.top):node(self.bottom) + return self.root +end + +-- Map Veto Class +---@class CriticalopsMapVeto: MatchSummaryRowInterface +---@operator call: CriticalopsMapVeto +---@field root Html +---@field table Html +local MapVeto = Class.new( + function(self) + self.root = mw.html.create('div'):addClass('brkts-popup-mapveto') + self.table = self.root:tag('table') + :addClass('wikitable-striped'):addClass('collapsible'):addClass('collapsed') + self:createHeader() + end +) + +---@return CriticalopsMapVeto +function MapVeto:createHeader() + self.table:tag('tr') + :tag('th'):css('width','33%'):done() + :tag('th'):css('width','34%'):wikitext('Map Veto'):done() + :tag('th'):css('width','33%'):done() + return self +end + +---@param firstVeto number? +---@return CriticalopsMapVeto +function MapVeto:vetoStart(firstVeto) + local textLeft + local textCenter + local textRight + if firstVeto == 1 then + textLeft = 'Start Map Veto' + textCenter = ARROW_LEFT + elseif firstVeto == 2 then + textCenter = ARROW_RIGHT + textRight = 'Start Map Veto' + else return self end + self.table:tag('tr'):addClass('brkts-popup-mapveto-vetostart') + :tag('th'):wikitext(textLeft or ''):done() + :tag('th'):wikitext(textCenter):done() + :tag('th'):wikitext(textRight or ''):done() + return self +end + +---@param map string? +---@return CriticalopsMapVeto +function MapVeto:addDecider(map) + map = Logic.emptyOr(map, TBD) + + local row = mw.html.create('tr'):addClass('brkts-popup-mapveto-vetoround') + + self:addColumnVetoType(row, 'brkts-popup-mapveto-decider', 'DECIDER') + self:addColumnVetoMap(row, map) + self:addColumnVetoType(row, 'brkts-popup-mapveto-decider', 'DECIDER') + + self.table:node(row) + return self +end + +---@param vetotype string? +---@param map1 string? +---@param map2 string? +---@return CriticalopsMapVeto +function MapVeto:addRound(vetotype, map1, map2) + map1 = Logic.emptyOr(map1, TBD) + map2 = Logic.emptyOr(map2, TBD) + + local class + local vetoText + if vetotype == 'ban' then + vetoText = 'BAN' + class = 'brkts-popup-mapveto-ban' + elseif vetotype == 'pick' then + vetoText = 'PICK' + class = 'brkts-popup-mapveto-pick' + elseif vetotype == 'defaultban' then + vetoText = 'DEFAULT BAN' + class = 'brkts-popup-mapveto-defaultban' + else + return self + end + + local row = mw.html.create('tr'):addClass('brkts-popup-mapveto-vetoround') + + self:addColumnVetoMap(row, map1) + self:addColumnVetoType(row, class, vetoText) + self:addColumnVetoMap(row, map2) + + self.table:node(row) + return self +end + +---@param row Html +---@param styleClass string +---@param vetoText string +---@return CriticalopsMapVeto +function MapVeto:addColumnVetoType(row, styleClass, vetoText) + row:tag('td') + :tag('span') + :addClass(styleClass) + :addClass('brkts-popup-mapveto-vetotype') + :wikitext(vetoText) + return self +end + +---@param row Html +---@param map string +---@return CriticalopsMapVeto +function MapVeto:addColumnVetoMap(row, map) + row:tag('td'):wikitext(map):done() + return self +end + +---@return Html +function MapVeto:create() + return self.root +end + +---@class CriticalopsMatchStatus: MatchSummaryRowInterface +---@operator call: CriticalopsMatchStatus +---@field root Html +local MatchStatus = Class.new( + function(self) + self.root = mw.html.create('div') + self.root + :addClass('brkts-popup-comment') + :css('white-space', 'normal') + :css('font-size', '85%') + end +) + +---@param content Html|string|number|nil +---@return CriticalopsMatchStatus +function MatchStatus:content(content) + self.root:node(content):node(MatchSummary.Break():create()) + return self +end + +---@return Html +function MatchStatus:create() + return self.root +end + +local CustomMatchSummary = {} + +---@param args table +---@return Html +function CustomMatchSummary.getByMatchId(args) + return MatchSummary.defaultGetByMatchId(CustomMatchSummary, args, {width = '400px'}) +end + +---@param match MatchGroupUtilMatch +---@return MatchSummaryBody +function CustomMatchSummary.createBody(match) + local body = MatchSummary.Body() + + if match.dateIsExact or (match.date ~= EPOCH_TIME_EXTENDED and match.date ~= EPOCH_TIME) then + if Logic.isNotEmpty(match.extradata.status) then + match.stream = {rawdatetime = true} + end + -- dateIsExact means we have both date and time. Show countdown + -- if match is not epoch=0, we have a date, so display the date + body:addRow(MatchSummary.Row():addElement( + DisplayHelper.MatchCountdownBlock(match) + )) + end + + -- Iterate each map + for _, game in ipairs(match.games) do + if game.map then + body:addRow(CustomMatchSummary._createMap(game)) + end + end + + -- Add the Map Vetoes + if match.extradata.mapveto then + local vetoData = match.extradata.mapveto + if vetoData then + local mapVeto = MapVeto() + + for _,vetoRound in ipairs(vetoData) do + if vetoRound.vetostart then + mapVeto:vetoStart(tonumber(vetoRound.vetostart)) + end + if vetoRound.type == 'decider' then + mapVeto:addDecider(vetoRound.decider) + else + mapVeto:addRound(vetoRound.type, vetoRound.team1, vetoRound.team2) + end + end + + body:addRow(mapVeto) + end + end + + -- Match Status (postponed/ cancel(l)ed) + if match.extradata.status then + local matchStatus = MatchStatus() + matchStatus:content('Match ' .. mw.getContentLanguage():ucfirst(match.extradata.status) .. '') + body:addRow(matchStatus) + end + + return body +end + +---@param game MatchGroupUtilGame +---@return MatchSummaryRow +function CustomMatchSummary._createMap(game) + local row = MatchSummary.Row() + local extradata = game.extradata or {} + + -- Score + local team1Score = Score():setLeft() + local team2Score = Score('rtl'):setRight() + + -- Teams map score + team1Score:setMapScore(game.scores[1]) + team2Score:setMapScore(game.scores[2]) + + local t1sides = extradata['t1sides'] or {} + local t2sides = extradata['t2sides'] or {} + local t1halfs = extradata['t1halfs'] or {} + local t2halfs = extradata['t2halfs'] or {} + + -- Teams half scores + for sideIndex, side in ipairs(t1sides) do + local oppositeSide = t2sides[sideIndex] + if math.fmod(sideIndex, 2) == 1 then + team1Score:setFirstHalfScore(t1halfs[sideIndex], side) + team2Score:setFirstHalfScore(t2halfs[sideIndex], oppositeSide) + else + team1Score:setSecondHalfScore(t1halfs[sideIndex], side) + team2Score:setSecondHalfScore(t2halfs[sideIndex], oppositeSide) + end + end + + -- Map Info + local mapInfo = mw.html.create('div') + mapInfo :addClass('brkts-popup-spaced') + :wikitext('[[' .. game.map .. ']]') + :css('text-align', 'center') + :css('padding','5px 2px') + :css('flex-grow','1') + + if game.resultType == 'np' then + mapInfo:addClass('brkts-popup-spaced-map-skip') + elseif game.resultType == 'draw' then + mapInfo:wikitext('(Draw)') + end + + -- Build the HTML + row:addElement(CustomMatchSummary._createCheckMark(game.winner == 1)) + row:addElement(team1Score:create()) + + row:addElement(mapInfo) + + row:addElement(team2Score:create()) + row:addElement(CustomMatchSummary._createCheckMark(game.winner == 2)) + + -- Add Comment + if not Logic.isEmpty(game.comment) then + row:addElement(MatchSummary.Break():create()) + local comment = mw.html.create('div') + comment :wikitext(game.comment) + :css('margin', 'auto') + row:addElement(comment) + end + + row:addClass('brkts-popup-body-game'):css('font-size', '85%'):css('overflow', 'hidden') + + return row +end + +---@param isWinner boolean? +---@return Html +function CustomMatchSummary._createCheckMark(isWinner) + local container = mw.html.create('div') + container:addClass('brkts-popup-spaced'):css('line-height', '27px') + + if isWinner then + container:node(GREEN_CHECK) + else + container:node(NO_CHECK) + end + + return container +end + +return CustomMatchSummary