code:
// Code taken from /bay/station.
// Modified to allow consequtive querys in one invocation, terminated with ";"

// Examples
/*
	-- Will call the proc for all computers in the world, thats dir is 2.
	CALL ex_act(1) ON /obj/machinery/computer IN world WHERE dir == 2
	-- Will open a window with a list of all the closets in the world, with a link to VV them.
	SELECT /obj/structure/closet/secure_closet/security/cargo IN world WHERE icon_off == "secoff"
	-- Will change all the tube lights to green
	UPDATE /obj/machinery/light IN world SET color = "#0F0" WHERE icon_state == "tube1"
	-- Will delete all pickaxes. "IN world" is not required.
	DELETE /obj/item/weapon/pickaxe
	-- Will flicker the lights once, then turn all mobs green. The semicolon is important to separate the consecutive querys, but is not required for standard one-query use
	CALL flicker(1) ON /obj/machinery/light; UPDATE /mob SET color = "#00cc00"

	--You can use operators other than ==, such as >, <=, != and etc..

*/

/client/proc/SDQL2_query(query_text as message)
	set category = "Debug"
	if(!check_rights(R_DEBUG))  //Shouldn't happen... but just to be safe.
		message_admins("ERROR: Non-admin [key_name(usr, usr.client)] attempted to execute a SDQL query!")
		log_admin("Non-admin [usr.ckey]([usr]) attempted to execute a SDQL query!")

	if(!query_text || length(query_text) < 1)
		return

	//world << query_text

	var/list/query_list = SDQL2_tokenize(query_text)

	if(!query_list || query_list.len < 1)
		return

	var/list/querys = SDQL_parse(query_list)

	if(!querys || querys.len < 1)
		return

	var/query_log = "executed SDQL query: \"[query_text]\"."
	message_admins("[key_name_admin(usr)] [query_log]")
	query_log = "[usr.ckey]([usr]) [query_log]"
	log_game(query_log)
	NOTICE(query_log)

	for(var/list/query_tree in querys)
		var/list/from_objs = list()
		var/list/select_types = list()

		switch(query_tree[1])
			if("explain")
				SDQL_testout(query_tree["explain"])
				return

			if("call")
				if("on" in query_tree)
					select_types = query_tree["on"]
				else
					return

			if("select", "delete", "update")
				select_types = query_tree[query_tree[1]]

		from_objs = SDQL_from_objs(query_tree["from"])

		var/list/objs = list()

		for(var/type in select_types)
			var/char = copytext(type, 1, 2)

			if(char == "/" || char == "*")
				for(var/from in from_objs)
					objs += SDQL_get_all(type, from)

			else if(char == "'" || char == "\"")
				objs += locate(copytext(type, 2, length(type)))

		if("where" in query_tree)
			var/objs_temp = objs
			objs = list()
			for(var/datum/d in objs_temp)
				if(SDQL_expression(d, query_tree["where"]))
					objs += d

		switch(query_tree[1])
			if("call")
				var/list/call_list = query_tree["call"]
				var/list/args_list = query_tree["args"]

				for(var/datum/d in objs)
					for(var/v in call_list)
						// To stop any procs which sleep from executing slowly.
						if(d)
							if(hascall(d, v))
								spawn() call(d, v)(arglist(args_list)) // Spawn in case the function sleeps.

			if("delete")
				for(var/datum/d in objs)
					del d

			if("select")
				var/text = ""
				for(var/datum/t in objs)
					text += "\ref[t]"
					if(istype(t, /atom))
						var/atom/a = t

						if(a.x)
							text += ": [t] at ([a.x], [a.y], [a.z])
"

						else if(a.loc && a.loc.x)
							text += ": [t] in [a.loc] at ([a.loc.x], [a.loc.y], [a.loc.z])
"

						else
							text += ": [t]
"

					else
						text += ": [t]
"

				usr << browse(text, "window=SDQL-result")

			if("update")
				if("set" in query_tree)
					var/list/set_list = query_tree["set"]
					for(var/datum/d in objs)
						var/list/vals = list()
						for(var/v in set_list)
							if(v in d.vars)
								vals += v
								vals[v] = SDQL_expression(d, set_list[v])

						if(istype(d, /turf))
							for(var/v in vals)
								if(v == "x" || v == "y" || v == "z")
									continue

								d.vars[v] = vals[v]

						else
							for(var/v in vals)
								d.vars[v] = vals[v]

/proc/SDQL_parse(list/query_list)
	var/datum/SDQL_parser/parser = new()
	var/list/querys = list()
	var/list/query_tree = list()
	var/pos = 1
	var/querys_pos = 1
	var/do_parse = 0

	for(var/val in query_list)
		if(val == ";")
			do_parse = 1
		else if(pos >= query_list.len)
			query_tree += val
			do_parse = 1

		if(do_parse)
			parser.query = query_tree
			var/list/parsed_tree
			parsed_tree = parser.parse()
			if(parsed_tree.len > 0)
				querys.len = querys_pos
				querys[querys_pos] = parsed_tree
				querys_pos++
			else //There was an error so don't run anything, and tell the user which query has errored.
				usr << "Parsing error on [querys_pos]\th query. Nothing was executed."
				return list()
			query_tree = list()
			do_parse = 0
		else
			query_tree += val
		pos++

	del(parser)

	return querys

/proc/SDQL_testout(list/query_tree, indent = 0)
	var/spaces = ""
	for(var/s = 0, s < indent, s++)
		spaces += "    "

	for(var/item in query_tree)
		if(istype(item, /list))
			usr << "[spaces]("
			SDQL_testout(item, indent + 1)
			usr << "[spaces])"

		else
			usr << "[spaces][item]"

		if(!isnum(item) && query_tree[item])

			if(istype(query_tree[item], /list))
				usr << "[spaces]    ("
				SDQL_testout(query_tree[item], indent + 2)
				usr << "[spaces]    )"

			else
				usr << "[spaces]    [query_tree[item]]"

/proc/SDQL_from_objs(list/tree)
	if("world" in tree)
		return list(world)

	var/list/out = list()

	for(var/type in tree)
		var/char = copytext(type, 1, 2)

		if(char == "/")
			out += SDQL_get_all(type, world)

		else if(char == "'" || char == "\"")
			out += locate(copytext(type, 2, length(type)))

	return out

/proc/SDQL_get_all(type, location)
	var/list/out = list()

	if(type == "*")
		for(var/datum/d in location)
			out += d

		return out

	type = text2path(type)

	if(ispath(type, /mob))
		for(var/mob/d in location)
			if(istype(d, type))
				out += d

	else if(ispath(type, /turf))
		for(var/turf/d in location)
			if(istype(d, type))
				out += d

	else if(ispath(type, /obj))
		for(var/obj/d in location)
			if(istype(d, type))
				out += d

	else if(ispath(type, /area))
		for(var/area/d in location)
			if(istype(d, type))
				out += d

	else if(ispath(type, /atom))
		for(var/atom/d in location)
			if(istype(d, type))
				out += d

	else
		for(var/datum/d in location)
			if(istype(d, type))
				out += d

	return out

