Module:Game

From IFWiki

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

local p = {}
local cargo = mw.ext.cargo

function arrangeIntoGroups(tableName, groupName)
	-- groupName is the relevant Cargo field name
	local groupedTable = {}
	for _, v in ipairs(tableName) do
	    local group = v[groupName]
	    if group then
	        if not groupedTable[group] then
	            groupedTable[group] = {} 
	        end
	        table.insert(groupedTable[group], v)
	    end
	end
	return groupedTable
end
	
function displayTime(num)
	local result = ""
	local hours = math.floor(num / 60)
	local minutes = num % 60
	if hours > 0 then 
		result = result .. hours
		if hours == 1 then
			result = result .. ' hour '
		else
			result = result .. ' hours '
		end
	end
	if minutes > 0 then
		result = result .. minutes
		if minutes == 1 then
			result = result .. ' minute'
		else
			result = result .. ' minutes'
		end
	end
	return result
end

function stringToTable(str)
	local result = {}
	if str then
		for word in string.gmatch(str, "([^,]+)") do
	    	table.insert(result, word:match("^%s*(.-)%s*$")) -- Trim spaces
		end
	end
	return result
end

function dateFormat ( dateString, precision, uncertain )
	-- Return date formatted nicely
	local year=dateString:sub(1,4)
	local month=dateString:sub(6,7)
	local day=dateString:sub(9,10)
	-- Format is YYYY-MM-DD
	if precision=='3' then
		-- Year only
		result = year
	elseif precision=='2' then
		-- Year and month only
		result = os.date('%B %Y', os.time({year=year, month=month, day=day}) )
	else
		-- Full date
		result = os.date('%d %B %Y', os.time({year=year, month=month, day=day}) )
		if result:sub(1,1) == '0' then
			result = result:sub(2) -- get rid of leading 0
		end
	end		
	if uncertain=='1' then
		result = result .. ' (uncertain)' 
	end
	return result
end


-----------------------------------------------------------------


