PC Gaming Shelter
An archive dedicated to preserving PC Gaming history and more

Module:Universe: Difference between revisions

From PC Gaming Shelter
No edit summary
No edit summary
Line 1: Line 1:
local utils = require( 'Module:Utils' )
local utils = require("Module:Utils")


local p = {}
local p = {}
Line 5: Line 5:
local function getCurrentGame()
local function getCurrentGame()
local title = mw.title.getCurrentTitle()
local title = mw.title.getCurrentTitle()
local game = title.rootText
local game = title.rootText
local resource = title.subpageText
local resource = title.subpageText
local data = {
local data = {
['game'] = game,
["game"] = game,
['resource'] = resource,
["resource"] = resource,
}
}
return data
return data
Line 15: Line 15:


local function setDisplayTitle(frame, title)
local function setDisplayTitle(frame, title)
local displayTitle = frame:preprocess(string.format('{{DISPLAYTITLE:%s|noreplace, noerror}}', title))
local displayTitle = frame:preprocess(string.format("{{DISPLAYTITLE:%s|noreplace, noerror}}", title))
return displayTitle
return displayTitle
end
end


local function escapePattern(value)
local function escapePattern(value)
return value:gsub('([^%w])', '%%%1')
return value:gsub("([^%w])", "%%%1")
end
end


Line 27: Line 27:


for index, link in ipairs(results) do
for index, link in ipairs(results) do
results[index] = link:gsub(
results[index] = link:gsub("|" .. gamePattern .. "%s+", "|")
'|' .. gamePattern .. '%s+',
'|'
)
end
end


Line 38: Line 35:
local function renameLabels(results)
local function renameLabels(results)
local labels = {
local labels = {
Mods = 'Mod Archive',
Mods = "Mod Archive",
Players = 'Player Roster',
Players = "Player Roster",
Clans = 'Clan History',
Clans = "Clan History",
}
}