/proc/SDQL_expression(datum/object, list/expression, start = 1)
	var/result = 0
	var/val

	for(var/i = start, i <= expression.len, i++)
		var/op = ""

		if(i > start)
			op = expression[i]
			i++

		var/list/ret = SDQL_value(object, expression, i)
		val = ret["val"]
		i = ret["i"]

		if(op != "")
			switch(op)
				if("+")
					result += val
				if("-")
					result -= val
				if("*")
					result *= val
				if("/")
					result /= val
				if("&")
					result &= val
				if("|")
					result |= val
				if("^")
					result ^= val
				if("=", "==")
					result = (result == val)
				if("!=", "<>")
					result = (result != val)
				if("<")
					result = (result < val)
				if("<=")
					result = (result <= val)
				if(">")
					result = (result > val)
				if(">=")
					result = (result >= val)
				if("and", "&&")
					result = (result && val)
				if("or", "||")
					result = (result || val)
				else
					usr << "SDQL2: Unknown op [op]"
					result = null
		else
			result = val

	return result

/proc/SDQL_value(datum/object, list/expression, start = 1)
	var/i = start
	var/val = null

	if(i > expression.len)
		return list("val" = null, "i" = i)

	if(istype(expression[i], /list))
		val = SDQL_expression(object, expression[i])

	else if(expression[i] == "!")
		var/list/ret = SDQL_value(object, expression, i + 1)
		val = !ret["val"]
		i = ret["i"]

	else if(expression[i] == "~")
		var/list/ret = SDQL_value(object, expression, i + 1)
		val = ~ret["val"]
		i = ret["i"]

	else if(expression[i] == "-")
		var/list/ret = SDQL_value(object, expression, i + 1)
		val = -ret["val"]
		i = ret["i"]

	else if(expression[i] == "null")
		val = null

	else if(isnum(expression[i]))
		val = expression[i]

	else if(copytext(expression[i], 1, 2) in list("'", "\""))
		val = copytext(expression[i], 2, length(expression[i]))

	else
		val = SDQL_var(object, expression, i)
		i = expression.len

	return list("val" = val, "i" = i)

/proc/SDQL_var(datum/object, list/expression, start = 1)

	if(expression[start] in object.vars)

		if(start < expression.len && expression[start + 1] == ".")
			return SDQL_var(object.vars[expression[start]], expression[start + 2])

		else
			return object.vars[expression[start]]

	else
		return null

/proc/SDQL2_tokenize(query_text)

	var/list/whitespace = list(" ", "\n", "\t")
	var/list/single = list("(", ")", ",", "+", "-", ".", ";")
	var/list/multi = list(
					"=" = list("", "="),
					"<" = list("", "=", ">"),
					">" = list("", "="),
					"!" = list("", "="))

	var/word = ""
	var/list/query_list = list()
	var/len = length(query_text)

	for(var/i = 1, i <= len, i++)
		var/char = copytext(query_text, i, i + 1)

		if(char in whitespace)
			if(word != "")
				query_list += word
				word = ""

		else if(char in single)
			if(word != "")
				query_list += word
				word = ""

			query_list += char

		else if(char in multi)
			if(word != "")
				query_list += word
				word = ""

			var/char2 = copytext(query_text, i + 1, i + 2)

			if(char2 in multi[char])
				query_list += "[char][char2]"
				i++

			else
				query_list += char

		else if(char == "'")
			if(word != "")
				usr << "\red SDQL2: You have an error in your SDQL syntax, unexpected ' in query: \"[query_text]\" following \"[word]\". Please check your syntax, and try again."
				return null

			word = "'"

			for(i++, i <= len, i++)
				char = copytext(query_text, i, i + 1)

				if(char == "'")
					if(copytext(query_text, i + 1, i + 2) == "'")
						word += "'"
						i++

					else
						break

				else
					word += char

			if(i > len)
				usr << "\red SDQL2: You have an error in your SDQL syntax, unmatched ' in query: \"[query_text]\". Please check your syntax, and try again."
				return null

			query_list += "[word]'"
			word = ""

		else if(char == "\"")
			if(word != "")
				usr << "\red SDQL2: You have an error in your SDQL syntax, unexpected \" in query: \"[query_text]\" following \"[word]\". Please check your syntax, and try again."
				return null

			word = "\""

			for(i++, i <= len, i++)
				char = copytext(query_text, i, i + 1)

				if(char == "\"")
					if(copytext(query_text, i + 1, i + 2) == "'")
						word += "\""
						i++

					else
						break

				else
					word += char

			if(i > len)
				usr << "\red SDQL2: You have an error in your SDQL syntax, unmatched \" in query: \"[query_text]\". Please check your syntax, and try again."
				return null

			query_list += "[word]\""
			word = ""

		else
			word += char

	if(word != "")
		query_list += word
	return query_list


code:
//I'm pretty sure that this is a recursive [s]descent[/s] ascent parser.

//Spec

//////////
//
//	query				:	select_query | delete_query | update_query | call_query | explain
//	explain				:	'EXPLAIN' query
//
//	select_query		:	'SELECT' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]
//	delete_query		:	'DELETE' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]
//	update_query		:	'UPDATE' select_list [('FROM' | 'IN') from_list] 'SET' assignments ['WHERE' bool_expression]
//	call_query			:	'CALL' call_function ['ON' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]]
//
//	select_list			:	select_item [',' select_list]
//	select_item			:	'*' | select_function | object_type
//	select_function		:	count_function
//	count_function		:	'COUNT' '(' '*' ')' | 'COUNT' '(' object_types ')'
//
//	from_list			:	from_item [',' from_list]
//	from_item			:	'world' | object_type
//
//	call_function		:	<function name> ['(' [arguments] ')']
//	arguments			:	expression [',' arguments]
//
//	object_type			:	<type path> | string
//
//	assignments			:	assignment, [',' assignments]
//	assignment			:	<variable name> '=' expression
//	variable			:	<variable name> | <variable name> '.' variable
//
//	bool_expression		:	expression comparitor expression  [bool_operator bool_expression]
//	expression			:	( unary_expression | '(' expression ')' | value ) [binary_operator expression]
//	unary_expression	:	unary_operator ( unary_expression | value | '(' expression ')' )
//	comparitor			:	'=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>='
//	value				:	variable | string | number | 'null'
//	unary_operator		:	'!' | '-' | '~'
//	binary_operator		:	comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^'
//	bool_operator		:	'AND' | '&&' | 'OR' | '||'
//
//	string				:	''' <some text> ''' | '"' <some text > '"'
//	number				:	<some digits>
//
//////////