function p.Main( frame )
	gf = require('Module:Game functions')
	cab = require('Module:Combined award boxes')
	
    local output = {}
    local categories = {}

	-- Easier to type
	function echo(text)
		table.insert(output, text)
	end
	
	function categorise(cat)
		-- Could be comma-separated list e.g. system
		catTable = stringToTable(cat)
		for _,v in ipairs(catTable) do
			table.insert(categories, v)
		end
	end
	
	function parchmentPlayable(filename)	
		-- Parchment can play these, except ADRIFT 5's blorb
		local extensions = { '.blb', '.blorb', '.taf', '.hex', '.gblorb', '.glb', '.ulx', '.gam', '.t3', '.zblorb', '.zlb', '.z3', '.z4', '.z5', '.z8' }
		for _, extension in ipairs(extensions) do
	        if filename:sub(-#extension) == extension and authoringSystem and not string.find(authoringSystem, 'ADRIFT 5', 1, true) then
	            return true
	        end
	    end
	    return false
	end		

	-- Get game (page) name
	local game = mw.title.getCurrentTitle().text

	-- Some plurals
	plural = {}
	plural['Author'] = 'Authors'
	plural['Porter'] = 'Porters'
	plural['Translator'] = 'Translators'
	plural['Artist'] = 'Artists'
	plural['Musician'] = 'Musicians'
	plural['Tester'] = 'Testers'
	plural['Distributor'] = 'Distributors'
	plural['Authoring system'] = 'Authoring systems'
	plural['Format'] = 'Formats'
	plural['System'] = 'Systems'
	plural['Literary genre'] = 'Literary genres'
	plural['Location'] = 'Locations'
	plural['Tag'] = 'Tags'
	plural['Language'] = 'Languages'

	-- Get game information from Cargo template parameters
	authoringSystem = frame:getParent().args['Authoring system']
	fileFormat = frame:getParent().args['File format']
	zCodeVersion = frame:getParent().args['Z code version']
	system = frame:getParent().args['System']
	oldInfoboxDate = frame:getParent().args['Old infobox date']
	interactionStyle = frame:getParent().args['Interaction style']
	literaryGenre = frame:getParent().args['Literary genre']
	location = frame:getParent().args['Location']
	tag = frame:getParent().args['Tag']
	series = frame:getParent().args['Series']
	otherTitle = frame:getParent().args['Other title']
	language = frame:getParent().args['Language']
	IFID = frame:getParent().args['IFID']
	licence = frame:getParent().args['Licence']
	cruelty = frame:getParent().args['Cruelty']
	accessibility = frame:getParent().args['Accessibility']
	cover = frame:getParent().args['Cover']
	IFArchive= frame:getParent().args['IF Archive']
	CASA = frame:getParent().args['CASA']
	playOnline = frame:getParent().args['Play online']
	download = frame:getParent().args['Download']
	sourceCode = frame:getParent().args['Source code']
	IFDB = frame:getParent().args['IFDB']
	IFDBIgnore = frame:getParent().args['IFDB ignore']
	release = frame:getParent().args['Release']
	externalLinks = frame:getParent().args['External links']
	mainArticleContent = frame:getParent().args['Main article content']

	-- IF Archive unbox for zip files
	if IFArchive 
	and ( IFArchive:lower():match('%.zip$') 
		or IFArchive:lower():match('%.tar%.gz$') 
		or IFArchive:lower():match('%.tar%.z$') 
		or IFArchive:lower():match('%.tgz$') 
		)
	and not IFArchive:match('^https://unbox%.ifarchive%.org') then
		IFArchiveUnbox = 'https://unbox.ifarchive.org/?url=' .. IFArchive
	end

	-- Get data from Games table (which are in addition to parameters)
	local tables = 'Games'
    local fields = 'TUID, Game_date, Game_date__precision, Game_date_uncertain'
    local args = {
    	where = '_pageName = "' .. game .. '"'
    }
    local results = cargo.query( tables, fields, args )
    if #results~=0 then
	    -- These are strings
		--IFDB = results[1]['IFDB']
		TUID = results[1]['TUID']
		gameDate = results[1]['Game_date']
		gameDatePrecision = results[1]['Game_date__precision']
		gameDateUncertain = results[1]['Game_date_uncertain']
    end	

	-- Convert some to tables to make things easier later
	TUIDTable = stringToTable(TUID)
	IFDBTable = stringToTable(IFDB)
	IFIDTable = stringToTable(IFID)

 	-- Get releases information from Game_releases table
    local tables = 'Game_releases'
	local fields = 'Date, Date__precision, Uncertain, Name, Notes' 
    local args = {
    	where = 'Game_releases._pageName="' .. game .. '"',
    	orderBy = '_rowID'
    }
    local results = cargo.query( tables, fields, args )
    releases = results

	-- Template:Game has already taken account of Corrected_date in setting Game_date
	if gameDate then
		-- Calculated by template from release information
		published = gameDate
		publishedPrecision = gameDatePrecision
		publishedUncertain = gameDateUncertain
	end

	-- Get all data from Game_people table
    local tables = 'Game_people'
	local fields = 'Name, Pseudonym, Role' 
    local args = {
    	where = 'Game_people._pageName="' .. game .. '"',
    	orderBy = '_rowID'
    }
    local gamePerson = cargo.query( tables, fields, args )

	-- Get all data from Game_companies table
    local tables = 'Game_companies'
	local fields = 'Name, Role' 
    local args = {
    	where = 'Game_companies._pageName="' .. game .. '"',
    }
    local gameCompany = cargo.query( tables, fields, args )

	-- Get all data from Game_crossreferences table
    local tables = 'Game_crossreferences'
	local fields = 'Relationship, Game' 
    local args = {
    	where = 'Game_crossreferences._pageName="' .. game .. '"',
    }
    local gameCrossreference = cargo.query( tables, fields, args )

    local tables = 'Game_crossreferences'
	local fields = '_pageName=Original, Relationship' 
    local args = {
    	where = 'Game_crossreferences.Game="' .. game .. '"',
    }
    local gameCrossreferencedBy = cargo.query( tables, fields, args )

	-- Get Events data from the Events and Awards tables
    local tables = 'Events, Awards'
    local fields = 'Events._pageName=Event, Part_of_series, Total, Award_name, Notes'
    local args = {
    	join = 'Events._pageID=Awards._pageID',
    	where = 'Game="' .. game .. '"'
    }
    local events = cargo.query( tables, fields, args )
    
    -- Get other games in series from Games and Game_releases tables
   	if series and series~='' then
		local tables = 'Games, Game_releases'
	    local fields = 'Games._pageName=Game'
	    local args = {
	    	join = 'Games._pageName = Game_releases._pageName',
	    	where = 'Series = "' .. series .. '"',
	    	groupBy = 'Games._pageName',
	    	orderBy = 'Series_order, Date',
	    }
	    seriesGames = cargo.query( tables, fields, args )
	end    

	-- If we have a TUID, then get some information from 1st IFDB link
	-- Unless IFDB ignore box ticked
	if TUIDTable and TUIDTable[1] and IFDBIgnore~='Yes' then
		local externalData, errors = mw.ext.externaldata.getWebData {
	    	url = 'https://ifdb.org/viewgame?ifiction&id=' .. TUIDTable[1] .. '&json', 
	    	format = 'JSON',
	    }
		local json = not errors and externalData.__json or false
		if json then
			-- Assume ifdb always present
			playTimeInMinutesExternal = json['ifdb']['playTimeInMinutes']
			starRatingExternal = json['ifdb']['starRating']
			ratingCountTotExternal = json['ifdb']['ratingCountTot']
			primaryPlayOnlineUrlExternal = json['ifdb']['primaryPlayOnlineUrl']
			if json['ifdb']['coverart'] then
				coverUrlExternal = json['ifdb']['coverart']['url']
			end
			if json['bibliographic'] then
				firstPublishedExternal = json['bibliographic']['firstpublished']
				authorExternal = json['bibliographic']['author']
			end
		end	
	end

	for i = 1, #IFIDTable do
		local externalData, errors = mw.ext.externaldata.getWebData {
	    	url = 'https://ifdb.org/viewgame?json&ifid=' .. mw.uri.encode(IFIDTable[i]), 
	    	format = 'JSON',
	    }
		local json = not errors and externalData.__json or false
		if json then
			IFIDTable[i] = '[https://ifdb.org/viewgame?ifid=' .. IFIDTable[i] .. ' ' .. IFIDTable[i] .. ']' 
		end
	end
	------------------------------------------------------------
	
	-- Begin infobox
	echo('<table class="wikitable ifwiki-infobox"> ')

	-- Game title
	content = mw.title.getCurrentTitle():getContent()
	displayTitle = content:match("{{DISPLAYTITLE:(.-)}}")
	if displayTitle then
		-- Use DISPLAYTITLE
		citationGame = displayTitle
	else
		-- Use page name, minus "(by...)" or "(game)"
		citationGame = string.gsub(game, "%(by [^)]+%)", "")
		citationGame = string.gsub(citationGame, "%(game%)", "")
	end
	echo(' <tr><th colspan="2" class="ifwiki-infobox-center"> ' )
	echo ( '<span style="font-size: 1.3em">' .. citationGame .. '</span>' )
	if otherTitle then
		echo '<br><span class="font-size: 0.85em">'
		echo ( '<i>a.k.a.</i> ' .. string.gsub(otherTitle, "%s*,%s*", " <i>or</i> ") )
		echo '</span>'
	end
	echo ( '</th></tr>' )

	-- "Game"
	echo('<tr><th colspan="2" class="ifwiki-infobox-center">Game</th></tr>')	
	
	-- Cover image
	if cover then
		echo('<tr><th colspan = "2">[[File:' .. cover .. '|class=ifwiki-infobox-image]]</th></tr>')
	else
		-- Not in IFWiki database so try IFDB
		if coverUrlExternal and coverUrlExternal~='' then
			echo('<tr><th colspan = "2">') 
			echo ( '<span class="plainlinks">[https://ifdb.org/viewgame?coverart&id=' .. TUID .. '&ldesc ' )
			echo(frame:callParserFunction( '#widget', { 'cover', url = coverUrlExternal } ) )
			echo ( ']</span>' )
			echo('</th></tr>')			
		end
	end

	-- Links
	if playOnline or primaryPlayOnlineUrlExternal or IFDB or IFArchive or CASA or download or sourceCode then
		echo ('<tr><th>Main links</th><td>')
		
		-- Display one Play online link, from our form field, or via IFDB, or via IF Archive
		if playOnline then
			echo ( '<span class="plainlinks">[' .. playOnline )
			echo ( '<span class="mw-ui-button mw-ui-progressive" style="margin: 5px" title="via IFWiki \'Play online\' link">' )
			echo ( 'Play online</span>]</span>' )
		elseif download and parchmentPlayable(download) then
			echo ( '<span class="plainlinks">[https://iplayif.com/?story=' .. download )
			echo ( '<span class="mw-ui-button mw-ui-progressive" style="margin: 5px" title="via IFWIki \'Download\' link">' )
			echo ( 'Play online</span>]</span>' )
		elseif IFArchive and parchmentPlayable(IFArchive) then
			echo ( '<span class="plainlinks">[https://iplayif.com/?story=' .. IFArchive )
			echo ( '<span class="mw-ui-button mw-ui-progressive" style="margin: 5px" title="via IF Archive link">' )
			echo ( 'Play online</span>]</span>' )
		elseif primaryPlayOnlineUrlExternal then
			echo ( '<span class="plainlinks">[' .. primaryPlayOnlineUrlExternal:gsub(' ', '%%20') )
			echo ( '<span class="mw-ui-button mw-ui-progressive" style="margin: 5px" title="via IFDB link">' )
			echo ( 'Play online</span>]</span>' )
		end
		
		-- Display one Download link, from our form field, or via IF Archive
		if download then
			echo ( '<span class="plainlinks">[' .. download )
			echo ( '<span class="mw-ui-button mw-ui-progressive" style="margin: 5px" title="via IFWiki link">Download</span>]</span>' )
		elseif IFArchive then
			echo ( '<span class="plainlinks">[' )
			echo ( IFArchiveUnbox or IFArchive or '' )
			echo ( '<span class="mw-ui-button mw-ui-progressive" style="margin: 5px" title="via IF Archive link">Download</span>]</span>' )
		end

		if sourceCode then
			echo ( '<span class="plainlinks">[' .. sourceCode )
			echo ( '<span class="mw-ui-button mw-ui-progressive" style="margin: 5px">Source code</span>]</span>' )
			categorise( 'Games with source code available' ) 
		end
		
		if IFDB or IFArchive or CASA then
			echo "<div>"
			-- Display text links to IFDB, IF Archive, CASA
			if IFDB then 
				for _,v in ipairs(IFDBTable) do
					echo ('[' .. v .. ' IFDB] ')
				end
			end
			if IFArchive then 
				echo '['
				echo ( IFArchiveUnbox or IFArchive or '' )
				echo ' IF Archive] '
			end
			if CASA then 
				echo ( '[' .. CASA .. ' CASA] ')
			end	
			echo "</div>"
		end
					
		echo ('</td></tr>')
	end

 	-- Published
	if published or oldInfoboxDate or (firstPublishedExternal and firstPublishedExternal~='') then
		echo('<tr><th>Published</th><td>')
	end
    if published then
		local year=published:sub(1,4)
		local month=published:sub(6,7)
		local day=published:sub(9,10)
		categorise( 'Works in ' .. year )
		if year >='1970' and year <='1979' then
			categorise ( 'Works in 1970s') -- existing category
		end
		-- Format is YYYY-MM-DD
		if publishedPrecision=='3' then
			-- Year only
			echo(year)
		elseif publishedPrecision=='2' then
			-- Year and month only
			published = os.date('%B %Y', os.time({year=year, month=month, day=day}) )
			echo (published)
		else
			-- Full date
			published = os.date('%d %B %Y', os.time({year=year, month=month, day=day}) )
			if published:sub(1,1) == '0' then
				published = published:sub(2) -- get rid of leading 0
			end
			echo(published)
		end		
		if publishedUncertain=='1' then
			echo(' (uncertain)' )
		end
    end
    
    -- This is relevant if conversion script couldn't work out what to do with the old 'released' date
    if oldInfoboxDate then
		echo( ' (Old infobox stated: "' .. oldInfoboxDate .. '")' )
	end

	-- If date not on IFWiki database then try IFDB
	if not published then
		if firstPublishedExternal and firstPublishedExternal~='' then
			if published or oldInfoboxDate then echo ('<br>') end
			echo('IFDB: ' .. firstPublishedExternal .. '*')
			usingExternal = true
		end
	end
	
	if published or oldInfoboxDate or (firstPublishedExternal and firstPublishedExternal~='') then
		echo('</td></tr>')
	end


 	-- Credits
 	if #gamePerson >= 1 or #gameCompany >= 1 then
	 	echo '<tr><th colspan="2" class="ifwiki-infobox-center">Credits</th></tr>'
	end

	function displayEntityRow(rolesTable, role, entityType)
		--Couldn't think of a better word than entities for people + companies
		--rolesTable is the table; role is a string for the left column
		--entityType is 'Person' or 'Company'
		entities = rolesTable[role]
		if entities then
			echo ("<tr><th>")
			if #rolesTable[role] > 1 and plural[role] then
				echo (plural[role])
			else 
				echo (role)
			end
			echo ("</th><td>")
			gf.displayEntities(entities, entityType) 
			echo "</td></tr>"		

			-- Some categorisation
			for i, entity in ipairs(entities) do
				if entity.Name == 'Infocom' then categorise( 'Infocom' ) end
			end
			if role == 'Translator' then categorise( 'Translated works (translations)' ) end
			if role == 'Porter' then categorise( 'Ported works (ports)' ) end				
			if role == 'Author' and #entities > 1 then
				categorise( 'Co-written works' )
			end
		end
	end
	
	-- People
	rolesTable = arrangeIntoGroups(gamePerson, 'Role')
	if rolesTable['Author'] ~= nil then
		displayEntityRow(rolesTable, 'Author', 'Person')
	else
		if authorExternal then
			-- No wiki data so try IFDB xxx
			echo ("<tr><th>Author(s)</th><td>")
			echo (authorExternal)
			echo ("*</td></tr>")
			usingExternal = true
		end
	end	
	if #gamePerson~=0 then
		displayEntityRow(rolesTable, 'Porter', 'Person')
		displayEntityRow(rolesTable, 'Translator', 'Person')
		displayEntityRow(rolesTable, 'Musician', 'Person')
		displayEntityRow(rolesTable, 'Artist', 'Person')
		-- And the rest, in the order they appear
		for r,v in pairs(rolesTable) do
			if r~='Author' and r~='Translator' and r~='Porter' and r~='Musician' and r~='Artist' then
				displayEntityRow(rolesTable, r, 'Person')
			end
		end
	end	

	-- Companies
	if #gameCompany~=0 then
		rolesTable = arrangeIntoGroups(gameCompany, 'Role')
		displayEntityRow(rolesTable, 'Development company', 'Company')
		displayEntityRow(rolesTable, 'Publisher', 'Company')
		--And the rest
		for r,v in pairs(rolesTable) do
			if r~='Development company' and r~='Publisher' then
				displayEntityRow(rolesTable, r, 'Company')
			end
		end	
	end

	--Reception
	if #events~=0 or starRatingExternal then
		echo '<tr><th colspan="2" class="ifwiki-infobox-center">Reception</th></tr>'
	end
	
	-- Events
	if #events~=0 then
		echo (cab.displayAwardBoxes(frame, game) )
	end

	if starRatingExternal then
		echo('<tr><th>IFDB&nbsp;rating</th><td>' .. starRatingExternal)
		if ratingCountTotExternal then
			echo(' out of 5 (' .. ratingCountTotExternal )
			if ratingCountTotExternal==1 then
				echo(' rating)')
			else 
				echo(' ratings)') 
			end
		end
		echo('</td></tr>')
	end
    
	function infoboxRow(field, fieldName, isLinked)
		-- Display infobox row
		-- So many special rules now maybe need separate functions
		fieldTable = stringToTable(field)
		if field then
			echo "<tr><th>"
			if #fieldTable>1 and plural[fieldName] then
				printFieldName = plural[fieldName]
			else 
				printFieldName = fieldName
			end	
			if fieldName == 'Literary genre' then
				echo ('[[IFWiki:Literary genres|' .. printFieldName .. ']]')
			elseif fieldName == 'Location' then
				echo ('[[IFWiki:Locations|' .. printFieldName .. ']]')
			elseif fieldName == 'Interaction style' then
				echo ('[[IFWiki:Interaction styles|' .. printFieldName .. ']]')
			elseif fieldName == 'Cruelty scale' then
				echo ('[[Cruelty scale|' .. printFieldName .. ']]')
			else
				echo (printFieldName)
			end
			echo "</th><td>"
			
			if field == location then
				-- Icons for location
				if location and location~='' then
					locationArray = mw.text.split(location, ',') 
					for s = 1, #locationArray do
						locationArray[s] = mw.text.trim(locationArray[s])
						categorise( locationArray[s] ) -- would be better to have "...games"
						echo(frame:expandTemplate{ title='Game icon', args={ locationArray[s] } } )
					end
				end				
			elseif field == literaryGenre then
				-- Icons for literary genre
				if literaryGenre and literaryGenre~='' then
					genreArray = mw.text.split(literaryGenre, ',')
					for g = 1, #genreArray do
						genreArray[g] = mw.text.trim(genreArray[g])
						categorise( genreArray[g] )
						echo(frame:expandTemplate{ title='Game icon', args={ genreArray[g] } } )
					end
				end	
			elseif field == interactionStyle then
				-- Icons for interaction style
				-- Parser, Choice, Parser-choice hybrid, Other
				if interactionStyle and interactionStyle~='' then
					interactionStyleArray = mw.text.split(interactionStyle, ',') 
					for s = 1, #interactionStyleArray do
						interactionStyleArray[s] = mw.text.trim(interactionStyleArray[s])
						categorise( interactionStyleArray[s] .. ' games')
						echo(frame:expandTemplate{ title='Game icon', args={ interactionStyleArray[s] } } )
					end
				end				
			else
				-- Just text for other fields
				for i,v in ipairs (fieldTable) do
					if i > 1 then echo ", " end
					if fieldName=='Format' and v=='Z-code' and zCodeVersion then
						echo ( zCodeVersion:gsub("%s*,%s*", "]], [["):gsub("^", "[["):gsub("$", "]]") )
					elseif fieldName=='Format' and v=='Other' and fileFormatOther then
						echo ('[[' .. fileFormatOther .. ']]')
					elseif fieldName == 'IFID' then
						-- Already will be IFDB link if the IFID is known there
						-- Maybe should display this row separately
						echo (IFIDTable[i])
					elseif fieldName == 'Tag' then
						echo ('<span class="plainlinks">[' .. mw.site.server .. '/Special:Drilldown/Games?Tag=' .. mw.uri.encode(v) .. ' ' .. v .. ']</span>')
					elseif fieldName == 'Cruelty scale' then
						echo ( '[[:Category:' .. v .. ' works|' .. v .. ']]' )
					elseif isLinked then
						echo ( '[[' .. v .. ']]')
					else
						echo (v)
					end
				end
			end
			echo "</td></tr>"
		end
	end

	-- Gameplay
	if interactionStyle or literaryGenre or location or tag or language or cruelty or playTimeInMinutesExternal or accessibility then
		echo '<tr><th colspan="2" class="ifwiki-infobox-center">Gameplay</th></tr>'
	
		infoboxRow(interactionStyle, 'Interaction style', true)
		infoboxRow(literaryGenre, 'Literary genre', true)
		infoboxRow(location, 'Location', true)
		infoboxRow(tag, 'Tag', true)
		infoboxRow(language, 'Language', true)
		if language then
			categorise( language:gsub("%s*,%s*", " works,") .. " works" )
		end	
		infoboxRow(cruelty, 'Cruelty scale', true)
		if cruelty then
			categorise( cruelty .. ' works' )
		end
		if playTimeInMinutesExternal then 
			echo('<tr><th>IFDB&nbsp;play&nbsp;time</th><td>' .. displayTime(playTimeInMinutesExternal) .. '</td></tr>')
		end
		infoboxRow(accessibility, 'Accessibility', false)
	end

	-- Technical details
	if authoringSystem or fileFormat or zCodeVersion or system or licence or IFID then
		echo '<tr><th colspan="2" class="ifwiki-infobox-center">Technical details</th></tr>'
		infoboxRow(authoringSystem, 'Authoring system', true)
		infoboxRow(fileFormat, 'Format', true)
		infoboxRow(system, 'System', true)

		if licence then
			local linked_licences = {
				["Freeware"] = true,
			    ["Commercial"] = true,
			    ["Former commercial"] = true,
			    ["Creative Commons"] = true,
			    ["Shareware"] = true,
			    ["GPL"] = true,
			    ["Open Source IF"] = true
			}
			echo ( '<tr><th>[[Licence|License]]</th><td>' )
			if linked_licences[licence] then
				echo ( '[[' .. licence .. ']]' )
			else 
				echo ( licence )
			end			
			echo ( '</td></tr>' )
		end		

		infoboxRow(IFID, 'IFID', false)
		if licence=='Commercial' then
			-- Maybe need to add other licences here
			categorise( 'Commercial works' )
		end
		if authoringSystem then
			categorise( authoringSystem:gsub("%s*,%s*", " works,") .. " works" )
		end
		if fileFormat then
			categorise( fileFormat:gsub("%s*,%s*", " works,") .. " works" )
		end
		if fileFormatOther then
			categorise( fileFormatOther:gsub("%s*,%s*", " works,") .. " works" )
		end
		if system then
			categorise( system:gsub("%s*,%s*", " works,") .. " works" )
		end
	end

	-- Encouragement to confirm IFDB data
	if usingExternal then
		echo '<tr><td colspan="2" style="text-align: center"><i>'
		echo '* The asterisked content is taken directly from IFDB. If it is accurate, please edit this page and it to our database.'
		echo '</i></td></tr>'
	end

	-- Final part of infobox
	echo('<tr><td colspan="2" style="text-align: center"><i>[[Special:Drilldown/Games|Browse the games database]]')
	echo(' &bull; [[Special:FormEdit/Game/' .. game .. '|Edit this page]]</i></td></tr>')

	-- End infobox
	echo('</table>')
	
	-- Main article content
	if mainArticleContent and mainArticleContent~='' then
		echo ( '\n' .. mainArticleContent)
	end
	
	-- Image gallery
	-- Ideally move to Lua
	echo( frame:expandTemplate{ title='Game image gallery' } )
	
	-- "Versions" heading for releases
	if release and release~='' then
		echo ( '\n==Versions==\n' )
		for i = 1, #releases do
			if releases[i].Name then
				echo ( '\n===' .. releases[i].Name .. '===\n')
			else
				echo ( '\n===Unnamed release===\n' )
			end
			echo ( "<p>'''Date:''' ")
			if releases[i].Date then
				echo ( dateFormat ( releases[i].Date, releases[i].Date__precision, releases[i].Uncertain ) )
			else
				echo ( 'Date unknown' )
			end
			echo ( ' </p>' )
			if releases[i].Notes then
				echo ('\n' .. releases[i].Notes)
			end
		end
	end

	-- "Related games" heading
	if series or #gameCrossreference>0 or #gameCrossreferencedBy>0 then
		echo '\n==Related games==\n'
	end

	-- Series
	if series and series~='' then
		echo('<ul><li>Part of "' .. series .. '": <ul>')
   		for _,g in ipairs(seriesGames) do
   			g = g['Game']
   			echo '<li>'
   			echo(frame:expandTemplate{ title='Game citation', args={ game=g } } )
    		echo '.</li>'
    	end	 
		echo('</ul></li></ul>')
	end

	-- Crossreferenes from the game's form
	if #gameCrossreference>0 then
		relationshipsTable = arrangeIntoGroups(gameCrossreference, 'Relationship')
		for relationship, crossreferences in pairs(relationshipsTable) do
			echo '<ul><li>'
			if #relationshipsTable[relationship] > 1 and plural[relationship] then
				echo (plural[relationship])
			else 
				echo (relationship)
			end
			multiple = #relationshipsTable[relationship] > 1
			if relationship=='Port of' then
				categorise ('Ported works (ports)')	
			end
			if relationship=='Translation of' then
				categorise ('Translated works (translations)')
			end
			if multiple then echo ' the following:<ul>' else echo ' ' end
		    for i, crossreference in ipairs(crossreferences) do
		    	if crossreference.Game then
		    		if multiple then echo '<li>' end
		    		echo(frame:expandTemplate{ title='Game citation', args={ game=crossreference.Game } } )
					echo '.'		    		
		    		if multiple then echo '</li>' end
				else 
					echo "Unknown" -- shouldn't happen
		    	end
		    end
		    if multiple then echo '</ul>' end
		    echo '</li></ul>'
		end
	end
	
	-- Has this game been cross-referenced by any others?
	-- Ideally would merge and order the two tables and deal with together
	if #gameCrossreferencedBy>0 then
		relationshipsTable = arrangeIntoGroups(gameCrossreferencedBy, 'Relationship')
		for relationship, crossreferences in pairs(relationshipsTable) do
			echo ("<ul><li>")
			if relationship=='Port of' then echo 'Ported as'
			elseif relationship=='Translation of' then echo 'Translated in'
			elseif relationship=='Makes reference to' then echo 'Referred to in'
			elseif relationship=='Prequel to' then echo 'Followed by sequel'
			elseif relationship=='Sequel to' then echo 'Followed by prequel'
			elseif relationship=='Adapted from' then echo 'Adapted as'
			elseif relationship=='Remake of' then echo 'Remade as'
			elseif relationship=='Inspired by' then echo 'Inspired'
			elseif relationship=='Spoof of' then echo 'Spoofed by'
			end
			multiple = #relationshipsTable[relationship] > 1		
			if relationship=='Ported as' then
				categorise ('Ported works (original)')	
			end
			if relationship=='Translation of' then
				categorise ('Translated works (original)')
			end
			if multiple then echo ' the following:<ul>' else echo ' ' end
		    for i, crossreference in ipairs(crossreferences) do
		    	if crossreference.Original then
		    		if multiple then echo '<li>' end
		    		echo(frame:expandTemplate{ title='Game citation', args={ game=crossreference.Original } } )
					echo '.'		    		
		    		if multiple then echo '</li>' end
				else 
					echo "Unknown" -- shouldn't happen
		    	end
		    end
			if multiple then echo '</ul>' end
			echo '</li></ul>'
		end
	end

	-- Old series box (will remove when all game pages created)
	if series and series~='' and mw.title.new(series, "Template").exists then
		echo( frame:expandTemplate{ title=series } )
	end

	-- "Links" section
	if externalLinks and externalLinks~='' then
		echo ( '\n==Links==\n' )
		
		-- Temporary warning message about OSA 2023 UK geofence
		if IFArchive 
		or (download and download:find('ifarchive.org', 1, true)) 
		or (playOnline and playOnline:find('ifarchive.org', 1, true)) 
		or (primaryPlayOnlineUrlExternal and primaryPlayOnlineUrlExternal:find('ifarchive.org', 1, true)) 
		then
			echo ( '<i>N.B. Many IF Archive games are temporarily unavailable in the UK.</i>\n\n' )
		end

		echo ( externalLinks )
	end

	-- Adventure
	if game:match(' %(%w%w%w%w%d%d%d%d%)$') or game:match(' %(%w%w%w%w_XXX%)$') then
		echo "\n"
		echo(frame:expandTemplate{ title='Adventure navbox' } )
	end
	
	-- Note about citation
	echo '\n<div class="mw-message-box" style="margin-top: 1em; font-size: 0.85em; clear: both; background-color: inherit">'
	echo '<p>Note: To refer to this game from another page, you can type '
	echo ( '<code>{{game citation|' .. game .. '}}</code>. ' )
	echo 'This will display as '
	echo(frame:expandTemplate{ title='Game citation', args={ game=game } } )
	echo '.</div>'
	
	-- Final categorisation
    categorise( 'Works' )
	for _, category in ipairs(categories) do
		echo ('[[Category:' .. category .. ']]')
	end

    output = table.concat(output)
    return output
end

return p