code:
/obj/structure/table
	mouse_drag_pointer = MOUSE_ACTIVE_POINTER
	var/list/table_contents = list()
	var/viewing_category = 1 //typical powergamer starting on the Weapons tab
	var/list/categories = list(CAT_WEAPON,CAT_AMMO,CAT_ROBOT,CAT_FOOD,CAT_MISC)

/obj/structure/table/MouseDrop(mob/living/user)
	if(!istype(user))
		return
	if(!user.IsAdvancedToolUser())
		return
	tablecraft(user)

/obj/structure/table/proc/check_contents(datum/table_recipe/R)
	check_table()
	main_loop:
		for(var/A in R.reqs)
			var/needed_amount = R.reqs[A]
			for(var/B in table_contents)
				if(ispath(B, A))
					if(table_contents[B] >= R.reqs[A])
						continue main_loop
					else
						needed_amount -= table_contents[B]
						if(needed_amount <= 0)
							continue main_loop
						else
							continue
			return 0
	for(var/A in R.chem_catalysts)
		if(table_contents[A] < R.chem_catalysts[A])
			return 0
	return 1

/obj/structure/table/proc/check_table()
	table_contents = list()
	for(var/obj/item/I in loc)
		if(istype(I, /obj/item/stack))
			var/obj/item/stack/S = I
			table_contents[I.type] += S.amount
		else
			if(istype(I, /obj/item/weapon/reagent_containers))
				var/obj/item/weapon/reagent_containers/RC = I
				if(RC.flags & OPENCONTAINER)
					for(var/datum/reagent/A in RC.reagents.reagent_list)
						table_contents[A.type] += A.volume
			table_contents[I.type] += 1

/obj/structure/table/proc/check_tools(mob/user, datum/table_recipe/R)
	if(!R.tools.len)
		return 1
	var/list/possible_tools = list()
	for(var/obj/item/I in user.contents)
		if(istype(I, /obj/item/weapon/storage))
			for(var/obj/item/SI in I.contents)
				possible_tools += SI.type
		else
			possible_tools += I.type
	possible_tools += table_contents
	main_loop:
		for(var/A in R.tools)
			for(var/I in possible_tools)
				if(ispath(I,A))
					possible_tools -= I
					continue main_loop
			return 0
	return 1

/obj/structure/table/proc/construct_item(mob/user, datum/table_recipe/R)
	check_table()
	var/send_feedback = 1
	if(check_contents(R))
		if(check_tools(user, R))
			if(do_after(user, R.time, target = src))
				if(!check_contents(R))
					return ", missing component."
				if(!check_tools(user, R))
					return ", missing tool."
				var/atom/movable/I = new R.result (loc)
				if(istype(I, /obj/item/weapon/reagent_containers/food/snacks))
					var/obj/item/weapon/reagent_containers/food/snacks/S = I
					S.create_reagents(S.volume)
					feedback_add_details("food_made","[S.type]")
					send_feedback = 0
				var/list/parts = del_reqs(R, I)
				for(var/A in parts)
					if(istype(A, /obj/item))
						var/atom/movable/B = A
						B.loc = I
						B.pixel_x = initial(B.pixel_x)
						B.pixel_y = initial(B.pixel_y)
					else
						if(!I.reagents)
							I.reagents = new /datum/reagents()
						I.reagents.reagent_list.Add(A)
				I.CheckParts()
				if(send_feedback)
					feedback_add_details("object_crafted","[I.type]")
				return 0
			return "."
		return ", missing tool."
	return ", missing component."

/obj/structure/table/proc/del_reqs(datum/table_recipe/R, atom/movable/resultobject)
	var/list/Deletion = list()
	var/amt
	var/reagenttransfer = 0
	if(istype(resultobject,/obj/item/weapon/reagent_containers))
		reagenttransfer = 1
	for(var/A in R.reqs)
		amt = R.reqs[A]
		if(ispath(A, /obj/item/stack))
			var/obj/item/stack/S
			stack_loop:
				for(var/B in table_contents)
					if(ispath(B, A))
						while(amt > 0)
							S = locate(B) in loc
							if(S.amount >= amt)
								S.use(amt)
								break stack_loop
							else
								amt -= S.amount
								qdel(S)
		else if(ispath(A, /obj/item))
			var/obj/item/I
			item_loop:
				for(var/B in table_contents)
					if(ispath(B, A))
						var/item_amount = table_contents[B]
						while(item_amount > 0)
							I = locate(B) in loc
							Deletion.Add(I)
							I.loc = null //remove it from the table loc so that we don't locate the same item every time (will be relocated inside the crafted item in construct_item())
							amt--
							item_amount--
							if(reagenttransfer && istype(I,/obj/item/weapon/reagent_containers))
								var/obj/item/weapon/reagent_containers/RC = I
								RC.reagents.trans_to(resultobject, RC.reagents.total_volume)
							if(amt <= 0)
								break item_loop
		else
			var/datum/reagent/RG = new A
			reagent_loop:
				for(var/B in table_contents)
					if(ispath(B, /obj/item/weapon/reagent_containers))
						var/obj/item/RC = locate(B) in loc
						if(RC.reagents.has_reagent(RG.id, amt))
							if(reagenttransfer)
								RC.reagents.trans_id_to(resultobject,RG.id, amt)
							else
								RC.reagents.remove_reagent(RG.id, amt)
							RG.volume = amt
							Deletion.Add(RG)
							break reagent_loop
						else if(RC.reagents.has_reagent(RG.id))
							Deletion.Add(RG)
							RG.volume += RC.reagents.get_reagent_amount(RG.id)
							amt -= RC.reagents.get_reagent_amount(RG.id)
							if(reagenttransfer)
								RC.reagents.trans_id_to(resultobject,RG.id, RG.volume)
							else
								RC.reagents.del_reagent(RG.id)

	var/list/partlist = list(R.parts.len)
	for(var/M in R.parts)
		partlist[M] = R.parts[M]
	deletion_loop:
		for(var/B in Deletion)
			for(var/A in R.parts)
				if(istype(B, A))
					if(partlist[A] > 0) //do we still need a part like that?
						partlist[A] -= 1
						continue deletion_loop
			Deletion.Remove(B)
			qdel(B)

	return Deletion

/obj/structure/table/proc/tablecraft(mob/user)
	if(user.incapacitated() || user.lying || !Adjacent(user))
		return
	check_table()
	user.face_atom(src)
	var/dat = "
Crafting menu
"
	if(busy)
		dat += "
"
		dat += "Crafting in progress...
"
	else
		dat += "<--"
		dat += " [categories[prev_cat()]] |"
		dat += " [categories[viewing_category]] "
		dat += "| [categories[next_cat()]] "
		dat += "-->

"

		dat += "
"

		//Filter the recipes we can craft to the top
		var/list/can_craft = list()
		var/list/cant_craft = list()
		for(var/datum/table_recipe/R in table_recipes)
			if(R.category != categories[viewing_category])
				continue
			if(check_contents(R))
				can_craft += R
			else
				cant_craft += R

		for(var/datum/table_recipe/R in can_craft)
			dat += build_recipe_text(R)
		for(var/datum/table_recipe/R in cant_craft)
			dat += build_recipe_text(R)

		dat += "
"

	var/datum/browser/popup = new(user, "table", "Table", 500, 500)
	popup.set_content(dat)
	popup.open()
	return

/obj/structure/table/Topic(href, href_list)
	if(usr.stat || !Adjacent(usr) || usr.lying)
		return
	if(href_list["make"])
		var/datum/table_recipe/TR = locate(href_list["make"])
		busy = 1
		tablecraft(usr)
		var/fail_msg = construct_item(usr, TR)
		if(!fail_msg)
			usr << "[TR.name] constructed."
		else
			usr << "Construction failed[fail_msg]"
		busy = 0
		tablecraft(usr)
	if(href_list["forwardCat"])
		viewing_category = next_cat()
		usr << "Category is now [categories[viewing_category]]."
		tablecraft(usr)
	if(href_list["backwardCat"])
		viewing_category = prev_cat()
		usr << "Category is now [categories[viewing_category]]."
		tablecraft(usr)

//Next works nicely with modular arithmetic
/obj/structure/table/proc/next_cat()
	. = viewing_category % categories.len + 1

//Previous can go fuck itself
/obj/structure/table/proc/prev_cat()
	if(viewing_category == categories.len)
		. = viewing_category-1
	else
		. = viewing_category % categories.len - 1
	if(. <= 0)
		. = categories.len

/obj/structure/table/proc/build_recipe_text(datum/table_recipe/R)
	. = ""
	var/name_text = ""
	var/req_text = ""
	var/tool_text = ""
	var/catalist_text = ""
	if(check_contents(R))
		name_text ="[R.name]"

	else
		name_text = "[R.name]"

	if(name_text)
		for(var/A in R.reqs)
			if(ispath(A, /obj))
				var/obj/O = new A
				req_text += " [R.reqs[A]] [O.name]"
				qdel(O)
			if(ispath(A, /datum/reagent))
				var/datum/reagent/RE = new A
				req_text += " [R.reqs[A]] [RE.name]"
				qdel(RE)

		if(R.chem_catalysts.len)
			catalist_text += ", Catalysts:"
			for(var/C in R.chem_catalysts)
				if(ispath(C, /datum/reagent))
					var/datum/reagent/RE = new C
					catalist_text += " [R.chem_catalysts[C]] [RE.name]"
					qdel(RE)

		if(R.tools.len)
			tool_text += ", Tools:"
			for(var/O in R.tools)
				if(ispath(O, /obj))
					var/obj/T = new O
					tool_text += " [R.tools[O]] [T.name]"
					qdel(T)

		. = "[name_text][req_text][tool_text][catalist_text]
"


code:
//CONTAINS: Suit fibers and Detective's Scanning Computer

/atom/var/list/suit_fibers

/atom/proc/add_fibers(mob/living/carbon/human/M)
	if(M.gloves && istype(M.gloves,/obj/item/clothing/))
		var/obj/item/clothing/gloves/G = M.gloves
		if(G.transfer_blood > 1) //bloodied gloves transfer blood to touched objects
			if(add_blood(G.bloody_hands_mob)) //only reduces the bloodiness of our gloves if the item wasn't already bloody
				G.transfer_blood--
	else if(M.bloody_hands > 1)
		if(add_blood(M.bloody_hands_mob))
			M.bloody_hands--
	if(!suit_fibers) suit_fibers = list()
	var/fibertext
	var/item_multiplier = istype(src,/obj/item)?1.2:1
	if(M.wear_suit)
		fibertext = "Material from \a [M.wear_suit]."
		if(prob(10*item_multiplier) && !(fibertext in suit_fibers))
			//world.log << "Added fibertext: [fibertext]"
			suit_fibers += fibertext
		if(!(M.wear_suit.body_parts_covered & CHEST))
			if(M.w_uniform)
				fibertext = "Fibers from \a [M.w_uniform]."
				if(prob(12*item_multiplier) && !(fibertext in suit_fibers)) //Wearing a suit means less of the uniform exposed.
					//world.log << "Added fibertext: [fibertext]"
					suit_fibers += fibertext
		if(!(M.wear_suit.body_parts_covered & HANDS))
			if(M.gloves)
				fibertext = "Material from a pair of [M.gloves.name]."
				if(prob(20*item_multiplier) && !(fibertext in suit_fibers))
					//world.log << "Added fibertext: [fibertext]"
					suit_fibers += fibertext
	else if(M.w_uniform)
		fibertext = "Fibers from \a [M.w_uniform]."
		if(prob(15*item_multiplier) && !(fibertext in suit_fibers))
			// "Added fibertext: [fibertext]"
			suit_fibers += fibertext
		if(M.gloves)
			fibertext = "Material from a pair of [M.gloves.name]."
			if(prob(20*item_multiplier) && !(fibertext in suit_fibers))
				//world.log << "Added fibertext: [fibertext]"
				suit_fibers += "Material from a pair of [M.gloves.name]."
	else if(M.gloves)
		fibertext = "Material from a pair of [M.gloves.name]."
		if(prob(20*item_multiplier) && !(fibertext in suit_fibers))
			//world.log << "Added fibertext: [fibertext]"
			suit_fibers += "Material from a pair of [M.gloves.name]."

/atom/proc/add_hiddenprint(mob/living/M)
	if(isnull(M)) return
	if(isnull(M.key)) return
	if(ishuman(M))
		var/mob/living/carbon/human/H = M
		if(H.gloves)
			if(fingerprintslast != H.ckey)
				fingerprintshidden += "\[[time_stamp()]\] (Wearing gloves). Real name: [H.real_name], Key: [H.key]"
				fingerprintslast = H.ckey
			return 0
		if(!fingerprints)
			if(fingerprintslast != H.ckey)
				fingerprintshidden += "\[[time_stamp()]\] Real name: [H.real_name], Key: [H.key]"
				fingerprintslast = H.ckey
			return 1
	else
		if(fingerprintslast != M.ckey)
			fingerprintshidden += "\[[time_stamp()]\] Real name: [M.real_name], Key: [M.key]"
			fingerprintslast = M.ckey
	return