/datum/SDQL_parser
	var/query_type
	var/error = 0

	var/list/query
	var/list/tree

	var/list/select_functions = list("count")
	var/list/boolean_operators = list("and", "or", "&&", "||")
	var/list/unary_operators = list("!", "-", "~")
	var/list/binary_operators = list("+", "-", "/", "*", "&", "|", "^")
	var/list/comparitors = list("=", "==", "!=", "<>", "<", "<=", ">", ">=")

/datum/SDQL_parser/New(query_list)
	query = query_list

/datum/SDQL_parser/proc/parse_error(error_message)
	error = 1
	usr << "SQDL2 Parsing Error: [error_message]"
	return query.len + 1

/datum/SDQL_parser/proc/parse()
	tree = list()
	query(1, tree)

	if(error)
		return list()
	else
		return tree

/datum/SDQL_parser/proc/token(i)
	if(i <= query.len)
		return query[i]

	else
		return null

/datum/SDQL_parser/proc/tokens(i, num)
	if(i + num <= query.len)
		return query.Copy(i, i + num)

	else
		return null

/datum/SDQL_parser/proc/tokenl(i)
	return lowertext(token(i))

/datum/SDQL_parser/proc

//query:	select_query | delete_query | update_query
	query(i, list/node)
		query_type = tokenl(i)

		switch(query_type)
			if("select")
				select_query(i, node)

			if("delete")
				delete_query(i, node)

			if("update")
				update_query(i, node)

			if("call")
				call_query(i, node)

			if("explain")
				node += "explain"
				node["explain"] = list()
				query(i + 1, node["explain"])

//	select_query:	'SELECT' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]
	select_query(i, list/node)
		var/list/select = list()
		i = select_list(i + 1, select)

		node += "select"
		node["select"] = select

		var/list/from = list()
		if(tokenl(i) in list("from", "in"))
			i = from_list(i + 1, from)
		else
			from += "world"

		node += "from"
		node["from"] = from

		if(tokenl(i) == "where")
			var/list/where = list()
			i = bool_expression(i + 1, where)

			node += "where"
			node["where"] = where

		return i

//delete_query:	'DELETE' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]
	delete_query(i, list/node)
		var/list/select = list()
		i = select_list(i + 1, select)

		node += "delete"
		node["delete"] = select

		var/list/from = list()
		if(tokenl(i) in list("from", "in"))
			i = from_list(i + 1, from)
		else
			from += "world"

		node += "from"
		node["from"] = from

		if(tokenl(i) == "where")
			var/list/where = list()
			i = bool_expression(i + 1, where)

			node += "where"
			node["where"] = where

		return i

//update_query:	'UPDATE' select_list [('FROM' | 'IN') from_list] 'SET' assignments ['WHERE' bool_expression]
	update_query(i, list/node)
		var/list/select = list()
		i = select_list(i + 1, select)

		node += "update"
		node["update"] = select

		var/list/from = list()
		if(tokenl(i) in list("from", "in"))
			i = from_list(i + 1, from)
		else
			from += "world"

		node += "from"
		node["from"] = from

		if(tokenl(i) != "set")
			i = parse_error("UPDATE has misplaced SET")

		var/list/set_assignments = list()
		i = assignments(i + 1, set_assignments)

		node += "set"
		node["set"] = set_assignments

		if(tokenl(i) == "where")
			var/list/where = list()
			i = bool_expression(i + 1, where)

			node += "where"
			node["where"] = where

		return i

//call_query:	'CALL' call_function ['ON' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]]
	call_query(i, list/node)
		var/list/func = list()
		var/list/arguments = list()
		i = call_function(i + 1, func, arguments)

		node += "call"
		node["call"] = func
		node["args"] = arguments

		if(tokenl(i) != "on")
			return i

		var/list/select = list()
		i = select_list(i + 1, select)

		node += "on"
		node["on"] = select

		var/list/from = list()
		if(tokenl(i) in list("from", "in"))
			i = from_list(i + 1, from)
		else
			from += "world"

		node += "from"
		node["from"] = from

		if(tokenl(i) == "where")
			var/list/where = list()
			i = bool_expression(i + 1, where)

			node += "where"
			node["where"] = where

		return i

//select_list:	select_item [',' select_list]
	select_list(i, list/node)
		i = select_item(i, node)

		if(token(i) == ",")
			i = select_list(i + 1, node)

		return i

//from_list:	from_item [',' from_list]
	from_list(i, list/node)
		i = from_item(i, node)

		if(token(i) == ",")
			i = from_list(i + 1, node)

		return i

//assignments:	assignment, [',' assignments]
	assignments(i, list/node)
		i = assignment(i, node)

		if(token(i) == ",")
			i = assignments(i + 1, node)

		return i

//select_item:	'*' | select_function | object_type
	select_item(i, list/node)

		if(token(i) == "*")
			node += "*"
			i++

		else if(tokenl(i) in select_functions)
			i = select_function(i, node)

		else
			i = object_type(i, node)

		return i

//from_item:	'world' | object_type
	from_item(i, list/node)

		if(token(i) == "world")
			node += "world"
			i++

		else
			i = object_type(i, node)

		return i

//bool_expression:	expression [bool_operator bool_expression]
	bool_expression(i, list/node)

		var/list/bool = list()
		i = expression(i, bool)

		node[++node.len] = bool

		if(tokenl(i) in boolean_operators)
			i = bool_operator(i, node)
			i = bool_expression(i, node)

		return i

//assignment:	<variable name> '=' expression
	assignment(i, list/node)

		node += token(i)

		if(token(i + 1) == "=")
			var/varname = token(i)
			node[varname] = list()

			i = expression(i + 2, node[varname])

		else
			parse_error("Assignment expected, but no = found")

		return i

//variable:	<variable name> | <variable name> '.' variable
	variable(i, list/node)
		var/list/L = list(token(i))
		node[++node.len] = L

		if(token(i + 1) == ".")
			L += "."
			i = variable(i + 2, L)

		else
			i++

		return i

//object_type:	<type path> | string
	object_type(i, list/node)

		if(copytext(token(i), 1, 2) == "/")
			node += token(i)

		else
			i = string(i, node)

		return i + 1

//comparitor:	'=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>='
	comparitor(i, list/node)

		if(token(i) in list("=", "==", "!=", "<>", "<", "<=", ">", ">="))
			node += token(i)

		else
			parse_error("Unknown comparitor [token(i)]")

		return i + 1

//bool_operator:	'AND' | '&&' | 'OR' | '||'
	bool_operator(i, list/node)

		if(tokenl(i) in list("and", "or", "&&", "||"))
			node += token(i)

		else
			parse_error("Unknown comparitor [token(i)]")

		return i + 1

