Module:ScriptItem2: Difference between revisions

From GTA Connected
Jump to navigation Jump to search
No edit summary
No edit summary
Line 276: Line 276:
function p.gamesBox(frame)
function p.gamesBox(frame)
local args = p.getArgs(frame)
local args = p.getArgs(frame)
local gamesText = p.getGames(args.games)
local gamesText = p.getGames(args.games)
if gamesText == "All Games" then
 
local games2 = { 'iii', 'vc', 'sa', 'iv' }
local counter = 0
for i2,game2 in ipairs(games2) do
if gamesText:lower():find(game2) then
counter = counter + 1
end
end
 
if counter == #games2 or gamesText == "All Games" then
return p.loadTemplate(frame, 'GreenInformationBox', {table.concat({
return p.loadTemplate(frame, 'GreenInformationBox', {table.concat({
p.loadTemplate(frame, 'icon-iii'),
p.loadTemplate(frame, 'icon-iii'),

Revision as of 02:56, 18 November 2022

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

local p = {}

-- settings
p.categoryItems = {
	-- Category Name		= table. (Fully resolved item name(s), case-sensitive.) (Event names must start with a capital letter "O", to match the dumpdoc XML.)
	['Audio']				= {'gta.addOneOffSound', 'gta.playFrontEndSound', 'gta.playSuspectLastSeen'},
	['Camera']				= {'gta.fading'},
	['Chatbox']				= {'message', 'messageClient', 'messageAllExcept', 'setChatWindowEnabled', 'OnChatOutput'},
	['Cursor']				= {'gta.getMouseSpeed'},
	['CustomText']			= {'gta.setCustomText', 'gta.setCustomText', 'gta.clearCustomText', 'gta.printBig'},
	['Drawing']				= {'gta.drawRectangle'},
	['Effect']				= {'gta.addParticleEffect', 'addParticleEffect', 'gta.addMovingParticleEffect', 'addMovingParticleEffect', 'gta.createSingleParticle', 'createSingleParticle'},
	['Font']				= {'font.render'},
	['GarbageCollection']	= {'collectAllGarbage'},
	['Game']				= {'gta.game', 'gta.standardControls', 'gta.gameSpeed', 'gta.loadDFF', 'gta.loadTXD', 'gta.loadCOL'},
	['GameStat']			= {'gta.setGameStat', 'gta.getGameStat'},
	['GUI']					= {'OnGUIClick'},
	['Key']					= {'isScancodePressed', 'OnCharacter'},
	['KillFrenzy']			= {'gta.startKillFrenzy'},
	['Message']				= {'gta.clearMessages', 'gta.smallMessage', 'gta.bigMessage', 'gta.pagerMessage'},
	['Miscellaneous']		= {'platform', 'toColour', 'gta.tickCount', 'getRandomSkin', 'gta.getRandomSkin', 'gta.processLineOfSight', 'processLineOfSight', 'gta.setIsland', 'gta.setDefaultInteriors'},
	['Network']				= {'isConnected', 'isConnecting', 'connect', 'disconnect', 'triggerNetworkEvent', 'OnDisconnect'},
	['Path']				= {'gta.trafficDensity', 'gta.setTrafficEnabled', 'setTrafficEnabled', 'gta.setCiviliansEnabled', 'setCiviliansEnabled'},
	['Ped']					= {'gta.tommyFatness'},
	['Radio']				= {'gta.setRadioChannel', 'gta.forceRadioChannel'},
	['Reflection']			= {'exportFunction', 'setErrorMode'},
	['Renderware']			= {'gta.rwRenderStateSet'},
	['SaveGame']			= {'gta.saveGame'},
	['Screen']				= {'gta.aspectRatio', 'gta.width', 'gta.height'},
	['Script']				= {'gta.terminateScript', 'gta.getActiveScripts', 'gta.scriptCommand'},
	['Timer']				= {'setImmediate', 'clearImmediate', 'setInterval', 'clearInterval', 'setTimeout', 'clearTimeout'},
	['Weather']				= {'snowing', 'forceSnowing'},
	['Window']				= {'OnFocus', 'OnLostFocus'},
	['World']				= {'gta.ssvBridgeEnabled', 'gta.planesEnabled', 'gta.trainsEnabled', 'setPlanesEnabled', 'setTrainsEnabled', 'world', 'gta.findGroundZForCoord', 'findGroundZForCoord', 'gta.setTrainsEnabled', 'gta.setPlanesEnabled'}
}

p.categoryWords = {
	-- Category Names. (String(s) to look for in an item name, case-INsensitive.)
	'Blip', 'Building', 'Camera', 'Chatbox', 'Cheat', 'Client', 'Command', 'Cursor', 'Debug', 'Dodo',
	'Element', 'Entity', 'Event', 'File', 'Font', 'Garage', 'HTTP', 'HUD', 'Key', 'Mission', 'Mouse', 'Network', 'Object',
	'Pause', 'Ped', 'Pickup', 'Player', 'Process', 'Render', 'Resource', 'Screen', 'Socket', 'Sphere', 'Time', 'Vehicle', 'Weather', 'World'
}

-- entry point
function p.main(frame)
	local args = p.getArgs(frame)
	
	if args.type == 'variable' then
		return p.showVariablePage(frame)
	elseif args.type == 'function' then
		return p.showFunctionPage(frame)
	elseif args.type == 'property' then
		return p.showPropertyPage(frame)
	elseif args.type == 'method' then
		return p.showMethodPage(frame)
	elseif args.type == 'event' then
		return p.showEventPage(frame)
	else
		return "This page has been documented incorrectly, '''type''' is missing."
	end
end

-- item page types
function p.showVariablePage(frame)
	return p.showPage(frame, { "Value" })
end

function p.showFunctionPage(frame)
	return p.showPage(frame, { "Parameters", "Return", "Callbacks" })
end

function p.showPropertyPage(frame)
	return p.showPage(frame, { "Value" })
end

function p.showMethodPage(frame)
	return p.showPage(frame, { "Parameters", "Return", "Callbacks" })
end

function p.showEventPage(frame)
	return p.showPage(frame, { "Parameters", "Attributes" })
end

-- item page
function p.showPage(frame, parts)
	local args = p.getArgs(frame)
	if args.type ~= 'event' then
		table.insert(parts, "Types")
	end
	--table.insert(parts, "Symbols")
	local lines = p.getPageTopLines(frame)
	for i,part in ipairs({"Notes", "Examples", "Compatibility", "Related"}) do
		table.insert(parts, part)
	end
	for i,part in ipairs(parts) do
		local data = p[part:lower() == 'return' and 'returns' or part:lower()](frame)
		if data and #data > 0 then
			if part == 'Settable' then
				part = args.readonly == 'true' and 'Read-Only' or 'Read and Set'
			end
			table.insert(lines, '== '..part..' ==')
			table.insert(lines, data)
		end
	end
	return table.concat(lines, "\n")
end

function p.getPageTopLines(frame)
	local lines = {}
	table.insert(lines, frame:preprocess("{{DISPLAYTITLE:"..p.getDisplayedName(frame).."}}"))
	table.insert(lines, "__NOTOC__"..p.getPageTopBoxes(frame).."\n")
	table.insert(lines, "<div style='margin-top: 20px;'>Available since "..p.since(frame).."</div>\n")
	table.insert(lines, "<div style='margin-top: 6px;'>\n"..p.syntax(frame).."</div>\n")
	table.insert(lines, "<div style='margin-top: 20px;'>\n"..p.description(frame).."</div>\n")
	return lines
end

function p.getPageTopBoxes(frame)
	local args = p.getArgs(frame)
	
	local topBoxes = { "type", "side", "games", "online" }
	if args.type == 'variable' or args.type == 'property' then
		table.insert(topBoxes, "settable")
	elseif args.type == 'event' then
		table.insert(topBoxes, "cancellable")
	end
	
	local outBoxes = {}
	for i,topBox in ipairs(topBoxes) do
		table.insert(outBoxes, p[topBox.."Box"](frame))
	end
	return table.concat(outBoxes, ' ').."<br>"
end

-- template arguments
function p.getArgs(frame)
	if p.argsCache then
		return p.argsCache
	else
		local args = frame:getParent().args
		p.copyOldToNewForTemplateArguments(frame, args)
		p.copyNonIndexedToIndexedTemplateArguments(frame, args)
		p.populateTypeSpecificTemplateArguments(frame, args)
		p.populateDefaultTemplateArguments(frame, args)
		p.argsCache = args
		return args
	end
end

function p.copyOldToNewForTemplateArguments(frame, args)
	--[[
	if args.parameters then
		args.syntax = args.parameters
	end
	if args.parametersSS then
		args.syntaxSS = args.parametersSS
	end
	if args.parametersCS then
		args.syntaxCS = args.parametersCS
	end
	
	if args.callbackParameters then
		args.callbackSyntax = args.callbackParameters
	end
	if args.callbackParametersSS then
		args.callbackSyntaxSS = args.callbackParametersSS
	end
	if args.callbackParametersCS then
		args.callbackSyntaxCS = args.callbackParametersCS
	end
	
	if args.type == 'event' then
		if args.callbackParameters then
			args.syntax = args.callbackParameters
		end
		if args.callbackParametersSS then
			args.syntaxSS = args.callbackParametersSS
		end
		if args.callbackParametersCS then
			args.syntaxCS = args.callbackParametersCS
		end
	end

	if args.returnType or args.returnTypes then
		local returnType = args.returnType and args.returnType or args.returnTypes
		if args.returnInfo then
			args.return1 = returnType.." "..args.returnInfo
			args.value = returnType.." "..args.returnInfo
		else
			args.return1 = returnType
			args.value = returnType
		end
	end
	]]
	
	if args.usage then
		args.desc = args.usage
	end
	
	if args.notes then
		local notes = p.splitLines(args.notes)
		for i,note in ipairs(notes) do
			args['note'..i] = note
		end
	end
	
	local exampleKeys = {
		'example', 'exampleSS', 'exampleCS', 'exampleJS', 'exampleLua', 'exampleSquirrel',
		'exampleJSSS', 'exampleJSCS', 'exampleLuaSS', 'exampleLuaCS', 'exampleSquirrelSS', 'exampleSquirrelCS'
	}
	for i,exampleKey in ipairs(exampleKeys) do
		if args[exampleKey] then
			local examples = p.split(args[exampleKey], '<br>')
			for i,example in ipairs(examples) do
				args[exampleKey..i] = example
			end
		end
	end
	
	if args.bcName and args.bcMaxVersion then
		args.previous1 = args.bcName.." "..args.bcMaxVersion
	end
end

function p.copyNonIndexedToIndexedTemplateArguments(frame, args)
	if args.description then
		args.description1 = args.description
	end
end

function p.populateTypeSpecificTemplateArguments(frame, args)
	if args.type == 'variable' or args.type == 'property' then
		if args.value then
			local tokens = p.split(args.value, ' ')
			local type = tokens[1]
			
			local name
			if args.type == 'property' then
				name = args.class.."."..args.name
			else
				name = args.name
			end
			
			args.syntax = type.." "..name
		end
	end
end

function p.populateDefaultTemplateArguments(frame, args)
	if not args.since then
		args.since = "1.0.0 1.0.0"
	end
	
	if args.syntax and args.syntax:lower() == "void" then
		args.parameter1 = "void"
	end
end

-- item top boxes
function p.typeBox(frame)
	local args = p.getArgs(frame)
	local typeText = p.getType(args.type)
	return p.loadTemplate(frame, 'GreenInformationBox', {typeText,id='type'});
end

function p.sideBox(frame)
	local args = p.getArgs(frame)
	local sideText, isShared = p.getSide(args.side)
	if isShared then
		return p.loadTemplate(frame, 'GreenInformationBox', {sideText,id='side'});
	else
		return p.loadTemplate(frame, 'RedInformationBox', {sideText,id='side'});
	end
end

function p.gamesBox(frame)
	local args = p.getArgs(frame)

	local gamesText = p.getGames(args.games)

	local games2 = { 'iii', 'vc', 'sa', 'iv' }
	local counter = 0
	for i2,game2 in ipairs(games2) do
		if gamesText:lower():find(game2) then
			counter = counter + 1
		end
	end

	if counter == #games2 or gamesText == "All Games" then
		return p.loadTemplate(frame, 'GreenInformationBox', {table.concat({
			p.loadTemplate(frame, 'icon-iii'),
			p.loadTemplate(frame, 'icon-vc'),
			p.loadTemplate(frame, 'icon-sa'),
			p.loadTemplate(frame, 'icon-iv')
		}, ' '), id='games'})
	else
		local games = { 'iii', 'vc', 'sa', 'iv' }
		local out = {}
		for i,game in ipairs(games) do
			if gamesText:lower():find(game) then
				table.insert(out, p.loadTemplate(frame, 'icon-'..game, {}))
			end
		end
		return p.loadTemplate(frame, 'RedInformationBox', {table.concat(out, ' '), id='games'})
	end
end

function p.onlineBox(frame)
	local args = p.getArgs(frame)
	if args.sp == 'true' or args.online == 'false' then
		return p.loadTemplate(frame, 'RedInformationBox', {'Offline Only',id='online'});
	elseif args.sp == 'false' or args.offline == 'false' then
		return p.loadTemplate(frame, 'RedInformationBox', {'Online Only',id='online'});
	else
		return p.loadTemplate(frame, 'GreenInformationBox', {'Online and Offline',id='online'});
	end
end

function p.settableBox(frame)
	local args = p.getArgs(frame)
	if args.readonly == 'true' then
		return p.loadTemplate(frame, 'RedInformationBox', {'Read-Only',id='settable'});
	else
		return p.loadTemplate(frame, 'GreenInformationBox', {'Read and Set',id='settable'});
	end
end

function p.cancellableBox(frame)
	local args = p.getArgs(frame)
	if p.isCancellable(frame)then
		return p.loadTemplate(frame, 'GreenInformationBox', {'Cancellable',id='cancellable'});
	else
		return p.loadTemplate(frame, 'RedInformationBox', {'Not Cancellable',id='cancellable'});
	end
end

-- item parts
function p.since(frame)
	local args = p.getArgs(frame)
	if not args.since then
		return p.documentationMissing(frame, "Available since")
	else
		local sideVersions = p.getSideVersions(args.since, args.side)
		if #sideVersions == 2 then
			return "'''Server "..sideVersions[1].."''', '''Client "..sideVersions[2].."'''"
		else
			if args.side == 'server' then
				return "'''Server "..sideVersions[1].."'''"
			elseif args.side == 'client' then
				return "'''Client "..sideVersions[1].."'''"
			else
				return p.documentationMissing(frame, "Available since (Both server and client version is needed for shared items)")
			end
		end
	end
end

function p.syntax(frame)
	--[[
	local args = p.getArgs(frame)
	
	if args.syntax then
		return p.loadTemplate(frame, 'CodeSyntax', {p.buildSyntax(frame, args.syntax),id='syntax'})
	end
	
	if args.syntaxSS and args.syntaxCS then
		local rows = {}
		rows[1] = { "'''Server:'''", p.loadTemplate(frame, 'CodeSyntax', {p.buildSyntax(frame, args.syntaxSS),id='syntaxss'}) }
		rows[2] = { "'''Client:'''", p.loadTemplate(frame, 'CodeSyntax', {p.buildSyntax(frame, args.syntaxCS),id='syntaxcs'}) }
		return p.table(false, rows, { 100 }, false)
	end
	
	return p.documentationMissing(frame, "Syntax")
	]]

	local args = p.getArgs(frame)

	local returnType
	local argsText = {}

	do
		local entries = p.getIndexedArgs(frame, 'return')

		if #entries == 0 then
		else
			local tokens = p.split(entries[1], ' ')
			
			returnType = tokens[1]
			returnType = p.standardizeNullType(returnType)	
		end
	end

	do
		local entries = p.getIndexedArgs(frame, 'arg')
		local isEvent = args.type == 'event'

		if isEvent and args.syntax and args.syntax:lower() == 'event event' then
		elseif #entries == 0 then
		elseif entries[1] == "void" then
			table.insert(argsText, 'void')
		else
			for i,arg in ipairs(entries) do
				local tokens = p.split(arg, ' ')
				
				local isOptional = tokens[1]:sub(1, 1) == '['
				if isOptional then
					tokens[1] = tokens[1]:sub(2, tokens[1]:len())
					tokens[4] = tokens[4]:sub(1, tokens[4]:len() - 1)
				end
				local defaultValue = isOptional and tokens[4] or "n/a"
				
				if isOptional then
					table.insert(argsText, '[ '..tokens[1]..' '..tokens[2]..' = '..defaultValue..' ]')
				else
					table.insert(argsText, tokens[1]..' '..tokens[2])
				end
			end
		end
	end

	return p.loadTemplate(frame, 'CodeSyntax', {
		returnType
		..' '..p.getDisplayedName(frame)
		..'('..(#argsText == 0 and 'void' or table.concat(argsText, ', ')..')')
	})
end

function p.description(frame)
	local args = p.getArgs(frame)
	local entries = p.getIndexedArgs(frame, 'desc')
	
	local startTextFirstLine = "The <span style=\"font-family: 'Source Code Pro', monospace;\">"..p.getDisplayedNameColoured(frame).."</span> [[HowTo/Functions|"..args.type.."]] is "..(args.type == "event" and "invoked when" or "used to").." "
	
	for i,entry in ipairs(entries) do
		if i == 1 then
			entry = startTextFirstLine..entry
		end
		
		entries[i] = p.formatDescription(entry)
	end
	return table.concat(entries, '<br>')
end

function p.parameters(frame)
	local args = p.getArgs(frame)
	local entries = p.getIndexedArgs(frame, 'arg')
	local rows = {}
	local isEvent = args.type == 'event'
	
	if isEvent and args.syntax and args.syntax:lower() == 'event event' then
	elseif #entries == 0 then
		return p.documentationMissing(frame, "Parameters")
	elseif entries[1] == "void" then
		local headers = nil
		local rows = { { p.formatType("void", frame), "This "..args.type.." doesn't take any parameters." } }
		local widths = { 120, 400 }
		return p.table(headers, rows, widths, 'wikitable-returns wikitable')
	else
		for i,arg in ipairs(entries) do
			local tokens = p.split(arg, ' ')
			
			local isOptional = tokens[1]:sub(1, 1) == '['
			if isOptional then
				tokens[1] = tokens[1]:sub(2, tokens[1]:len())
				tokens[4] = tokens[4]:sub(1, tokens[4]:len() - 1)
			end
			local defaultValue = isOptional and "<span style='font-family: \"Source Code Pro\", monospace;'>"..tokens[4].."</span>" or "n/a"
			local description = p.formatDescription(table.concat(tokens, ' ', isOptional and 5 or 3))
			
			if isEvent then
				rows[i] = { (i + 1)..") ", p.formatType(tokens[1], frame), p.formatName(tokens[2], frame), description }
			else
				rows[i] = { i..") ", p.formatType(tokens[1], frame), p.formatName(tokens[2], frame), (isOptional and ("Optional, defaults to "..defaultValue..". ") or "")..description }
			end
		end
	end
	
	if isEvent then
		table.insert(rows, 1, { "1) ", p.formatType("Event", frame), p.formatName("event", frame), "The event object for this event." })
	end
	
	local headers, widths
	if isEvent then
		headers = nil--{ "Type", "Name", "Description" }
		widths = { 30, 80, 110, 400 }
	else
		headers = nil--{ "Type", "Name", "Presence", "Default Value", "Description" }
		widths = { 30, 80, 110, 400 }
	end
	return p.table(headers, rows, widths, 'wikitable-parameters wikitable')
end

function p.returns(frame)
	local args = p.getArgs(frame)
	local text = ""
	local entries
	local rows
	local headers
	local widths
	local isVoidReturn = false
	
	rows = {}
	entries = p.getIndexedArgs(frame, 'return')
	if #entries == 0 then
		text = text..p.documentationMissing(frame, "Returns").."<br><br>"
	else
		for i,arg in ipairs(entries) do
			local tokens = p.split(arg, ' ')
			
			local type = tokens[1]
			type = p.standardizeNullType(type)
			
			local description
			if type:lower() == "void" then
				isVoidReturn = true
				description = "This "..args.type.." doesn't return a value."
			elseif tokens[2] then
				description = p.formatDescription(table.concat(tokens, ' ', 2))
			else
				description = p.documentationMissing(frame, "Description for return value")
			end
			
			rows[i] = { p.formatType(tokens[1], frame), description }
		end
		headers = nil-- { "Type", "Description" }
		widths = { 120, 400 }
		text = text..p.table(headers, rows, widths, 'wikitable-returns wikitable')
	end
	
	--[[
	-- commented out as failure returns are not currently implemented in GTAC.
	if not isVoidReturn then
		rows = {}
		entries = p.getIndexedArgs(frame, 'freturn')
		text = text.."'''Failure Return:'''\n\n"
		if #entries == 0 then
			text = text..p.documentationMissing(frame, "Failure returns")
		else
			for i,arg in ipairs(entries) do
				local tokens = p.split(arg, ' ')
				
				local type = tokens[1]
				type = p.standardizeNullType(type)
				
				local value
				if type:lower() == 'void' then
					value = "n/a"
				elseif tokens[2] then
					value = p.formatSource(table.concat(tokens, ' ', 2), frame)
				else
					value = p.formatSource(p.documentationMissing(frame, "Description for failure return value"), frame)
				end
				
				rows[i] = { p.formatType(tokens[1], frame), value }
			end
			headers = nil--{ "Type", "Value" }
			widths = { 120, 400 }
			text = text..p.table(headers, rows, widths, 'wikitable-returns wikitable')
		end
	end
	]]
	
	return text
end

function p.callbacks(frame)
	local args = p.getArgs(frame)
	
	local suffixes = {
		{ '', '' },
		{ 'SS', 'Server-Side Callbacks' },
		{ 'CS', 'Client-Side Callbacks' }
	}
	
	local html = {}
	
	for i3,suffixData in ipairs(suffixes) do
		local suffix = suffixData[1]
		
		local parameters = p.getIndexedArgs(frame, 'arg')
		for i=1, #parameters do
			local callbackSyntax = args['cb'..i..'Syntax'..suffix]
			local callbackText = args['cb'..i..'Text'..suffix]
			local callbackNParameters = p.getIndexedArgs(frame, 'cb'..i..'arg'..suffix)
			
			if #callbackNParameters > 0 then
				if suffixData[1] ~= '' then
					table.insert(html, "'''"..suffixData[2].."'''")
				end
				
				local parameterTokens = p.split(parameters[i], ' ')
				local rows = {}
				
				if #parameterTokens > 0 then
					local syntaxLine = parameterTokens[1]..' '..parameterTokens[2]..'('..callbackSyntax..')'
					syntaxLine = p.loadTemplate(frame, 'CodeSyntax', {syntaxLine})
					table.insert(html, syntaxLine)
				end
				
				if callbackText and #callbackText > 0 then
					table.insert(html, callbackText)
				end
				
				for i2,callbackNParameter in ipairs(callbackNParameters) do
					local callbackTokens = p.split(callbackNParameter, ' ')
					
					local parameterType = callbackTokens[1]
					local parameterName = callbackTokens[2]
					local description = p.formatDescription(table.concat(callbackTokens, ' ', 3))
					
					rows[i2] = { i2..') ', p.formatType(parameterType, frame), p.formatName(parameterName, frame), description }
				end
				
				local headers, widths
				headers = nil--{ "Type", "Name", "Description" }
				widths = { 30, 80, 110, 400 }
				local table2 = "\n"..p.table(headers, rows, widths, 'wikitable-callback wikitable')
				table.insert(html, table2)
				
				if i ~= #parameters then
					table.insert(html, "<br>")
				end
			end
		end
	end
	
	if args.callbackSyntax then
		table.concat(html, p.loadTemplate(frame, 'CodeSyntax', {p.buildSyntax(frame, args.callbackSyntax, true),id='callbacks'}))
	end
	
	if args.callbackSyntaxSS and args.callbackSyntaxCS then
		local rows = {}
		rows[1] = { "'''Server:'''", p.loadTemplate(frame, 'CodeSyntax', {p.buildSyntax(frame, args.callbackSyntaxSS, true),id='callbacks'}) }
		rows[2] = { "'''Client:'''", p.loadTemplate(frame, 'CodeSyntax', {p.buildSyntax(frame, args.callbackSyntaxCS, true),id='callbacks'}) }
		table.concat(html, p.table(false, rows, { 100 }, false, 'wikitable-callback wikitable'))
	end
	
	return #html > 0 and table.concat(html, '') or false
end

function p.attributes(frame)
	local args = p.getArgs(frame)
	local headers = nil--{ "Type", "Description" }
	local rows = {}
	if p.isCancellable(frame) then
		rows[1] = { 'cancellable', 'This '..args.type..' can be cancelled, by using [[event.preventDefault|event.preventDefault]].' }
	else
		rows[1] = { p.formatType('const', frame), 'This '..args.type..' cannot be cancelled.' }
	end
	local widths = { 120, 400 }
	return p.table(headers, rows, widths, 'wikitable-returns wikitable')
end

function p.value(frame)
	local args = p.getArgs(frame)
	
	if not args.value then
		return p.documentationMissing(frame, "Value type and description")
	end
	
	local arg = args.value
	local tokens = p.split(arg, ' ')
	local description = p.formatDescription(table.concat(tokens, ' ', 2))
	
	local headers = nil--{ "Type", "Description" }
	local rows = {}
	rows[1] = { p.formatType(tokens[1], frame), description }
	if args.readonly == 'true' then
		rows[2] = { p.formatType('readonly', frame), 'This '..args.type..' cannot be changed.' }
	else
		rows[2] = { 'read/set', 'This '..args.type..' can be changed, as well as read.' }
	end
	local widths = { 120, 400 }
	return p.table(headers, rows, widths, 'wikitable-returns wikitable')
end

function p.settable(frame)
	local args = p.getArgs(frame)
	
	if args.readonly == 'true' then
		return "This "..args.type.." is read-only."
	else
		return "This "..args.type.." can be read and set."
	end
end

function p.types(frame)
	local args = p.getArgs(frame)
	return p.getTypesText(frame, args.class)
end

function p.symbols(frame)
	local args = p.getArgs(frame)
	return p.getSymbolsText(frame, args.class)
end

function p.notes(frame)
	local args = p.getArgs(frame)
	local entries = p.getIndexedArgs(frame, 'note')
	if #entries == 0 then
		return "There aren't any notes for this "..args.type.."."
	end
	for i,entry in ipairs(entries) do
		entries[i] = p.dot(entry)
	end
	return "* "..table.concat(entries, "\n* ")
end

function p.examples(frame)
	local args = p.getArgs(frame)
	
	local data = {
		{ "example", "" },
		
		{ "exampleSS", "Server-Side" },
		{ "exampleCS", "Client-Side" },
		
		{ "exampleJS", "JavaScript" },
		{ "exampleLua", "Lua" },
		{ "exampleSquirrel", "Squirrel" },
		
		{ "exampleJSSS", "JavaScript - Server-Side" },
		{ "exampleJSCS", "JavaScript - Client-Side" },
		
		{ "exampleLuaSS", "Lua - Server-Side" },
		{ "exampleLuaCS", "Lua - Client-Side" },
		
		{ "exampleSquirrelSS", "Squirrel - Server-Side" },
		{ "exampleSquirrelCS", "Squirrel - Client-Side" }
	}
	
	local examples = {}
	local exampleIndex = 1
	for i,data2 in ipairs(data) do
		local entries = p.getIndexedArgs(frame, data2[1])
		for i2, entry in ipairs(entries) do
			local entry = "'''Example "..exampleIndex.." - "..data2[2]..":'''<br>"..p.loadTemplate(frame, 'CodeSyntax', {entry,id='example_'..exampleIndex})
			table.insert(examples, entry)
			exampleIndex = exampleIndex + 1
		end
	end
	
	if #examples == 0 then
		return "There aren't any examples for this "..args.type.."."
	end
	return table.concat(examples, "<br>")
end

function p.compatibility(frame)
	local args = p.getArgs(frame)
	local entries1 = p.getIndexedArgs(frame, 'previous')
	local entries2 = p.getIndexedArgs(frame, 'compat')
	if #entries1 == 0 and #entries2 == 0 then
		return "There isn't any compatibility information for this "..args.type.."."
	else
		local entriesOut = {}
		for i,arg in ipairs(entries1) do
			local versionsText, tokens = p.getVersionsText(arg, args.side, 2)
			entriesOut[i] = "This "..args.type.." was previously named <code>"..tokens[1].."</code> in "..versionsText.."."
		end
		local offset = #entriesOut
		for i,arg in ipairs(entries2) do
			entriesOut[i + offset] = p.dot(arg)
		end
		return "* "..table.concat(entriesOut, "\n* ")
	end
end

function p.related(frame)
	local args = p.getArgs(frame)
	local categoryTitle = p.getItemCategory(frame)
	if categoryTitle and categoryTitle:len() > 0 then
		local shared = args.side:lower() == 'shared'
		local server = args.side:lower() == 'shared' or args.side:lower() == 'server'
		local client = args.side:lower() == 'shared' or args.side:lower() == 'client'
		local pageName = args.type:lower() == 'event' and 'Event' or 'Function'
		
		local parts = {}
		
		local name
		if args.type == 'property' then
			name = args.class.."."..args.name
		else
			name = args.name
		end
		
		if server then
			local title = 'Server/'..pageName..'s/'..categoryTitle
			local t = p.loadTemplate(frame, title, args)
			
			--t = p.remove(t, "[["..name.."|"..name.."]]<br>\n")
			
			--[[
			t = p.replace(t, "  ", " ");
			t = p.remove(t, "\t");
			t = p.remove(t, "\r");
			t = p.remove(t, "\n");
			t = p.remove(t, "<a class=\"mw-selflink selflink\">"..name.."</a><br />")
			]]
			
			table.insert(parts, "'''Server Related'''<br><br>"..t)
		end
		
		if client then
			local title = 'Client/'..pageName..'s/'..categoryTitle
			local t = p.loadTemplate(frame, title, args)
			
			--t = p.remove(t, "[["..name.."|"..name.."]]<br>\n")
			
			--[[
			t = p.replace(t, "  ", " ");
			t = p.remove(t, "\t");
			t = p.remove(t, "\r");
			t = p.remove(t, "\n");
			t = p.remove(t, "<a class=\"mw-selflink selflink\">"..name.."</a><br />")
			]]
			
			table.insert(parts, "'''Client Related'''<br><br>"..t)
		end
		
		return table.concat(parts, '<br><br>')
	else
		return ''
	end
end

-- item category
function p.getItemCategory(frame)
	local args = p.getArgs(frame)
	local resolvedNameLower = p.getDisplayedName(frame):lower()
	
	for categoryTitle,categoryData in pairs(p.categoryItems) do
		for i2,resolvedItemName in ipairs(categoryData) do
			if resolvedNameLower == resolvedItemName:lower() then
				return categoryTitle
			end
		end
	end
	
	for i,categoryTitle in ipairs(p.categoryWords) do
		if resolvedNameLower:find(categoryTitle:lower(), 1, true) then
			return categoryTitle
		end
	end
	
	return false
end

-- item part utility
function p.getType(text)
	local finds = { "variable", "function", "property", "method", "event", "define" }
	local replaces = { "Variable", "Function", "Property", "Method", "Event", "Define" }
	local defaults = {}
	return p.findText(text, finds, replaces, defaults, "'''undocumented type'''")
end

function p.getGames(text)
	local finds = { "iii", "vc", "sa", "iv", "ug" }
	local replaces = { "GTA III", "GTA VC", "GTA SA", "GTA IV", "GTA UG" }
	local defaults = { "all", "all games" }
	return p.findText(text, finds, replaces, defaults, "All Games")
end

function p.getSide(text)
	local finds = { "shared", "server", "client" }
	local replaces = { "Server and Client", "Server Only", "Client Only" }
	local defaults = {}
	local result = p.findText(text, finds, replaces, defaults, "'''undocumented side'''")
	local isShared = result == replaces[1]
	return result, isShared
end

function p.getDisplayedName(frame)
	local args = p.getArgs(frame)
	if p.isOOP(frame) then
		return args.class:lower().."."..args.name
	else
		return args.name
	end
end

function p.getTypeRgb()
	return '#009106'
end

function p.getNameRgb()
	return '#0645ad'
end

function p.getDisplayedNameColoured(frame)
	local typeRgb = p.getTypeRgb()
	local nameRgb = p.getNameRgb()
	local args = p.getArgs(frame)
	if p.isOOP(frame) then
		return "<span style='color:"..typeRgb..";'>"..args.class:lower().."</span>.<span style='color:"..nameRgb.."';>"..args.name.."</span>"
	else
		return "<span style='color:"..nameRgb.."';>"..args.name.."</span>"
	end
end

function p.getSuccessReturnTypes(frame)
	local entries = p.getIndexedArgs(frame, 'return')
	if #entries == 0 then
		return "unknown"
	end
	for i,entry in ipairs(entries) do
		local tokens = p.split(entry, ' ')
		entries[i] = tokens[1]
	end
	return table.concat(entries, ", ")
end

function p.isOOP(frame)
	local args = p.getArgs(frame)
	return args.type == "property" or args.type == "method"
end

function p.warning(frame, text)
	return p.loadTemplate(frame, 'BlueInformationBox', {text})..'\n'
end

function p.documentationMissing(frame, text)
	return p.warning(frame, "Documentation Missing: "..text)
end

function p.buildSyntax(frame, parameters, isCallback)
	if isCallback == nil then
		isCallback = false;
	end
	local args = p.getArgs(frame)
	if not isCallback and (args.type == 'variable' or args.type == 'property') then
		return p.getSuccessReturnTypes(frame).." "..p.getDisplayedName(frame)
	elseif isCallback or args.type == 'event' then
		return "function("..parameters..")"
	else
		return p.getSuccessReturnTypes(frame).." "..p.getDisplayedName(frame).."("..parameters..")"
	end
end

function p.getTypesText(frame, baseType)
	local args = p.getArgs(frame)
	
	local derivedTypes = {
		ped			    = {'Player'},
		physical		= {'Object', 'Ped', 'Vehicle', 'Player'},
		entity			= {'Building', 'Physical', 'Object', 'Ped', 'Vehicle', 'Player'},
		element			= {'Blip', 'Entity', 'Marker', 'Building', 'Physical', 'Object', 'Ped', 'Vehicle', 'Player'},
		vehicle			= {'Train'},
		event			= {'CancellableEvent', 'KeyEvent'},
		surface			= {'RenderTarget', 'Texture'}
	}
	
	local serverTypes = {}
	local sharedTypes = {
		'Building', 'Client', 'Effect', 'Event', 'Timer', 'ReflectedFunction', 'Resource', 'Stream', 'Vec2', 'Vec3', 'Matrix4x4',
		'Element', 'Blip', 'Pickup', 'Entity', 'Physical','Vehicle', 'Train', 'XmlDocument', 'XmlElement',
		'CancellableEvent', 'KeyEvent', 'Marker', 'Object', 'Ped', 'Player'
	}
	
	local out = {}
	
	local getTypeSide = function(type)
		for i2,type2 in ipairs(serverTypes) do
			if type:lower() == type2:lower() then
				return 'server'
			end
		end
		
		for i2,type2 in ipairs(sharedTypes) do
			if type:lower() == type2:lower() then
				return 'shared'
			end
		end
		
		return 'client'
	end
	
	if baseType then
		local baseTypeLower = baseType:lower()
		local allTypes
		if derivedTypes[baseTypeLower] then
			allTypes = p.copyTable(derivedTypes[baseTypeLower])
			table.insert(allTypes, 1, baseType)
		else
			allTypes = { baseType }
		end
		local types = {}
		for i,type in ipairs(allTypes) do
			types[i] = type
		end
		p.sortTable(types)
		for i,type in ipairs(types) do
			types[i] = p.loadTemplate(frame, 'Side', {[getTypeSide(type)]='1'}).." ".."<span style=\"font-family: 'Source Code Pro', monospace;\">"..type.."</span>"
		end
		--table.insert(out, "<div style='margin-top: 20px;'></div>")
		table.insert(out, "\n"..table.concat(types, "<br>"))
	end
	
	return table.concat(out, "")
end

function p.getSymbolsText(frame, baseType)
	local args = p.getArgs(frame)
	
	local out = {}
	
	local rows = {}
	
	if args.type == 'function' or args.type == 'variable' then
		local parts = p.split(p.getDisplayedName(frame), ".")
		for i=1,(#parts)-1,1 do
			rows[i] = {}
			rows[i][1] = "<span style=\"font-family: 'Source Code Pro', monospace; color: "..p.getNameRgb()..";\">"..parts[i].."</span>"
			rows[i][2] = 'Namespace'
			rows[i][3] = i == 1 and 'Global' or 'Namespace: '..parts[i-1]
			rows[i][4] = i == 1 and parts[i]..' is a namespace, which exists in the global namespace.' or parts[i]..' is a namespace, which exists in the '..parts[i-1]..' namespace.'
		end
		rows[#parts] = {}
		rows[#parts][1] = "<span style=\"font-family: 'Source Code Pro', monospace; color: "..p.getNameRgb()..";\">"..parts[#parts].."</span>"
		rows[#parts][2] = p.properCase(args.type)
		rows[#parts][3] = #parts == 1 and 'Global' or 'Namespace: '..parts[#parts-1]
		rows[#parts][4] = #parts == 1 and parts[1]..' is a '..p.properCase(args.type)..', which exists in the global namespace.' or parts[#parts]..' is a '..p.properCase(args.type)..', which exists in the '..parts[#parts-1]..' namespace.'
	elseif args.type == 'method' or args.type == 'property' then
		local parts = p.split(p.getDisplayedName(frame), ".")
		rows[1] = {}
		rows[1][1] = "<span style=\"font-family: 'Source Code Pro', monospace; color: "..p.getNameRgb()..";\">"..parts[1].."</span>"
		rows[1][2] = 'Object'
		rows[1][3] = 'n/a'
		rows[1][4] = "<span style=\"font-family: 'Source Code Pro', monospace; color: "..p.getNameRgb()..";\">"..parts[1].."</span>"..' is an object.'
		
		rows[2] = {}
		rows[2][1] = "<span style=\"font-family: 'Source Code Pro', monospace; color: "..p.getNameRgb()..";\">"..parts[2].."</span>"
		rows[2][2] = p.properCase(args.type)
		rows[2][3] = #parts == 1 and 'Global' or 'Namespace: '..parts[#parts-1]
		rows[2][4] = "<span style=\"font-family: 'Source Code Pro', monospace; color: "..p.getNameRgb()..";\">"..parts[2].."</span>"..' is a '..p.properCase(args.type)..', which is used on objects of type '..parts[1]
	elseif args.type == 'event' then
		rows[1] = {}
		rows[1][1] = "<span style=\"font-family: 'Source Code Pro', monospace; color: "..p.getNameRgb()..";\">"..p.getDisplayedName(frame).."</span>"
		rows[1][2] = 'String'
		rows[1][3] = 'n/a'
		rows[1][4] = 'Events are referenced by name. Event names use data type String.'
	end
	
	--table.insert(out, "<div style='margin-top: 20px;'></div>")
	local headers = { 'Symbol Name', 'Symbol Type', 'Container', 'Description' }
	table.insert(out, "\n"..p.table(headers, rows, { 120, 120, 400 }, false))
	
	if args.type ~= 'event' then
		local languages = { 'JS', 'Lua', 'Squirrel' }
		local languageSymbols = {
			['JS']			= { ['function'] = 'dot',	['variable'] = 'dot',	['method'] = 'dot',		['property'] = 'dot' },
			['Lua']			= { ['function'] = 'dot',	['variable'] = 'dot',	['method'] = 'colon',	['property'] = 'dot' },
			['Squirrel']	= { ['function'] = 'dot',	['variable'] = 'dot',	['method'] = 'dot',		['property'] = 'dot' }
		}
		for i,language in ipairs(languages) do
			local line
			if args.type == 'function' then
				line = language..' uses the '..languageSymbols[language][args.type]..' symbol to call a function.'
			elseif args.type == 'variable' then
				line = language..' uses the '..languageSymbols[language][args.type]..' symbol to get or set a variable.'
			elseif args.type == 'method' then
				line = language..' uses the '..languageSymbols[language][args.type]..' symbol to call a method.'
			elseif args.type == 'property' then
				line = language..' uses the '..languageSymbols[language][args.type]..' symbol to get or set a property.'
			end
			table.insert(out, "<br>"..line)
		end
	end
	
	return table.concat(out, "")
end

function p.isCancellable(frame)
	local args = p.getArgs(frame)
	return (args.cancellable and args.cancellable == 'true') or (args.cancelable and args.cancelable == 'true')
end

function p.formatType(type, frame)
	return "<span style='font-family: \"Source Code Pro\", monospace; color: "..p.getTypeRgb()..";\"'>"..type.."</span>"
end

function p.formatName(name, frame)
	return "<span style='font-family: \"Source Code Pro\", monospace; color: "..p.getNameRgb()..";\"'>"..name.."</span>"
end

function p.formatSource(type, frame)
	return frame:preprocess('<source>'..type..'</source>')
end

function p.formatCode(code)
	return "<code>"..code.."</code>"
end

function p.formatDescription(description)
	description = p.capital(description)
	description = p.dot(description)
	return description
end

function p.standardizeNullType(type)
	type = type:lower()
	if type == 'void' or type == 'null' or type == 'undefined' or type == 'n/a' then
		return 'void'
	else
		return type
	end
end

-- general utility
function p.split(str, sep, limit)
   if not sep or sep == "" then
      return false
   end
   if not str then
      return false
   end
   limit = limit or math.huge
   if limit == 0 or limit == 1 then
      return {str}, 1
   end

   local r = {}
   local n, init = 0, 1

   while true do
      local s,e = str:find(sep, init, true)
      if not s then
         break
      end
      r[#r+1] = str:sub(init, s - 1)
      init = e + 1
      n = n + 1
      if n == limit - 1 then
         break
      end
   end

   if init <= str:len() then
      r[#r+1] = str:sub(init)
   else
      r[#r+1] = ""
   end
   n = n + 1

   if limit < 0 then
      for i=n, n + limit + 1, -1 do r[i] = nil end
      n = n + limit
   end

   return r, n
end

function p.splitLines(text)
	text = p.remove(text, "\n")
	text = p.remove(text, "\r")
	return p.split(text, "<br>")
end

function p.remove(text, remove)
	return p.replace(text, remove, "")
end

function p.replace(text, find, replace)
	return text:gsub(find, replace)
end

function p.getIndexedArgs(frame, name)
	local args = p.getArgs(frame)
	local i = 1
	local out = {}
	if args[name] then
		out[i] = args[name]
		i = i + 1
	end
	while args[name..i] do
		out[i] = args[name..i]
		i = i + 1
	end
	return out
end

function p.loadTemplate(frame, title, args)
	return frame:expandTemplate{ title = title, args = args }
end

function p.getSideVersions(arg, side, start)
	if not start then start = 1 end
	local tokens = p.split(arg, ' ')
	local sideVersions = {}
	if side == 'shared' then
		if tokens[start+1] then
			sideVersions[1] = tokens[start]
			sideVersions[2] = tokens[start+1]
		else
			sideVersions[1] = tokens[start]
		end
	elseif side == 'server' then
		sideVersions[1] = tokens[start]
	elseif side == 'client' then
		sideVersions[1] = tokens[start]
	end
	return sideVersions, tokens
end

function p.getVersionsText(arg, side, start)
	if not start then start = 1 end
	local tokens = p.split(arg, ' ')
	local text
	if side == 'shared' then
		if tokens[start+1] then
			text = "server version "..tokens[start].." and client version "..tokens[start+1]
		else
			text = "version "..tokens[start]
		end
	elseif side == 'server' then
		text = "server version "..tokens[start]
	elseif side == 'client' then
		text = "client version "..tokens[start]
	end
	return text, tokens
end

function p.properCase(text)
	return text:sub(1,1):upper()..text:sub(2):lower()
end

function p.findText(text, finds, replaces, defaults, default)
	if not text then return default end
	local textLower = text:lower()
	
	for i,default2 in ipairs(defaults) do
		if textLower == default2 then
			return default
		end
	end
	
	local items = {}
	for i,find in ipairs(finds) do
		if textLower:find(find, 1, true) then
			table.insert(items, replaces[i])
		end
	end
	return p.getListText(items)
end

function p.getListText(items)
	local count = #items
	
	if count == 1 then
		return items[1]
	elseif count == 2 then
		return items[1].." and "..items[2]
	else
		return table.concat(items, ", ", 1, count - 1).." and "..items[count]
	end
end

function p.dot(text)
	local i = #text
	while i > 0 and text:sub(i, i) == "." do
		i = i - 1
	end
	return text:sub(1,i).."."
end

function p.capital(text)
	return text:sub(1,1):upper()..text:sub(2)
end

function p.copyTable(t)
	local out = {}
	for k,v in pairs(t) do
		out[k] = v
	end
	return out
end

function p.sortTable(t)
	table.sort(t, function(a,b)
		return a:lower() < b:lower()
	end)
end

function p.table(headers, rows, widths, tableCssClass)
	if tableCssClass == nil then tableCssClass = 'wikitable' end
	rows = p.copyTable(rows)
	if not widths then widths = {} end
	
	if headers then
		headers = p.copyTable(headers)
		
		for i,header in ipairs(headers) do
			local width = widths[i]
			if width and width > 0 then
				headers[i] = "id='th_"..i.."' style='text-align:left;"..(i==#headers and 'min-width' or 'width')..":"..width.."px;'| "..header
			end
		end
	else
		for i,cell in ipairs(rows[1]) do
			local width = widths[i]
			if width and width > 0 then
				rows[1][i] = "id='td_"..i.."' style='text-align:left;"..(i==#rows[1] and 'min-width' or 'width')..":"..width.."px;'| "..cell
			end
		end
	end
	
	for i,row in ipairs(rows) do
		rows[i] = '|'..table.concat(row, '\n|')
	end
	
	return '{|'..(tableCssClass and ' class="'..tableCssClass..'"' or '')..(headers and " id='headers' " and '\n!'..table.concat(headers, '\n!')..'\n|-' or '')..'\n'..table.concat(rows, '\n|-\n')..'\n|}'
end

-- return the module
return p