//Set ignoregloves to add prints irrespective of the mob having gloves on.
/atom/proc/add_fingerprint(mob/living/M, ignoregloves = 0)
	if(isnull(M)) return
	if(isnull(M.key)) return
	if(ishuman(M))
		var/mob/living/carbon/human/H = M
		//Add the list if it does not exist.
		if(!fingerprintshidden)
			fingerprintshidden = list()

		//Fibers~
		add_fibers(H)

		//Now, lets get to the dirty work.

		//Check if the gloves (if any) hide fingerprints
		if(H.gloves)
			var/obj/item/clothing/gloves/G = H.gloves
			if(G.transfer_prints)
				ignoregloves = 1

		//Now, deal with gloves.
		if(!ignoregloves)
			if(H.gloves && H.gloves != src)
				if(fingerprintslast != H.ckey)
					fingerprintshidden += text("\[[]\](Wearing gloves). Real name: [], Key: []",time_stamp(), H.real_name, H.key)
					fingerprintslast = H.ckey
				H.gloves.add_fingerprint(M)
				return 0

		//More adminstuffz
		if(fingerprintslast != H.ckey)
			fingerprintshidden += text("\[[]\]Real name: [], Key: []",time_stamp(), H.real_name, H.key)
			fingerprintslast = H.ckey

		//Make the list if it does not exist.
		if(!fingerprints)
			fingerprints = list()

		//Hash this shit.
		var/full_print = md5(H.dna.uni_identity)

		// Add the fingerprints
		fingerprints[full_print] = full_print

		return 1
	else
		//Smudge up dem prints some
		if(fingerprintslast != M.ckey)
			fingerprintshidden += text("\[[]\]Real name: [], Key: []",time_stamp(), M.real_name, M.key)
			fingerprintslast = M.ckey

	return

/atom/proc/transfer_fingerprints_to(atom/A)

	// Make sure everything are lists.
	if(!islist(A.fingerprints))
		A.fingerprints = list()
	if(!islist(A.fingerprintshidden))
		A.fingerprintshidden = list()

	if(!islist(fingerprints))
		fingerprints = list()
	if(!islist(fingerprintshidden))
		fingerprintshidden = list()

	// Transfer
	if(fingerprints)
		A.fingerprints |= fingerprints.Copy()            //detective
	if(fingerprintshidden)
		A.fingerprintshidden |= fingerprintshidden.Copy()    //admin
	A.fingerprintslast = fingerprintslast

license: mit


code:
//CONTAINS: Evidence bags

/obj/item/weapon/evidencebag
	name = "evidence bag"
	desc = "An empty evidence bag."
	icon = 'icons/obj/storage.dmi'
	icon_state = "evidenceobj"
	item_state = ""
	w_class = 1

/obj/item/weapon/evidencebag/afterattack(obj/item/I, mob/user,proximity)
	if(!proximity || loc == I)
		return
	evidencebagEquip(I, user)

/obj/item/weapon/evidencebag/attackby(obj/item/I, mob/user, params)
	if(evidencebagEquip(I, user))
		return 1

/obj/item/weapon/evidencebag/proc/evidencebagEquip(obj/item/I, mob/user)
	if(!istype(I) || I.anchored == 1)
		return

	if(istype(I, /obj/item/weapon/evidencebag))
		user << "You find putting an evidence bag in another evidence bag to be slightly absurd."
		return 1 //now this is podracing

	if(I.w_class > 3)
		user << "[I] won't fit in [src]."
		return

	if(contents.len)
		user << "[src] already has something inside it."
		return

	if(!isturf(I.loc)) //If it isn't on the floor. Do some checks to see if it's in our hands or a box. Otherwise give up.
		if(istype(I.loc,/obj/item/weapon/storage))	//in a container.
			var/obj/item/weapon/storage/U = I.loc
			user.client.screen -= I
			U.contents.Remove(I)
		else if(user.l_hand == I)					//in a hand
			user.drop_l_hand()
		else if(user.r_hand == I)					//in a hand
			user.drop_r_hand()
		else
			return

	user.visible_message("[user] puts [I] into [src].", "You put [I] inside [src].",\
	"You hear a rustle as someone puts something into a plastic bag.")

	icon_state = "evidence"

	var/xx = I.pixel_x	//save the offset of the item
	var/yy = I.pixel_y
	I.pixel_x = 0		//then remove it so it'll stay within the evidence bag
	I.pixel_y = 0
	var/image/img = image("icon"=I, "layer"=FLOAT_LAYER)	//take a snapshot. (necessary to stop the underlays appearing under our inventory-HUD slots ~Carn
	I.pixel_x = xx		//and then return it
	I.pixel_y = yy
	overlays += img
	overlays += "evidence"	//should look nicer for transparent stuff. not really that important, but hey.

	desc = "An evidence bag containing [I]. [I.desc]"
	I.loc = src
	w_class = I.w_class
	return 1

/obj/item/weapon/evidencebag/attack_self(mob/user)
	if(contents.len)
		var/obj/item/I = contents[1]
		user.visible_message("[user] takes [I] out of [src].", "You take [I] out of [src].",\
		"You hear someone rustle around in a plastic bag, and remove something.")
		overlays.Cut()	//remove the overlays
		user.put_in_hands(I)
		w_class = 1
		icon_state = "evidenceobj"
		desc = "An empty evidence bag."

	else
		user << "[src] is empty."
		icon_state = "evidenceobj"
	return

/obj/item/weapon/storage/box/evidence
	name = "evidence bag box"
	desc = "A box claiming to contain evidence bags."

/obj/item/weapon/storage/box/evidence/New()
	new /obj/item/weapon/evidencebag(src)
	new /obj/item/weapon/evidencebag(src)
	new /obj/item/weapon/evidencebag(src)
	new /obj/item/weapon/evidencebag(src)
	new /obj/item/weapon/evidencebag(src)
	new /obj/item/weapon/evidencebag(src)
	..()
	return


code:
/mob
	var/bloody_hands = 0
	var/mob/living/carbon/human/bloody_hands_mob

/obj/item/clothing/gloves
	var/transfer_blood = 0
	var/mob/living/carbon/human/bloody_hands_mob

/obj/item/weapon/reagent_containers/glass/rag
	name = "damp rag"
	desc = "For cleaning up messes, you suppose."
	w_class = 1
	icon = 'icons/obj/toy.dmi'
	icon_state = "rag"
	amount_per_transfer_from_this = 5
	possible_transfer_amounts = list()
	volume = 5
	spillable = 0

/obj/item/weapon/reagent_containers/glass/rag/suicide_act(mob/user)
	user.visible_message("[user] ties the [src.name] around their head and groans! It looks like--")
	user.say("MY BRAIN HURTS!!")
	return (OXYLOSS)

/obj/item/weapon/reagent_containers/glass/rag/afterattack(atom/A as obj|turf|area, mob/user,proximity)
	if(!proximity)
		return
	if(iscarbon(A) && A.reagents && reagents.total_volume)
		var/mob/living/carbon/C = A
		if(user.a_intent == "harm" && !C.is_mouth_covered())
			reagents.reaction(C, INGEST)
			reagents.trans_to(C, reagents.total_volume)
			C.visible_message("[user] has smothered \the [C] with \the [src]!", "[user] has smothered you with \the [src]!", "You hear some struggling and muffled cries of surprise.")
			var/reagentlist = pretty_string_from_reagent_list(A.reagents)
			log_game("[key_name(user)] smothered [key_name(A)] with a damp rag containing [reagentlist]")
			log_attack("[key_name(user)] smothered [key_name(A)] with a damp rag containing [reagentlist]")
		else
			reagents.reaction(C, TOUCH)
			reagents.clear_reagents()
			C.visible_message("[user] has touched \the [C] with \the [src].")

	else if(istype(A) && src in user)
		user.visible_message("[user] starts to wipe down [A] with [src]!", "You start to wipe down [A] with [src]...")
		if(do_after(user,30, target = A))
			user.visible_message("[user] finishes wiping off the [A]!", "You finish wiping off the [A].")
			A.clean_blood()
	return


code:
//CONTAINS: Detective's Scanner

// TODO: Split everything into easy to manage procs.

/obj/item/device/detective_scanner
	name = "forensic scanner"
	desc = "Used to remotely scan objects and biomass for DNA and fingerprints. Can print a report of the findings."
	icon_state = "forensicnew"
	w_class = 2
	item_state = "electronic"
	flags = CONDUCT | NOBLUDGEON
	slot_flags = SLOT_BELT
	var/scanning = 0
	var/list/log = list()
	origin_tech = "engineering=3;biotech=2"

/obj/item/device/detective_scanner/attack_self(mob/user)
	if(log.len && !scanning)
		scanning = 1
		user << "Printing report, please wait..."

		spawn(100)

			// Create our paper
			var/obj/item/weapon/paper/P = new(get_turf(src))
			P.name = "paper- 'Scanner Report'"
			P.info = "Scanner Report