//string:	''' <some text> ''' | '"' <some text > '"'
	string(i, list/node)

		if(copytext(token(i), 1, 2) in list("'", "\""))
			node += token(i)

		else
			parse_error("Expected string but found '[token(i)]'")

		return i + 1

//call_function:	<function name> ['(' [arguments] ')']
	call_function(i, list/node, list/arguments)
		if(length(tokenl(i)))
			node += token(i++)
			if(token(i) != "(")
				parse_error("Expected ( but found '[token(i)]'")
			else if(token(i + 1) != ")")
				do
					i = expression(i + 1, arguments)
					if(token(i) == ",")
						continue
				while(token(i) && token(i) != ")")
			else
				i++
		else
			parse_error("Expected a function but found nothing")
		return i + 1

//select_function:	count_function
	select_function(i, list/node)

		parse_error("Sorry, function calls aren't available yet")

		return i

//expression:	( unary_expression | '(' expression ')' | value ) [binary_operator expression]
	expression(i, list/node)

		if(token(i) in unary_operators)
			i = unary_expression(i, node)

		else if(token(i) == "(")
			var/list/expr = list()

			i = expression(i + 1, expr)

			if(token(i) != ")")
				parse_error("Missing ) at end of expression.")

			else
				i++

			node[++node.len] = expr

		else
			i = value(i, node)

		if(token(i) in binary_operators)
			i = binary_operator(i, node)
			i = expression(i, node)

		else if(token(i) in comparitors)
			i = binary_operator(i, node)

			var/list/rhs = list()
			i = expression(i, rhs)

			node[++node.len] = rhs

		return i

//unary_expression:	unary_operator ( unary_expression | value | '(' expression ')' )
	unary_expression(i, list/node)

		if(token(i) in unary_operators)
			var/list/unary_exp = list()

			unary_exp += token(i)
			i++

			if(token(i) in unary_operators)
				i = unary_expression(i, unary_exp)

			else if(token(i) == "(")
				var/list/expr = list()

				i = expression(i + 1, expr)

				if(token(i) != ")")
					parse_error("Missing ) at end of expression.")

				else
					i++

				unary_exp[++unary_exp.len] = expr

			else
				i = value(i, unary_exp)

			node[++node.len] = unary_exp

		else
			parse_error("Expected unary operator but found '[token(i)]'")

		return i

//binary_operator:	comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^'
	binary_operator(i, list/node)

		if(token(i) in (binary_operators + comparitors))
			node += token(i)

		else
			parse_error("Unknown binary operator [token(i)]")

		return i + 1

//value:	variable | string | number | 'null'
	value(i, list/node)

		if(token(i) == "null")
			node += "null"
			i++

		else if(isnum(text2num(token(i))))
			node += text2num(token(i))
			i++

		else if(copytext(token(i), 1, 2) in list("'", "\""))
			i = string(i, node)

		else
			i = variable(i, node)

		return i

/*EXPLAIN SELECT * WHERE 42 = 6 * 9 OR val = - 5 == 7*/

license: mit


code:
//This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE!
var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey","alien","as")

/client/verb/adminhelp(msg as text)
	set category = "Admin"
	set name = "Adminhelp"

	if(say_disabled)	//This is here to try to identify lag problems
		usr << "Speech is currently admin-disabled."
		return

	//handle muting and automuting
	if(prefs.muted & MUTE_ADMINHELP)
		src << "Error: Admin-PM: You cannot send adminhelps (Muted)."
		return
	if(src.handle_spam_prevention(msg,MUTE_ADMINHELP))
		return

	//remove out adminhelp verb temporarily to prevent spamming of admins.
	src.verbs -= /client/verb/adminhelp
	spawn(1200)
		src.verbs += /client/verb/adminhelp	// 2 minute cool-down for adminhelps

	//clean the input msg
	if(!msg)	return
	msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN))
	if(!msg)	return
	var/original_msg = msg

	//explode the input msg into a list
	var/list/msglist = text2list(msg, " ")

	//generate keywords lookup
	var/list/surnames = list()
	var/list/forenames = list()
	var/list/ckeys = list()
	for(var/mob/M in mob_list)
		var/list/indexing = list(M.real_name, M.name)
		if(M.mind)	indexing += M.mind.name

		for(var/string in indexing)
			var/list/L = text2list(string, " ")
			var/surname_found = 0
			//surnames
			for(var/i=L.len, i>=1, i--)
				var/word = ckey(L[i])
				if(word)
					surnames[word] = M
					surname_found = i
					break
			//forenames
			for(var/i=1, i<surname_found, i++)
				var/word = ckey(L[i])
				if(word)
					forenames[word] = M
			//ckeys
			ckeys[M.ckey] = M

	var/ai_found = 0
	msg = ""
	var/list/mobs_found = list()
	for(var/original_word in msglist)
		var/word = ckey(original_word)
		if(word)
			if(!(word in adminhelp_ignored_words))
				if(word == "ai")
					ai_found = 1
				else
					var/mob/found = ckeys[word]
					if(!found)
						found = surnames[word]
						if(!found)
							found = forenames[word]
					if(found)
						if(!(found in mobs_found))
							mobs_found += found
							if(!ai_found && isAI(found))
								ai_found = 1
							msg += "[original_word] (?) "
							continue
			msg += "[original_word] "

	if(!mob)	return						//this doesn't happen

	var/ref_mob = "\ref[mob]"
	msg = "HELP: [key_name(src, 1)] (?) (PP) (VV) (SM) (JMP) (TP) [ai_found ? " (CL)" : ""]: [msg]"

	//send this msg to all admins

	for(var/client/X in admins)
		if(X.prefs.toggles & SOUND_ADMINHELP)
			X << 'sound/effects/adminhelp.ogg'
		X << msg

	//show it to the person adminhelping too
	src << "PM to-Admins: [original_msg]"

	//send it to irc if nobody is on and tell us how many were on
	var/admin_number_present = send2irc_adminless_only(ckey,original_msg)
	log_admin("HELP: [key_name(src)]: [original_msg] - heard by [admin_number_present] non-AFK admins who have +BAN.")
	feedback_add_details("admin_verb","AH") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	return

proc/send2irc_adminless_only(source, msg, requiredflags = R_BAN)
	var/admin_number_total = 0		//Total number of admins
	var/admin_number_afk = 0		//Holds the number of admins who are afk
	var/admin_number_ignored = 0	//Holds the number of admins without +BAN (so admins who are not really admins)
	var/admin_number_decrease = 0	//Holds the number of admins with are afk, ignored or both
	for(var/client/X in admins)
		admin_number_total++;
		var/invalid = 0
		if(requiredflags != 0 && !check_rights_for(X, requiredflags))
			admin_number_ignored++
			invalid = 1
		if(X.is_afk())
			admin_number_afk++
			invalid = 1
		if(X.holder.fakekey)
			admin_number_ignored++
			invalid = 1
		if(invalid)
			admin_number_decrease++
	var/admin_number_present = admin_number_total - admin_number_decrease	//Number of admins who are neither afk nor invalid
	if(admin_number_present <= 0)
		if(!admin_number_afk && !admin_number_ignored)
			send2irc(source, "[msg] - No admins online")
		else
			send2irc(source, "[msg] - All admins AFK ([admin_number_afk]/[admin_number_total]) or skipped ([admin_number_ignored]/[admin_number_total])")
	return admin_number_present

