code:
var/global/BSACooldown = 0
var/global/floorIsLava = 0

////////////////////////////////
/proc/message_admins(var/msg)
	msg = "ADMIN LOG: [msg]"
	admins << msg

///////////////////////////////////////////////////////////////////////////////////////////////Panels

/datum/admins/proc/show_player_panel(var/mob/M in mob_list)
	set category = "Admin"
	set name = "Show Player Panel"
	set desc="Edit player (respawn, ban, heal, etc)"

	if(!check_rights())
		return

	if(!M)
		usr << "You seem to be selecting a mob that doesn't exist anymore."
		return

	var/body = "<title>Options for [M.key]</title>"
	body += "Options panel for [M]"
	if(M.client)
		body += " played by [M.client] "
		body += "\[[M.client.holder ? M.client.holder.rank : "Player"]\]"

	if(istype(M, /mob/new_player))
		body += " Hasn't Entered Game "
	else
		body += " \[Heal\] "

	body += "

\[ "
	body += "VV - "
	body += "TP - "
	body += "PM - "
	body += "SM - "
	body += "JMP\]
"

	body += "Mob type = [M.type]

"

	body += "Kick | "
	body += "Ban | "
	body += "Jobban | "
	body += "Identity Ban | "
	body += "Notes "

	if(M.client)
		body += "| Prison | "
		body += "\ Send back to Lobby | "
		var/muted = M.client.prefs.muted
		body += "
Mute:  "
		body += "\[IC | "
		body += "OOC | "
		body += "PRAY | "
		body += "ADMINHELP | "
		body += "DEADCHAT\]"
		body += "(toggle all)"

	body += "

"
	body += "Jump to | "
	body += "Get | "
	body += "Send To"

	body += "

"
	body += "Traitor panel | "
	body += "Narrate to | "
	body += "Subtle message"

	if (M.client)
		if(!istype(M, /mob/new_player))
			body += "

"
			body += "Transformation:"
			body += "
"

			//Human
			if(ishuman(M))
				body += "Human | "
			else
				body += "Humanize | "

			//Monkey
			if(ismonkey(M))
				body += "Monkeyized | "
			else
				body += "Monkeyize | "

			//Corgi
			if(iscorgi(M))
				body += "Corgized | "
			else
				body += "Corgize | "

			//AI / Cyborg
			if(isAI(M))
				body += "Is an AI "
			else if(ishuman(M))
				body += "Make AI | "
				body += "Make Robot | "
				body += "Make MoMMI | "
				body += "Make Alien | "
				body += "Make Slime | "
				body += "Make Blob | "

			//Simple Animals
			if(isanimal(M))
				body += "Re-Animalize | "
			else
				body += "Animalize | "

			body += "

"
			body += "Rudimentary transformation:
These transformations only create a new mob type and copy stuff over. They do not take into account MMIs and similar mob-specific things. The buttons in 'Transformations' are preferred, when possible.
"
			body += "Observer | "
			body += "\[ Alien: Drone, "
			body += "Hunter, "
			body += "Queen, "
			body += "Sentinel, "
			body += "Larva \] "
			body += "Human "
			body += "\[ slime: Baby, "
			body += "Adult \] "
			body += "Monkey | "
			body += "Cyborg | "
			body += "Cat | "
			body += "Runtime | "
			body += "Corgi | "
			body += "Ian | "
			body += "Crab | "
			body += "Coffee | "
			//body += "Parrot | "
			//body += "Poly | "
			body += "\[ Construct: Armored , "
			body += "Builder , "
			body += "Wraith \] "
			body += "Shade"
			body += "
"

	if (M.client)
		body += "

"
		body += "Other actions:"
		body += "
"
		body += "Forcesay | "
		body += "Thunderdome 1 | "
		body += "Thunderdome 2 | "
		body += "Thunderdome Admin | "
		body += "Thunderdome Observer | "

	body += "
"
	body += ""

	usr << browse(body, "window=adminplayeropts-\ref[M];size=550x515")
	feedback_add_details("admin_verb","SPP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/datum/admins/proc/access_news_network() //MARKER
	set category = "Fun"
	set name = "Access Newscaster Network"
	set desc = "Allows you to view, add and edit news feeds."

	if (!istype(src,/datum/admins))
		src = usr.client.holder
	if (!istype(src,/datum/admins))
		usr << "Error: you are not an admin!"
		return
	var/dat
	dat = text("<TITLE>Admin Newscaster</TITLE>
Admin Newscaster Unit
")

	switch(admincaster_screen)
		if(0)
			dat += "Welcome to the admin newscaster.
 Here you can add, edit and censor every newspiece on the network."
			dat += "
Feed channels and stories entered through here will be uneditable and handled as official news by the rest of the units."
			dat += "
Note that this panel allows full freedom over the news network, there are no constrictions except the few basic ones. Don't break things!"
			if(news_network.wanted_issue)
				dat+= "
Read Wanted Issue"
			dat+= "

Create Feed Channel"
			dat+= "
View Feed Channels"
			dat+= "
Submit new Feed story"
			dat+= "

Exit"
			var/wanted_already = 0
			if(news_network.wanted_issue)
				wanted_already = 1
			dat+="
Feed Security functions:
"
			dat+="
[(wanted_already) ? ("Manage") : ("Publish")] \"Wanted\" Issue"
			dat+="
Censor Feed Stories"
			dat+="
Mark Feed Channel with Nanotrasen D-Notice (disables and locks the channel."
			dat+="

The newscaster recognises you as:
 [src.admincaster_signature]"
		if(1)
			dat+= "Station Feed Channels
"
			if( isemptylist(news_network.network_channels) )
				dat+="No active channels found..."
			else
				for(var/datum/feed_channel/CHANNEL in news_network.network_channels)
					if(CHANNEL.is_admin_channel)
						dat+="[CHANNEL.channel_name]
"
					else
						dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ()]
"
			dat+="

Refresh"
			dat+="
Back"
		if(2)
			dat+="Creating new Feed Channel..."
			dat+="
Channel Name: [src.admincaster_feed_channel.channel_name]
"
			dat+="Channel Author: [src.admincaster_signature]
"
			dat+="Will Accept Public Feeds: [(src.admincaster_feed_channel.locked) ? ("NO") : ("YES")]

"
			dat+="
Submit

Cancel
"
		if(3)
			dat+="Creating new Feed Message..."
			dat+="
Receiving Channel: [src.admincaster_feed_channel.channel_name]
" //MARK
			dat+="Message Author: [src.admincaster_signature]
"
			dat+="Message Body: [src.admincaster_feed_message.body]
"
			dat+="
Submit

Cancel
"
		if(4)
			dat+="Feed story successfully submitted to [src.admincaster_feed_channel.channel_name].

"
			dat+="
Return
"
		if(5)
			dat+="Feed Channel [src.admincaster_feed_channel.channel_name] created successfully.

"
			dat+="
Return
"
		if(6)
			dat+="ERROR: Could not submit Feed story to Network.

"
			if(src.admincaster_feed_channel.channel_name=="")
				dat+="�Invalid receiving channel name.
"
			if(src.admincaster_feed_message.body == "" || src.admincaster_feed_message.body == "\[REDACTED\]")
				dat+="�Invalid message body.
"
			dat+="
Return
"
		if(7)
			dat+="ERROR: Could not submit Feed Channel to Network.

"
			if(src.admincaster_feed_channel.channel_name =="" || src.admincaster_feed_channel.channel_name == "\[REDACTED\]")
				dat+="�Invalid channel name.
"
			var/check = 0
			for(var/datum/feed_channel/FC in news_network.network_channels)
				if(FC.channel_name == src.admincaster_feed_channel.channel_name)
					check = 1
					break
			if(check)
				dat+="�Channel name already in use.
"
			dat+="
Return
"
		if(9)
			dat+="[src.admincaster_feed_channel.channel_name]: \[created by: [src.admincaster_feed_channel.author]\]
"
			if(src.admincaster_feed_channel.censored)
				dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
"
				dat+="No further feed story additions are allowed while the D-Notice is in effect.

"
			else
				if( isemptylist(src.admincaster_feed_channel.messages) )
					dat+="No feed messages found in channel...
"
				else
					var/i = 0
					for(var/datum/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
						i++
						dat+="-[MESSAGE.body]
"
						if(MESSAGE.img)
							usr << browse_rsc(MESSAGE.img, "tmp_photo[i].png")
							dat+="

"
						dat+="\[Story by [MESSAGE.author]\]
"
						dat+="[MESSAGE.comments.len] comment[MESSAGE.comments.len > 1 ? "s" : ""]:
"
						for(var/datum/feed_comment/comment in MESSAGE.comments)
							dat+="[comment.body]
[comment.author] [comment.time_stamp]
"
						dat+="
"
			dat+="

Refresh"
			dat+="
Back"
		if(10)
			dat+="Nanotrasen Feed Censorship Tool
"
			dat+="NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.
"
			dat+="Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it."
			dat+="
Select Feed channel to get Stories from:
"
			if(isemptylist(news_network.network_channels))
				dat+="No feed channels found active...
"
			else
				for(var/datum/feed_channel/CHANNEL in news_network.network_channels)
					dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ()]
"
			dat+="
Cancel"
		if(11)
			dat+="Nanotrasen D-Notice Handler
"
			dat+="A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's"
			dat+="morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed"
			dat+="stories it might contain at the time. You can lift a D-Notice if you have the required access at any time.
"
			if(isemptylist(news_network.network_channels))
				dat+="No feed channels found active...
"
			else
				for(var/datum/feed_channel/CHANNEL in news_network.network_channels)
					dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ()]
"

			dat+="
Back"
		if(12)
			dat+="[src.admincaster_feed_channel.channel_name]: \[ created by: [src.admincaster_feed_channel.author] \]
"
			dat+="[(src.admincaster_feed_channel.author=="\[REDACTED\]") ? ("Undo Author censorship") : ("Censor channel Author")]
"

			if( isemptylist(src.admincaster_feed_channel.messages) )
				dat+="No feed messages found in channel...
"
			else
				for(var/datum/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
					dat+="-[MESSAGE.body]
\[Story by [MESSAGE.author]\]
"
					dat+="[(MESSAGE.body == "\[REDACTED\]") ? ("Undo story censorship") : ("Censor story")]  -  [(MESSAGE.author == "\[REDACTED\]") ? ("Undo Author Censorship") : ("Censor message Author")]
"
					dat+="[MESSAGE.comments.len] comment[MESSAGE.comments.len > 1 ? "s" : ""]: [MESSAGE.locked ? "Unlock" : "Lock"]
"
					for(var/datum/feed_comment/comment in MESSAGE.comments)
						dat+="[comment.body] X
[comment.author] [comment.time_stamp]
"
			dat+="
Back"
		if(13)
			dat+="[src.admincaster_feed_channel.channel_name]: \[ created by: [src.admincaster_feed_channel.author] \]
"
			dat+="Channel messages listed below. If you deem them dangerous to the station, you can Bestow a D-Notice upon the channel.
"
			if(src.admincaster_feed_channel.censored)
				dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
"
				dat+="No further feed story additions are allowed while the D-Notice is in effect.

"
			else
				if( isemptylist(src.admincaster_feed_channel.messages) )
					dat+="No feed messages found in channel...
"
				else
					for(var/datum/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
						dat+="-[MESSAGE.body]
\[Story by [MESSAGE.author]\]
"
			dat+="
Back"
		if(14)
			dat+="Wanted Issue Handler:"
			var/wanted_already = 0
			var/end_param = 1
			if(news_network.wanted_issue)
				wanted_already = 1
				end_param = 2
			if(wanted_already)
				dat+="
A wanted issue is already in Feed Circulation. You can edit or cancel it below."
			dat+="
"
			dat+="Criminal Name: [src.admincaster_feed_message.author]
"
			dat+="Description: [src.admincaster_feed_message.body]
"
			if(wanted_already)
				dat+="Wanted Issue created by: [news_network.wanted_issue.backup_author]
"
			else
				dat+="Wanted Issue will be created under prosecutor: [src.admincaster_signature]
"
			dat+="
[(wanted_already) ? ("Edit Issue") : ("Submit")]"
			if(wanted_already)
				dat+="
Take down Issue"
			dat+="
Cancel"
		if(15)
			dat+="Wanted issue for [src.admincaster_feed_message.author] is now in Network Circulation.

"
			dat+="
Return
"
		if(16)
			dat+="ERROR: Wanted Issue rejected by Network.

"
			if(src.admincaster_feed_message.author =="" || src.admincaster_feed_message.author == "\[REDACTED\]")
				dat+="�Invalid name for person wanted.
"
			if(src.admincaster_feed_message.body == "" || src.admincaster_feed_message.body == "\[REDACTED\]")
				dat+="�Invalid description.
"
			dat+="
Return
"
		if(17)
			dat+="Wanted Issue successfully deleted from Circulation
"
			dat+="
Return
"
		if(18)
			dat+="-- STATIONWIDE WANTED ISSUE --
\[Submitted by: [news_network.wanted_issue.backup_author]\]
"
			dat+="Criminal: [news_network.wanted_issue.author]
"
			dat+="Description: [news_network.wanted_issue.body]
"
			dat+="Photo:: "
			if(news_network.wanted_issue.img)
				usr << browse_rsc(news_network.wanted_issue.img, "tmp_photow.png")
				dat+="
"
			else
				dat+="None"
			dat+="
Back
"
		if(19)
			dat+="Wanted issue for [src.admincaster_feed_message.author] successfully edited.

"
			dat+="
Return
"
		else
			dat+="I'm sorry to break your immersion. This shit's bugged. Report this bug to Agouri, polyxenitopalidou@gmail.com"

	//world << "Channelname: [src.admincaster_feed_channel.channel_name] [src.admincaster_feed_channel.author]"
	//world << "Msg: [src.admincaster_feed_message.author] [src.admincaster_feed_message.body]"
	usr << browse(dat, "window=admincaster_main;size=400x600")
	onclose(usr, "admincaster_main")

/datum/admins/proc/Jobbans()
	if(!check_rights(R_BAN))	return

	var/dat = "Job Bans!

"
	for(var/t in jobban_keylist)
		var/r = t
		if( findtext(r,"##") )
			r = copytext( r, 1, findtext(r,"##") )//removes the description
		dat += text("
[t] (unban)
")
	dat += "
"
	usr << browse(dat, "window=ban;size=400x400")

/datum/admins/proc/Game()
	if(!check_rights(0))	return

	var/dat = {"
		Game Panel
\n
		Change Game Mode

		"}
	if(master_mode == "secret")
		dat += "(Force Secret Mode)
"

	dat += {"

		Create Object

		Quick Create Object

		Create Turf

		Create Mob

		"}

	usr << browse(dat, "window=admin2;size=210x180")
	return

/datum/admins/proc/Secrets()
	if(!check_rights(0))	return

	var/dat = "The first rule of adminbuse is: you don't talk about the adminbuse.
"

	dat +={"
			General Secrets

			Show Job Debug

			Admin Log

			Show Admin List

			"}

	if(check_rights(R_ADMIN,0))
		dat += {"
			Admin Secrets

			Cure all diseases currently in existence

			Bombing List

			Show current traitors and objectives

			Show last [length(lastsignalers)] signalers

			Show last [length(lawchanges)] law changes

			Show AI Laws

			Show Game Mode

			Show Crew Manifest

			List DNA (Blood)

			List Fingerprints

			Shuttles

			Move Ferry

			Move Mining Shuttle

			Move Labor Shuttle

			"}

	if(check_rights(R_FUN,0))
		dat += {"
			Fun Secrets

			Reset Thunderdome to default state

			Trigger a Virus Outbreak

			Turn all humans into monkeys

			Change the species of all humans

			Make all areas powered

			Make all areas unpowered

			Power all SMES

			Triple AI mode (needs to be used in the lobby)

			Everyone is the traitor

			Summon Guns

			Summon Magic

			Summon Events (Toggle)

			There can only be one!

			Make all players retarded

			Egalitarian Station Mode

			Break all lights

			Fix all lights

			Japanese Animes Mode

			The floor is lava! (DANGEROUS: extremely lame)

			Final Solutions

			(Warning, these will end the round!)

			Summon Nar-Sie

			Start a Supermatter Cascade

			"}

/* DEATH SQUADS
Send in a strike team

*/
	if(check_rights(R_SERVER,0))
		dat += "Toggle bomb cap
"

	dat += "
"

	if(check_rights(R_DEBUG,0))
		dat += {"
			Security Level Elevated

			Change all maintenance doors to engie/brig access only

			Change all maintenance doors to brig access only

			Remove cap on security officers

			"}

	usr << browse(dat, "window=secrets")
	return

/////////////////////////////////////////////////////////////////////////////////////////////////admins2.dm merge
//i.e. buttons/verbs

/datum/admins/proc/restart()
	set category = "Server"
	set name = "Restart"
	set desc="Restarts the world immediately"
	if (!usr.client.holder)
		return
	var/confirm = alert("Restart the game world?", "Restart", "Yes", "Cancel")
	if(confirm == "Cancel")
		return
	if(confirm == "Yes")
		log_admin("[key_name(usr)] initiated a reboot.")
		ticker.delay_end = 0

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

		world.Reboot("Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key].", "end_error", "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 10)

/datum/admins/proc/announce()
	set category = "Special Verbs"
	set name = "Announce"
	set desc="Announce your desires to the world"
	if(!check_rights(0))	return

	var/message = input("Global message to send:", "Admin Announce", null, null)  as message
	if(message)
		if(!check_rights(R_SERVER,0))
			message = adminscrub(message,500)
		world << "[usr.client.holder.fakekey ? "Administrator" : usr.key] Announces:\n \t [message]"
		log_admin("Announce: [key_name(usr)] : [message]")
	feedback_add_details("admin_verb","A") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/datum/admins/proc/set_admin_notice()
	set category = "Special Verbs"
	set name = "Set Admin Notice"
	set desc ="Set an announcement that appears to everyone who joins the server. Only lasts this round"
	if(!check_rights(0))	return

	var/new_admin_notice = input(src,"Set a public notice for this round. Everyone who joins the server will see it.\n(Leaving it blank will delete the current notice):","Set Notice",admin_notice) as message|null
	if(new_admin_notice == null)
		return
	if(new_admin_notice == admin_notice)
		return
	if(new_admin_notice == "")
		message_admins("[key_name(usr)] removed the admin notice.")
		log_admin("[key_name(usr)] removed the admin notice:\n[admin_notice]")
	else
		message_admins("[key_name(usr)] set the admin notice.")
		log_admin("[key_name(usr)] set the admin notice:\n[new_admin_notice]")
		world << "Admin Notice:\n \t [new_admin_notice]"
	feedback_add_details("admin_verb","SAN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	admin_notice = new_admin_notice
	return

/datum/admins/proc/toggleooc()
	set category = "Server"
	set desc="Toggle dis bitch"
	set name="Toggle OOC"
	toggle_ooc()
	log_admin("[key_name(usr)] toggled OOC.")
	message_admins("[key_name_admin(usr)] toggled OOC.")
	feedback_add_details("admin_verb","TOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/datum/admins/proc/toggleoocdead()
	set category = "Server"
	set desc="Toggle dis bitch"
	set name="Toggle Dead OOC"
	dooc_allowed = !( dooc_allowed )

	log_admin("[key_name(usr)] toggled OOC.")
	message_admins("[key_name_admin(usr)] toggled Dead OOC.")
	feedback_add_details("admin_verb","TDOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/*
/datum/admins/proc/toggletraitorscaling()
	set category = "Server"
	set desc="Toggle traitor scaling"
	set name="Toggle Traitor Scaling"
	traitor_scaling = !traitor_scaling
	log_admin("[key_name(usr)] toggled Traitor Scaling to [traitor_scaling].")
	message_admins("[key_name_admin(usr)] toggled Traitor Scaling [traitor_scaling ? "on" : "off"].")
	feedback_add_details("admin_verb","TTS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
*/
/datum/admins/proc/startnow()
	set category = "Server"
	set desc="Start the round RIGHT NOW"
	set name="Start Now"
	if(ticker.current_state == GAME_STATE_PREGAME)
		ticker.can_fire = 1
		ticker.timeLeft = 0
		log_admin("[usr.key] has started the game.")
		message_admins("[usr.key] has started the game.")
		feedback_add_details("admin_verb","SN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
		return 1
	else
		usr << "Error: Start Now: Game has already started."
		return 0

/datum/admins/proc/toggleenter()
	set category = "Server"
	set desc="People can't enter"
	set name="Toggle Entering"
	enter_allowed = !( enter_allowed )
	if (!( enter_allowed ))
		world << "New players may no longer enter the game."
	else
		world << "New players may now enter the game."
	log_admin("[key_name(usr)] toggled new player game entering.")
	message_admins("[key_name_admin(usr)] toggled new player game entering.")
	world.update_status()
	feedback_add_details("admin_verb","TE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/datum/admins/proc/toggleAI()
	set category = "Server"
	set desc="People can't be AI"
	set name="Toggle AI"
	config.allow_ai = !( config.allow_ai )
	if (!( config.allow_ai ))
		world << "The AI job is no longer chooseable."
	else
		world << "The AI job is chooseable now."
	log_admin("[key_name(usr)] toggled AI allowed.")
	world.update_status()
	feedback_add_details("admin_verb","TAI") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/datum/admins/proc/toggleaban()
	set category = "Server"
	set desc="Respawn basically"
	set name="Toggle Respawn"
	abandon_allowed = !( abandon_allowed )
	if (abandon_allowed)
		world << "You may now respawn."
	else
		world << "You may no longer respawn :("
	message_admins("[key_name_admin(usr)] toggled respawn to [abandon_allowed ? "On" : "Off"].")
	log_admin("[key_name(usr)] toggled respawn to [abandon_allowed ? "On" : "Off"].")
	world.update_status()
	feedback_add_details("admin_verb","TR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/datum/admins/proc/delay()
	set category = "Server"
	set desc="Delay the game start"
	set name="Delay"
	if(ticker.current_state > GAME_STATE_PREGAME)
		return alert("Too late... The game has already started!")
	ticker.can_fire = !ticker.can_fire
	if(!ticker.can_fire)
		world << "The game start has been delayed."
		log_admin("[key_name(usr)] delayed the game.")
	else
		world << "The game will start soon."
		log_admin("[key_name(usr)] removed the delay.")
	feedback_add_details("admin_verb","DELAY") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/datum/admins/proc/unprison(var/mob/M in mob_list)
	set category = "Admin"
	set name = "Unprison"
	if (M.z == ZLEVEL_CENTCOM)
		M.loc = pick(latejoin)
		message_admins("[key_name_admin(usr)] has unprisoned [key_name_admin(M)]")
		log_admin("[key_name(usr)] has unprisoned [key_name(M)]")
	else
		alert("[M.name] is not prisoned.")
	feedback_add_details("admin_verb","UP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

////////////////////////////////////////////////////////////////////////////////////////////////ADMIN HELPER PROCS

/*
/datum/admins/proc/get_sab_desc(var/target)
	switch(target)
		if(1)
			return "Destroy at least 70% of the plasma canisters on the station"
		if(2)
			return "Destroy the AI"
		if(3)
			var/count = 0
			for(var/mob/living/carbon/monkey/Monkey in world)
				if(Monkey.z == 1)
					count++
			return "Kill all [count] of the monkeys on the station"
		if(4)
			return "Cut power to at least 80% of the station"
		else
			return "Error: Invalid sabotage target: [target]"
*/
/datum/admins/proc/spawn_atom(var/object as text)
	set category = "Debug"
	set desc = "(atom path) Spawn an atom"
	set name = "Spawn"

	if(!check_rights(R_SPAWN))	return

	var/list/matches = get_fancy_list_of_types()
	if (!isnull(object) && object!="")
		matches = filter_fancy_list(matches, object)

	if(matches.len==0)
		return

	var/chosen
	if(matches.len==1)
		chosen = matches[1]
	else
		chosen = input("Select an atom type", "Spawn Atom", matches[1]) as null|anything in matches
		if(!chosen)
			return
	chosen = matches[chosen]

	if(ispath(chosen,/turf))
		var/turf/T = get_turf(usr.loc)
		T.ChangeTurf(chosen)
	else
		new chosen(usr.loc)

	log_admin("[key_name(usr)] spawned [chosen] at ([usr.x],[usr.y],[usr.z])")
	feedback_add_details("admin_verb","SA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/datum/admins/proc/show_traitor_panel(var/mob/M in mob_list)
	set category = "Admin"
	set desc = "Edit mobs's memory and role"
	set name = "Show Traitor Panel"

	if(!istype(M))
		usr << "This can only be used on instances of type /mob"
		return
	if(!M.mind)
		usr << "This mob has no mind!"
		return

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

/datum/admins/proc/toggletintedweldhelmets()
	set category = "Debug"
	set desc="Reduces view range when wearing welding helmets"
	set name="Toggle tinted welding helmes"
	tinted_weldhelh = !( tinted_weldhelh )
	if (tinted_weldhelh)
		world << "The tinted_weldhelh has been enabled!"
	else
		world << "The tinted_weldhelh has been disabled!"
	log_admin("[key_name(usr)] toggled tinted_weldhelh.")
	message_admins("[key_name_admin(usr)] toggled tinted_weldhelh.")
	feedback_add_details("admin_verb","TTWH") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/datum/admins/proc/toggleguests()
	set category = "Server"
	set desc="Guests can't enter"
	set name="Toggle guests"
	guests_allowed = !( guests_allowed )
	if (!( guests_allowed ))
		world << "Guests may no longer enter the game."
	else
		world << "Guests may now enter the game."
	log_admin("[key_name(usr)] toggled guests game entering [guests_allowed?"":"dis"]allowed.")
	message_admins("[key_name_admin(usr)] toggled guests game entering [guests_allowed?"":"dis"]allowed.")
	feedback_add_details("admin_verb","TGU") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/client/proc/unjobban_panel()
	set name = "Unjobban Panel"
	set category = "Admin"
	if (src.holder)
		src.holder.unjobbanpanel()
	feedback_add_details("admin_verb","UJBP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	return

/datum/admins/proc/output_ai_laws()
	var/ai_number = 0
	for(var/mob/living/silicon/S in mob_list)
		ai_number++
		if(isAI(S))
			usr << "AI [key_name(S, usr)]'s laws:"
		else if(isrobot(S) && !ismommi(S))
			var/mob/living/silicon/robot/R = S
			usr << "CYBORG [key_name(S, usr)] [R.connected_ai?"(Slaved to: [R.connected_ai])":"(Independant)"]: laws:"
		else if(ismommi(S))
			var/mob/living/silicon/robot/mommi/R = S
			usr << "MoMMI [key_name(S, usr)] [R.connected_ai?"(Slaved to: [R.connected_ai])":"(Independant)"]: laws:"
		else if (ispAI(S))
			usr << "pAI [key_name(S, usr)]'s laws:"
		else
			usr << "SOMETHING SILICON [key_name(S, usr)]'s laws:"

		if (S.laws == null)
			usr << "[key_name(S, usr)]'s laws are null?? Contact a coder."
		else
			S.laws.show_laws(usr)
	if(!ai_number)
		usr << "No AIs located" //Just so you know the thing is actually working and not just ignoring you.

/datum/admins/proc/manage_free_slots()
	if(!check_rights())
		return
	var/dat = "<title>Manage Free Slots</title>"
	var/count = 0

	if(ticker && !ticker.mode)
		alert(usr, "You cannot manage jobs before the round starts!")
		return

	if(SSjob)
		for(var/datum/job/job in SSjob.occupations)
			count++
			var/J_title = html_encode(job.title)
			var/J_opPos = html_encode(job.total_positions - (job.total_positions - job.current_positions))
			var/J_totPos = html_encode(job.total_positions)
			if(job.total_positions < 0)
				dat += "[J_title]: [J_opPos]   (unlimited)"
			else
				dat += "[J_title]: [J_opPos]/[J_totPos]"

			if(job.title == "AI" || job.title == "Cyborg")
				dat += "   (Cannot Late Join)
"
				continue
			if(job.total_positions >= 0)
				dat += "   Add  |  "
				if(job.total_positions > job.current_positions)
					dat += "Remove  |  "
				else
					dat += "Remove  |  "
				dat += "Unlimit"
			else
				dat += "   Limit"
			dat += "
"

	dat += ""
	var/winheight = 100 + (count * 20)
	winheight = min(winheight, 690)
	usr << browse(dat, "window=players;size=375x[winheight]")

//
//
//ALL DONE
//*********************************************************************************************************
//TO-DO:
//
//

//RIP ferry snowflakes

//Kicks all the clients currently in the lobby. The second parameter (kick_only_afk) determins if an is_afk() check is ran, or if all clients are kicked
//defaults to kicking everyone (afk + non afk clients in the lobby)
//returns a list of ckeys of the kicked clients
proc/kick_clients_in_lobby(var/message, var/kick_only_afk = 0)
	var/list/kicked_client_names = list()
	for(var/client/C in clients)
		if(istype(C.mob, /mob/new_player))
			if(kick_only_afk && !C.is_afk())	//Ignore clients who are not afk
				continue
			if(message)
				C << message
			kicked_client_names.Add("[C.ckey]")
			del(C)
	return kicked_client_names


code:
//By Carnwennan

//This system was made as an alternative to all the in-game lists and variables used to log stuff in-game.
//lists and variables are great. However, they have several major flaws:
//Firstly, they use memory. TGstation has one of the highest memory usage of all the ss13 branches.
//Secondly, they are usually stored in an object. This means that they aren't centralised. It also means that
//the data is lost when the object is deleted! This is especially annoying for things like the singulo engine!
#define INVESTIGATE_DIR "data/investigate/"

//SYSTEM
/proc/investigate_subject2file(var/subject)
	return file("[INVESTIGATE_DIR][subject].html")

/proc/investigate_reset()
	if(fdel(INVESTIGATE_DIR))	return 1
	return 0

/atom/proc/investigate_log(var/message, var/subject)
	if(!message)	return
	var/F = investigate_subject2file(subject)
	if(!F)	return
	F << "[time2text(world.timeofday,"hh:mm")] \ref[src] ([x],[y],[z]) || [src] [message]
"

//ADMINVERBS
/client/proc/investigate_show( subject in list("hrefs","notes","ntsl","singulo","wires","telesci", "gravity", "records", "cargo", "supermatter", "atmos", "experimentor") )
	set name = "Investigate"
	set category = "Admin"
	if(!holder)	return
	switch(subject)
		if("singulo", "ntsl", "wires", "telesci", "gravity", "records", "cargo", "supermatter", "atmos")			//general one-round-only stuff
			var/F = investigate_subject2file(subject)
			if(!F)
				src << "Error: admin_investigate: [INVESTIGATE_DIR][subject] is an invalid path or cannot be accessed."
				return
			src << browse(F,"window=investigate[subject];size=800x300")

		if("hrefs")				//persistant logs and stuff
			if(config && config.log_hrefs)
				if(href_logfile)
					src << browse(href_logfile,"window=investigate[subject];size=800x300")
				else
					src << "Error: admin_investigate: No href logfile found."
					return
			else
				src << "Error: admin_investigate: Href Logging is not on."
				return
		if("notes")
			holder.notes_show()

license: mit


code:
#define MEMOFILE "data/memo.sav"	//where the memos are saved
#define ENABLE_MEMOS 1				//using a define because screw making a config variable for it. This is more efficient and purty.

//switch verb so we don't spam up the verb lists with like, 3 verbs for this feature.
/client/proc/admin_memo(task in list("write","show","delete"))
	set name = "Memo"
	set category = "Server"
	if(!ENABLE_MEMOS)		return
	if(!check_rights(0))	return
	switch(task)
		if("write")		admin_memo_write()
		if("show")		admin_memo_show()
		if("delete")	admin_memo_delete()

//write a message
/client/proc/admin_memo_write()
	var/savefile/F = new(MEMOFILE)
	if(F)
		var/memo = input(src,"Type your memo\n(Leaving it blank will delete your current memo):","Write Memo",null) as null|message
		switch(memo)
			if(null)
				return
			if("")
				message_admins("[src.ckey] removed their own Memo")
				log_admin("[src.ckey] removed their own Memo")
				F.dir.Remove(ckey)
				return
		if( findtext(memo,"


code:
var/list/admin_ranks = list()								//list of all admin_rank datums

/datum/admin_rank
	var/name = "NoRank"
	var/rights = 0
	var/list/adds
	var/list/subs

/datum/admin_rank/New(init_name, init_rights, list/init_adds, list/init_subs)
	name = init_name
	switch(name)
		if("Removed",null,"")
			ERROR("invalid admin-rank name. datum deleted")
			del(src)
	if(init_rights)	rights = init_rights
	if(!init_adds)	init_adds = list()
	if(!init_subs)	init_subs = list()
	adds = init_adds
	subs = init_subs

/proc/admin_keyword_to_flag(word, previous_rights=0)
	var/flag = 0
	switch(ckey(word))
		if("buildmode","build")			flag = R_BUILDMODE
		if("admin")						flag = R_ADMIN
		if("ban")						flag = R_BAN
		if("fun")						flag = R_FUN
		if("server")					flag = R_SERVER
		if("debug")						flag = R_DEBUG
		if("permissions","rights")		flag = R_PERMISSIONS
		if("possess")					flag = R_POSSESS
		if("stealth")					flag = R_STEALTH
		if("rejuv","rejuvinate")		flag = R_REJUVINATE
		if("varedit")					flag = R_VAREDIT
		if("everything","host","all")	flag = 65535
		if("sound","sounds")			flag = R_SOUNDS
		if("spawn","create")			flag = R_SPAWN
		if("@","prev")					flag = previous_rights
	return flag

/proc/admin_keyword_to_path(word) //use this with verb keywords eg +/client/proc/blah
	return text2path(copytext(word,2,findtext(word," ",2,0)))

// Adds/removes rights to this admin_rank
/datum/admin_rank/proc/process_keyword(word, previous_rights=0)
	var/flag = admin_keyword_to_flag(word, previous_rights)
	if(flag)
		switch(text2ascii(word,1))
			if(43)	rights |= flag	//+
			if(45)	rights &= ~flag	//-
	else
		//isn't a keyword so maybe it's a verbpath?
		var/path = admin_keyword_to_path(word)
		if(path)
			switch(text2ascii(word,1))
				if(43)
					if(!subs.Remove(path))
						adds += path	//+
				if(45)
					if(!adds.Remove(path))
						subs += path	//-

// Checks for (keyword-formatted) rights on this admin
/datum/admins/proc/check_keyword(word)
	var/flag = admin_keyword_to_flag(word)
	if(flag)
		return ((rank.rights & flag) == flag) //true only if right has everything in flag
	else
		var/path = admin_keyword_to_path(word)
		for(var/i in owner.verbs) //this needs to be a foreach loop for some reason. in operator and verbs.Find() don't work
			if(i == path)
				return 1
		return 0

//load our rank - > rights associations
/proc/load_admin_ranks()
	admin_ranks.Cut()

	if(config.admin_legacy_system)
		var/previous_rights = 0
		//load text from file and process each line seperately
		for(var/line in file2list("config/admin_ranks.txt"))
			if(!line)						continue
			if(findtextEx(line,"#",1,2))	continue

			var/next = findtext(line, "=")
			var/datum/admin_rank/R = new(ckeyEx(copytext(line, 1, next)))
			if(!R)	continue
			admin_ranks += R

			var/prev = findchar(line, "+-", next, 0)
			while(prev)
				next = findchar(line, "+-", prev+1, 0)
				R.process_keyword(copytext(line, prev, next), previous_rights)
				prev = next

			previous_rights = R.rights
	else
		establish_db_connection()
		if(!dbcon.IsConnected())
			world.log << "Failed to connect to database in load_admin_ranks(). Reverting to legacy system."
			diary << "Failed to connect to database in load_admin_ranks(). Reverting to legacy system."
			config.admin_legacy_system = 1
			load_admin_ranks()
			return

		var/DBQuery/query = dbcon.NewQuery("SELECT rank, flags FROM [format_table_name("admin_ranks")]")
		query.Execute()
		while(query.NextRow())
			var/rank_name = ckeyEx(query.item[1])
			var/flags = query.item[2]
			if(istext(flags))	flags = text2num(flags)
			var/datum/admin_rank/R = new(rank_name, flags)
			if(!R)	continue
			admin_ranks += R

	#ifdef TESTING
	var/msg = "Permission Sets Built:\n"
	for(var/datum/admin_rank/R in admin_ranks)
		msg += "\t[R.name]"
		var/rights = rights2text(R.rights,"\n\t\t",R.adds,R.subs)
		if(rights)	msg += "\t\t[rights]\n"
	testing(msg)
	#endif

/proc/load_admins()
	//clear the datums references
	admin_datums.Cut()
	for(var/client/C in admins)
		C.remove_admin_verbs()
		C.holder = null
	admins.Cut()
	load_admin_ranks()

	var/list/rank_names = list()
	for(var/datum/admin_rank/R in admin_ranks)
		rank_names[R.name] = R

	if(config.admin_legacy_system)
		//load text from file
		var/list/Lines = file2list("config/admins.txt")

		//process each line seperately
		for(var/line in Lines)
			if(!length(line))				continue
			if(findtextEx(line,"#",1,2))	continue

			//Split the line at every "="
			var/list/List = text2list(line, "=")
			if(!List.len)					continue

			//ckey is before the first "="
			var/ckey = ckey(List[1])
			if(!ckey)						continue

			//rank follows the first "="
			var/rank = ""
			if(List.len >= 2)
				rank = ckeyEx(List[2])

			var/datum/admins/D = new(rank_names[rank], ckey)	//create the admin datum and store it for later use
			if(!D)	continue									//will occur if an invalid rank is provided
			D.associate(directory[ckey])	//find the client for a ckey if they are connected and associate them with the new admin datum

	else
		establish_db_connection()
		if(!dbcon.IsConnected())
			world.log << "Failed to connect to database in load_admins(). Reverting to legacy system."
			diary << "Failed to connect to database in load_admins(). Reverting to legacy system."
			config.admin_legacy_system = 1
			load_admins()
			return

		var/DBQuery/query = dbcon.NewQuery("SELECT ckey, rank FROM [format_table_name("admin")]")
		query.Execute()
		while(query.NextRow())
			var/ckey = ckey(query.item[1])
			var/rank = ckeyEx(query.item[2])
			if(rank_names[rank] == null)
				error("Admin rank ([rank]) does not exist.")
				continue
			var/datum/admins/D = new(rank_names[rank], ckey)				//create the admin datum and store it for later use
			if(!D)	continue									//will occur if an invalid rank is provided
			D.associate(directory[ckey])	//find the client for a ckey if they are connected and associate them with the new admin datum

	#ifdef TESTING
	var/msg = "Admins Built:\n"
	for(var/ckey in admin_datums)
		var/datum/admins/D = admin_datums[ckey]
		msg += "\t[ckey] - [D.rank.name]\n"
	testing(msg)
	#endif

#ifdef TESTING
/client/verb/changerank(newrank in admin_ranks)
	if(holder)
		holder.rank = newrank
	else
		holder = new /datum/admins(newrank,ckey)
	remove_admin_verbs()
	holder.associate(src)

/client/verb/changerights(newrights as num)
	if(holder)
		holder.rank.rights = newrights
	else
		holder = new /datum/admins("testing",newrights,ckey)
	remove_admin_verbs()
	holder.associate(src)

#endif

/datum/admins/proc/edit_rights_topic(list/href_list)
	if(!check_rights(R_PERMISSIONS))
		message_admins("[key_name_admin(usr)] attempted to edit the admin permissions without sufficient rights.")
		log_admin("[key_name(usr)] attempted to edit the admin permissions without sufficient rights.")
		return

	var/adm_ckey
	var/task = href_list["editrights"]
	switch(task)
		if("add")
			var/new_ckey = ckey(input(usr,"New admin's ckey","Admin ckey", null) as text|null)
			if(!new_ckey)	return
			if(new_ckey in admin_datums)
				usr << "Error: Topic 'editrights': [new_ckey] is already an admin"
				return
			adm_ckey = new_ckey
			task = "rank"
		else
			adm_ckey = ckey(href_list["ckey"])
			if(!adm_ckey)
				usr << "Error: Topic 'editrights': No valid ckey"
				return

	var/datum/admins/D = admin_datums[adm_ckey]

	switch(task)
		if("remove")
			if(alert("Are you sure you want to remove [adm_ckey]?","Message","Yes","Cancel") == "Yes")
				if(!D)	return
				if(!check_if_greater_rights_than_holder(D))
					message_admins("[key_name_admin(usr)] attempted to remove [adm_ckey] from the admins list without sufficient rights.")
					log_admin("[key_name(usr)] attempted to remove [adm_ckey] from the admins list without sufficient rights.")
					return
				admin_datums -= adm_ckey
				D.disassociate()

				message_admins("[key_name_admin(usr)] removed [adm_ckey] from the admins list")
				log_admin("[key_name(usr)] removed [adm_ckey] from the admins list")
				log_admin_rank_modification(adm_ckey, "Removed")

		if("rank")
			var/datum/admin_rank/R

			var/list/rank_names = list("*New Rank*")
			for(R in admin_ranks)
				rank_names[R.name] = R

			var/new_rank = input("Please select a rank", "New rank", null, null) as null|anything in rank_names

			switch(new_rank)
				if(null)	return
				if("*New Rank*")
					new_rank = ckeyEx(input("Please input a new rank", "New custom rank", null, null) as null|text)
					if(!new_rank)	return

			if(D)
				if(!check_if_greater_rights_than_holder(D))
					message_admins("[key_name_admin(usr)] attempted to change the rank of [adm_ckey] to [new_rank] without sufficient rights.")
					log_admin("[key_name(usr)] attempted to change the rank of [adm_ckey] to [new_rank] without sufficient rights.")
					return

			R = rank_names[new_rank]
			if(!R)	//rank with that name doesn't exist yet - make it
				if(D)	R = new(new_rank, D.rank.rights, D.rank.adds, D.rank.subs)	//duplicate our previous admin_rank but with a new name
				else	R = new(new_rank)							//blank new admin_rank
				admin_ranks += R

			if(D)	//they were previously an admin
				D.disassociate()	//existing admin needs to be disassociated
				D.rank = R			//set the admin_rank as our rank
			else
				D = new(R,adm_ckey)	//new admin

			var/client/C = directory[adm_ckey]	//find the client with the specified ckey (if they are logged in)
			D.associate(C)						//link up with the client and add verbs

			message_admins("[key_name_admin(usr)] edited the admin rank of [adm_ckey] to [new_rank]")
			log_admin("[key_name(usr)] edited the admin rank of [adm_ckey] to [new_rank]")
			log_admin_rank_modification(adm_ckey, new_rank)

		if("permissions")
			if(!D)	return	//they're not an admin!

			var/keyword = input("Input permission keyword (one at a time):\ne.g. +BAN or -FUN or +/client/proc/someverb", "Permission toggle", null, null) as null|text
			if(!keyword)	return

			if(!check_keyword(keyword) || !check_if_greater_rights_than_holder(D))
				message_admins("[key_name_admin(usr)] attempted to give [adm_ckey] the keyword [keyword] without sufficient rights.")
				log_admin("[key_name(usr)] attempted to give [adm_ckey] the keyword [keyword] without sufficient rights.")
				return

			D.disassociate()

			if(!findtext(D.rank.name, "([adm_ckey])"))	//not a modified subrank, need to duplicate the admin_rank datum to prevent modifying others too
				D.rank = new("[D.rank.name]([adm_ckey])", D.rank.rights, D.rank.adds, D.rank.subs)	//duplicate our previous admin_rank but with a new name
				//we don't add this clone to the admin_ranks list, as it is unique to that ckey

			D.rank.process_keyword(keyword)

			var/client/C = directory[adm_ckey]	//find the client with the specified ckey (if they are logged in)
			D.associate(C)						//link up with the client and add verbs

			message_admins("[key_name(usr)] added keyword [keyword] to permission of [adm_ckey]")
			log_admin("[key_name(usr)] added keyword [keyword] to permission of [adm_ckey]")
			log_admin_permission_modification(adm_ckey, D.rank.rights)

	edit_admin_permissions()


code:
//admin verb groups - They can overlap if you so wish. Only one of each verb will exist in the verbs list regardless
var/list/admin_verbs_default = list(
	/client/proc/toggleadminhelpsound,	/*toggles whether we hear a sound when adminhelps/PMs are used*/
	/client/proc/deadmin_self,			/*destroys our own admin datum so we can play as a regular player*/
	/client/proc/cmd_admin_say,			/*admin-only ooc chat*/
	/client/proc/hide_verbs,			/*hides all our adminverbs*/
	/client/proc/hide_most_verbs,		/*hides all our hideable adminverbs*/
	/client/proc/debug_variables,		/*allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify*/
	/client/proc/admin_memo,			/*admin memo system. show/delete/write. +SERVER needed to delete admin memos of others*/
	/client/proc/deadchat,				/*toggles deadchat on/off*/
	/client/proc/dsay,					/*talk in deadchat using our ckey/fakekey*/
	/client/proc/toggleprayers,			/*toggles prayers on/off*/
	/client/proc/toggle_hear_radio,		/*toggles whether we hear the radio*/
	/client/proc/investigate_show,		/*various admintools for investigation. Such as a singulo grief-log*/
	/client/proc/secrets,
	/client/proc/reload_admins,
	/client/proc/reestablish_db_connection,/*reattempt a connection to the database*/
	/client/proc/cmd_admin_pm_context,	/*right-click adminPM interface*/
	/client/proc/cmd_admin_pm_panel,		/*admin-pm list*/
	/client/proc/stop_sounds
	)
var/list/admin_verbs_admin = list(
	/client/proc/player_panel,			/*shows an interface for all players, with links to various panels (old style)*/
	/client/proc/player_panel_new,		/*shows an interface for all players, with links to various panels*/
	/client/proc/invisimin,				/*allows our mob to go invisible/visible*/
//	/datum/admins/proc/show_traitor_panel,	/*interface which shows a mob's mind*/ -Removed due to rare practical use. Moved to debug verbs ~Errorage
	/datum/admins/proc/show_player_panel,	/*shows an interface for individual players, with various links (links require additional flags*/
	/client/proc/game_panel,			/*game panel, allows to change game-mode etc*/
	/client/proc/check_ai_laws,			/*shows AI and borg laws*/
	/datum/admins/proc/toggleooc,		/*toggles ooc on/off for everyone*/
	/datum/admins/proc/toggleoocdead,	/*toggles ooc on/off for everyone who is dead*/
	/datum/admins/proc/toggleenter,		/*toggles whether people can join the current game*/
	/datum/admins/proc/toggleguests,	/*toggles whether guests can join the current game*/
	/datum/admins/proc/announce,		/*priority announce something to all clients.*/
	/datum/admins/proc/set_admin_notice,/*announcement all clients see when joining the server.*/
	/client/proc/admin_ghost,			/*allows us to ghost/reenter body at will*/
	/client/proc/toggle_view_range,		/*changes how far we can see*/
	/datum/admins/proc/view_txt_log,	/*shows the server log (diary) for today*/
	/datum/admins/proc/view_atk_log,	/*shows the server combat-log, doesn't do anything presently*/
	/client/proc/cmd_admin_subtle_message,	/*send an message to somebody as a 'voice in their head'*/
	/client/proc/cmd_admin_delete,		/*delete an instance/object/mob/etc*/
	/client/proc/cmd_admin_check_contents,	/*displays the contents of an instance*/
	/client/proc/check_antagonists,		/*shows all antags*/
	/datum/admins/proc/access_news_network,	/*allows access of newscasters*/
	/client/proc/giveruntimelog,		/*allows us to give access to runtime logs to somebody*/
	/client/proc/getruntimelog,			/*allows us to access runtime logs to somebody*/
	/client/proc/getserverlog,			/*allows us to fetch server logs (diary) for other days*/
	/client/proc/jumptocoord,			/*we ghost and jump to a coordinate*/
	/client/proc/Getmob,				/*teleports a mob to our location*/
	/client/proc/Getkey,				/*teleports a mob with a certain ckey to our location*/
//	/client/proc/sendmob,				/*sends a mob somewhere*/ -Removed due to it needing two sorting procs to work, which were executed every time an admin right-clicked. ~Errorage
	/client/proc/jumptoarea,
	/client/proc/jumptokey,				/*allows us to jump to the location of a mob with a certain ckey*/
	/client/proc/jumptomob,				/*allows us to jump to a specific mob*/
	/client/proc/jumptoturf,			/*allows us to jump to a specific turf*/
	/client/proc/admin_call_shuttle,	/*allows us to call the emergency shuttle*/
	/client/proc/admin_cancel_shuttle,	/*allows us to cancel the emergency shuttle, sending it back to centcom*/
	/client/proc/cmd_admin_direct_narrate,	/*send text directly to a player with no padding. Useful for narratives and fluff-text*/
	/client/proc/cmd_admin_world_narrate,	/*sends text to all players with no padding*/
	/client/proc/cmd_admin_create_centcom_report,
	/client/proc/check_words,			/*displays cult-words*/
	/client/proc/reset_all_tcs			/*resets all telecomms scripts*/
	)
var/list/admin_verbs_ban = list(
	/client/proc/unban_panel,
	/client/proc/jobbans,
	/client/proc/unjobban_panel,
	/client/proc/DB_ban_panel
	)
var/list/admin_verbs_sounds = list(
	/client/proc/play_local_sound,
	/client/proc/play_sound,
	)
var/list/admin_verbs_fun = list(
	/client/proc/cmd_admin_dress,
	/client/proc/cmd_admin_gib_self,
	/client/proc/drop_bomb,
	/client/proc/cinematic,
	/client/proc/one_click_antag,
	/client/proc/send_space_ninja,
	/client/proc/cmd_admin_add_freeform_ai_law,
	/client/proc/cmd_admin_add_random_ai_law,
	/client/proc/object_say,
	/client/proc/toggle_random_events,
	/client/proc/set_ooc,
	/client/proc/forceEvent
	)
var/list/admin_verbs_spawn = list(
	/datum/admins/proc/spawn_atom,		/*allows us to spawn instances*/
	/client/proc/respawn_character
	)
var/list/admin_verbs_server = list(
	/datum/admins/proc/startnow,
	/datum/admins/proc/restart,
	/datum/admins/proc/delay,
	/datum/admins/proc/toggleaban,
	/client/proc/toggle_log_hrefs,
	/client/proc/everyone_random,
	/datum/admins/proc/toggleAI,
	/client/proc/cmd_admin_delete,		/*delete an instance/object/mob/etc*/
	/client/proc/cmd_debug_del_all,
	/client/proc/toggle_random_events,
	/client/proc/panicbunker
	)
var/list/admin_verbs_debug = list(
	/client/proc/restart_controller,
	/client/proc/cmd_admin_list_open_jobs,
	/client/proc/Debug2,
	/client/proc/cmd_debug_make_powernets,
	/client/proc/debug_controller,
	/client/proc/cmd_debug_mob_lists,
	/client/proc/cmd_admin_delete,
	/client/proc/cmd_debug_del_all,
	/client/proc/restart_controller,
	/client/proc/enable_debug_verbs,
	/client/proc/callproc,
	/client/proc/callproc_datum,
	/client/proc/SDQL2_query,
	/client/proc/test_movable_UI,
	/client/proc/test_snap_UI,
	/client/proc/debugNatureMapGenerator,
	/client/proc/check_bomb_impacts,
	/proc/machine_upgrade
	)
var/list/admin_verbs_possess = list(
	/proc/possess,
	/proc/release
	)
var/list/admin_verbs_permissions = list(
	/client/proc/edit_admin_permissions
	)
var/list/admin_verbs_rejuv = list(
	/client/proc/respawn_character
	)

//verbs which can be hidden - needs work
var/list/admin_verbs_hideable = list(
	/client/proc/set_ooc,
	/client/proc/deadmin_self,
	/client/proc/deadchat,
	/client/proc/toggleprayers,
	/client/proc/toggle_hear_radio,
	/datum/admins/proc/show_traitor_panel,
	/datum/admins/proc/toggleenter,
	/datum/admins/proc/toggleguests,
	/datum/admins/proc/announce,
	/datum/admins/proc/set_admin_notice,
	/client/proc/admin_ghost,
	/client/proc/toggle_view_range,
	/datum/admins/proc/view_txt_log,
	/datum/admins/proc/view_atk_log,
	/client/proc/cmd_admin_subtle_message,
	/client/proc/cmd_admin_check_contents,
	/datum/admins/proc/access_news_network,
	/client/proc/admin_call_shuttle,
	/client/proc/admin_cancel_shuttle,
	/client/proc/cmd_admin_direct_narrate,
	/client/proc/cmd_admin_world_narrate,
	/client/proc/check_words,
	/client/proc/play_local_sound,
	/client/proc/play_sound,
	/client/proc/cmd_admin_dress,
	/client/proc/cmd_admin_gib_self,
	/client/proc/drop_bomb,
	/client/proc/cinematic,
	/client/proc/send_space_ninja,
	/client/proc/cmd_admin_add_freeform_ai_law,
	/client/proc/cmd_admin_add_random_ai_law,
	/client/proc/cmd_admin_create_centcom_report,
	/client/proc/object_say,
	/client/proc/toggle_random_events,
	/client/proc/cmd_admin_add_random_ai_law,
	/datum/admins/proc/startnow,
	/datum/admins/proc/restart,
	/datum/admins/proc/delay,
	/datum/admins/proc/toggleaban,
	/client/proc/toggle_log_hrefs,
	/client/proc/everyone_random,
	/datum/admins/proc/toggleAI,
	/client/proc/restart_controller,
	/client/proc/cmd_admin_list_open_jobs,
	/client/proc/callproc,
	/client/proc/callproc_datum,
	/client/proc/Debug2,
	/client/proc/reload_admins,
	/client/proc/cmd_debug_make_powernets,
	/client/proc/debug_controller,
	/client/proc/startSinglo,
	/client/proc/cmd_debug_mob_lists,
	/client/proc/cmd_debug_del_all,
	/client/proc/enable_debug_verbs,
	/proc/possess,
	/proc/release,
	/client/proc/reload_admins,
	/client/proc/reset_all_tcs,
	/client/proc/panicbunker
	)

/client/proc/add_admin_verbs()
	if(holder)
		control_freak = CONTROL_FREAK_SKIN | CONTROL_FREAK_MACROS

		var/rights = holder.rank.rights
		verbs += admin_verbs_default
		if(rights & R_BUILDMODE)	verbs += /client/proc/togglebuildmodeself
		if(rights & R_ADMIN)		verbs += admin_verbs_admin
		if(rights & R_BAN)			verbs += admin_verbs_ban
		if(rights & R_FUN)			verbs += admin_verbs_fun
		if(rights & R_SERVER)		verbs += admin_verbs_server
		if(rights & R_DEBUG)		verbs += admin_verbs_debug
		if(rights & R_POSSESS)		verbs += admin_verbs_possess
		if(rights & R_PERMISSIONS)	verbs += admin_verbs_permissions
		if(rights & R_STEALTH)		verbs += /client/proc/stealth
		if(rights & R_REJUVINATE)	verbs += admin_verbs_rejuv
		if(rights & R_SOUNDS)		verbs += admin_verbs_sounds
		if(rights & R_SPAWN)		verbs += admin_verbs_spawn

		for(var/path in holder.rank.adds)
			verbs += path
		for(var/path in holder.rank.subs)
			verbs -= path

/client/proc/remove_admin_verbs()
	verbs.Remove(
		admin_verbs_default,
		/client/proc/togglebuildmodeself,
		admin_verbs_admin,
		admin_verbs_ban,
		admin_verbs_fun,
		admin_verbs_server,
		admin_verbs_debug,
		admin_verbs_possess,
		admin_verbs_permissions,
		/client/proc/stealth,
		admin_verbs_rejuv,
		admin_verbs_sounds,
		admin_verbs_spawn,
		/*Debug verbs added by "show debug verbs"*/
		/client/proc/Cell,
		/client/proc/do_not_use_these,
		/client/proc/camera_view,
		/client/proc/sec_camera_report,
		/client/proc/intercom_view,
		/client/proc/air_status,
		/client/proc/atmosscan,
		/client/proc/powerdebug,
		/client/proc/count_objects_on_z_level,
		/client/proc/count_objects_all,
		/client/proc/cmd_assume_direct_control,
		/client/proc/startSinglo,
		/client/proc/fps,
		/client/proc/cmd_admin_grantfullaccess,
		/client/proc/cmd_admin_areatest,
		/client/proc/readmin
		)
	if(holder)
		verbs.Remove(holder.rank.adds)

/client/proc/hide_most_verbs()//Allows you to keep some functionality while hiding some verbs
	set name = "Adminverbs - Hide Most"
	set category = "Admin"

	verbs.Remove(/client/proc/hide_most_verbs, admin_verbs_hideable)
	verbs += /client/proc/show_verbs

	src << "Most of your adminverbs have been hidden."
	feedback_add_details("admin_verb","HMV") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	return

/client/proc/hide_verbs()
	set name = "Adminverbs - Hide All"
	set category = "Admin"

	remove_admin_verbs()
	verbs += /client/proc/show_verbs

	src << "Almost all of your adminverbs have been hidden."
	feedback_add_details("admin_verb","TAVVH") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	return

/client/proc/show_verbs()
	set name = "Adminverbs - Show"
	set category = "Admin"

	verbs -= /client/proc/show_verbs
	add_admin_verbs()

	src << "All of your adminverbs are now visible."
	feedback_add_details("admin_verb","TAVVS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/client/proc/admin_ghost()
	set category = "Admin"
	set name = "Aghost"
	if(!holder)	return
	if(istype(mob,/mob/dead/observer))
		//re-enter
		var/mob/dead/observer/ghost = mob
		ghost.can_reenter_corpse = 1			//just in-case.
		ghost.reenter_corpse()
		feedback_add_details("admin_verb","P") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	else if(istype(mob,/mob/new_player))
		src << "Error: Aghost: Can't admin-ghost whilst in the lobby. Join or Observe first."
	else
		//ghostize
		var/mob/body = mob
		body.ghostize(1)
		if(body && !body.key)
			body.key = "@[key]"	//Haaaaaaaack. But the people have spoken. If it breaks; blame adminbus
		feedback_add_details("admin_verb","O") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/client/proc/invisimin()
	set name = "Invisimin"
	set category = "Admin"
	set desc = "Toggles ghost-like invisibility (Don't abuse this)"
	if(holder && mob)
		if(mob.invisibility == INVISIBILITY_OBSERVER)
			mob.invisibility = initial(mob.invisibility)
			mob << "Invisimin off. Invisibility reset."
		else
			mob.invisibility = INVISIBILITY_OBSERVER
			mob << "Invisimin on. You are now as invisible as a ghost."

/client/proc/player_panel()
	set name = "Player Panel"
	set category = "Admin"
	if(holder)
		holder.player_panel_old()
	feedback_add_details("admin_verb","PP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	return

/client/proc/player_panel_new()
	set name = "Player Panel New"
	set category = "Admin"
	if(holder)
		holder.player_panel_new()
	feedback_add_details("admin_verb","PPN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	return

/client/proc/check_antagonists()
	set name = "Check Antagonists"
	set category = "Admin"
	if(holder)
		holder.check_antagonists()
		log_admin("[key_name(usr)] checked antagonists.")	//for tsar~
	feedback_add_details("admin_verb","CHA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	return

/client/proc/findStealthKey(txt)
	if(txt)
		for(var/P in stealthminID)
			if(stealthminID[P] == txt)
				return P
	txt = stealthminID[ckey]
	return txt

/client/proc/createStealthKey()
	var/num = (rand(0,1000))
	var/i = 0
	while(i == 0)
		i = 1
		for(var/P in stealthminID)
			if(num == stealthminID[P])
				num++
				i = 0
	stealthminID["[ckey]"] = "@[num2text(num)]"

/client/proc/jobbans()
	set name = "Display Job bans"
	set category = "Admin"
	if(holder)
		if(config.ban_legacy_system)
			holder.Jobbans()
		else
			holder.DB_ban_panel()
	feedback_add_details("admin_verb","VJB") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	return

/client/proc/unban_panel()
	set name = "Unban Panel"
	set category = "Admin"
	if(holder)
		if(config.ban_legacy_system)
			holder.unbanpanel()
		else
			holder.DB_ban_panel()
	feedback_add_details("admin_verb","UBP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	return

/client/proc/game_panel()
	set name = "Game Panel"
	set category = "Admin"
	if(holder)
		holder.Game()
	feedback_add_details("admin_verb","GP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	return

/client/proc/secrets()
	set name = "Secrets"
	set category = "Admin"
	if (holder)
		holder.Secrets()
	feedback_add_details("admin_verb","S") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	return

/client/proc/stealth()
	set category = "Admin"
	set name = "Stealth Mode"
	if(holder)
		if(holder.fakekey)
			holder.fakekey = null
		else
			var/new_key = ckeyEx(input("Enter your desired display name.", "Fake Key", key) as text|null)
			if(!new_key)	return
			if(length(new_key) >= 26)
				new_key = copytext(new_key, 1, 26)
			holder.fakekey = new_key
			createStealthKey()
		log_admin("[key_name(usr)] has turned stealth mode [holder.fakekey ? "ON" : "OFF"]")
		message_admins("[key_name_admin(usr)] has turned stealth mode [holder.fakekey ? "ON" : "OFF"]")
	feedback_add_details("admin_verb","SM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/client/proc/drop_bomb()
	set category = "Special Verbs"
	set name = "Drop Bomb"
	set desc = "Cause an explosion of varying strength at your location."

	var/turf/epicenter = mob.loc
	var/list/choices = list("Small Bomb", "Medium Bomb", "Big Bomb", "Custom Bomb")
	var/choice = input("What size explosion would you like to produce?") in choices
	switch(choice)
		if(null)
			return 0
		if("Small Bomb")
			explosion(epicenter, 1, 2, 3, 3)
		if("Medium Bomb")
			explosion(epicenter, 2, 3, 4, 4)
		if("Big Bomb")
			explosion(epicenter, 3, 5, 7, 5)
		if("Custom Bomb")
			var/devastation_range = input("Devastation range (in tiles):") as null|num
			if(devastation_range == null)
				return
			var/heavy_impact_range = input("Heavy impact range (in tiles):") as null|num
			if(heavy_impact_range == null)
				return
			var/light_impact_range = input("Light impact range (in tiles):") as null|num
			if(light_impact_range == null)
				return
			var/flash_range = input("Flash range (in tiles):") as null|num
			if(flash_range == null)
				return
			explosion(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range)
	message_admins("[ckey] creating an admin explosion at [epicenter.loc].")
	feedback_add_details("admin_verb","DB") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/client/proc/give_spell(mob/T as mob in mob_list)
	set category = "Fun"
	set name = "Give Spell"
	set desc = "Gives a spell to a mob."
	var/obj/effect/proc_holder/spell/S = input("Choose the spell to give to that guy", "ABRAKADABRA") as null|anything in spells
	if(!S)
		return
	feedback_add_details("admin_verb","GS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	log_admin("[key_name(usr)] gave [key_name(T)] the spell [S].")
	message_admins("[key_name_admin(usr)] gave [key_name(T)] the spell [S].")

	if(T.mind)
		T.mind.AddSpell(new S)
	else
		T.AddSpell(new S)
		message_admins("Spells given to mindless mobs will not be transferred in mindswap or cloning!")

/client/proc/give_disease(mob/T as mob in mob_list)
	set category = "Fun"
	set name = "Give Disease"
	set desc = "Gives a Disease to a mob."
	var/datum/disease/D = input("Choose the disease to give to that guy", "ACHOO") as null|anything in diseases
	if(!D) return
	T.ForceContractDisease(new D)
	feedback_add_details("admin_verb","GD") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	log_admin("[key_name(usr)] gave [key_name(T)] the disease [D].")
	message_admins("[key_name_admin(usr)] gave [key_name(T)] the disease [D].")

/client/proc/object_say(var/obj/O in world)
	set category = "Special Verbs"
	set name = "OSay"
	set desc = "Makes an object say something."
	var/message = input(usr, "What do you want the message to be?", "Make Sound") as text | null
	if(!message)
		return
	var/templanguages = O.languages
	O.languages |= ALL
	O.say(message)
	O.languages = templanguages
	log_admin("[key_name(usr)] made [O] at [O.x], [O.y], [O.z] say \"[message]\"")
	message_admins("[key_name_admin(usr)] made [O] at [O.x], [O.y], [O.z]. say \"[message]\"")
	feedback_add_details("admin_verb","OS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/togglebuildmodeself()
	set name = "Toggle Build Mode Self"
	set category = "Special Verbs"
	if(src.mob)
		togglebuildmode(src.mob)
	feedback_add_details("admin_verb","TBMS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/client/proc/deadmin_self()
	set name = "De-admin self"
	set category = "Admin"

	if(holder)
		log_admin("[src] deadmined themself.")
		message_admins("[src] deadmined themself.")
		deadmin()
		verbs += /client/proc/readmin
		deadmins += ckey
		src << "You are now a normal player."
	feedback_add_details("admin_verb","DAS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

/client/proc/toggle_log_hrefs()
	set name = "Toggle href logging"
	set category = "Server"
	if(!holder)	return
	if(config)
		if(config.log_hrefs)
			config.log_hrefs = 0
			src << "Stopped logging hrefs"
		else
			config.log_hrefs = 1
			src << "Started logging hrefs"

/client/proc/check_ai_laws()
	set name = "Check AI Laws"
	set category = "Admin"
	if(holder)
		src.holder.output_ai_laws()

/client/proc/readmin()
	set name = "Re-admin self"
	set category = "Admin"
	set desc = "Regain your admin powers."
	var/list/rank_names = list()
	for(var/datum/admin_rank/R in admin_ranks)
		rank_names[R.name] = R
	var/datum/admins/D = admin_datums[ckey]
	var/rank = null
	if(config.admin_legacy_system)
		//load text from file
		var/list/Lines = file2list("config/admins.txt")
		for(var/line in Lines)
			var/list/splitline = text2list(line, " = ")
			if(splitline.len >= 2)
				if(cmptext(splitline[1], src.ckey))
					rank = ckeyEx(splitline[2])
				break
			continue
	else
		if(!dbcon.IsConnected())
			message_admins("Warning, mysql database is not connected.")
			src << "Warning, mysql database is not connected."
			return
		var/sql_ckey = sanitizeSQL(ckey)
		var/DBQuery/query = dbcon.NewQuery("SELECT rank FROM [format_table_name("admin")] WHERE ckey = '[sql_ckey]'")
		query.Execute()
		while(query.NextRow())
			rank = ckeyEx(query.item[1])
	if(!D)
		if(rank_names[rank] == null)
			var/error_extra = ""
			if(!config.admin_legacy_system)
				error_extra = " Check mysql DB connection."
			error("Error while re-adminning [src], admin rank ([rank]) does not exist.[error_extra]")
			src << "Error while re-adminning, admin rank ([rank]) does not exist.[error_extra]"
			return
		D = new(rank_names[rank],ckey)
		var/client/C = directory[ckey]
		D.associate(C)
		message_admins("[src] re-adminned themselves.")
		log_admin("[src] re-adminned themselves.")
		deadmins -= ckey
		feedback_add_details("admin_verb","RAS")
		return
	else
		src << "You are already an admin."
		verbs -= /client/proc/readmin
		deadmins -= ckey
		return


code:
//ban people from using custom names and appearances. that'll show 'em.

var/appearanceban_runonce	//Updates legacy bans with new info
var/appearance_keylist[0]	//to store the keys

/proc/appearance_fullban(mob/M, reason)
	if (!M || !M.key) return
	appearance_keylist.Add(text("[M.ckey] ## [reason]"))
	appearance_savebanfile()

/proc/appearance_client_fullban(ckey)
	if (!ckey) return
	appearance_keylist.Add(text("[ckey]"))
	appearance_savebanfile()

//returns a reason if M is banned, returns 0 otherwise
/proc/appearance_isbanned(mob/M)
	if(M)
		for(var/s in appearance_keylist)
			if(findtext(s, "[M.ckey]") == 1)
				var/startpos = findtext(s, "## ") + 3
				if(startpos && startpos < length(s))
					var/text = copytext(s, startpos, 0)
					if(text)
						return text
				return "Reason Unspecified"
	return 0

/*
DEBUG
/mob/verb/list_all_appearances()
	set name = "list all appearances"

	for(var/s in appearance_keylist)
		world << s

/mob/verb/reload_appearances()
	set name = "reload appearances"

	appearance_loadbanfile()
*/

/proc/appearance_loadbanfile()
	if(config.ban_legacy_system)
		var/savefile/S=new("data/appearance_full.ban")
		S["keys[0]"] >> appearance_keylist
		log_admin("Loading appearance_rank")
		S["runonce"] >> appearanceban_runonce

		if (!length(appearance_keylist))
			appearance_keylist=list()
			log_admin("appearance_keylist was empty")
	else
		if(!establish_db_connection())
			world.log << "Database connection failed. Reverting to the legacy ban system."
			diary << "Database connection failed. Reverting to the legacy ban system."
			config.ban_legacy_system = 1
			appearance_loadbanfile()
			return

		//appearance bans
		var/DBQuery/query = dbcon.NewQuery("SELECT ckey FROM [format_table_name("ban")] WHERE bantype = 'APPEARANCE_PERMABAN' AND NOT unbanned = 1")
		query.Execute()

		while(query.NextRow())
			var/ckey = query.item[1]

			appearance_keylist.Add("[ckey]")

/proc/appearance_savebanfile()
	var/savefile/S=new("data/appearance_full.ban")
	S["keys[0]"] << appearance_keylist

/proc/appearance_unban(mob/M)
	appearance_remove("[M.ckey]")
	appearance_savebanfile()

/proc/appearance_updatelegacybans()
	if(!appearanceban_runonce)
		log_admin("Updating appearancefile!")
		// Updates bans.. Or fixes them. Either way.
		for(var/T in appearance_keylist)
			if(!T)	continue
		appearanceban_runonce++	//don't run this update again

/proc/appearance_remove(X)
	for (var/i = 1; i <= length(appearance_keylist); i++)
		if( findtext(appearance_keylist[i], "[X]") )
			appearance_keylist.Remove(appearance_keylist[i])
			appearance_savebanfile()
			return 1
	return 0

/*
proc/DB_ban_isappearancebanned(var/playerckey)
	establish_db_connection()
	if(!dbcon.IsConnected())
		return

	var/sqlplayerckey = sanitizeSQL(ckey(playerckey))

	var/DBQuery/query = dbcon.NewQuery("SELECT id FROM [format_table_name("ban")] WHERE CKEY = '[sqlplayerckey]' AND ((bantype = 'APPEARANCE_PERMABAN') OR (bantype = 'APPEARANCE_TEMPBAN' AND expiration_time > Now())) AND unbanned != 1")
	query.Execute()
	while(query.NextRow())
		return 1
	return 0
*/


code:
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:32

var/jobban_runonce			// Updates legacy bans with new info
var/jobban_keylist[0]		//to store the keys & ranks

/proc/jobban_fullban(mob/M, rank, reason)
	if (!M || !M.key) return
	jobban_keylist.Add(text("[M.ckey] - [rank] ## [reason]"))
	jobban_savebanfile()

/proc/jobban_client_fullban(ckey, rank)
	if (!ckey || !rank) return
	jobban_keylist.Add(text("[ckey] - [rank]"))
	jobban_savebanfile()

//returns a reason if M is banned from rank, returns 0 otherwise
/proc/jobban_isbanned(mob/M, rank)
	if(M && rank)
		/*
		if(_jobban_isbanned(M, rank)) return "Reason Unspecified"	//for old jobban
		if (guest_jobbans(rank))
			if(config.guest_jobban && IsGuestKey(M.key))
				return "Guest Job-ban"
			if(config.usewhitelist && !check_whitelist(M))
				return "Whitelisted Job"
		*/
		for (var/s in jobban_keylist)
			if( findtext(s,"[M.ckey] - [rank]") == 1 )
				var/startpos = findtext(s, "## ")+3
				if(startpos && startpos<length(s))
					var/text = copytext(s, startpos, 0)
					if(text)
						return text
				return "Reason Unspecified"
	return 0

/*
DEBUG
/mob/verb/list_all_jobbans()
	set name = "list all jobbans"

	for(var/s in jobban_keylist)
		world << s

/mob/verb/reload_jobbans()
	set name = "reload jobbans"

	jobban_loadbanfile()
*/

/proc/jobban_loadbanfile()
	if(config.ban_legacy_system)
		var/savefile/S=new("data/job_full.ban")
		S["keys[0]"] >> jobban_keylist
		log_admin("Loading jobban_rank")
		S["runonce"] >> jobban_runonce

		if (!length(jobban_keylist))
			jobban_keylist=list()
			log_admin("jobban_keylist was empty")
	else
		if(!establish_db_connection())
			world.log << "Database connection failed. Reverting to the legacy ban system."
			diary << "Database connection failed. Reverting to the legacy ban system."
			config.ban_legacy_system = 1
			jobban_loadbanfile()
			return

		//Job permabans
		var/DBQuery/query = dbcon.NewQuery("SELECT ckey, job FROM [format_table_name("ban")] WHERE bantype = 'JOB_PERMABAN' AND isnull(unbanned)")
		query.Execute()

		while(query.NextRow())
			var/ckey = query.item[1]
			var/job = query.item[2]

			jobban_keylist.Add("[ckey] - [job]")

		//Job tempbans
		var/DBQuery/query1 = dbcon.NewQuery("SELECT ckey, job FROM [format_table_name("ban")] WHERE bantype = 'JOB_TEMPBAN' AND isnull(unbanned) AND expiration_time > Now()")
		query1.Execute()

		while(query1.NextRow())
			var/ckey = query1.item[1]
			var/job = query1.item[2]

			jobban_keylist.Add("[ckey] - [job]")

/proc/jobban_savebanfile()
	var/savefile/S=new("data/job_full.ban")
	S["keys[0]"] << jobban_keylist

/proc/jobban_unban(mob/M, rank)
	jobban_remove("[M.ckey] - [rank]")
	jobban_savebanfile()

/proc/ban_unban_log_save(var/formatted_log)
	text2file(formatted_log,"data/ban_unban_log.txt")

/proc/jobban_updatelegacybans()
	if(!jobban_runonce)
		log_admin("Updating jobbanfile!")
		// Updates bans.. Or fixes them. Either way.
		for(var/T in jobban_keylist)
			if(!T)	continue
		jobban_runonce++	//don't run this update again

/proc/jobban_remove(X)
	for (var/i = 1; i <= length(jobban_keylist); i++)
		if( findtext(jobban_keylist[i], "[X]") )
			jobban_keylist.Remove(jobban_keylist[i])
			jobban_savebanfile()
			return 1
	return 0


code:
/var/create_mob_html = null
/datum/admins/proc/create_mob(var/mob/user)
	if (!create_mob_html)
		var/mobjs = null
		mobjs = list2text(typesof(/mob), ";")
		create_mob_html = file2text('html/create_object.html')
		create_mob_html = replacetext(create_mob_html, "null /* object types */", "\"[mobjs]\"")

	user << browse(replacetext(create_mob_html, "/* ref src */", "\ref[src]"), "window=create_mob;size=425x475")

/proc/randomize_human(mob/living/carbon/human/H)
	H.gender = pick(MALE, FEMALE)
	H.real_name = random_name(H.gender)
	H.name = H.real_name
	H.dna.generate_uni_identity(H)
	H.underwear = random_underwear(H.gender)
	H.skin_tone = random_skin_tone()
	H.hair_style = random_hair_style(H.gender)
	H.facial_hair_style = random_facial_hair_style(H.gender)
	H.hair_color = random_short_color()
	H.facial_hair_color = H.hair_color
	H.eye_color = random_eye_color()
	H.dna.blood_type = random_blood_type()
	H.update_icons()


code:
/var/create_object_html = null

/datum/admins/proc/create_object(var/mob/user)
	if (!create_object_html)
		var/objectjs = null
		objectjs = list2text(typesof(/obj), ";")
		create_object_html = file2text('html/create_object.html')
		create_object_html = replacetext(create_object_html, "null /* object types */", "\"[objectjs]\"")

	user << browse(replacetext(create_object_html, "/* ref src */", "\ref[src]"), "window=create_object;size=425x475")

/datum/admins/proc/quick_create_object(var/mob/user)

	var/quick_create_object_html = null
	var/pathtext = null

	pathtext = input("Select the path of the object you wish to create.", "Path", "/obj") in list("/obj","/obj/structure","/obj/item","/obj/item/clothing","/obj/item/weapon/reagent_containers/food","/obj/item/weapon","/obj/machinery")

	var path = text2path(pathtext)

	if (!quick_create_object_html)
		var/objectjs = null
		objectjs = list2text(typesof(path), ";")
		quick_create_object_html = file2text('html/create_object.html')
		quick_create_object_html = replacetext(quick_create_object_html, "null /* object types */", "\"[objectjs]\"")

	user << browse(replacetext(quick_create_object_html, "/* ref src */", "\ref[src]"), "window=quick_create_object;size=425x475")


code:
/var/create_turf_html = null
/datum/admins/proc/create_turf(var/mob/user)
	if (!create_turf_html)
		var/turfjs = null
		turfjs = list2text(typesof(/turf), ";")
		create_turf_html = file2text('html/create_object.html')
		create_turf_html = replacetext(create_turf_html, "null /* object types */", "\"[turfjs]\"")

	user << browse(replacetext(create_turf_html, "/* ref src */", "\ref[src]"), "window=create_turf;size=425x475")


code:
var/list/admin_datums = list()

/datum/admins
	var/datum/admin_rank/rank

	var/client/owner	= null
	var/fakekey			= null

	var/datum/marked_datum

	var/admincaster_screen = 0	//See newscaster.dm under machinery for a full description
	var/datum/feed_message/admincaster_feed_message = new /datum/feed_message   //These two will act as holders.
	var/datum/feed_channel/admincaster_feed_channel = new /datum/feed_channel
	var/admincaster_signature	//What you'll sign the newsfeeds as

/datum/admins/New(datum/admin_rank/R, ckey)
	if(!ckey)
		ERROR("Admin datum created without a ckey argument. Datum has been deleted")
		del(src)
		return
	if(!istype(R))
		ERROR("Admin datum created without a rank. Datum has been deleted")
		del(src)
		return
	rank = R
	admincaster_signature = "Nanotrasen Officer #[rand(0,9)][rand(0,9)][rand(0,9)]"
	admin_datums[ckey] = src

/datum/admins/proc/associate(client/C)
	if(istype(C))
		owner = C
		owner.holder = src
		owner.add_admin_verbs()	//TODO
		owner.verbs -= /client/proc/readmin
		admins |= C

/datum/admins/proc/disassociate()
	if(owner)
		admins -= owner
		owner.remove_admin_verbs()
		owner.holder = null
		owner = null

/datum/admins/proc/check_if_greater_rights_than_holder(datum/admins/other)
	if(!other)
		return 1 //they have no rights
	if(rank.rights == 65535)
		return 1 //we have all the rights
	if(src == other)
		return 1 //you always have more rights than yourself
	if(rank.rights != other.rank.rights)
		if( (rank.rights & other.rank.rights) == other.rank.rights )
			return 1 //we have all the rights they have and more
	return 0

/*
checks if usr is an admin with at least ONE of the flags in rights_required. (Note, they don't need all the flags)
if rights_required == 0, then it simply checks if they are an admin.
if it doesn't return 1 and show_msg=1 it will prints a message explaining why the check has failed
generally it would be used like so:

proc/admin_proc()
	if(!check_rights(R_ADMIN)) return
	world << "you have enough rights!"

NOTE: it checks usr! not src! So if you're checking somebody's rank in a proc which they did not call
you will have to do something like if(client.rights & R_ADMIN) yourself.
*/
/proc/check_rights(rights_required, show_msg=1)
	if(usr && usr.client)
		if (check_rights_for(usr.client, rights_required))
			return 1
		else
			if(show_msg)
				usr << "Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")]."
	return 0

//probably a bit iffy - will hopefully figure out a better solution
/proc/check_if_greater_rights_than(client/other)
	if(usr && usr.client)
		if(usr.client.holder)
			if(!other || !other.holder)
				return 1
			return usr.client.holder.check_if_greater_rights_than_holder(other.holder)
	return 0

/client/proc/deadmin()
	admin_datums -= ckey
	if(holder)
		holder.disassociate()
		del(holder)
	return 1

//This proc checks whether subject has at least ONE of the rights specified in rights_required.
/proc/check_rights_for(client/subject, rights_required)
	if(subject && subject.holder && subject.holder.rank)
		if(rights_required && !(rights_required & subject.holder.rank.rights))
			return 0
		return 1
	return 0


code:
var/savefile/Banlistjob

/proc/_jobban_isbanned(var/client/clientvar, var/rank)
	if(!clientvar) return 1
	ClearTempbansjob()
	var/id = clientvar.computer_id
	var/key = clientvar.ckey
	if (guest_jobbans(rank))
		if(config.guest_jobban && IsGuestKey(key))
			return 1
	Banlistjob.cd = "/base"
	if (Banlistjob.dir.Find("[key][id][rank]"))
		return 1

	Banlistjob.cd = "/base"
	for (var/A in Banlistjob.dir)
		Banlistjob.cd = "/base/[A]"
		if ((id == Banlistjob["id"] || key == Banlistjob["key"]) && rank == Banlistjob["rank"])
			return 1
	return 0

///proc/UpdateTime() //No idea why i made this a proc.
//	CMinutes = (world.realtime / 10) / 60
//	return 1

/proc/LoadBansjob()

	Banlistjob = new("data/job_fullnew.bdb")
	log_admin("Loading Banlistjob")

	if (!length(Banlistjob.dir)) log_admin("Banlistjob is empty.")

	if (!Banlistjob.dir.Find("base"))
		log_admin("Banlistjob missing base dir.")
		Banlistjob.dir.Add("base")
		Banlistjob.cd = "/base"
	else if (Banlistjob.dir.Find("base"))
		Banlistjob.cd = "/base"

	ClearTempbansjob()
	return 1

/proc/ClearTempbansjob()
	UpdateTime()

	Banlistjob.cd = "/base"
	for (var/A in Banlistjob.dir)
		Banlistjob.cd = "/base/[A]"
		//if (!Banlistjob["key"] || !Banlistjob["id"])
		//	RemoveBanjob(A, "full")
		//	log_admin("Invalid Ban.")
		//	message_admins("Invalid Ban.")
		//	continue

		if (!Banlistjob["temp"]) continue
		if (CMinutes >= Banlistjob["minutes"]) RemoveBanjob(A)

	return 1

/proc/AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, rank)
	UpdateTime()
	var/bantimestamp
	if (temp)
		UpdateTime()
		bantimestamp = CMinutes + minutes
	if(rank == "Heads")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Head of Personnel")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Captain")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Head of Security")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Chief Engineer")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Research Director")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Chief Medical Officer")
		return 1
	if(rank == "Security")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Head of Security")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Warden")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Detective")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Security Officer")
		return 1
	if(rank == "Engineering")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Station Engineer")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Atmospheric Technician")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Chief Engineer")
		return 1
	if(rank == "Research")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Scientist")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Geneticist")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Chief Medical Officer")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Research Director")
		return 1
	if(rank == "Medical")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Geneticist")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Medical Doctor")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Chief Medical Officer")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Chemist")
		return 1
	if(rank == "CE_Station_Engineer")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Station Engineer")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Chief Engineer")
		return 1
	if(rank == "CE_Atmospheric_Tech")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Atmospheric Technician")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Chief Engineer")
		return 1
	if(rank == "CE_Shaft_Miner")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Shaft Miner")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Chief Engineer")
		return 1
	if(rank == "Chemist_RD_CMO")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Chief Medical Officer")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Research Director")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Chemist")
		return 1
	if(rank == "Geneticist_RD_CMO")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Chief Medical Officer")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Research Director")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Geneticist")
		return 1
	if(rank == "MD_CMO")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Chief Medical Officer")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Medical Doctor")
		return 1
	if(rank == "Scientist_RD")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Research Director")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Scientist")
		return 1
	if(rank == "AI_Cyborg")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Cyborg")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "AI")
		return 1
	if(rank == "Detective_HoS")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Detective")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Head of Security")
		return 1
	if(rank == "Virologist_RD_CMO")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Chief Medical Officer")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Research Director")
		AddBanjob(ckey, computerid, reason, bannedby, temp, minutes, "Virologist")
		return 1

	Banlistjob.cd = "/base"
	if ( Banlistjob.dir.Find("[ckey][computerid][rank]") )
		usr << text("Banjob already exists.")
		return 0
	else
		Banlistjob.dir.Add("[ckey][computerid][rank]")
		Banlistjob.cd = "/base/[ckey][computerid][rank]"
		Banlistjob["key"] << ckey
		Banlistjob["id"] << computerid
		Banlistjob["rank"] << rank
		Banlistjob["reason"] << reason
		Banlistjob["bannedby"] << bannedby
		Banlistjob["temp"] << temp
		if (temp)
			Banlistjob["minutes"] << bantimestamp

	return 1

/proc/RemoveBanjob(foldername)
	var/key
	var/id
	var/rank
	Banlistjob.cd = "/base/[foldername]"
	Banlistjob["key"] >> key
	Banlistjob["id"] >> id
	Banlistjob["rank"] >> rank
	Banlistjob.cd = "/base"

	if (!Banlistjob.dir.Remove(foldername)) return 0

	if(!usr)
		log_admin("Banjob Expired: [key]")
		message_admins("Banjob Expired: [key]")
	else
		log_admin("[key_name(usr)] unjobbanned [key] from [rank]")
		message_admins("[key_name_admin(usr)] unjobbanned:[key] from [rank]")
		ban_unban_log_save("[key_name(usr)] unjobbanned [key] from [rank]")
		feedback_inc("ban_job_unban",1)
		feedback_add_details("ban_job_unban","- [rank]")

	for (var/A in Banlistjob.dir)
		Banlistjob.cd = "/base/[A]"
		if ((key == Banlistjob["key"] || id == Banlistjob["id"]) && (rank == Banlistjob["rank"]))
			Banlistjob.cd = "/base"
			Banlistjob.dir.Remove(A)
			continue

	return 1

/proc/GetExpjob(minutes as num)
	UpdateTime()
	var/exp = minutes - CMinutes
	if (exp <= 0)
		return 0
	else
		var/timeleftstring
		if (exp >= 1440) //1440 = 1 day in minutes
			timeleftstring = "[round(exp / 1440, 0.1)] Days"
		else if (exp >= 60) //60 = 1 hour in minutes
			timeleftstring = "[round(exp / 60, 0.1)] Hours"
		else
			timeleftstring = "[exp] Minutes"
		return timeleftstring

/datum/admins/proc/unjobbanpanel()
	var/count = 0
	var/dat
	//var/dat = "
Unban Player: \blue(U) = Unban , (E) = Edit Ban\green (Total

"
	Banlistjob.cd = "/base"
	for (var/A in Banlistjob.dir)
		count++
		Banlistjob.cd = "/base/[A]"
		dat += text("
(U) Key: [Banlistjob["key"]] Rank: [Banlistjob["rank"]]	 ([Banlistjob["temp"] ? "[GetExpjob(Banlistjob["minutes"]) ? GetExpjob(Banlistjob["minutes"]) : "Removal pending" ]" : "Permaban"])	(By: [Banlistjob["bannedby"]])	(Reason: [Banlistjob["reason"]])
")

	dat += "
"
	dat = "
Bans: (U) = Unban ,  - ([count] Bans)

[dat]"
	usr << browse(dat, "window=unbanp;size=875x400")

/*/datum/admins/proc/permjobban(ckey, computerid, reason, bannedby, temp, minutes, rank)
	if(AddBanjob(ckey, computerid, reason, usr.ckey, 0, 0, job))
		M << "\red<BIG>You have been banned from [job] by [usr.client.ckey].\nReason: [reason]</BIG>"
		M << "\red This is a permanent ban."
		if(config.banappeals)
			M << "\red To try to resolve this matter head to [config.banappeals]"
		else
			M << "\red No ban appeals URL has been set."
		log_admin("[usr.client.ckey] has banned from [job] [ckey].\nReason: [reason]\nThis is a permanent ban.")
		message_admins("\blue[usr.client.ckey] has banned from [job] [ckey].\nReason: [reason]\nThis is a permanent ban.")
/datum/admins/proc/timejobban(ckey, computerid, reason, bannedby, temp, minutes, rank)
	if(AddBanjob(ckey, computerid, reason, usr.ckey, 1, mins, job))
		M << "\red<BIG>You have been jobbanned from [job] by [usr.client.ckey].\nReason: [reason]</BIG>"
		M << "\red This is a temporary ban, it will be removed in [mins] minutes."
		if(config.banappeals)
			M << "\red To try to resolve this matter head to [config.banappeals]"
		else
			M << "\red No ban appeals URL has been set."
		log_admin("[usr.client.ckey] has jobbanned from [job] [ckey].\nReason: [reason]\nThis will be removed in [mins] minutes.")
		message_admins("\blue[usr.client.ckey] has banned from [job] [ckey].\nReason: [reason]\nThis will be removed in [mins] minutes.")*/
//////////////////////////////////// DEBUG ////////////////////////////////////

/proc/CreateBansjob()

	UpdateTime()

	var/i
	var/last

	for(i=0, i<1001, i++)
		var/a = pick(1,0)
		var/b = pick(1,0)
		if(b)
			Banlistjob.cd = "/base"
			Banlistjob.dir.Add("trash[i]trashid[i]")
			Banlistjob.cd = "/base/trash[i]trashid[i]"
			Banlistjob["key"] << "trash[i]"
		else
			Banlistjob.cd = "/base"
			Banlistjob.dir.Add("[last]trashid[i]")
			Banlistjob.cd = "/base/[last]trashid[i]"
			Banlistjob["key"] << last
		Banlistjob["id"] << "trashid[i]"
		Banlistjob["reason"] << "Trashban[i]."
		Banlistjob["temp"] << a
		Banlistjob["minutes"] << CMinutes + rand(1,2000)
		Banlistjob["bannedby"] << "trashmin"
		last = "trash[i]"

	Banlistjob.cd = "/base"

/proc/ClearAllBansjob()
	Banlistjob.cd = "/base"
	for (var/A in Banlistjob.dir)
		RemoveBanjob(A, "full")


code:
/client/proc/edit_admin_permissions()
	set category = "Admin"
	set name = "Permissions Panel"
	set desc = "Edit admin permissions"
	if(!check_rights(R_PERMISSIONS))	return
	usr.client.holder.edit_admin_permissions()

/datum/admins/proc/edit_admin_permissions()
	if(!check_rights(R_PERMISSIONS))	return

	var/output = {"<!DOCTYPE html>

<title>Permissions Panel</title>

<link rel='stylesheet' type='text/css' href='panels.css'>

CKEY \[+\]
RANK
PERMISSIONS
VERB-OVERRIDES

"}

	for(var/adm_ckey in admin_datums)
		var/datum/admins/D = admin_datums[adm_ckey]
		if(!D)	continue

		var/rights = rights2text(D.rank.rights," ")
		if(!rights)	rights = "*none*"

		output += "
"
		output += "[adm_ckey] \[-\]	"
		output += "[D.rank.name]	"
		output += "[rights]	"
		output += "[rights2text(0," ",D.rank.adds,D.rank.subs)]	"
		output += "
"

	output += {"

Search: <input type='text' id='filter' value='' style='width:70%;' onkeyup='updateSearch();'>

"}

	usr << browse(output,"window=editrights;size=900x650")

/datum/admins/proc/log_admin_rank_modification(var/adm_ckey, var/new_rank)
	if(config.admin_legacy_system)	return

	if(!usr.client)
		return

	if (!check_rights(R_PERMISSIONS))
		return

	establish_db_connection()

	if(!dbcon.IsConnected())
		usr << "Failed to establish database connection."
		return

	if(!adm_ckey || !new_rank)
		return

	adm_ckey = ckey(adm_ckey)

	if(!adm_ckey)
		return

	if(!istext(adm_ckey) || !istext(new_rank))
		return

	var/DBQuery/select_query = dbcon.NewQuery("SELECT id FROM [format_table_name("admin")] WHERE ckey = '[adm_ckey]'")
	select_query.Execute()

	var/new_admin = 1
	var/admin_id
	while(select_query.NextRow())
		new_admin = 0
		admin_id = text2num(select_query.item[1])

	if(new_admin)
		var/DBQuery/insert_query = dbcon.NewQuery("INSERT INTO `[format_table_name("admin")]` (`id`, `ckey`, `rank`, `level`, `flags`) VALUES (null, '[adm_ckey]', '[new_rank]', -1, 0)")
		insert_query.Execute()
		var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added new admin [adm_ckey] to rank [new_rank]');")
		log_query.Execute()
		usr << "New admin added."
	else
		if(!isnull(admin_id) && isnum(admin_id))
			var/DBQuery/insert_query = dbcon.NewQuery("UPDATE `[format_table_name("admin")]` SET rank = '[new_rank]' WHERE id = [admin_id]")
			insert_query.Execute()
			var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Edited the rank of [adm_ckey] to [new_rank]');")
			log_query.Execute()
			usr << "Admin rank changed."

/datum/admins/proc/log_admin_permission_modification(var/adm_ckey, var/new_permission)
	if(config.admin_legacy_system)	return
	if(!usr.client)					return
	if(check_rights(R_PERMISSIONS))	return

	establish_db_connection()
	if(!dbcon.IsConnected())
		usr << "Failed to establish database connection."
		return

	if(!adm_ckey || !istext(adm_ckey) || !isnum(new_permission))
		return

	var/DBQuery/select_query = dbcon.NewQuery("SELECT id, flags FROM [format_table_name("admin")] WHERE ckey = '[adm_ckey]'")
	select_query.Execute()

	var/admin_id
	while(select_query.NextRow())
		admin_id = text2num(select_query.item[1])

	if(!admin_id)	return

	var/DBQuery/insert_query = dbcon.NewQuery("UPDATE `[format_table_name("admin")]` SET flags = [new_permission] WHERE id = [admin_id]")
	insert_query.Execute()
	var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Edit permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]');")
	log_query.Execute()


code:
//This stuff was originally intended to be integrated into the ban-system I was working on
//but it's safe to say that'll never be finished. So I've merged it into the current player panel.
//enjoy				~Carn

#define NOTESFILE "data/player_notes.sav"	//where the player notes are saved

/proc/see_own_notes()
	if(!config.see_own_notes)
		return
	var/ckey = usr.client.ckey
	if(!ckey)
		usr << "Error: No ckey found."
		return
	var/savefile/notesfile = new(NOTESFILE)
	if(!notesfile)
		usr << "Error: Cannot access [NOTESFILE]"
		return
	notesfile.cd = "/[ckey]"
	var/dat = "Notes for [ckey]:
"
	while(!notesfile.eof)
		var/note
		notesfile >> note
		dat += note + "
"
	var/datum/browser/popup = new(usr, "player_notes", "Player Notes", 700, 400)
	popup.set_content(dat)
	popup.open()

datum/admins/proc/notes_show(var/ckey)
	usr << browse("<title>Player Notes</title>[notes_gethtml(ckey)]","window=player_notes;size=700x400")

datum/admins/proc/notes_gethtml(var/ckey)
	var/savefile/notesfile = new(NOTESFILE)
	if(!notesfile)	return "Error: Cannot access [NOTESFILE]"
	if(ckey)
		. = "Notes for [ckey]: \[+\]
"
		notesfile.cd = "/[ckey]"
		var/index = 1
		while( !notesfile.eof )
			var/note
			notesfile >> note
			. += "[note] \[-\]
"
			index++
	else
		. = "All Notes: \[+\]
"
		notesfile.cd = "/"
		for(var/dir in notesfile.dir)
			. += "[dir]
"
	return

//handles adding notes to the end of a ckey's buffer
//originally had seperate entries such as var/by to record who left the note and when
//but the current bansystem is a heap of dung.
/proc/notes_add(var/ckey, var/note, var/lognote = 0)
	if(!ckey)
		ckey = ckey(input(usr,"Who would you like to add notes for?","Enter a ckey",null) as text|null)
		if(!ckey)	return

	if(!note)
		note = html_encode(input(usr,"Enter your note:","Enter some text",null) as message|null)
		if(!note)	return

	var/savefile/notesfile = new(NOTESFILE)
	if(!notesfile)	return
	notesfile.cd = "/[ckey]"
	notesfile.eof = 1		//move to the end of the buffer
	notesfile << "[time2text(world.realtime,"DD-MMM-YYYY")] | [note][(usr && usr.ckey)?" ~[usr.ckey]":""]"

	if(lognote)//don't need an admin log for the notes applied automatically during bans.
		message_admins("[key_name(usr)] added note '[note]' to [ckey]")
		log_admin("[key_name(usr)] added note '[note]' to [ckey]")

	return

//handles removing entries from the buffer, or removing the entire directory if no start_index is given
/proc/notes_remove(var/ckey, var/start_index, var/end_index)
	var/savefile/notesfile = new(NOTESFILE)
	var/admin_msg
	if(!notesfile)	return

	if(!ckey)
		notesfile.cd = "/"
		ckey = ckey(input(usr,"Who would you like to remove notes for?","Enter a ckey",null) as null|anything in notesfile.dir)
		if(!ckey)	return

	if(start_index)
		notesfile.cd = "/[ckey]"
		var/list/noteslist = list()
		if(!end_index)	end_index = start_index
		var/index = 0
		while( !notesfile.eof )
			index++
			var/temp
			notesfile >> temp
			if( (start_index <= index) && (index <= end_index) )
				admin_msg = temp
				continue

			noteslist += temp

		notesfile.eof = -2		//Move to the start of the buffer and then erase.

		for( var/note in noteslist )
			notesfile << note

		message_admins("[key_name(usr)] removed a note '[admin_msg]' from [ckey]")
		log_admin("[key_name(usr)] removed a note '[admin_msg]' from [ckey]")

		if(noteslist.len == 0)
			notesfile.cd = "/"
			notesfile.dir.Remove(ckey)
			message_admins("[ckey] has no notes and was removed from the notes list.")
	return

#undef NOTESFILE