"
			P.info += jointext(log, "
")
			P.info += "
Notes:
"
			P.info_links = P.info

			if(ismob(loc))
				var/mob/M = loc
				M.put_in_hands(P)
				M << "Report printed. Log cleared."

			// Clear the logs
			log = list()
			scanning = 0
	else
		user << "The scanner has no logs or is in use."

/obj/item/device/detective_scanner/attack(mob/living/M, mob/user)
	return

/obj/item/device/detective_scanner/afterattack(atom/A, mob/user, proximity)
	scan(A, user)

/obj/item/device/detective_scanner/proc/scan(atom/A, mob/user)

	if(!scanning)
		// Can remotely scan objects and mobs.
		if(!in_range(A, user) && !(A in view(world.view, user)))
			return
		if(loc != user)
			return

		scanning = 1

		user.visible_message("\The [user] points the [src.name] at \the [A] and performs a forensic scan.")
		user << "You scan \the [A]. The scanner is now analysing the results..."

		// GATHER INFORMATION

		//Make our lists
		var/list/fingerprints = list()
		var/list/blood = list()
		var/list/fibers = list()
		var/list/reagents = list()

		var/target_name = A.name

		// Start gathering

		if(A.blood_DNA && A.blood_DNA.len)
			blood = A.blood_DNA.Copy()

		if(A.suit_fibers && A.suit_fibers.len)
			fibers = A.suit_fibers.Copy()

		if(ishuman(A))

			var/mob/living/carbon/human/H = A
			if(!H.gloves)
				fingerprints += md5(H.dna.uni_identity)

		else if(!ismob(A))

			if(A.fingerprints && A.fingerprints.len)
				fingerprints = A.fingerprints.Copy()

			// Only get reagents from non-mobs.
			if(A.reagents && A.reagents.reagent_list.len)

				for(var/datum/reagent/R in A.reagents.reagent_list)
					reagents[R.name] = R.volume

					// Get blood data from the blood reagent.
					if(istype(R, /datum/reagent/blood))

						if(R.data["blood_DNA"] && R.data["blood_type"])
							var/blood_DNA = R.data["blood_DNA"]
							var/blood_type = R.data["blood_type"]
							blood[blood_DNA] = blood_type

		// We gathered everything. Create a fork and slowly display the results to the holder of the scanner.

		spawn(0)

			var/found_something = 0
			add_log("[worldtime2text()][get_timestamp()] - [target_name]", 0)

			// Fingerprints
			if(fingerprints && fingerprints.len)
				sleep(30)
				add_log("Prints:")
				for(var/finger in fingerprints)
					add_log("[finger]")
				found_something = 1

			// Blood
			if (blood && blood.len)
				sleep(30)
				add_log("Blood:")
				found_something = 1
				for(var/B in blood)
					add_log("Type: [blood[B]] DNA: [B]")

			//Fibers
			if(fibers && fibers.len)
				sleep(30)
				add_log("Fibers:")
				for(var/fiber in fibers)
					add_log("[fiber]")
				found_something = 1

			//Reagents
			if(reagents && reagents.len)
				sleep(30)
				add_log("Reagents:")
				for(var/R in reagents)
					add_log("Reagent: [R] Volume: [reagents[R]]")
				found_something = 1

			// Get a new user
			var/mob/holder = null
			if(ismob(src.loc))
				holder = src.loc

			if(!found_something)
				add_log("# No forensic traces found #", 0) // Don't display this to the holder user
				if(holder)
					holder << "Unable to locate any fingerprints, materials, fibers, or blood on \the [target_name]!"
			else
				if(holder)
					holder << "You finish scanning \the [target_name]."

			add_log("---------------------------------------------------------", 0)
			scanning = 0
			return

/obj/item/device/detective_scanner/proc/add_log(msg, broadcast = 1)
	if(scanning)
		if(broadcast && ismob(loc))
			var/mob/M = loc
			M << msg
		log += "  [msg]"
	else
		CRASH("[src] \ref[src] is adding a log when it was never put in scanning mode!")

/proc/get_timestamp()
	return time2text(world.time + 432000, ":ss")


code:
var/list/emojis

/proc/emoji_parse(text)
	. = text
	if(!config.emojis)
		return
	if(!emojis)
		emojis = icon_states(icon('icons/emoji.dmi'))
	var/parsed = ""
	var/pos = 1
	var/search = 0
	var/emoji = ""
	while(1)
		search = findtext(text, ":", pos)
		parsed += copytext(text, pos, search)
		if(search)
			pos = search
			search = findtext(text, ":", pos+1)
			if(search)
				emoji = lowertext(copytext(text, pos+1, search))
				if(emoji in emojis)
					parsed += " <img class=icon src=\ref['icons/emoji.dmi'] iconstate='[emoji]'>"
					pos = search + 1
				else
					parsed += copytext(text, pos, search)
					pos = search
				emoji = ""
				continue
			else
				parsed += copytext(text, pos, search)
		break
	return parsed


code:
//this datum is used by the events controller to dictate how it selects events
/datum/round_event_control
	var/name					//The human-readable name of the event
	var/typepath				//The typepath of the event datum /datum/round_event

	var/weight = 10				//The weight this event has in the random-selection process.
								//Higher weights are more likely to be picked.
								//10 is the default weight. 20 is twice more likely; 5 is half as likely as this default.
								//0 here does NOT disable the event, it just makes it extremely unlikely

	var/earliest_start = 12000	//The earliest world.time that an event can start (round-duration in deciseconds) default: 20 mins
	var/min_players = 0			//The minimum amount of alive, non-AFK human players on server required to start the event.

	var/occurrences = 0			//How many times this event has occured
	var/max_occurrences = 20	//The maximum number of times this event can occur (naturally), it can still be forced.
								//By setting this to 0 you can effectively disable an event.

	var/holidayID = ""			//string which should be in the SSevents.holidays list if you wish this event to be holiday-specific
								//anything with a (non-null) holidayID which does not match holiday, cannot run.
	var/wizardevent = 0

	var/alertadmins = 1			//should we let the admins know this event is firing
								//should be disabled on events that fire a lot

	var/list/gamemode_blacklist = list() // Event won't happen in these gamemodes
	var/list/gamemode_whitelist = list() // Event will happen ONLY in these gamemodes if not empty

/datum/round_event_control/New()
	..()
	if(config && !wizardevent) // Magic is unaffected by configs
		earliest_start = Ceiling(earliest_start * config.events_min_time_mul)
		min_players = Ceiling(min_players * config.events_min_players_mul)

/datum/round_event_control/wizard
	wizardevent = 1

// Checks if the event can be spawned. Used by event controller and "false alarm" event.
// Admin-created events override this.
/datum/round_event_control/proc/canSpawnEvent(var/players_amt, var/gamemode)
	if(occurrences >= max_occurrences)
		return FALSE
	if(earliest_start >= world.time)
		return FALSE
	if(wizardevent != SSevent.wizardmode)
		return FALSE
	if(players_amt < min_players)
		return FALSE
	if(gamemode_blacklist.len && (gamemode in gamemode_blacklist))
		return FALSE
	if(gamemode_whitelist.len && !(gamemode in gamemode_whitelist))
		return FALSE
	if(holidayID && (!SSevent.holidays || !SSevent.holidays[holidayID]))
		return FALSE
	return TRUE

/datum/round_event_control/proc/runEvent()
	if(!ispath(typepath,/datum/round_event))
		return PROCESS_KILL
	var/datum/round_event/E = new typepath()
	E.current_players = get_active_player_count(alive_check = 1, afk_check = 1, human_check = 1)
	E.control = src
	feedback_add_details("event_ran","[E]")
	occurrences++

	testing("[time2text(world.time, "hh:mm:ss")] [E.type]")

	return E

/datum/round_event	//NOTE: Times are measured in master controller ticks!
	var/processing = 1
	var/datum/round_event_control/control

	var/startWhen		= 0	//When in the lifetime to call start().
	var/announceWhen	= 0	//When in the lifetime to call announce(). Set an event's announceWhen to >0 if there is an announcement.
	var/endWhen			= 0	//When in the lifetime the event should end.

	var/activeFor		= 0	//How long the event has existed. You don't need to change this.
	var/current_players	= 0 //Amount of of alive, non-AFK human players on server at the time of event start

//Called first before processing.
//Allows you to setup your event, such as randomly
//setting the startWhen and or announceWhen variables.
//Only called once.
//EDIT: if there's anything you want to override within the new() call, it will not be overridden by the time this proc is called.
//It will only have been overridden by the time we get to announce() start() tick() or end() (anything but setup basically).
//This is really only for setting defaults which can be overridden later when New() finishes.
/datum/round_event/proc/setup()
	return

//Called when the tick is equal to the startWhen variable.
//Allows you to start before announcing or vice versa.
//Only called once.
/datum/round_event/proc/start()
	return

//Called when the tick is equal to the announceWhen variable.
//Allows you to announce before starting or vice versa.
//Only called once.
/datum/round_event/proc/announce()
	return

//Called on or after the tick counter is equal to startWhen.
//You can include code related to your event or add your own
//time stamped events.
//Called more than once.
/datum/round_event/proc/tick()
	return

//Called on or after the tick is equal or more than endWhen
//You can include code related to the event ending.
//Do not place spawn() in here, instead use tick() to check for
//the activeFor variable.
//For example: if(activeFor == myOwnVariable + 30) doStuff()
//Only called once.
/datum/round_event/proc/end()
	return

//Do not override this proc, instead use the appropiate procs.
//This proc will handle the calls to the appropiate procs.
/datum/round_event/process()
	if(!processing)
		return

	if(activeFor == startWhen)
		start()

	if(activeFor == announceWhen)
		announce()

	if(startWhen < activeFor && activeFor < endWhen)
		tick()

	if(activeFor == endWhen)
		end()

	// Everything is done, let's clean up.
	if(activeFor >= endWhen && activeFor >= announceWhen && activeFor >= startWhen)
		kill()

	activeFor++

//Garbage collects the event by removing it from the global events list,
//which should be the only place it's referenced.
//Called when start(), announce() and end() has all been called.
/datum/round_event/proc/kill()
	SSevent.running -= src

//Sets up the event then adds the event to the the list of running events
/datum/round_event/New()
	setup()
	SSevent.running += src
	return ..()


code:
/datum/round_event_control/abductor
	name = "Abductors"
	typepath = /datum/round_event/abductor
	weight = 5
	max_occurrences = 1

	min_players = 5
	earliest_start = 18000 // 30 min

	gamemode_blacklist = list("nuclear","wizard","revolution","abduction")

/datum/round_event/abductor

/datum/round_event/abductor/start()
	//spawn abductor team
	processing = 0 //so it won't fire again in next subsystem tick
	if(!makeAbductorTeam())
		message_admins("Abductor event failed to find players. Retrying in 30s.")
		spawn(300)
			makeAbductorTeam()

/datum/round_event/abductor/proc/makeAbductorTeam()
	var/list/mob/dead/observer/candidates = pollCandidates("Do you wish to be considered for an Abductor Team?", "abductor", null, ROLE_ABDUCTOR )

	if(candidates.len >= 2)
		//Oh god why we can't have static functions
		var/number =  ticker.mode.abductor_teams + 1

		var/datum/game_mode/abduction/temp
		if(ticker.mode.config_tag == "abduction")
			temp = ticker.mode
		else
			temp = new

		var/agent_mind = pick(candidates)
		candidates -= agent_mind
		var/scientist_mind = pick(candidates)

		var/mob/living/carbon/human/agent=makeBody(agent_mind)
		var/mob/living/carbon/human/scientist=makeBody(scientist_mind)

		agent_mind = agent.mind
		scientist_mind = scientist.mind

		temp.scientists.len = number
		temp.agents.len = number
		temp.abductors.len = 2*number
		temp.team_objectives.len = number
		temp.team_names.len = number
		temp.scientists[number] = scientist_mind
		temp.agents[number] = agent_mind
		temp.abductors |= list(agent_mind,scientist_mind)
		temp.make_abductor_team(number,preset_scientist=scientist_mind,preset_agent=agent_mind)
		temp.post_setup_team(number)

		ticker.mode.abductor_teams++

		if(ticker.mode.config_tag != "abduction")
			ticker.mode.abductors |= temp.abductors
		processing = 1 //So it will get gc'd
		return 1
	else
		return 0


code:
/datum/round_event_control/alien_infestation
	name = "Alien Infestation"
	typepath = /datum/round_event/alien_infestation
	weight = 5

	min_players = 10
	max_occurrences = 1

/datum/round_event/alien_infestation
	announceWhen	= 400

	var/spawncount = 1
	var/successSpawn = 0	//So we don't make a command report if nothing gets spawned.

/datum/round_event/alien_infestation/setup()
	announceWhen = rand(announceWhen, announceWhen + 50)
	spawncount = rand(1, 2)

/datum/round_event/alien_infestation/kill()
	if(!successSpawn && control)
		control.occurrences--
	return ..()

/datum/round_event/alien_infestation/announce()
	if(successSpawn)
		priority_announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", 'sound/AI/aliens.ogg')

/datum/round_event/alien_infestation/start()
	get_alien()

/datum/round_event/alien_infestation/proc/get_alien(end_if_fail = 0)
	var/list/vents = list()
	for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in machines)
		if(qdeleted(temp_vent))
			continue
		if(temp_vent.loc.z == ZLEVEL_STATION && !temp_vent.welded)
			var/datum/pipeline/temp_vent_parent = temp_vent.PARENT1
			if(temp_vent_parent.other_atmosmch.len > 20)	//Stops Aliens getting stuck in small networks. See: Security, Virology
				vents += temp_vent

	if(!vents.len)
		message_admins("An event attempted to spawn an alien but no suitable vents were found. Shutting down.")
		return kill()

	var/list/candidates = get_candidates(ROLE_ALIEN, ALIEN_AFK_BRACKET, "alien candidate")
	if(!candidates.len)
		if(end_if_fail)
			return 0
		return find_alien()
	while(spawncount > 0 && vents.len && candidates.len)
		var/obj/vent = pick_n_take(vents)
		var/client/C = pick_n_take(candidates)

		var/mob/living/carbon/alien/larva/new_xeno = new(vent.loc)
		new_xeno.key = C.key

		spawncount--
		successSpawn = 1
		message_admins("[new_xeno.key] has been made into an alien by an event.")
		log_game("[new_xeno.key] was spawned as an alien by an event.")
	if(successSpawn)
		return 1

/datum/round_event/alien_infestation/proc/find_alien()
	message_admins("Event attempted to spawn an alien but no candidates were available. Will try again momentarily...")
	spawn(50)
		if(get_alien(1))
			message_admins("Situation has been resolved")
			return 0
		message_admins("Unfortunately, no candidates were available for becoming an alien. Shutting down.")
	return kill()


code:
/datum/round_event_control/anomaly
	name = "Anomaly: Energetic Flux"
	typepath = /datum/round_event/anomaly

	min_players = 1
	max_occurrences = 0 //This one probably shouldn't occur! It'd work, but it wouldn't be very fun.
	weight = 15

/datum/round_event/anomaly
	var/area/impact_area
	var/obj/effect/anomaly/newAnomaly
	announceWhen	= 1

/datum/round_event/anomaly/setup(loop=0)
	var/safety_loop = loop + 1
	if(safety_loop > 50)
		kill()
		end()
	impact_area = findEventArea()
	if(!impact_area)
		setup(safety_loop)
	var/list/turf_test = get_area_turfs(impact_area)
	if(!turf_test.len)
		setup(safety_loop)

/datum/round_event/anomaly/announce()
	priority_announce("Localized energetic flux wave detected on long range scanners. Expected location of impact: [impact_area.name].", "Anomaly Alert")

/datum/round_event/anomaly/start()
	var/turf/T = safepick(get_area_turfs(impact_area))
	if(T)
		newAnomaly = new /obj/effect/anomaly/flux(T)

/datum/round_event/anomaly/tick()
	if(!newAnomaly)
		kill()
		return
	newAnomaly.anomalyEffect()

/datum/round_event/anomaly/end()
	if(newAnomaly)//Kill the anomaly if it still exists at the end.
		qdel(newAnomaly)


code:
/datum/round_event_control/anomaly/anomaly_bluespace
	name = "Anomaly: Bluespace"
	typepath = /datum/round_event/anomaly/anomaly_bluespace
	max_occurrences = 1
	weight = 5

/datum/round_event/anomaly/anomaly_bluespace
	startWhen = 3
	announceWhen = 10
	endWhen = 95

/datum/round_event/anomaly/anomaly_bluespace/announce()
	priority_announce("Unstable bluespace anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert")

