code:
/datum/admins/proc/player_panel_new()//The new one
	if(!check_rights())
		return
	var/dat = "<title>Player Panel</title>"

	//javascript, the part that does most of the work~
	dat += {"

	"}

	//body tag start + onload and onkeypress (onkeyup) javascript event calls
	dat += ""

	//title + search bar
	dat += {"

					Player panel

					Hover over a line to see more information - Check antagonists - Kick everyone/AFKers in lobby

					Search: <input type='text' id='filter' value='' style='width:300px;'>

	"}

	//player table header
	dat += {"

"}

	var/list/mobs = sortmobs()
	var/i = 1
	for(var/mob/M in mobs)
		if(M.ckey)

			var/color = "#e6e6e6"
			if(i%2 == 0)
				color = "#f2f2f2"
			var/is_antagonist = is_special_character(M)

			var/M_job = ""

			if(isliving(M))

				if(iscarbon(M)) //Carbon stuff
					if(ishuman(M))
						M_job = M.job
					else if(ismonkey(M))
						M_job = "Monkey"
					else if(isalien(M)) //aliens
						if(islarva(M))
							M_job = "Alien larva"
						else
							M_job = "Alien"
					else
						M_job = "Carbon-based"

				else if(issilicon(M)) //silicon
					if(isAI(M))
						M_job = "AI"
					else if(ispAI(M))
						M_job = "pAI"
					else if(isrobot(M))
						M_job = "Cyborg"
					else
						M_job = "Silicon-based"

				else if(isanimal(M)) //simple animals
					if(iscorgi(M))
						M_job = "Corgi"
					else if(isslime(M))
						M_job = "slime"
					else
						M_job = "Animal"

				else
					M_job = "Living"

			else if(istype(M,/mob/new_player))
				M_job = "New player"

			else if(isobserver(M))
				var/mob/dead/observer/O = M
				if(O.started_as_observer)//Did they get BTFO or are they just not trying?
					M_job = "Observer"
				else
					M_job = "Ghost"

			var/M_name = html_encode(M.name)
			var/M_rname = html_encode(M.real_name)
			var/M_key = html_encode(M.key)

			//output for each mob
			dat += {"

						[M_name] - [M_rname] - [M_key] ([M_job])

			"}

			i++

	//player table ending
	dat += {"

	"}

	usr << browse(dat, "window=players;size=600x480")

/datum/admins/proc/check_antagonists()
	if (ticker && ticker.current_state >= GAME_STATE_PLAYING)
		var/dat = "<title>Round Status</title>
Round Status
"
		if(ticker.mode.replacementmode)
			dat += "Former Game Mode: [ticker.mode.name]
"
			dat += "Replacement Game Mode: [ticker.mode.replacementmode.name]
"
		else
			dat += "Current Game Mode: [ticker.mode.name]
"
		dat += "Round Duration: [round(world.time / 36000)]:[add_zero("[world.time / 600 % 60]", 2)]:[world.time / 100 % 6][world.time / 100 % 10]
"
		dat += "Emergency shuttle
"
		if(SSshuttle.emergency.mode < SHUTTLE_CALL)
			dat += "Call Shuttle
"
		else
			var/timeleft = SSshuttle.emergency.timeLeft()
			if(SSshuttle.emergency.mode < SHUTTLE_DOCKED)
				dat += "ETA: [(timeleft / 60) % 60]:[add_zero(num2text(timeleft % 60), 2)]
"
				dat += "Send Back
"
			else
				dat += "ETA: [(timeleft / 60) % 60]:[add_zero(num2text(timeleft % 60), 2)]
"
		dat += "Continuous Round Status
"
		dat += "[config.continuous[ticker.mode.config_tag] ? "Continue if antagonists die" : "End on antagonist death"]"
		if(config.continuous[ticker.mode.config_tag])
			dat += ", [config.midround_antag[ticker.mode.config_tag] ? "creating replacement antagonists" : "not creating new antagonists"]
"
		else
			dat += "
"
		if(config.midround_antag[ticker.mode.config_tag])
			dat += "Time limit: [config.midround_antag_time_check] minutes into round
"
			dat += "Living crew limit: [config.midround_antag_life_check * 100]% of crew alive
"
			dat += "If limits past: [ticker.mode.round_ends_with_antag_death ? "End The Round" : "Continue As Extended"]
"

		dat += "
"
		dat += "End Round Now
"
		dat += "[ticker.delay_end ? "End Round Normally" : "Delay Round End"]
"
		if(ticker.mode.syndicates.len)
			dat += "

Syndicates
"
			for(var/datum/mind/N in ticker.mode.syndicates)
				var/mob/M = N.current
				if(M)
					dat += "
[M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM	"
					dat += "FLW
"
				else
					dat += "
[N.name]([N.key]) Nuclear Operative Body destroyed!	"
					dat += "PM
"
			dat += "

Nuclear Disk(s)
"
			for(var/obj/item/weapon/disk/nuclear/N in poi_list)
				dat += "
[N.name], "
				var/atom/disk_loc = N.loc
				while(!istype(disk_loc, /turf))
					if(istype(disk_loc, /mob))
						var/mob/M = disk_loc
						dat += "carried by [M.real_name] "
					if(istype(disk_loc, /obj))
						var/obj/O = disk_loc
						dat += "in \a [O.name] "
					disk_loc = disk_loc.loc
				dat += "in [disk_loc.loc] at ([disk_loc.x], [disk_loc.y], [disk_loc.z])
"
			dat += "
"

		if(ticker.mode.head_revolutionaries.len || ticker.mode.revolutionaries.len)
			dat += "

Revolutionaries
"
			for(var/datum/mind/N in ticker.mode.head_revolutionaries)
				var/mob/M = N.current
				if(!M)
					dat += "
[N.name]([N.key])Head Revolutionary body destroyed!	"
					dat += "PM
"
				else
					dat += "
[M.real_name] (Leader)[M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM	"
					dat += "FLW
"
			for(var/datum/mind/N in ticker.mode.revolutionaries)
				var/mob/M = N.current
				if(M)
					dat += "
[M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM	"
					dat += "FLW
"
			dat += "

Target(s)		Location
"
			for(var/datum/mind/N in ticker.mode.get_living_heads())
				var/mob/M = N.current
				if(M)
					dat += "
[M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM	"
					dat += "FLW	"
					var/turf/mob_loc = get_turf(M)
					dat += "[mob_loc.loc]
"
				else
					dat += "
[N.name]([N.key])Head body destroyed!	"
					dat += "PM
"
			dat += "
"

		for(var/datum/gang/G in ticker.mode.gangs)
			dat += "

[G.name] Gang: [G.points] Influence | [round((G.territory.len/start_state.num_territories)*100, 1)]% Control
"
			for(var/datum/mind/N in G.bosses)
				var/mob/M = N.current
				if(!M)
					dat += "
[N.name]([N.key])Gang Boss body destroyed!	"
					dat += "PM
"
				else
					dat += "
[M.real_name] (Boss)[M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM	"
					dat += "FLW
"
			for(var/datum/mind/N in G.gangsters)
				var/mob/M = N.current
				if(M)
					dat += "
[M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM
"
			dat += "
"

		if(ticker.mode.changelings.len > 0)
			dat += "

Changelings
"
			for(var/datum/mind/changeling in ticker.mode.changelings)
				var/mob/M = changeling.current
				if(M)
					dat += "
[M.mind.changeling.changelingID] as [M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM	"
					dat += "FLW	"
					dat += "Show Objective
"
				else
					dat += "
[changeling.name]([changeling.key])Changeling body destroyed!	"
					dat += "PM
"
			dat += "
"

		if(ticker.mode.wizards.len > 0)
			dat += "

Wizards
"
			for(var/datum/mind/wizard in ticker.mode.wizards)
				var/mob/M = wizard.current
				if(M)
					dat += "
[M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM	"
					dat += "FLW	"
					dat += "Show Objective
"
				else
					dat += "
[wizard.name]([wizard.key])Wizard body destroyed!
"
					dat += "PM
"
			dat += "
"

		if(ticker.mode.apprentices.len > 0)
			dat += "

Apprentice
"
			for(var/datum/mind/apprentice in ticker.mode.apprentices)
				var/mob/M = apprentice.current
				if(M)
					dat += "
[M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM	"
					dat += "FLW	"
					dat += "Show Objective
"
				else
					dat += "
[apprentice.name]([apprentice.key])Apprentice body destroyed!!
"
					dat += "PM
"
			dat += "
"

		if(ticker.mode.cult.len)
			dat += "

Cultists
"
			for(var/datum/mind/N in ticker.mode.cult)
				var/mob/M = N.current
				if(M)
					dat += "
[M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM	"
					dat += "FLW
"
			dat += "
"

		if(ticker.mode.red_deities.len || ticker.mode.red_deity_prophets.len || ticker.mode.blue_deity_prophets.len || ticker.mode.red_deity_followers.len || ticker.mode.blue_deity_followers.len)
			dat += "

Red Deity
"
			for(var/datum/mind/N in ticker.mode.red_deities)
				var/mob/M = N.current
				if(M)
					dat += "
Red Deity: [M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
			dat += "
"

		if(ticker.mode.blue_deities.len)
			dat += "

Blue Deity
"
			for(var/datum/mind/N in ticker.mode.blue_deities)
				var/mob/M = N.current
				if(M)
					dat += "
Blue Deity: [M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
			dat += "
"

		if(ticker.mode.red_deity_prophets.len)
			dat += "

Red Deity Prophets
"
			for(var/datum/mind/N in ticker.mode.red_deity_prophets)
				var/mob/M = N.current
				if(M)
					dat += "
Red Deity Prophet: [M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
			dat += "
"

		if(ticker.mode.blue_deity_prophets.len)
			dat += "

Blue Deity Prophets
"
			for(var/datum/mind/N in ticker.mode.blue_deity_prophets)
				var/mob/M = N.current
				if(M)
					dat += "
Blue Deity Prophet: [M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
			dat += "
"

		if(ticker.mode.red_deity_followers.len)
			dat += "

Red Deity Followers
"
			for(var/datum/mind/N in ticker.mode.red_deity_followers)
				var/mob/M = N.current
				if(M)
					dat += "
Red Deity Followers: [M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
			dat += "
"

		if(ticker.mode.blue_deity_followers.len)
			dat += "

Blue Deity Followers
"
			for(var/datum/mind/N in ticker.mode.blue_deity_followers)
				var/mob/M = N.current
				if(M)
					dat += "
Blue Deity Followers: [M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
			dat += "
"

		if(ticker.mode.traitors.len > 0)
			dat += "

Traitors
"
			for(var/datum/mind/traitor in ticker.mode.traitors)
				var/mob/M = traitor.current
				if(M)
					dat += "
[M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM	"
					dat += "FLW	"
					dat += "Show Objective
"
				else
					dat += "
[traitor.name]([traitor.key])Traitor body destroyed!	"
					dat += "PM
"
			dat += "
"

		if(ticker.mode.shadows.len)
			dat += "

Shadowlings
"
			for(var/datum/mind/N in ticker.mode.shadows)
				var/mob/M = N.current
				if(M)
					dat += "
[M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM	"
					dat += "FLW
"
				else
					dat += "
[N.name]([N.key])Shadowling body destroyed!	"
					dat += "PM
"
			dat += "
"

		if(ticker.mode.thralls.len)
			dat += "

Shadowling Thralls
"
			for(var/datum/mind/N in ticker.mode.thralls)
				var/mob/M = N.current
				if(M)
					dat += "
[M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM	"
					dat += "FLW
"
			dat += "
"

		if(ticker.mode.abductors.len)
			dat += "

Abductors
"
			for(var/datum/mind/abductor in ticker.mode.abductors)
				var/mob/M = abductor.current
				if(M)
					dat += "
[M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM	"
					dat += "FLW	"
					dat += "Show Objective
"
				else
					dat += "
[abductor.name]([abductor.key])Abductor body destroyed!
"
					dat += "PM	"
			dat += "
"
			dat += "

Abductees
"
			for(var/obj/machinery/abductor/experiment/E in machines)
				for(var/datum/mind/abductee in E.abductee_minds)
					var/mob/M = abductee.current
					if(M)
						dat += "
[M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
						dat += "PM	"
						dat += "FLW	"
						dat += "Show Objective
"
					else
						dat += "
[abductee.name]([abductee.key])Abductee body destroyed!	"
						dat += "PM
"
			dat += "
"

		var/list/blob_minds = list()
		for(var/mob/camera/blob/B in mob_list)
			blob_minds |= B.mind

		if(istype(ticker.mode, /datum/game_mode/blob) || blob_minds.len)
			dat += "

Blob
"
			if(istype(ticker.mode,/datum/game_mode/blob))
				var/datum/game_mode/blob/mode = ticker.mode
				blob_minds |= mode.blob_overminds
				dat += "
Progress: [blobs_legit.len]/[mode.blobwincount]
"

			for(var/datum/mind/blob in blob_minds)
				var/mob/M = blob.current
				if(M)
					dat += "
[M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM	"
					dat += "FLW
"
				else
					dat += "
[blob.name]([blob.key])Blob not found!	"
					dat += "PM
"
			dat += "
"

		if(istype(ticker.mode, /datum/game_mode/monkey))
			var/datum/game_mode/monkey/mode = ticker.mode
			dat += "

Monkey
"

			for(var/datum/mind/eek in mode.ape_infectees)
				var/mob/M = eek.current
				if(M)
					dat += "
[M.real_name][M.client ? "" : " (ghost)"][M.stat == 2 ? " (DEAD)" : ""]	"
					dat += "PM	"
					dat += "FLW
"
				else
					dat += "
[eek.name]([eek.key])Monkey not found!	"
					dat += "PM
"
			dat += "
"

		dat += ""
		usr << browse(dat, "window=roundstatus;size=420x500")
	else
		alert("The game hasn't started yet!")


code:
/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

			Enable/Disable CTF

			Reset Thunderdome to default state

			Shuttles

			Move Ferry

			Move Mining Shuttle

			Move Labor Shuttle

			"}

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

			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!

			There can only be me!

			Make all players retarded

			Egalitarian Station Mode

			Break all lights

			Fix all lights

			The floor is lava! (DANGEROUS: extremely lame)

			Change 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

/datum/admins/proc/Secrets_topic(item,href_list)
	var/datum/round_event/E
	var/ok = 0
	switch(item)
		if("admin_log")
			var/dat = "Admin Log
"
			for(var/l in admin_log)
				dat += "
[l]
"
			if(!admin_log.len)
				dat += "No-one has done anything this round!"
			usr << browse(dat, "window=admin_log")

		if("list_job_debug")
			var/dat = "Job Debug info.
"
			if(SSjob)
				for(var/line in SSjob.job_debug)
					dat += "[line]
"
				dat+= "*******

"
				for(var/datum/job/job in SSjob.occupations)
					if(!job)
						continue
					dat += "job: [job.title], current_positions: [job.current_positions], total_positions: [job.total_positions]
"
				usr << browse(dat, "window=jobdebug;size=600x500")

		if("show_admins")
			var/dat = "Current admins:
"
			if(admin_datums)
				for(var/ckey in admin_datums)
					var/datum/admins/D = admin_datums[ckey]
					dat += "[ckey] - [D.rank.name]
"
				usr << browse(dat, "window=showadmins;size=600x500")

		if("tdomereset")
			if(!check_rights(R_ADMIN))
				return
			var/delete_mobs = alert("Clear all mobs?","Confirm","Yes","No","Cancel")
			if(delete_mobs == "Cancel")
				return

			log_admin("[key_name(usr)] reset the thunderdome to default with delete_mobs==[delete_mobs].", 1)
			message_admins("[key_name_admin(usr)] reset the thunderdome to default with delete_mobs==[delete_mobs].")

			var/area/thunderdome = locate(/area/tdome/arena)
			if(delete_mobs == "Yes")
				for(var/mob/living/mob in thunderdome)
					qdel(mob) //Clear mobs
			for(var/obj/obj in thunderdome)
				if(!istype(obj,/obj/machinery/camera))
					qdel(obj) //Clear objects

			var/area/template = locate(/area/tdome/arena_source)
			template.copy_contents_to(thunderdome)

		if("clear_virus")

			var/choice = input("Are you sure you want to cure all disease?") in list("Yes", "Cancel")
			if(choice == "Yes")
				message_admins("[key_name_admin(usr)] has cured all diseases.")
				for(var/datum/disease/D in SSdisease.processing)
					D.cure(D)

		if("list_bombers")
			if(!check_rights(R_ADMIN))
				return
			var/dat = "Bombing List
"
			for(var/l in bombers)
				dat += text("[l]
")
			usr << browse(dat, "window=bombers")

		if("list_signalers")
			if(!check_rights(R_ADMIN))
				return
			var/dat = "Showing last [length(lastsignalers)] signalers.
"
			for(var/sig in lastsignalers)
				dat += "[sig]
"
			usr << browse(dat, "window=lastsignalers;size=800x500")

		if("list_lawchanges")
			if(!check_rights(R_ADMIN))
				return
			var/dat = "Showing last [length(lawchanges)] law changes.
"
			for(var/sig in lawchanges)
				dat += "[sig]
"
			usr << browse(dat, "window=lawchanges;size=800x500")

		if("moveminingshuttle")
			if(!check_rights(R_ADMIN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","ShM")
			if(!SSshuttle.toggleShuttle("mining","mining_home","mining_away"))
				message_admins("[key_name_admin(usr)] moved mining shuttle")
				log_admin("[key_name(usr)] moved the mining shuttle")

		if("movelaborshuttle")
			if(!check_rights(R_ADMIN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","ShL")
			if(!SSshuttle.toggleShuttle("laborcamp","laborcamp_home","laborcamp_away"))
				message_admins("[key_name_admin(usr)] moved labor shuttle")
				log_admin("[key_name(usr)] moved the labor shuttle")

		if("moveferry")
			if(!check_rights(R_ADMIN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","ShF")
			if(!SSshuttle.toggleShuttle("ferry","ferry_home","ferry_away"))
				message_admins("[key_name_admin(usr)] moved the centcom ferry")
				log_admin("[key_name(usr)] moved the centcom ferry")

		if("showailaws")
			if(!check_rights(R_ADMIN))
				return
			output_ai_laws()
		if("showgm")
			if(!check_rights(R_ADMIN))
				return
			if(!ticker || !ticker.mode)
				alert("The game hasn't started yet!")
			else if (ticker.mode)
				alert("The game mode is [ticker.mode.name]")
			else alert("For some reason there's a ticker, but not a game mode")
		if("manifest")
			if(!check_rights(R_ADMIN))
				return
			var/dat = "Showing Crew Manifest.
"
			dat += "

Name	Position
"
			for(var/datum/data/record/t in data_core.general)
				dat += "
[t.fields["name"]]	[t.fields["rank"]]
"
			dat += "
"
			usr << browse(dat, "window=manifest;size=440x410")
		if("DNA")
			if(!check_rights(R_ADMIN))
				return
			var/dat = "Showing DNA from blood.
"
			dat += "

Name	DNA	Blood Type
"
			for(var/mob/living/carbon/human/H in mob_list)
				if(H.ckey)
					dat += "
[H]	[H.dna.unique_enzymes]	[H.dna.blood_type]
"
			dat += "
"
			usr << browse(dat, "window=DNA;size=440x410")
		if("fingerprints")
			if(!check_rights(R_ADMIN))
				return
			var/dat = "Showing Fingerprints.
"
			dat += "

Name	Fingerprints
"
			for(var/mob/living/carbon/human/H in mob_list)
				if(H.ckey)
					dat += "
[H]	[md5(H.dna.uni_identity)]
"
			dat += "
"
			usr << browse(dat, "window=fingerprints;size=440x410")

		if("monkey")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","M")
			for(var/mob/living/carbon/human/H in mob_list)
				spawn(0)
					H.monkeyize()
			ok = 1

		if("allspecies")
			if(!check_rights(R_FUN))
				return
			var/result = input(usr, "Please choose a new species","Species") as null|anything in species_list
			if(result)
				log_admin("[key_name(usr)] turned all humans into [result]", 1)
				message_admins("\blue [key_name_admin(usr)] turned all humans into [result]")
				var/newtype = species_list[result]
				for(var/mob/living/carbon/human/H in mob_list)
					H.set_species(newtype)

		if("corgi")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","M")
			for(var/mob/living/carbon/human/H in mob_list)
				spawn(0)
					H.corgize()
			ok = 1

		if("tripleAI")
			if(!check_rights(R_FUN))
				return
			usr.client.triple_ai()
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","TriAI")

		if("power")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","P")
			log_admin("[key_name(usr)] made all areas powered", 1)
			message_admins("[key_name_admin(usr)] made all areas powered")
			power_restore()

		if("unpower")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","UP")
			log_admin("[key_name(usr)] made all areas unpowered", 1)
			message_admins("[key_name_admin(usr)] made all areas unpowered")
			power_failure()

		if("quickpower")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","QP")
			log_admin("[key_name(usr)] made all SMESs powered", 1)
			message_admins("[key_name_admin(usr)] made all SMESs powered")
			power_restore_quick()

		if("traitor_all")
			if(!check_rights(R_FUN))
				return
			if(!ticker || !ticker.mode)
				alert("The game hasn't started yet!")
				return
			var/objective = copytext(sanitize(input("Enter an objective")),1,MAX_MESSAGE_LEN)
			if(!objective)
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","TA([objective])")
			for(var/mob/living/carbon/human/H in player_list)
				if(H.stat == 2 || !H.client || !H.mind) continue
				if(is_special_character(H)) continue
				//traitorize(H, objective, 0)
				ticker.mode.traitors += H.mind
				H.mind.special_role = "traitor"
				var/datum/objective/new_objective = new
				new_objective.owner = H
				new_objective.explanation_text = objective
				H.mind.objectives += new_objective
				ticker.mode.greet_traitor(H.mind)
				//ticker.mode.forge_traitor_objectives(H.mind)
				ticker.mode.finalize_traitor(H.mind)
			for(var/mob/living/silicon/A in player_list)
				if(A.stat == 2 || !A.client || !A.mind) continue
				if(ispAI(A)) continue
				else if(is_special_character(A)) continue
				ticker.mode.traitors += A.mind
				A.mind.special_role = "traitor"
				var/datum/objective/new_objective = new
				new_objective.owner = A
				new_objective.explanation_text = objective
				A.mind.objectives += new_objective
				ticker.mode.greet_traitor(A.mind)
				ticker.mode.finalize_traitor(A.mind)
			message_admins("[key_name_admin(usr)] used everyone is a traitor secret. Objective is [objective]")
			log_admin("[key_name(usr)] used everyone is a traitor secret. Objective is [objective]")

		if("changebombcap")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","BC")

			var/newBombCap = input(usr,"What would you like the new bomb cap to be. (entered as the light damage range (the 3rd number in common (1,2,3) notation)) Must be above 4)", "New Bomb Cap", MAX_EX_LIGHT_RANGE) as num|null
			if (newBombCap < 4)
				return

			MAX_EX_DEVESTATION_RANGE = round(newBombCap/4)
			MAX_EX_HEAVY_RANGE = round(newBombCap/2)
			MAX_EX_LIGHT_RANGE = newBombCap
			//I don't know why these are their own variables, but fuck it, they are.
			MAX_EX_FLASH_RANGE = newBombCap
			MAX_EX_FLAME_RANGE = newBombCap

			message_admins("[key_name_admin(usr)] changed the bomb cap to [MAX_EX_DEVESTATION_RANGE], [MAX_EX_HEAVY_RANGE], [MAX_EX_LIGHT_RANGE]")
			log_admin("[key_name(usr)] changed the bomb cap to [MAX_EX_DEVESTATION_RANGE], [MAX_EX_HEAVY_RANGE], [MAX_EX_LIGHT_RANGE]")

		if("lightsout")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","LO")
			message_admins("[key_name_admin(usr)] has broke a lot of lights")
			E = new /datum/round_event/electrical_storm{lightsoutAmount = 2}()

		if("blackout")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","BO")
			message_admins("[key_name_admin(usr)] broke all lights")
			for(var/obj/machinery/light/L in machines)
				L.broken()

		if("whiteout")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","WO")
			message_admins("[key_name_admin(usr)] fixed all lights")
			for(var/obj/machinery/light/L in machines)
				L.fix()

		if("floorlava")
			var/datum/weather/floor_is_lava/storm = new /datum/weather/floor_is_lava
			storm.weather_start_up()

		if("virus")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","V")
			switch(alert("Do you want this to be a random disease or do you have something in mind?",,"Make Your Own","Random","Choose"))
				if("Make Your Own")
					AdminCreateVirus(usr.client)
				if("Random")
					E = new /datum/round_event/disease_outbreak()
				if("Choose")
					var/virus = input("Choose the virus to spread", "BIOHAZARD") as null|anything in typesof(/datum/disease)
					E = new /datum/round_event/disease_outbreak{}()
					var/datum/round_event/disease_outbreak/DO = E
					DO.virus_type = virus

		if("retardify")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","RET")
			for(var/mob/living/carbon/human/H in player_list)
				H << "You suddenly feel stupid."
				H.setBrainLoss(60)
			message_admins("[key_name_admin(usr)] made everybody retarded")

		if("eagles")//SCRAW
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","EgL")
			for(var/obj/machinery/door/airlock/W in machines)
				if(W.z == ZLEVEL_STATION && !istype(get_area(W), /area/bridge) && !istype(get_area(W), /area/crew_quarters) && !istype(get_area(W), /area/security/prison))
					W.req_access = list()
			message_admins("[key_name_admin(usr)] activated Egalitarian Station mode")
			priority_announce("Centcom airlock control override activated. Please take this time to get acquainted with your coworkers.", null, 'sound/AI/commandreport.ogg')

		if("guns")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","SG")
			var/survivor_probability = 0
			switch(alert("Do you want this to create survivors antagonists?",,"No Antags","Some Antags","All Antags!"))
				if("Some Antags")
					survivor_probability = 25
				if("All Antags!")
					survivor_probability = 100

			rightandwrong(0, usr, survivor_probability)

		if("magic")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","SM")
			var/survivor_probability = 0
			switch(alert("Do you want this to create survivors antagonists?",,"No Antags","Some Antags","All Antags!"))
				if("Some Antags")
					survivor_probability = 25
				if("All Antags!")
					survivor_probability = 100

			rightandwrong(1, usr, survivor_probability)

		if("events")
			if(!check_rights(R_FUN))
				return
			if(!SSevent.wizardmode)
				if(alert("Do you want to toggle summon events on?",,"Yes","No") == "Yes")
					summonevents()
					feedback_inc("admin_secrets_fun_used",1)
					feedback_add_details("admin_secrets_fun_used","SE")

			else
				switch(alert("What would you like to do?",,"Intensify Summon Events","Turn Off Summon Events","Nothing"))
					if("Intensify Summon Events")
						summonevents()
						feedback_inc("admin_secrets_fun_used",1)
						feedback_add_details("admin_secrets_fun_used","SE")
					if("Turn Off Summon Events")
						SSevent.toggleWizardmode()
						SSevent.resetFrequency()

		if("dorf")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","DF")
			for(var/mob/living/carbon/human/B in mob_list)
				B.facial_hair_style = "Dward Beard"
				B.update_hair()
			message_admins("[key_name_admin(usr)] activated dorf mode")

		if("onlyone")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","OO")
			usr.client.only_one()
//				message_admins("[key_name_admin(usr)] has triggered a battle to the death (only one)")

		if("onlyme")
			if(!check_rights(R_FUN))
				return
			feedback_inc("admin_secrets_fun_used",1)
			feedback_add_details("admin_secrets_fun_used","OM")
			only_me()

		if("maint_access_brig")
			if(!check_rights(R_DEBUG))
				return
			for(var/obj/machinery/door/airlock/maintenance/M in machines)
				M.check_access()
				if (access_maint_tunnels in M.req_access)
					M.req_access = list(access_brig)
			message_admins("[key_name_admin(usr)] made all maint doors brig access-only.")
		if("maint_access_engiebrig")
			if(!check_rights(R_DEBUG))
				return
			for(var/obj/machinery/door/airlock/maintenance/M in machines)
				M.check_access()
				if (access_maint_tunnels in M.req_access)
					M.req_access = list()
					M.req_one_access = list(access_brig,access_engine)
			message_admins("[key_name_admin(usr)] made all maint doors engineering and brig access-only.")
		if("infinite_sec")
			if(!check_rights(R_DEBUG))
				return
			var/datum/job/J = SSjob.GetJob("Security Officer")
			if(!J) return
			J.total_positions = -1
			J.spawn_positions = -1
			message_admins("[key_name_admin(usr)] has removed the cap on security officers.")

		if("ctfbutton")
			if(!check_rights(R_ADMIN))
				return
			var/ctf_enabled = 0
			for(var/obj/machinery/capture_the_flag/CTF in machines)
				ctf_enabled = !CTF.ctf_enabled
				CTF.ctf_enabled = !CTF.ctf_enabled
			message_admins("[key_name_admin(usr)] has [ctf_enabled? "enabled" : "disabled"] CTF!")

	if(E)
		E.processing = 0
		if(E.announceWhen>0)
			if(alert(usr, "Would you like to alert the crew?", "Alert", "Yes", "No") == "No")
				E.announceWhen = -1
		E.processing = 1
	if (usr)
		log_admin("[key_name(usr)] used secret [item]")
		if (ok)
			world << text("A secret has been activated by []!", usr.key)

license: mit


code:
/proc/add_note(target_ckey, notetext, timestamp, adminckey, logged = 1, server)
	if(!dbcon.IsConnected())
		usr << "Failed to establish database connection."
		return
	if(!target_ckey)
		var/new_ckey = ckey(input(usr,"Who would you like to add a note for?","Enter a ckey",null) as text)
		if(!new_ckey)
			return
		new_ckey = sanitizeSQL(new_ckey)
		var/DBQuery/query_find_ckey = dbcon.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE ckey = '[new_ckey]'")
		if(!query_find_ckey.Execute())
			var/err = query_find_ckey.ErrorMsg()
			log_game("SQL ERROR obtaining ckey from player table. Error : \[[err]\]\n")
			return
		if(!query_find_ckey.NextRow())
			if(alert(usr, "[new_ckey] has not been seen before, are you sure you want to add them to the watchlist?", "Unknown ckey", "Yes", "No", "Cancel") != "Yes")
				return
		target_ckey = new_ckey
	var/target_sql_ckey = sanitizeSQL(target_ckey)
	if(!notetext)
		notetext = input(usr,"Write your Note","Add Note") as message
		if(!notetext)
			return
	notetext = sanitizeSQL(notetext)
	if(!timestamp)
		timestamp = SQLtime()
	if(!adminckey)
		adminckey = usr.ckey
		if(!adminckey)
			return
	var/admin_sql_ckey = sanitizeSQL(adminckey)
	if(!server)
		if (config && config.server_name)
			server = config.server_name
	server = sanitizeSQL(server)
	var/DBQuery/query_noteadd = dbcon.NewQuery("INSERT INTO [format_table_name("notes")] (ckey, timestamp, notetext, adminckey, server) VALUES ('[target_sql_ckey]', '[timestamp]', '[notetext]', '[admin_sql_ckey]', '[server]')")
	if(!query_noteadd.Execute())
		var/err = query_noteadd.ErrorMsg()
		log_game("SQL ERROR adding new note to table. Error : \[[err]\]\n")
		return
	if(logged)
		log_admin("[key_name(usr)] has added a note to [target_ckey]: [notetext]")
		message_admins("[key_name_admin(usr)] has added a note to [target_ckey]:
[notetext]")
		show_note(target_ckey)

/proc/remove_note(note_id)
	var/ckey
	var/notetext
	var/adminckey
	if(!dbcon.IsConnected())
		usr << "Failed to establish database connection."
		return
	if(!note_id)
		return
	note_id = text2num(note_id)
	var/DBQuery/query_find_note_del = dbcon.NewQuery("SELECT ckey, notetext, adminckey FROM [format_table_name("notes")] WHERE id = [note_id]")
	if(!query_find_note_del.Execute())
		var/err = query_find_note_del.ErrorMsg()
		log_game("SQL ERROR obtaining ckey, notetext, adminckey from notes table. Error : \[[err]\]\n")
		return
	if(query_find_note_del.NextRow())
		ckey = query_find_note_del.item[1]
		notetext = query_find_note_del.item[2]
		adminckey = query_find_note_del.item[3]
	var/DBQuery/query_del_note = dbcon.NewQuery("DELETE FROM [format_table_name("notes")] WHERE id = [note_id]")
	if(!query_del_note.Execute())
		var/err = query_del_note.ErrorMsg()
		log_game("SQL ERROR removing note from table. Error : \[[err]\]\n")
		return
	log_admin("[key_name(usr)] has removed a note made by [adminckey] from [ckey]: [notetext]")
	message_admins("[key_name_admin(usr)] has removed a note made by [adminckey] from [ckey]:
[notetext]")
	show_note(ckey)

/proc/edit_note(note_id)
	if(!dbcon.IsConnected())
		usr << "Failed to establish database connection."
		return
	if(!note_id)
		return
	note_id = text2num(note_id)
	var/target_ckey
	var/sql_ckey = sanitizeSQL(usr.ckey)
	var/DBQuery/query_find_note_edit = dbcon.NewQuery("SELECT ckey, notetext, adminckey FROM [format_table_name("notes")] WHERE id = [note_id]")
	if(!query_find_note_edit.Execute())
		var/err = query_find_note_edit.ErrorMsg()
		log_game("SQL ERROR obtaining notetext from notes table. Error : \[[err]\]\n")
		return
	if(query_find_note_edit.NextRow())
		target_ckey = query_find_note_edit.item[1]
		var/old_note = query_find_note_edit.item[2]
		var/adminckey = query_find_note_edit.item[3]
		var/new_note = input("Input new note", "New Note", "[old_note]") as message
		if(!new_note)
			return
		new_note = sanitizeSQL(new_note)
		var/edit_text = "Edited by [sql_ckey] on [SQLtime()] from
[old_note]
to
[new_note]
"
		edit_text = sanitizeSQL(edit_text)
		var/DBQuery/query_update_note = dbcon.NewQuery("UPDATE [format_table_name("notes")] SET notetext = '[new_note]', last_editor = '[sql_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE id = [note_id]")
		if(!query_update_note.Execute())
			var/err = query_update_note.ErrorMsg()
			log_game("SQL ERROR editing note. Error : \[[err]\]\n")
			return
		log_admin("[key_name(usr)] has edited [target_ckey]'s note made by [adminckey] from [old_note] to [new_note]")
		message_admins("[key_name_admin(usr)] has edited [target_ckey]'s note made by [adminckey] from
[old_note]
to
[new_note]")
		show_note(target_ckey)

/proc/show_note(target_ckey, index, linkless = 0)
	var/output
	var/navbar
	var/ruler
	ruler = "
"
	navbar = "\[All\]|\[#\]"
	for(var/letter in alphabet)
		navbar += "|\[[letter]\]"
	navbar += "
<form method='GET' name='search' action='?'>\
	<input type='hidden' name='_src_' value='holder'>\
	<input type='text' name='notessearch' value='[index]'>\
	<input type='submit' value='Search'></form>"
	if(!linkless)
		output = navbar
	if(target_ckey)
		var/target_sql_ckey = sanitizeSQL(target_ckey)
		var/DBQuery/query_get_notes = dbcon.NewQuery("SELECT id, timestamp, notetext, adminckey, last_editor, server FROM [format_table_name("notes")] WHERE ckey = '[target_sql_ckey]' ORDER BY timestamp")
		if(!query_get_notes.Execute())
			var/err = query_get_notes.ErrorMsg()
			log_game("SQL ERROR obtaining ckey, notetext, adminckey, last_editor, server from notes table. Error : \[[err]\]\n")
			return
		output += "
Notes of [target_ckey]
"
		if(!linkless)
			output += "\[Add Note\]"
		output += ruler
		while(query_get_notes.NextRow())
			var/id = query_get_notes.item[1]
			var/timestamp = query_get_notes.item[2]
			var/notetext = query_get_notes.item[3]
			var/adminckey = query_get_notes.item[4]
			var/last_editor = query_get_notes.item[5]
			var/server = query_get_notes.item[6]
			output += "[timestamp] | [server] | [adminckey]"
			if(!linkless)
				output += " \[Remove Note\] \[Edit Note\]"
				if(last_editor)
					output += " Last edit by [last_editor] (Click here to see edit log)"
			output += "
[notetext]
"
	else if(index)
		var/index_ckey
		var/search
		output += "\[Add Note\]"
		output += ruler
		if(!isnum(index))
			index = sanitizeSQL(index)
		switch(index)
			if(1)
				search = "^."
			if(2)
				search = "^\[^\[:alpha:\]\]"
			else
				search = "^[index]"
		var/DBQuery/query_list_notes = dbcon.NewQuery("SELECT DISTINCT ckey FROM [format_table_name("notes")] WHERE ckey REGEXP '[search]' ORDER BY ckey")
		if(!query_list_notes.Execute())
			var/err = query_list_notes.ErrorMsg()
			log_game("SQL ERROR obtaining ckey from notes table. Error : \[[err]\]\n")
			return
		while(query_list_notes.NextRow())
			index_ckey = query_list_notes.item[1]
			output += "[index_ckey]
"
	else
		output += "\[Add Note\]"
		output += ruler
	usr << browse(output, "window=show_notes;size=900x500")

#define NOTESFILE "data/player_notes.sav"
//if the AUTOCONVERT_NOTES is turned on, anytime a player connects this will be run to try and add all their notes to the databas
/proc/convert_notes_sql(ckey)
	var/savefile/notesfile = new(NOTESFILE)
	if(!notesfile)
		log_game("Error: Cannot access [NOTESFILE]")
		return
	notesfile.cd = "/[ckey]"
	while(!notesfile.eof)
		var/notetext
		notesfile >> notetext
		var/server
		if(config && config.server_name)
			server = config.server_name
		var/regex/note = new("^(\\d{2}-\\w{3}-\\d{4}) \\| (.+) ~(\\w+)$", "i")
		note.Find(notetext)
		var/timestamp = note.group[1]
		notetext = note.group[2]
		var/adminckey = note.group[3]
		var/DBQuery/query_convert_time = dbcon.NewQuery("SELECT ADDTIME(STR_TO_DATE('[timestamp]','%d-%b-%Y'), '0')")
		if(!query_convert_time.Execute())
			var/err = query_convert_time.ErrorMsg()
			log_game("SQL ERROR converting timestamp. Error : \[[err]\]\n")
			return
		if(query_convert_time.NextRow())
			timestamp = query_convert_time.item[1]
		if(ckey && notetext && timestamp && adminckey && server)
			add_note(ckey, notetext, timestamp, adminckey, 0, server)
	notesfile.cd = "/"
	notesfile.dir.Remove(ckey)

/*alternatively this proc can be run once to pass through every note and attempt to convert it before deleting the file, if done then AUTOCONVERT_NOTES should be turned off
this proc can take several minutes to execute fully if converting and cause DD to hang if converting a lot of notes; it's not advised to do so while a server is live
/proc/mass_convert_notes()
	world << "Beginning mass note conversion"
	var/savefile/notesfile = new(NOTESFILE)
	if(!notesfile)
		log_game("Error: Cannot access [NOTESFILE]")
		return
	notesfile.cd = "/"
	for(var/ckey in notesfile.dir)
		convert_notes_sql(ckey)
	world << "Deleting NOTESFILE"
	fdel(NOTESFILE)
	world << "Finished mass note conversion, remember to turn off AUTOCONVERT_NOTES"*/
#undef NOTESFILE


code:
/datum/admins/proc/stickyban(action,data)
	if(!check_rights(R_BAN))
		return
	switch (action)
		if ("show")
			stickyban_show()
		if ("add")
			var/list/ban = list()
			var/ckey
			ban["admin"] = usr.key
			ban["type"] = list("sticky")
			ban["reason"] = "(InGameBan)([usr.key])" //this will be displayed in dd only

			if (data["ckey"])
				ckey = ckey(data["ckey"])
			else
				ckey = input(usr,"Ckey","Ckey","") as text|null
				if (!ckey)
					return
				ckey = ckey(ckey)
			if (get_stickyban_from_ckey(ckey))
				usr << "Error: Can not add a stickyban: User already has a current sticky ban"

			if (data["reason"])
				ban["message"] = data["reason"]
			else
				var/reason = input(usr,"Reason","Reason","Ban Evasion") as text|null
				if (!reason)
					return
				ban["message"] = "[reason]"

			world.SetConfig("ban",ckey,list2stickyban(ban))

			log_admin("[key_name(usr)] has stickybanned [ckey].\nReason: [ban["message"]]")
			message_admins("[key_name_admin(usr)] has stickybanned [ckey].\nReason: [ban["message"]]")

		if ("remove")
			if (!data["ckey"])
				return
			var/ckey = data["ckey"]

			var/ban = get_stickyban_from_ckey(ckey)
			if (!ban)
				usr << "Error: No sticky ban for [ckey] found!"
				return
			if (alert("Are you sure you want to remove the sticky ban on [ckey]?","Are you sure","Yes","No") == "No")
				return
			if (!get_stickyban_from_ckey(ckey))
				usr << "Error: The ban disappeared."
				return
			world.SetConfig("ban",ckey, null)

			log_admin("[key_name(usr)] removed [ckey]'s stickyban")
			message_admins("[key_name_admin(usr)] removed [ckey]'s stickyban")

		if ("remove_alt")
			if (!data["ckey"])
				return
			var/ckey = data["ckey"]
			if (!data["alt"])
				return
			var/alt = ckey(data["alt"])
			var/ban = get_stickyban_from_ckey(ckey)
			if (!ban)
				usr << "Error: No sticky ban for [ckey] found!"
				return

			var/found = 0
			//we have to do it this way because byond keeps the case in its sticky ban matches WHY!!!
			for (var/key in ban["keys"])
				if (ckey(key) == alt)
					found = 1
					break

			if (!found)
				usr << "Error: [alt] is not linked to [ckey]'s sticky ban!"
				return

			if (alert("Are you sure you want to disassociate [alt] from [ckey]'s sticky ban? \nNote: Nothing stops byond from re-linking them","Are you sure","Yes","No") == "No")
				return

			//we have to do this again incase something changes
			ban = get_stickyban_from_ckey(ckey)
			if (!ban)
				usr << "Error: The ban disappeared."
				return

			found = 0
			for (var/key in ban["keys"])
				if (ckey(key) == alt)
					ban["keys"] -= key
					found = 1
					break

			if (!found)
				usr << "Error: [alt] link to [ckey]'s sticky ban disappeared."
				return

			world.SetConfig("ban",ckey,list2stickyban(ban))

			log_admin("[key_name(usr)] has disassociated [alt] from [ckey]'s sticky ban")
			message_admins("[key_name_admin(usr)] has disassociated [alt] from [ckey]'s sticky ban")

		if ("edit")
			if (!data["ckey"])
				return
			var/ckey = data["ckey"]
			var/ban = get_stickyban_from_ckey(ckey)
			if (!ban)
				usr << "Error: No sticky ban for [ckey] found!"
				return
			var/oldreason = ban["message"]
			var/reason = input(usr,"Reason","Reason","[ban["message"]]") as text|null
			if (!reason || reason == oldreason)
				return
			//we have to do this again incase something changed while we waited for input
			ban = get_stickyban_from_ckey(ckey)
			if (!ban)
				usr << "Error: The ban disappeared."
				return
			ban["message"] = "[reason]"

			world.SetConfig("ban",ckey,list2stickyban(ban))

			log_admin("[key_name(usr)] has edited [ckey]'s sticky ban reason from [oldreason] to [reason]")
			message_admins("[key_name_admin(usr)] has edited [ckey]'s sticky ban reason from [oldreason] to [reason]")

/datum/admins/proc/stickyban_gethtml(ckey, ban)
	. = "\[-\][ckey]
"
	. += "[ban["message"]] \[Edit\]
"
	if (ban["admin"])
		. += "[ban["admin"]]
"
	else
		. += "LEGACY
"
	. += "Caught keys
\n
"
	for (var/key in ban["keys"])
		if (ckey(key) == ckey)
			continue
		. += "
\[-\][key]
"
	. += "
\n"

/datum/admins/proc/stickyban_show()
	if(!check_rights(R_BAN))
		return
	var/list/bans = sortList(world.GetConfig("ban"))
	var/banhtml = ""
	for(var/key in bans)
		var/ckey = ckey(key)
		var/ban = stickyban2list(world.GetConfig("ban",key))
		banhtml += "

\n"
		banhtml += stickyban_gethtml(ckey,ban)

	var/html = {"

		<title>Sticky Bans</title>

All Sticky Bans:
 \[+\]

		[banhtml]

	"}
	usr << browse(html,"window=stickybans;size=700x400")

/proc/get_stickyban_from_ckey(var/ckey)
	if (!ckey)
		return null
	ckey = ckey(ckey)
	. = null
	for (var/key in world.GetConfig("ban"))
		if (ckey(key) == ckey)
			. = stickyban2list(world.GetConfig("ban",key))
			break

/proc/stickyban2list(var/ban)
	if (!ban)
		return null
	. = params2list(ban)
	.["keys"] = splittext(.["keys"], ",")
	.["type"] = splittext(.["type"], ",")
	.["IP"] = splittext(.["IP"], ",")
	.["computer_id"] = splittext(.["computer_id"], ",")

/proc/list2stickyban(var/list/ban)
	if (!ban || !islist(ban))
		return null
	. = ban.Copy()
	if (.["keys"])
		.["keys"] = jointext(.["keys"], ",")
	if (.["type"])
		.["type"] = jointext(.["type"], ",")
	if (.["IP"])
		.["IP"] = jointext(.["IP"], ",")
	if (.["computer_id"])
		.["computer_id"] = jointext(.["computer_id"], ",")
	. = list2params(.)

/client/proc/stickybanpanel()
	set name = "Sticky Ban Panel"
	set category = "Admin"
	if (!holder)
		return
	holder.stickyban_show()