for i, link in ipairs(results) do
for i, link in ipairs(results) do
results[i] = link:gsub('|([^%]]+)%]%]', function(label)
results[i] = link:gsub("|([^%]]+)%]%]", function(label)
return '|' .. (labels[label] or label) .. ']]'
return "|" .. (labels[label] or label) .. "]]"
end)
end)
end
end
Line 61: Line 58:
table.sort(results, function(a, b)
table.sort(results, function(a, b)
-- Extract the final subpage name from the link target.
-- Extract the final subpage name from the link target.
local targetA = a:match('^%[%[:.-/([^/|%]]+)|')
local targetA = a:match("^%[%[:.-/([^/|%]]+)|")
local targetB = b:match('^%[%[:.-/([^/|%]]+)|')
local targetB = b:match("^%[%[:.-/([^/|%]]+)|")


local rankA = order[targetA] or math.huge
local rankA = order[targetA] or math.huge
Line 80: Line 77:
local function getSubpages(page)
local function getSubpages(page)
local subpages = mw.smw.ask({
local subpages = mw.smw.ask({
'[[Has parent page::' .. page .. ']][[Modification date::+]]'
"[[Has parent page::" .. page .. "]][[Modification date::+]]",
}) or {}
}) or {}
 
local subpageTable = {}
local subpageTable = {}
 
for _, subpage in ipairs(subpages) do
for _, subpage in ipairs(subpages) do
local link = subpage[1]
local link = subpage[1]
Line 93: Line 90:
end
end


local allGames = string.format('[[%s|%s]]', 'Games', 'All Games')
local allGames = string.format("[[%s|%s]]", "Games", "All Games")
table.insert(subpageTable, allGames)
table.insert(subpageTable, allGames)
 
removeGameFromLabels(subpageTable, page)
removeGameFromLabels(subpageTable, page)
renameLabels(subpageTable)
renameLabels(subpageTable)
Line 101: Line 98:


if #subpageTable == 0 then
if #subpageTable == 0 then
return ''
return ""
end
end


local html = mw.html.create()
local html = mw.html.create()
 
local universe = html:tag('div'):attr('id', 'universeMenu'):wikitext(' | ' .. table.concat(subpageTable, ' | '))
local universe = html:tag("div"):attr("id", "universeMenu"):wikitext(" | " .. table.concat(subpageTable, " | "))
 
return tostring(html)
return tostring(html)
end
end
Line 113: Line 110:
local function formatCellValue(value)
local function formatCellValue(value)
if value == nil then
if value == nil then
return ''
return ""
end
end


if type(value) == 'table' then
if type(value) == "table" then
local values = {}
local values = {}


Line 123: Line 120:
end
end


return table.concat(values, '<br>')
return table.concat(values, "<br>")
end
end


Line 130: Line 127:


local function renderModTable(content)
local function renderModTable(content)
local wrapper = mw.html.create('div')
local wrapper = mw.html.create("div")
wrapper
wrapper:attr("id", "assets")
:attr('id', 'assets')
 
local tableNode = wrapper:tag("table")
local tableNode = wrapper:tag('table')
tableNode:addClass("wikitable")
tableNode
:addClass('wikitable')


for _, row in ipairs(content or {}) do
for _, row in ipairs(content or {}) do
local tr = tableNode:tag('tr')
local tr = tableNode:tag("tr")


tr:tag('td'):wikitext(formatCellValue(row[1]))
tr:tag("td"):wikitext(formatCellValue(row[1]))
tr:tag('td'):wikitext(formatCellValue(row['Category']))
tr:tag("td"):wikitext(formatCellValue(row["Category"]))
tr:tag('td'):wikitext(
tr:tag("td"):wikitext(
  formatCellValue(row['Has map type'])
formatCellValue(row["Has map type"]),
, formatCellValue(row['Has skin type'])
formatCellValue(row["Has skin type"]),
, formatCellValue(row['Has mod type'])
formatCellValue(row["Has mod type"]),
, formatCellValue(row['Has utility type'])
formatCellValue(row["Has utility type"])
)
)
tr:tag('td'):wikitext(formatCellValue(row['Has game mode']))
tr:tag("td"):wikitext(formatCellValue(row["Has game mode"]))
tr:tag('td'):wikitext(formatCellValue(row['Has developer']))
tr:tag("td"):wikitext(formatCellValue(row["Has developer"]))
tr:tag('td'):wikitext(formatCellValue(row['Has release date']))
tr:tag("td"):wikitext(formatCellValue(row["Has release date"]))
tr:tag('td'):wikitext(formatCellValue(row['Has status']))
tr:tag("td"):wikitext(formatCellValue(row["Has status"]))
tr:tag('td'):wikitext(formatCellValue(row['Has short description']))
tr:tag("td"):wikitext(formatCellValue(row["Has short description"]))
tr:tag('td'):wikitext(formatCellValue(row['Works on game version']))
tr:tag("td"):wikitext(formatCellValue(row["Works on game version"]))
tr:tag('td'):wikitext(formatCellValue(row['Download links']))
tr:tag("td"):wikitext(formatCellValue(row["Download links"]))
tr:tag('td'):wikitext(formatCellValue(row['Related game']))
tr:tag("td"):wikitext(formatCellValue(row["Related game"]))
end
end


Line 163: Line 158:


local function renderPlayerTable(content, panes)
local function renderPlayerTable(content, panes)
local wrapper = mw.html.create("div")
wrapper:attr("id", "players"):attr("data-pane-columns", panes)


local wrapper = mw.html.create('div')
local tableNode = wrapper:tag("table")
wrapper
tableNode:addClass("wikitable")
:attr('id', 'players')
:attr('data-pane-columns', panes)
local tableNode = wrapper:tag('table')
tableNode
:addClass('wikitable')


for _, row in ipairs(content or {}) do
for _, row in ipairs(content or {}) do
Line 178: Line 169:
local clanAndRoleValues = {}
local clanAndRoleValues = {}


local member = row['Member of']
local member = row["Member of"]


if member then
if member then
-- SMW may return either one value or a table of values.
-- SMW may return either one value or a table of values.
local memberships = type(member) == 'table'
local memberships = type(member) == "table" and member or { member }
and member
or { member }


for _, clan in ipairs(memberships) do
for _, clan in ipairs(memberships) do
local formattedClan = formatCellValue(clan)
local formattedClan = formatCellValue(clan)


table.insert(
table.insert(clanValues, string.format("<div>%s</div>", formattedClan))
clanValues,
string.format('<div>%s</div>', formattedClan)
)


local query = {
local query = {
'[[-Has subobject::' .. row[1] .. ']]',
"[[-Has subobject::" .. row[1] .. "]]",
'[[Member of::' .. clan .. ']]',
"[[Member of::" .. clan .. "]]",
'?Has role#-=role',
"?Has role#-=role",
mainlabel = '-',
mainlabel = "-",
}
}


local results = mw.smw.ask(query)
local results = mw.smw.ask(query)
local clanRoles = {}
local clanRoles = {}


local function addRoles(value)
local function addRoles(value)
if value == nil or value == '' then
if value == nil or value == "" then
return
return
end
end


for role in mw.text.gsplit(tostring(value), ',', true) do
for role in mw.text.gsplit(tostring(value), ",", true) do
local normalizedRole = mw.text.trim(role)
local normalizedRole = mw.text.trim(role)


if normalizedRole ~= '' then
if normalizedRole ~= "" then
table.insert(
table.insert(clanRoles, formatCellValue(normalizedRole))
clanRoles,
end
formatCellValue(normalizedRole)
end
)
end
end
end
end


for _, resultRow in ipairs(results or {}) do
for _, resultRow in ipairs(results or {}) do
local resultRoles = resultRow.role
local resultRoles = resultRow.role


if type(resultRoles) == 'table' then
if type(resultRoles) == "table" then
for _, roleValue in ipairs(resultRoles) do
for _, roleValue in ipairs(resultRoles) do
addRoles(roleValue)
addRoles(roleValue)
end
end
else
else
addRoles(resultRoles)
addRoles(resultRoles)
end
end
end
end


if #clanRoles > 0 then
if #clanRoles > 0 then
local roleDivs =
local roleDivs = "<div>" .. table.concat(clanRoles, "</div><div>") .. "</div>"
'<div>' ..
table.concat(clanRoles, '</div><div>') ..
'</div>'


table.insert(roleValues, roleDivs)
table.insert(roleValues, roleDivs)


table.insert(
table.insert(
clanAndRoleValues,
clanAndRoleValues,
string.format(
string.format("<div>%s (%s)</div>", formattedClan, table.concat(clanRoles, ", "))
'<div>%s (%s)</div>',
)
formattedClan,
else
table.concat(clanRoles, ', ')
table.insert(roleValues, "<div>—</div>")
)
)
else
table.insert(roleValues, '<div>—</div>')


table.insert(
table.insert(clanAndRoleValues, string.format("<div>%s</div>", formattedClan))
clanAndRoleValues,
end
string.format('<div>%s</div>', formattedClan)
end
)
end
end
end
end
 
local tr = tableNode:tag('tr')
local tr = tableNode:tag("tr")
tr:tag('td'):wikitext(formatCellValue(row[1]))
tr:tag("td"):wikitext(formatCellValue(row[1]))
tr:tag('td'):wikitext(formatCellValue(row['Plays game']))
tr:tag("td"):wikitext(formatCellValue(row["Plays game"]))
tr:tag('td'):wikitext(table.concat(clanValues, ''))
tr:tag("td"):wikitext(table.concat(clanValues, ""))
tr:tag('td'):wikitext(table.concat(roleValues, ''))
tr:tag("td"):wikitext(table.concat(roleValues, ""))
tr:tag('td'):wikitext(table.concat(clanAndRoleValues, ''))
tr:tag("td"):wikitext(table.concat(clanAndRoleValues, ""))
tr:tag('td'):wikitext(formatCellValue(row['Has status']))
tr:tag("td"):wikitext(formatCellValue(row["Has status"]))
end
end


return wrapper
return wrapper
end
end


local function renderClanPlayerTable(content, clanname)
local function renderClanPlayerTable(content, clanname)
local title = mw.title.getCurrentTitle()
local title = mw.title.getCurrentTitle()
local clanpage = clanname
local clanpage = clanname
 
local wrapper = mw.html.create('div')
local wrapper = mw.html.create("div")
wrapper
wrapper:attr("id", "players"):attr("data-pane-columns", "3, 5")
:attr('id', 'players')
 
:attr('data-pane-columns', '3, 5')
local tableNode = wrapper:tag("table")
tableNode:addClass("wikitable")
local tableNode = wrapper:tag('table')
tableNode
:addClass('wikitable')


for _, row in ipairs(content or {}) do
for _, row in ipairs(content or {}) do
Line 290: Line 259:
local roleValues = {}
local roleValues = {}


local member = row['Member of']
local member = row["Member of"]


if member then
if member then
Line 303: Line 272:


if formattedClan == clanpage then
if formattedClan == clanpage then
table.insert(clanValues, string.format("<div>%s</div>", formattedClan))
table.insert(
clanValues,
string.format('<div>%s</div>', formattedClan)
)


local query = {
local query = {
'[[-Has subobject::' .. row[1] .. ']]',
"[[-Has subobject::" .. row[1] .. "]]",
'[[Member of::' .. clan .. ']]',
"[[Member of::" .. clan .. "]]",
'?Has role#-=role',
"?Has role#-=role",
mainlabel = '-',
mainlabel = "-",
}
}


local results = mw.smw.ask(query)
local results = mw.smw.ask(query)
 
local clanRoles = {}
local clanRoles = {}


local function addRoles(value)
local function addRoles(value)
if value == nil or value == '' then
if value == nil or value == "" then
return
return
end
end


for role in mw.text.gsplit(tostring(value), ',', true) do
for role in mw.text.gsplit(tostring(value), ",", true) do
local normalizedRole = mw.text.trim(role)
local normalizedRole = mw.text.trim(role)


if normalizedRole ~= '' then
if normalizedRole ~= "" then
table.insert(
table.insert(clanRoles, formatCellValue(normalizedRole))
clanRoles,
formatCellValue(normalizedRole)
)
end
end
end
end
Line 340: Line 302:
local resultRoles = resultRow.role
local resultRoles = resultRow.role


if type(resultRoles) == 'table' then
if type(resultRoles) == "table" then
for _, roleValue in ipairs(resultRoles) do
for _, roleValue in ipairs(resultRoles) do
addRoles(roleValue)
addRoles(roleValue)
Line 348: Line 310:
end
end
end
end
 
if #clanRoles > 0 then
if #clanRoles > 0 then
local roleDivs = table.concat(clanRoles, ', ')
local roleDivs = table.concat(clanRoles, ", ")
table.insert(roleValues, roleDivs)
table.insert(roleValues, roleDivs)
else
else
table.insert(roleValues, '')
table.insert(roleValues, "")
end
end
end
end


local tr = tableNode:tag('tr')
local tr = tableNode:tag("tr")
tr:tag('td'):wikitext(formatCellValue(row[1]))
tr:tag("td"):wikitext(formatCellValue(row[1]))
tr:tag('td'):wikitext(formatCellValue(row['Plays game']))
tr:tag("td"):wikitext(formatCellValue(row["Plays game"]))
tr:tag('td'):wikitext(table.concat(clanValues, ''))
tr:tag("td"):wikitext(table.concat(clanValues, ""))
tr:tag('td'):wikitext(table.concat(roleValues, ''))
tr:tag("td"):wikitext(table.concat(roleValues, ""))
tr:tag('td'):wikitext(table.concat(roleValues, ''))
tr:tag("td"):wikitext(table.concat(roleValues, ""))
tr:tag('td'):wikitext(formatCellValue(row['Has status']))
tr:tag("td"):wikitext(formatCellValue(row["Has status"]))
end
end
end
end
Line 372: Line 334:


function p.Mods(frame)
function p.Mods(frame)
local data     = getCurrentGame()
local data = getCurrentGame()
local game     = data.game or '+'
local game = data.game or "+"
local resource = data.resource
local resource = data.resource
 
mw.smw.set({
mw.smw.set({
['Has parent page'] = game
["Has parent page"] = game,
})
})


local query = {
local query = {
'[[Category:Mods||Maps||Skins||Utilities]][[Related game::' .. game .. ']]',
"[[Category:Mods||Maps||Skins||Utilities]][[Related game::" .. game .. "]]",
'?Related game',
"?Related game",
'?Category',
"?Category",
'?Has mod type',
"?Has mod type",
'?Has map type',
"?Has map type",
'?Has skin type',
"?Has skin type",
'?Has utility type',
"?Has utility type",
'?Has game mode',
"?Has game mode",
'?Has developer',
"?Has developer",
'?Has release date#-F[Y]',
"?Has release date#-F[Y]",
'?Has status',  
"?Has status",
'?Has short description',
"?Has short description",
'?Works on game version',
"?Works on game version",
'?Download links'
"?Download links",
}
}
local content = mw.smw.ask(query)
local content = mw.smw.ask(query)
local forminput = frame:callParserFunction({ name = '#forminput', '', args = {
'form=Mod',
'query string=Mod[Related game]=' .. game,
'button text=Create or Edit Mod',
'autofocus=no'
}})


setDisplayTitle(frame, string.format('%s %s', game, resource))
local forminput = frame:callParserFunction({
name = "#forminput",
"",
args = {
"form=Mod",
"query string=Mod[Related game]=" .. game,
"button text=Create or Edit Mod",
"autofocus=no",
},
})
 
setDisplayTitle(frame, string.format("%s %s", game, resource))
 
local html = mw.html.create()
local html = mw.html.create()
html:wikitext(getSubpages(game))
html:wikitext(getSubpages(game))
html:wikitext(forminput)
html:wikitext(forminput)
html:node(renderModTable(content))
html:node(renderModTable(content))
 
return html
return html
end
end


function p.allMods(frame)
function p.allMods(frame)
local query = {
local query = {
'[[Category:Mods||Maps||Skins||Utilities]][[Related game::+]]',
"[[Category:Mods||Maps||Skins||Utilities]][[Related game::+]]",
'?Related game',
"?Related game",
'?Category',
"?Category",
'?Has mod type',
"?Has mod type",
'?Has map type',
"?Has map type",
'?Has skin type',
"?Has skin type",
'?Has utility type',
"?Has utility type",
'?Has game mode',
"?Has game mode",
'?Has developer',
"?Has developer",
'?Has release date#-F[Y]',
"?Has release date#-F[Y]",
'?Has status',  
"?Has status",
'?Has short description',
"?Has short description",
'?Works on game version',
"?Works on game version",
'?Download links'
"?Download links",
}
}
local content = mw.smw.ask(query)
local content = mw.smw.ask(query)
Line 437: Line 402:
local html = mw.html.create()
local html = mw.html.create()
html:node(renderModTable(content, true))
html:node(renderModTable(content, true))
 
return html
return html
end
end


function p.Players(frame)
function p.Players(frame)
local data     = getCurrentGame()
local data = getCurrentGame()
local game     = data.game
local game = data.game
local resource = data.resource
local resource = data.resource


mw.smw.set({
mw.smw.set({
['Has parent page'] = game
["Has parent page"] = game,
})
})


local query = {
local query = {
'[[Category:Players]][[Plays game::' .. game .. ']]',
"[[Category:Players]][[Plays game::" .. game .. "]]",
'?Plays game',
"?Plays game",
'?Member of',
"?Member of",
'?Has status'
"?Has status",
}
}
 
local content = mw.smw.ask(query)
local content = mw.smw.ask(query)
local forminput = frame:callParserFunction({ name = '#forminput', '', args = {
'form=Player',
'query string=Player[Plays game]=' .. game,
'button text=Create or Edit Player Entry',
'autocomplete on category=Players',
'autofocus=no'
}})


setDisplayTitle(frame, string.format('%s %s', game, resource))
local forminput = frame:callParserFunction({
name = "#forminput",
"",
args = {
"form=Player",
"query string=Player[Plays game]=" .. game,
"button text=Create or Edit Player Entry",
"autocomplete on category=Players",
"autofocus=no",
},
})
 
setDisplayTitle(frame, string.format("%s %s", game, resource))


local html = mw.html.create()
local html = mw.html.create()
html:wikitext(getSubpages(game))
html:wikitext(getSubpages(game))
html:wikitext(forminput)
html:wikitext(forminput)
html:node(renderPlayerTable(content, '2, 3, 5'))
html:node(renderPlayerTable(content, "2, 3, 5"))
 
return html
return html
end
end


function p.allPlayers(frame)
function p.allPlayers(frame)
local query = {
local query = {
'[[Category:Players]][[Plays game::+]]',
"[[Category:Players]][[Plays game::+]]",
'?Plays game',
"?Plays game",
'?Member of',
"?Member of",
'?Has status'
"?Has status",
}
}
 
local content = mw.smw.ask(query)
local content = mw.smw.ask(query)


local html = mw.html.create()
local html = mw.html.create()
html:node(renderPlayerTable(content, '1, 2, 3, 5'))
html:node(renderPlayerTable(content, "1, 2, 3, 5"))
 
return html
return html
end
end
Line 497: Line 465:
local title = mw.title.getCurrentTitle()
local title = mw.title.getCurrentTitle()
local clan = title.fullText
local clan = title.fullText
 
local query = {
local query = {
'[[Member of::' .. clan .. ']][[Modification date::+]]',
"[[Member of::" .. clan .. "]][[Modification date::+]]",
'?Plays game',
"?Plays game",
'?Member of',
"?Member of",
'?Has status'
"?Has status",
}
}
 
local content = mw.smw.ask(query)
local content = mw.smw.ask(query)


local html = mw.html.create()
local html = mw.html.create()
html:node(renderClanPlayerTable(content, clan))
html:node(renderClanPlayerTable(content, clan))
 
return html
return html
end
end


function p.Clans(frame)
function p.Clans(frame)
local data     = getCurrentGame()
local data = getCurrentGame()
local game     = data.game
local game = data.game
local resource = data.resource
local resource = data.resource


mw.smw.set({
mw.smw.set({
['Has parent page'] = game
["Has parent page"] = game,
})
})


local query = {
local query = {
'[[Category:Communities]][[Related game::' .. game .. ']]'
"[[Category:Communities]][[Related game::" .. game .. "]]",
}
}
 
local content = mw.smw.ask(query)
local content = mw.smw.ask(query)
local forminput = frame:callParserFunction({ name = '#forminput', '', args = {
'form=Community',
'query string=Community[Related game]=' .. game,
'button text=Create or Edit Community Entry',
'autocomplete on category=Communities',
'autofocus=no'
}})


setDisplayTitle(frame, string.format('%s %s', game, resource))
local forminput = frame:callParserFunction({
name = "#forminput",
"",
args = {
"form=Community",
"query string=Community[Related game]=" .. game,
"button text=Create or Edit Community Entry",
"autocomplete on category=Communities",
"autofocus=no",
},
})
 
setDisplayTitle(frame, string.format("%s %s", game, resource))


local html = mw.html.create()
local html = mw.html.create()
html:wikitext(getSubpages(game))
html:wikitext(getSubpages(game))
html:wikitext(forminput)
html:wikitext(forminput)
 
return html
return html
end
end


return p
return p

Revision as of 07:31, 6 August 2026

Documentation for this module may be created at Module:Universe/doc

local utils = require("Module:Utils")

local p = {}

local function getCurrentGame()
	local title = mw.title.getCurrentTitle()
	local game = title.rootText
	local resource = title.subpageText
	local data = {
		["game"] = game,
		["resource"] = resource,
	}
	return data
end

local function setDisplayTitle(frame, title)
	local displayTitle = frame:preprocess(string.format("{{DISPLAYTITLE:%s|noreplace, noerror}}", title))
	return displayTitle
end

local function escapePattern(value)
	return value:gsub("([^%w])", "%%%1")
end

local function removeGameFromLabels(results, game)
	local gamePattern = escapePattern(game)

	for index, link in ipairs(results) do
		results[index] = link:gsub("|" .. gamePattern .. "%s+", "|")
	end

	return results
end

local function renameLabels(results)
	local labels = {
		Mods = "Mod Archive",
		Players = "Player Roster",
		Clans = "Clan History",
	}

	for i, link in ipairs(results) do
		results[i] = link:gsub("|([^%]]+)%]%]", function(label)
			return "|" .. (labels[label] or label) .. "]]"
		end)
	end

	return results
end

local function sortSubpages(results)
	local order = {
		Mods = 1,
		Players = 2,
		Clans = 3,
	}

	table.sort(results, function(a, b)
		-- Extract the final subpage name from the link target.
		local targetA = a:match("^%[%[:.-/([^/|%]]+)|")
		local targetB = b:match("^%[%[:.-/([^/|%]]+)|")

		local rankA = order[targetA] or math.huge
		local rankB = order[targetB] or math.huge

		if rankA ~= rankB then
			return rankA < rankB
		end

		-- Keep all remaining subpages alphabetically ordered.
		return (targetA or a) < (targetB or b)
	end)

	return results
end

local function getSubpages(page)
	local subpages = mw.smw.ask({
		"[[Has parent page::" .. page .. "]][[Modification date::+]]",
	}) or {}

	local subpageTable = {}

	for _, subpage in ipairs(subpages) do
		local link = subpage[1]

		if link then
			table.insert(subpageTable, link)
		end
	end

	local allGames = string.format("[[%s|%s]]", "Games", "All Games")
	table.insert(subpageTable, allGames)

	removeGameFromLabels(subpageTable, page)
	renameLabels(subpageTable)
	sortSubpages(subpageTable)

	if #subpageTable == 0 then
		return ""
	end

	local html = mw.html.create()

	local universe = html:tag("div"):attr("id", "universeMenu"):wikitext(" | " .. table.concat(subpageTable, " | "))

	return tostring(html)
end

local function formatCellValue(value)
	if value == nil then
		return ""
	end

	if type(value) == "table" then
		local values = {}

		for _, item in ipairs(value) do
			table.insert(values, tostring(item))
		end

		return table.concat(values, "<br>")
	end

	return tostring(value)
end

local function renderModTable(content)
	local wrapper = mw.html.create("div")
	wrapper:attr("id", "assets")

	local tableNode = wrapper:tag("table")
	tableNode:addClass("wikitable")

	for _, row in ipairs(content or {}) do
		local tr = tableNode:tag("tr")

		tr:tag("td"):wikitext(formatCellValue(row[1]))
		tr:tag("td"):wikitext(formatCellValue(row["Category"]))
		tr:tag("td"):wikitext(
			formatCellValue(row["Has map type"]),
			formatCellValue(row["Has skin type"]),
			formatCellValue(row["Has mod type"]),
			formatCellValue(row["Has utility type"])
		)
		tr:tag("td"):wikitext(formatCellValue(row["Has game mode"]))
		tr:tag("td"):wikitext(formatCellValue(row["Has developer"]))
		tr:tag("td"):wikitext(formatCellValue(row["Has release date"]))
		tr:tag("td"):wikitext(formatCellValue(row["Has status"]))
		tr:tag("td"):wikitext(formatCellValue(row["Has short description"]))
		tr:tag("td"):wikitext(formatCellValue(row["Works on game version"]))
		tr:tag("td"):wikitext(formatCellValue(row["Download links"]))
		tr:tag("td"):wikitext(formatCellValue(row["Related game"]))
	end

	return wrapper
end

local function renderPlayerTable(content, panes)
	local wrapper = mw.html.create("div")
	wrapper:attr("id", "players"):attr("data-pane-columns", panes)

	local tableNode = wrapper:tag("table")
	tableNode:addClass("wikitable")

	for _, row in ipairs(content or {}) do
		local clanValues = {}
		local roleValues = {}
		local clanAndRoleValues = {}

		local member = row["Member of"]

		if member then
			-- SMW may return either one value or a table of values.
			local memberships = type(member) == "table" and member or { member }

			for _, clan in ipairs(memberships) do
				local formattedClan = formatCellValue(clan)

				table.insert(clanValues, string.format("<div>%s</div>", formattedClan))

				local query = {
					"[[-Has subobject::" .. row[1] .. "]]",
					"[[Member of::" .. clan .. "]]",
					"?Has role#-=role",
					mainlabel = "-",
				}

				local results = mw.smw.ask(query)
				local clanRoles = {}

				local function addRoles(value)
					if value == nil or value == "" then
						return
					end

					for role in mw.text.gsplit(tostring(value), ",", true) do
						local normalizedRole = mw.text.trim(role)

						if normalizedRole ~= "" then
							table.insert(clanRoles, formatCellValue(normalizedRole))
						end
					end
				end

				for _, resultRow in ipairs(results or {}) do
					local resultRoles = resultRow.role

					if type(resultRoles) == "table" then
						for _, roleValue in ipairs(resultRoles) do
							addRoles(roleValue)
						end
					else
						addRoles(resultRoles)
					end
				end

				if #clanRoles > 0 then
					local roleDivs = "<div>" .. table.concat(clanRoles, "</div><div>") .. "</div>"

					table.insert(roleValues, roleDivs)

					table.insert(
						clanAndRoleValues,
						string.format("<div>%s (%s)</div>", formattedClan, table.concat(clanRoles, ", "))
					)
				else
					table.insert(roleValues, "<div>—</div>")

					table.insert(clanAndRoleValues, string.format("<div>%s</div>", formattedClan))
				end
			end
		end

		local tr = tableNode:tag("tr")
		tr:tag("td"):wikitext(formatCellValue(row[1]))
		tr:tag("td"):wikitext(formatCellValue(row["Plays game"]))
		tr:tag("td"):wikitext(table.concat(clanValues, ""))
		tr:tag("td"):wikitext(table.concat(roleValues, ""))
		tr:tag("td"):wikitext(table.concat(clanAndRoleValues, ""))
		tr:tag("td"):wikitext(formatCellValue(row["Has status"]))
	end

	return wrapper
end

local function renderClanPlayerTable(content, clanname)
	local title = mw.title.getCurrentTitle()
	local clanpage = clanname

	local wrapper = mw.html.create("div")
	wrapper:attr("id", "players"):attr("data-pane-columns", "3, 5")

	local tableNode = wrapper:tag("table")
	tableNode:addClass("wikitable")

	for _, row in ipairs(content or {}) do
		local clanValues = {}
		local roleValues = {}

		local member = row["Member of"]

		if member then
			-- SMW may return either one value or a table of values.
			-- The outer query already restricts rows to the current clan. Query
			-- that clan directly so linked/formatted SMW values cannot make the
			-- equality check skip the role lookup.
			local memberships = { clanpage }

			for _, clan in ipairs(memberships) do
				local formattedClan = formatCellValue(clan)

				if formattedClan == clanpage then
					table.insert(clanValues, string.format("<div>%s</div>", formattedClan))

					local query = {
						"[[-Has subobject::" .. row[1] .. "]]",
						"[[Member of::" .. clan .. "]]",
						"?Has role#-=role",
						mainlabel = "-",
					}

					local results = mw.smw.ask(query)

					local clanRoles = {}

					local function addRoles(value)
						if value == nil or value == "" then
							return
						end

						for role in mw.text.gsplit(tostring(value), ",", true) do
							local normalizedRole = mw.text.trim(role)

							if normalizedRole ~= "" then
								table.insert(clanRoles, formatCellValue(normalizedRole))
							end
						end
					end

					for _, resultRow in ipairs(results or {}) do
						local resultRoles = resultRow.role

						if type(resultRoles) == "table" then
							for _, roleValue in ipairs(resultRoles) do
								addRoles(roleValue)
							end
						else
							addRoles(resultRoles)
						end
					end

					if #clanRoles > 0 then
						local roleDivs = table.concat(clanRoles, ", ")
						table.insert(roleValues, roleDivs)
					else
						table.insert(roleValues, "—")
					end
				end

				local tr = tableNode:tag("tr")
				tr:tag("td"):wikitext(formatCellValue(row[1]))
				tr:tag("td"):wikitext(formatCellValue(row["Plays game"]))
				tr:tag("td"):wikitext(table.concat(clanValues, ""))
				tr:tag("td"):wikitext(table.concat(roleValues, ""))
				tr:tag("td"):wikitext(table.concat(roleValues, ""))
				tr:tag("td"):wikitext(formatCellValue(row["Has status"]))
			end
		end
	end

	return wrapper
end

function p.Mods(frame)
	local data = getCurrentGame()
	local game = data.game or "+"
	local resource = data.resource

	mw.smw.set({
		["Has parent page"] = game,
	})

	local query = {
		"[[Category:Mods||Maps||Skins||Utilities]][[Related game::" .. game .. "]]",
		"?Related game",
		"?Category",
		"?Has mod type",
		"?Has map type",
		"?Has skin type",
		"?Has utility type",
		"?Has game mode",
		"?Has developer",
		"?Has release date#-F[Y]",
		"?Has status",
		"?Has short description",
		"?Works on game version",
		"?Download links",
	}
	local content = mw.smw.ask(query)

	local forminput = frame:callParserFunction({
		name = "#forminput",
		"",
		args = {
			"form=Mod",
			"query string=Mod[Related game]=" .. game,
			"button text=Create or Edit Mod",
			"autofocus=no",
		},
	})

	setDisplayTitle(frame, string.format("%s %s", game, resource))

	local html = mw.html.create()
	html:wikitext(getSubpages(game))
	html:wikitext(forminput)
	html:node(renderModTable(content))

	return html
end

function p.allMods(frame)
	local query = {
		"[[Category:Mods||Maps||Skins||Utilities]][[Related game::+]]",
		"?Related game",
		"?Category",
		"?Has mod type",
		"?Has map type",
		"?Has skin type",
		"?Has utility type",
		"?Has game mode",
		"?Has developer",
		"?Has release date#-F[Y]",
		"?Has status",
		"?Has short description",
		"?Works on game version",
		"?Download links",
	}
	local content = mw.smw.ask(query)

	local html = mw.html.create()
	html:node(renderModTable(content, true))

	return html
end

function p.Players(frame)
	local data = getCurrentGame()
	local game = data.game
	local resource = data.resource

	mw.smw.set({
		["Has parent page"] = game,
	})

	local query = {
		"[[Category:Players]][[Plays game::" .. game .. "]]",
		"?Plays game",
		"?Member of",
		"?Has status",
	}

	local content = mw.smw.ask(query)

	local forminput = frame:callParserFunction({
		name = "#forminput",
		"",
		args = {
			"form=Player",
			"query string=Player[Plays game]=" .. game,
			"button text=Create or Edit Player Entry",
			"autocomplete on category=Players",
			"autofocus=no",
		},
	})

	setDisplayTitle(frame, string.format("%s %s", game, resource))

	local html = mw.html.create()
	html:wikitext(getSubpages(game))
	html:wikitext(forminput)
	html:node(renderPlayerTable(content, "2, 3, 5"))

	return html
end

function p.allPlayers(frame)
	local query = {
		"[[Category:Players]][[Plays game::+]]",
		"?Plays game",
		"?Member of",
		"?Has status",
	}

	local content = mw.smw.ask(query)

	local html = mw.html.create()
	html:node(renderPlayerTable(content, "1, 2, 3, 5"))

	return html
end

function p.clanPlayers(frame)
	local title = mw.title.getCurrentTitle()
	local clan = title.fullText

	local query = {
		"[[Member of::" .. clan .. "]][[Modification date::+]]",
		"?Plays game",
		"?Member of",
		"?Has status",
	}

	local content = mw.smw.ask(query)

	local html = mw.html.create()
	html:node(renderClanPlayerTable(content, clan))

	return html
end

function p.Clans(frame)
	local data = getCurrentGame()
	local game = data.game
	local resource = data.resource

	mw.smw.set({
		["Has parent page"] = game,
	})

	local query = {
		"[[Category:Communities]][[Related game::" .. game .. "]]",
	}

	local content = mw.smw.ask(query)

	local forminput = frame:callParserFunction({
		name = "#forminput",
		"",
		args = {
			"form=Community",
			"query string=Community[Related game]=" .. game,
			"button text=Create or Edit Community Entry",
			"autocomplete on category=Communities",
			"autofocus=no",
		},
	})

	setDisplayTitle(frame, string.format("%s %s", game, resource))

	local html = mw.html.create()
	html:wikitext(getSubpages(game))
	html:wikitext(forminput)

	return html
end

return p