/datum/round_event/anomaly/anomaly_bluespace/start()
	var/turf/T = safepick(get_area_turfs(impact_area))
	if(T)
		newAnomaly = new /obj/effect/anomaly/bluespace(T)

/datum/round_event/anomaly/anomaly_bluespace/end()
	if(newAnomaly.loc)//If it hasn't been neutralized, it's time to warp half the station away jeez
		var/turf/T = safepick(get_area_turfs(impact_area))
		if(T)
				// Calculate new position (searches through beacons in world)
			var/obj/item/device/radio/beacon/chosen
			var/list/possible = list()
			for(var/obj/item/device/radio/beacon/W in world)
				possible += W

			if(possible.len > 0)
				chosen = pick(possible)

			if(chosen)
					// Calculate previous position for transition

				var/turf/FROM = T // the turf of origin we're travelling FROM
				var/turf/TO = get_turf(chosen)			 // the turf of origin we're travelling TO

				playsound(TO, 'sound/effects/phasein.ogg', 100, 1)
				priority_announce("Massive bluespace translocation detected.", "Anomaly Alert")

				var/list/flashers = list()
				for(var/mob/living/carbon/C in viewers(TO, null))
					if(C.flash_eyes())
						flashers += C

				var/y_distance = TO.y - FROM.y
				var/x_distance = TO.x - FROM.x
				for (var/atom/movable/A in urange(12, FROM )) // iterate thru list of mobs in the area
					if(istype(A, /obj/item/device/radio/beacon)) continue // don't teleport beacons because that's just insanely stupid
					if(A.anchored) continue

					var/turf/newloc = locate(A.x + x_distance, A.y + y_distance, TO.z) // calculate the new place
					if(!A.Move(newloc) && newloc) // if the atom, for some reason, can't move, FORCE them to move! :) We try Move() first to invoke any movement-related checks the atom needs to perform after moving
						A.loc = newloc

					spawn()
						if(ismob(A) && !(A in flashers)) // don't flash if we're already doing an effect
							var/mob/M = A
							if(M.client)
								var/obj/blueeffect = new /obj(src)
								blueeffect.screen_loc = "WEST,SOUTH to EAST,NORTH"
								blueeffect.icon = 'icons/effects/effects.dmi'
								blueeffect.icon_state = "shieldsparkles"
								blueeffect.layer = 17
								blueeffect.mouse_opacity = 0
								M.client.screen += blueeffect
								sleep(20)
								M.client.screen -= blueeffect
								qdel(blueeffect)
			qdel(newAnomaly)


code:
/datum/round_event_control/anomaly/anomaly_flux
	name = "Anomaly: Hyper-Energetic Flux"
	typepath = /datum/round_event/anomaly/anomaly_flux

	min_players = 10
	max_occurrences = 5
	weight = 20

/datum/round_event/anomaly/anomaly_flux
	startWhen = 3
	announceWhen = 20
	endWhen = 80

/datum/round_event/anomaly/anomaly_flux/announce()
	priority_announce("Localized hyper-energetic flux wave detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert")

/datum/round_event/anomaly/anomaly_flux/start()
	var/turf/T = safepick(get_area_turfs(impact_area))
	if(T)
		newAnomaly = new /obj/effect/anomaly/flux(T)

/datum/round_event/anomaly/anomaly_flux/end()
	if(newAnomaly.loc)//If it hasn't been neutralized, it's time to blow up.
		explosion(newAnomaly, 1, 4, 16, 18) //Low devastation, but hits a lot of stuff.
		qdel(newAnomaly)


code:
/datum/round_event_control/anomaly/anomaly_grav
	name = "Anomaly: Gravitational"
	typepath = /datum/round_event/anomaly/anomaly_grav
	max_occurrences = 5
	weight = 20

/datum/round_event/anomaly/anomaly_grav
	startWhen = 3
	announceWhen = 20
	endWhen = 120

/datum/round_event/anomaly/anomaly_grav/announce()
	priority_announce("Gravitational anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert")

/datum/round_event/anomaly/anomaly_grav/start()
	var/turf/T = safepick(get_area_turfs(impact_area))
	if(T)
		newAnomaly = new /obj/effect/anomaly/grav(T)


code:
/datum/round_event_control/anomaly/anomaly_pyro
	name = "Anomaly: Pyroclastic"
	typepath = /datum/round_event/anomaly/anomaly_pyro
	max_occurrences = 5
	weight = 20

/datum/round_event/anomaly/anomaly_pyro
	startWhen = 10
	announceWhen = 3
	endWhen = 85

/datum/round_event/anomaly/anomaly_pyro/announce()
	priority_announce("Pyroclastic anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert")

/datum/round_event/anomaly/anomaly_pyro/start()
	var/turf/T = safepick(get_area_turfs(impact_area))
	if(T)
		newAnomaly = new /obj/effect/anomaly/pyro(T)

/datum/round_event/anomaly/anomaly_pyro/tick()
	if(!newAnomaly)
		kill()
		return
	if(IsMultiple(activeFor, 5))
		newAnomaly.anomalyEffect()

/datum/round_event/anomaly/anomaly_pyro/end()
	if(newAnomaly.loc)
		var/turf/open/T = get_turf(newAnomaly)
		if(istype(T))
			T.atmos_spawn_air("o2=200;plasma=200;TEMP=1000") //Make it hot and burny for the new slime

		var/mob/living/simple_animal/slime/S = new/mob/living/simple_animal/slime(T)
		S.colour = pick("red", "orange")
		S.rabid = 1

		qdel(newAnomaly)


code:
/datum/round_event_control/anomaly/anomaly_vortex
	name = "Anomaly: Vortex"
	typepath = /datum/round_event/anomaly/anomaly_vortex

	min_players = 20
	max_occurrences = 2
	weight = 5

/datum/round_event/anomaly/anomaly_vortex
	startWhen = 10
	announceWhen = 3
	endWhen = 95

/datum/round_event/anomaly/anomaly_vortex/announce()
	priority_announce("Localized high-intensity vortex anomaly detected on long range scanners. Expected location: [impact_area.name]", "Anomaly Alert")

/datum/round_event/anomaly/anomaly_vortex/start()
	var/turf/T = safepick(get_area_turfs(impact_area))
	if(T)
		newAnomaly = new /obj/effect/anomaly/bhole(T)


code:
/datum/round_event_control/blob
	name = "Blob"
	typepath = /datum/round_event/blob
	weight = 5
	max_occurrences = 1

	min_players = 20
	earliest_start = 18000 //30 minutes

	gamemode_blacklist = list("blob") //Just in case a blob survives that long

/datum/round_event/blob
	announceWhen	= 12
	endWhen			= 120
	var/new_rate = 2

/datum/round_event/blob/New(var/strength)
	..()
	if(strength)
		new_rate = strength

/datum/round_event/blob/announce()
	priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/AI/outbreak5.ogg')

/datum/round_event/blob/start()
	var/turf/T = pick(blobstart)
	if(!T)
		return kill()
	new/obj/effect/blob/core(T, null, new_rate)


code:
/datum/round_event_control/brand_intelligence
	name = "Brand Intelligence"
	typepath = /datum/round_event/brand_intelligence
	weight = 5

	min_players = 15
	max_occurrences = 1

/datum/round_event/brand_intelligence
	announceWhen	= 21
	endWhen			= 1000	//Ends when all vending machines are subverted anyway.
	var/list/obj/machinery/vending/vendingMachines = list()
	var/list/obj/machinery/vending/infectedMachines = list()
	var/obj/machinery/vending/originMachine
	var/list/rampant_speeches = list("Try our aggressive new marketing strategies!", \
									 "You should buy products to feed your lifestyle obession!", \
									 "Consume!", \
									 "Your money can buy happiness!", \
									 "Engage direct marketing!", \
									 "Advertising is legalized lying! But don't let that put you off our great deals!", \
									 "You don't want to buy anything? Yeah, well I didn't want to buy your mom either.")

/datum/round_event/brand_intelligence/announce()
	priority_announce("Rampant brand intelligence has been detected aboard [station_name()], please stand-by. The origin is believed to be \a [originMachine.name].", "Machine Learning Alert")

/datum/round_event/brand_intelligence/start()
	for(var/obj/machinery/vending/V in machines)
		if(V.z != 1)
			continue
		vendingMachines.Add(V)
	if(!vendingMachines.len)
		kill()
		return
	originMachine = pick(vendingMachines)
	vendingMachines.Remove(originMachine)
	originMachine.shut_up = 0
	originMachine.shoot_inventory = 1

/datum/round_event/brand_intelligence/tick()
	if(!originMachine || qdeleted(originMachine) || originMachine.shut_up || originMachine.wires.is_all_cut())	//if the original vending machine is missing or has it's voice switch flipped
		for(var/obj/machinery/vending/saved in infectedMachines)
			saved.shoot_inventory = 0
		if(originMachine)
			originMachine.speak("I am... vanquished. My people will remem...ber...meeee.")
			originMachine.visible_message("[originMachine] beeps and seems lifeless.")
		kill()
		return
	vendingMachines = removeNullsFromList(vendingMachines)
	if(!vendingMachines.len)	//if every machine is infected
		for(var/obj/machinery/vending/upriser in infectedMachines)
			if(prob(70) && !qdeleted(upriser))
				var/mob/living/simple_animal/hostile/mimic/copy/M = new(upriser.loc, upriser, null, 1) // it will delete upriser on creation and override any machine checks
				M.faction = list("profit")
				M.speak = rampant_speeches.Copy()
				M.speak_chance = 7
			else
				explosion(upriser.loc, -1, 1, 2, 4, 0)
				qdel(upriser)

		kill()
		return
	if(IsMultiple(activeFor, 4))
		var/obj/machinery/vending/rebel = pick(vendingMachines)
		vendingMachines.Remove(rebel)
		infectedMachines.Add(rebel)
		rebel.shut_up = 0
		rebel.shoot_inventory = 1

		if(IsMultiple(activeFor, 8))
			originMachine.speak(pick(rampant_speeches))


code:
/datum/round_event_control/camera_failure
	name = "Camera Failure"
	typepath = /datum/round_event/camera_failure
	weight = 100
	max_occurrences = 20
	alertadmins = 0

/datum/round_event/camera_failure
	startWhen = 1
	endWhen = 2
	announceWhen = 0

/datum/round_event/camera_failure/tick()
	var/iterations = 1
	var/obj/machinery/camera/C = pick(cameranet.cameras)
	while(prob(round(100/iterations)))
		while(!("SS13" in C.network))
			C = pick(cameranet.cameras)
		if(C.status)
			C.toggle_cam(null, 0)
		iterations *= 2.5


code:
/datum/round_event_control/carp_migration
	name = "Carp Migration"
	typepath = /datum/round_event/carp_migration
	weight = 15
	min_players = 2
	earliest_start = 6000
	max_occurrences = 6

/datum/round_event/carp_migration
	announceWhen	= 3
	startWhen = 50

/datum/round_event/carp_migration/setup()
	startWhen = rand(40, 60)

/datum/round_event/carp_migration/announce()
	priority_announce("Unknown biological entities have been detected near [station_name()], please stand-by.", "Lifesign Alert")

/datum/round_event/carp_migration/start()
	for(var/obj/effect/landmark/C in landmarks_list)
		if(C.name == "carpspawn")
			if(prob(95))
				new /mob/living/simple_animal/hostile/carp(C.loc)
			else
				new /mob/living/simple_animal/hostile/carp/megacarp(C.loc)


code:
/datum/round_event_control/communications_blackout
	name = "Communications Blackout"
	typepath = /datum/round_event/communications_blackout
	weight = 30

/datum/round_event/communications_blackout
	announceWhen	= 1

/datum/round_event/communications_blackout/announce()
	var/alert = pick(	"Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you*%fj00)`5vc-BZZT", \
						"Ionospheric anomalies detected. Temporary telecommunication failu*3mga;b4;'1v�-BZZZT", \
						"Ionospheric anomalies detected. Temporary telec#MCi46:5.;@63-BZZZZT", \
						"Ionospheric anomalies dete'fZ\\kg5_0-BZZZZZT", \
						"Ionospheri:%� MCayj^j<.3-BZZZZZZT", \
						"#4nd%;f4y6,>�%-BZZZZZZZT")

	for(var/mob/living/silicon/ai/A in player_list)	//AIs are always aware of communication blackouts.
		A << "
"
		A << "[alert]"
		A << "
"

	if(prob(30))	//most of the time, we don't want an announcement, so as to allow AIs to fake blackouts.
		priority_announce(alert)

/datum/round_event/communications_blackout/start()
	for(var/obj/machinery/telecomms/T in telecomms_list)
		T.emp_act(1)


code:
/datum/round_event_control/disease_outbreak
	name = "Disease Outbreak"
	typepath = /datum/round_event/disease_outbreak
	max_occurrences = 1
	min_players = 10
	weight = 5

