From 6f9ed8894949b7e1413e8084cd58bca7622a0493 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Fri, 27 Mar 2026 10:33:49 +0100 Subject: [PATCH 01/41] MainPageSeasonEvents --- lua/wikis/starcraft2/MainPageSeasonEvents.lua | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 lua/wikis/starcraft2/MainPageSeasonEvents.lua diff --git a/lua/wikis/starcraft2/MainPageSeasonEvents.lua b/lua/wikis/starcraft2/MainPageSeasonEvents.lua new file mode 100644 index 00000000000..491098dd7ba --- /dev/null +++ b/lua/wikis/starcraft2/MainPageSeasonEvents.lua @@ -0,0 +1,171 @@ +local Lua = require('Module:Lua') + +local Arguments = Lua.import('Module:Arguments') +local Array = Lua.import('Module:Array') +local Countdown = Lua.import('Module:Countdown') +local DateExt = Lua.import('Module:Date/Ext') +local Logic = Lua.import('Module:Logic') +local Opponent = Lua.import('Module:Opponent/Custom') +local OpponentDisplay = Lua.import('Module:OpponentDisplay/Custom') +local Timezone = Lua.import('Module:Timezone') + +local Condition = Lua.import('Module:Condition') +local ConditionTree = Condition.Tree +local ConditionNode = Condition.Node +local Comparator = Condition.Comparator +local BooleanOperator = Condition.BooleanOperator +local ColumnName = Condition.ColumnName +local ConditionUtil = Condition.Util + +local HtmlWidgets = require('Module:Widget/Html/All') +local Image = Lua.import('Module:Widget/Image/Icon/Image') +local Link = Lua.import('Module:Widget/Basic/Link') +local WidgetUtil = Lua.import('Module:Widget/Util') + +local DEFAULT_ICON = 'InfoboxIcon_Tournament.png' +local SPOILER_DELAY = DateExt.daysToSeconds(3) +local YESTERDAY = DateExt.getCurrentTimestamp() - DateExt.daysToSeconds(1) + +---@class Sc2MainPageSeasonEventsTournamentData +---@field displayName string +---@field pageName string +---@field finished boolean +---@field icon string? +---@field iconDark string? +---@field endDate integer? + +local MainPageSeasonEvents = {} + +---@param frame Frame +---@return Widget +function MainPageSeasonEvents.run(frame) + local args = Arguments.getArgs(frame) + + local pageName = assert(args.event, 'No event specified') + pageName = pageName:gsub(' ', '_') + + local tournamentData = MainPageSeasonEvents._fecthTournamentData(args, pageName) + + local iconDisplay = HtmlWidgets.Div{ + css = { + height = '70px', + display = 'flex', + ['justify-content'] = 'center', + ['align-items'] = 'center', + }, + children = { + Image{ + imageLight = tournamentData.icon, + imageDark = tournamentData.iconDark, + link = pageName, + alt = tournamentData.displayName, + size = '140x70px', + }, + }, + } + + return HtmlWidgets.Div{ + css = {['text-align'] = 'center'}, + children = WidgetUtil.collect( + iconDisplay, + HtmlWidgets.Br{}, + Link{link = pageName, children = tournamentData.displayName}, + tournamentData.finished + and MainPageSeasonEvents._winnerDisplay(tournamentData) + or MainPageSeasonEvents._countdown(tournamentData, args) + ) + } +end + +---@param args table +---@param pageName string +---@return Sc2MainPageSeasonEventsTournamentData +function MainPageSeasonEvents._fecthTournamentData(args, pageName) + local pageData = mw.ext.LiquipediaDB.lpdb('tournament', { + conditions = '[[pagename::' .. pageName .. ']]', + query = 'icon, icondark, enddate, shortname, tickername, name, status', + limit = 1, + }) + pageData = pageData[1] or {} + + return { + displayName = args.displayname + or Logic.isNotEmpty(pageData.tickername) and pageData.tickername + or Logic.isNotEmpty(pageData.shortname) and pageData.shortname + or Logic.isNotEmpty(pageData.name) and pageData.name + or pageName:gsub('_', ' '), + icon = Logic.nilIfEmpty(pageData.icon) or args.icon or DEFAULT_ICON, + iconDark = Logic.nilIfEmpty(pageData.icondark) or args.icondark or args.iconDark, + finished = pageData.status == 'finished', + pageName = pageName, + endDate = DateExt.readTimestamp(pageData.enddate), + } +end + +---@param tournamentData Sc2MainPageSeasonEventsTournamentData +---@return Widget[] +function MainPageSeasonEvents._winnerDisplay(tournamentData) + local winnerPlacement = mw.ext.LiquipediaDB.lpdb('placement', { + conditions = '[[parent::' .. tournamentData.pageName .. ']] and [[placement::1]]', + query = 'opponentplayers, opponenttemplate, opponenttype', + limit = 1, + })[1] + + local winner = winnerPlacement and Opponent.fromLpdbStruct(winnerPlacement) + local dateDiff = DateExt.getCurrentTimestamp() - (tournamentData.endDate or 0) + local showWinner = winner and dateDiff > SPOILER_DELAY + + + + return WidgetUtil.collect( + HtmlWidgets.Br{}, + showWinner and { + Image{ + imageLight = 'Trophy icon small.gif', + size = '18px', + link = '', + alt = 'Winner', + }, + OpponentDisplay.InlineOpponent{opponent = winner} + } or HtmlWidgets.Span{ + classes = {'forest-green-text'}, + children = HtmlWidgets.B{children = 'Completed'}, + } + ) +end + +---@param tournamentData Sc2MainPageSeasonEventsTournamentData +---@param args table +---@return Widget[]? +function MainPageSeasonEvents._countdown(tournamentData, args) + + local pages = Array.mapIndexes(function(index) + return Logic.nilIfEmpty((args['additional_page' .. index] or ''):gsub(' ', '_')) + end) + table.insert(pages, tournamentData.pageName) + + local conditions = ConditionTree(BooleanOperator.all):add{ + ConditionUtil.anyOf(ColumnName('parent'), pages), + ConditionNode(ColumnName('finished'), Comparator.eq, 0), + ConditionNode(ColumnName('date'), Comparator.gt, YESTERDAY), + ConditionNode(ColumnName('dateexact'), Comparator.eq, 1), + } + + local matches = mw.ext.LiquipediaDB.lpdb('match2', { + conditions = tostring(conditions), + query = 'date', + order = 'date asc', + limit = 1, + }) + + if #matches == 0 then + return + end + + return { + HtmlWidgets.Br{}, + Countdown.create{date = matches[1].date .. Timezone.getTimezoneString{timezone = 'UTC'}, rawcountdown = 'true'}, + } +end + +return MainPageSeasonEvents From e03f35cd1614442a0f855d8c4066d7d17d8dee55 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Fri, 27 Mar 2026 12:12:23 +0100 Subject: [PATCH 02/41] StreamList --- lua/wikis/starcraft2/StreamList.lua | 115 ++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 lua/wikis/starcraft2/StreamList.lua diff --git a/lua/wikis/starcraft2/StreamList.lua b/lua/wikis/starcraft2/StreamList.lua new file mode 100644 index 00000000000..29e56c71e7a --- /dev/null +++ b/lua/wikis/starcraft2/StreamList.lua @@ -0,0 +1,115 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Class = Lua.import('Module:Class') +local Box = Lua.import('Module:Box') +local Flags = Lua.import('Module:Flags') +local Logic = Lua.import('Module:Logic') +local StreamLinks = Lua.import('Module:Links/Stream') +local Table = Lua.import('Module:Table') + +local HtmlWidgets = require('Module:Widget/Html/All') +local Link = Lua.import('Module:Widget/Basic/Link') + +local StreamList = {} + +local DEFAULT_DEFAULT_PROVIDER = 'twitch' +local DEFAULT_DEFAULT_FLAG = 'usuk' +local COLUMN_BREAK = 5 + +local PROVIDERS = StreamLinks.countdownPlatformNames + +---@param args table +---@return Widget[] +function StreamList.run(args) + local defaultProvider = Logic.nilIfEmpty(args.defaultprovider) or DEFAULT_DEFAULT_PROVIDER + local defaultFlag = Logic.nilIfEmpty(args.defaultflag) or DEFAULT_DEFAULT_FLAG + + ---@param streamIndex integer + ---@return Widget? + local makeLink = function(streamIndex) + local stream = args['s' .. streamIndex] or args['link' .. streamIndex] + if Logic.isEmpty(stream) then + return + end + if Logic.isNotEmpty(args['link' .. streamIndex]) then + return Link{ + link = args['link' .. streamIndex], + children = args['display' .. streamIndex] or stream, + linktype = 'external', + } + end + + local provider = string.lower(args['provider' .. streamIndex] or defaultProvider) + assert(Table.includes(PROVIDERS, provider), 'Invalid Provider: ' .. provider) + if provider == 'afreeca' then + provider = 'afreecatv' + end + + return Link{ + link = 'Special:Stream/' .. provider .. '/' .. stream, + children = args['display' .. streamIndex] or stream, + } + end + + ---@type {link: Widget, flag: string}[] + local streams = Array.mapIndexes(function(streamIndex) + local link = makeLink(streamIndex) + if not link then + return + end + return { + link = makeLink(streamIndex), + flag = args['flag' .. streamIndex] or defaultFlag, + } + end) + + return StreamList._display(streams, tonumber(args.columnbreak) or COLUMN_BREAK) +end + +---@param streams {link: Widget, flag: string}[] +---@param columnBreak integer +---@return Widget|(Widget|string)[] +function StreamList._display(streams, columnBreak) + local makeList = function(items) + return HtmlWidgets.Ul{children = Array.map(items, StreamList._row)} + end + + if #streams <= columnBreak then + return makeList(streams) + end + + ---@type {link: Widget, flag: string}[][] + local segments = {} + local currentIndex = 0 + Array.forEach(streams, function(item, index) + if index % columnBreak == 1 then + currentIndex = currentIndex + 1 + segments[currentIndex] = {} + end + table.insert(segments[currentIndex], item) + end) + + ---@type (Widget|string)[] + local parts = Array.map(segments, makeList) + parts = Array.interleave(parts, Box.brk{padding = '2em'}) + + table.insert(parts, 1, Box._template_box_start{padding = '2em'}) + table.insert(parts, Box.finish()) + + return HtmlWidgets.Fragment{children = parts} +end + +---@param data {link: Widget, flag: string} +---@return Widget +function StreamList._row(data) + return HtmlWidgets.Li{ + children = { + Flags.Icon{flag = data.flag, shouldLink = false}, + ' ', + data.link, + }, + } +end + +return Class.export(StreamList, {exports = {'run'}}) From efac17a6e476f42db69fe0ad2980aa53fded3d71 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sat, 28 Mar 2026 14:14:45 +0100 Subject: [PATCH 03/41] TranslationList --- lua/wikis/starcraft2/MainPageSeasonEvents.lua | 1 - lua/wikis/starcraft2/StreamList.lua | 42 +++++------ lua/wikis/starcraft2/TranslationList.lua | 69 +++++++++++++++++++ 3 files changed, 86 insertions(+), 26 deletions(-) create mode 100644 lua/wikis/starcraft2/TranslationList.lua diff --git a/lua/wikis/starcraft2/MainPageSeasonEvents.lua b/lua/wikis/starcraft2/MainPageSeasonEvents.lua index 491098dd7ba..705b35683f8 100644 --- a/lua/wikis/starcraft2/MainPageSeasonEvents.lua +++ b/lua/wikis/starcraft2/MainPageSeasonEvents.lua @@ -138,7 +138,6 @@ end ---@param args table ---@return Widget[]? function MainPageSeasonEvents._countdown(tournamentData, args) - local pages = Array.mapIndexes(function(index) return Logic.nilIfEmpty((args['additional_page' .. index] or ''):gsub(' ', '_')) end) diff --git a/lua/wikis/starcraft2/StreamList.lua b/lua/wikis/starcraft2/StreamList.lua index 29e56c71e7a..cce82774558 100644 --- a/lua/wikis/starcraft2/StreamList.lua +++ b/lua/wikis/starcraft2/StreamList.lua @@ -10,6 +10,8 @@ local Table = Lua.import('Module:Table') local HtmlWidgets = require('Module:Widget/Html/All') local Link = Lua.import('Module:Widget/Basic/Link') +local UnorderedList = Lua.import('Module:Widget/List/Unordered') +local WidgetUtil = Lua.import('Module:Widget/Util') local StreamList = {} @@ -71,14 +73,6 @@ end ---@param columnBreak integer ---@return Widget|(Widget|string)[] function StreamList._display(streams, columnBreak) - local makeList = function(items) - return HtmlWidgets.Ul{children = Array.map(items, StreamList._row)} - end - - if #streams <= columnBreak then - return makeList(streams) - end - ---@type {link: Widget, flag: string}[][] local segments = {} local currentIndex = 0 @@ -91,24 +85,22 @@ function StreamList._display(streams, columnBreak) end) ---@type (Widget|string)[] - local parts = Array.map(segments, makeList) - parts = Array.interleave(parts, Box.brk{padding = '2em'}) - - table.insert(parts, 1, Box._template_box_start{padding = '2em'}) - table.insert(parts, Box.finish()) - - return HtmlWidgets.Fragment{children = parts} -end + local parts = Array.map(segments, function(group) + return UnorderedList{children = Array.map(group, function(data) + return { + Flags.Icon{flag = data.flag, shouldLink = false}, + ' ', + data.link, + } + end)} + end) ----@param data {link: Widget, flag: string} ----@return Widget -function StreamList._row(data) - return HtmlWidgets.Li{ - children = { - Flags.Icon{flag = data.flag, shouldLink = false}, - ' ', - data.link, - }, + return HtmlWidgets.Fragment{ + children = WidgetUtil.collect( + Box._template_box_start{padding = '2em'}, + Array.interleave(parts, Box.brk{padding = '2em'}), + Box.finish() + ) } end diff --git a/lua/wikis/starcraft2/TranslationList.lua b/lua/wikis/starcraft2/TranslationList.lua new file mode 100644 index 00000000000..7c55559afbf --- /dev/null +++ b/lua/wikis/starcraft2/TranslationList.lua @@ -0,0 +1,69 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Box = require('Module:Box') +local Class = Lua.import('Module:Class') +local Flags = Lua.import('Module:Flags') +local Logic = Lua.import('Module:Logic') +local Operator = Lua.import('Module:Operator') + +local UnorderedList = Lua.import('Module:Widget/List/Unordered') +local Widget = Lua.import('Module:Widget') +local WidgetUtil = Lua.import('Module:Widget/Util') + +local COLUMN_BREAK = 6 + +---@class TranslationList: Widget +---@operator call(table): TranslationList +local TranslationList = Class.new(Widget) + +---@return Widget? +function TranslationList:render() + ---@param item {flag: string, value: string, postFix: string?} + ---@return string + local displayItem = function(item) + return Flags.Icon{flag = item.flag} .. (item.postFix or ' ') .. item.value + end + + ---@type Widget[] + local parts = Array.map(self:_parse(), function(group) + return UnorderedList{children = Array.map(group, displayItem)} + end) + + return WidgetUtil.collect( + Box._template_box_start{padding = '2em'}, + Array.interleave(parts, Box.brk{padding = '2em'}), + Box.finish() + ) +end + +---@return {flag: string, value: string, postFix: string?}[][] +function TranslationList:_parse() + local data = Array.map(Array.extractKeys(self.props), function(key) + if key == 'children' then + return + elseif key == 'simpliefiedChinese' or key == 'chineseSimplified' then + return {flag = 'cn', value = self.props[key], postFix = ' (simplified) '} + elseif key == 'traditionalChinese' or key == 'chineseTraditional' then + return {flag = 'cn', value = self.props[key], postFix = ' (traditional) '} + end + return { + flag = assert(Logic.nilIfEmpty(Flags.CountryCode{flag = key}), 'Unsupported parameter: ' .. key), + value = self.props[key], + } + end) + + Array.sortInPlaceBy(data, Operator.property('flag')) + + local groupedData = {} + Array.forEach(data, function(item, index) + if index % COLUMN_BREAK == 1 then + table.insert(groupedData, {}) + end + table.insert(groupedData[#groupedData], item) + end) + + return groupedData +end + +return TranslationList From c938c6d907acb39dfb87983a8ed87896a7e9a4e4 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sat, 28 Mar 2026 18:49:58 +0100 Subject: [PATCH 04/41] TeamTabs --- lua/wikis/starcraft2/TeamTabs.lua | 234 ++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 lua/wikis/starcraft2/TeamTabs.lua diff --git a/lua/wikis/starcraft2/TeamTabs.lua b/lua/wikis/starcraft2/TeamTabs.lua new file mode 100644 index 00000000000..18fb105a31d --- /dev/null +++ b/lua/wikis/starcraft2/TeamTabs.lua @@ -0,0 +1,234 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Arguments = Lua.import('Module:Arguments') +local DateExt = Lua.import('Module:Date/Ext') +local Logic = Lua.import('Module:Logic') +local OpponentDisplay = Lua.import('Module:OpponentDisplay/Custom') +local Page = Lua.import('Module:Page') +local Table = Lua.import('Module:Table') +local Tabs = Lua.import('Module:Tabs') +local TeamTemplate = Lua.import('Module:TeamTemplate') + +local Link = Lua.import('Module:Widget/Basic/Link') + +local SPECIAL_SUBPAGES = { + 'Transfers', + 'Team Record', + 'History', + 'Teamkills', + 'League Rosters', + 'Clan Wars', +} +local NOW = DateExt.toYmdInUtc(DateExt.getCurrentTimestamp() + DateExt.daysToSeconds(1)) + +local TeamTabs = {} + +---@param frame Frame +---@return Widget +function TeamTabs.run(frame) + local args = Arguments.getArgs(frame) + + local pageName = args.team or args.pageName or mw.title.getCurrentTitle().prefixedText + -- first remove any year range sub sub page stuff + pageName = string.gsub(pageName, '/[%d%-]+$', '') + pageName = string.gsub(pageName, '/%d+%-Present$', '') + + local subTeams = Array.mapIndexes(function(subTeamIndex) + return args['sub' .. subTeamIndex] or args['subTeam' .. subTeamIndex] + end) + + local mainTeam, currentSubTeam = TeamTabs._getTeam(pageName, subTeams) + local subpageName = pageName:gsub(mainTeam, '') + if currentSubTeam then + subpageName = subpageName:gsub('^/currentSubTeam', '') + end + subpageName = subpageName:gsub('.*/([^/]+)$', '%1') + subpageName = Logic.nilIfEmpty(subpageName) + + TeamTabs._setDisplayTitle(args, currentSubTeam or mainTeam, subpageName) + + return TeamTabs._display( + mainTeam, + subTeams, + not Logic.readBool(args.hidePlayerSpecific), + currentSubTeam, + tonumber(args.currentTab) + ) +end + +---@param pageName string +---@param subTeams string[] +---@return string, string? +function TeamTabs._getTeam(pageName, subTeams) + local mainTeam = pageName:gsub('(.*)/[^/]-$', '%1') + + local subpageName = pageName:gsub(mainTeam, ''):gsub('.*/([^/]+)$', '%1') + + if Table.includes(subTeams, subpageName) then + return mainTeam, mainTeam .. '/' .. subpageName + end + + return mainTeam +end + +---@private +---@param args table +---@param team string +---@param subpageName string +function TeamTabs._setDisplayTitle(args, team, subpageName) + ---@param title string + local setDisplayTitle = function(title) + mw.getCurrentFrame():callParserFunction('DISPLAYTITLE', title) + end + + if Logic.isNotEmpty(args.title) then + setDisplayTitle(args.title) + return + end + + ---@return string + local queryDisplayName = function() + local data = mw.ext.LiquipediaDB.lpdb('team', { + conditions = '[[pagename::' .. team:gsub(' ', '_') .. ']]', + query = 'name', + }) + return (data[1] or {}).name or team + end + + local title = (args.displayName or queryDisplayName()) .. (subpageName and (': ' .. subpageName) or '') + + setDisplayTitle(title) +end + +---@private +---@param mainTeam string +---@param subTeams string[] +---@param showPlayerSubTabs boolean +---@param currentSubTeam string? +---@param currentSubTab integer? +---@param displayName string? +---@return Widget +function TeamTabs._display(mainTeam, subTeams, showPlayerSubTabs, currentSubTeam, currentSubTab, displayName) + if Logic.isEmpty(subTeams) then + return TeamTabs._getTabsForSubTeam(mainTeam, showPlayerSubTabs, currentSubTab) + end + + mw.ext.TeamLiquidIntegration.add_category('Teams with subTeam pages') + + ---@type table + local tabArgs = {} + local currentTab + + ---@param subTeam string? + ---@param subTeamIndex integer + local buildArgsForTeam = function(subTeam, subTeamIndex) + local tabIndex = subTeamIndex + 1 + local link = mainTeam .. (subTeam and ('/' .. subTeam) or '') + + local teamTemplateDisplay = TeamTemplate.exists(link) and OpponentDisplay.InlineTeamContainer{template = link} or nil + if not teamTemplateDisplay then + tabArgs['link' .. tabIndex] = link + end + + tabArgs['name' .. tabIndex] = teamTemplateDisplay or displayName or (mainTeam .. ': ' .. subTeam) + + if link == currentSubTeam or (not currentSubTeam and link == mainTeam) then + currentTab = tabIndex + tabArgs['tabs' .. tabIndex] = TeamTabs._getTabsForSubTeam(link, showPlayerSubTabs, currentSubTab) + end + end + + buildArgsForTeam(nil, 0) + Array.forEach(subTeams, buildArgsForTeam) + tabArgs.This = currentTab + + return Tabs.static(tabArgs) +end + +---@private +---@param team string +---@param showPlayerSubTabs boolean +---@param currentTab integer? +---@return Widget +function TeamTabs._getTabsForSubTeam(team, showPlayerSubTabs, currentTab) + ---@param args {form: string, template: string, display: string, queryArgs: table} + ---@return string + local makeQueryLink = function(args) + local prefix = args.template + local queryArgs = Table.map(args.queryArgs, function(key, item) + return prefix .. key, item + end) + return Link{ + linktype = 'external', + children = args.display, + link = tostring(mw.uri.fullUrl( + 'Special:RunQuery/' .. args.form, + mw.uri.buildQueryString(queryArgs) .. '&_run' + )) + } + end + + local tabArgs = { + name1 = 'Overview', + link1 = team, + name2 = makeQueryLink{ + form = 'Team Results', + display = 'Team Results', + template = 'Team results', + queryArgs = { + ['[team]'] = team, + ['[tier]'] = '1,2,3', + ['[edate]'] = NOW, + ['[limit]'] = '250', + }, + }, + name3 = makeQueryLink{ + form = 'Team Matches', + display = 'Team Matches', + template = 'Team matches', + queryArgs = { + ['[team]'] = team, + ['[tier]'] = '1,2,3', + ['[edate]'] = NOW, + ['[linkSubPage]'] = 'false', + ['[limit]'] = '250', + }, + }, + } + + local tabCounter = 3 + if showPlayerSubTabs then + tabArgs.name4 = makeQueryLink{ + form = 'Team Player Results', + display = 'Player Results', + template = 'Team player results', + queryArgs = { + ['[team]'] = team, + ['[tier]'] = '1,2,3', + ['[edate]'] = NOW, + ['[limit]'] = '250', + }, + } + + tabArgs.name5 = 'Player Matches' + tabArgs.link5 = team .. '/Player Matches' + + tabCounter = 5 + end + + -- add special sub pages that some might have + -- only add them if the according sub page actually exists + Array.forEach(SPECIAL_SUBPAGES, function(item) + if not Page.exists(team .. '/' .. item) then return end + tabCounter = tabCounter + 1 + tabArgs['name' .. tabCounter] = item + tabArgs['link' .. tabCounter] = team .. '/' .. item + end) + + tabArgs.This = currentTab + + return Tabs.static(tabArgs) +end + +return TeamTabs From cdf917ec2e421c1635703750fdb94efaa0fc582b Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sun, 29 Mar 2026 13:17:29 +0200 Subject: [PATCH 05/41] moverino --- lua/wikis/starcraft2/{ => Widget}/TranslationList.lua | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lua/wikis/starcraft2/{ => Widget}/TranslationList.lua (100%) diff --git a/lua/wikis/starcraft2/TranslationList.lua b/lua/wikis/starcraft2/Widget/TranslationList.lua similarity index 100% rename from lua/wikis/starcraft2/TranslationList.lua rename to lua/wikis/starcraft2/Widget/TranslationList.lua From 8e4310152209db745346c70c53c71f04e971a1e3 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sun, 29 Mar 2026 13:30:10 +0200 Subject: [PATCH 06/41] CountryParticipation --- .../Widget/CountryParticipation.lua | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 lua/wikis/starcraft2/Widget/CountryParticipation.lua diff --git a/lua/wikis/starcraft2/Widget/CountryParticipation.lua b/lua/wikis/starcraft2/Widget/CountryParticipation.lua new file mode 100644 index 00000000000..9e991944625 --- /dev/null +++ b/lua/wikis/starcraft2/Widget/CountryParticipation.lua @@ -0,0 +1,101 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Class = Lua.import('Module:Class') +local Flags = Lua.import('Module:Flags') +local TournamentStructure = Lua.import('Module:TournamentStructure') + +local Condition = Lua.import('Module:Condition') +local ConditionTree = Condition.Tree +local ConditionNode = Condition.Node +local Comparator = Condition.Comparator +local BooleanOperator = Condition.BooleanOperator +local ColumnName = Condition.ColumnName + +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local Widget = Lua.import('Module:Widget') + +---@class CountryParticipation: Widget +---@operator call(table): CountryParticipation +local CountryParticipation = Class.new(Widget) + +function CountryParticipation:render() + return TableWidgets.Table{ + sortable = true, + columns = { + {align = 'left'}, + {align = 'center'}, + }, + children = { + TableWidgets.TableHeader{ + children = { + TableWidgets.Row{ + children = { + TableWidgets.CellHeader{children = 'Country'}, + TableWidgets.CellHeader{children = '#Players'}, + } + } + } + }, + TableWidgets.TableBody{children = Array.map(self:_fetch(), CountryParticipation._row)} + }, + } +end + +---@param data {flag: string, count: integer} +---@return Widget +function CountryParticipation._row(data) + return TableWidgets.Row{ + children = { + TableWidgets.Cell{ + children = { + Flags.Icon{flag = data.flag, shouldLink = false}, + ' ', + data.flag, + } + }, + TableWidgets.Cell{children = data.count}, + } + } +end + +---@return {flag: string, count: integer}[] +function CountryParticipation:_fetch() + local pageNames = Array.flatten((TournamentStructure.readMatchGroupsSpec(self.props) + or TournamentStructure.currentPageSpec()).pageNames) + + local conditions = ConditionTree(BooleanOperator.all):add{ + ConditionNode(ColumnName('name'), Comparator.neq, ''), + TournamentStructure.getPageNamesFilter('bracket', pageNames), + } + + local queryData = mw.ext.LiquipediaDB.lpdb('match2player', { + conditions = tostring(conditions), + groupby = 'name asc', + query = 'flag', + limit = 5000 + }) + + ---@type table + local flags = {} + Array.forEach(queryData, function(item) + local flag = item.flag + if not flags[flag] then + flags[flag] = {flag = flag, count = 0} + end + flags[flag].count = flags[flag].count + 1 + end) + + ---@type {flag: string, count: integer}[] + local data = Array.extractValues(flags) + table.sort(data, function(a, b) + if a.count ~= b.count then + return a.count > b.count + end + return a.flag < b.flag + end) + + return data +end + +return CountryParticipation From 00f1a7c98a320c0c48ac6748a1a7a58717bf7d3f Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sun, 29 Mar 2026 13:54:18 +0200 Subject: [PATCH 07/41] FindMatchesByPlayerDisplayName --- .../FindMatchesByPlayerDisplayName.lua | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 lua/wikis/starcraft2/FindMatchesByPlayerDisplayName.lua diff --git a/lua/wikis/starcraft2/FindMatchesByPlayerDisplayName.lua b/lua/wikis/starcraft2/FindMatchesByPlayerDisplayName.lua new file mode 100644 index 00000000000..0a7c5b9bfad --- /dev/null +++ b/lua/wikis/starcraft2/FindMatchesByPlayerDisplayName.lua @@ -0,0 +1,44 @@ +local Lua = require('Module:Lua') + +local Arguments = Lua.import('Module:Arguments') +local Array = Lua.import('Module:Array') +local Condition = Lua.import('Module:Condition') + +local Link = Lua.import('Module:Widget/Basic/Link') +local UnorderedList = Lua.import('Module:Widget/List/Unordered') + +local FindMatchesByPlayerDisplayName = {} + +---@param frame Frame +---@return Widget +function FindMatchesByPlayerDisplayName.run(frame) + local args = Arguments.getArgs(frame) + assert(args.p1, 'No player(s) specified') + + local displayNames = Array.flatten(Array.mapIndexes(function(index) + if not args['p' .. index] then return end + return { + mw.getContentLanguage():lcfirst(args['p' .. index]), + mw.getContentLanguage():ucfirst(args['p' .. index]), + } + end)) + + local data = mw.ext.LiquipediaDB.lpdb('match2player', { + conditions = tostring(Condition.Util.anyOf(Condition.ColumnName('displayname'), displayNames)), + query = 'pagename, name, displayname', + groupby = 'pagename asc', + limit = 5000, + }) + + return UnorderedList{ + children = Array.map(data, function(item) + return { + Link{link = item.pagename}, + ' - name: ' .. item.name, + ' - displayName: ' .. item.displayname, + } + end) + } +end + +return FindMatchesByPlayerDisplayName From 9f21658730cfb949e4ed5a10b59d58508f1d6414 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sun, 29 Mar 2026 14:08:38 +0200 Subject: [PATCH 08/41] FindPlayersOnTeamFromPlacements --- .../FindPlayersOnTeamFromPlacements.lua | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 lua/wikis/starcraft2/FindPlayersOnTeamFromPlacements.lua diff --git a/lua/wikis/starcraft2/FindPlayersOnTeamFromPlacements.lua b/lua/wikis/starcraft2/FindPlayersOnTeamFromPlacements.lua new file mode 100644 index 00000000000..b217db0dc2f --- /dev/null +++ b/lua/wikis/starcraft2/FindPlayersOnTeamFromPlacements.lua @@ -0,0 +1,73 @@ +local Lua = require('Module:Lua') + +local Arguments = Lua.import('Module:Arguments') +local Array = Lua.import('Module:Array') +local Logic = Lua.import('Module:Logic') + +local Condition = Lua.import('Module:Condition') +local ConditionTree = Condition.Tree +local ConditionNode = Condition.Node +local Comparator = Condition.Comparator +local BooleanOperator = Condition.BooleanOperator +local ColumnName = Condition.ColumnName + +local Link = Lua.import('Module:Widget/Basic/Link') +local UnorderedList = Lua.import('Module:Widget/List/Unordered') + +local FindPlayersOnTeamFromPlacements = {} + +---@param frame Frame +---@return Widget +function FindPlayersOnTeamFromPlacements.run(frame) + local args = Arguments.getArgs(frame) + assert(args.team, 'No player(s) specified') + local team = mw.ext.TeamLiquidIntegration.resolve_redirect(args.team) + + local conditions = ConditionTree(BooleanOperator.any):add(Array.map(Array.range(1, 30), function(index) + return ConditionNode(ColumnName('opponentplayers_p' .. index .. 'team'), Comparator.eq, team) + end)) + + local data = mw.ext.LiquipediaDB.lpdb('match2player', { + conditions = tostring(conditions), + query = 'opponentplayers, date', + order = 'date desc', + limit = 5000, + }) + + local players = {} + Array.forEach(data, function(item) + local _ = Array.mapIndexes(function(index) + local player = item.opponentplayers['p' .. index] + if Logic.isEmpty(player) then + return + end + local playerTeam = item.opponentplayers['p' .. index .. 'team'] + if playerTeam ~= team then + return true + end + players[player] = players[player] or { + pageName = player, + displayName = item.opponentplayers['p' .. index .. 'dn'], + flag = item.opponentplayers['p' .. index .. 'flag'] or 'unknown flag', + faction = item.opponentplayers['p' .. index .. 'faction'] or 'unknown faction', + date = item.date, + } + + return true + end) + end) + + return UnorderedList{ + children = Array.map(Array.extractValues(players), function(player) + return { + Link{link = player.pageName}, + ' - displayName: ' .. player.displayName, + ' - flag: ' .. player.flag, + ' - faction: ' .. player.faction, + ' - last result from: ' .. player.date, + } + end) + } +end + +return FindPlayersOnTeamFromPlacements From 121ec0169830a49bd6e1d79d05f8bcccf45b8cd6 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sun, 29 Mar 2026 14:26:18 +0200 Subject: [PATCH 09/41] fixerino --- lua/wikis/starcraft2/FindPlayersOnTeamFromPlacements.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/wikis/starcraft2/FindPlayersOnTeamFromPlacements.lua b/lua/wikis/starcraft2/FindPlayersOnTeamFromPlacements.lua index b217db0dc2f..63230ef3c63 100644 --- a/lua/wikis/starcraft2/FindPlayersOnTeamFromPlacements.lua +++ b/lua/wikis/starcraft2/FindPlayersOnTeamFromPlacements.lua @@ -18,7 +18,7 @@ local FindPlayersOnTeamFromPlacements = {} ---@param frame Frame ---@return Widget -function FindPlayersOnTeamFromPlacements.run(frame) +function FindPlayersOnTeamFromPlacements.get(frame) local args = Arguments.getArgs(frame) assert(args.team, 'No player(s) specified') local team = mw.ext.TeamLiquidIntegration.resolve_redirect(args.team) @@ -27,7 +27,7 @@ function FindPlayersOnTeamFromPlacements.run(frame) return ConditionNode(ColumnName('opponentplayers_p' .. index .. 'team'), Comparator.eq, team) end)) - local data = mw.ext.LiquipediaDB.lpdb('match2player', { + local data = mw.ext.LiquipediaDB.lpdb('placement', { conditions = tostring(conditions), query = 'opponentplayers, date', order = 'date desc', From 0519a2d6a8ce8380ead8ad777b50255aaf141763 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sun, 29 Mar 2026 14:56:04 +0200 Subject: [PATCH 10/41] PlayerAchievementsTableWrapper --- .../PlayerAchievementsTableWrapper.lua | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 lua/wikis/starcraft2/PlayerAchievementsTableWrapper.lua diff --git a/lua/wikis/starcraft2/PlayerAchievementsTableWrapper.lua b/lua/wikis/starcraft2/PlayerAchievementsTableWrapper.lua new file mode 100644 index 00000000000..5362813e66f --- /dev/null +++ b/lua/wikis/starcraft2/PlayerAchievementsTableWrapper.lua @@ -0,0 +1,53 @@ +local Lua = require('Module:Lua') + +local Json = Lua.import('Module:Json') +local Logic = Lua.import('Module:Logic') +local Page = Lua.import('Module:Page') +local Tabs = Lua.import('Module:Tabs') +local Variables = Lua.import('Module:Variables') + +local ResultsTable = Lua.import('Module:ResultsTable/Custom') +local BroadcasterTable = Lua.import('Module:BroadcastTalentTable') + +local PlayerAchievementsTableWrapper = {} + +function PlayerAchievementsTableWrapper.run(frame) + local awards = Json.parseIfTable(Variables.varDefault('awardAchievements')) + + local hasBroadCastsSubPage = Page.exists(mw.title.getCurrentTitle().prefixedText .. '/Broadcasts') + + if Logic.isEmpty(awards) and not hasBroadCastsSubPage then + return ResultsTable.results(frame) + end + + local tabArgs = { + name1 = 'Player Achievements', + content1 = ResultsTable.results(frame) + } + + local tabIndex = 1 + if Logic.isNotEmpty(awards) then + frame.args.awards = 1 + frame.args.resultsSubPage = 'Awards' + tabIndex = tabIndex + 1 + tabArgs['name' .. tabIndex] = 'Awards' + tabArgs['content' .. tabIndex] = ResultsTable.awards(frame) + end + + if hasBroadCastsSubPage then + tabIndex = tabIndex + 1 + + local broadcastAchievements = BroadcasterTable.run{ + achievements = 1, + aboutAchievementsLink = 'Template:Broadcast talent achievements table/doc', + useTickerNames = true, + } + + tabArgs['name' .. tabIndex] = broadcastAchievements and 'Talent Achievements' or nil + tabArgs['content' .. tabIndex] = broadcastAchievements + end + + return Tabs.dynamic(tabArgs) +end + +return PlayerAchievementsTableWrapper From 15daf23bcc209ae85d654020fafb09e17f769c2d Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sun, 29 Mar 2026 15:13:20 +0200 Subject: [PATCH 11/41] require to import --- lua/wikis/starcraft2/MainPageSeasonEvents.lua | 2 +- lua/wikis/starcraft2/StreamList.lua | 2 +- lua/wikis/starcraft2/Widget/TranslationList.lua | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/wikis/starcraft2/MainPageSeasonEvents.lua b/lua/wikis/starcraft2/MainPageSeasonEvents.lua index 705b35683f8..aae8b9540c2 100644 --- a/lua/wikis/starcraft2/MainPageSeasonEvents.lua +++ b/lua/wikis/starcraft2/MainPageSeasonEvents.lua @@ -17,7 +17,7 @@ local BooleanOperator = Condition.BooleanOperator local ColumnName = Condition.ColumnName local ConditionUtil = Condition.Util -local HtmlWidgets = require('Module:Widget/Html/All') +local HtmlWidgets = Lua.import('Module:Widget/Html/All') local Image = Lua.import('Module:Widget/Image/Icon/Image') local Link = Lua.import('Module:Widget/Basic/Link') local WidgetUtil = Lua.import('Module:Widget/Util') diff --git a/lua/wikis/starcraft2/StreamList.lua b/lua/wikis/starcraft2/StreamList.lua index cce82774558..ca10db2a873 100644 --- a/lua/wikis/starcraft2/StreamList.lua +++ b/lua/wikis/starcraft2/StreamList.lua @@ -8,7 +8,7 @@ local Logic = Lua.import('Module:Logic') local StreamLinks = Lua.import('Module:Links/Stream') local Table = Lua.import('Module:Table') -local HtmlWidgets = require('Module:Widget/Html/All') +local HtmlWidgets = Lua.import('Module:Widget/Html/All') local Link = Lua.import('Module:Widget/Basic/Link') local UnorderedList = Lua.import('Module:Widget/List/Unordered') local WidgetUtil = Lua.import('Module:Widget/Util') diff --git a/lua/wikis/starcraft2/Widget/TranslationList.lua b/lua/wikis/starcraft2/Widget/TranslationList.lua index 7c55559afbf..478955888f5 100644 --- a/lua/wikis/starcraft2/Widget/TranslationList.lua +++ b/lua/wikis/starcraft2/Widget/TranslationList.lua @@ -1,7 +1,7 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Box = require('Module:Box') +local Box = Lua.import('Module:Box') local Class = Lua.import('Module:Class') local Flags = Lua.import('Module:Flags') local Logic = Lua.import('Module:Logic') From 25c7b71208ca907ef63675f798dcc127dfbb8a72 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sun, 29 Mar 2026 15:32:22 +0200 Subject: [PATCH 12/41] MostAllKills --- lua/wikis/starcraft2/Widget/MostAllKills.lua | 80 ++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 lua/wikis/starcraft2/Widget/MostAllKills.lua diff --git a/lua/wikis/starcraft2/Widget/MostAllKills.lua b/lua/wikis/starcraft2/Widget/MostAllKills.lua new file mode 100644 index 00000000000..6f7e0ed3adb --- /dev/null +++ b/lua/wikis/starcraft2/Widget/MostAllKills.lua @@ -0,0 +1,80 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Class = Lua.import('Module:Class') +local Opponent = Lua.import('Module:Opponent/Custom') +local OpponentDisplay = Lua.import('Module:OpponentDisplay/Custom') + +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local Widget = Lua.import('Module:Widget') + +---@class MostAllKills: Widget +---@operator call(table): MostAllKills +local MostAllKills = Class.new(Widget) + +---@return Html +function MostAllKills:render() + return TableWidgets.Table{ + sortable = false, + tableClasses = {'prizepooltable', 'collapsed'}, + tableAttributes = { + ['data-cutafter'] = 5, + ['data-opentext'] = 'Show more', + ['data-closetext'] = 'Show less', + }, + columns = { + {align = 'left'}, + {align = 'center'}, + }, + children = { + TableWidgets.TableHeader{ + children = TableWidgets.Row{ + children = { + TableWidgets.CellHeader{children = 'Player'}, + TableWidgets.CellHeader{children = 'All-kills'}, + } + } + }, + TableWidgets.TableBody{children = self:_rows()} + }, + } +end + +---@return Widget[] +function MostAllKills:_rows() + local allKillList = mw.ext.LiquipediaDB.lpdb('datapoint', { + conditions = '[[type::allkills]]', + order = 'information desc, date desc, pagename asc', + query = 'information, pagename', + limit = 5000, + }) + + return Array.map(allKillList, function(allKillInfo) + return TableWidgets.Row{ + children = { + TableWidgets.Cell{children = self:_player(allKillInfo.pagename)}, + TableWidgets.Cell{children = allKillInfo.information}, + } + } + end) +end + +---@param pageName string +---@return Widget +function MostAllKills:_player(pageName) + local playerInfo = mw.ext.LiquipediaDB.lpdb('player', { + conditions = '[[pagename::' .. pageName .. ']]', + query = 'extradata, nationality, id', + limit = '1', + })[1] or {} + + return OpponentDisplay.BlockOpponent{opponent = Opponent.readOpponentArgs{ + name = playerInfo.id or pageName, + faction = (playerInfo.extradata or {}).faction, + flag = playerInfo.nationality, + link = pageName, + type = Opponent.solo, + }} +end + +return MostAllKills From cc9ba4f7f0d7f44e4eb6e59ff481bc5fd4866de5 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sun, 29 Mar 2026 16:24:48 +0200 Subject: [PATCH 13/41] SeriesTotalPrize --- lua/wikis/starcraft2/SeriesTotalPrize.lua | 100 ++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 lua/wikis/starcraft2/SeriesTotalPrize.lua diff --git a/lua/wikis/starcraft2/SeriesTotalPrize.lua b/lua/wikis/starcraft2/SeriesTotalPrize.lua new file mode 100644 index 00000000000..6c8f6b921df --- /dev/null +++ b/lua/wikis/starcraft2/SeriesTotalPrize.lua @@ -0,0 +1,100 @@ +local Lua = require('Module:Lua') + +local Arguments = Lua.import('Module:Arguments') +local Array = Lua.import('Module:Array') +local Currency = Lua.import('Module:Currency') +local Logic = Lua.import('Module:Logic') +local MathUtil = Lua.import('Module:MathUtil') +local Table = Lua.import('Module:Table') +local Tier = require('Module:Tier/Custom') + +local Condition = Lua.import('Module:Condition') +local ConditionTree = Condition.Tree +local ConditionNode = Condition.Node +local Comparator = Condition.Comparator +local BooleanOperator = Condition.BooleanOperator +local ColumnName = Condition.ColumnName +local ConditionUtil = Condition.Util + +local SeriesTotalPrize = {} + +---@param frame Frame +---@return string +function SeriesTotalPrize.run(frame) + local args = Arguments.getArgs(frame) + + local series = Array.parseCommaSeparatedString(args.series or mw.title.getCurrentTitle().prefixedText, '||') + series = Array.map(series, mw.ext.TeamLiquidIntegration.resolve_redirect) + + local conditions = ConditionTree(BooleanOperator.all):add{ + ConditionUtil.anyOf(ColumnName('series'), series), + ConditionNode(ColumnName('prizepool'), Comparator.gt, 0), + ConditionTree(BooleanOperator.any):add{ + ConditionNode(ColumnName('status'), Comparator.eq, ''), + ConditionNode(ColumnName('status'), Comparator.eq, 'finished'), + } + } + + local parseToFormattedNumber = function(input) + local int = MathUtil.toInteger(input) + if not int then return end + return string.format("%05d", int) + end + + local offset = parseToFormattedNumber(args.offset) + if offset then + conditions:add(ConditionNode(ColumnName('extradata_seriesnumber'), Comparator.gt, offset)) + end + local limit = parseToFormattedNumber(args.limit) + if limit then + conditions:add(ConditionNode(ColumnName('extradata_seriesnumber'), Comparator.le, limit)) + end + + local data = mw.ext.LiquipediaDB.lpdb('tournament', { + conditions = tostring(conditions), + query = 'prizepool, liquipediatier', + limit = 5000, + }) + + if not data[1] then + return '$0' + end + + local sums = {total = 0} + Array.forEach(data, function(item) + local value = (tonumber(item.prizepool) or 0) + sums[item.liquipediatier] = (sums[item.liquipediatier] or 0) + value + sums.total = sums.total + value + end) + + ---@param value number + ---@param tier string|integer? + ---@return string + local displayRow = function(value, tier) + local row = '≃ ' .. Currency.display('USD', value, {formatPrecision = 0, formatValue = true}) + if not tier then + return row + end + return Tier.display(tier) .. ': ' .. row + end + + local rows = { + displayRow(Table.extract(sums, 'total')) + } + + if Logic.readBool(args.onlytotal) or Table.size(sums) == 1 then + return rows[1] + end + + for tier, value in Table.iter.spairs(sums) do + table.insert(rows, displayRow(value, tier)) + end + + return table.concat(Array.interleave(rows, '
')) +end + +-- for legacy reasons... +SeriesTotalPrize.get = SeriesTotalPrize.run +SeriesTotalPrize._get = SeriesTotalPrize.run + +return SeriesTotalPrize From 65ab2caa9622b1c22f1a0228e878ebb486569671 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sun, 29 Mar 2026 16:56:16 +0200 Subject: [PATCH 14/41] BuildingStatisticsOverviewTable --- .../BuildingStatisticsOverviewTable.lua | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 lua/wikis/starcraft2/BuildingStatisticsOverviewTable.lua diff --git a/lua/wikis/starcraft2/BuildingStatisticsOverviewTable.lua b/lua/wikis/starcraft2/BuildingStatisticsOverviewTable.lua new file mode 100644 index 00000000000..edd47431f16 --- /dev/null +++ b/lua/wikis/starcraft2/BuildingStatisticsOverviewTable.lua @@ -0,0 +1,156 @@ +local Lua = require('Module:Lua') + +local Arguments = Lua.import('Module:Arguments') +local Array = Lua.import('Module:Array') +local Faction = Lua.import('Module:Faction') +local FnUtil = Lua.import('Module:FnUtil') +local Game = Lua.import('Module:Game') +local String = Lua.import('Module:StringUtils') +local Tabs = Lua.import('Module:Tabs') + +local Link = Lua.import('Module:Widget/Basic/Link') +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local WidgetUtil = Lua.import('Module:Widget/Util') + +local GAS = Lua.import('Module:Gas', {loadData = true}) +local BUILDTIME = Lua.import('Module:Buildtime', {loadData = true}) +local MINERALS = '[[File:Minerals.gif|baseline|link=Minerals]]' +local ARMOR = '[[File:Icon_Armor.png|link=Armor]]' +local HEALTH = '[[File:Icon_Hitpoints.png|link=]]' +local SHIELD = '[[File:Icon_Shields.png|link=Plasma Shield]]' +local PROTOSS = 'p' + +local BuildingStats = {} + +---@param frame Frame +---@return Widget +function BuildingStats.wrapper(frame) + local args = Arguments.getArgs(frame) + + local tabArgs = {} + Array.forEach(Faction.coreFactions, function(faction, index) + tabArgs['name' .. index] = Faction.Icon{faction = faction} .. ' ' .. Faction.toName(faction) + tabArgs['content' .. index] = BuildingStats._build{game = args.game, faction = faction} + end) + + return Tabs.dynamic(tabArgs) +end + +---@private +---@param args {game: string?, faction: string} +---@return Widget? +function BuildingStats._build(args) + local game = Game.name{game = args.game or 'Legacy of the Void'} + local faction = args.faction + + local buildings = BuildingStats._queryBuildings(game, faction) + if not buildings or type(buildings[1]) ~= 'table' then + return + end + + --exclude some Buildings (Campaign) + buildings = Array.filter(buildings, function(building) + local page = building.pagename:lower() + local name = building.name:lower() + + return not string.match(page,'khaydarin_monolith') + and not string.match(page,'cut_features') + and not string.match(page,'drakken_laser') + and not string.match(page,'hive_mind_emulator') + and not string.match(page,'merc_compound') + and not string.match(page,'campaign') + and not string.match(page,'starbase') + and not string.match(page,'phase_cannon') + and not string.match(page,'monolith') + and not string.match(page,'radar_tower') + and not string.match(page,'obelisk') + and not string.match(name,'supply depot lowered') + end) + + return TableWidgets.Table{ + columns = BuildingStats._columns(faction), + children = { + BuildingStats._header(faction), + TableWidgets.TableBody{children = Array.map(buildings, FnUtil.curry(BuildingStats._row, faction))} + } + } +end + +---@private +---@param game string +---@param faction string +---@return table[] +function BuildingStats._queryBuildings(game, faction) + local lowercasedGame = game:lower():gsub(' ', '_') + local buildingType = 'Building information ' .. faction + local buildings = mw.ext.LiquipediaDB.lpdb('datapoint', { + conditions = '[[type::' .. buildingType .. ']]', + order = 'name asc', + query = 'information, name, pagename, extradata', + limit = 5000, + }) + + return Array.filter(buildings, function(building) + return building.information == game + or String.contains(building.pagename:lower(), lowercasedGame) + end) +end + +---@private +---@param faction string +---@return {}[] +function BuildingStats._columns(faction) + return WidgetUtil.collect( + {align = 'left'}, -- name + {align = 'center'}, -- mins + {align = 'center'}, -- gas + {align = 'center'}, -- time + {align = 'center'}, -- helath + faction == PROTOSS and {align = 'center'} or nil, -- shield + {align = 'center'}, -- armor + {align = 'center'} -- sight + ) +end + +---@private +---@param faction string +---@return Widget +function BuildingStats._header(faction) + return TableWidgets.TableHeader{ + children = TableWidgets.Row{ + children = WidgetUtil.collect( + TableWidgets.CellHeader{children = 'Building'}, + TableWidgets.CellHeader{children = MINERALS}, + TableWidgets.CellHeader{children = GAS[faction]}, + TableWidgets.CellHeader{children = BUILDTIME[faction]}, + TableWidgets.CellHeader{children = HEALTH}, + faction == PROTOSS and TableWidgets.CellHeader{children = SHIELD} or nil, + TableWidgets.CellHeader{children = ARMOR}, + TableWidgets.CellHeader{children = Link{link = 'Sight'}} + ) + } + } +end + +---@private +---@param faction string +---@param building table +---@return Html +function BuildingStats._row(faction, building) + local extradata = building.extradata or {} + + return TableWidgets.Row{ + children = WidgetUtil.collect( + TableWidgets.Cell{children = Link{link = building.pagename, children = building.name}}, + TableWidgets.Cell{children = extradata.minerals or '-'}, + TableWidgets.Cell{children = extradata.gas or '-'}, + TableWidgets.Cell{children = extradata.buildtime}, + TableWidgets.Cell{children = extradata.hp}, + faction == PROTOSS and TableWidgets.CellHeader{children = extradata.shield} or nil, + TableWidgets.Cell{children = extradata.armor or 0}, + TableWidgets.Cell{children = extradata.sight or 9} + ) + } +end + +return BuildingStats From 93159662de1b282e644e0610faa2d9414f228549 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sun, 29 Mar 2026 17:02:46 +0200 Subject: [PATCH 15/41] fix anno --- lua/wikis/starcraft2/Widget/MostAllKills.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/wikis/starcraft2/Widget/MostAllKills.lua b/lua/wikis/starcraft2/Widget/MostAllKills.lua index 6f7e0ed3adb..878f9f2eaba 100644 --- a/lua/wikis/starcraft2/Widget/MostAllKills.lua +++ b/lua/wikis/starcraft2/Widget/MostAllKills.lua @@ -12,7 +12,7 @@ local Widget = Lua.import('Module:Widget') ---@operator call(table): MostAllKills local MostAllKills = Class.new(Widget) ----@return Html +---@return Widget function MostAllKills:render() return TableWidgets.Table{ sortable = false, @@ -40,6 +40,7 @@ function MostAllKills:render() } end +---@private ---@return Widget[] function MostAllKills:_rows() local allKillList = mw.ext.LiquipediaDB.lpdb('datapoint', { @@ -59,6 +60,7 @@ function MostAllKills:_rows() end) end +---@private ---@param pageName string ---@return Widget function MostAllKills:_player(pageName) From ec5b50569027ce23b572c57e408c97074e94ea0a Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sun, 29 Mar 2026 17:18:01 +0200 Subject: [PATCH 16/41] style --- lua/wikis/starcraft2/Widget/CountryParticipation.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lua/wikis/starcraft2/Widget/CountryParticipation.lua b/lua/wikis/starcraft2/Widget/CountryParticipation.lua index 9e991944625..5e982979412 100644 --- a/lua/wikis/starcraft2/Widget/CountryParticipation.lua +++ b/lua/wikis/starcraft2/Widget/CountryParticipation.lua @@ -28,12 +28,10 @@ function CountryParticipation:render() }, children = { TableWidgets.TableHeader{ - children = { - TableWidgets.Row{ - children = { - TableWidgets.CellHeader{children = 'Country'}, - TableWidgets.CellHeader{children = '#Players'}, - } + children = TableWidgets.Row{ + children = { + TableWidgets.CellHeader{children = 'Country'}, + TableWidgets.CellHeader{children = '#Players'}, } } }, @@ -42,6 +40,7 @@ function CountryParticipation:render() } end +---@private ---@param data {flag: string, count: integer} ---@return Widget function CountryParticipation._row(data) @@ -59,6 +58,7 @@ function CountryParticipation._row(data) } end +---@private ---@return {flag: string, count: integer}[] function CountryParticipation:_fetch() local pageNames = Array.flatten((TournamentStructure.readMatchGroupsSpec(self.props) From a83722f7255f669e27545da12efa35a5ddb73d36 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sun, 29 Mar 2026 18:13:31 +0200 Subject: [PATCH 17/41] AbilityUpgradeCard --- .../starcraft2/Widget/AbilityUpgradeCard.lua | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 lua/wikis/starcraft2/Widget/AbilityUpgradeCard.lua diff --git a/lua/wikis/starcraft2/Widget/AbilityUpgradeCard.lua b/lua/wikis/starcraft2/Widget/AbilityUpgradeCard.lua new file mode 100644 index 00000000000..58dfb90e96c --- /dev/null +++ b/lua/wikis/starcraft2/Widget/AbilityUpgradeCard.lua @@ -0,0 +1,166 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Buildtime = Lua.import('Module:Buildtime', {loadData = true}) +local Class = Lua.import('Module:Class') +local Faction = Lua.import('Module:Faction') +local Gas = Lua.import('Module:Gas', {loadData = true}) +local Hotkey = Lua.import('Module:Hotkey') +local Logic = Lua.import('Module:Logic') +local Page = Lua.import('Module:Page') +local Supply = Lua.import('Module:Supply', {loadData = true}) + +local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Image = Lua.import('Module:Widget/Image/Icon/Image') +local Link = Lua.import('Module:Widget/Basic/Link') +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local Widget = Lua.import('Module:Widget') +local WidgetUtil = Lua.import('Module:Widget/Util') + +local MINERALS = '[[File:Minerals.gif|baseline|link=Minerals]]' + +---@class AbilityUpgradeCard: Widget +---@operator call(table): AbilityUpgradeCard +local AbilityUpgradeCard = Class.new(Widget) +AbilityUpgradeCard.defaultProps = { + name = 'missing name', +} + +---@return Widget +function AbilityUpgradeCard:render() + self.cardType = assert(self.props.cardType,'no "|cardType=" specified') + + self.faction = Faction.read(self.props.faction or self.props.race) + + if self.cardType == 'Upgrade' and not Logic.readBool(self.props['no-cat']) then + mw.ext.TeamLiquidIntegration.add_category('Upgrades') + if self.faction then + mw.ext.TeamLiquidIntegration.add_category(Faction.toName(self.faction) .. ' Upgrades') + end + end + + return TableWidgets.Table{ + sortable = true, + columns = { + {align = 'center', width = '75px'}, + {align = 'left', width = '375px'}, + }, + children = { + TableWidgets.TableHeader{ + children = TableWidgets.Row{ + children = TableWidgets.CellHeader{ + colspan = 2, + classes = {Faction.bgClass(self.faction or 't')}, + children = (self.props.link or Page.exists(self.props.name)) + and Link{link = self.props.link or self.props.name, children = self.props.name} + or self.props.name, + } + } + }, + TableWidgets.TableBody{ + children = TableWidgets.Row{ + children = { + self:_image(), + self:_infos(), + } + } + } + } + } +end + +---@private +---@return Widget +function AbilityUpgradeCard:_image() + return TableWidgets.Cell{ + children = Image{ + imageLight = self.props.image, + imageDark = self.props.imageDark, + size = '62x62px', + } + } +end + +---@private +---@return Widget +function AbilityUpgradeCard:_infos() + return TableWidgets.Cell{ + children = { + self:_renderData(), + HtmlWidgets.Div{ + css = { + width = '350px', + ['white-space'] = 'normal', + ['font-size'] = '90%', + }, + children = self.props.description, + } + } + } +end + +---@private +---@return Widget +function AbilityUpgradeCard:_renderData() + local faction = self.faction or 'default' + + ---@param title Renderable + ---@param input Renderable|Renderable[]? + ---@return Widget? + local makeCell = function(title, input) + if Logic.isEmpty(input) then return end + return HtmlWidgets.Div{ + css = {padding = '2px 8px'}, + children = WidgetUtil.collect( + HtmlWidgets.B{children = title}, + ' ', + input + ) + } + end + + return HtmlWidgets.Div{ + css = { + width = '350px', + ['white-space'] = 'normal', + ['font-size'] = '90%', + display = 'flex', + ['flex-wrap'] = 'wrap', + ['align-content'] = 'stretch', + }, + children = WidgetUtil.collect( + makeCell( + 'Caster:', + Array.map({self.props.caster, self.props.caster2}, function(caster) + return Link{link = caster} + end) + ), + makeCell(MINERALS, self.props.min), + makeCell(Gas[faction], self.props.gas), + makeCell(Supply[faction], self.props.supply), + self.cardType == 'Upgrade' and makeCell(Buildtime[faction], self.props.duration) or nil, + makeCell('Solarite:', self.props.solarite), + makeCell(Image{imageLght = 'EnergyIcon.gif', link = 'Energy'}, self.props.energy), + makeCell(Link{link = 'Range'}, self.props.range), + makeCell(Link{link = 'Cooldown'}, self.props.cooldown), + self.cardType == 'Ability' + and makeCell(Link{link = 'Game Speed', children = 'Duration'}, self.props.duration) + or nil, + makeCell('Radius:', self.props['effect-radius']), + makeCell('Damage:', self.props.damage), + makeCell( + Link{link = 'Hotkeys per Race', children = 'Hotkey'}, + self.props.hotkey and Hotkey.hotkey{hotkey = self.props.hotkey} or nil + ), + makeCell(Image{imageLght = 'Minimap research zerg.png', link = ''}, self.props.zerg), + makeCell(Image{imageLght = 'Minimap research protoss.png', link = ''}, self.props.protoss), + makeCell( + 'Researched from:', + Link{link = self.props['researched_from'], children = self.props['alt_researched_from']} + ), + makeCell('Requires:', self.props.requires) + ), + } +end + +return AbilityUpgradeCard From 8f3703baad8b6ac87d0bb246a4dcfa6a93bde5f0 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Sun, 29 Mar 2026 18:25:01 +0200 Subject: [PATCH 18/41] some fixes --- lua/wikis/starcraft2/Widget/AbilityUpgradeCard.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lua/wikis/starcraft2/Widget/AbilityUpgradeCard.lua b/lua/wikis/starcraft2/Widget/AbilityUpgradeCard.lua index 58dfb90e96c..2993cacab19 100644 --- a/lua/wikis/starcraft2/Widget/AbilityUpgradeCard.lua +++ b/lua/wikis/starcraft2/Widget/AbilityUpgradeCard.lua @@ -40,7 +40,7 @@ function AbilityUpgradeCard:render() end return TableWidgets.Table{ - sortable = true, + css = {['margin-bottom'] = '0.5rem'}, columns = { {align = 'center', width = '75px'}, {align = 'left', width = '375px'}, @@ -110,7 +110,7 @@ function AbilityUpgradeCard:_renderData() local makeCell = function(title, input) if Logic.isEmpty(input) then return end return HtmlWidgets.Div{ - css = {padding = '2px 8px'}, + css = {padding = '0.125rem 0.5rem'}, children = WidgetUtil.collect( HtmlWidgets.B{children = title}, ' ', @@ -156,7 +156,9 @@ function AbilityUpgradeCard:_renderData() makeCell(Image{imageLght = 'Minimap research protoss.png', link = ''}, self.props.protoss), makeCell( 'Researched from:', - Link{link = self.props['researched_from'], children = self.props['alt_researched_from']} + self.props['researched_from'] + and Link{link = self.props['researched_from'], children = self.props['alt_researched_from']} + or nil ), makeCell('Requires:', self.props.requires) ), From 50fc4bd09ab1cfe3aed1d306f05479af39766b5c Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 31 Mar 2026 08:35:49 +0200 Subject: [PATCH 19/41] moverino --- lua/wikis/starcraft2/StreamList.lua | 107 --------------------- lua/wikis/starcraft2/Widget/StreamList.lua | 106 ++++++++++++++++++++ 2 files changed, 106 insertions(+), 107 deletions(-) delete mode 100644 lua/wikis/starcraft2/StreamList.lua create mode 100644 lua/wikis/starcraft2/Widget/StreamList.lua diff --git a/lua/wikis/starcraft2/StreamList.lua b/lua/wikis/starcraft2/StreamList.lua deleted file mode 100644 index ca10db2a873..00000000000 --- a/lua/wikis/starcraft2/StreamList.lua +++ /dev/null @@ -1,107 +0,0 @@ -local Lua = require('Module:Lua') - -local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') -local Box = Lua.import('Module:Box') -local Flags = Lua.import('Module:Flags') -local Logic = Lua.import('Module:Logic') -local StreamLinks = Lua.import('Module:Links/Stream') -local Table = Lua.import('Module:Table') - -local HtmlWidgets = Lua.import('Module:Widget/Html/All') -local Link = Lua.import('Module:Widget/Basic/Link') -local UnorderedList = Lua.import('Module:Widget/List/Unordered') -local WidgetUtil = Lua.import('Module:Widget/Util') - -local StreamList = {} - -local DEFAULT_DEFAULT_PROVIDER = 'twitch' -local DEFAULT_DEFAULT_FLAG = 'usuk' -local COLUMN_BREAK = 5 - -local PROVIDERS = StreamLinks.countdownPlatformNames - ----@param args table ----@return Widget[] -function StreamList.run(args) - local defaultProvider = Logic.nilIfEmpty(args.defaultprovider) or DEFAULT_DEFAULT_PROVIDER - local defaultFlag = Logic.nilIfEmpty(args.defaultflag) or DEFAULT_DEFAULT_FLAG - - ---@param streamIndex integer - ---@return Widget? - local makeLink = function(streamIndex) - local stream = args['s' .. streamIndex] or args['link' .. streamIndex] - if Logic.isEmpty(stream) then - return - end - if Logic.isNotEmpty(args['link' .. streamIndex]) then - return Link{ - link = args['link' .. streamIndex], - children = args['display' .. streamIndex] or stream, - linktype = 'external', - } - end - - local provider = string.lower(args['provider' .. streamIndex] or defaultProvider) - assert(Table.includes(PROVIDERS, provider), 'Invalid Provider: ' .. provider) - if provider == 'afreeca' then - provider = 'afreecatv' - end - - return Link{ - link = 'Special:Stream/' .. provider .. '/' .. stream, - children = args['display' .. streamIndex] or stream, - } - end - - ---@type {link: Widget, flag: string}[] - local streams = Array.mapIndexes(function(streamIndex) - local link = makeLink(streamIndex) - if not link then - return - end - return { - link = makeLink(streamIndex), - flag = args['flag' .. streamIndex] or defaultFlag, - } - end) - - return StreamList._display(streams, tonumber(args.columnbreak) or COLUMN_BREAK) -end - ----@param streams {link: Widget, flag: string}[] ----@param columnBreak integer ----@return Widget|(Widget|string)[] -function StreamList._display(streams, columnBreak) - ---@type {link: Widget, flag: string}[][] - local segments = {} - local currentIndex = 0 - Array.forEach(streams, function(item, index) - if index % columnBreak == 1 then - currentIndex = currentIndex + 1 - segments[currentIndex] = {} - end - table.insert(segments[currentIndex], item) - end) - - ---@type (Widget|string)[] - local parts = Array.map(segments, function(group) - return UnorderedList{children = Array.map(group, function(data) - return { - Flags.Icon{flag = data.flag, shouldLink = false}, - ' ', - data.link, - } - end)} - end) - - return HtmlWidgets.Fragment{ - children = WidgetUtil.collect( - Box._template_box_start{padding = '2em'}, - Array.interleave(parts, Box.brk{padding = '2em'}), - Box.finish() - ) - } -end - -return Class.export(StreamList, {exports = {'run'}}) diff --git a/lua/wikis/starcraft2/Widget/StreamList.lua b/lua/wikis/starcraft2/Widget/StreamList.lua new file mode 100644 index 00000000000..11eb6465c49 --- /dev/null +++ b/lua/wikis/starcraft2/Widget/StreamList.lua @@ -0,0 +1,106 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Class = Lua.import('Module:Class') +local Box = Lua.import('Module:Box') +local Flags = Lua.import('Module:Flags') +local Logic = Lua.import('Module:Logic') +local StreamLinks = Lua.import('Module:Links/Stream') +local Table = Lua.import('Module:Table') + +local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Link = Lua.import('Module:Widget/Basic/Link') +local UnorderedList = Lua.import('Module:Widget/List/Unordered') +local Widget = Lua.import('Module:Widget') +local WidgetUtil = Lua.import('Module:Widget/Util') + +---@class StreamList: Widget +---@operator call(table): StreamList +local StreamList = Class.new(Widget) +StreamList.defaultProps = { + defaultprovider = 'twitch', + defaultflag = 'usuk', + columnbreak = 5, +} + +---@return Widget +function StreamList:render() + ---@type {link: Widget, flag: string}[] + local streams = self:_parse() + + local columnBreak = assert(tonumber(self.props.columnbreak), 'Invalid "|columnbreak=" input') + + ---@type {link: Widget, flag: string}[][] + local segments = {} + local currentIndex = 0 + Array.forEach(streams, function(item, index) + if index % columnBreak == 1 then + currentIndex = currentIndex + 1 + segments[currentIndex] = {} + end + table.insert(segments[currentIndex], item) + end) + + ---@type (Widget|string)[] + local parts = Array.map(segments, function(group) + return UnorderedList{ + children = Array.map(group, function(data) + return { + Flags.Icon{flag = data.flag, shouldLink = false}, + ' ', + data.link, + } + end) + } + end) + + return WidgetUtil.collect( + Box._template_box_start{padding = '2em'}, + Array.interleave(parts, Box.brk{padding = '2em'}), + Box.finish() + ) +end + +---@private +---@return {link: Widget, flag: string}[] +function StreamList:_parse() + ---@param streamIndex integer + ---@return Widget? + local makeLink = function(streamIndex) + local stream = self.props['s' .. streamIndex] or self.props['link' .. streamIndex] + if Logic.isEmpty(stream) then + return + end + if Logic.isNotEmpty(self.props['link' .. streamIndex]) then + return Link{ + link = self.props['link' .. streamIndex], + children = self.props['display' .. streamIndex] or stream, + linktype = 'external', + } + end + + local provider = string.lower(self.props['provider' .. streamIndex] or self.props.defaultprovider) + assert(Table.includes(StreamLinks.countdownPlatformNames, provider), 'Invalid Provider: ' .. provider) + if provider == 'afreeca' then + provider = 'afreecatv' + end + + return Link{ + link = 'Special:Stream/' .. provider .. '/' .. stream, + children = self.props['display' .. streamIndex] or stream, + } + end + + return Array.mapIndexes(function(streamIndex) + local link = makeLink(streamIndex) + if not link then + return + end + return { + link = makeLink(streamIndex), + flag = self.props['flag' .. streamIndex] or self.props.defaultflag, + } + end) +end + +return StreamList From dd0b5c69c4f512f4aade78e443c3b7c84eb1e3b3 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 31 Mar 2026 08:38:17 +0200 Subject: [PATCH 20/41] some private annos --- lua/wikis/starcraft2/MainPageSeasonEvents.lua | 3 +++ lua/wikis/starcraft2/PlayerAchievementsTableWrapper.lua | 2 ++ lua/wikis/starcraft2/Widget/TranslationList.lua | 1 + 3 files changed, 6 insertions(+) diff --git a/lua/wikis/starcraft2/MainPageSeasonEvents.lua b/lua/wikis/starcraft2/MainPageSeasonEvents.lua index aae8b9540c2..107328564a4 100644 --- a/lua/wikis/starcraft2/MainPageSeasonEvents.lua +++ b/lua/wikis/starcraft2/MainPageSeasonEvents.lua @@ -77,6 +77,7 @@ function MainPageSeasonEvents.run(frame) } end +---@private ---@param args table ---@param pageName string ---@return Sc2MainPageSeasonEventsTournamentData @@ -102,6 +103,7 @@ function MainPageSeasonEvents._fecthTournamentData(args, pageName) } end +---@private ---@param tournamentData Sc2MainPageSeasonEventsTournamentData ---@return Widget[] function MainPageSeasonEvents._winnerDisplay(tournamentData) @@ -134,6 +136,7 @@ function MainPageSeasonEvents._winnerDisplay(tournamentData) ) end +---@private ---@param tournamentData Sc2MainPageSeasonEventsTournamentData ---@param args table ---@return Widget[]? diff --git a/lua/wikis/starcraft2/PlayerAchievementsTableWrapper.lua b/lua/wikis/starcraft2/PlayerAchievementsTableWrapper.lua index 5362813e66f..15a62d0f827 100644 --- a/lua/wikis/starcraft2/PlayerAchievementsTableWrapper.lua +++ b/lua/wikis/starcraft2/PlayerAchievementsTableWrapper.lua @@ -11,6 +11,8 @@ local BroadcasterTable = Lua.import('Module:BroadcastTalentTable') local PlayerAchievementsTableWrapper = {} +---@param frame Frame +---@return Widget function PlayerAchievementsTableWrapper.run(frame) local awards = Json.parseIfTable(Variables.varDefault('awardAchievements')) diff --git a/lua/wikis/starcraft2/Widget/TranslationList.lua b/lua/wikis/starcraft2/Widget/TranslationList.lua index 478955888f5..12f7661bf84 100644 --- a/lua/wikis/starcraft2/Widget/TranslationList.lua +++ b/lua/wikis/starcraft2/Widget/TranslationList.lua @@ -37,6 +37,7 @@ function TranslationList:render() ) end +---@private ---@return {flag: string, value: string, postFix: string?}[][] function TranslationList:_parse() local data = Array.map(Array.extractKeys(self.props), function(key) From 5e77632c193136c4940a3b1d955d0c42d40a3cd1 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 31 Mar 2026 09:24:23 +0200 Subject: [PATCH 21/41] mirror #7324 --- lua/wikis/commons/Widget/Box.lua | 57 ++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 lua/wikis/commons/Widget/Box.lua diff --git a/lua/wikis/commons/Widget/Box.lua b/lua/wikis/commons/Widget/Box.lua new file mode 100644 index 00000000000..174adee7890 --- /dev/null +++ b/lua/wikis/commons/Widget/Box.lua @@ -0,0 +1,57 @@ +--- +-- @Liquipedia +-- page=Module:Widget/Box +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Class = Lua.import('Module:Class') + +local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Widget = Lua.import('Module:Widget') + +---@class BoxProps +---@field children Renderable[]|Renderable +---@field maxWidth string? +---@field paddingRight string? +---@field paddingLeft string? +---@field paddingBottom string? +---@field width string? +---@field height string? + +---@class Box: Widget +---@operator call(BoxProps): Box +---@field props BoxProps +local Box = Class.new(Widget) + +---@return Widget|Renderable +function Box:render() + local children = self.props.children + if not Array.isArray(children) then + return self.props.children + end + ---@cast children -Renderable + + return HtmlWidgets.Div{ + css = {['max-width'] = self.props.maxWidth}, + children = Array.map(children, function(child) + return HtmlWidgets.Div{ + classes = {'template-box'}, + css = { + ['padding-right'] = self.props.paddingRight, + ['padding-left'] = self.props.paddingLeft, + ['padding-bottom'] = self.props.paddingBottom, + width = self.props.width, + height = self.props.height, + overflow = self.props.height and 'hidden' or nil, + }, + children = child, + } + end) + } +end + +return Box From 064b8f309a97d6dc10128e49e9e9a7c12c13a199 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 31 Mar 2026 09:24:37 +0200 Subject: [PATCH 22/41] use #7324 --- lua/wikis/starcraft2/Widget/StreamList.lua | 12 +++--------- lua/wikis/starcraft2/Widget/TranslationList.lua | 9 ++------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/lua/wikis/starcraft2/Widget/StreamList.lua b/lua/wikis/starcraft2/Widget/StreamList.lua index 11eb6465c49..4561e993872 100644 --- a/lua/wikis/starcraft2/Widget/StreamList.lua +++ b/lua/wikis/starcraft2/Widget/StreamList.lua @@ -2,17 +2,15 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') local Class = Lua.import('Module:Class') -local Box = Lua.import('Module:Box') local Flags = Lua.import('Module:Flags') local Logic = Lua.import('Module:Logic') local StreamLinks = Lua.import('Module:Links/Stream') local Table = Lua.import('Module:Table') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Box = Lua.import('Module:Widget/Box') local Link = Lua.import('Module:Widget/Basic/Link') local UnorderedList = Lua.import('Module:Widget/List/Unordered') local Widget = Lua.import('Module:Widget') -local WidgetUtil = Lua.import('Module:Widget/Util') ---@class StreamList: Widget ---@operator call(table): StreamList @@ -41,7 +39,7 @@ function StreamList:render() table.insert(segments[currentIndex], item) end) - ---@type (Widget|string)[] + ---@type Widget[] local parts = Array.map(segments, function(group) return UnorderedList{ children = Array.map(group, function(data) @@ -54,11 +52,7 @@ function StreamList:render() } end) - return WidgetUtil.collect( - Box._template_box_start{padding = '2em'}, - Array.interleave(parts, Box.brk{padding = '2em'}), - Box.finish() - ) + return Box{children = parts, paddingRight = '2em'} end ---@private diff --git a/lua/wikis/starcraft2/Widget/TranslationList.lua b/lua/wikis/starcraft2/Widget/TranslationList.lua index 12f7661bf84..bb270825ebf 100644 --- a/lua/wikis/starcraft2/Widget/TranslationList.lua +++ b/lua/wikis/starcraft2/Widget/TranslationList.lua @@ -1,15 +1,14 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Box = Lua.import('Module:Box') local Class = Lua.import('Module:Class') local Flags = Lua.import('Module:Flags') local Logic = Lua.import('Module:Logic') local Operator = Lua.import('Module:Operator') +local Box = Lua.import('Module:Widget/Box') local UnorderedList = Lua.import('Module:Widget/List/Unordered') local Widget = Lua.import('Module:Widget') -local WidgetUtil = Lua.import('Module:Widget/Util') local COLUMN_BREAK = 6 @@ -30,11 +29,7 @@ function TranslationList:render() return UnorderedList{children = Array.map(group, displayItem)} end) - return WidgetUtil.collect( - Box._template_box_start{padding = '2em'}, - Array.interleave(parts, Box.brk{padding = '2em'}), - Box.finish() - ) + return Box{children = parts, paddingRight = '2em'} end ---@private From fa902ae2ae4a6037edfa4612ae89e5a89f03f679 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 31 Mar 2026 18:37:02 +0200 Subject: [PATCH 23/41] stats on player and team pages --- .../starcraft2/Widget/EarningsStatsChart.lua | 103 ++++++++++++ lua/wikis/starcraft2/Widget/MedalsTable.lua | 82 ++++++++++ .../Widget/PlayerPageStatistics.lua | 154 ++++++++++++++++++ .../starcraft2/Widget/TeamPageStatistics.lua | 64 ++++++++ 4 files changed, 403 insertions(+) create mode 100644 lua/wikis/starcraft2/Widget/EarningsStatsChart.lua create mode 100644 lua/wikis/starcraft2/Widget/MedalsTable.lua create mode 100644 lua/wikis/starcraft2/Widget/PlayerPageStatistics.lua create mode 100644 lua/wikis/starcraft2/Widget/TeamPageStatistics.lua diff --git a/lua/wikis/starcraft2/Widget/EarningsStatsChart.lua b/lua/wikis/starcraft2/Widget/EarningsStatsChart.lua new file mode 100644 index 00000000000..9125cbc7083 --- /dev/null +++ b/lua/wikis/starcraft2/Widget/EarningsStatsChart.lua @@ -0,0 +1,103 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Class = Lua.import('Module:Class') +local Info = Lua.import('Module:Info', {loadData = true}) +local Logic = Lua.import('Module:Logic') +local Operator = Lua.import('Module:Operator') + +local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Widget = Lua.import('Module:Widget') + +---@class EarningsStatsChart: Widget +---@operator call(table): EarningsStatsChart +---@field props {dataPoints: {legend: string, key: string}[], data: table} +local EarningsStatsChart = Class.new(Widget) + +---@return Widget? +function EarningsStatsChart:render() + local data, years = self:_parse() + if Logic.isEmpty(data) then + return + end + + return HtmlWidgets.Div{ + classes = {'table-responsive'}, + children = { + HtmlWidgets.H3{children = 'Earnings Statistics'}, + mw.ext.Charts.chart{ + grid = { + left = '15%', + right = '12%', + top = '15%', + bottom = '10%' + }, + size = { + height = 400, + width = 1400, + }, + tooltip = { + trigger = 'axis', + }, + legend = Array.map(self.props.dataPoints, Operator.property('legend')), + xAxis = { + axisLabel = {rotate = 0}, + axisTick = {alignWithLabel = true}, + data = years, + name = 'Year', + type = 'category', + }, + yAxis = {name = 'Earnings ($USD)', type = 'value'}, + series = data, + } + }, + } +end + +---@return {data: number[], emphasis: {focus: string}, name: string, stack: string, type: string}[]? +---@return integer[]? +function EarningsStatsChart:_parse() + local dataSets = Array.map(self.props.dataPoints, function(dataPoint) + return self.props.data[dataPoint.key] or {} + end) + + ---@param year integer + ---@return boolean + local hasAnyPositiveValue = function(year) + return Array.any(dataSets, function(dataSet) + return (dataSet[year] or 0) > 0 + end) + end + + local years = Array.filter(Array.range(Info.startYear, tonumber(os.date('%Y'))), function(year) + return hasAnyPositiveValue(year - 1) or hasAnyPositiveValue(year) or hasAnyPositiveValue(year + 1) + end) + + if Logic.isEmpty(years) then + return + end + + if not hasAnyPositiveValue(years[1]) then + table.remove(years, 1) + end + + if not hasAnyPositiveValue(years[#years]) then + table.remove(years) + end + + local chartData = Array.map(self.props.dataPoints, function(dataPoint, index) + return { + data = Array.map(years, function(year) return dataSets[index][year] or 0 end), + emphasis = { + focus = 'series', + }, + name = dataPoint.legend, + stack = 'total', + type = 'bar', + } + end) + + return chartData, years +end + +return EarningsStatsChart diff --git a/lua/wikis/starcraft2/Widget/MedalsTable.lua b/lua/wikis/starcraft2/Widget/MedalsTable.lua new file mode 100644 index 00000000000..94e56c44a7e --- /dev/null +++ b/lua/wikis/starcraft2/Widget/MedalsTable.lua @@ -0,0 +1,82 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Class = Lua.import('Module:Class') +local Medals = Lua.import('Module:Medals') +local Table = Lua.import('Module:Table') +local Tier = require('Module:Tier/Utils') + +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local Widget = Lua.import('Module:Widget') +local WidgetUtil = Lua.import('Module:Widget/Util') + +local DATA_COLUMNS = {'1', '2', '3', '3-4', '4', 'total'} + +---@class MedalsTable: Widget +---@operator call(table): MedalsTable +---@field props {caption: string?, footer: Renderable?, data: table} +local MedalsTable = Class.new(Widget) + +---@return Widget +function MedalsTable:render() + return TableWidgets.Table{ + caption = self.props.caption, + sortable = true, + columns = WidgetUtil.collect( + {align = 'left'}, -- tier + Array.map(DATA_COLUMNS, function() return {align = 'right'} end) + ), + children = { + TableWidgets.TableHeader{ + children = TableWidgets.Row{ + children = WidgetUtil.collect( + TableWidgets.CellHeader{children = 'Tier'}, + Array.map(DATA_COLUMNS, MedalsTable._headerCell) + ) + } + }, + TableWidgets.TableBody{children = self:_rows()} + }, + footer = self.props.footer + } +end + +---@private +---@param dataColumn string +---@return Widget +function MedalsTable._headerCell(dataColumn) + return TableWidgets.CellHeader{ + children = dataColumn == 'total' and 'Total' or Medals.display{medal = dataColumn} + } +end + +---@private +---@return Widget[] +function MedalsTable:_rows() + local totalRowDataSet = Table.extract(self.props.data, 'total') + local rows = {} + + for tier, dataSet in Table.iter.spairs(self.props.data) do + table.insert(rows, self:_row(Tier.display(tonumber(tier), nil, {link = true}), dataSet)) + end + table.insert(rows, self:_row('Total', totalRowDataSet)) + + return rows +end + +---@private +---@param firstCellContent Renderable[]|Renderable +---@param data table +---@return Widget[] +function MedalsTable:_row(firstCellContent, data) + return TableWidgets.Row{ + children = WidgetUtil.collect( + TableWidgets.Cell{children = firstCellContent}, + Array.map(DATA_COLUMNS, function(column) + return TableWidgets.Cell{children = data[tonumber(column) or column]} + end) + ) + } +end + +return MedalsTable diff --git a/lua/wikis/starcraft2/Widget/PlayerPageStatistics.lua b/lua/wikis/starcraft2/Widget/PlayerPageStatistics.lua new file mode 100644 index 00000000000..e586a259e33 --- /dev/null +++ b/lua/wikis/starcraft2/Widget/PlayerPageStatistics.lua @@ -0,0 +1,154 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Class = Lua.import('Module:Class') +local Faction = Lua.import('Module:Faction') +local Json = require('Module:Json') +local Logic = require('Module:Logic') +local Math = require('Module:MathUtil') +local Operator = require('Module:Operator') +local Table = require('Module:Table') +local Variables = require('Module:Variables') + +local EarningsStatsChart = Lua.import('Module:Widget/EarningsStatsChart') +local MedalsTable = Lua.import('Module:Widget/MedalsTable') +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local Widget = Lua.import('Module:Widget') +local WidgetUtil = Lua.import('Module:Widget/Util') + +---@class PlayerPageStatistics: Widget +---@operator call(table): PlayerPageStatistics +local PlayerPageStatistics = Class.new(Widget) + +---@return Widget +function PlayerPageStatistics:render() + return WidgetUtil.collect( + self:_matchupStats(), + self:_earningsChart(), + MedalsTable{ + caption = '1v1 Medal Statistics', + data = Json.parseIfString(Variables.varDefault('medals')), + } + ) +end + +---@return Widget? +function PlayerPageStatistics:_matchupStats() + local data = Json.parseIfString(Variables.varDefault('matchUpStats')) + if Logic.isEmpty(data) then + return + end + + local columns = Array.map(Faction.knownFactions, function(faction) + return data.total[faction] and faction or nil + end) + table.insert(columns, 'total') + + local rows = {} + for row, rowData in Table.iter.spairs(data, function(tbl, key1, key2) + local total1 = (tbl[key1].total.w or 0) + (tbl[key1].total.l or 0) + local total2 = (tbl[key2].total.w or 0) + (tbl[key2].total.l or 0) + return total1 > total2 + end) do + table.insert(rows, self:_matchupStatsRow(row, rowData, columns)) + end + + return TableWidgets.Table{ + caption = 'Matchup Statistics', + columns = WidgetUtil.collect( + {align = 'center'}, -- faction + unpack(Array.map(columns, function() + return { + {align = 'center'}, -- Record + {align = 'center'}, -- Win% + } + end)) + ), + children = { + self:_matchupStatsHeader(columns), + TableWidgets.TableBody{children = rows} + }, + footer = self.props.footer + } +end + +---@param row string +---@param rowData table +---@param columns string[] +---@return unknown +function PlayerPageStatistics:_matchupStatsRow(row, rowData, columns) + return TableWidgets.Row{ + classes = {row ~= 'total' and Faction.bgClass(row) or nil}, + children = WidgetUtil.collect( + TableWidgets.Cell{ + children = row == 'total' and 'Σ' or { + 'as ', + Faction.Icon{faction = row}, + } + }, + unpack(Array.map(columns, function(col) + local data = rowData[col] or {} + local sum = (data.w or 0) + (data.l or 0) + local percent = sum == 0 and '-' or (Math.round((data.w or 0) * 100 / sum, 1) .. ' %') + return { + TableWidgets.Cell{ + children = { + data.w or 0, + ' - ', + data.l or 0 + } + }, + TableWidgets.Cell{children = percent}, + } + end)) + ) + } +end + +---@param columns string[] +---@return Widget +function PlayerPageStatistics:_matchupStatsHeader(columns) + return TableWidgets.TableHeader{ + children = { + TableWidgets.Row{ + children = WidgetUtil.collect( + TableWidgets.CellHeader{children = '', rowspan = 2}, + Array.map(columns, function(col) + local text = col == 'total' and 'Total' or ('vs ' .. Faction.Icon{faction = col}) + return TableWidgets.CellHeader{children = text, colspan = 2} + end) + ) + }, + TableWidgets.Row{ + children = WidgetUtil.collect( + unpack(Array.map(columns, function(col) + return { + TableWidgets.CellHeader{children = 'Record'}, + TableWidgets.CellHeader{children = 'Win%'}, + } + end)) + ) + }, + } + } +end + +---@return Widget +function PlayerPageStatistics:_earningsChart() + local rawData = Json.parseIfString(Variables.varDefault('earningsStats')) or {} + + return EarningsStatsChart{ + data = { + solo = Table.mapValues(rawData, Operator.property('solo')), + team = Table.mapValues(rawData, Operator.property('team')), + other = Table.mapValues(rawData, Operator.property('other')), + }, + dataPoints = { + {key = 'solo', legend = '1v1 Earnings'}, + {key = 'team', legend = 'Team Event Earnings'}, + {key = 'other', legend = 'Other Earnings'}, + }, + } +end + +return PlayerPageStatistics diff --git a/lua/wikis/starcraft2/Widget/TeamPageStatistics.lua b/lua/wikis/starcraft2/Widget/TeamPageStatistics.lua new file mode 100644 index 00000000000..c8322f5e512 --- /dev/null +++ b/lua/wikis/starcraft2/Widget/TeamPageStatistics.lua @@ -0,0 +1,64 @@ +local Lua = require('Module:Lua') + +local Class = Lua.import('Module:Class') +local Json = require('Module:Json') +local Variables = require('Module:Variables') + +local Box = Lua.import('Module:Widget/Box') +local EarningsStatsChart = Lua.import('Module:Widget/EarningsStatsChart') +local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local MedalsTable = Lua.import('Module:Widget/MedalsTable') +local Widget = Lua.import('Module:Widget') +local WidgetUtil = Lua.import('Module:Widget/Util') + +---@class TeamPageStatistics: Widget +---@operator call(table): TeamPageStatistics +local TeamPageStatistics = Class.new(Widget) + +---@return Widget +function TeamPageStatistics:render() + return WidgetUtil.collect( + self:_earningsChart(), + self:_medalTables() + ) +end + +---@return Widget +function TeamPageStatistics:_earningsChart() + return EarningsStatsChart{ + data = { + solo = Json.parseIfString(Variables.varDefault('playerEarnings')) or {}, + team = Json.parseIfString(Variables.varDefault('teamEarnings')) or {}, + }, + dataPoints = { + {key = 'team', legend = 'Team Earnings'}, + {key = 'solo', legend = 'Player (non-Team Event) Earnings while on the Team'}, + }, + } +end + +---@return Widget +function TeamPageStatistics:_medalTables() + local data = Json.parseIfString(Variables.varDefault('medals')) + return Box{ + paddingRight = '2em', + children = WidgetUtil.collect( + MedalsTable{ + caption = 'Team Medal Statistics', + data = data.team, + }, + MedalsTable{ + caption = '1v1 Medal Statistics', + data = data.solo, + footer = HtmlWidgets.Small{ + children = { + HtmlWidgets.B{children = 'Note:'}, + ' This table shows the medals won by players in 1v1 events while on the team.' + } + }, + } + ), + } +end + +return TeamPageStatistics From 6470b2300a7b7b0ed46eded73fad370b8def6a8b Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 07:02:07 +0200 Subject: [PATCH 24/41] Widget/Box --> Widget/Basic/Box --- lua/wikis/commons/Widget/{ => Basic}/Box.lua | 2 +- lua/wikis/starcraft2/Widget/StreamList.lua | 2 +- lua/wikis/starcraft2/Widget/TeamPageStatistics.lua | 2 +- lua/wikis/starcraft2/Widget/TranslationList.lua | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename lua/wikis/commons/Widget/{ => Basic}/Box.lua (97%) diff --git a/lua/wikis/commons/Widget/Box.lua b/lua/wikis/commons/Widget/Basic/Box.lua similarity index 97% rename from lua/wikis/commons/Widget/Box.lua rename to lua/wikis/commons/Widget/Basic/Box.lua index 174adee7890..494844c6543 100644 --- a/lua/wikis/commons/Widget/Box.lua +++ b/lua/wikis/commons/Widget/Basic/Box.lua @@ -1,6 +1,6 @@ --- -- @Liquipedia --- page=Module:Widget/Box +-- page=Module:Widget/Basic/Box -- -- Please see https://github.com/Liquipedia/Lua-Modules to contribute -- diff --git a/lua/wikis/starcraft2/Widget/StreamList.lua b/lua/wikis/starcraft2/Widget/StreamList.lua index 4561e993872..e02e601ff61 100644 --- a/lua/wikis/starcraft2/Widget/StreamList.lua +++ b/lua/wikis/starcraft2/Widget/StreamList.lua @@ -7,7 +7,7 @@ local Logic = Lua.import('Module:Logic') local StreamLinks = Lua.import('Module:Links/Stream') local Table = Lua.import('Module:Table') -local Box = Lua.import('Module:Widget/Box') +local Box = Lua.import('Module:Widget/Basic/Box') local Link = Lua.import('Module:Widget/Basic/Link') local UnorderedList = Lua.import('Module:Widget/List/Unordered') local Widget = Lua.import('Module:Widget') diff --git a/lua/wikis/starcraft2/Widget/TeamPageStatistics.lua b/lua/wikis/starcraft2/Widget/TeamPageStatistics.lua index c8322f5e512..198e5e0c8fe 100644 --- a/lua/wikis/starcraft2/Widget/TeamPageStatistics.lua +++ b/lua/wikis/starcraft2/Widget/TeamPageStatistics.lua @@ -4,7 +4,7 @@ local Class = Lua.import('Module:Class') local Json = require('Module:Json') local Variables = require('Module:Variables') -local Box = Lua.import('Module:Widget/Box') +local Box = Lua.import('Module:Widget/Basic/Box') local EarningsStatsChart = Lua.import('Module:Widget/EarningsStatsChart') local HtmlWidgets = Lua.import('Module:Widget/Html/All') local MedalsTable = Lua.import('Module:Widget/MedalsTable') diff --git a/lua/wikis/starcraft2/Widget/TranslationList.lua b/lua/wikis/starcraft2/Widget/TranslationList.lua index bb270825ebf..ace7e788bdc 100644 --- a/lua/wikis/starcraft2/Widget/TranslationList.lua +++ b/lua/wikis/starcraft2/Widget/TranslationList.lua @@ -6,7 +6,7 @@ local Flags = Lua.import('Module:Flags') local Logic = Lua.import('Module:Logic') local Operator = Lua.import('Module:Operator') -local Box = Lua.import('Module:Widget/Box') +local Box = Lua.import('Module:Widget/Basic/Box') local UnorderedList = Lua.import('Module:Widget/List/Unordered') local Widget = Lua.import('Module:Widget') From e856799dd8071b1584d8fb54201edaad3c08925c Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 08:28:35 +0200 Subject: [PATCH 25/41] GslMaps --- lua/wikis/starcraft2/GslMaps.lua | 109 +++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 lua/wikis/starcraft2/GslMaps.lua diff --git a/lua/wikis/starcraft2/GslMaps.lua b/lua/wikis/starcraft2/GslMaps.lua new file mode 100644 index 00000000000..b39512d5367 --- /dev/null +++ b/lua/wikis/starcraft2/GslMaps.lua @@ -0,0 +1,109 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local FnUtil = Lua.import('Module:FnUtil') +local Operator = Lua.import('Module:Operator') +local Json = Lua.import('Module:Json') + +local Condition = Lua.import('Module:Condition') +local ConditionTree = Condition.Tree +local ConditionNode = Condition.Node +local Comparator = Condition.Comparator +local BooleanOperator = Condition.BooleanOperator +local ColumnName = Condition.ColumnName + +local Link = Lua.import('Module:Widget/Basic/Link') +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local WidgetUtil = Lua.import('Module:Widget/Util') + +local GslMaps = {} + +---@return Widget +function GslMaps.run() + local seasons, maps = GslMaps._fetch() + + return TableWidgets.Table{ + sortable = true, + children = { + TableWidgets.TableHeader{children = GslMaps._header(seasons)}, + TableWidgets.TableBody{children = Array.map(maps, FnUtil.curry(GslMaps._row, #seasons))}, + }, + } +end + +---@private +---@return Widget[] +---@return {display: Widget, seasons: table, sortKey: string}[] +function GslMaps._fetch() + local conditions = ConditionTree(BooleanOperator.all):add{ + ConditionNode(ColumnName('seriespage'), Comparator.eq, 'Global_StarCraft_II_League'), + ConditionNode(ColumnName('sortdate'), Comparator.gt, '2016-01-01'), + ConditionNode(ColumnName('liquipediatiertype'), Comparator.neq, 'Qualifier'), + } + + local queryData = mw.ext.LiquipediaDB.lpdb('tournament', { + conditions = tostring(conditions), + query = 'tickername, maps, pagename', + limit = 5000, + order = 'sortdate asc', + }) + + ---@type table}> + local maps = {} + local seasons = Array.map(queryData, function(tournament, tournamentIndex) + Array.forEach(Json.parseIfString(tournament.maps) or {}, function(mapInfo) + maps[mapInfo.link] = maps[mapInfo.link] or { + display = Link{link = mapInfo.link, children = mapInfo.displayname}, + seasons = {}, + sortKey = mapInfo.link:lower(), + } + maps[mapInfo.link].seasons[tournamentIndex] = true + end) + local displayName = tournament.tickername + :gsub('AfreecaTV GSL Super Tournament', 'ST') + :gsub('GSL Season', 'S') + :gsub(': Code S', '') + :gsub('GSL vs the World', 'GSLvW') + return Link{link = tournament.pagename, children = displayName} + end) + + local mapsArray = Array.extractValues(maps) + Array.sortInPlaceBy(mapsArray, Operator.property('sortKey')) + + return seasons, mapsArray +end + +---@private +---@param seasons Widget[] +---@return Widget +function GslMaps._header(seasons) + return TableWidgets.Row{ + children = WidgetUtil.collect( + TableWidgets.CellHeader{children = 'Map'}, + Array.map(seasons, function(season) + return TableWidgets.CellHeader{children = season} + end) + ) + } +end + +---@private +---@param numberOfSeasons integer +---@param mapData {display: Widget, seasons: table} +---@return Widget +function GslMaps._row(numberOfSeasons, mapData) + return TableWidgets.Row{ + children = WidgetUtil.collect( + TableWidgets.Cell{children = mapData.display}, + Array.map(Array.range(1, numberOfSeasons), function(seasonIndex) + return TableWidgets.Cell{ + classes = {mapData.seasons[seasonIndex] and 'bg-up' or nil}, + attributes = {['data-sort-value'] = mapData.seasons[seasonIndex] and -1 or 0}, + children = '' + } + end) + ) + } +end + +return GslMaps From f50490e51941d44041d75388101ca3ece6c4bdf6 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 13:51:26 +0200 Subject: [PATCH 26/41] #7333 --- lua/wikis/commons/Widget/Image/Icon/Image.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/wikis/commons/Widget/Image/Icon/Image.lua b/lua/wikis/commons/Widget/Image/Icon/Image.lua index 306dbe87201..36e4e531612 100644 --- a/lua/wikis/commons/Widget/Image/Icon/Image.lua +++ b/lua/wikis/commons/Widget/Image/Icon/Image.lua @@ -20,6 +20,7 @@ local WidgetIcon = Lua.import('Module:Widget/Image/Icon') ---@field caption string? ---@field alt string? ---@field alignment string? +---@field location string? ---@class IconImageWidget: IconWidget ---@operator call(IconImageWidgetParameters): IconImageWidget @@ -41,6 +42,7 @@ function Icon:render() caption = self.props.caption, alt = self.props.alt, alignment = self.props.alignment, + location = self.props.location, } ) end From ad5fb522dca28e366443deef0068e6d7c2d1f937 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 13:51:53 +0200 Subject: [PATCH 27/41] CurrentLadderMaps --- lua/wikis/starcraft2/CurrentLadderMaps.lua | 173 +++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 lua/wikis/starcraft2/CurrentLadderMaps.lua diff --git a/lua/wikis/starcraft2/CurrentLadderMaps.lua b/lua/wikis/starcraft2/CurrentLadderMaps.lua new file mode 100644 index 00000000000..95f1681d5e0 --- /dev/null +++ b/lua/wikis/starcraft2/CurrentLadderMaps.lua @@ -0,0 +1,173 @@ +local Lua = require('Module:Lua') + +local Arguments = Lua.import('Module:Arguments') +local Array = Lua.import('Module:Array') +local FnUtil = Lua.import('Module:FnUtil') +local Page = Lua.import('Module:Page') + +local Condition = Lua.import('Module:Condition') +local ConditionTree = Condition.Tree +local ConditionNode = Condition.Node +local Comparator = Condition.Comparator +local BooleanOperator = Condition.BooleanOperator +local ColumnName = Condition.ColumnName + +local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Image = Lua.import('Module:Widget/Image/Icon/Image') +local Link = Lua.import('Module:Widget/Basic/Link') +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local UnorderedList = Lua.import('Module:Widget/List/Unordered') +local WidgetUtil = Lua.import('Module:Widget/Util') + +local CurrentLadderMaps = {} + +---@param frame Frame +---@return Widget +function CurrentLadderMaps.run(frame) + local args = Arguments.getArgs(frame) + + ---@param prefix string + ---@param index integer + ---@return {}? + local readInput = function(prefix, index) + local input = args[prefix .. '_' .. index] + if not input then return end + local mapData = CurrentLadderMaps._fetchSingle(input) + local extradata = mapData.extradata or {} + return { + text = args[prefix .. '_' .. index .. 'text'], + spawns = extradata.spawns, + pageName = mapData.pagename or Page.pageifyLink(input), + displayName = mapData.name or input, + image = mapData.image, + creator = extradata.creator1dn or extradata.creator1 or extradata.creator or 'unknown', + } + end + + local data = { + {mode = '1v1', maps = Array.mapIndexes(FnUtil.curry(readInput, '1v1'))}, + {mode = '2v2', maps = Array.mapIndexes(FnUtil.curry(readInput, '2v2'))}, + {mode = '3v3', maps = Array.mapIndexes(FnUtil.curry(readInput, '3v3'))}, + {mode = '4v4', maps = Array.mapIndexes(FnUtil.curry(readInput, '4v4'))}, + } + + return HtmlWidgets.Fragment{ + children = { + CurrentLadderMaps._mapsOverview(data), + HtmlWidgets.Br{}, + CurrentLadderMaps._mapsDetails(data), + } + } +end + +---@private +---@param map string +---@return datapoint +function CurrentLadderMaps._fetchSingle(map) + local conditions = ConditionTree(BooleanOperator.all):add{ + ConditionNode(ColumnName('type'), Comparator.eq, 'map'), + ConditionNode(ColumnName('pagename'), Comparator.eq, Page.pageifyLink(map)), + } + + return mw.ext.LiquipediaDB.lpdb('datapoint', { + conditions = tostring(conditions), + query = 'name, pagename, extradata, image', + limit = 1 + })[1] or {} +end + +---@private +---@param data {mode:string, maps: {pageName: string, displayName: string, text: string?, spawns: string?, image: string?, creator: string}[]}[] +---@return Widget +function CurrentLadderMaps._mapsOverview(data) + return TableWidgets.Table{ + children = { + TableWidgets.TableHeader{ + children = TableWidgets.Row{ + children = Array.map(data, function(item) + return TableWidgets.CellHeader{children = item.mode} + end), + } + }, + TableWidgets.TableBody{ + children = TableWidgets.Row{ + children = Array.map(data, function(item) + return TableWidgets.Cell{ + children = UnorderedList{ + children = Array.map(item.maps, function (map) + return { + Link{link = map.pageName, children = map.displayName}, + map.spawns and (' (' .. map.spawns .. ')') or nil, + } + end) + } + } + end), + } + }, + }, + footer = 'Above maps are listed in the map preferences.' + } +end + +---@private +---@param data {mode:string, maps: {pageName: string, displayName: string, text: string?, spawns: string?, image: string?, creator: string}[]}[] +---@return Widget +function CurrentLadderMaps._mapsDetails(data) + return HtmlWidgets.Div{ + classes = {'row'}, + children = Array.map(data, function(info) + return HtmlWidgets.Div{ + classes = {'col-lg-3', 'col-md-6', 'col-sm-6', 'col-xs-12'}, + children = TableWidgets.Table{ + tableClasses = {'collapsible', 'collapsed'}, + css = {width = '100%'}, + children = { + TableWidgets.TableHeader{ + children = TableWidgets.Row{ + children = TableWidgets.CellHeader{ + children = { + info.mode, + ' Ladder Maps', + }, + } + } + }, + TableWidgets.TableBody{ + children = Array.map(info.maps, CurrentLadderMaps._mapDetails), + }, + }, + } + } + end) + } +end + +---@private +---@param map {pageName: string, displayName: string, text: string?, spawns: string?, image: string?, creator: string} +---@return Widget +function CurrentLadderMaps._mapDetails(map) + return TableWidgets.Row{ + children = TableWidgets.Cell{ + children = WidgetUtil.collect( + HtmlWidgets.B{children = Link{link = map.pageName, children = map.displayName}}, + map.spawns and (' (' .. map.spawns .. ')') or nil, + map.image and Image{ + imageLight = map.image, + size = '150px', + caption = 'Created by: ' .. map.creator, + alignment = 'thumb', + location = 'right', + alt = map.displayName, + } or nil, + HtmlWidgets.Br{}, + HtmlWidgets.I{ + css = {['white-space'] = 'normal'}, + children = map.text, + } + ), + }, + } +end + +return CurrentLadderMaps From d31af7977fcee6c49a9a994834b0ff3e839b2a20 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 13:59:14 +0200 Subject: [PATCH 28/41] line breaks --- lua/wikis/starcraft2/CurrentLadderMaps.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lua/wikis/starcraft2/CurrentLadderMaps.lua b/lua/wikis/starcraft2/CurrentLadderMaps.lua index 95f1681d5e0..467e411dce5 100644 --- a/lua/wikis/starcraft2/CurrentLadderMaps.lua +++ b/lua/wikis/starcraft2/CurrentLadderMaps.lua @@ -77,7 +77,8 @@ function CurrentLadderMaps._fetchSingle(map) end ---@private ----@param data {mode:string, maps: {pageName: string, displayName: string, text: string?, spawns: string?, image: string?, creator: string}[]}[] +---@param data {mode:string, maps: {pageName: string, displayName: string, text: string?, +--- spawns: string?, image: string?, creator: string}[]}[] ---@return Widget function CurrentLadderMaps._mapsOverview(data) return TableWidgets.Table{ @@ -111,7 +112,8 @@ function CurrentLadderMaps._mapsOverview(data) end ---@private ----@param data {mode:string, maps: {pageName: string, displayName: string, text: string?, spawns: string?, image: string?, creator: string}[]}[] +---@param data {mode:string, maps: {pageName: string, displayName: string, text: string?, +--- spawns: string?, image: string?, creator: string}[]}[] ---@return Widget function CurrentLadderMaps._mapsDetails(data) return HtmlWidgets.Div{ From aae05430fcfd3cfc973dda070056adc16ce6af22 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 16:10:11 +0200 Subject: [PATCH 29/41] LadderMapsTimeLine --- lua/wikis/starcraft2/LadderMapsTimeLine.lua | 150 ++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 lua/wikis/starcraft2/LadderMapsTimeLine.lua diff --git a/lua/wikis/starcraft2/LadderMapsTimeLine.lua b/lua/wikis/starcraft2/LadderMapsTimeLine.lua new file mode 100644 index 00000000000..5213e87d7c3 --- /dev/null +++ b/lua/wikis/starcraft2/LadderMapsTimeLine.lua @@ -0,0 +1,150 @@ +local Lua = require('Module:Lua') + +local Arguments = Lua.import('Module:Arguments') +local Array = Lua.import('Module:Array') +local FnUtil = Lua.import('Module:FnUtil') +local Game = Lua.import('Module:Game') +local Operator = Lua.import('Module:Operator') +local Seasons = Lua.import('Module:Map Seasons', {loadData = true}) +local Table = Lua.import('Module:Table') + +local Condition = Lua.import('Module:Condition') +local ConditionTree = Condition.Tree +local ConditionNode = Condition.Node +local Comparator = Condition.Comparator +local BooleanOperator = Condition.BooleanOperator +local ColumnName = Condition.ColumnName + +local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Link = Lua.import('Module:Widget/Basic/Link') +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local WidgetUtil = Lua.import('Module:Widget/Util') + +local DEFAULT_DATE = '2999-01-01' + +local LadderMapsTimeLine = {} + +---@param frame Frame +---@return Widget +function LadderMapsTimeLine.run(frame) + local args = Arguments.getArgs(frame) + local mode = (args.mode or '1v1'):lower() + local game = Game.toIdentifier{game = args.game, useDefault = false} + assert(game, 'Missing or invalid game input') + + local seasons, maps = LadderMapsTimeLine._fetch(mode, game) + + return TableWidgets.Table{ + sortable = true, + children = { + TableWidgets.TableHeader{children = LadderMapsTimeLine._header(seasons)}, + TableWidgets.TableBody{children = Array.map(maps, FnUtil.curry(LadderMapsTimeLine._row, Table.size(seasons)))}, + }, + } +end + +---@private +---@param mode string +---@param game string? +---@return {name: string, date_value: string}[] +---@return {display: Widget, seasons: table, sortKey: string, introduction: string[], removal: string[]}[] +function LadderMapsTimeLine._fetch(mode, game) + local conditions = ConditionTree(BooleanOperator.all):add{ + ConditionNode(ColumnName('type'), Comparator.eq, 'maphistory'), + ConditionNode(ColumnName('information'), Comparator.eq, mode .. (game ~= 'lotv' and game or '')), + } + + local queryData = mw.ext.LiquipediaDB.lpdb('datapoint', { + conditions = tostring(conditions), + query = 'name, pagename, extradata', + order = 'date asc', + limit = 5000, + }) + + local seasons = Seasons[game] + + ---@type table, introduction: string[], + ---removal: string[], sortKey: string}> + local maps = {} + Array.map(queryData, function(mapHistory) + if not maps[mapHistory.pagename] then + maps[mapHistory.pagename] = { + display = Link{link = mapHistory.pagename, children = mapHistory.name}, + seasons = {}, + introduction = {}, + removal = {}, + sortKey = mapHistory.pagename:lower(), + } + end + local startDate = mapHistory.extradata.sdate + local endDate = mapHistory.extradata.edate + Array.forEach(seasons, function(season, seasonIndex) + local seasonStart = season.date_value + local seasonEnd = (seasons[seasonIndex + 1] or {date_value = DEFAULT_DATE}).date_value + + -- started before season and ended after season + if startDate < seasonStart and endDate > seasonEnd then + maps[mapHistory.pagename].seasons[seasonIndex] = true + -- started within the season + elseif startDate > seasonStart and startDate < seasonEnd then + maps[mapHistory.pagename].seasons[seasonIndex] = true + -- end within the season + elseif endDate < seasonStart and endDate < seasonEnd then + maps[mapHistory.pagename].seasons[seasonIndex] = true + end + end) + table.insert(maps[mapHistory.pagename].introduction, startDate) + table.insert(maps[mapHistory.pagename].removal, endDate) + end) + + local mapsArray = Array.extractValues(maps) + Array.sortInPlaceBy(mapsArray, Operator.property('sortKey')) + + return seasons, mapsArray +end + +---@private +---@param seasons {name: string, date_value: string}[] +---@return Widget +function LadderMapsTimeLine._header(seasons) + return TableWidgets.Row{ + children = WidgetUtil.collect( + TableWidgets.CellHeader{children = 'Map'}, + TableWidgets.CellHeader{children = 'Introduction'}, + TableWidgets.CellHeader{children = 'Removal'}, + Array.map(seasons, function(season) + return TableWidgets.CellHeader{children = season.name} + end) + ) + } +end + +---@private +---@param numberOfSeasons integer +---@param mapData {display: Widget, seasons: table, introduction: string[], removal: string[]} +---@return Widget +function LadderMapsTimeLine._row(numberOfSeasons, mapData) + ---@param dates string[] + ---@return Renderable[] + local displayDates = function(dates) + dates = Array.map(dates, function(date) return date == DEFAULT_DATE and 'TBD' or date end) + return Array.interleave(dates, HtmlWidgets.Br{}) + end + + return TableWidgets.Row{ + children = WidgetUtil.collect( + TableWidgets.Cell{children = mapData.display}, + TableWidgets.Cell{children = displayDates(mapData.introduction)}, + TableWidgets.Cell{children = displayDates(mapData.removal)}, + Array.map(Array.range(1, numberOfSeasons), function(seasonIndex) + return TableWidgets.Cell{ + classes = {mapData.seasons[seasonIndex] and 'bg-up' or nil}, + attributes = {['data-sort-value'] = mapData.seasons[seasonIndex] and -1 or 0}, + children = '' + } + end) + ) + } +end + +return LadderMapsTimeLine From e7c4b544e37ecd1fdc3a30bb2c4258e39710a1ce Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 16:11:15 +0200 Subject: [PATCH 30/41] shut up linter --- lua/wikis/starcraft2/LadderMapsTimeLine.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/wikis/starcraft2/LadderMapsTimeLine.lua b/lua/wikis/starcraft2/LadderMapsTimeLine.lua index 5213e87d7c3..b0edd49b2ae 100644 --- a/lua/wikis/starcraft2/LadderMapsTimeLine.lua +++ b/lua/wikis/starcraft2/LadderMapsTimeLine.lua @@ -47,7 +47,7 @@ end ---@param mode string ---@param game string? ---@return {name: string, date_value: string}[] ----@return {display: Widget, seasons: table, sortKey: string, introduction: string[], removal: string[]}[] +---@return {display: Widget, seasons: table, introduction: string[], removal: string[]}[] function LadderMapsTimeLine._fetch(mode, game) local conditions = ConditionTree(BooleanOperator.all):add{ ConditionNode(ColumnName('type'), Comparator.eq, 'maphistory'), From ed5e00e08be8990c29a79667c71fccb6e632f56b Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 16:21:09 +0200 Subject: [PATCH 31/41] Disambiguation --- lua/wikis/starcraft2/Disambiguation.lua | 71 +++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 lua/wikis/starcraft2/Disambiguation.lua diff --git a/lua/wikis/starcraft2/Disambiguation.lua b/lua/wikis/starcraft2/Disambiguation.lua new file mode 100644 index 00000000000..88c7f0a3931 --- /dev/null +++ b/lua/wikis/starcraft2/Disambiguation.lua @@ -0,0 +1,71 @@ +local Lua = require('Module:Lua') + +local AnOrA = Lua.import('Module:A or an') +local Arguments = Lua.import('Module:Arguments') +local DateExt = Lua.import('Module:Date/Ext') +local Faction = Lua.import('Module:Faction') +local Flags = Lua.import('Module:Flags') +local Logic = Lua.import('Module:Logic') +local String = Lua.import('Module:StringUtils') +local Team = Lua.import('Module:Team') + +local Disambiguation = {} + +function Disambiguation.player(frame) + local args = Arguments.getArgs(frame) + local player = mw.ext.TeamLiquidIntegration.resolve_redirect(args.player or args[1]):gsub(' ', '_') + local nameInput = args.name or args.id or mw.title.getCurrentTitle().text + + local data = mw.ext.LiquipediaDB.lpdb('player', { + conditions = '[[pagename::' .. player .. ']]', + limit = 1, + })[1] + + if not data then + return '' + end + + local id = args.name or args.id or mw.title.getCurrentTitle().text + + local extraData = data.extradata + + local nameArray = mw.text.split(data.name, ' ', true) + local firstName = Logic.emptyOr(args.firstname, extraData.firstname, table.remove(nameArray, 1)) + local lastName = Logic.emptyOr(args.lastname, extraData.lastname, table.concat(nameArray, ' ')) + local nameDisplay = firstName .. ' "[[' .. player .. '|' .. id .. ']]" ' .. lastName + + local localisation = Flags.getLocalisation(data.nationality) + localisation = String.isNotEmpty(localisation) and (localisation .. ' ') or '' + + local isDeceased = data.deathdate ~= DateExt.defaultDate or (data.status or ''):lower() == 'deceased' + + local statusDisplay = (data.status or ''):lower() ~= 'active' and (not isDeceased) and (data.status .. ' ') or '' + + local factionDisplay = Faction.toName(extraData.faction) or '' + + local typeDisplay = Logic.emptyOr(data.type, 'player'):lower() + + local display = '* ' .. nameDisplay .. ', ' .. AnOrA._main{ + statusDisplay .. localisation .. factionDisplay .. typeDisplay, + } + + if String.isNotEmpty(data.teamtemplate) then + display = display .. ' currently ' + if typeDisplay == 'player' then + display = display .. 'playing' + else + display = display .. 'working' + end + display = display .. ' for ' .. Team.team(nil, data.teamtemplate) + end + + display = display .. '.' + + if nameInput ~= data.id then + display = display .. ' Their current ID is ' .. data.id .. '.' + end + + return display +end + +return Disambiguation From c17fb52fd6e40992383ab887bb0371f85b50316b Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 16:21:32 +0200 Subject: [PATCH 32/41] PlayerTabs --- lua/wikis/starcraft2/PlayerTabs.lua | 171 ++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 lua/wikis/starcraft2/PlayerTabs.lua diff --git a/lua/wikis/starcraft2/PlayerTabs.lua b/lua/wikis/starcraft2/PlayerTabs.lua new file mode 100644 index 00000000000..136bec4c40f --- /dev/null +++ b/lua/wikis/starcraft2/PlayerTabs.lua @@ -0,0 +1,171 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Arguments = Lua.import('Module:Arguments') +local DateExt = Lua.import('Module:Date/Ext') +local Logic = Lua.import('Module:Logic') +local Page = Lua.import('Module:Page') +local Table = Lua.import('Module:Table') +local Tabs = Lua.import('Module:Tabs') + +local Link = Lua.import('Module:Widget/Basic/Link') + +local SPECIAL_SUBPAGES = { + 'Broadcasts', + 'Schedule', + 'Rivalries', +} +local NOW = DateExt.toYmdInUtc(DateExt.getCurrentTimestamp() + DateExt.daysToSeconds(1)) +local PAST = DateExt.toYmdInUtc(DateExt.getCurrentTimestamp() - DateExt.daysToSeconds(180)) + +local PlayerTabs = {} + +---@param frame Frame +---@return Widget +function PlayerTabs.run(frame) + local args = Arguments.getArgs(frame) + + local pageName = args.player or args.pageName or mw.title.getCurrentTitle().prefixedText + -- first remove any year range sub sub page stuff + pageName = string.gsub(pageName, '/[%d%-]+$', '') + pageName = string.gsub(pageName, '/%d+%-Present$', '') + + local subpageName = string.gsub(pageName, '.*/([^/]+)$', '%1') + local player = string.gsub(pageName, '(.*)/[^/]-$', '%1') + + PlayerTabs._setDisplayTitle(args, player, subpageName) + + return PlayerTabs._display(player, tonumber(args.currentTab)) +end + +---@private +---@param args table +---@param player string +---@param subpageName string +function PlayerTabs._setDisplayTitle(args, player, subpageName) + ---@param title string + local setDisplayTitle = function(title) + mw.getCurrentFrame():callParserFunction('DISPLAYTITLE', title) + end + + if Logic.isNotEmpty(args.title) then + setDisplayTitle(args.title) + return + end + + if player == subpageName then + return + end + + ---@return string + local queryDisplayName = function() + local data = mw.ext.LiquipediaDB.lpdb('player', { + conditions = '[[pagename::' .. player:gsub(' ', '_') .. ']]', + query = 'id', + }) + return (data[1] or {}).id or player + end + + local title = args.displayName or queryDisplayName() + + if Table.includes(SPECIAL_SUBPAGES, subpageName) then + title = title .. ': ' .. subpageName + end + + setDisplayTitle(title) +end + +---@private +---@param player string +---@param currentTab integer? +---@return Widget +function PlayerTabs._display(player, currentTab) + ---@param args {form: string, template: string, display: string, queryArgs: table} + ---@return string + local makeQueryLink = function(args) + local prefix = args.template + local queryArgs = Table.map(args.queryArgs, function(key, item) + return prefix .. key, item + end) + return Link{ + linktype = 'external', + children = args.display, + link = tostring(mw.uri.fullUrl( + 'Special:RunQuery/' .. args.form, + mw.uri.buildQueryString(queryArgs) .. '&_run' + )) + } + end + + local tabArgs = { + name1 = 'Overview', + link1 = player, + name2 = makeQueryLink{ + form = 'Player Results', + display = 'Results', + template = 'Player results', + queryArgs = { + ['[player]'] = player, + ['[tier]'] = '1,2,3', + ['[edate]'] = NOW, + ['[limit]'] = '250', + }, + }, + name3 = makeQueryLink{ + form = 'Player Matches', + display = 'Matches', + template = 'Player matches', + queryArgs = { + ['[player]'] = player, + ['[tier]'] = '1,2,3', + ['[edate]'] = NOW, + ['[linkSubPage]'] = 'false', + ['[limit]'] = '250', + }, + }, + name4 = 'Awards', + link4 = player .. '/' .. 'Awards', + } + + local tabCounter = 4 + + -- add special sub pages that some might have + -- only add them if the according sub page actually exists + Array.forEach(SPECIAL_SUBPAGES, function(item) + if not Page.exists(player .. '/' .. item) then return end + tabCounter = tabCounter + 1 + tabArgs['name' .. tabCounter] = item + tabArgs['link' .. tabCounter] = player .. '/' .. item + end) + + -- Head to head query link + tabCounter = tabCounter + 1 + tabArgs['name' .. tabCounter] = makeQueryLink{ + form = 'Match history', + display = 'Head to Head', + template = 'Head_to_head_query', + queryArgs = {['[player]'] = player}, + } + + -- Statistics query link + tabCounter = tabCounter + 1 + tabArgs['name' .. tabCounter] = makeQueryLink{ + form = 'PlayerStats', + display = 'Statistics', + template = 'PlayerStatistics', + queryArgs = { + ['[player]'] = player, + ['[tiers]'] = '1,2,3', + ['[tierTypes]'] = 'Unset,Monthly,Weekly,Biweekly,Showmatch,Daily', + ['[onlySolo]'] = 'no', + ['[sdate]'] = PAST, + ['[edate]'] = NOW, + }, + } + + tabArgs.This = currentTab + + return Tabs.static(tabArgs) +end + +return PlayerTabs From fb6673afb6b6027ac5cde0695f975005c1ac2f3a Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 16:22:10 +0200 Subject: [PATCH 33/41] TransferTabs --- lua/wikis/starcraft2/TransferTabs.lua | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 lua/wikis/starcraft2/TransferTabs.lua diff --git a/lua/wikis/starcraft2/TransferTabs.lua b/lua/wikis/starcraft2/TransferTabs.lua new file mode 100644 index 00000000000..2bc30da7a07 --- /dev/null +++ b/lua/wikis/starcraft2/TransferTabs.lua @@ -0,0 +1,21 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Class = Lua.import('Module:Class') +local Tabs = Lua.import('Module:Tabs') + +local TransferTabs = {} + +function TransferTabs.run(args) + args = args or {} + local currentYear = os.date('%Y') + Array.forEach(Array.range(2010, currentYear), function(year) + local index = year - 2009 + args['name' .. index] = year + args['link' .. index] = 'Player Transfers/' .. year + end) + + return Tabs.static(args) +end + +return Class.export(TransferTabs) From be2472dc414ed2f0df52694f12766e95f9916b56 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 16:46:43 +0200 Subject: [PATCH 34/41] Maps --- lua/wikis/starcraft2/Maps.lua | 238 ++++++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 lua/wikis/starcraft2/Maps.lua diff --git a/lua/wikis/starcraft2/Maps.lua b/lua/wikis/starcraft2/Maps.lua new file mode 100644 index 00000000000..6ca929d2f10 --- /dev/null +++ b/lua/wikis/starcraft2/Maps.lua @@ -0,0 +1,238 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Class = Lua.import('Module:Class') +local Json = Lua.import('Module:Json') +local Logic = Lua.import('Module:Logic') +local Table = Lua.import('Module:Table') +local Variables = Lua.import('Module:Variables') + +---@type table +local MapData = Lua.import('Module:MapData', {loadData = true}) + +local Image = Lua.import('Module:Widget/Image/Icon/Image') +local Link = Lua.import('Module:Widget/Basic/Link') +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local WidgetUtil = Lua.import('Module:Widget/Util') + +---@class SC2MapsMap +---@field link string +---@field displayname string? +---@field author string? +---@field authorLink string? +---@field image string? +---@field imageDark string? + +---@class SC2MapsConfig +---@field note string? +---@field showAuthor boolean +---@field sort boolean +---@field size string +---@field title string? +---@field tournament string? + +local DEFAULT_THUMB_SIZE = 'x120px' + +local Maps = {} + +---@private +---@param key string? +---@return SC2MapsMap? +function Maps._getData(key) + if Logic.isEmpty(key) then + return + end + ---@cast key -nil + + key = key:gsub('%s*LE$', ''):gsub('%s*TE$', ''):gsub('%s*CE$', ''):gsub(' %([mM]ap%)$', ''):gsub('_', ' ') + return MapData[key:lower()] +end + +-- EntryPoint Template:MapThumb +---@param args {[1]: string?, size: string|number?}? +---@return Widget? +function Maps.thumb(args) + args = args or {} + local data = Maps._getData(args[1]) + if not data then + return + end + + return Maps._displayThumb(data, args.size) +end + +---@private +---@param data SC2MapsMap +---@param size string|number? +---@return Widget +function Maps._displayThumb(data, size) + return Image{ + imageLight = data.image, + imageDark = data.imageDark, + link = data.link, + size = size or DEFAULT_THUMB_SIZE, + alt = data.displayname or data.link, + } +end + +-- EntryPoint Template:MapAuthor +---@param args {[1]: string?}? +---@return Widget|string? +function Maps.author(args) + args = args or {} + + local data = Maps._getData(args[1]) + if not data then + return + end + + return Maps._displayAuthor(data) +end + +---@private +---@param data SC2MapsMap +---@return Widget|string? +function Maps._displayAuthor(data) + if not data.authorLink then + return data.author + end + + return Link{link = data.authorLink, children = data.author} +end + +-- EntryPoint Template:Maps +---@param args table? +---@return Widget? +function Maps.run(args) + args = args or {} + + local config = Maps._readConfig(args) + local mapList = Maps._readMaps(args, config) + + if Logic.isEmpty(mapList) then + return + end + + mapList = Array.map(mapList, function(map) + if Logic.isNotEmpty(map.image) and (Logic.isNotEmpty(map.author) or not config.showAuthor) then + return map + end + + local data = Maps._getData(map.link) or {} + return Table.merge(data, map) + end) + + if config.sort then + Array.sortInPlaceBy(mapList, function(map) + return map.link .. (map.displayname or '') + end) + end + + return Maps._display(mapList, config) +end + +---@private +---@param args table +---@return SC2MapsConfig +function Maps._readConfig(args) + return { + note = args.note, + showAuthor = Logic.readBool(args.author), + sort = Logic.readBool(args.sort), + size = args.size, + title = args.title, + tournament = args.tournament, + } +end + +---@private +---@param args table +---@param config SC2MapsConfig +---@return SC2MapsMap[] +function Maps._readMaps(args, config) + ---@type SC2MapsMap[] + local maps = Array.mapIndexes(function(mapIndex) + local prefix = 'map' .. mapIndex + local map = args[prefix] + if Logic.isEmpty(map) then + return + end + + return { + displayname = args[prefix .. 'display'], + link = mw.ext.TeamLiquidIntegration.resolve_redirect(map), + image = args[prefix .. 'image'], + imageDark = args[prefix .. 'imageDark'], + author = args[prefix .. 'author'], + authorLink = args[prefix .. 'authorLink'], + } + end) + + if maps[1] then + return maps + end + + if not config.tournament then + maps = Json.parseIfTable(Variables.varDefault('tournament_maps')) --[[@as SC2MapsMap[] ]] + if maps then + return maps + end + end + + local tournament = config.tournament or mw.title.getCurrentTitle().prefixedText + local data = mw.ext.LiquipediaDB.lpdb('tournament', { + conditions = '[[pagename::' .. tournament:gsub(' ', '_') .. ']]', + query = 'maps' + }) + + if Logic.isEmpty(data[1]) then + return {} + end + + return Json.parseIfTable(data[1].maps) or {} +end + +---@private +---@param mapList SC2MapsMap[] +---@param config SC2MapsConfig +---@return Widget +function Maps._display(mapList, config) + return TableWidgets.Table{ + sortable = false, + caption = config.title, + columns = Array.map(mapList, function() + return {align = 'center'} + end), + children = { + -- name row + TableWidgets.Row{children = Array.map(mapList, function(map) + return TableWidgets.CellHeader{children = Link{link = map.link, children = map.displayname}} + end)}, + TableWidgets.TableBody{children = WidgetUtil.collect( + -- image row + TableWidgets.Row{children = Array.map(mapList, function(map) + return TableWidgets.Cell{ + children = Maps._displayThumb(map, config.size), + } + end)}, + -- author row (if enabled) + config.showAuthor and TableWidgets.Row{children = Array.map(mapList, function(map) + return TableWidgets.Cell{ + children = map.authorLink and { + 'By ', + Maps._displayAuthor(map) + } or nil, + css = { + ['font-size'] = '90%', + ['padding-left'] = '4px', + ['padding-right'] = '4px', + }, + } + end)} or nil + )} + }, + footer = config.note, + } +end + +return Class.export(Maps, {exports = {'thumb', 'author', 'run'}}) From 0ca725304f238bd8af195be069dd69313637c18c Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 16:48:20 +0200 Subject: [PATCH 35/41] PortalTournamentsTabs --- .../starcraft2/PortalTournamentsTabs.lua | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 lua/wikis/starcraft2/PortalTournamentsTabs.lua diff --git a/lua/wikis/starcraft2/PortalTournamentsTabs.lua b/lua/wikis/starcraft2/PortalTournamentsTabs.lua new file mode 100644 index 00000000000..2f8617dede5 --- /dev/null +++ b/lua/wikis/starcraft2/PortalTournamentsTabs.lua @@ -0,0 +1,70 @@ + +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local DateExt = Lua.import('Module:Date/Ext') +local Logic = Lua.import('Module:Logic') +local Tabs = Lua.import('Module:Tabs') +local Tier = Lua.import('Module:Tier/Custom') + +local Link = Lua.import('Module:Widget/Basic/Link') + +local NOW = DateExt.toYmdInUtc(DateExt.getCurrentTimestamp() + DateExt.daysToSeconds(1)) +local PAST = DateExt.toYmdInUtc(DateExt.getCurrentTimestamp() - DateExt.daysToSeconds(183)) + +local PortalTournamentsTabs = {} + +---@return Widget +function PortalTournamentsTabs.run() + local tabArgs = { + name1 = 'Introduction', + link1 = 'Portal:Tournaments', + name2 = 'Recent Results', + link2 = 'Recent Tournament Results' + } + + local tabCounter = 2 + local tiers = {} + for tier in Tier.iterate('tiers') do + table.insert(tiers, tier) + end + + Array.forEach(tiers, function(tier) + if Logic.isEmpty(tier) then + return + end + + tabCounter = tabCounter + 1 + + local isNotMisc = (tonumber(tier) ~= -1) + + tabArgs['name' .. tabCounter] = Link{ + linktype = 'external', + children = Tier.toName(tier), + link = tostring(mw.uri.fullUrl( + 'Special:RunQuery/Portal Tournaments', + mw.uri.buildQueryString{ + ['TournamentsList[tier]'] = tier, + ['TournamentsList[game]'] = 'lotv', + ['TournamentsList[noLis]'] = 'true', + ['TournamentsList[excludeTiertype1]'] = isNotMisc and 'Qualifier' or nil, + ['TournamentsList[excludeTiertype2]'] = isNotMisc and 'Charity' or nil, + ['TournamentsList[enddate]'] = NOW, + ['TournamentsList[startdate]'] = isNotMisc and PAST or nil, + } .. '&_run' + )) + } + end) + + tabCounter = tabCounter + 1 + tabArgs['name' .. tabCounter] = '2v2 - Archon' + tabArgs['link' .. tabCounter] = '2v2 - Archon Tournaments' + + tabCounter = tabCounter + 1 + tabArgs['name' .. tabCounter] = 'Female Only' + tabArgs['link' .. tabCounter] = 'Female Tournaments' + + return Tabs.static(tabArgs) +end + +return PortalTournamentsTabs From 72d5a3eb770d21be592e00d1ee3cabe7d15166a1 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 16:48:42 +0200 Subject: [PATCH 36/41] BirthCategories --- lua/wikis/starcraft2/BirthCategories.lua | 38 ++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 lua/wikis/starcraft2/BirthCategories.lua diff --git a/lua/wikis/starcraft2/BirthCategories.lua b/lua/wikis/starcraft2/BirthCategories.lua new file mode 100644 index 00000000000..0b7eb497abc --- /dev/null +++ b/lua/wikis/starcraft2/BirthCategories.lua @@ -0,0 +1,38 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') + +local Link = Lua.import('Module:Widget/Basic/Link') +local HtmlWidgets = Lua.import('Module:Widget/Html/All') + +local BirthCategories = {} + +---@return Widget +function BirthCategories.get() + local yearString = string.gsub(mw.title.getCurrentTitle().text, ' births', '') + local year = tonumber(yearString) + + local row = HtmlWidgets.Div{ + classes = {'hlist'}, + css = {['margin-left'] = '0'}, + children = HtmlWidgets.Ul{children = Array.map(Array.range((year-4), (year+4)), function(currentYear) + return HtmlWidgets.Li{ + children = Link{link = ':Category:' .. currentYear .. ' births', children = currentYear} + } + end)} + } + + return HtmlWidgets.Fragment{ + children = { + HtmlWidgets.Table{ + classes = {'toccolours'}, + attributes = {align = 'right'}, + children = HtmlWidgets.Tr{children = HtmlWidgets.Td{children = row}} + }, + HtmlWidgets.Br{}, + 'List of people born in ' .. year .. '.' + } + } +end + +return BirthCategories From 008d074ca348c9c04b809b6c62f17f6aac842531 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 16:49:01 +0200 Subject: [PATCH 37/41] Automated Patch --- lua/wikis/starcraft2/Automated Patch.lua | 141 +++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 lua/wikis/starcraft2/Automated Patch.lua diff --git a/lua/wikis/starcraft2/Automated Patch.lua b/lua/wikis/starcraft2/Automated Patch.lua new file mode 100644 index 00000000000..5fde3084973 --- /dev/null +++ b/lua/wikis/starcraft2/Automated Patch.lua @@ -0,0 +1,141 @@ +local Lua = require('Module:Lua') + +local Arguments = Lua.import('Module:Arguments') + +local PatchAuto = {} + +---@type {patch: string, date: string}[] +local PATCH_DATA = { + {patch = '3.0.0', date = '2015-10-06'}, + {patch = '3.0.1', date = '2015-10-15'}, + {patch = '3.0.2', date = '2015-10-19'}, + {patch = '3.0.3', date = '2015-10-27'}, + {patch = '3.0.4', date = '2015-11-10'}, + {patch = '3.0.5', date = '2015-11-12'}, + {patch = '3.1.0', date = '2015-12-15'}, + {patch = '3.1.1', date = '2016-01-12'}, + {patch = '3.1.2', date = '2016-02-02'}, + {patch = '3.1.3', date = '2016-02-23'}, + {patch = '3.1.4', date = '2016-03-03'}, + {patch = '3.2.0', date = '2016-03-29'}, + {patch = '3.2.1', date = '2016-04-05'}, + {patch = '3.2.2', date = '2016-04-05'}, + {patch = '3.3.0', date = '2016-05-17'}, + {patch = '3.3.1', date = '2016-05-26'}, + {patch = '3.3.2', date = '2016-06-14'}, + {patch = '3.4.0', date = '2016-07-12'}, + {patch = '3.5.0', date = '2016-08-02'}, + {patch = '3.5.1', date = '2016-08-04'}, + {patch = '3.5.2', date = '2016-08-11'}, + {patch = '3.5.3', date = '2016-08-25'}, + {patch = '3.6.0', date = '2016-09-13'}, + {patch = '3.7.0', date = '2016-10-18'}, + {patch = '3.7.1', date = '2016-10-25'}, + {patch = '3.8.0', date = '2016-11-22'}, + {patch = '3.9.0', date = '2016-12-13'}, + {patch = '3.9.1', date = '2016-12-21'}, + {patch = '3.10.0', date = '2017-01-24'}, + {patch = '3.10.1', date = '2017-01-31'}, + {patch = '3.11.0', date = '2017-03-07'}, + {patch = '3.12.0', date = '2017-03-28'}, + {patch = '3.13.0', date = '2017-05-02'}, + {patch = '3.14.0', date = '2017-05-23'}, + {patch = '3.15.0', date = '2017-06-20'}, + {patch = '3.15.1', date = '2017-06-22'}, + {patch = '3.16.0', date = '2017-07-18'}, + {patch = '3.16.1', date = '2017-08-07'}, + {patch = '3.17.0', date = '2017-08-30'}, + {patch = '3.17.1', date = '2017-09-07'}, + {patch = '3.18.0', date = '2017-09-19'}, + {patch = '3.19.0', date = '2017-10-10'}, + {patch = '3.19.1', date = '2017-10-12'}, + {patch = '4.0.0', date = '2017-11-14'}, + {patch = '4.0.1', date = '2017-11-15'}, + {patch = '4.0.2', date = '2017-11-21'}, + {patch = '4.1.0', date = '2017-12-05'}, + {patch = '4.1.1', date = '2017-12-07'}, + {patch = '4.1.2', date = '2017-12-19'}, + {patch = '4.1.3', date = '2018-01-09'}, + {patch = '4.1.4', date = '2018-01-23'}, + {patch = '4.2.0', date = '2018-02-20'}, + {patch = '4.2.1', date = '2018-03-06'}, + {patch = '4.2.2', date = '2018-03-27'}, + {patch = '4.2.3', date = '2018-04-05'}, + {patch = '4.2.4', date = '2018-04-17'}, + {patch = '4.3.0', date = '2018-04-24'}, + {patch = '4.3.1', date = '2018-05-17'}, + {patch = '4.3.2', date = '2018-05-30'}, + {patch = '4.4.0', date = '2018-06-20'}, + {patch = '4.4.1', date = '2018-07-17'}, + {patch = '4.5.0', date = '2018-08-07'}, + {patch = '4.5.1', date = '2018-08-14'}, + {patch = '4.6.0', date = '2018-09-04'}, + {patch = '4.6.1', date = '2018-09-11'}, + {patch = '4.6.2', date = '2018-10-16'}, + {patch = '4.7.0', date = '2018-11-13'}, + {patch = '4.7.1', date = '2018-11-20'}, + {patch = '4.8.0', date = '2018-12-18'}, + {patch = '4.8.1', date = '2019-01-10'}, + {patch = '4.8.2', date = '2019-01-22'}, + {patch = '4.8.3', date = '2019-02-19'}, + {patch = '4.8.4', date = '2019-04-09'}, + {patch = '4.8.5', date = '2019-04-23'}, + {patch = '4.8.6', date = '2019-04-24'}, + {patch = '4.9.0', date = '2019-05-21'}, + {patch = '4.9.1', date = '2019-06-04'}, + {patch = '4.9.2', date = '2019-06-18'}, + {patch = '4.9.3', date = '2019-07-10'}, + {patch = '4.10.0', date = '2019-08-13'}, + {patch = '4.10.1', date = '2019-08-21'}, + {patch = '4.10.2', date = '2019-09-03'}, + {patch = '4.10.3', date = '2019-09-10'}, + {patch = '4.10.4', date = '2019-09-22'}, + {patch = '4.11.0', date = '2019-11-26'}, + {patch = '4.11.1', date = '2019-11-27'}, + {patch = '4.11.2', date = '2019-12-06'}, + {patch = '4.11.3', date = '2019-12-17'}, + {patch = '4.11.4', date = '2020-02-18'}, + {patch = '4.12.0', date = '2020-06-09'}, + {patch = '4.12.1', date = '2020-06-16'}, + {patch = '5.0.0', date = '2020-07-27'}, + {patch = '5.0.1', date = '2020-07-29'}, + {patch = '5.0.2', date = '2020-08-06'}, + {patch = '5.0.3', date = '2020-08-25'}, + {patch = '5.0.4', date = '2020-11-04'}, + {patch = '5.0.5', date = '2020-12-02'}, + {patch = '5.0.6', date = '2021-02-02'}, + {patch = '5.0.7', date = '2021-04-07'}, + {patch = '5.0.8', date = '2021-10-19'}, + {patch = '5.0.9', date = '2022-03-15'}, + {patch = '5.0.10', date = '2022-07-21'}, + {patch = '5.0.11', date = '2023-01-24'}, + {patch = '5.0.12', date = '2023-09-29'}, + {patch = '5.0.13', date = '2024-03-26'}, + {patch = '5.0.14', date = '2024-11-26'}, + {patch = '5.0.15', date = '2025-10-01'}, + + {patch = '', date = '3999-01-01'}, -- keep this as last! +} + +---@param frame Frame +---@return string? +function PatchAuto.get(frame) + return PatchAuto._main(Arguments.getArgs(frame)) +end + +---@param args table +---@return string? +function PatchAuto._main(args) + local numberOfPatches = #PATCH_DATA + + -- Get the date for which the patch is to be determined + local date = args[1]:gsub('-XX', '99'):gsub('-??', '99') + + for index = 0, (numberOfPatches - 2) do + if (PATCH_DATA[numberOfPatches - index].date > date) and (PATCH_DATA[numberOfPatches - 1 - index].date <= date) then + return PATCH_DATA[numberOfPatches - 1 - index].patch + end + end +end + +return PatchAuto From 3f0f4b46d7c5db4f26cff82d8f6810b7b9350614 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 16:49:18 +0200 Subject: [PATCH 38/41] GTLWrapper --- lua/wikis/starcraft2/GTLWrapper.lua | 69 +++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 lua/wikis/starcraft2/GTLWrapper.lua diff --git a/lua/wikis/starcraft2/GTLWrapper.lua b/lua/wikis/starcraft2/GTLWrapper.lua new file mode 100644 index 00000000000..6f43f33e7c3 --- /dev/null +++ b/lua/wikis/starcraft2/GTLWrapper.lua @@ -0,0 +1,69 @@ +local Lua = require('Module:Lua') + +local Arguments = Lua.import('Module:Arguments') +local Array = Lua.import('Module:Array') +local Class = Lua.import('Module:Class') +local Json = Lua.import('Module:Json') +local Logic = Lua.import('Module:Logic') +local Variables = Lua.import('Module:Variables') + +local GroupTableLeague = Lua.import('Module:GroupTableLeague/Starcraft/next/downstream', {requireDevIfEnabled = true}) +local MatchGroup = Lua.import('Module:MatchGroup', {requireDevIfEnabled = true}) +local MatchGroupUtil = Lua.import('Module:MatchGroup/Util', {requireDevIfEnabled = true}) + +local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local WidgetUtil = Lua.import('Module:Widget/Util') + +local GroupTableWrapper = {} + +---@param args table +---@return Widget +function GroupTableWrapper.run(args) + args = args or {} + + return HtmlWidgets.Fragment{children = WidgetUtil.collect( + GroupTableLeague.run(args), + GroupTableWrapper._getMatchGroups(args) + )} +end + + +---@private +---@param args table +---@return Renderable[] +function GroupTableWrapper._getMatchGroups(args) + args.matchGroup1 = args.matchGroup1 or args.matchGroup + local matchGroups = Array.mapIndexes(function(index) + return args['matchGroup' .. index] + end) + + if Logic.isNotEmpty(matchGroups) then + return matchGroups + end + + -- query the matchgroups + local matchGroupIds = Array.mapIndexes(function(index) + return args['matchGroupId' .. index] + end) + table.insert(matchGroupIds, args.id) + table.insert(matchGroupIds, args.matchGroupId) + + return Array.map(matchGroupIds, function(id) + local matchRecords = MatchGroupUtil.fetchMatchRecords(id) + if not matchRecords then + return + end + Variables.varDefine('match2bracket_' .. id, Json.stringify(matchRecords)) + if Logic.readBool(args.onlySetVarForMatchLists) then + return + end + return MatchGroup.MatchGroupById{ + id = id, + width = args.width, + collapsed = args.collapsed or true, + attached = args.attached or true, + } + end) +end + +return Class.export(GroupTableWrapper) From 178a0a3c281cf82a810ef83007e5ce9d6ef4b999 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 16:50:36 +0200 Subject: [PATCH 39/41] MapEventList --- lua/wikis/starcraft2/MapEventList.lua | 92 +++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 lua/wikis/starcraft2/MapEventList.lua diff --git a/lua/wikis/starcraft2/MapEventList.lua b/lua/wikis/starcraft2/MapEventList.lua new file mode 100644 index 00000000000..11821e76a71 --- /dev/null +++ b/lua/wikis/starcraft2/MapEventList.lua @@ -0,0 +1,92 @@ +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Class = Lua.import('Module:Class') +local FnUtil = Lua.import('Module:FnUtil') +local Json = Lua.import('Module:Json') +local LeagueIcon = Lua.import('Module:LeagueIcon') +local Logic = Lua.import('Module:Logic') + +local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Link = Lua.import('Module:Widget/Basic/Link') +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local WidgetUtil = Lua.import('Module:Widget/Util') + +local MapEventList = {} + +---@param args table +---@return Widget? +function MapEventList.run(args) + args = args or {} + local mapName = (args.map or mw.title.getCurrentTitle().text):gsub(' ', '_') + + local data = mw.ext.LiquipediaDB.lpdb('tournament', { + conditions = '[[maps::!]] AND [[liquipediatier::1]] AND [[liquipediatiertype::]]', + query = 'pagename, maps, startdate, enddate, icon, icondark, name, extradata', + order = 'enddate desc', + limit = 5000, + }) + + if not data or not data[1] then + return + end + + return HtmlWidgets.Fragment{children = { + HtmlWidgets.H3{children = 'Played in Premier Tournaments'}, + TableWidgets.Table{ + sortable = true, + columns = { + {}, + {}, + {}, + {}, + }, + children = { + MapEventList._header(), + TableWidgets.TableBody{children = Array.map(data, FnUtil.curry(MapEventList._row, mapName))}, + }, + } + }} +end + +---@private +---@return Widget +function MapEventList._header() + return TableWidgets.TableHeader{children = { + TableWidgets.Row{children = { + TableWidgets.CellHeader{children = {'Start date'}}, + TableWidgets.CellHeader{children = {'End date'}}, + TableWidgets.CellHeader{colspan = 2, children = {'Tournament'}}, + }} + }} +end + +---@private +---@param mapName string +---@param item {maps: string, pagename: string, startdate: string, enddate: string, icon: string, icondark: string, name: string} +---@return Widget? +function MapEventList._row(mapName, item) + local maps = Json.parseIfTable(item.maps) or {} + if Logic.isEmpty(maps) then + return + end + + if Array.all(maps, function(mapData) + return (mapData.link:gsub(' ', '_')) ~= mapName + end) then return end + + return TableWidgets.Row{children = { + TableWidgets.Cell{children = item.startdate}, + TableWidgets.Cell{children = item.enddate}, + TableWidgets.Cell{children = LeagueIcon.display{ + icon = item.icon, + iconDark = item.icondark, + link = item.pagename, + name = item.name, + options = {noTemplate = true}, + }}, + TableWidgets.Cell{children = Link{link = item.pagename, children = item.name or item.pagename:gsub('_', ' ')}}, + }} +end + +return Class.export(MapEventList, {exports = {'run'}}) From 54dd7e7c7dc81803ce01ddb5df04f8f73f34fad0 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 16:51:50 +0200 Subject: [PATCH 40/41] shut up anno --- lua/definitions/mw.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/definitions/mw.lua b/lua/definitions/mw.lua index 112ff071d31..d9a481990ae 100644 --- a/lua/definitions/mw.lua +++ b/lua/definitions/mw.lua @@ -1091,7 +1091,7 @@ function mw.uri.encode(str, enctype) end function mw.uri.decode(str, enctype) end ---Encodes a table as a URI query string. ----@param query table +---@param query table ---@return string function mw.uri.buildQueryString(query) end From 4c70cc368bf4ef94be94325440a6318d1e5f1f17 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Wed, 1 Apr 2026 16:53:23 +0200 Subject: [PATCH 41/41] make linter happy --- lua/wikis/starcraft2/GTLWrapper.lua | 1 - lua/wikis/starcraft2/MapEventList.lua | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lua/wikis/starcraft2/GTLWrapper.lua b/lua/wikis/starcraft2/GTLWrapper.lua index 6f43f33e7c3..7b1389b75a3 100644 --- a/lua/wikis/starcraft2/GTLWrapper.lua +++ b/lua/wikis/starcraft2/GTLWrapper.lua @@ -1,6 +1,5 @@ local Lua = require('Module:Lua') -local Arguments = Lua.import('Module:Arguments') local Array = Lua.import('Module:Array') local Class = Lua.import('Module:Class') local Json = Lua.import('Module:Json') diff --git a/lua/wikis/starcraft2/MapEventList.lua b/lua/wikis/starcraft2/MapEventList.lua index 11821e76a71..c39d4caebfc 100644 --- a/lua/wikis/starcraft2/MapEventList.lua +++ b/lua/wikis/starcraft2/MapEventList.lua @@ -10,7 +10,6 @@ local Logic = Lua.import('Module:Logic') local HtmlWidgets = Lua.import('Module:Widget/Html/All') local Link = Lua.import('Module:Widget/Basic/Link') local TableWidgets = Lua.import('Module:Widget/Table2/All') -local WidgetUtil = Lua.import('Module:Widget/Util') local MapEventList = {} @@ -63,7 +62,8 @@ end ---@private ---@param mapName string ----@param item {maps: string, pagename: string, startdate: string, enddate: string, icon: string, icondark: string, name: string} +---@param item {maps: string, pagename: string, startdate: string, enddate: string, +---icon: string, icondark: string, name: string} ---@return Widget? function MapEventList._row(mapName, item) local maps = Json.parseIfTable(item.maps) or {}