diff --git a/components/participant_table/commons/participant_table_base.lua b/components/participant_table/commons/participant_table_base.lua new file mode 100644 index 00000000000..cf83c4dad32 --- /dev/null +++ b/components/participant_table/commons/participant_table_base.lua @@ -0,0 +1,398 @@ +--- +-- @Liquipedia +-- wiki=commons +-- page=Module:ParticipantTable/Base +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +--can not name it `Module:ParticipantTable` due to that already existing on some wikis + +local Arguments = require('Module:Arguments') +local Array = require('Module:Array') +local Class = require('Module:Class') +local DateExt = require('Module:Date/Ext') +local Json = require('Module:Json') +local Logic = require('Module:Logic') +local Lua = require('Module:Lua') +local Namespace = require('Module:Namespace') +local PageVariableNamespace = require('Module:PageVariableNamespace') +local String = require('Module:StringUtils') +local Table = require('Module:Table') +local Template = require('Module:Template') +local Variables = require('Module:Variables') + +local OpponentLibraries = require('Module:OpponentLibraries') +local Opponent = OpponentLibraries.Opponent +local OpponentDisplay = OpponentLibraries.OpponentDisplay + +local Import = Lua.import('Module:ParticipantTable/Import', {requireDevIfEnabled = true}) +local PlayerExt = Lua.import('Module:Player/Ext', {requireDevIfEnabled = true}) +local TournamentStructure = Lua.import('Module:TournamentStructure', {requireDevIfEnabled = true}) + +local pageVars = PageVariableNamespace('ParticipantTable') + +---@class ParticipantTable +---@operator call(Frame): ParticipantTable +---@field args table +---@field config ParticipantTableConfig +---@field display Html? +---@field sections ParticipantTableSection[] +local ParticipantTable = Class.new( + function(self, frame) + self.args = Arguments.getArgs(frame) +end) + +---@param frame Frame +---@return Html? +function ParticipantTable.run(frame) + return ParticipantTable(frame):read():store():create() +end + +---@return self +function ParticipantTable:read() + self.config = self.readConfig(self.args) + self:readSections() + + return self +end + +---@class ParticipantTableConfig +---@field lpdbPrefix string +---@field noStorage boolean +---@field matchGroupSpec table? +---@field syncPlayers boolean +---@field showCountBySection boolean +---@field onlyNotable boolean +---@field count number? +---@field colSpan number +---@field resolveDate string +---@field sortPlayers boolean sort players within an opponent +---@field showTeams boolean +---@field title string? +---@field importOnlyQualified boolean? +---@field display boolean +---@field columnWidth number? width of the column in px + +---@param args table +---@param parentConfig ParticipantTableConfig? +---@return ParticipantTableConfig +function ParticipantTable.readConfig(args, parentConfig) + parentConfig = parentConfig or {} + local config = { + lpdbPrefix = args.lpdbPrefix or parentConfig.lpdbPrefix or Variables.varDefault('lpdbPrefix') or '', + noStorage = Logic.readBool(args.noStorage or parentConfig.noStorage or + Variables.varDefault('disable_LPDB_storage') or not Namespace.isMain()), + matchGroupSpec = TournamentStructure.readMatchGroupsSpec(args), + syncPlayers = Logic.nilOr(Logic.readBoolOrNil(args.syncPlayers), parentConfig.syncPlayers, true), + showCountBySection = Logic.readBool(args.showCountBySection or parentConfig.showCountBySection), + count = tonumber(args.count), + colSpan = tonumber(args.colspan) or parentConfig.colSpan or 4, + onlyNotable = Logic.readBool(args.onlyNotable or parentConfig.onlyNotable), + resolveDate = args.date or parentConfig.resolveDate or DateExt.getContextualDate(), + sortPlayers = Logic.readBool(args.sortPlayers or parentConfig.sortPlayers), + showTeams = not Logic.readBool(args.disable_teams), + title = args.title, + importOnlyQualified = Logic.readBool(args.onlyQualified), + display = not Logic.readBool(args.hidden) + } + + config.columnWidth = tonumber(args.entrywidth) or config.showTeams and 212 or 156 + + return config +end + +function ParticipantTable:readSections() + self.sections = {} + Array.forEach(self:fetchSectionsArgs(), function(sectionArgs) + self:readSection(sectionArgs) + end) +end + +---@return table[] +function ParticipantTable:fetchSectionsArgs() + local args = self.args + + pageVars:set('stashArgs', '1') + + -- make sure that all sections stashArgs + for _, potentialSection in pairs(args) do + ParticipantTable._stashArgs(potentialSection) + end + + -- retrieve sectionsArgs + local sectionsArgs = Template.retrieveReturnValues('ParticipantTable') + pageVars:delete('stashArgs') + + --case no sections: use whole table as first section + if Table.isEmpty(sectionsArgs) then + return {args} + end + + return sectionsArgs +end + +---access the args so it stashes +---@param potentialSection string +---@return string +function ParticipantTable._stashArgs(potentialSection) + return potentialSection +end + + +---@class ParticipantTableSection +---@field config ParticipantTableConfig +---@field entries ParticipantTableEntry + +---@param args table +function ParticipantTable:readSection(args) + local config = self.readConfig(args, self.config) + local section = {config = config} + + local keys = Array.filter(Array.extractKeys(args), function(key) + return String.contains(key, '^%d+$') or + String.contains(key, '^p%d+$') or + String.contains(key, '^player%d+$') + end) + + local entriesByName = {} + + --need custom sort so it doesn't compare number with string + local sortKeys = function(tbl, key1, key2) return tostring(key1) < tostring(key2) end + + for _, key in Table.iter.spairs(keys, sortKeys) do + local entry = self:readEntry(args, key, config) + if entriesByName[entry.name] then + error('Duplicate Input "|' .. key .. '=' .. args[key] .. '"') + end + + entriesByName[entry.name] = entry + end + + section.entries = Array.map(Import.importFromMatchGroupSpec(config, entriesByName), function(entry) + entry.opponent = Opponent.resolve(entry.opponent, config.resolveDate, {syncPlayer = config.syncPlayers}) + return entry + end) + + Array.sortInPlaceBy(section.entries, function(entry) return entry.name:lower() end) + + table.insert(self.sections, section) +end + +---@class ParticipantTableEntry +---@field opponent standardOpponent +---@field name string +---@field note string? +---@field dq boolean + +---@param sectionArgs table +---@param key string|number +---@param config any +---@return ParticipantTableEntry +function ParticipantTable:readEntry(sectionArgs, key, config) + --if not a json assume it is a solo opponent + local opponentArgs = Json.parseIfTable(sectionArgs[key]) or Logic.isNumeric(key) and { + type = Opponent.solo, + name = sectionArgs[key], + } or { + type = Opponent.solo, + name = sectionArgs[key], + link = sectionArgs[key .. 'link'], + flag = sectionArgs[key .. 'flag'], + team = sectionArgs[key .. 'team'], + } + + assert(Opponent.isType(opponentArgs.type) and opponentArgs.type ~= Opponent.team, + 'Missing or unsupported opponent type for "' .. sectionArgs[key] .. '"') + + local opponent = Opponent.readOpponentArgs(opponentArgs) + + if config.sortPlayers and opponent.players then + table.sort(opponent.players, function (player1, player2) + local name1 = (player1.displayName or player1.name):lower() + local name2 = (player2.displayName or player2.name):lower() + return name1 < name2 + end) + end + + return { + dq = Logic.readBool(opponentArgs.dq or sectionArgs[key .. 'dq']), + note = opponentArgs.note or sectionArgs[key .. 'note'], + opponent = opponent, + name = Opponent.toName(opponent), + } +end + +---@return self +function ParticipantTable:store() + if self.config.noStorage then return self end + + local lpdbTournamentData = { + tournament = Variables.varDefault('tournament_name'), + parent = Variables.varDefault('tournament_parent'), + series = Variables.varDefault('tournament_series'), + startdate = Variables.varDefault('tournament_startdate'), + mode = Variables.varDefault('tournament_mode'), + type = Variables.varDefault('tournament_type'), + liquipediatier = Variables.varDefault('tournament_liquipediatier'), + liquipediatiertype = Variables.varDefault('tournament_liquipediatiertype'), + publishertier = Variables.varDefault('tournament_publishertier'), + icon = Variables.varDefault('tournament_icon'), + icondark = Variables.varDefault('tournament_icondark'), + game = Variables.varDefault('tournament_game'), + prizepoolindex = tonumber(Variables.varDefault('prizepool_index')) or 0, + } + + local placements = self:getPlacements() + + Array.forEach(self.sections, function(section) Array.forEach(section.entries, function(entry) + local lpdbData = Opponent.toLpdbStruct(entry.opponent) + + if placements[lpdbData.opponentname] or section.config.noStorage or + Opponent.isTbd(entry.opponent) or Opponent.isEmpty(entry.opponent) then return end + + + lpdbData = Table.merge(lpdbTournamentData, lpdbData, {date = section.config.resolveDate, extradata = {}}) + + ParticipantTable:adjustLpdbData(lpdbData, entry, section.config) + + mw.ext.LiquipediaDB.lpdb_placement(self:objectName(lpdbData), Json.stringifySubTables(lpdbData)) + end) end) + + return self +end + +---Get placements already set on the page from prize pools +---@return table +function ParticipantTable:getPlacements() + local placements = {} + Array.forEach(mw.ext.LiquipediaDB.lpdb('placement', { + limit = 5000, + conditions = '[[placement::!]] AND [[pagename::' .. string.gsub(mw.title.getCurrentTitle().text, ' ', '_') .. ']]', + }), function(placement) placements[placement.opponentname] = true end) + + return placements +end + +---@param lpdbData table +---@return string +function ParticipantTable:objectName(lpdbData) + return 'ranking_' .. self.config.lpdbPrefix .. '_' .. lpdbData.prizepoolindex .. '_' .. lpdbData.opponentname +end + +---@param lpdbData table +---@param entry ParticipantTableEntry +---@param config ParticipantTableConfig +function ParticipantTable:adjustLpdbData(lpdbData, entry, config) +end + +---@return Html? +function ParticipantTable:create() + local config = self.config + + if not config.display then return end + + self.display = mw.html.create('div') + :addClass('participantTable') + :css('max-width', '100%!important') + :css('width', (config.colSpan * config.columnWidth + 1) .. 'px') + :node(mw.html.create('div'):addClass('participantTable-title'):wikitext(config.title or 'Participants')) + + Array.forEach(self.sections, function(section) self:displaySection(section) end) + + return self.display +end + +---@return Html +function ParticipantTable.newSectionNode() + return mw.html.create('div'):addClass('participantTable-row') +end + +---@param section ParticipantTableSection +function ParticipantTable:displaySection(section) + local entries = section.config.onlyNotable and self.filterOnlyNotables(section.entries) or section.entries + + local sectionEntryCount = #Array.filter(entries, function(entry) return not entry.dq end) + + self.display:node(self.newSectionNode():node(self.sectionTitle(section, sectionEntryCount))) + + if Table.isEmpty(section.entries) then + self.display:node(self.newSectionNode():node(self:tbd())) + return + end + + local sectionNode = ParticipantTable.newSectionNode() + + Array.forEach(entries, function(entry, entryIndex) + sectionNode:node(self:displayEntry(entry):css('width', self.config.columnWidth .. 'px')) + end) + + local nextColumn = (#entries % self.config.colSpan) + 1 + + Array.forEach(Array.range(nextColumn, self.config.colSpan), + function() sectionNode:node(self:empty()) end) + + self.display:node(sectionNode) +end + +---@return Html +function ParticipantTable:tbd() + return mw.html.create('div') + :addClass('participantTable-tbd') + :wikitext('To be determined') +end + +---@return Html +function ParticipantTable:empty() + return mw.html.create('div'):addClass('participantTable-entry participantTable-empty') +end + +---@param section ParticipantTableSection +---@param amountOfEntries number +---@return Html? +function ParticipantTable.sectionTitle(section, amountOfEntries) + if Logic.isEmpty(section.config.title) then return end + + local title = mw.html.create('div'):addClass('participantTable-title'):wikitext(section.config.title) + + if not section.config.showCountBySection then return title end + + return title:tag('i'):wikitext(' (' .. (section.config.count or amountOfEntries) .. ')'):done() +end + +---@param entry ParticipantTableEntry +---@param additionalProps table? +---@return Html +function ParticipantTable:displayEntry(entry, additionalProps) + additionalProps = additionalProps or {} + + return mw.html.create('div') + :addClass('participantTable-entry brkts-opponent-hover') + :attr('aria-label', entry.name) + :node(OpponentDisplay.BlockOpponent(Table.merge(additionalProps, { + dq = entry.dq, + note = entry.note, + showPlayerTeam = self.config.showTeams, + opponent = entry.opponent, + }))) +end + +---@param entries ParticipantTableEntry[] +---@return ParticipantTableEntry[] +function ParticipantTable.filterOnlyNotables(entries) + return Array.filter(entries, function(entry) return ParticipantTable.isNotable(entry) end) +end + +---@param entry ParticipantTableEntry +---@return boolean +function ParticipantTable.isNotable(entry) + return Array.any(entry.opponent.players or {}, ParticipantTable.isNotablePlayer) +end + +---@param player standardPlayer +---@return boolean +function ParticipantTable.isNotablePlayer(player) + return Logic.isNotEmpty(player.pageName) and PlayerExt.fetchPlayerFlag(player.pageName) ~= nil +end + +return ParticipantTable diff --git a/components/participant_table/commons/participant_table_custom.lua b/components/participant_table/commons/participant_table_custom.lua new file mode 100644 index 00000000000..659fe77ab8c --- /dev/null +++ b/components/participant_table/commons/participant_table_custom.lua @@ -0,0 +1,11 @@ +--- +-- @Liquipedia +-- wiki=commons +-- page=Module:ParticipantTable/Custom +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Lua = require('Module:Lua') + +return Lua.import('Module:ParticipantTable/Base', {requireDevIfEnabled = true}) diff --git a/components/participant_table/commons/participant_table_import.lua b/components/participant_table/commons/participant_table_import.lua new file mode 100644 index 00000000000..e6a742a9fbc --- /dev/null +++ b/components/participant_table/commons/participant_table_import.lua @@ -0,0 +1,91 @@ +--- +-- @Liquipedia +-- wiki=commons +-- page=Module:ParticipantTable/Import +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Array = require('Module:Array') +local Logic = require('Module:Logic') +local Lua = require('Module:Lua') +local Table = require('Module:Table') + +local Opponent = require('Module:OpponentLibraries').Opponent + +local TournamentStructure = Lua.import('Module:TournamentStructure', {requireDevIfEnabled = true}) + +local ParticipantTableImport = {} + +---@param config ParticipantTableConfig +---@param entriesByName table +---@return ParticipantTableEntry[] +function ParticipantTableImport.importFromMatchGroupSpec(config, entriesByName) + if Table.isEmpty(config.matchGroupSpec) then + return Array.extractValues(entriesByName) + end + + local matchRecords = ParticipantTableImport._fetchMatchRecords(config.matchGroupSpec) + if Table.isEmpty(matchRecords) then + return Array.extractValues(entriesByName) + end + ---@cast matchRecords -nil + return ParticipantTableImport._entriesFromMatchRecords(matchRecords, config, entriesByName) +end + +---@param matchGroupSpec table +---@return table[] +function ParticipantTableImport._fetchMatchRecords(matchGroupSpec) + return mw.ext.LiquipediaDB.lpdb('match2', { + conditions = TournamentStructure.getMatch2Filter(matchGroupSpec), + query = 'pagename, match2bracketdata, match2opponents, winner', + order = 'date asc', + limit = 5000, + }) +end + +---@param matchRecords table[] +---@param config ParticipantTableConfig +---@param entriesByName table +---@return ParticipantTableEntry[] +function ParticipantTableImport._entriesFromMatchRecords(matchRecords, config, entriesByName) + Array.forEach(matchRecords, function(matchRecord) + Array.forEach(matchRecord.match2opponents, function(opponentRecord, opponentIndex) + if not ParticipantTableImport._shouldInclude(opponentIndex, matchRecord, config.importOnlyQualified) then + return + end + + local entry = ParticipantTableImport._entryFromOpponentRecord(opponentRecord) + if not entry then return end + + entriesByName[entry.name] = entriesByName[entry.name] or entry + end) + end) + + return Array.extractValues(entriesByName) +end + +---@param opponentIndex integer +---@param matchRecord table +---@param importOnlyQualified boolean? +---@return boolean +function ParticipantTableImport._shouldInclude(opponentIndex, matchRecord, importOnlyQualified) + local bracketData = matchRecord.match2bracketdata + return not importOnlyQualified or Logic.readBool(bracketData.quallose) or + Logic.readBool(bracketData.qualwin) and tonumber(matchRecord.winner) == opponentIndex +end + +---@param opponentRecord table +---@return ParticipantTableEntry? +function ParticipantTableImport._entryFromOpponentRecord(opponentRecord) + local opponent = Opponent.fromMatch2Record(opponentRecord) + if Opponent.isTbd(opponent) then + return + end + return { + opponent = opponent, + name = opponentRecord.name, + } +end + +return ParticipantTableImport diff --git a/components/participant_table/commons/starcraft_starcraft2/participant_table_starcraft.lua b/components/participant_table/commons/starcraft_starcraft2/participant_table_starcraft.lua new file mode 100644 index 00000000000..f302a62b1dd --- /dev/null +++ b/components/participant_table/commons/starcraft_starcraft2/participant_table_starcraft.lua @@ -0,0 +1,291 @@ +--- +-- @Liquipedia +-- wiki=commons +-- page=Module:ParticipantTable/Starcraft +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Array = require('Module:Array') +local Json = require('Module:Json') +local Faction = require('Module:Faction') +local Logic = require('Module:Logic') +local Lua = require('Module:Lua') +local PageVariableNamespace = require('Module:PageVariableNamespace') +local Table = require('Module:Table') +local Variables = require('Module:Variables') + +---@class StarcraftParticipantTableConfig: ParticipantTableConfig +---@field displayUnknownColumn boolean? +---@field displayRandomColumn boolean? +---@field showCountByRace boolean +---@field isRandomEvent boolean +---@field isQualified boolean? +---@field manualFactionCounts table + +---@class StarcraftParticipantTableEntry: ParticipantTableEntry +---@field isQualified boolean? +---@field opponent StarcraftStandardOpponent + +---@class StarcraftParticipantTableSection: ParticipantTableSection +---@field entries StarcraftParticipantTableEntry[] + +---@class StarcraftParticipantTable: ParticipantTable +---@field isPureSolo boolean +---@field _displaySoloRaceTableSection function +---@field _displayHeader function +---@field _getFactionNumbers function + +local ParticipantTable = Lua.import('Module:ParticipantTable/Base', {requireDevIfEnabled = true}) + +local Opponent = require('Module:OpponentLibraries').Opponent + +local prizePoolVars = PageVariableNamespace('PrizePool') + +local StarcraftParticipantTable = {} + +---@param frame Frame +---@return Html? +function StarcraftParticipantTable.run(frame) + local participantTable = ParticipantTable(frame) --[[@as StarcraftParticipantTable]] + + participantTable.readConfig = StarcraftParticipantTable.readConfig + participantTable.readEntry = StarcraftParticipantTable.readEntry + participantTable.adjustLpdbData = StarcraftParticipantTable.adjustLpdbData + participantTable.getPlacements = StarcraftParticipantTable.getPlacements + participantTable._displaySoloRaceTableSection = StarcraftParticipantTable._displaySoloRaceTableSection + participantTable._displayHeader = StarcraftParticipantTable._displayHeader + participantTable._getFactionNumbers = StarcraftParticipantTable._getFactionNumbers + + participantTable:read():store() + + if StarcraftParticipantTable.isPureSolo(participantTable.sections) then + participantTable.create = StarcraftParticipantTable.createSoloRaceTable + end + + return participantTable:create() +end + +---@param args table +---@param parentConfig StarcraftParticipantTableConfig? +---@return StarcraftParticipantTableConfig +function StarcraftParticipantTable.readConfig(args, parentConfig) + local config = ParticipantTable.readConfig(args, parentConfig) --[[@as StarcraftParticipantTableConfig]] + parentConfig = parentConfig or {} + + config.displayUnknownColumn = Logic.readBoolOrNil(args.unknowncolumn) + config.displayRandomColumn = Logic.readBoolOrNil(args.randomcolumn) + config.showCountByRace = Logic.readBool(args.showCountByRace or args.count) + config.isRandomEvent = Logic.readBool(args.is_random_event) + config.isQualified = Logic.nilOr(Logic.readBoolOrNil(args.isQualified), parentConfig.isQualified) + config.sortPlayers = true + + config.manualFactionCounts = {} + Array.forEach(Faction.knownFactions, function(faction) + config.manualFactionCounts[faction] = tonumber(args[Faction.toName(faction):lower()]) + end) + + return config +end + +---@param sectionArgs table +---@param key string|number +---@param config StarcraftParticipantTableConfig +---@return StarcraftParticipantTableEntry +function StarcraftParticipantTable:readEntry(sectionArgs, key, config) + --if not a json assume it is a solo opponent + local opponentArgs = Json.parseIfTable(sectionArgs[key]) or Logic.isNumeric(key) and { + type = Opponent.solo, + name = sectionArgs[key], + } or { + type = Opponent.solo, + name = sectionArgs[key], + link = sectionArgs[key .. 'link'], + flag = sectionArgs[key .. 'flag'], + team = sectionArgs[key .. 'team'], + race = sectionArgs[key .. 'race'], + } + + assert(Opponent.isType(opponentArgs.type) and opponentArgs.type ~= Opponent.team, + 'Missing or unsupported opponent type for "' .. sectionArgs[key] .. '"') + + local opponent = Opponent.readOpponentArgs(opponentArgs) + + if config.sortPlayers and opponent.players then + table.sort(opponent.players, function (player1, player2) + local name1 = (player1.displayName or player1.name):lower() + local name2 = (player2.displayName or player2.name):lower() + return name1 < name2 + end) + end + + return { + dq = Logic.readBool(opponentArgs.dq or sectionArgs[key .. 'dq']), + note = opponentArgs.note or sectionArgs[key .. 'note'], + opponent = opponent, + name = Opponent.toName(opponent), + isQualified = Logic.nilOr(Logic.readBoolOrNil(sectionArgs[key .. 'qualified']) or config.isQualified), + } +end + +---@param lpdbData table +---@param entry StarcraftParticipantTableEntry +---@param config StarcraftParticipantTableConfig +function StarcraftParticipantTable:adjustLpdbData(lpdbData, entry, config) + local seriesNumber = tonumber(Variables.varDefault('tournament_series_number')) + local isQualified = entry.isQualified or config.isQualified + + lpdbData.extradata.seriesnumber = seriesNumber and string.format('%05d', seriesNumber) or nil + lpdbData.extradata.isqualified = tostring(isQualified) + + lpdbData.qualified = isQualified and 1 or nil +end + +---@return table +function StarcraftParticipantTable:getPlacements() + local placements = {} + local maxPrizePoolIndex = tonumber(Variables.varDefault('prizepool_index')) or 0 + + for prizePoolIndex = 1, maxPrizePoolIndex do + Array.forEach(Json.parseIfTable(prizePoolVars:get('placementRecords.' .. prizePoolIndex)) or {}, function(placement) + placements[placement.opponentname] = true + end) + end + + return placements +end + +---@param sections StarcraftParticipantTableSection[] +---@return boolean +function StarcraftParticipantTable.isPureSolo(sections) + return Array.all(sections, function(section) return Array.all(section.entries, function(entry) + return entry.opponent.type == Opponent.solo + end) end) +end + +---@return Html? +function StarcraftParticipantTable:createSoloRaceTable() + local config = self.config + + if not config.display then return end + + local factioNumbers = self:_getFactionNumbers() + + local factionColumns + if not config.isRandomEvent and + (config.displayRandomColumn or config.displayRandomColumn == nil and factioNumbers.rDisplay > 0) then + + factionColumns = Array.copy(Faction.knownFactions) + else + factionColumns = Array.copy(Faction.coreFactions) + end + + if config.displayUnknownColumn or + config.displayUnknownColumn == nil and factioNumbers[Faction.defaultFaction .. 'Display'] > 0 then + + table.insert(factionColumns, Faction.defaultFaction) + end + + local colSpan = #factionColumns + + self.display = mw.html.create('div') + :addClass('participantTable participantTable-faction') + :css('grid-template-columns', 'repeat(' .. colSpan .. ', 1fr)') + :css('width', (colSpan * config.columnWidth) .. 'px') + :node(self:_displayHeader(factionColumns, factioNumbers)) + + Array.forEach(self.sections, function(section) self:_displaySoloRaceTableSection(section, factionColumns) end) + + return mw.html.create('div') + :addClass('table-responsive') + :node(self.display) +end + +---@return table +function StarcraftParticipantTable:_getFactionNumbers() + local calculatedNumbers = {} + + Array.forEach(self.sections, function(section) + section.entries = section.config.onlyNotable and self.filterOnlyNotables(section.entries) or section.entries + + Array.forEach(section.entries, function(entry) + local faction = entry.opponent.players[1].race or Faction.defaultFaction + --if we have defaultFaction push it into the entry too + entry.opponent.players[1].race = faction + calculatedNumbers[faction] = (calculatedNumbers[faction] or 0) + 1 + if entry.dq then + calculatedNumbers[faction .. 'Dq'] = (calculatedNumbers[faction .. 'Dq'] or 0) + 1 + end + end) + end) + + local factionNumbers = {} + for _, faction in pairs(Faction.factions) do + factionNumbers[faction] = calculatedNumbers[faction] or 0 + factionNumbers[faction .. 'Display'] = self.config.manualFactionCounts[faction] or + (factionNumbers[faction] - (calculatedNumbers[faction .. 'Dq'] or 0)) + end + + return factionNumbers +end + +---@param factionColumns table +---@param factioNumbers table +---@return Html +function StarcraftParticipantTable:_displayHeader(factionColumns, factioNumbers) + local config = self.config + local header = mw.html.create('div'):addClass('participantTable-row') + + Array.forEach(factionColumns, function(faction) + local parts = Array.extend( + config.isRandomEvent and Faction.Icon{faction = 'r'} or nil, + faction ~= Faction.defaultFaction and Faction.Icon{faction = faction} or nil, + ' ' .. Faction.toName(faction), + config.isRandomEvent and ' Main' or nil, + config.showCountByRace and " ''(" .. factioNumbers[faction .. 'Display'] .. ")''" or nil + ) + + header:tag('div') + :addClass('participantTable-faction-header participantTable-entry') + :addClass(Faction.bgClass(faction)) + :tag('div') + :wikitext(table.concat(parts)) + end) + + return header +end + +---@param section StarcraftParticipantTableSection +---@param factionColumns table +function StarcraftParticipantTable:_displaySoloRaceTableSection(section, factionColumns) + local sectionEntryCount = #Array.filter(section.entries, function(entry) return not entry.dq end) + + self.display:node(self.newSectionNode():node(self.sectionTitle(section, sectionEntryCount))) + + if Table.isEmpty(section.entries) then + self.display:node(self.newSectionNode():node(self:tbd())) + return + end + + -- Group entries by faction + local _, byFaction = Array.groupBy(section.entries, function(entry) return entry.opponent.players[1].race end) + + -- Find the race with the most players + local maxRaceLength = Array.max( + Array.map(factionColumns, function(faction) return #(byFaction[faction] or {}) end) + ) or 0 + + Array.forEach(Array.range(1, maxRaceLength), function(rowIndex) + local sectionNode = self.newSectionNode() + Array.forEach(factionColumns, function(faction) + local entry = byFaction[faction] and byFaction[faction][rowIndex] + sectionNode:node( + entry and self:displayEntry(entry, {showRace = false}) or + mw.html.create('div'):addClass('participantTable-entry') + ) + end) + self.display:node(sectionNode) + end) +end + +return StarcraftParticipantTable diff --git a/components/participant_table/wikis/starcraft/participant_table_custom.lua b/components/participant_table/wikis/starcraft/participant_table_custom.lua new file mode 100644 index 00000000000..b926fc40e35 --- /dev/null +++ b/components/participant_table/wikis/starcraft/participant_table_custom.lua @@ -0,0 +1,11 @@ +--- +-- @Liquipedia +-- wiki=starcraft +-- page=Module:ParticipantTable/Custom +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Lua = require('Module:Lua') + +return Lua.import('Module:ParticipantTable/Starcraft', {requireDevIfEnabled = true}) diff --git a/components/participant_table/wikis/starcraft2/participant_table_custom.lua b/components/participant_table/wikis/starcraft2/participant_table_custom.lua new file mode 100644 index 00000000000..f49fad77dd3 --- /dev/null +++ b/components/participant_table/wikis/starcraft2/participant_table_custom.lua @@ -0,0 +1,11 @@ +--- +-- @Liquipedia +-- wiki=starcraft2 +-- page=Module:ParticipantTable/Custom +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Lua = require('Module:Lua') + +return Lua.import('Module:ParticipantTable/Starcraft', {requireDevIfEnabled = true})