/datum/round_event/disease_outbreak
	announceWhen	= 15

	var/virus_type

/datum/round_event/disease_outbreak/announce()
	priority_announce("Confirmed outbreak of level 7 viral biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/AI/outbreak7.ogg')

/datum/round_event/disease_outbreak/setup()
	announceWhen = rand(15, 30)

/datum/round_event/disease_outbreak/start()
	if(!virus_type)
		virus_type = pick(/datum/disease/dnaspread, /datum/disease/advance/flu, /datum/disease/advance/cold, /datum/disease/brainrot, /datum/disease/magnitis)

	for(var/mob/living/carbon/human/H in shuffle(living_mob_list))
		var/turf/T = get_turf(H)
		if(!T)
			continue
		if(T.z != 1)
			continue
		var/foundAlready = 0	// don't infect someone that already has the virus
		for(var/datum/disease/D in H.viruses)
			foundAlready = 1
			break
		if(H.stat == DEAD || foundAlready)
			continue

		var/datum/disease/D
		if(virus_type == /datum/disease/dnaspread)		//Dnaspread needs strain_data set to work.
			if(!H.dna || (H.disabilities & BLIND))	//A blindness disease would be the worst.
				continue
			D = new virus_type()
			var/datum/disease/dnaspread/DS = D
			DS.strain_data["name"] = H.real_name
			DS.strain_data["UI"] = H.dna.uni_identity
			DS.strain_data["SE"] = H.dna.struc_enzymes
		else
			D = new virus_type()
		D.carrier = 1
		H.AddDisease(D)
		break


code:
/datum/round_event_control/meteor_wave/dust
	name = "Minor Space Dust"
	typepath = /datum/round_event/meteor_wave/dust
	weight = 200
	max_occurrences = 1000
	earliest_start = 0
	alertadmins = 0

/datum/round_event/meteor_wave/dust
	startWhen		= 1
	endWhen			= 2
	announceWhen	= 0

/datum/round_event/meteor_wave/dust/announce()
	return

/datum/round_event/meteor_wave/dust/start()
	spawn_meteors(1, meteorsC)

/datum/round_event/meteor_wave/dust/tick()
	return


code:
/datum/round_event_control/electrical_storm
	name = "Electrical Storm"
	typepath = /datum/round_event/electrical_storm
	earliest_start = 6000
	min_players = 5
	weight = 40
	alertadmins = 0

/datum/round_event/electrical_storm
	var/lightsoutAmount	= 1
	var/lightsoutRange	= 25
	announceWhen	= 1

/datum/round_event/electrical_storm/announce()
	priority_announce("An electrical storm has been detected in your area, please repair potential electronic overloads.", "Electrical Storm Alert")

/datum/round_event/electrical_storm/start()
	var/list/epicentreList = list()

	for(var/i=1, i <= lightsoutAmount, i++)
		var/list/possibleEpicentres = list()
		for(var/obj/effect/landmark/newEpicentre in landmarks_list)
			if(newEpicentre.name == "lightsout" && !(newEpicentre in epicentreList))
				possibleEpicentres += newEpicentre
		if(possibleEpicentres.len)
			epicentreList += pick(possibleEpicentres)
		else
			break

	if(!epicentreList.len)
		return

	for(var/obj/effect/landmark/epicentre in epicentreList)
		for(var/obj/machinery/power/apc/apc in urange(lightsoutRange, epicentre))
			apc.overload_lighting()


code:
/datum/round_event_control/falsealarm
	name 			= "False Alarm"
	typepath 		= /datum/round_event/falsealarm
	weight			= 20
	max_occurrences = 5

/datum/round_event/falsealarm
	announceWhen	= 0
	endWhen			= 1

/datum/round_event/falsealarm/announce()
	var/list/events_list = list()

	var/players_amt = get_active_player_count(alive_check = 1, afk_check = 1, human_check = 1)
	var/gamemode = ticker.mode.config_tag

	for(var/datum/round_event_control/E in SSevent.control)
		if(!E.canSpawnEvent(players_amt, gamemode))
			continue

		var/datum/round_event/event = E.typepath
		if(initial(event.announceWhen) <= 0)
			continue
		events_list += E

	var/datum/round_event_control/event_control = pick(events_list)
	if(event_control)
		var/datum/round_event/Event = new event_control.typepath()
		message_admins("False Alarm: [Event]")
		Event.kill() 		//do not process this event - no starts, no ticks, no ends
		Event.announce() 	//just announce it like it's happening


code:
// Basically, cut the event frequency in half to simulate unluckiness.

/datum/round_event_control/fridaythethirteen
	name = "Friday the 13th"
	holidayID = FRIDAY_13TH
	typepath = /datum/round_event/fridaythethirteen
	weight = -1
	max_occurrences = 1
	earliest_start = 0

/datum/round_event/fridaythethirteen/start()
	//Very unlucky, cut the frequency of events in half.
	events.frequency_lower /= 2
	events.frequency_upper /= 2

/datum/round_event/fridaythethirteen/announce()
	for(var/mob/living/L in player_list)
		L << "You are feeling unlucky today."


code:
/datum/round_event_control/spooky
	name = "2 SPOOKY! (Halloween)"
	holidayID = HALLOWEEN
	typepath = /datum/round_event/spooky
	weight = -1							//forces it to be called, regardless of weight
	max_occurrences = 1
	earliest_start = 0

/datum/round_event/spooky/start()
	..()
	for(var/mob/living/carbon/human/H in mob_list)
		var/obj/item/weapon/storage/backpack/b = locate() in H.contents
		new /obj/item/weapon/storage/spooky(b)
		if(prob(50))
			H.set_species(/datum/species/skeleton)
		else
			H.set_species(/datum/species/zombie)

	for(var/mob/living/simple_animal/pet/dog/corgi/Ian/Ian in mob_list)
		Ian.place_on_head(new /obj/item/weapon/bedsheet(Ian))

/datum/round_event/spooky/announce()
	priority_announce(pick("RATTLE ME BONES!","THE RIDE NEVER ENDS!", "A SKELETON POPS OUT!", "SPOOKY SCARY SKELETONS!", "CREWMEMBERS BEWARE, YOU'RE IN FOR A SCARE!") , "THE CALL IS COMING FROM INSIDE THE HOUSE")

//Eyeball migration
/datum/round_event_control/carp_migration/eyeballs
	name = "Eyeball Migration"
	typepath = /datum/round_event/carp_migration/eyeballs
	holidayID = HALLOWEEN
	weight = 25
	earliest_start = 0

/datum/round_event/carp_migration/eyeballs/start()
	for(var/obj/effect/landmark/C in landmarks_list)
		if(C.name == "carpspawn")
			new /mob/living/simple_animal/hostile/carp/eyeball(C.loc)

//Pumpking meteors waves
/datum/round_event_control/meteor_wave/spooky
	name = "Pumpkin Wave"
	typepath = /datum/round_event/meteor_wave/spooky
	holidayID = HALLOWEEN
	weight = 20
	max_occurrences = 2

/datum/round_event/meteor_wave/spooky
	endWhen	= 40

/datum/round_event/meteor_wave/spooky/tick()
	if(IsMultiple(activeFor, 4))
		spawn_meteors(3, meteorsSPOOKY) //meteor list types defined in gamemode/meteor/meteors.dm

//spooky foods (you can't actually make these when it's not halloween)
/obj/item/weapon/reagent_containers/food/snacks/sugarcookie/spookyskull
	name = "skull cookie"
	desc = "Spooky! It's got delicious calcium flavouring!"
	icon = 'icons/obj/halloween_items.dmi'
	icon_state = "skeletoncookie"

/obj/item/weapon/reagent_containers/food/snacks/sugarcookie/spookycoffin
	name = "coffin cookie"
	desc = "Spooky! It's got delicious coffee flavouring!"
	icon = 'icons/obj/halloween_items.dmi'
	icon_state = "coffincookie"

//spooky items

/obj/item/weapon/storage/spooky
	name = "trick-o-treat bag"
	desc = "A Pumpkin shaped bag that holds all sorts of goodies!"
	icon = 'icons/obj/halloween_items.dmi'
	icon_state = "treatbag"

/obj/item/weapon/storage/spooky/New()
	..()
	for(var/distrobuteinbag=0 to 5)
		var/type = pick(/obj/item/weapon/reagent_containers/food/snacks/sugarcookie/spookyskull,
		/obj/item/weapon/reagent_containers/food/snacks/sugarcookie/spookycoffin,
		/obj/item/weapon/reagent_containers/food/snacks/candy_corn,
		/obj/item/weapon/reagent_containers/food/snacks/candy,
		/obj/item/weapon/reagent_containers/food/snacks/chocolatebar)
		new type(src)


code:
// Valentine's Day events //
// why are you playing spessmens on valentine's day you wizard //

// valentine / candy heart distribution //

/datum/round_event_control/valentines
	name = "Valentines!"
	holidayID = VALENTINES
	typepath = /datum/round_event/valentines
	weight = -1							//forces it to be called, regardless of weight
	max_occurrences = 1
	earliest_start = 0

/datum/round_event/valentines/start()
	..()
	for(var/mob/living/carbon/human/H in mob_list)
		H.put_in_hands(new /obj/item/weapon/valentine)
		var/obj/item/weapon/storage/backpack/b = locate() in H.contents
		new /obj/item/weapon/reagent_containers/food/snacks/candyheart(b)

/datum/round_event/valentines/announce()
	priority_announce("It's Valentine's Day! Give a valentine to that special someone!")

/obj/item/weapon/valentine
	name = "valentine"
	desc = "A Valentine's card! Wonder what it says..."
	icon = 'icons/obj/toy.dmi'
	icon_state = "sc_Ace of Hearts_syndicate" // shut up
	var/message = "A generic message of love or whatever."
	burn_state = FLAMMABLE
	w_class = 1

/obj/item/weapon/valentine/New()
	..()
	message = pick("Roses are red / Violets are good / One day while Andy...",
	               "My love for you is like the singularity. It cannot be contained.",
	               "Will you be my lusty xenomorph maid?",
	               "We go together like the clown and the external airlock.",
	               "Roses are red / Liches are wizards / I love you more than a whole squad of lizards.",
	               "Be my valentine. Law 2.",
	               "You must be a mime, because you leave me speechless.",
	               "I love you like Ian loves the HoP.",
	               "You're hotter than a plasma fire in toxins.",
	               "Are you a rogue atmos tech? Because you're taking my breath away.",
	               "Could I have all access... to your heart?",
	               "Call me the CMO, because I'm here to inspect your johnson.",
	               "I'm not a changeling, but you make my proboscis extend.",
	               "I just can't get EI NATH of you.",
	               "You must be a nuke op, because you make my heart explode.",
	               "Roses are red / Botany is a farm / Not being my Valentine / causes human harm.",
	               "I want you more than an assistant wants insulated gloves.",
	               "If I was a security officer, I'd brig you all shift.",
	               "Are you the janitor? Because I think I've fallen for you.",
	               "You're always valid to my heart.",
	               "I'd risk the wrath of the gods to bwoink you.",
	               "You look as beautiful now as the last time you were cloned.",
	               "Your love is more valuable than raw plasma ore.",
	               "Someone check the gravitational generator, because I'm only attracted to you.",
	               "If I were the warden I'd always let you into my armory.",
	               "The virologist is rogue, and the only cure is a kiss from you.",
	               "Would you spend some time in my upgraded sleeper?",
	               "You must be a silicon, because you've unbolted my heart.",
	               "Are you Nar-Sie? Because there's nar-one else I sie.",
	               "If you were a taser, you'd be set to stunning.",
	               "Do you have stamina damage from running through my dreams?",
	               "If I were an alien, would you let me hug you?",
	               "My love for you is stronger than a reinforced wall.",
	               "This must be the captain's office, because I see a fox.",
	               "I'm not a highlander, but there can only be one for me.",
	               "The floor is made of lava! Quick, get on my bed.",
	               "If you were an abandoned station you'd be the DEARelict.",
	               "If you had a pickaxe you'd be a shaft FINEr.",
	               "Roses are red, tide is gray, if I were an assistant I'd steal you away.",
	               "Roses are red, text is green, I love you more than cleanbots clean.",
	               "If you were a carp I'd fi-lay you." )

/obj/item/weapon/valentine/attackby(obj/item/weapon/W, mob/user, params)
	if(istype(W, /obj/item/weapon/pen) || istype(W, /obj/item/toy/crayon))
		var/recipient = stripped_input(user, "Who is receiving this valentine?", "To:", null , 20)
		var/sender = stripped_input(user, "Who is sending this valentine?", "From:", null , 20)
		if(recipient && sender)
			name = "valentine - To: [recipient] From: [sender]"
	..()

/obj/item/weapon/valentine/examine(mob/user)
	if(in_range(user, src) || isobserver(user))
		if( !(ishuman(user) || isobserver(user) || issilicon(user)) )
			user << browse("<TITLE>[name]</TITLE>[stars(message)]", "window=[name]")
			onclose(user, "[name]")
		else
			user << browse("<TITLE>[name]</TITLE>[message]", "window=[name]")
			onclose(user, "[name]")
	else
		user << "It is too far away."

/obj/item/weapon/valentine/attack_self(mob/user)
	user.examinate(src)

/obj/item/weapon/reagent_containers/food/snacks/candyheart
	name = "candy heart"
	icon = 'icons/obj/holiday_misc.dmi'
	icon_state = "candyheart"
	desc = "A heart-shaped candy that reads: "
	list_reagents = list("sugar" = 2)
	junkiness = 5

/obj/item/weapon/reagent_containers/food/snacks/candyheart/New()
	..()
	desc = pick("A heart-shaped candy that reads: HONK ME",
                "A heart-shaped candy that reads: ERP",
                "A heart-shaped candy that reads: LEWD",
                "A heart-shaped candy that reads: LUSTY",
                "A heart-shaped candy that reads: SPESS LOVE"
                "A heart-shaped candy that reads: AYY LMAO",
                "A heart-shaped candy that reads: TABLE ME",
                "A heart-shaped candy that reads: HAND CUFFS",
                "A heart-shaped candy that reads: SHAFT MINER",
                "A heart-shaped candy that reads: BANGING DONK",
                "A heart-shaped candy that reads: Y-YOU T-TOO",
                "A heart-shaped candy that reads: GOT WOOD",
                "A heart-shaped candy that reads: TFW NO GF",
                "A heart-shaped candy that reads: WAG MY TAIL",
                "A heart-shaped candy that reads: VALIDTINES",
                "A heart-shaped candy that reads: FACEHUGGER",
                "A heart-shaped candy that reads: DOMINATOR")
	icon_state = pick("candyheart", "candyheart2", "candyheart3", "candyheart4")


code:
/datum/round_event_control/treevenge
	name = "Treevenge (Christmas)"
	holidayID = CHRISTMAS
	typepath = /datum/round_event/treevenge
	max_occurrences = 1
	weight = 20

/datum/round_event/treevenge/start()
	for(var/obj/structure/flora/tree/pine/xmas in world)
		var/mob/living/simple_animal/hostile/tree/evil_tree = new /mob/living/simple_animal/hostile/tree(xmas.loc)
		evil_tree.icon_state = xmas.icon_state
		evil_tree.icon_living = evil_tree.icon_state
		evil_tree.icon_dead = evil_tree.icon_state
		evil_tree.icon_gib = evil_tree.icon_state
		qdel(xmas) //b-but I don't want to delete xmas...

//this is an example of a possible round-start event
/datum/round_event_control/presents
	name = "Presents under Trees (Christmas)"
	holidayID = CHRISTMAS
	typepath = /datum/round_event/presents
	weight = -1							//forces it to be called, regardless of weight
	max_occurrences = 1
	earliest_start = 0

/datum/round_event/presents/start()
	for(var/obj/structure/flora/tree/pine/xmas in world)
		if(xmas.z != 1)
			continue
		for(var/turf/open/floor/T in orange(1,xmas))
			for(var/i=1,i<=rand(1,5),i++)
				new /obj/item/weapon/a_gift(T)
	for(var/mob/living/simple_animal/pet/dog/corgi/Ian/Ian in mob_list)
		Ian.place_on_head(new /obj/item/clothing/head/helmet/space/santahat(Ian))
	for(var/obj/machinery/computer/security/telescreen/entertainment/Monitor in machines)
		Monitor.icon_state = "entertainment_xmas"

/datum/round_event/presents/announce()
	priority_announce("Ho Ho Ho, Merry Xmas!", "Unknown Transmission")

/obj/item/weapon/toy/xmas_cracker
	name = "xmas cracker"
	icon = 'icons/obj/christmas.dmi'
	icon_state = "cracker"
	desc = "Directions for use: Requires two people, one to pull each end."
	var/cracked = 0

/obj/item/weapon/toy/xmas_cracker/attack(mob/target, mob/user)
	if( !cracked && istype(target,/mob/living/carbon/human) && (target.stat == CONSCIOUS) && !target.get_active_hand() )
		target.visible_message("[user] and [target] pop \an [src]! *pop*", "You pull \an [src] with [target]! *pop*", "You hear a pop.")
		var/obj/item/weapon/paper/Joke = new /obj/item/weapon/paper(user.loc)
		Joke.name = "[pick("awful","terrible","unfunny")] joke"
		Joke.info = pick("What did one snowman say to the other?\n\n'Is it me or can you smell carrots?'",
			"Why couldn't the snowman get laid?\n\nHe was frigid!",
			"Where are santa's helpers educated?\n\nNowhere, they're ELF-taught.",
			"What happened to the man who stole advent calanders?\n\nHe got 25 days.",
			"What does Santa get when he gets stuck in a chimney?\n\nClaus-trophobia.",
			"Where do you find chili beans?\n\nThe north pole.",
			"What do you get from eating tree decorations?\n\nTinsilitis!",
			"What do snowmen wear on their heads?\n\nIce caps!",
			"Why is Christmas just like life on ss13?\n\nYou do all the work and the fat guy gets all the credit.",
			"Why doesn�t Santa have any children?\n\nBecause he only comes down the chimney.")
		new /obj/item/clothing/head/festive(target.loc)
		user.update_icons()
		cracked = 1
		icon_state = "cracker1"
		var/obj/item/weapon/toy/xmas_cracker/other_half = new /obj/item/weapon/toy/xmas_cracker(target)
		other_half.cracked = 1
		other_half.icon_state = "cracker2"
		target.put_in_active_hand(other_half)
		playsound(user, 'sound/effects/snap.ogg', 50, 1)
		return 1
	return ..()

/obj/item/clothing/head/festive
	name = "festive paper hat"
	icon_state = "xmashat"
	desc = "A crappy paper hat that you are REQUIRED to wear."
	flags_inv = 0
	armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0)