proc/send2irc(msg,msg2)
	if(config.useircbot)
		shell("python nudge.py [msg] [msg2]")
	return


code:
/client/proc/jumptoarea(area/A in sortedAreas)
	set name = "Jump to Area"
	set desc = "Area to jump to"
	set category = "Admin"
	if(!src.holder)
		src << "Only administrators may use this command."
		return

	if(!A)
		return

	var/list/turfs = list()
	for(var/turf/T in A)
		if(T.density)
			continue
		turfs.Add(T)

	var/turf/T = pick_n_take(turfs)
	if(!T)
		src << "Nowhere to jump to!"
		return
	admin_forcemove(usr, T)
	log_admin("[key_name(usr)] jumped to [A]")
	message_admins("[key_name_admin(usr)] jumped to [A]")
	feedback_add_details("admin_verb","JA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/client/proc/jumptoturf(var/turf/T in world)
	set name = "Jump to Turf"
	set category = "Admin"
	if(!src.holder)
		src << "Only administrators may use this command."
		return

	log_admin("[key_name(usr)] jumped to [T.x],[T.y],[T.z] in [T.loc]")
	message_admins("[key_name_admin(usr)] jumped to [T.x],[T.y],[T.z] in [T.loc]")
	usr.loc = T
	feedback_add_details("admin_verb","JT") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	return

/client/proc/jumptomob(var/mob/M in mob_list)
	set category = "Admin"
	set name = "Jump to Mob"

	if(!src.holder)
		src << "Only administrators may use this command."
		return

	log_admin("[key_name(usr)] jumped to [key_name(M)]")
	message_admins("[key_name_admin(usr)] jumped to [key_name_admin(M)]")
	if(src.mob)
		var/mob/A = src.mob
		var/turf/T = get_turf(M)
		if(T && isturf(T))
			feedback_add_details("admin_verb","JM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
			admin_forcemove(A, M.loc)
		else
			A << "This mob is not located in the game world."

/client/proc/jumptocoord(tx as num, ty as num, tz as num)
	set category = "Admin"
	set name = "Jump to Coordinate"

	if (!holder)
		src << "Only administrators may use this command."
		return

	if(src.mob)
		var/mob/A = src.mob
		A.x = tx
		A.y = ty
		A.z = tz
		feedback_add_details("admin_verb","JC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	message_admins("[key_name_admin(usr)] jumped to coordinates [tx], [ty], [tz]")

/client/proc/jumptokey()
	set category = "Admin"
	set name = "Jump to Key"

	if(!src.holder)
		src << "Only administrators may use this command."
		return

	var/list/keys = list()
	for(var/mob/M in player_list)
		keys += M.client
	var/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys)
	if(!selection)
		src << "No keys found."
		return
	var/mob/M = selection:mob
	log_admin("[key_name(usr)] jumped to [key_name(M)]")
	message_admins("[key_name_admin(usr)] jumped to [key_name_admin(M)]")

	admin_forcemove(usr, M.loc)

	feedback_add_details("admin_verb","JK") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/client/proc/Getmob(var/mob/M in mob_list)
	set category = "Admin"
	set name = "Get Mob"
	set desc = "Mob to teleport"
	if(!src.holder)
		src << "Only administrators may use this command."
		return

	log_admin("[key_name(usr)] teleported [key_name(M)]")
	message_admins("[key_name_admin(usr)] teleported [key_name_admin(M)]")
	admin_forcemove(M, get_turf(usr))
	feedback_add_details("admin_verb","GM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/client/proc/Getkey()
	set category = "Admin"
	set name = "Get Key"
	set desc = "Key to teleport"

	if(!src.holder)
		src << "Only administrators may use this command."
		return

	var/list/keys = list()
	for(var/mob/M in player_list)
		keys += M.client
	var/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys)
	if(!selection)
		return
	var/mob/M = selection:mob

	if(!M)
		return
	log_admin("[key_name(usr)] teleported [key_name(M)]")
	message_admins("[key_name_admin(usr)] teleported [key_name(M)]")
	if(M)
		admin_forcemove(M, get_turf(usr))
		usr.loc = M.loc
		feedback_add_details("admin_verb","GK") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/client/proc/sendmob(var/mob/M in sortmobs())
	set category = "Admin"
	set name = "Send Mob"
	if(!src.holder)
		src << "Only administrators may use this command."
		return
	var/area/A = input(usr, "Pick an area.", "Pick an area") in sortedAreas
	if(A)
		admin_forcemove(M, pick(get_area_turfs(A)))
		feedback_add_details("admin_verb","SMOB") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
		log_admin("[key_name(usr)] teleported [key_name(M)] to [A]")
		message_admins("[key_name_admin(usr)] teleported [key_name_admin(M)] to [A]")

/proc/admin_forcemove(var/mob/mover, var/atom/newloc)
	mover.loc = newloc
	mover.on_forcemove(newloc)

/mob/proc/on_forcemove(var/atom/newloc)
	return


code:
//allows right clicking mobs to send an admin PM to their client, forwards the selected mob's client to cmd_admin_pm
/client/proc/cmd_admin_pm_context(mob/M as mob in mob_list)
	set category = null
	set name = "Admin PM Mob"
	if(!holder)
		src << "Error: Admin-PM-Context: Only administrators may use this command."
		return
	if( !ismob(M) || !M.client )	return
	cmd_admin_pm(M.client,null)
	feedback_add_details("admin_verb","APMM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

//shows a list of clients we could send PMs to, then forwards our choice to cmd_admin_pm
/client/proc/cmd_admin_pm_panel()
	set category = "Admin"
	set name = "Admin PM"
	if(!holder)
		src << "Error: Admin-PM-Panel: Only administrators may use this command."
		return
	var/list/client/targets[0]
	for(var/client/T)
		if(T.mob)
			if(istype(T.mob, /mob/new_player))
				targets["(New Player) - [T]"] = T
			else if(istype(T.mob, /mob/dead/observer))
				targets["[T.mob.name](Ghost) - [T]"] = T
			else
				targets["[T.mob.real_name](as [T.mob.name]) - [T]"] = T
		else
			targets["(No Mob) - [T]"] = T
	var/list/sorted = sortList(targets)
	var/target = input(src,"To whom shall we send a message?","Admin PM",null) in sorted|null
	cmd_admin_pm(targets[target],null)
	feedback_add_details("admin_verb","APM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

//takes input from cmd_admin_pm_context, cmd_admin_pm_panel or /client/Topic and sends them a PM.
//Fetching a message if needed. src is the sender and C is the target client
/client/proc/cmd_admin_pm(whom, msg)
	if(prefs.muted & MUTE_ADMINHELP)
		src << "Error: Admin-PM: You are unable to use admin PM-s (muted)."
		return

	var/client/C
	if(istext(whom))
		if(cmptext(copytext(whom,1,2),"@"))
			whom = findStealthKey(whom)
		C = directory[whom]
	else if(istype(whom,/client))
		C = whom
	if(!C)
		if(holder)	src << "Error: Admin-PM: Client not found."
		else		adminhelp(msg)	//admin we are replying to left. adminhelp instead
		return

	//get message text, limit it's length.and clean/escape html
	if(!msg)
		msg = input(src,"Message:", "Private message to [key_name(C, 0, 0)]") as text|null

		if(!msg)	return
		if(!C)
			if(holder)	src << "Error: Admin-PM: Client not found."
			else		adminhelp(msg)	//admin we are replying to has vanished, adminhelp instead
			return

	if (src.handle_spam_prevention(msg,MUTE_ADMINHELP))
		return

	//clean the message if it's not sent by a high-rank admin
	if(!check_rights(R_SERVER|R_DEBUG,0))
		msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN))
		if(!msg)	return

	msg = emoji_parse(msg)

	if(C.holder)
		if(holder)	//both are admins
			C << "Admin PM from-[key_name(src, C, 1)]: [msg]"
			src << "Admin PM to-[key_name(C, src, 1)]: [msg]"

		else		//recipient is an admin but sender is not
			C << "Reply PM from-[key_name(src, C, 1)]: [msg]"
			src << "PM to-Admins: [msg]"

		//play the recieving admin the adminhelp sound (if they have them enabled)
		if(C.prefs.toggles & SOUND_ADMINHELP)
			C << 'sound/effects/adminhelp.ogg'

	else
		if(holder)	//sender is an admin but recipient is not. Do BIG RED TEXT
			C << "-- Administrator private message --"
			C << "Admin PM from-[key_name(src, C, 0)]: [msg]"
			C << "Click on the administrator's name to reply."
			src << "Admin PM to-[key_name(C, src, 1)]: [msg]"

			//always play non-admin recipients the adminhelp sound
			C << 'sound/effects/adminhelp.ogg'

			//AdminPM popup for ApocStation and anybody else who wants to use it. Set it with POPUP_ADMIN_PM in config.txt ~Carn
			if(config.popup_admin_pm)
				spawn()	//so we don't hold the caller proc up
					var/sender = src
					var/sendername = key
					var/reply = input(C, msg,"Admin PM from-[sendername]", "") as text|null		//show message and await a reply
					if(C && reply)
						if(sender)
							C.cmd_admin_pm(sender,reply)										//sender is still about, let's reply to them
						else
							adminhelp(reply)													//sender has left, adminhelp instead
					return

		else		//neither are admins
			src << "Error: Admin-PM: Non-admin to non-admin PM communication is forbidden."
			return

	log_admin("PM: [key_name(src)]->[key_name(C)]: [msg]")

	//we don't use message_admins here because the sender/receiver might get it too
	for(var/client/X in admins)
		if(X.key!=key && X.key!=C.key)	//check client/X is an admin and isn't the sender or recipient
			X << "PM: [key_name(src, X, 0)]->[key_name(C, X, 0)]: \blue [msg]" //inform X


code:
/client/proc/cmd_admin_say(msg as text)
	set category = "Special Verbs"
	set name = "Asay" //Gave this shit a shorter name so you only have to time out "asay" rather than "admin say" to use it --NeoFite
	set hidden = 1
	if(!check_rights(0))	return

	msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
	if(!msg)	return

	log_admin("[key_name(src)] : [msg]")

	if(check_rights(R_ADMIN,0))
		msg = "ADMIN: [key_name(usr, 1)] (JMP): [msg]"
		admins << msg
	else
		msg = "ADMIN: [key_name(usr, 1)]: [msg]"
		admins << msg

	feedback_add_details("admin_verb","M") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!


code:
/client/proc/atmosscan()
	set category = "Mapping"
	set name = "Check Plumbing"
	if(!src.holder)
		src << "Only administrators may use this command."
		return
	feedback_add_details("admin_verb","CP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

	//all plumbing - yes, some things might get stated twice, doesn't matter.
	for (var/obj/machinery/atmospherics/plumbing in world)
		if (plumbing.nodealert)
			usr << "Unconnected [plumbing.name] located at [plumbing.x],[plumbing.y],[plumbing.z] ([get_area(plumbing.loc)])"

	//Manifolds
	for (var/obj/machinery/atmospherics/pipe/manifold/pipe in world)
		if (!pipe.node1 || !pipe.node2 || !pipe.node3)
			usr << "Unconnected [pipe.name] located at [pipe.x],[pipe.y],[pipe.z] ([get_area(pipe.loc)])"

	//Pipes
	for (var/obj/machinery/atmospherics/pipe/simple/pipe in world)
		if (!pipe.node1 || !pipe.node2)
			usr << "Unconnected [pipe.name] located at [pipe.x],[pipe.y],[pipe.z] ([get_area(pipe.loc)])"

/client/proc/powerdebug()
	set category = "Mapping"
	set name = "Check Power"
	if(!src.holder)
		src << "Only administrators may use this command."
		return
	feedback_add_details("admin_verb","CPOW") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

	for (var/datum/powernet/PN in powernets)
		if (!PN.nodes || !PN.nodes.len)
			if(PN.cables && (PN.cables.len > 1))
				var/obj/structure/cable/C = PN.cables[1]
				usr << "Powernet with no nodes! (number [PN.number]) - example cable at [C.x], [C.y], [C.z] in area [get_area(C.loc)]"

		if (!PN.cables || (PN.cables.len < 10))
			if(PN.cables && (PN.cables.len > 1))
				var/obj/structure/cable/C = PN.cables[1]
				usr << "Powernet with fewer than 10 cables! (number [PN.number]) - example cable at [C.x], [C.y], [C.z] in area [get_area(C.loc)]"


code:
/proc/togglebuildmode(mob/M as mob in player_list)
	set name = "Toggle Build Mode"
	set category = "Special Verbs"
	if(M.client)
		if(M.client.buildmode)
			log_admin("[key_name(usr)] has left build mode.")
			M.client.buildmode = 0
			M.client.show_popup_menus = 1
			for(var/obj/effect/bmode/buildholder/H)
				if(H.cl == M.client)
					qdel(H)
		else
			message_admins("[key_name(usr)] has entered build mode.")
			log_admin("[key_name(usr)] has entered build mode.")
			M.client.buildmode = 1
			M.client.show_popup_menus = 0

			var/obj/effect/bmode/buildholder/H = new/obj/effect/bmode/buildholder()
			var/obj/effect/bmode/builddir/A = new/obj/effect/bmode/builddir(H)
			A.master = H
			var/obj/effect/bmode/buildhelp/B = new/obj/effect/bmode/buildhelp(H)
			B.master = H
			var/obj/effect/bmode/buildmode/C = new/obj/effect/bmode/buildmode(H)
			C.master = H
			var/obj/effect/bmode/buildquit/D = new/obj/effect/bmode/buildquit(H)
			D.master = H

			H.builddir = A
			H.buildhelp = B
			H.buildmode = C
			H.buildquit = D
			M.client.screen += A
			M.client.screen += B
			M.client.screen += C
			M.client.screen += D
			H.cl = M.client

/obj/effect/bmode//Cleaning up the tree a bit
	density = 1
	anchored = 1
	layer = 20
	dir = NORTH
	icon = 'icons/misc/buildmode.dmi'
	var/obj/effect/bmode/buildholder/master = null

/obj/effect/bmode/builddir
	icon_state = "build"
	screen_loc = "NORTH,WEST"

/obj/effect/bmode/builddir/Click()
	switch(dir)
		if(NORTH)
			dir = EAST
		if(EAST)
			dir = SOUTH
		if(SOUTH)
			dir = WEST
		if(WEST)
			dir = NORTHWEST
		if(NORTHWEST)
			dir = NORTH
	return 1

/obj/effect/bmode/buildhelp
	icon = 'icons/misc/buildmode.dmi'
	icon_state = "buildhelp"
	screen_loc = "NORTH,WEST+1"

/obj/effect/bmode/buildhelp/Click()
	switch(master.cl.buildmode)
		if(1)
			usr << "\blue ***********************************************************"
			usr << "\blue Left Mouse Button        = Construct / Upgrade"
			usr << "\blue Right Mouse Button       = Deconstruct / Delete / Downgrade"
			usr << "\blue Left Mouse Button + ctrl = R-Window"
			usr << "\blue Left Mouse Button + alt  = Airlock"
			usr << ""
			usr << "\blue Use the button in the upper left corner to"
			usr << "\blue change the direction of built objects."
			usr << "\blue ***********************************************************"
		if(2)
			usr << "\blue ***********************************************************"
			usr << "\blue Right Mouse Button on buildmode button = Set object type"
			usr << "\blue Left Mouse Button on turf/obj          = Place objects"
			usr << "\blue Right Mouse Button                     = Delete objects"
			usr << ""
			usr << "\blue Use the button in the upper left corner to"
			usr << "\blue change the direction of built objects."
			usr << "\blue ***********************************************************"
		if(3)
			usr << "\blue ***********************************************************"
			usr << "\blue Right Mouse Button on buildmode button = Select var(type) & value"
			usr << "\blue Left Mouse Button on turf/obj/mob      = Set var(type) & value"
			usr << "\blue Right Mouse Button on turf/obj/mob     = Reset var's value"
			usr << "\blue ***********************************************************"
		if(4)
			usr << "\blue ***********************************************************"
			usr << "\blue Left Mouse Button on turf/obj/mob      = Select"
			usr << "\blue Right Mouse Button on turf/obj/mob     = Throw"
			usr << "\blue ***********************************************************"
	return 1

/obj/effect/bmode/buildquit
	icon_state = "buildquit"
	screen_loc = "NORTH,WEST+3"

/obj/effect/bmode/buildquit/Click()
	togglebuildmode(master.cl.mob)
	return 1

/obj/effect/bmode/buildholder
	density = 0
	anchored = 1
	var/client/cl = null
	var/obj/effect/bmode/builddir/builddir = null
	var/obj/effect/bmode/buildhelp/buildhelp = null
	var/obj/effect/bmode/buildmode/buildmode = null
	var/obj/effect/bmode/buildquit/buildquit = null
	var/atom/movable/throw_atom = null

/obj/effect/bmode/buildmode
	icon_state = "buildmode1"
	screen_loc = "NORTH,WEST+2"
	var/varholder = "name"
	var/valueholder = "derp"
	var/objholder = /obj/structure/closet

/obj/effect/bmode/buildmode/Click(location, control, params)
	var/list/pa = params2list(params)

	if(pa.Find("left"))
		switch(master.cl.buildmode)
			if(1)
				master.cl.buildmode = 2
				src.icon_state = "buildmode2"
			if(2)
				master.cl.buildmode = 3
				src.icon_state = "buildmode3"
			if(3)
				master.cl.buildmode = 4
				src.icon_state = "buildmode4"
			if(4)
				master.cl.buildmode = 1
				src.icon_state = "buildmode1"

	else if(pa.Find("right"))
		switch(master.cl.buildmode)
			if(1)
				return 1
			if(2)
				objholder = text2path(input(usr,"Enter typepath:" ,"Typepath","/obj/structure/closet"))
				if(!ispath(objholder))
					objholder = /obj/structure/closet
					alert("That path is not allowed.")
				else
					if(ispath(objholder,/mob) && !check_rights(R_DEBUG,0))
						objholder = /obj/structure/closet
			if(3)
				var/list/locked = list("vars", "key", "ckey", "client", "firemut", "ishulk", "telekinesis", "xray", "virus", "viruses", "cuffed", "ka", "last_eaten", "urine")

				master.buildmode.varholder = input(usr,"Enter variable name:" ,"Name", "name")
				if(master.buildmode.varholder in locked && !check_rights(R_DEBUG,0))
					return 1
				var/thetype = input(usr,"Select variable type:" ,"Type") in list("text","number","mob-reference","obj-reference","turf-reference")
				if(!thetype) return 1
				switch(thetype)
					if("text")
						master.buildmode.valueholder = input(usr,"Enter variable value:" ,"Value", "value") as text
					if("number")
						master.buildmode.valueholder = input(usr,"Enter variable value:" ,"Value", 123) as num
					if("mob-reference")
						master.buildmode.valueholder = input(usr,"Enter variable value:" ,"Value") as mob in mob_list
					if("obj-reference")
						master.buildmode.valueholder = input(usr,"Enter variable value:" ,"Value") as obj in world
					if("turf-reference")
						master.buildmode.valueholder = input(usr,"Enter variable value:" ,"Value") as turf in world
	return 1

/proc/build_click(var/mob/user, buildmode, params, var/obj/object)
	var/obj/effect/bmode/buildholder/holder = null
	for(var/obj/effect/bmode/buildholder/H)
		if(H.cl == user.client)
			holder = H
			break
	if(!holder) return
	var/list/pa = params2list(params)

	switch(buildmode)
		if(1)
			if(istype(object,/turf) && pa.Find("left") && !pa.Find("alt") && !pa.Find("ctrl") )
				var/turf/T = object
				if(istype(object,/turf/space))
					T.ChangeTurf(/turf/simulated/floor/plasteel)
				else if(istype(object,/turf/simulated/floor))
					T.ChangeTurf(/turf/simulated/wall)
				else if(istype(object,/turf/simulated/wall))
					T.ChangeTurf(/turf/simulated/wall/r_wall)
				log_admin("Build Mode: [key_name(usr)] built [T] at ([T.x],[T.y],[T.z])")
				return
			else if(pa.Find("right"))
				log_admin("Build Mode: [key_name(usr)] deleted [object] at ([object.x],[object.y],[object.z])")
				if(istype(object,/turf/simulated/wall))
					var/turf/T = object
					T.ChangeTurf(/turf/simulated/floor/plasteel)
				else if(istype(object,/turf/simulated/floor))
					var/turf/T = object
					T.ChangeTurf(/turf/space)
				else if(istype(object,/turf/simulated/wall/r_wall))
					var/turf/T = object
					T.ChangeTurf(/turf/simulated/wall)
				else if(istype(object,/obj))
					qdel(object)
				return
			else if(istype(object,/turf) && pa.Find("alt") && pa.Find("left"))
				log_admin("Build Mode: [key_name(usr)] built an airlock at ([object.x],[object.y],[object.z])")
				new/obj/machinery/door/airlock(get_turf(object))
			else if(istype(object,/turf) && pa.Find("ctrl") && pa.Find("left"))
				switch(holder.builddir.dir)
					if(NORTH)
						var/obj/structure/window/reinforced/WIN = new/obj/structure/window/reinforced(get_turf(object))
						WIN.dir = NORTH
					if(SOUTH)
						var/obj/structure/window/reinforced/WIN = new/obj/structure/window/reinforced(get_turf(object))
						WIN.dir = SOUTH
					if(EAST)
						var/obj/structure/window/reinforced/WIN = new/obj/structure/window/reinforced(get_turf(object))
						WIN.dir = EAST
					if(WEST)
						var/obj/structure/window/reinforced/WIN = new/obj/structure/window/reinforced(get_turf(object))
						WIN.dir = WEST
					if(NORTHWEST)
						var/obj/structure/window/reinforced/WIN = new/obj/structure/window/reinforced(get_turf(object))
						WIN.dir = NORTHWEST
				log_admin("Build Mode: [key_name(usr)] built a window at ([object.x],[object.y],[object.z])")
		if(2)
			if(pa.Find("left"))
				if(ispath(holder.buildmode.objholder,/turf))
					var/turf/T = get_turf(object)
					log_admin("Build Mode: [key_name(usr)] modified [T] ([T.x],[T.y],[T.z]) to [holder.buildmode.objholder]")
					T.ChangeTurf(holder.buildmode.objholder)
				else
					var/obj/A = new holder.buildmode.objholder (get_turf(object))
					A.dir = holder.builddir.dir
					log_admin("Build Mode: [key_name(usr)] modified [A]'s ([A.x],[A.y],[A.z]) dir to [holder.builddir.dir]")
			else if(pa.Find("right"))
				if(isobj(object))
					log_admin("Build Mode: [key_name(usr)] deleted [object] at ([object.x],[object.y],[object.z])")
					qdel(object)

		if(3)
			if(pa.Find("left")) //I cant believe this shit actually compiles.
				if(object.vars.Find(holder.buildmode.varholder))
					log_admin("Build Mode: [key_name(usr)] modified [object.name]'s [holder.buildmode.varholder] to [holder.buildmode.valueholder]")
					object.vars[holder.buildmode.varholder] = holder.buildmode.valueholder
				else
					usr << "[initial(object.name)] does not have a var called '[holder.buildmode.varholder]'"
			if(pa.Find("right"))
				if(object.vars.Find(holder.buildmode.varholder))
					log_admin("Build Mode: [key_name(usr)] modified [object.name]'s [holder.buildmode.varholder] to [holder.buildmode.valueholder]")
					object.vars[holder.buildmode.varholder] = initial(object.vars[holder.buildmode.varholder])
				else
					usr << "[initial(object.name)] does not have a var called '[holder.buildmode.varholder]'"

		if(4)
			if(pa.Find("left"))
				if(isturf(object))
					return
				holder.throw_atom = object
			if(pa.Find("right"))
				if(holder.throw_atom)
					holder.throw_atom.throw_at(object, 10, 1)
					log_admin("Build Mode: [key_name(usr)] threw [holder.throw_atom] at [object] ([object.x],[object.y],[object.z])")


code:
/client/proc/cinematic(var/cinematic as anything in list("explosion",null))
	set name = "cinematic"
	set category = "Fun"
	set desc = "Shows a cinematic."	// Intended for testing but I thought it might be nice for events on the rare occasion Feel free to comment it out if it's not wanted.
	set hidden = 1
	if(!ticker)	return
	switch(cinematic)
		if("explosion")
			var/parameter = input(src,"station_missed = ?","Enter Parameter",0) as num
			var/override
			switch(parameter)
				if(1)
					override = input(src,"mode = ?","Enter Parameter",null) as anything in list("nuclear emergency","no override")
				if(0)
					override = input(src,"mode = ?","Enter Parameter",null) as anything in list("blob","nuclear emergency","AI malfunction","no override")
			ticker.station_explosion_cinematic(parameter,override)
	return


code:
/client/proc/dsay(msg as text)
	set category = "Special Verbs"
	set name = "Dsay" //Gave this shit a shorter name so you only have to time out "dsay" rather than "dead say" to use it --NeoFite
	set hidden = 1
	if(!src.holder)
		src << "Only administrators may use this command."
		return
	if(!src.mob)
		return
	if(prefs.muted & MUTE_DEADCHAT)
		src << "You cannot send DSAY messages (muted)."
		return

	if (src.handle_spam_prevention(msg,MUTE_DEADCHAT))
		return

	msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
	log_admin("[key_name(src)] : [msg]")

	if (!msg)
		return
	var/nicknames = file2list("config/admin_nicknames.txt")

	var/rendered = "DEAD: ADMIN([src.holder.fakekey ? pick(nicknames) : src.key]) says, \"[msg]\""

	for (var/mob/M in player_list)
		if (istype(M, /mob/new_player))
			continue
		if (M.stat == DEAD || (M.client && M.client.holder && (M.client.prefs.chat_toggles & CHAT_DEAD))) //admins can toggle deadchat on and off. This is a proc in admin.dm and is only give to Administrators and above
			M.show_message(rendered, 2)

	feedback_add_details("admin_verb","D") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!