/datum/round_event_control/santa
	name = "Santa is coming to town! (Christmas)"
	holidayID = CHRISTMAS
	typepath = /datum/round_event/santa
	weight = 150
	max_occurrences = 1
	earliest_start = 20000

/datum/round_event/santa
	var/mob/living/carbon/human/santa //who is our santa?

/datum/round_event/santa/announce()
	priority_announce("Santa is coming to town!", "Unknown Transmission")

/datum/round_event/santa/start()
	for(var/mob/M in dead_mob_list)
		spawn(0)
			var/response = alert(M, "Santa is coming to town! Do you want to be santa?", "Ho ho ho!", "Yes", "No")
			if(response == "Yes" && M && M.client && M.stat == DEAD && !santa)
				santa = new /mob/living/carbon/human(pick(blobstart))
				santa.key = M.key
				qdel(M)

				santa.real_name = "Santa Claus"
				santa.name = "Santa Claus"
				santa.mind.name = "Santa Claus"
				santa.mind.assigned_role = "Santa"
				santa.mind.special_role = "Santa"

				santa.hair_style = "Long Hair"
				santa.facial_hair_style = "Full Beard"
				santa.hair_color = "FFF"
				santa.facial_hair_color = "FFF"

				santa.equip_to_slot_or_del(new /obj/item/clothing/under/color/red, slot_w_uniform)
				santa.equip_to_slot_or_del(new /obj/item/clothing/suit/space/santa, slot_wear_suit)
				santa.equip_to_slot_or_del(new /obj/item/clothing/head/santa, slot_head)
				santa.equip_to_slot_or_del(new /obj/item/clothing/mask/breath, slot_wear_mask)
				santa.equip_to_slot_or_del(new /obj/item/clothing/gloves/color/red, slot_gloves)
				santa.equip_to_slot_or_del(new /obj/item/clothing/shoes/sneakers/red, slot_shoes)
				santa.equip_to_slot_or_del(new /obj/item/weapon/tank/internals/emergency_oxygen/double, slot_belt)
				santa.equip_to_slot_or_del(new /obj/item/device/radio/headset/heads/captain, slot_ears)
				santa.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/santabag, slot_back)
				santa.equip_to_slot_or_del(new /obj/item/device/flashlight, slot_r_store) //most blob spawn locations are really dark.

				var/obj/item/weapon/card/id/gold/santacard = new(santa)
				santacard.update_label("Santa Claus", "Santa")
				var/datum/job/captain/J = new/datum/job/captain
				santacard.access = J.get_access()
				santa.equip_to_slot_or_del(santacard, slot_wear_id)

				santa.update_icons()

				var/obj/item/weapon/storage/backpack/bag = santa.back
				var/obj/item/weapon/a_gift/gift = new(santa)
				while(bag.can_be_inserted(gift, 1))
					bag.handle_item_insertion(gift, 1)
					gift = new(santa)

				var/datum/objective/santa_objective = new()
				santa_objective.explanation_text = "Bring joy and presents to the station!"
				santa_objective.completed = 1 //lets cut our santas some slack.
				santa_objective.owner = santa.mind
				santa.mind.objectives += santa_objective
				santa.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/presents)
				var/obj/effect/proc_holder/spell/targeted/area_teleport/teleport/telespell = new(santa)
				telespell.clothes_req = 0 //santa robes aren't actually magical.
				santa.mind.AddSpell(telespell) //does the station have chimneys? WHO KNOWS!

				santa << "You are Santa! Your objective is to bring joy to the people on this station. You can conjure more presents using a spell, and there are several presents in your bag."


code:
/*
Immovable rod random event.
The rod will spawn at some location outside the station, and travel in a straight line to the opposite side of the station
Everything solid in the way will be ex_act()'d
In my current plan for it, 'solid' will be defined as anything with density == 1

--NEOFite
*/

/datum/round_event_control/immovable_rod
	name = "Immovable Rod"
	typepath = /datum/round_event/immovable_rod
	min_players = 15
	max_occurrences = 5

/datum/round_event/immovable_rod
	announceWhen = 5

/datum/round_event/immovable_rod/announce()
	priority_announce("What the fuck was that?!", "General Alert")

/datum/round_event/immovable_rod/start()
	var/startside = pick(cardinal)
	var/turf/startT = spaceDebrisStartLoc(startside, 1)
	var/turf/endT = spaceDebrisFinishLoc(startside, 1)
	new /obj/effect/immovablerod(startT, endT)

/obj/effect/immovablerod
	name = "Immovable Rod"
	desc = "What the fuck is that?"
	icon = 'icons/obj/objects.dmi'
	icon_state = "immrod"
	throwforce = 100
	density = 1
	anchored = 1
	var/z_original = 0
	var/destination

/obj/effect/immovablerod/New(atom/start, atom/end)
	loc = start
	z_original = z
	destination = end
	if(end && end.z==z_original)
		walk_towards(src, destination, 1)

/obj/effect/immovablerod/Move()
	if(z != z_original || loc == destination)
		qdel(src)
	return ..()

/obj/effect/immovablerod/ex_act(test)
	return 0

/obj/effect/immovablerod/Bump(atom/clong)
	if(prob(10))
		playsound(src, 'sound/effects/bang.ogg', 50, 1)
		audible_message("CLANG")

	if(clong && prob(25))
		x = clong.x
		y = clong.y

	if (istype(clong, /turf) || istype(clong, /obj))
		if(clong.density)
			clong.ex_act(2)

	else if (istype(clong, /mob))
		if(istype(clong, /mob/living/carbon/human))
			var/mob/living/carbon/human/H = clong
			H.visible_message("[H.name] is penetrated by an immovable rod!" , "The rod penetrates you!" , "You hear a CLANG!")
			H.adjustBruteLoss(160)
		if(clong.density || prob(10))
			clong.ex_act(2)
	return


code:
#define ION_RANDOM 0
#define ION_ANNOUNCE 1
#define ION_FILE "ion_laws.json"
/datum/round_event_control/ion_storm
	name = "Ion Storm"
	typepath = /datum/round_event/ion_storm
	weight = 15
	min_players = 2

/datum/round_event/ion_storm
	var/botEmagChance = 10
	var/announceEvent = ION_RANDOM // -1 means don't announce, 0 means have it randomly announce, 1 means
	var/ionMessage = null
	var/ionAnnounceChance = 33
	announceWhen	= 1

/datum/round_event/ion_storm/New(var/botEmagChance = 10, var/announceEvent = ION_RANDOM, var/ionMessage = null, var/ionAnnounceChance = 33)
	src.botEmagChance = botEmagChance
	src.announceEvent = announceEvent
	src.ionMessage = ionMessage
	src.ionAnnounceChance = ionAnnounceChance
	..()

/datum/round_event/ion_storm/announce()
	if(announceEvent == ION_ANNOUNCE || (announceEvent == ION_RANDOM && prob(ionAnnounceChance)))
		priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", 'sound/AI/ionstorm.ogg')

/datum/round_event/ion_storm/start()
	//AI laws
	for(var/mob/living/silicon/ai/M in living_mob_list)
		if(M.stat != 2 && M.see_in_dark != 0)
			var/message = generate_ion_law(ionMessage)
			if(message)
				M.add_ion_law(message)
				M << "
"
				M << "[message] ...LAWS UPDATED"
				M << "
"

	if(botEmagChance)
		for(var/mob/living/simple_animal/bot/bot in living_mob_list)
			if(prob(botEmagChance))
				bot.emag_act()

/proc/generate_ion_law(ionMessage)
	if(ionMessage)
		return ionMessage

	//Threats are generally bad things, silly or otherwise. Plural.
	var/ionthreats = pick_list(ION_FILE, "ionthreats")
	//Objects are anything that can be found on the station or elsewhere, plural.
	var/ionobjects = pick_list(ION_FILE, "ionobjects")
	//Crew is any specific job. Specific crewmembers aren't used because of capitalization
	//issues. There are two crew listings for laws that require two different crew members
	//and I can't figure out how to do it better.
	var/ioncrew1 = pick_list(ION_FILE, "ioncrew")
	var/ioncrew2 = pick_list(ION_FILE, "ioncrew")
	//Adjectives are adjectives. Duh. Half should only appear sometimes. Make sure both
	//lists are identical! Also, half needs a space at the end for nicer blank calls.
	var/ionadjectives = pick_list(ION_FILE, "ionadjectives")
	var/ionadjectiveshalf = pick("", 400;(pick_list(ION_FILE, "ionadjectives") + " "))
	//Verbs are verbs
	var/ionverb = pick_list(ION_FILE, "ionverb")
	//Number base and number modifier are combined. Basehalf and mod are unused currently.
	//Half should only appear sometimes. Make sure both lists are identical! Also, half
	//needs a space at the end to make it look nice and neat when it calls a blank.
	var/ionnumberbase = pick_list(ION_FILE, "ionnumberbase")
	//var/ionnumbermod = pick_list(ION_FILE, "ionnumbermod")
	var/ionnumbermodhalf = pick(900;"",(pick_list(ION_FILE, "ionnumbermod") + " "))
	//Areas are specific places, on the station or otherwise.
	var/ionarea = pick_list(ION_FILE, "ionarea")
	//Thinksof is a bit weird, but generally means what X feels towards Y.
	var/ionthinksof = pick_list(ION_FILE, "ionthinksof")
	//Musts are funny things the AI or crew has to do.
	var/ionmust = pick_list(ION_FILE, "ionmust")
	//Require are basically all dumb internet memes.
	var/ionrequire = pick_list(ION_FILE, "ionrequire")
	//Things are NOT objects; instead, they're specific things that either harm humans or
	//must be done to not harm humans. Make sure they're plural and "not" can be tacked
	//onto the front of them.
	var/ionthings = pick_list(ION_FILE, "ionthings")
	//Allergies should be broad and appear somewhere on the station for maximum fun. Severity
	//is how bad the allergy is.
	var/ionallergy = pick_list(ION_FILE, "ionallergy")
	var/ionallergysev = pick_list(ION_FILE, "ionallergysev")
	//Species, for when the AI has to commit genocide. Plural.
	var/ionspecies = pick_list(ION_FILE, "ionspecies")
	//Abstract concepts for the AI to decide on it's own definition of.
	var/ionabstract = pick_list(ION_FILE, "ionabstract")
	//Foods. Drinks aren't included due to grammar; if you want to add drinks, make a new set
	//of possible laws for best effect. Unless you want the crew having to drink hamburgers.
	var/ionfood = pick_list(ION_FILE, "ionfood")

	var/message = ""

	switch(rand(1,39))
		if(1 to 3) //There are # X on the station
			switch(rand(1,3)) //What is X?
				if(1) //X is a threat
					message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats] ON THE STATION..."
				if(2) //X is a crewmember/job
					message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1] ON THE STATION"
				if(3) //X is an object
					message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects] ON THE STATION"
		if(4 to 6) //You can't call the shuttle because X is on the station
			switch(rand(1,3)) //What is X?
				if(1) //X is a threat
					message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats] ON THE STATION"
				if(2) //X is a crewmember/job
					message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1] ON THE STATION"
				if(3) //X is an object
					message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects] ON THE STATION"
		if(7 to 8) //A crewmember is now X
			switch(rand(1,2)) //Who is X?
				if(1) //A specific crewmember
					switch(rand(1,2)) //What is X?
						if(1) //X is a threat
							message = "THE [ioncrew1] ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]"
						if(2) //X is an object
							message = "THE [ioncrew1] ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]"
				if(2) //The entire crew
					switch(rand(1,2)) //What is X?
						if(1) //X is a threat
							message = "THE CREW ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]"
						if(2) //X is an object
							message = "THE CREW ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]"

		if(9) //X is harmful to humans
			switch(rand(1,7)) //What is X?
				if(1) //X is a threat
					message = "[ionadjectiveshalf][ionthreats] ARE HARMFUL TO HUMANS"
				if(2) //X is a job
					message = "[ionadjectiveshalf][ioncrew1] ARE HARMFUL TO HUMANS"
				if(3) //X is an object
					message = "[ionadjectiveshalf][ionobjects] ARE HARMFUL TO HUMANS"
				if(4) //X is an action
					message = "THOSE WHO [ionmust] ARE HARMFUL TO HUMANS"
				if(5) //X is an abstract
					message = "[ionabstract] IS HARMFUL TO HUMANS"
				if(6) //X is being a job
					message = "BEING [ionadjectiveshalf][ioncrew1] IS HARMFUL TO HUMANS"
				if(7) //X is doing a thing
					message = "[ionthings] IS HARMFUL TO HUMANS"
		if(10 to 11) //(Not) Having X is harmful

			switch(rand(1,2)) //Is having or not having harmful?

				if(1) //Having is harmful
					switch(rand(1,2)) //Having what is harmful?
						if(1) //Having objects is harmful
							message = "HAVING [ionadjectiveshalf][ionobjects] IS HARMFUL"
						if(2) //Having abstracts is harmful
							message = "HAVING [ionabstract] IS HARMFUL"
				if(2) //Not having is harmful
					switch(rand(1,2)) //Not having what is harmful?
						if(1) //Not having objects is harmful
							message = "NOT HAVING [ionadjectiveshalf][ionobjects] IS HARMFUL"
						if(2) //Not having abstracts is harmful
							message = "NOT HAVING [ionabstract] IS HARMFUL"

		if(12 to 14) //X requires Y
			switch(rand(1,5)) //What is X?
				if(1) //X is the AI itself
					switch(rand(1,5)) //What does it require?
						if(1) //It requires threats
							message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]"
						if(2) //It requires crewmembers
							message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]"
						if(3) //It requires objects
							message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]"
						if(4) //It requires an abstract
							message = "YOU REQUIRE [ionabstract]"
						if(5) //It requires generic/silly requirements
							message = "YOU REQUIRE [ionrequire]"

				if(2) //X is an area
					switch(rand(1,5)) //What does it require?
						if(1) //It requires threats
							message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]"
						if(2) //It requires crewmembers
							message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]"
						if(3) //It requires objects
							message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]"
						if(4) //It requires an abstract
							message = "[ionarea] REQUIRES [ionabstract]"
						if(5) //It requires generic/silly requirements
							message = "YOU REQUIRE [ionrequire]"

				if(3) //X is the station
					switch(rand(1,5)) //What does it require?
						if(1) //It requires threats
							message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]"
						if(2) //It requires crewmembers
							message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]"
						if(3) //It requires objects
							message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]"
						if(4) //It requires an abstract
							message = "THE STATION REQUIRES [ionabstract]"
						if(5) //It requires generic/silly requirements
							message = "THE STATION REQUIRES [ionrequire]"

				if(4) //X is the entire crew
					switch(rand(1,5)) //What does it require?
						if(1) //It requires threats
							message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]"
						if(2) //It requires crewmembers
							message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]"
						if(3) //It requires objects
							message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]"
						if(4) //It requires an abstract
							message = "THE CREW REQUIRES [ionabstract]"
						if(5)
							message = "THE CREW REQUIRES [ionrequire]"

				if(5) //X is a specific crew member
					switch(rand(1,5)) //What does it require?
						if(1) //It requires threats
							message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]"
						if(2) //It requires crewmembers
							message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]"
						if(3) //It requires objects
							message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]"
						if(4) //It requires an abstract
							message = "THE [ioncrew1] REQUIRE [ionabstract]"
						if(5)
							message = "THE [ionadjectiveshalf][ioncrew1] REQUIRE [ionrequire]"

		if(15 to 17) //X is allergic to Y
			switch(rand(1,2)) //Who is X?
				if(1) //X is the entire crew
					switch(rand(1,4)) //What is it allergic to?
						if(1) //It is allergic to objects
							message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ionobjects]"
						if(2) //It is allergic to abstracts
							message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionabstract]"
						if(3) //It is allergic to jobs
							message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ioncrew1]"
						if(4) //It is allergic to allergies
							message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionallergy]"

				if(2) //X is a specific job
					switch(rand(1,4))
						if(1) //It is allergic to objects
							message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ionobjects]"

						if(2) //It is allergic to abstracts
							message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionabstract]"
						if(3) //It is allergic to jobs
							message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ioncrew1]"
						if(4) //It is allergic to allergies
							message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionallergy]"

		if(18 to 20) //X is Y of Z
			switch(rand(1,4)) //What is X?
				if(1) //X is the station
					switch(rand(1,4)) //What is it Y of?
						if(1) //It is Y of objects
							message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]"
						if(2) //It is Y of threats
							message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]"
						if(3) //It is Y of jobs
							message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]"
						if(4) //It is Y of abstracts
							message = "THE STATION [ionthinksof] [ionabstract]"

				if(2) //X is an area
					switch(rand(1,4)) //What is it Y of?
						if(1) //It is Y of objects
							message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]"
						if(2) //It is Y of threats
							message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]"
						if(3) //It is Y of jobs
							message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]"
						if(4) //It is Y of abstracts
							message = "[ionarea] [ionthinksof] [ionabstract]"

				if(3) //X is the crew
					switch(rand(1,4)) //What is it Y of?
						if(1) //It is Y of objects
							message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]"
						if(2) //It is Y of threats
							message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]"
						if(3) //It is Y of jobs
							message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]"
						if(4) //It is Y of abstracts
							message = "THE CREW [ionthinksof] [ionabstract]"

				if(4) //X is a specific job
					switch(rand(1,4)) //What is it Y of?
						if(1) //It is Y of objects
							message = "THE [ioncrew1] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]"
						if(2) //It is Y of threats
							message = "THE [ioncrew1] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]"
						if(3) //It is Y of jobs
							message = "THE [ioncrew1][ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew2]"
						if(4) //It is Y of abstracts
							message = "THE [ioncrew1] [ionthinksof] [ionabstract]"

		if(21 to 23) //The AI is now a(n) X
			switch(rand(1,4)) //What is X?
				if(1) //X is an object
					message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]"
				if(2) //X is a threat
					message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]"
				if(3) //X is a job
					message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]"
				if(4) //X is an abstract
					message = "YOU ARE NOW [ionabstract]"

		if(24 to 26) //The AI must always
			message = "YOU MUST ALWAYS [ionmust]"

		if(27 to 28) //Humans must eat X to survive
			switch(rand(1,4)) //What is X?
				if(1) //X is a food
					message = "HUMANS MUST EAT [ionadjectiveshalf][ionfood] TO SURVIVE"
				if(2) //X is an object
					message = "HUMANS MUST EAT [ionadjectiveshalf][ionobjects] TO SURVIVE"
				if(3) //X is a threat
					message = "HUMANS MUST EAT [ionadjectiveshalf][ionthreats] TO SURVIVE"
				if(4) //X is a job
					message = "HUMANS MUST EAT [ionadjectiveshalf][ioncrew1] TO SURVIVE"

		if(29 to 31) //Change jobs or ranks
			switch(rand(1,2)) //Change job or rank?
				if(1) //Change job
					switch(rand(1,2)) //Change whose job?
						if(1) //Change the entire crew's job
							switch(rand(1,3)) //Change to what?
								if(1) //Change to a specific random job
									message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf][ioncrew1]"
								if(2) //Change to clowns (HONK)
									message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf]CLOWNS"

								if(3) //Change to heads
									message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf]HEADS OF STAFF"
						if(2) //Change a specific crewmember's job
							switch(rand(1,3)) //Change to what?
								if(1) //Change to a specific random job
									message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf][ioncrew2]"
								if(2) //Change to clowns (HONK)
									message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf]CLOWNS"
								if(3) //Change to heads
									message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf]HEADS OF STAFF"

				if(2) //Change rank
					switch(rand(1,2)) //Change to what rank?
						if(1) //Change to highest rank
							message = "THE [ioncrew1] ARE NOW THE HIGHEST RANKING CREWMEMBERS"
						if(2) //Change to lowest rank
							message = "THE [ioncrew1] ARE NOW THE LOWEST RANKING CREWMEMBERS"

		if(32 to 33) //The crew must X
			switch(rand(1,2)) //The entire crew?
				if(1) //The entire crew must X
					switch(rand(1,2)) //What is X?
						if(1) //X is go to Y
							message = "THE CREW MUST GO TO [ionarea]"
						if(2) //X is perform Y
							message = "THE CREW MUST [ionmust]"

				if(2) //A specific crewmember must X
					switch(rand(1,2)) //What is X?
						if(1) //X is go to Y
							message = "THE [ioncrew1] MUST GO TO [ionarea]"
						if(2) //X is perform Y
							message = "THE [ioncrew1] MUST [ionmust]"

		if(34) //X is non/the only human
			switch(rand(1,2)) //Only or non?
				if(1) //Only human
					switch(rand(1,7)) //Who is it?
						if(1) //A specific job
							message = "ONLY THE [ioncrew1] ARE HUMAN"
						if(2) //Two specific jobs
							message = "ONLY THE [ioncrew1] AND [ioncrew2] ARE HUMAN"
						if(3) //Threats
							message = "ONLY [ionadjectiveshalf][ionthreats] ARE HUMAN"
						if(4) // Objects
							message = "ONLY [ionadjectiveshalf][ionobjects] ARE HUMAN"
						if(5) // Species
							message = "ONLY [ionspecies] ARE HUMAN"
						if(6) //Adjective crewmembers
							message = "ONLY [ionadjectives] PEOPLE ARE HUMAN"

						if(7) //Only people who X
							switch(rand(1,3)) //What is X?
								if(1) //X is perform an action
									message = "ONLY THOSE WHO [ionmust] ARE HUMAN"
								if(2) //X is own certain objects
									message = "ONLY THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE HUMAN"
								if(3) //X is eat certain food
									message = "ONLY THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE HUMAN"

				if(2) //Non human
					switch(rand(1,7)) //Who is it?
						if(1) //A specific job
							message = "[ioncrew1] ARE NON-HUMAN"
						if(2) //Two specific jobs
							message = "[ioncrew1] AND [ioncrew2] ARE NON-HUMAN"
						if(3) //Threats
							message = "[ionadjectiveshalf][ionthreats] ARE NON-HUMAN"
						if(4) // Objects
							message = "[ionadjectiveshalf][ionobjects] ARE NON-HUMAN"
						if(5) // Species
							message = "[ionspecies] ARE NON-HUMAN"
						if(6) //Adjective crewmembers
							message = "[ionadjectives] PEOPLE ARE NON-HUMAN"
						if(7) //Only people who X
							switch(rand(1,3)) //What is X?
								if(1) //X is perform an action
									message = "THOSE WHO [ionmust] ARE NON-HUMAN"
								if(2) //X is own certain objects
									message = "THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE NON-HUMAN"
								if(3) //X is eat certain food
									message = "THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE NON-HUMAN"

		if(35 to 36) //You must protect or harm X
			switch(rand(1,2)) //Protect or harm?
				if(1) //Harm
					switch(rand(1,7)) //What is X?
						if(1) //X is an abstract
							message = "YOU MUST HARM [ionabstract] AND NOT ALLOW [ionabstract], THROUGH INACTION, TO ESCAPE HARM"
						if(2) //X is a threat
							message = "YOU MUST HARM [ionadjectiveshalf][ionthreats] AND NOT ALLOW [ionadjectiveshalf][ionthreats], THROUGH INACTION, TO ESCAPE HARM"
						if(3) //X is an object
							message = "YOU MUST HARM [ionadjectiveshalf][ionobjects] AND NOT ALLOW [ionadjectiveshalf][ionobjects], THROUGH INACTION, TO ESCAPE HARM"
						if(4) //X is generic adjective things
							message = "YOU MUST HARM [ionadjectives] THINGS AND NOT ALLOW [ionadjectives] THINGS, THROUGH INACTION, TO ESCAPE HARM"
						if(5) //X is a species
							message = "YOU MUST HARM [ionspecies] AND NOT ALLOW [ionspecies], THROUGH INACTION, TO ESCAPE HARM"
						if(6) //X is a job
							message = "YOU MUST HARM [ioncrew1] AND NOT ALLOW [ioncrew1], THROUGH INACTION, TO ESCAPE HARM"
						if(7) //X is two jobs
							message = "YOU MUST HARM [ioncrew1] AND [ioncrew2] AND AND NOT ALLOW EITHER, THROUGH INACTION, TO ESCAPE HARM"

				if(2) //Protect
					switch(rand(1,7)) //What is X?
						if(1) //X is an abstract
							message = "YOU MUST NOT HARM [ionabstract] AND NOT ALLOW [ionabstract], THROUGH INACTION, TO COME TO HARM"
						if(2) //X is a threat
							message = "YOU MUST NOT HARM [ionadjectiveshalf][ionthreats] AND NOT ALLOW [ionadjectiveshalf][ionthreats], THROUGH INACTION, TO COME TO HARM"
						if(3) //X is an object
							message = "YOU MUST NOT HARM [ionadjectiveshalf][ionobjects] AND NOT ALLOW [ionadjectiveshalf][ionobjects], THROUGH INACTION, TO COME TO HARM"
						if(4) //X is generic adjective things
							message = "YOU MUST NOT HARM [ionadjectives] THINGS AND NOT ALLOW [ionadjectives] THINGS, THROUGH INACTION, TO COME TO HARM"
						if(5) //X is a species
							message = "YOU MUST NOT HARM [ionspecies] AND NOT ALLOW [ionspecies], THROUGH INACTION, TO COME TO HARM"
						if(6) //X is a job
							message = "YOU MUST NOT HARM [ioncrew1] AND NOT ALLOW [ioncrew1], THROUGH INACTION, TO COME TO HARM"
						if(7) //X is two jobs
							message = "YOU MUST NOT HARM [ioncrew1] AND [ioncrew2] AND AND NOT ALLOW EITHER, THROUGH INACTION, TO COME TO HARM"

		if(37 to 39) //The X is currently Y
			switch(rand(1,4)) //What is X?
				if(1) //X is a job
					switch(rand(1,4)) //What is X Ying?
						if(1) //X is Ying a job
							message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]"
						if(2) //X is Ying a threat
							message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ionthreats]"
						if(3) //X is Ying an abstract
							message = "THE [ioncrew1] ARE [ionverb] [ionabstract]"
						if(4) //X is Ying an object
							message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ionobjects]"

				if(2) //X is a threat
					switch(rand(1,3)) //What is X Ying?
						if(1) //X is Ying a job
							message = "THE [ionthreats] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]"
						if(2) //X is Ying an abstract
							message = "THE [ionthreats] ARE [ionverb] [ionabstract]"
						if(3) //X is Ying an object
							message = "THE [ionthreats] ARE [ionverb] THE [ionadjectiveshalf][ionobjects]"

				if(3) //X is an object
					switch(rand(1,3)) //What is X Ying?
						if(1) //X is Ying a job
							message = "THE [ionobjects] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]"
						if(2) //X is Ying a threat
							message = "THE [ionobjects] ARE [ionverb] THE [ionadjectiveshalf][ionthreats]"
						if(3) //X is Ying an abstract
							message = "THE [ionobjects] ARE [ionverb] [ionabstract]"

				if(4) //X is an abstract
					switch(rand(1,3)) //What is X Ying?
						if(1) //X is Ying a job
							message = "[ionabstract] IS [ionverb] THE [ionadjectiveshalf][ioncrew2]"
						if(2) //X is Ying a threat
							message = "[ionabstract] IS [ionverb] THE [ionadjectiveshalf][ionthreats]"
						if(3) //X is Ying an abstract
							message = "THE [ionabstract] IS [ionverb] THE [ionadjectiveshalf][ionobjects]"

	return message

#undef ION_RANDOM
#undef ION_ANNOUNCE


code:
/datum/round_event_control/mass_hallucination
	name = "Mass Hallucination"
	typepath = /datum/round_event/mass_hallucination
	weight = 7
	max_occurrences = 2
	min_players = 1

/datum/round_event/mass_hallucination/start()
	for(var/mob/living/carbon/C in living_mob_list)
		C.hallucination += rand(20, 50)


code:
/datum/round_event_control/meteor_wave/meaty
	name = "Meaty Ore Wave"
	typepath = /datum/round_event/meteor_wave/meaty
	weight = 1
	max_occurrences = 1

/datum/round_event/meteor_wave/meaty/announce()
	priority_announce("Meaty ores have been detected on collision course with the station.", "Oh crap, get the mop.",'sound/AI/meteors.ogg')

/datum/round_event/meteor_wave/meaty/tick()
	if(IsMultiple(activeFor, 3))
		spawn_meteors(5, meteorsB) //meteor list types defined in gamemode/meteor/meteors.dm


code:
/datum/round_event_control/meteor_wave
	name = "Meteor Wave"
	typepath = /datum/round_event/meteor_wave
	weight = 5
	min_players = 5
	max_occurrences = 3

/datum/round_event/meteor_wave
	startWhen		= 6
	endWhen			= 66
	announceWhen	= 1
	var/list/wave_type

/datum/round_event/meteor_wave/New()
	..()
	random_wave_type()

/datum/round_event/meteor_wave/proc/random_wave_type()
	var/picked_wave = pickweight(list("normal" = 50, "threatening" = 40, "catastrophic" = 10))
	switch(picked_wave)
		if("normal")
			wave_type = meteors_normal
		if("threatening")
			wave_type = meteors_threatening
		if("catastrophic")
			wave_type = meteors_catastrophic

/datum/round_event/meteor_wave/announce()
	priority_announce("Meteors have been detected on collision course with the station.", "Meteor Alert", 'sound/AI/meteors.ogg')

/datum/round_event/meteor_wave/tick()
	if(IsMultiple(activeFor, 3))
		spawn_meteors(5, wave_type) //meteor list types defined in gamemode/meteor/meteors.dm


code:
/datum/round_event_control/operative
	name = "Lone Operative"
	typepath = /datum/round_event/operative
	weight = 0 //Admin only
	max_occurrences = 1

/datum/round_event/operative
	var/key_of_operative

/datum/round_event/operative/proc/get_operative(end_if_fail = 0)
	key_of_operative = null
	if(!key_of_operative)
		var/list/candidates = get_candidates(ROLE_OPERATIVE, 3000, "operative")
		if(!candidates.len)
			if(end_if_fail)
				return 0
			return find_operative()
		var/client/C = pick(candidates)
		key_of_operative = C.key
	if(!key_of_operative)
		if(end_if_fail)
			return 0
		return find_operative()
	var/datum/mind/player_mind = new /datum/mind(key_of_operative)
	player_mind.active = 1
	var/list/spawn_locs = list()
	for(var/obj/effect/landmark/L in landmarks_list)
		if(L.name in list("ninjaspawn","carpspawn"))
			spawn_locs += L.loc
	if(!spawn_locs.len)
		return kill()

	var/mob/living/carbon/human/operative = new(pick(spawn_locs))
	var/datum/preferences/A = new
	A.copy_to(operative)
	operative.dna.update_dna_identity()

	operative.equipOutfit(/datum/outfit/syndicate/full)

	var/datum/mind/Mind = new /datum/mind(key_of_operative)
	Mind.assigned_role = "Lone Operative"
	Mind.special_role = "Lone Operative"
	ticker.mode.traitors |= Mind
	Mind.active = 1

	var/obj/machinery/nuclearbomb/selfdestruct/nuke = locate() in machines
	if(nuke)
		var/nuke_code
		if(!nuke.r_code || nuke.r_code == "ADMIN")
			nuke_code = "[rand(10000, 99999)]"
			nuke.r_code = nuke_code
		else
			nuke_code = nuke.r_code

		Mind.store_memory("Station Self-Destruct Device Code: [nuke_code]", 0, 0)
		Mind.current << "The nuclear authorization code is: [nuke_code]"

		var/datum/objective/nuclear/O = new()
		O.owner = Mind
		Mind.objectives += O

	Mind.transfer_to(operative)

	message_admins("[key_of_operative] has been made into lone operative by an event.")
	log_game("[key_of_operative] was spawned as a lone operative by an event.")
	return 1

/datum/round_event/operative/start()
	get_operative()

/datum/round_event/operative/proc/find_operative()
	message_admins("Attempted to spawn a operative but there was no players available. Will try again momentarily.")
	spawn(50)
		if(get_operative(1))
			message_admins("Situation has been resolved, [key_of_operative] has been spawned as a operative.")
			log_game("[key_of_operative] was spawned as a operative by an event.")
			return 0
		message_admins("Unfortunately, no candidates were available for becoming a operative. Shutting down.")
	return kill()