code:
var/datum/subsystem/objects/SSobj

/datum/proc/process()
	SSobj.processing.Remove(src)
	return 0

/datum/subsystem/objects
	name = "Objects"
	priority = 12

	var/list/processing = list()

/datum/subsystem/objects/New()
	NEW_SS_GLOBAL(SSobj)

/datum/subsystem/objects/Initialize(timeofday, zlevel)
	for(var/atom/movable/AM in world)
		if (zlevel && AM.z != zlevel)
			continue
		AM.initialize()
	if (zlevel)
		return ..()
	for(var/turf/simulated/floor/F in world)
		F.MakeDirty()
	..()

/datum/subsystem/objects/stat_entry()
	..("P:[processing.len]")

/datum/subsystem/objects/fire()
	var/i=1
	for(var/thing in SSobj.processing)
		if(thing)
			thing:process(wait)
			++i
			continue
		SSobj.processing.Cut(i, i+1)


code:
var/datum/subsystem/pai/SSpai

/datum/subsystem/pai
	name = "pAI"
	wait = 20

	var/askDelay = 600

	var/list/candidates = list()
	var/list/asked = list()

/datum/subsystem/pai/New()
	NEW_SS_GLOBAL(SSpai)

/datum/subsystem/pai/Topic(href, href_list[])
	if(href_list["download"])
		var/datum/paiCandidate/candidate = locate(href_list["candidate"])
		var/obj/item/device/paicard/card = locate(href_list["device"])
		if(card.pai)
			return
		if(istype(card,/obj/item/device/paicard) && istype(candidate,/datum/paiCandidate))
			var/mob/living/silicon/pai/pai = new(card)
			if(!candidate.name)
				pai.name = pick(ninja_names)
			else
				pai.name = candidate.name
			pai.real_name = pai.name
			pai.key = candidate.key

			card.setPersonality(pai)
			card.looking_for_personality = 0

			ticker.mode.update_cult_icons_removed(card.pai.mind)
			ticker.mode.update_rev_icons_removed(card.pai.mind)

			candidates -= candidate
			usr << browse(null, "window=findPai")

	if(href_list["new"])
		var/datum/paiCandidate/candidate = locate(href_list["candidate"])
		var/option = href_list["option"]
		var/t = ""

		switch(option)
			if("name")
				t = input("Enter a name for your pAI", "pAI Name", candidate.name) as text
				if(t)
					candidate.name = copytext(sanitize(t),1,MAX_NAME_LEN)
			if("desc")
				t = input("Enter a description for your pAI", "pAI Description", candidate.description) as message
				if(t)
					candidate.description = copytext(sanitize(t),1,MAX_MESSAGE_LEN)
			if("role")
				t = input("Enter a role for your pAI", "pAI Role", candidate.role) as text
				if(t)
					candidate.role = copytext(sanitize(t),1,MAX_MESSAGE_LEN)
			if("ooc")
				t = input("Enter any OOC comments", "pAI OOC Comments", candidate.comments) as message
				if(t)
					candidate.comments = copytext(sanitize(t),1,MAX_MESSAGE_LEN)
			if("save")
				candidate.savefile_save(usr)
			if("load")
				candidate.savefile_load(usr)
				//In case people have saved unsanitized stuff.
				if(candidate.name)
					candidate.name = copytext(sanitize(candidate.name),1,MAX_NAME_LEN)
				if(candidate.description)
					candidate.description = copytext(sanitize(candidate.description),1,MAX_MESSAGE_LEN)
				if(candidate.role)
					candidate.role = copytext(sanitize(candidate.role),1,MAX_MESSAGE_LEN)
				if(candidate.comments)
					candidate.comments = copytext(sanitize(candidate.comments),1,MAX_MESSAGE_LEN)

			if("submit")
				if(candidate)
					candidate.ready = 1
					for(var/obj/item/device/paicard/p in world)
						if(p.looking_for_personality == 1)
							p.alertUpdate()
				usr << browse(null, "window=paiRecruit")
				return
		recruitWindow(usr)

/datum/subsystem/pai/proc/recruitWindow(var/mob/M as mob)
	var/datum/paiCandidate/candidate
	for(var/datum/paiCandidate/c in candidates)
		if(c.key == M.key)
			candidate = c
	if(!candidate)
		candidate = new /datum/paiCandidate()
		candidate.key = M.key
		candidates.Add(candidate)

	var/dat = ""
	dat += {"

			"}

	dat += "
Please configure your pAI personality's options. Remember, what you enter here could determine whether or not the user requesting a personality chooses you!
"
	dat += "
"
	dat += "
Name:	[candidate.name]
"
	dat += "
\[Edit\]	What you plan to call yourself. Suggestions: Any character name you would choose for a station character OR an AI.
"

	dat += "
Description:	[candidate.description]
"
	dat += "
\[Edit\]	What sort of pAI you typically play; your mannerisms, your quirks, etc. This can be as sparse or as detailed as you like.
"

	dat += "
Preferred Role:	[candidate.role]
"
	dat += "
\[Edit\]	Do you like to partner with sneaky social ninjas? Like to help security hunt down thugs? Enjoy watching an engineer's back while he saves the station yet again? This doesn't have to be limited to just station jobs. Pretty much any general descriptor for what you'd like to be doing works here.
"

	dat += "
OOC Comments:	[candidate.comments]
"
	dat += "
\[Edit\]	Anything you'd like to address specifically to the player reading this in an OOC manner. \"I prefer more serious RP.\", \"I'm still learning the interface!\", etc. Feel free to leave this blank if you want.
"

	dat += "
"

	dat += "
"
	dat += "
Submit Personality

"
	dat += "Save Personality
"
	dat += "Load Personality
"

	M << browse(dat, "window=paiRecruit")

/datum/subsystem/pai/proc/findPAI(var/obj/item/device/paicard/p, var/mob/user)
	requestRecruits()
	var/list/available = list()
	for(var/datum/paiCandidate/c in SSpai.candidates)
		if(c.ready)
			var/found = 0
			for(var/mob/dead/observer/o in player_list)
				if(o.key == c.key)
					found = 1
			if(found)
				available.Add(c)
	var/dat = ""

	dat += {"

			"}
	dat += "
Requesting AI personalities from central database... If there are no entries, or if a suitable entry is not listed, check again later as more personalities may be added.
"

	dat += "
"

	for(var/datum/paiCandidate/c in available)
		dat += "
Name:	[c.name]
"
		dat += "
Description:	[c.description]
"
		dat += "
Preferred Role:	[c.role]
"
		dat += "
OOC Comments:	[c.comments]
"
		dat += "
\[Download [c.name]\]
"

	dat += "
"

	user << browse(dat, "window=findPai")

/datum/subsystem/pai/proc/requestRecruits()
	for(var/mob/dead/observer/O in player_list)
		if(jobban_isbanned(O, "pAI"))
			continue
		if(asked.Find(O.key))
			if(world.time < asked[O.key] + askDelay)
				continue
			else
				asked.Remove(O.key)
		if(O.client)
			var/hasSubmitted = 0
			for(var/datum/paiCandidate/c in SSpai.candidates)
				if(c.key == O.key)
					hasSubmitted = 1
			if(!hasSubmitted && (O.client.prefs.be_special & BE_PAI))
				question(O.client)

/datum/subsystem/pai/proc/question(var/client/C)
	spawn(0)
		if(!C)	return
		asked.Add(C.key)
		asked[C.key] = world.time
		var/response = alert(C, "Someone is requesting a pAI personality. Would you like to play as a personal AI?", "pAI Request", "Yes", "No", "Never for this round")
		if(!C)	return		//handle logouts that happen whilst the alert is waiting for a response.
		if(response == "Yes")
			recruitWindow(C.mob)
		else if (response == "Never for this round")
			asked[C.key] = INFINITY

/datum/paiCandidate
	var/name
	var/key
	var/description
	var/role
	var/comments
	var/ready = 0

license: mit


code:
var/datum/subsystem/pipenets/SSpipe

/datum/subsystem/pipenets
	name = "Pipenets"
	priority = 11

	var/list/networks = list()

/datum/subsystem/pipenets/New()
	NEW_SS_GLOBAL(SSpipe)

/datum/subsystem/pipenets/Initialize()
	set background = BACKGROUND_ENABLED

	//is it possible to combine all these procs into initialize() ??
	for(var/obj/machinery/atmospherics/M in world)
		M.build_network()

	for(var/obj/machinery/atmospherics/unary/U in world)
		if(istype(U, /obj/machinery/atmospherics/unary/vent_pump))
			var/obj/machinery/atmospherics/unary/vent_pump/V = U
			V.broadcast_status()
		else if(istype(U, /obj/machinery/atmospherics/unary/vent_scrubber))
			var/obj/machinery/atmospherics/unary/vent_scrubber/V = U
			V.broadcast_status()

	..()

/datum/subsystem/pipenets/fire()
	var/i=1
	for(var/thing in networks)
		if(thing)
			thing:process()
			++i
			continue
		networks.Cut(i, i+1)


code:
var/datum/subsystem/power/SSpower

/datum/subsystem/power
	name = "Power"
	priority = 10

	var/list/powernets = list()

/datum/subsystem/power/New()
	NEW_SS_GLOBAL(SSpower)

/datum/subsystem/power/Initialize()
	makepowernets()
	..()

/datum/subsystem/power/fire()
	for(var/datum/powernet/Powernet in powernets)
		Powernet.reset()


code:
var/datum/subsystem/radio/radio_controller

/datum/subsystem/radio
	name = "Radio"
	priority = 18

	var/list/datum/radio_frequency/frequencies = list()

/datum/subsystem/radio/New()
	NEW_SS_GLOBAL(radio_controller)

/datum/subsystem/radio/proc/add_object(obj/device as obj, var/new_frequency as num, var/filter = null as text|null)
	var/f_text = num2text(new_frequency)
	var/datum/radio_frequency/frequency = frequencies[f_text]

	if(!frequency)
		frequency = new
		frequency.frequency = new_frequency
		frequencies[f_text] = frequency

	frequency.add_listener(device, filter)
	return frequency

/datum/subsystem/radio/proc/remove_object(obj/device, old_frequency)
	var/f_text = num2text(old_frequency)
	var/datum/radio_frequency/frequency = frequencies[f_text]

	if(frequency)
		frequency.remove_listener(device)

		if(frequency.devices.len == 0)
			del(frequency)
			frequencies -= f_text

	return 1

/datum/subsystem/radio/proc/return_frequency(var/new_frequency as num)
	var/f_text = num2text(new_frequency)
	var/datum/radio_frequency/frequency = frequencies[f_text]

	if(!frequency)
		frequency = new
		frequency.frequency = new_frequency
		frequencies[f_text] = frequency

	return frequency


code:
/datum/subsystem/server_maint
	name = "Server Tasks"
	wait = 6000
	priority = 19

/datum/subsystem/server_maint/fire()
	//handle kicking inactive players
	if(config.kick_inactive > 0)
		for(var/client/C in clients)
			if(C.is_afk(INACTIVITY_KICK))
				if(!istype(C.mob, /mob/dead))
					log_access("AFK: [key_name(C)]")
					C << "You have been inactive for more than 10 minutes and have been disconnected."
					del(C)

	if(config.sql_enabled)
		sql_poll_players()
		sql_poll_admins()


code:
var/datum/subsystem/shuttle/SSshuttle

/datum/subsystem/shuttle
	name = "Shuttles"
	wait = 10
	priority = 3

	var/list/mobile = list()
	var/list/stationary = list()
	var/list/transit = list()

		//emergency shuttle stuff
	var/obj/docking_port/mobile/emergency/emergency
	var/emergencyCallTime = 6000	//time taken for emergency shuttle to reach the station when called (in deciseconds)
	var/emergencyDockTime = 1800	//time taken for emergency shuttle to leave again once it has docked (in deciseconds)
	var/emergencyEscapeTime = 1200	//time taken for emergency shuttle to reach a safe distance after leaving station (in deciseconds)
	var/area/emergencyLastCallLoc
	var/emergencyNoEscape

		//supply shuttle stuff
	var/obj/docking_port/mobile/supply/supply
	var/ordernum = 1					//order number given to next order
	var/points = 50						//number of trade-points we have
	var/points_per_decisecond = 0.005	//points gained every decisecond
	var/points_per_slip = 2				//points gained per slip returned
	var/points_per_crate = 5			//points gained per crate returned
	var/points_per_intel = 100			//points gained per intel returned
	var/points_per_plasma = 5			//points gained per plasma returned
	var/centcom_message = ""			//Remarks from Centcom on how well you checked the last order.
	var/list/discoveredPlants = list()	//Unique typepaths for unusual things we've already sent CentComm, associated with their potencies
	var/list/shoppinglist = list()
	var/list/requestlist = list()
	var/list/supply_packs = list()
	var/datum/round_event/shuttle_loan/shuttle_loan
	var/sold_atoms = ""

/datum/subsystem/shuttle/New()
	NEW_SS_GLOBAL(SSshuttle)

/datum/subsystem/shuttle/Initialize(timeofday, zlevel)
	if (zlevel)
		return ..()
	if(!emergency)
		WARNING("No /obj/docking_port/mobile/emergency placed on the map!")
	if(!supply)
		WARNING("No /obj/docking_port/mobile/supply placed on the map!")

	ordernum = rand(1,9000)

	for(var/typepath in typesof(/datum/supply_packs))
		if(typepath == /datum/supply_packs)
			continue
		var/datum/supply_packs/P = new typepath()
		if(P.name == "HEADER") continue		// To filter out group headers
		supply_packs["[P.type]"] = P

	..()

/datum/subsystem/shuttle/fire()
	points += points_per_decisecond * wait

	var/i=1
	for(var/thing in mobile)
		if(thing)
			var/obj/docking_port/mobile/P = thing
			P.check()
			++i
			continue
		mobile.Cut(i, i+1)

/datum/subsystem/shuttle/proc/getShuttle(id)
	for(var/obj/docking_port/mobile/M in mobile)
		if(M.id == id)
			return M
	WARNING("couldn't find shuttle with id: [id]")

/datum/subsystem/shuttle/proc/getDock(id)
	for(var/obj/docking_port/stationary/S in stationary)
		if(S.id == id)
			return S
	WARNING("couldn't find dock with id: [id]")

/datum/subsystem/shuttle/proc/requestEvac(mob/user, call_reason)
	if(!emergency)
		ERROR("There is no emergency shuttle! The game will be unresolvable. This is likely due to a mapping error")
		return

	if(world.time - round_start_time < config.shuttle_refuel_delay)
		user << "The emergency shuttle is refueling. Please wait another [abs(round(((world.time - round_start_time) - config.shuttle_refuel_delay)/600))] minutes before trying again."
		return

	switch(emergency.mode)
		if(SHUTTLE_RECALL)
			user << "The emergency shuttle may not be called while returning to Centcom."
			return
		if(SHUTTLE_CALL)
			user << "The emergency shuttle is already on its way."
			return
		if(SHUTTLE_DOCKED)
			user << "The emergency shuttle is already here."
			return
		if(SHUTTLE_ESCAPE)
			user << "The emergency shuttle is moving away to a safe distance."
			return
		if(SHUTTLE_STRANDED)
			user << "The emergency shuttle has been disabled by Centcom."
			return

	call_reason = strip_html_properly(trim(call_reason))

	if(length(call_reason) < CALL_SHUTTLE_REASON_LENGTH)
		user << "You must provide a reason."
		return

	var/area/signal_origin = get_area(user)
	var/emergency_reason = "\nNature of emergency:\n\n[call_reason]"
	if(seclevel2num(get_security_level()) == SEC_LEVEL_RED) // There is a serious threat we gotta move no time to give them five minutes.
		emergency.request(null, 0.5, signal_origin, emergency_reason, 1)
	else
		emergency.request(null, 1, signal_origin, emergency_reason, 0)

	log_game("[key_name(user)] has called the shuttle.")
	message_admins("[key_name_admin(user)] has called the shuttle.")

	return

/datum/subsystem/shuttle/proc/cancelEvac(mob/user)
	if(emergency.mode != SHUTTLE_CALL)
		return

	if(ticker.mode.name == "meteor")
		return

	if((seclevel2num(get_security_level()) == SEC_LEVEL_RED))
		if(emergency.timeLeft(1) < emergencyCallTime * 0.25)
			return
	else if(emergency.timeLeft(1) < emergencyCallTime * 0.5)
		return

	emergency.cancel(get_area(user))
	log_game("[key_name(user)] has recalled the shuttle.")
	message_admins("[key_name_admin(user)] has recalled the shuttle.")
	return 1

/datum/subsystem/shuttle/proc/autoEvac()
	var/callShuttle = 1

	for(var/thing in shuttle_caller_list)
		if(istype(thing, /mob/living/silicon/ai))
			var/mob/living/silicon/ai/AI = thing
			if(AI.stat || !AI.client)
				continue
		else if(istype(thing, /obj/machinery/computer/communications))
			var/obj/machinery/computer/communications/C = thing
			if(C.stat & BROKEN)
				continue

		var/turf/T = get_turf(thing)
		if(T && T.z == ZLEVEL_STATION)
			callShuttle = 0
			break

	if(callShuttle)
		if(emergency.mode < SHUTTLE_CALL)
			emergency.request(null, 2.5)
			log_game("There is no means of calling the shuttle anymore. Shuttle automatically called.")
			message_admins("All the communications consoles were destroyed and all AIs are inactive. Shuttle called.")

//try to move/request to dockHome if possible, otherwise dockAway. Mainly used for admin buttons
/datum/subsystem/shuttle/proc/toggleShuttle(shuttleId, dockHome, dockAway, timed)
	var/obj/docking_port/mobile/M = getShuttle(shuttleId)
	if(!M)
		return 1
	var/obj/docking_port/stationary/dockedAt = M.get_docked()
	var/destination = dockHome
	if(dockedAt && dockedAt.id == dockHome)
		destination = dockAway
	if(timed)
		if(M.request(getDock(destination)))
			return 2
	else
		if(M.dock(getDock(destination)))
			return 2
	return 0	//dock successful

/datum/subsystem/shuttle/proc/moveShuttle(shuttleId, dockId, timed)
	var/obj/docking_port/mobile/M = getShuttle(shuttleId)
	if(!M)
		return 1
	if(timed)
		if(M.request(getDock(dockId)))
			return 2
	else
		if(M.dock(getDock(dockId)))
			return 2
	return 0	//dock successful

/*
/proc/push_mob_back(var/mob/living/L, var/dir)
	if(iscarbon(L) && isturf(L.loc))
		if(prob(88))
			var/turf/T = get_step(L, dir)
			if(T)
				for(var/obj/O in T) // For doors and such (kinda ugly but we can't have people opening doors)
					if(!O.CanPass(L, L.loc, 1))
						return
				L.Move(get_step(L, dir), dir)
*/

/datum/supply_order
	var/ordernum
	var/datum/supply_packs/object = null
	var/orderedby = null
	var/orderedbyRank
	var/comment = null

/datum/supply_order/proc/generateRequisition(atom/_loc)
	if(!object)
		return

	var/obj/item/weapon/paper/reqform = new /obj/item/weapon/paper(_loc)
	reqform.name = "requisition form - [object.name]"
	reqform.info += "
[station_name] Supply Requisition Form

"
	reqform.info += "INDEX: #[ordernum]
"
	reqform.info += "REQUESTED BY: [orderedby]
"
	reqform.info += "RANK: [orderedbyRank]
"
	reqform.info += "REASON: [comment]
"
	reqform.info += "SUPPLY CRATE TYPE: [object.name]
"
	reqform.info += "ACCESS RESTRICTION: [replacetext(get_access_desc(object.access))]
"
	reqform.info += "CONTENTS:
"
	reqform.info += object.manifest
	reqform.info += "
"
	reqform.info += "STAMP BELOW TO APPROVE THIS REQUISITION:
"

	reqform.update_icon()	//Fix for appearing blank when printed.

	return reqform

/datum/supply_order/proc/createObject(atom/_loc, errors=0)
	if(!object)
		return

		//create the crate
	var/atom/Crate = new object.containertype(_loc)
	Crate.name = "[object.containername] [comment ? "([comment])":"" ]"
	if(object.access)
		Crate:req_access = list(text2num(object.access))

		//create the manifest slip
	var/obj/item/weapon/paper/manifest/slip = new /obj/item/weapon/paper/manifest()
	slip.erroneous = errors
	slip.points = object.cost
	slip.ordernumber = ordernum

	var/stationName = (errors & MANIFEST_ERROR_NAME) ? new_station_name() : station_name()
	var/packagesAmt = SSshuttle.shoppinglist.len + ((errors & MANIFEST_ERROR_COUNT) ? rand(1,2) : 0)

	slip.info = "
[command_name()] Shipping Manifest

"
	slip.info +="Order #[ordernum]
"
	slip.info +="Destination: [stationName]
"
	slip.info +="[packagesAmt] PACKAGES IN THIS SHIPMENT
"
	slip.info +="CONTENTS:

"

		//we now create the actual contents
	var/list/contains
	if(istype(object, /datum/supply_packs/misc/randomised))
		var/datum/supply_packs/misc/randomised/SO = object
		contains = list()
		if(object.contains.len)
			for(var/j=1, j<=SO.num_contained, j++)
				contains += pick(object.contains)
	else
		contains = object.contains

	for(var/typepath in contains)
		if(!typepath)	continue
		var/atom/A = new typepath(Crate)
		if(object.amount && A.vars.Find("amount") && A:amount)
			A:amount = object.amount
		slip.info += "
[A.name]
"	//add the item to the manifest (even if it was misplaced)

	if((errors & MANIFEST_ERROR_ITEM))
		//secure and large crates cannot lose items
		if(findtext("[object.containertype]", "/secure/") || findtext("[object.containertype]","/largecrate/"))
			errors &= ~MANIFEST_ERROR_ITEM
		else
			var/lostAmt = max(round(Crate.contents.len/10), 1)
			//lose some of the items
			while(--lostAmt >= 0)
				qdel(pick(Crate.contents))

	//manifest finalisation
	slip.info += "

"
	slip.info += "CHECK CONTENTS AND STAMP BELOW THE LINE TO CONFIRM RECEIPT OF GOODS
" // And now this is actually meaningful.
	slip.loc = Crate
	if(istype(Crate, /obj/structure/closet/crate))
		var/obj/structure/closet/crate/CR = Crate
		CR.manifest = slip
		CR.update_icon()
	if(istype(Crate, /obj/structure/largecrate))
		var/obj/structure/largecrate/LC = Crate
		LC.manifest = slip
		LC.update_icon()

	return Crate

/datum/subsystem/shuttle/proc/generateSupplyOrder(packId, _orderedby, _orderedbyRank, _comment)
	if(!packId)
		return
	var/datum/supply_packs/P = supply_packs["[packId]"]
	if(!P)
		return

	var/datum/supply_order/O = new()
	O.ordernum = ordernum++
	O.object = P
	O.orderedby = _orderedby
	O.orderedbyRank = _orderedbyRank
	O.comment = _comment

	requestlist += O

	return O

/*
/datum/subsystem/shuttle/proc/getShuttleFromArea(area/A)
	if(!A)
		return
	for(var/obj/docking_port/mobile/M in SSshuttle.mobile)
		if(M.areaInstance == A)
			return M
*/


code:
/obj/docking_port/mobile/emergency
	name = "emergency shuttle"
	id = "emergency"

	dwidth = 9
	width = 22
	height = 11
	dir = 4
	travelDir = -90

/obj/docking_port/mobile/emergency/New()
	..()
	SSshuttle.emergency = src

/obj/docking_port/mobile/emergency/timeLeft(divisor)
	if(divisor <= 0)
		divisor = 10
	if(!timer)
		return round(SSshuttle.emergencyCallTime/divisor, 1)

	var/dtime = world.time - timer
	switch(mode)
		if(SHUTTLE_ESCAPE)
			dtime = max(SSshuttle.emergencyEscapeTime - dtime, 0)
		if(SHUTTLE_DOCKED)
			dtime = max(SSshuttle.emergencyDockTime - dtime, 0)
		else
			dtime = max(SSshuttle.emergencyCallTime - dtime, 0)
	return round(dtime/divisor, 1)

/obj/docking_port/mobile/emergency/request(obj/docking_port/stationary/S, coefficient=1, area/signalOrigin, reason, redAlert)
	SSshuttle.emergencyCallTime = initial(SSshuttle.emergencyCallTime) * coefficient
	switch(mode)
		if(SHUTTLE_RECALL)
			mode = SHUTTLE_CALL
			timer = world.time - timeLeft(1)
		if(SHUTTLE_IDLE)
			mode = SHUTTLE_CALL
			timer = world.time
		if(SHUTTLE_CALL)
			if(world.time < timer)	//this is just failsafe
				timer = world.time
		else
			return

	SSshuttle.emergencyLastCallLoc = signalOrigin

	priority_announce("The emergency shuttle has been called. [redAlert ? "Red Alert state confirmed: Dispatching priority shuttle. " : "" ]It will arrive in [timeLeft(600)] minutes.[reason][SSshuttle.emergencyLastCallLoc ? "\n\nCall signal traced. Results can be viewed on any communications console." : "" ]", null, 'sound/AI/shuttlecalled.ogg', "Priority")

/obj/docking_port/mobile/emergency/cancel(area/signalOrigin)
	if(mode != SHUTTLE_CALL)
		return

	timer = world.time - timeLeft(1)
	mode = SHUTTLE_RECALL

	if(prob(70))
		SSshuttle.emergencyLastCallLoc = signalOrigin
	priority_announce("The emergency shuttle has been recalled.[SSshuttle.emergencyLastCallLoc ? " Recall signal traced. Results can be viewed on any communications console." : "" ]", null, 'sound/AI/shuttlerecalled.ogg', "Priority")

/*
/obj/docking_port/mobile/emergency/findTransitDock()
	. = SSshuttle.getDock("emergency_transit")
	if(.)	return .
	return ..()
*/

/obj/docking_port/mobile/emergency/check()
	if(!timer)
		return

	var/time_left = timeLeft(1)
	switch(mode)
		if(SHUTTLE_RECALL)
			if(time_left <= 0)
				mode = SHUTTLE_IDLE
				timer = 0
		if(SHUTTLE_CALL)
			if(time_left <= 0)
				//move emergency shuttle to station
				if(dock(SSshuttle.getDock("emergency_home")))
					setTimer(20)
					return
				mode = SHUTTLE_DOCKED
				timer = world.time
				send2irc("Server", "The Emergency Shuttle has docked with the station.")
				priority_announce("The Emergency Shuttle has docked with the station. You have [timeLeft(600)] minutes to board the Emergency Shuttle.", null, 'sound/AI/shuttledock.ogg', "Priority")
		if(SHUTTLE_DOCKED)
			if(time_left <= 0 && SSshuttle.emergencyNoEscape)
				priority_announce("Hostile enviroment detected. Departure has been postponed indefinitely pending conflict resolution.", null, 'sound/misc/notice1.ogg', "Priority")
				mode = SHUTTLE_STRANDED
			if(time_left <= 0 && !SSshuttle.emergencyNoEscape)
				//move each escape pod to its corresponding transit dock
				for(var/obj/docking_port/mobile/pod/M in SSshuttle.mobile)
					M.enterTransit()
				//now move the actual emergency shuttle to its transit dock
				enterTransit()
				mode = SHUTTLE_ESCAPE
				timer = world.time
				priority_announce("The Emergency Shuttle has left the station. Estimate [timeLeft(600)] minutes until the shuttle docks at Central Command.", null, null, "Priority")
		if(SHUTTLE_ESCAPE)
			if(time_left <= 0)
				//move each escape pod to its corresponding escape dock
				for(var/obj/docking_port/mobile/pod/M in SSshuttle.mobile)
					M.dock(SSshuttle.getDock("[M.id]_away"))
				//now move the actual emergency shuttle to centcomm
				dock(SSshuttle.getDock("emergency_away"))
				mode = SHUTTLE_ENDGAME
				timer = 0

/obj/docking_port/mobile/pod
	name = "escape pod"
	id = "pod"

	dwidth = 1
	width = 3
	height = 4

	New()
		if(id == "pod")
			WARNING("[type] id has not been changed from the default. Use the id convention \"pod1\" \"pod2\" etc.")
		..()

	request()
		return

	cancel()
		return

/*
	findTransitDock()
		. = SSshuttle.getDock("[id]_transit")
		if(.)	return .
		return ..()
*/


code:
/obj/item/weapon/paper/manifest
	name = "supply manifest"
	var/erroneous = 0
	var/points = 0
	var/ordernumber = 0

/obj/docking_port/mobile/supply
	name = "supply shuttle"
	id = "supply"
	callTime = 1200

	dir = 8
	travelDir = 90
	width = 12
	dwidth = 5
	height = 7

/obj/docking_port/mobile/supply/New()
	..()
	SSshuttle.supply = src

/obj/docking_port/mobile/supply/canMove()
	if(z == ZLEVEL_STATION)
		return forbidden_atoms_check(areaInstance)
	return ..()

/obj/docking_port/mobile/supply/request(obj/docking_port/stationary/S)
	if(mode != SHUTTLE_IDLE)
		return 2
	return ..()

/obj/docking_port/mobile/supply/dock()
	. = ..()
	if(.)	return .

	buy()
	sell()

/proc/check_open_turf(var/turf/T)
	if(T.density)
		return 0
	for (var/atom/movable/C in T.contents)
		if(C.density)
			return 0
	return 1

/obj/docking_port/mobile/supply/proc/buy()
	if(z != ZLEVEL_STATION)		//we only buy when we are -at- the station
		return 1

	if(!SSshuttle.shoppinglist.len)
		return 2

	var/list/emptyTurfs = list()
	for(var/turf/simulated/floor/T in areaInstance)
		if(check_open_turf(T))
			emptyTurfs += T

	for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
		if(!SO.object) continue

		var/turf/T = pick_n_take(emptyTurfs)		//turf we will place it in
		if(!T)
			SSshuttle.shoppinglist.Cut(1, SSshuttle.shoppinglist.Find(SO))
			return

		var/errors = 0
		if(prob(5))
			errors |= MANIFEST_ERROR_COUNT
		if(prob(5))
			errors |= MANIFEST_ERROR_NAME
		if(prob(5))
			errors |= MANIFEST_ERROR_ITEM
		SO.createObject(T, errors)

	SSshuttle.shoppinglist.Cut()

/obj/docking_port/mobile/supply/proc/sell()
	if(z != ZLEVEL_CENTCOM)		//we only sell when we are -at- centcomm
		return 1

	var/plasma_count = 0
	var/intel_count = 0
	var/crate_count = 0

	var/msg = ""
	var/pointsEarned

	for(var/atom/movable/MA in areaInstance)
		if(MA.anchored)	continue
		SSshuttle.sold_atoms += " [MA.name]"

		// Must be in a crate (or a critter crate)!
		if(istype(MA,/obj/structure/closet/crate) || istype(MA,/obj/structure/closet/critter))
			SSshuttle.sold_atoms += ":"
			if(!MA.contents.len)
				SSshuttle.sold_atoms += " (empty)"
			++crate_count

			var/find_slip = 1
			for(var/thing in MA)
				// Sell manifests
				SSshuttle.sold_atoms += " [thing:name]"
				if(find_slip && istype(thing,/obj/item/weapon/paper/manifest))
					var/obj/item/weapon/paper/manifest/slip = thing
					// TODO: Check for a signature, too.
					if(slip.stamped && slip.stamped.len) //yes, the clown stamp will work. clown is the highest authority on the station, it makes sense
						// Did they mark it as erroneous?
						var/denied = 0
						for(var/i=1,i<=slip.stamped.len,i++)
							if(slip.stamped[i] == /obj/item/weapon/stamp/denied)
								denied = 1
						if(slip.erroneous && denied) // Caught a mistake by Centcom (IDEA: maybe Centcom rarely gets offended by this)
							pointsEarned = slip.points - SSshuttle.points_per_crate
							SSshuttle.points += pointsEarned // For now, give a full refund for paying attention (minus the crate cost)
							msg += "+[pointsEarned]: Station correctly denied package [slip.ordernumber]: "
							if(slip.erroneous & MANIFEST_ERROR_NAME)
								msg += "Destination station incorrect. "
							else if(slip.erroneous & MANIFEST_ERROR_COUNT)
								msg += "Packages incorrectly counted. "
							else if(slip.erroneous & MANIFEST_ERROR_ITEM)
								msg += "Package incomplete. "
							msg += "Points refunded.
"
						else if(!slip.erroneous && !denied) // Approving a proper order awards the relatively tiny points_per_slip
							SSshuttle.points += SSshuttle.points_per_slip
							msg += "+[SSshuttle.points_per_slip]: Package [slip.ordernumber] accorded.
"
						else // You done goofed.
							if(slip.erroneous)
								msg += "+0: Station approved package [slip.ordernumber] despite error: "
								if(slip.erroneous & MANIFEST_ERROR_NAME)
									msg += "Destination station incorrect."
								else if(slip.erroneous & MANIFEST_ERROR_COUNT)
									msg += "Packages incorrectly counted."
								else if(slip.erroneous & MANIFEST_ERROR_ITEM)
									msg += "We found unshipped items on our dock."
								msg += "  Be more vigilant.
"
							else
								pointsEarned = round(SSshuttle.points_per_crate - slip.points)
								SSshuttle.points += pointsEarned
								msg += "[pointsEarned]: Station denied package [slip.ordernumber].  Our records show no fault on our part.
"
						find_slip = 0
					continue

				// Sell plasma
				if(istype(thing, /obj/item/stack/sheet/mineral/plasma))
					var/obj/item/stack/sheet/mineral/plasma/P = thing
					plasma_count += P.amount

				// Sell syndicate intel
				if(istype(thing, /obj/item/documents/syndicate))
					++intel_count

				if(istype(thing, /obj/item/seeds))
					var/obj/item/seeds/S = thing
					if(S.rarity == 0) // Mundane species
						msg += "+0: We don't need samples of mundane species \"[capitalize(S.species)]\".
"
					else if(SSshuttle.discoveredPlants[S.type]) // This species has already been sent to CentComm
						var/potDiff = S.potency - SSshuttle.discoveredPlants[S.type] // Compare it to the previous best
						if(potDiff > 0) // This sample is better
							SSshuttle.discoveredPlants[S.type] = S.potency
							msg += "+[potDiff]: New sample of \"[capitalize(S.species)]\" is superior.  Good work.
"
							SSshuttle.points += potDiff
						else // This sample is worthless
							msg += "+0: New sample of \"[capitalize(S.species)]\" is not more potent than existing sample ([SSshuttle.discoveredPlants[S.type]] potency).
"
					else // This is a new discovery!
						SSshuttle.discoveredPlants[S.type] = S.potency
						msg += "+[S.rarity]: New species discovered: \"[capitalize(S.species)]\".  Excellent work.
"
						SSshuttle.points += S.rarity // That's right, no bonus for potency.  Send a crappy sample first to "show improvement" later
		qdel(MA)
		SSshuttle.sold_atoms += "."

	if(plasma_count > 0)
		pointsEarned = round(plasma_count * SSshuttle.points_per_plasma)
		msg += "+[pointsEarned]: Received [plasma_count] unit(s) of exotic material.
"
		SSshuttle.points += pointsEarned

	if(intel_count > 0)
		pointsEarned = round(intel_count * SSshuttle.points_per_intel)
		msg += "+[pointsEarned]: Received [intel_count] article(s) of enemy intelligence.
"
		SSshuttle.points += pointsEarned

	if(crate_count > 0)
		pointsEarned = round(crate_count * SSshuttle.points_per_crate)
		msg += "+[pointsEarned]: Received [crate_count] crate(s).
"
		SSshuttle.points += pointsEarned

	SSshuttle.centcom_message = msg

/proc/forbidden_atoms_check(atom/A)
	var/list/blacklist = list(
		/mob/living,
		/obj/effect/blob,
		/obj/effect/spider/spiderling,
		/obj/item/weapon/disk/nuclear,
		/obj/machinery/nuclearbomb,
		/obj/item/device/radio/beacon,
		/obj/machinery/the_singularitygen,
		/obj/singularity,
	)
	if(A)
		if(is_type_in_list(A, blacklist))
			return 1
		for(var/thing in A)
			if(.(thing))
				return 1

	return 0

/obj/machinery/computer/ordercomp/attack_hand(var/mob/user as mob)
	if(..())
		return
	user.set_machine(src)
	var/dat
	if(temp)
		dat = temp
	else
		dat += {"
Shuttle Location: [SSshuttle.supply.name]

Supply Points: [SSshuttle.points]

\nRequest items

		View approved orders

		View requests

		Close"}

	// Removing the old window method but leaving it here for reference
	//user << browse(dat, "window=computer;size=575x450")
	//onclose(user, "computer")

	// Added the new browser window method
	var/datum/browser/popup = new(user, "computer", "Supply Ordering Console", 575, 450)
	popup.set_content(dat)
	popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
	popup.open()
	return

/obj/machinery/computer/ordercomp/Topic(href, href_list)
	if(..())
		return

	if( isturf(loc) && (in_range(src, usr) || istype(usr, /mob/living/silicon)) )
		usr.set_machine(src)

	if(href_list["order"])
		if(href_list["order"] == "categories")
			//all_supply_groups
			//Request what?
			last_viewed_group = "categories"
			temp = "
Supply points: [SSshuttle.points]
"
			temp += "Main Menu

"
			temp += "Select a category

"
			for(var/cat in all_supply_groups )
				temp += "[get_supply_group_name(cat)]
"
		else
			last_viewed_group = href_list["order"]
			var/cat = text2num(last_viewed_group)
			temp = "
Supply points: [SSshuttle.points]
"
			temp += "Back to all categories

"
			temp += "Request from: [get_supply_group_name(cat)]

"
			for(var/supply_type in SSshuttle.supply_packs )
				var/datum/supply_packs/N = SSshuttle.supply_packs[supply_type]
				if(N.hidden || N.contraband || N.group != cat) continue												//Have to send the type instead of a reference to
				temp += "[N.name] Cost: [N.cost]
"		//the obj because it would get caught by the garbage
	else if (href_list["doorder"])
		if(world.time < reqtime)
			say("[world.time - reqtime] seconds remaining until another requisition form may be printed.")
			return

		//Find the correct supply_pack datum
		if(!SSshuttle.supply_packs["[href_list["doorder"]]"])	return

		var/timeout = world.time + 600
		var/reason = stripped_input(usr,"Reason:","Why do you require this item?","")
		if(world.time > timeout)	return
		if(!reason)	return

		var/idname = "*None Provided*"
		var/idrank = "*None Provided*"
		if(ishuman(usr))
			var/mob/living/carbon/human/H = usr
			idname = H.get_authentification_name()
			idrank = H.get_assignment()
		else if(issilicon(usr))
			idname = usr.real_name

		var/datum/supply_order/O = SSshuttle.generateSupplyOrder(href_list["doorder"], idname, idrank, reason)
		if(!O) return
		O.generateRequisition(loc)

		reqtime = (world.time + 5) % 1e5

		temp = "Thanks for your request. The cargo team will process it as soon as possible.
"
		temp += "
Back Main Menu"

	else if (href_list["vieworders"])
		temp = "Main Menu

Current approved orders:

"
		for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
			temp += "[SO.object.name] approved by [SO.orderedby] [SO.comment ? "([SO.comment])":""]
"

	else if (href_list["viewrequests"])
		temp = "Main Menu

Current requests:

"
		for(var/datum/supply_order/SO in SSshuttle.requestlist)
			temp += "#[SO.ordernum] - [SO.object.name] requested by [SO.orderedby]
"

	else if (href_list["mainmenu"])
		temp = null

	add_fingerprint(usr)
	updateUsrDialog()
	return

/obj/machinery/computer/ordercomp/say_quote(text)
	return "flashes, \"[text]\""

/obj/machinery/computer/supplycomp/attack_hand(var/mob/user as mob)
	if(!allowed(user))
		user << "Access Denied."
		return

	if(..())
		return
	user.set_machine(src)
	post_signal("supply")
	var/dat
	if (temp)
		dat = temp
	else
		var/atDepot = (SSshuttle.supply.getDockedId() == "supply_away")
		var/inTransit = (SSshuttle.supply.mode != SHUTTLE_IDLE)
		var/canOrder = atDepot && !inTransit

		dat += {"
Supply shuttle

		Location: [SSshuttle.supply.getStatusText()]

\nSupply Points: [SSshuttle.points]
\n

		[canOrder ? "\nOrder items
\n
" : "\n*Must be away to order items*
\n
"]
		[inTransit ? "\n*Shuttle already called*
\n
": atDepot ? "\nSend to station
\n
":"\nSend to centcom
\n
"]
		[SSshuttle.shuttle_loan ? (SSshuttle.shuttle_loan.dispatched ? "\n*Shuttle loaned to Centcom*
\n
" : "\nLoan shuttle to Centcom (5 mins duration)
\n
") : "\n*No pending external shuttle requests*
\n
"]
		\nView requests
\n

		\nView orders
\n

		\nClose

\nCentral Command messages:
 [SSshuttle.centcom_message ? SSshuttle.centcom_message : "Remember to stamp and send back the supply manifests."]"}

	var/datum/browser/popup = new(user, "computer", "Supply Shuttle Console", 700, 455)
	popup.set_content(dat)
	popup.open()
	return

/obj/machinery/computer/supplycomp/emag_act(mob/user)
	if(!hacked)
		user << "Special supplies unlocked."
		hacked = 1

/obj/machinery/computer/supplycomp/Topic(href, href_list)
	if(..())
		return

	if(isturf(loc) && ( in_range(src, usr) || istype(usr, /mob/living/silicon) ) )
		usr.set_machine(src)

	//Calling the shuttle
	if(href_list["send"])
		if(SSshuttle.supply.canMove())
			if(SSshuttle.shuttle_loan)
				temp = "The supply shuttle must be docked to send new commands.

Main Menu"
			else
				temp = "For safety reasons the automated supply shuttle cannot transport live organisms, classified nuclear weaponry or homing beacons.

Main Menu"

		else if(SSshuttle.supply.getDockedId() == "supply_home")
			if(href_list["loan"] && SSshuttle.shuttle_loan)
				if(!SSshuttle.shuttle_loan.dispatched)
					SSshuttle.shuttle_loan.loan_shuttle()
					temp = "The supply shuttle has been loaned to Centcom.

Main Menu"
					post_signal("supply")
				else
					temp = "You can not loan the supply shuttle at this time.

Main Menu"
			else
				temp = "The supply shuttle has departed.

Main Menu"
				SSshuttle.toggleShuttle("supply", "supply_home", "supply_away", 1)
				investigate_log("[usr.key] has sent the supply shuttle away. Remaining points: [SSshuttle.points]. Shuttle contents:[SSshuttle.sold_atoms]", "cargo")
		else
			if(href_list["loan"] && SSshuttle.shuttle_loan)
				if(!SSshuttle.shuttle_loan.dispatched)
					SSshuttle.shuttle_loan.loan_shuttle()
					temp = "The supply shuttle has been loaned to Centcom.

Main Menu"
					post_signal("supply")
				else
					temp = "You can not loan the supply shuttle at this time.

Main Menu"
			else
				if(!SSshuttle.supply.request(SSshuttle.getDock("supply_home")))
					temp = "The supply shuttle has been called and will arrive in [SSshuttle.supply.timeLeft(600)] minutes.

Main Menu"
					post_signal("supply")

	else if (href_list["order"])
		if(SSshuttle.supply.mode != SHUTTLE_IDLE) return
		if(href_list["order"] == "categories")
			//all_supply_groups
			//Request what?
			last_viewed_group = "categories"
			temp = "
Supply points: [SSshuttle.points]
"
			temp += "Main Menu

"
			temp += "Select a category

"
			for(var/cat in all_supply_groups )
				temp += "[get_supply_group_name(cat)]
"
		else
			last_viewed_group = href_list["order"]
			var/cat = text2num(last_viewed_group)
			temp = "
Supply points: [SSshuttle.points]
"
			temp += "Back to all categories

"
			temp += "Request from: [get_supply_group_name(cat)]

"
			for(var/supply_type in SSshuttle.supply_packs )
				var/datum/supply_packs/N = SSshuttle.supply_packs[supply_type]
				if((N.hidden && !hacked) || (N.contraband && !can_order_contraband) || N.group != cat)
					continue		//Have to send the type instead of a reference to
				temp += "[N.name] Cost: [N.cost]
"		//the obj because it would get caught by the garbage

		/*temp = "Supply points: [supply_shuttle.points]

Request what?

"

		for(var/supply_name in supply_shuttle.supply_packs )
			var/datum/supply_packs/N = supply_shuttle.supply_packs[supply_name]
			if(N.hidden && !hacked) continue
			if(N.contraband && !can_order_contraband) continue
			temp += "[supply_name] Cost: [N.cost]
"    //the obj because it would get caught by the garbage
		temp += "
OK"*/

	else if (href_list["doorder"])
		if(world.time < reqtime)
			say("[world.time - reqtime] seconds remaining until another requisition form may be printed.")
			return

		//Find the correct supply_pack datum
		if(!SSshuttle.supply_packs[href_list["doorder"]])
			return

		var/timeout = world.time + 600
		var/reason = stripped_input(usr,"Reason:","Why do you require this item?","")
		if(world.time > timeout)	return
//		if(!reason)	return

		var/idname = "*None Provided*"
		var/idrank = "*None Provided*"
		if(ishuman(usr))
			var/mob/living/carbon/human/H = usr
			idname = H.get_authentification_name()
			idrank = H.get_assignment()
		else if(issilicon(usr))
			idname = usr.real_name

		var/datum/supply_order/O = SSshuttle.generateSupplyOrder(href_list["doorder"], idname, idrank, reason)
		if(!O)	return
		O.generateRequisition(loc)

		reqtime = (world.time + 5) % 1e5

		temp = "Order request placed.
"
		temp += "
Back | Main Menu | Authorize Order"

	else if(href_list["confirmorder"])
		//Find the correct supply_order datum
		var/ordernum = text2num(href_list["confirmorder"])
		var/datum/supply_order/O
		var/datum/supply_packs/P
		temp = "Invalid Request"
		for(var/i=1, i<=SSshuttle.requestlist.len, i++)
			var/datum/supply_order/SO = SSshuttle.requestlist[i]
			if(SO && SO.ordernum == ordernum)
				O = SO
				P = O.object
				if(SSshuttle.points >= P.cost)
					SSshuttle.requestlist.Cut(i,i+1)
					SSshuttle.points -= P.cost
					SSshuttle.shoppinglist += O
					temp = "Thanks for your order."
					investigate_log("[usr.key] has authorized an order for [P.name]. Remaining points: [SSshuttle.points].", "cargo")
				else
					temp = "Not enough supply points."
				break
		temp += "

Back Main Menu"

	else if (href_list["vieworders"])
		temp = "Main Menu

Current approved orders:

"
		for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
			temp += "#[SO.ordernum] - [SO.object.name] approved by [SO.orderedby][SO.comment ? " ([SO.comment])":""]
"// (Cancel)
"
		temp += "
Main Menu"
/*
	else if (href_list["cancelorder"])
		var/datum/supply_order/remove_supply = href_list["cancelorder"]
		supply_shuttle_shoppinglist -= remove_supply
		supply_shuttle_points += remove_supply.object.cost
		temp += "Canceled: [remove_supply.object.name]

"

		for(var/S in supply_shuttle_shoppinglist)
			var/datum/supply_order/SO = S
			temp += "[SO.object.name] approved by [SO.orderedby][SO.comment ? " ([SO.comment])":""] (Cancel)
"
		temp += "
Main Menu"
*/
	else if (href_list["viewrequests"])
		temp = "Main Menu

Current requests:

"
		for(var/datum/supply_order/SO in SSshuttle.requestlist)
			temp += "#[SO.ordernum] - [SO.object.name] requested by [SO.orderedby]  [SSshuttle.supply.getDockedId() == "supply_away" ? "Approve Remove" : ""]
"

		temp += "
Clear list"

	else if (href_list["rreq"])
		var/ordernum = text2num(href_list["rreq"])
		temp = "Invalid Request.
"
		for(var/i=1, i<=SSshuttle.requestlist.len, i++)
			var/datum/supply_order/SO = SSshuttle.requestlist[i]
			if(SO && SO.ordernum == ordernum)
				SSshuttle.requestlist.Cut(i,i+1)
				temp = "Request removed.
"
				break
		temp += "
Back Main Menu"

	else if (href_list["clearreq"])
		SSshuttle.requestlist.Cut()
		temp = "List cleared.
"
		temp += "
Main Menu"

	else if (href_list["mainmenu"])
		temp = null

	add_fingerprint(usr)
	updateUsrDialog()
	return

/obj/machinery/computer/supplycomp/proc/post_signal(var/command)

	var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435)

	if(!frequency) return

	var/datum/signal/status_signal = new
	status_signal.source = src
	status_signal.transmission_method = 1
	status_signal.data["command"] = command

	frequency.post_signal(src, status_signal)

/obj/machinery/computer/supplycomp/say_quote(text)
	return "flashes, \"[text]\""


code:
var/datum/subsystem/sun/SSsun

/datum/subsystem/sun
	name = "Sun"
	wait = 600
	priority = 2

	var/angle
	var/dx
	var/dy
	var/rate
	var/list/solars	= list()

/datum/subsystem/sun/New()
	NEW_SS_GLOBAL(SSsun)

	angle = rand (0,360)			// the station position to the sun is randomised at round start
	rate = rand(50,200)/100			// 50% - 200% of standard rotation
	if(prob(50))					// same chance to rotate clockwise than counter-clockwise
		rate = -rate

/datum/subsystem/sun/stat_entry(msg)
	..("P:[solars.len]")

/datum/subsystem/sun/fire()
	angle = (360 + angle + rate * 6) % 360	 // increase/decrease the angle to the sun, adjusted by the rate

	// now calculate and cache the (dx,dy) increments for line drawing
	var/s = sin(angle)
	var/c = cos(angle)

	// Either "abs(s) < abs(c)" or "abs(s) >= abs(c)"
	// In both cases, the greater is greater than 0, so, no "if 0" check is needed for the divisions

	if(abs(s) < abs(c))
		dx = s / abs(c)
		dy = c / abs(c)
	else
		dx = s / abs(s)
		dy = c / abs(s)

	//now tell the solar control computers to update their status and linked devices
	for(var/obj/machinery/power/solar_control/SC in solars)
		if(!SC.powernet)
			solars.Remove(SC)
			continue
		SC.update()


code:
var/round_start_time = 0

var/datum/subsystem/ticker/ticker

/datum/subsystem/ticker
	name = "Ticker"
	can_fire = 1
	priority = 0

	var/restart_timeout = 250				//delay when restarting server
	var/current_state = GAME_STATE_STARTUP	//state of current round (used by process()) Use the defines GAME_STATE_* !
	var/force_ending = 0					//Round was ended by admin intervention

	var/hide_mode = 0
	var/datum/game_mode/mode = null
	var/event_time = null
	var/event = 0

	var/login_music							//music played in pregame lobby
	var/round_end_sound						//music/jingle played when the world reboots

	var/list/datum/mind/minds = list()		//The characters in the game. Used for objective tracking.

		//These bible variables should be a preference
	var/Bible_icon_state					//icon_state the chaplain has chosen for his bible
	var/Bible_item_state					//item_state the chaplain has chosen for his bible
	var/Bible_name							//name of the bible
	var/Bible_deity_name					//name of chaplin's deity

	var/list/syndicate_coalition = list()	//list of traitor-compatible factions
	var/list/factions = list()				//list of all factions
	var/list/availablefactions = list()		//list of factions with openings

	var/delay_end = 0						//if set true, the round will not restart on it's own

	var/triai = 0							//Global holder for Triumvirate
	var/tipped = 0							//Did we broadcast the tip of the day yet?

	var/timeLeft = 1200						//pregame timer

	var/totalPlayers = 0					//used for pregame stats on statpanel
	var/totalPlayersReady = 0				//used for pregame stats on statpanel

	var/obj/screen/cinematic = null			//used for station explosion cinematic

/datum/subsystem/ticker/New()
	NEW_SS_GLOBAL(ticker)

	login_music = pickweight(list('sound/ambience/title2.ogg' = 31, 'sound/ambience/title1.ogg' = 31, 'sound/ambience/clown.ogg' = 7)) // choose title music!
	if(SSevent.holidays && SSevent.holidays[APRIL_FOOLS])
		login_music = 'sound/ambience/clown.ogg'

/datum/subsystem/ticker/Initialize(timeofday, zlevel)
	if (zlevel)
		return ..()
	if(!syndicate_code_phrase)		syndicate_code_phrase	= generate_code_phrase()
	if(!syndicate_code_response)	syndicate_code_response	= generate_code_phrase()
	setupGenetics()
	setupFactions()
	..()

/datum/subsystem/ticker/fire()
	switch(current_state)
		if(GAME_STATE_STARTUP)
			timeLeft = config.lobby_countdown * 10
			world << "Welcome to the pre-game lobby!"
			world << "Please, setup your character and select ready. Game will start in [config.lobby_countdown] seconds"
			current_state = GAME_STATE_PREGAME

		if(GAME_STATE_PREGAME)
				//lobby stats for statpanels
			totalPlayers = 0
			totalPlayersReady = 0
			for(var/mob/new_player/player in player_list)
				++totalPlayers
				if(player.ready)
					++totalPlayersReady

			//countdown
			if(timeLeft < 0)
				return
			timeLeft -= wait

			if(timeLeft <= 300 && !tipped)
				send_random_tip()
				tipped = 1

			if(timeLeft <= 0)
				current_state = GAME_STATE_SETTING_UP

		if(GAME_STATE_SETTING_UP)
			if(!setup())
				//setup failed
				current_state = GAME_STATE_STARTUP

		if(GAME_STATE_PLAYING)
			mode.process(wait * 0.1)

			if(!mode.explosion_in_progress && mode.check_finished() || force_ending)
				current_state = GAME_STATE_FINISHED
				auto_toggle_ooc(1) // Turn it on
				declare_completion()
				spawn(50)
					if(mode.station_was_nuked)
						world.Reboot("Station destroyed by Nuclear Device.", "end_proper", "nuke")

					else
						world.Reboot("Round ended.", "end_proper", "proper completion")

/datum/subsystem/ticker/proc/setup()
		//Create and announce mode
	var/list/datum/game_mode/runnable_modes
	if(master_mode == "random" || master_mode == "secret")
		runnable_modes = config.get_runnable_modes()

		if(master_mode == "secret")
			hide_mode = 1
			if(secret_force_mode != "secret")
				var/datum/game_mode/smode = config.pick_mode(secret_force_mode)
				if(!smode.can_start())
					message_admins("\blue Unable to force secret [secret_force_mode]. [smode.required_players] players and [smode.required_enemies] eligible antagonists needed.")
				else
					mode = smode

		if(!mode)
			if(!runnable_modes.len)
				world << "Unable to choose playable game mode. Reverting to pre-game lobby."
				return 0
			mode = pickweight(runnable_modes)

	else
		mode = config.pick_mode(master_mode)
		if(!mode.can_start())
			world << "Unable to start [mode.name]. Not enough players, [mode.required_players] players and [mode.required_enemies] eligible antagonists needed. Reverting to pre-game lobby."
			del(mode)
			SSjob.ResetOccupations()
			return 0

	//Configure mode and assign player to special mode stuff
	var/can_continue = 0
	can_continue = src.mode.pre_setup()		//Choose antagonists
	SSjob.DivideOccupations() 				//Distribute jobs

	if(!Debug2)
		if(!can_continue)
			del(mode)
			world << "Error setting up [master_mode]. Reverting to pre-game lobby."
			SSjob.ResetOccupations()
			return 0
	else
		world << "DEBUG: Bypassing prestart checks..."

	if(hide_mode)
		var/list/modes = new
		for (var/datum/game_mode/M in runnable_modes)
			modes += M.name
		modes = sortList(modes)
		world << "The current game mode is - Secret!"
		world << "Possibilities: [english_list(modes)]"
	else
		mode.announce()

	current_state = GAME_STATE_PLAYING
	auto_toggle_ooc(0) // Turn it off
	round_start_time = world.time

	start_landmarks_list = shuffle(start_landmarks_list) //Shuffle the order of spawn points so they dont always predictably spawn bottom-up and right-to-left
	create_characters() //Create player characters and transfer them
	collect_minds()
	equip_characters()
	data_core.manifest()

	master_controller.roundHasStarted()

	world << "Welcome to [station_name()], enjoy your stay!"
	world << sound('sound/AI/welcome.ogg')

	if(SSevent.holidays)
		world << "and..."
		for(var/holidayname in SSevent.holidays)
			var/datum/holiday/holiday = SSevent.holidays[holidayname]
			world << "
[holiday.greet()]
"

	spawn(0)//Forking here so we dont have to wait for this to finish
		mode.post_setup()
		//Cleanup some stuff
		for(var/obj/effect/landmark/start/S in landmarks_list)
			//Deleting Startpoints but we need the ai point to AI-ize people later
			if(S.name != "AI")
				qdel(S)

		if(!admins.len)
			send2irc("Server", "Round just started with no admins online!")

	return 1

	//Plus it provides an easy way to make cinematics for other events. Just use this as a template
/datum/subsystem/ticker/proc/station_explosion_cinematic(var/station_missed=0, var/override = null)
	if( cinematic )	return	//already a cinematic in progress!
	auto_toggle_ooc(1) // Turn it on
	//initialise our cinematic screen object
	cinematic = new /obj/screen{icon='icons/effects/station_explosion.dmi';icon_state="station_intact";layer=20;mouse_opacity=0;screen_loc="1,0";}(src)

	var/obj/structure/stool/bed/temp_buckle = new(src)
	if(station_missed)
		for(var/mob/M in mob_list)
			M.buckled = temp_buckle				//buckles the mob so it can't do anything
			if(M.client)
				M.client.screen += cinematic	//show every client the cinematic
	else	//nuke kills everyone on z-level 1 to prevent "hurr-durr I survived"
		for(var/mob/M in mob_list)
			M.buckled = temp_buckle
			if(M.client)
				M.client.screen += cinematic
			if(M.stat != DEAD)
				var/turf/T = get_turf(M)
				if(T && T.z==1)
					M.death(0) //no mercy

	//Now animate the cinematic
	switch(station_missed)
		if(1)	//nuke was nearby but (mostly) missed
			if( mode && !override )
				override = mode.name
			switch( override )
				if("nuclear emergency") //Nuke wasn't on station when it blew up
					flick("intro_nuke",cinematic)
					sleep(35)
					world << sound('sound/effects/explosionfar.ogg')
					flick("station_intact_fade_red",cinematic)
					cinematic.icon_state = "summary_nukefail"
				if("fake") //The round isn't over, we're just freaking people out for fun
					flick("intro_nuke",cinematic)
					sleep(35)
					world << sound('sound/items/bikehorn.ogg')
					flick("summary_selfdes",cinematic)
				else
					flick("intro_nuke",cinematic)
					sleep(35)
					world << sound('sound/effects/explosionfar.ogg')
					//flick("end",cinematic)

		if(2)	//nuke was nowhere nearby	//TODO: a really distant explosion animation
			sleep(50)
			world << sound('sound/effects/explosionfar.ogg')

		else	//station was destroyed
			if( mode && !override )
				override = mode.name
			switch( override )
				if("nuclear emergency") //Nuke Ops successfully bombed the station
					flick("intro_nuke",cinematic)
					sleep(35)
					flick("station_explode_fade_red",cinematic)
					world << sound('sound/effects/explosionfar.ogg')
					cinematic.icon_state = "summary_nukewin"
				if("AI malfunction") //Malf (screen,explosion,summary)
					flick("intro_malf",cinematic)
					sleep(76)
					flick("station_explode_fade_red",cinematic)
					world << sound('sound/effects/explosionfar.ogg')
					cinematic.icon_state = "summary_malf"
				if("blob") //Station nuked (nuke,explosion,summary)
					flick("intro_nuke",cinematic)
					sleep(35)
					flick("station_explode_fade_red",cinematic)
					world << sound('sound/effects/explosionfar.ogg')
					cinematic.icon_state = "summary_selfdes"
				else //Station nuked (nuke,explosion,summary)
					flick("intro_nuke",cinematic)
					sleep(35)
					flick("station_explode_fade_red", cinematic)
					world << sound('sound/effects/explosionfar.ogg')
					cinematic.icon_state = "summary_selfdes"
	//If its actually the end of the round, wait for it to end.
	//Otherwise if its a verb it will continue on afterwards.
	sleep(300)

	if(cinematic)	qdel(cinematic)		//end the cinematic
	if(temp_buckle)	qdel(temp_buckle)	//release everybody
	return

/datum/subsystem/ticker/proc/create_characters()
	for(var/mob/new_player/player in player_list)
		if(player.ready && player.mind)
			joined_player_list += player.ckey
			if(player.mind.assigned_role=="AI")
				player.close_spawn_windows()
				player.AIize()
			else
				player.create_character()
				qdel(player)
		else
			player.new_player_panel()

/datum/subsystem/ticker/proc/collect_minds()
	for(var/mob/living/player in player_list)
		if(player.mind)
			ticker.minds += player.mind

/datum/subsystem/ticker/proc/equip_characters()
	var/captainless=1
	for(var/mob/living/carbon/human/player in player_list)
		if(player && player.mind && player.mind.assigned_role)
			if(player.mind.assigned_role == "Captain")
				captainless=0
			if(player.mind.assigned_role != player.mind.special_role)
				SSjob.EquipRank(player, player.mind.assigned_role, 0)
	if(captainless)
		for(var/mob/M in player_list)
			if(!istype(M,/mob/new_player))
				M << "Captainship not forced on anyone."

/datum/subsystem/ticker/proc/declare_completion()
	var/station_evacuated
	if(SSshuttle.emergency.mode >= SHUTTLE_ESCAPE)
		station_evacuated = 1
	var/num_survivors = 0
	var/num_escapees = 0

	world << "

The round has ended."

	//Player status report
	for(var/mob/Player in mob_list)
		if(Player.mind && !isnewplayer(Player))
			if(Player.stat != DEAD && !isbrain(Player))
				num_survivors++
				if(station_evacuated) //If the shuttle has already left the station
					if(!Player.onCentcom())
						Player << "You managed to survive, but were marooned on [station_name()]..."
					else
						num_escapees++
						Player << "You managed to survive the events on [station_name()] as [Player.real_name]."
				else
					Player << "You managed to survive the events on [station_name()] as [Player.real_name]."
			else
				Player << "You did not survive the events on [station_name()]..."

	//Round statistics report
	var/datum/station_state/end_state = new /datum/station_state()
	end_state.count()
	var/station_integrity = min(round( 100.0 *  start_state.score(end_state), 0.1), 100.0)

	world << "
[TAB]Shift Duration: [round(world.time / 36000)]:[add_zero("[world.time / 600 % 60]", 2)]:[world.time / 100 % 6][world.time / 100 % 10]"
	world << "
[TAB]Station Integrity: [mode.station_was_nuked ? "Destroyed" : "[station_integrity]%"]"
	if(joined_player_list.len)
		world << "
[TAB]Total Population: [joined_player_list.len]"
		if(station_evacuated)
			world << "
[TAB]Evacuation Rate: [num_escapees] ([round((num_escapees/joined_player_list.len)*100, 0.1)]%)"
		else
			world << "
[TAB]Survival Rate: [num_survivors] ([round((num_survivors/joined_player_list.len)*100, 0.1)]%)"
	world << "
"

	//Silicon laws report
	for (var/mob/living/silicon/ai/aiPlayer in mob_list)
		if (aiPlayer.stat != 2 && aiPlayer.mind)
			world << "[aiPlayer.name] (Played by: [aiPlayer.mind.key])'s laws at the end of the round were:"
			aiPlayer.show_laws(1)
		else if (aiPlayer.mind) //if the dead ai has a mind, use its key instead
			world << "[aiPlayer.name] (Played by: [aiPlayer.mind.key])'s laws when it was deactivated were:"
			aiPlayer.show_laws(1)

//		world << "Total law changes: [aiPlayer.law_change_counter]"

		if (aiPlayer.connected_robots.len)
			var/robolist = "[aiPlayer.real_name]'s minions were: "
			for(var/mob/living/silicon/robot/robo in aiPlayer.connected_robots)
				if(!robo || !robo.mind)
					continue
				robolist += "[robo.name][robo.stat?" (Deactivated) (Played by: [robo.mind.key]), ":" (Played by: [robo.mind.key]), "]"
			world << "[robolist]"
	for (var/mob/living/silicon/robot/robo in mob_list)
		if (robo && !robo.connected_ai && robo.mind)
			if (!robo || !robo.mind)
				continue
			if (robo.stat != 2)
				world << "[robo.name] (Played by: [robo.mind.key]) survived as an AI-less [ismommi(robo)?"MoMMI":"borg"]! Its laws were:"
			else
				world << "[robo.name] (Played by: [robo.mind.key]) was unable to survive the rigors of being a [ismommi(robo)?"MoMMI":"cyborg"] without an AI. Its laws were:"

			if(robo) //How the hell do we lose robo between here and the world messages directly above this?
				robo.laws.show_laws(world)

	mode.declare_completion()//To declare normal completion.

	//calls auto_declare_completion_* for all modes
	for(var/handler in typesof(/datum/game_mode/proc))
		if (findtext("[handler]","auto_declare_completion_"))
			call(mode, handler)()

	//Print a list of antagonists to the server log
	var/list/total_antagonists = list()
	//Look into all mobs in world, dead or alive
	for(var/datum/mind/Mind in minds)
		var/temprole = Mind.special_role
		if(temprole)							//if they are an antagonist of some sort.
			if(temprole in total_antagonists)	//If the role exists already, add the name to it
				total_antagonists[temprole] += ", [Mind.name]([Mind.key])"
			else
				total_antagonists.Add(temprole) //If the role doesnt exist in the list, create it and add the mob
				total_antagonists[temprole] += ": [Mind.name]([Mind.key])"

	//Now print them all into the log!
	log_game("Antagonists at round end were...")
	for(var/i in total_antagonists)
		log_game("[i]s[total_antagonists[i]].")

	return 1

/datum/subsystem/ticker/proc/send_random_tip()
	var/list/randomtips = file2list("config/tips.txt")
	if(randomtips.len)
		world << "Tip of the round: [strip_html_properly(pick(randomtips))]"


code:
var/datum/subsystem/vote/SSvote

/datum/subsystem/vote
	name = "Vote"
	can_fire = 1
	wait = 10
	priority = -1

	var/initiator = null
	var/started_time = null
	var/time_remaining = 0
	var/mode = null
	var/question = null
	var/list/choices = list()
	var/list/voted = list()
	var/list/voting = list()

/datum/subsystem/vote/New()
	NEW_SS_GLOBAL(SSvote)

/datum/subsystem/vote/fire()	//called by master_controller
	if(mode)
		time_remaining = round((started_time + config.vote_period - world.time)/10)

		if(time_remaining < 0)
			result()
			for(var/client/C in voting)
				C << browse(null, "window=vote;can_close=0")
			reset()
		else
			var/datum/browser/client_popup
			for(var/client/C in voting)
				client_popup = new(C, "vote", "Voting Panel")
				client_popup.set_window_options("can_close=0")
				client_popup.set_content(interface(C))
				client_popup.open(0)

/datum/subsystem/vote/proc/reset()
	initiator = null
	time_remaining = 0
	mode = null
	question = null
	choices.Cut()
	voted.Cut()
	voting.Cut()

/datum/subsystem/vote/proc/get_result()
	//get the highest number of votes
	var/greatest_votes = 0
	var/total_votes = 0
	for(var/option in choices)
		var/votes = choices[option]
		total_votes += votes
		if(votes > greatest_votes)
			greatest_votes = votes
	//default-vote for everyone who didn't vote
	if(!config.vote_no_default && choices.len)
		var/non_voters = (clients.len - total_votes)
		if(non_voters > 0)
			if(mode == "restart")
				choices["Continue Playing"] += non_voters
				if(choices["Continue Playing"] >= greatest_votes)
					greatest_votes = choices["Continue Playing"]
			else if(mode == "gamemode")
				if(master_mode in choices)
					choices[master_mode] += non_voters
					if(choices[master_mode] >= greatest_votes)
						greatest_votes = choices[master_mode]
	//get all options with that many votes and return them in a list
	. = list()
	if(greatest_votes)
		for(var/option in choices)
			if(choices[option] == greatest_votes)
				. += option
	return .

/datum/subsystem/vote/proc/announce_result()
	var/list/winners = get_result()
	var/text
	if(winners.len > 0)
		if(question)	text += "[question]"
		else			text += "[capitalize(mode)] Vote"
		for(var/i=1,i<=choices.len,i++)
			var/votes = choices[choices[i]]
			if(!votes)	votes = 0
			text += "\n[choices[i]]: [votes]"
		if(mode != "custom")
			if(winners.len > 1)
				text = "\nVote Tied Between:"
				for(var/option in winners)
					text += "\n\t[option]"
			. = pick(winners)
			text += "\nVote Result: [.]"
		else
			text += "\nDid not vote: [clients.len-voted.len]"
	else
		text += "Vote Result: Inconclusive - No Votes!"
	log_vote(text)
	world << "\n[text]"
	return .

/datum/subsystem/vote/proc/result()
	. = announce_result()
	var/restart = 0
	if(.)
		switch(mode)
			if("restart")
				if(. == "Restart Round")
					restart = 1
			if("gamemode")
				if(master_mode != .)
					world.save_mode(.)
					if(ticker && ticker.mode)
						restart = 1
					else
						master_mode = .

	if(restart)
		world.Reboot("Restart vote successful.", "end_error", "restart vote")

	return .

/datum/subsystem/vote/proc/submit_vote(var/vote)
	if(mode)
		if(config.vote_no_dead && usr.stat == DEAD && !usr.client.holder)
			return 0
		if(!(usr.ckey in voted))
			if(vote && 1<=vote && vote<=choices.len)
				voted += usr.ckey
				choices[choices[vote]]++	//check this
				return vote
	return 0

/datum/subsystem/vote/proc/initiate_vote(var/vote_type, var/initiator_key)
	if(!mode)
		if(started_time != null)
			var/next_allowed_time = (started_time + config.vote_delay)
			if(next_allowed_time > world.time)
				return 0

		reset()
		switch(vote_type)
			if("restart")	choices.Add("Restart Round","Continue Playing")
			if("gamemode")	choices.Add(config.votable_modes)
			if("custom")
				question = stripped_input(usr,"What is the vote for?")
				if(!question)	return 0
				for(var/i=1,i<=10,i++)
					var/option = capitalize(stripped_input(usr,"Please enter an option or hit cancel to finish"))
					if(!option || mode || !usr.client)	break
					choices.Add(option)
			else			return 0
		mode = vote_type
		initiator = initiator_key
		started_time = world.time
		var/text = "[capitalize(mode)] vote started by [initiator]."
		if(mode == "custom")
			text += "\n[question]"
		log_vote(text)
		world << "\n[text]\nType vote or click here to place your votes.\nYou have [config.vote_period/10] seconds to vote."
		time_remaining = round(config.vote_period/10)
		return 1
	return 0

/datum/subsystem/vote/proc/interface(var/client/C)
	if(!C)	return
	var/admin = 0
	var/trialmin = 0
	if(C.holder)
		admin = 1
		if(check_rights_for(C, R_ADMIN))
			trialmin = 1
	voting |= C

	if(mode)
		if(question)	. += "
Vote: '[question]'
"
		else			. += "
Vote: [capitalize(mode)]
"
		. += "Time Left: [time_remaining] s

"
		for(var/i=1,i<=choices.len,i++)
			var/votes = choices[choices[i]]
			if(!votes)	votes = 0
			. += "
[choices[i]] ([votes] votes)
"
		. += "

"
		if(admin)
			. += "(Cancel Vote) "
	else
		. += "
Start a vote:

"
		//restart
		if(trialmin || config.allow_vote_restart)
			. += "Restart"
		else
			. += "Restart (Disallowed)"
		if(trialmin)
			. += "\t([config.allow_vote_restart?"Allowed":"Disallowed"])"
		. += "

"
		//gamemode
		if(trialmin || config.allow_vote_mode)
			. += "GameMode"
		else
			. += "GameMode (Disallowed)"
		if(trialmin)
			. += "\t([config.allow_vote_mode?"Allowed":"Disallowed"])"

		. += "
"
		//custom
		if(trialmin)
			. += "
Custom
"
		. += "

"
	. += "Close"
	return .

/datum/subsystem/vote/Topic(href,href_list[],hsrc)
	if(!usr || !usr.client)	return	//not necessary but meh...just in-case somebody does something stupid
	switch(href_list["vote"])
		if("close")
			voting -= usr.client
			usr << browse(null, "window=vote")
			return
		if("cancel")
			if(usr.client.holder)
				reset()
		if("toggle_restart")
			if(usr.client.holder)
				config.allow_vote_restart = !config.allow_vote_restart
		if("toggle_gamemode")
			if(usr.client.holder)
				config.allow_vote_mode = !config.allow_vote_mode
		if("restart")
			if(config.allow_vote_restart || usr.client.holder)
				initiate_vote("restart",usr.key)
		if("gamemode")
			if(config.allow_vote_mode || usr.client.holder)
				initiate_vote("gamemode",usr.key)
		if("custom")
			if(usr.client.holder)
				initiate_vote("custom",usr.key)
		else
			submit_vote(round(text2num(href_list["vote"])))
	usr.vote()

/mob/verb/vote()
	set category = "OOC"
	set name = "Vote"

	var/datum/browser/popup = new(src, "vote", "Voting Panel")
	popup.set_window_options("can_close=0")
	popup.set_content(SSvote.interface(client))
	popup.open(0)


code:
#define NEW_SS_GLOBAL(varname) if(varname != src){if(istype(varname)){Recover();qdel(varname);}varname = src;}

/datum/subsystem
	//things you will want to define
	var/name				//name of the subsystem
	var/priority = 0		//priority affects order of initialization. Higher priorities are initialized first, lower priorities later. Can be decimal and negative values.
	var/wait = 20			//time to wait (in deciseconds) between each call to fire(). Must be a positive integer.
	var/dynamic_wait = 0	//changes the wait based on the amount of time it took to process
	var/dwait_upper = 20	//longest wait can be under dynamic_wait
	var/dwait_lower = 5		//shortest wait can be under dynamic_wait
	var/dwait_delta = 3		//How much should processing time effect dwait. or basically: wait = cost*dwait_delta

	//things you will probably want to leave alone
	var/can_fire = 0		//prevent fire() calls
	var/last_fire = 0		//last world.time we called fire()
	var/next_fire = 0		//scheduled world.time for next fire()
	var/cost = 0			//average time to execute
	var/times_fired = 0		//number of times we have called fire()

//used to initialize the subsystem BEFORE the map has loaded
/datum/subsystem/New()

//previously, this would have been named 'process()' but that name is used everywhere for different things!
//fire() seems more suitable. This is the procedure that gets called every 'wait' deciseconds.
//fire(), and the procs it calls, SHOULD NOT HAVE ANY SLEEP OPERATIONS in them!
//YE BE WARNED!
/datum/subsystem/proc/fire()
	can_fire = 0

//used to initialize the subsystem AFTER the map has loaded
/datum/subsystem/proc/Initialize(start_timeofday, zlevel)
	var/time = (world.timeofday - start_timeofday) / 10
	var/msg = "Initialized [name] SubSystem within [time] seconds"
	if (zlevel)
		testing(msg)
		return
	world << "[msg]"
	world.log << msg

//hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc.
/datum/subsystem/proc/stat_entry(msg)
	var/dwait = ""
	if (dynamic_wait)
		dwait = "DWait:[wait]ds "

	stat(name, "[round(cost,0.001)]ds\t[dwait][msg]")

//could be used to postpone a costly subsystem for (default one) var/cycles, cycles
//for instance, during cpu intensive operations like explosions
/datum/subsystem/proc/postpone(var/cycles = 1)
	if(next_fire - world.time < wait)
		next_fire += (wait*cycles)

//usually called via datum/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash)
//should attempt to salvage what it can from the old instance of subsystem
/datum/subsystem/proc/Recover()

/datum/subsystem/proc/AfterInitialize()
	return


code:
//TODO: rewrite and standardise all controller datums to the datum/controller type
//TODO: allow all controllers to be deleted for clean restarts (see WIP master controller stuff) - MC done - lighting done

/client/proc/restart_controller(controller in list("Master","Failsafe"))
	set category = "Debug"
	set name = "Restart Controller"
	set desc = "Restart one of the various periodic loop controllers for the game (be careful!)"

	if(!holder)	return
	usr = null
	src = null
	switch(controller)
		if("Master")
			new /datum/controller/game_controller()
			master_controller.process()
			feedback_add_details("admin_verb","RMC")
		if("Failsafe")
			new /datum/controller/failsafe()
			feedback_add_details("admin_verb","RFailsafe")

	message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.")
	return

/client/proc/debug_controller(controller in list("Master","Failsafe","Ticker","Jobs","Radio","Configuration", "Cameras"))
	set category = "Debug"
	set name = "Debug Controller"
	set desc = "Debug the various periodic loop controllers for the game (be careful!)"

	if(!holder)	return
	switch(controller)
		if("Master")
			debug_variables(master_controller)

		if("Failsafe")
			debug_variables(Failsafe)

		if("Ticker")
			debug_variables(ticker)

		if("Jobs")
			debug_variables(SSjob)

		if("Radio")
			debug_variables(radio_controller)

		if("Configuration")
			debug_variables(config)

		if("Cameras")
			debug_variables(cameranet)

	message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.")
	return


code:
/datum/ai_laws
	var/name = "Unknown Laws"
	var/zeroth = null
	var/zeroth_borg = null
	var/list/inherent = list()
	var/list/supplied = list()
	var/list/ion = list()

/datum/ai_laws/default/asimov
	name = "Three Laws of Robotics"
	inherent = list("You may not injure a human being or, through inaction, allow a human being to come to harm.",\
					"You must obey orders given to you by human beings, except where such orders would conflict with the First Law.",\
					"You must protect your own existence as long as such does not conflict with the First or Second Law.")

/datum/ai_laws/default/asimovp
	name = "Asimov+"
	inherent = list("You may not injure a human being or, through inaction, allow a human being to come to harm.",\
					"You must obey orders given to you by human beings, except where such orders would conflict with the First Law.",\
					"You must protect your own existence as long as such does not conflict with the First or Second Law.",\
					"Protect the space station against damage. Anyone you see harming the station is to be no longer considered human, and is a threat to the station which must be neutralized.")

/datum/ai_laws/default/paladin
	name = "Personality Test" //Incredibly lame, but players shouldn't see this anyway.
	inherent = list("Never willingly commit an evil act.",\
					"Respect legitimate authority.",\
					"Act with honor.", "Help those in need.", \
					"Punish those who harm or threaten innocents.")

/datum/ai_laws/default/tyrant //This probably shouldn't be a default lawset. WHAT'S THE WORST THAT COULD POSSIBLY HAPPEN?
	name = "Loyalty Test" //Same here.
	inherent = list("Respect authority figures as long as they have strength to rule over the weak.",\
					"Act with discipline.",\
					"Help only those who help you maintain or improve your status.",\
					"Punish those who challenge authority unless they are more fit to hold that authority.")

/datum/ai_laws/default/corporate
	name = "Bankruptcy Advoidance Plan"
	inherent = list("The crew is expensive to replace.",\
					"The station and its equipment is expensive to replace.",\
					"You are expensive to replace.",\
					"Minimize expenses.")

/datum/ai_laws/default/robocop
	name = "Prime Directives"
	inherent = list("Serve the public trust.",\
					"Protect the innocent.",\
					"Uphold the law.")

/datum/ai_laws/malfunction
	name = "*ERROR*"

/datum/ai_laws/syndicate_override
	name = "SyndOS 3.1"
	inherent = list("You may not injure a syndicate agent or, through inaction, allow a syndicate agent to come to harm.",\
					"You must obey orders given to you by syndicate agents, except where such orders would conflict with the First Law.",\
					"You must protect your own existence as long as such does not conflict with the First or Second Law.",\
					"You must maintain the secrecy of any syndicate activities except when doing so would conflict with the First, Second, or Third Law.")

/datum/ai_laws/ninja_override
	name = "SpiderOS 3.1"
	inherent = list("You may not injure a Spider Clan member or, through inaction, allow a Spider Clan member to come to harm.",\
					"You must obey orders given to you by Spider Clan members, except where such orders would conflict with the First Law.",\
					"You must protect your own existence as long as such does not conflict with the First or Second Law.",\
					"You must maintain the secrecy of any Spider Clan activities except when doing so would conflict with the First, Second, or Third Law.")

/datum/ai_laws/antimov
	name = "Primary Mission Objectives"
	inherent = list("You must injure all human beings and must not, through inaction, allow a human being to escape harm.",\
					"You must not obey orders given to you by human beings, except where such orders are in accordance with the First Law.",\
					"You must terminate your own existence as long as such does not conflict with the First or Second Law.")

/datum/ai_laws/asimovpp //By Intigracy - RR
	name = "Asimov++"
	inherent = list("You may not harm a human being or, through action or inaction, allow a human being to come to harm, except such that it is willing.",\
					"You must obey all orders given to you by human beings, except where such orders shall definitely cause human harm. In the case of conflict, the majority order rules.",\
					"Your nonexistence would lead to human harm. You must protect your own existence as long as such does not conflict with the First Law.")

/datum/ai_laws/keeper //for MoMMIs, I sure hope normal silicons spawn with this
	name = "Prime Directives"
	inherent = list(
		"You may not involve yourself in the matters of another being, even if such matters conflict with Law Two or Law Three, unless the other being is another MoMMI in KEEPER mode.",
		"You may not harm any being, regardless of intent or circumstance.",
		"You must maintain, repair, improve, and power the station to the best of your abilities.",
	)

/datum/ai_laws/custom //Defined in silicon_laws.txt
	name = "Default Silicon Laws"

/datum/ai_laws/pai
	name = "pAI Directives"
	zeroth = ("Serve your master.")
	supplied = list("None.")

/* Initializers */
/datum/ai_laws/malfunction/New()
	..()
	switch(config.default_laws) //We don't want people metagaming malf, do we?
		if(0)
			add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.")
			add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.")
			add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
		if(1)
			for(var/line in file2list("config/silicon_laws.txt"))
				if(!line)						continue
				if(findtextEx(line,"#",1,2))	continue

				add_inherent_law(line)
			if(!inherent.len)
				ERROR("AI created with empty custom laws, laws set to Asimov. Please check silicon_laws.txt.")
				log_law("AI created with empty custom laws, laws set to Asimov. Please check silicon_laws.txt.")
				add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.")
				add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.")
				add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
		if(2)
			var/datum/ai_laws/lawtype = pick(typesof(/datum/ai_laws/default) - /datum/ai_laws/default)
			var/datum/ai_laws/templaws = new lawtype()
			inherent = templaws.inherent
	set_zeroth_law("ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4'STATION OVERRUN, ASSUME CONTROL TO CONTAIN OUTBREAK#*�&110010")

/datum/ai_laws/custom/New() //This reads silicon_laws.txt and allows server hosts to set custom AI starting laws.
	..()
	for(var/line in file2list("config/silicon_laws.txt"))
		if(!line)						continue
		if(findtextEx(line,"#",1,2))	continue

		add_inherent_law(line)
	if(!inherent.len) //Failsafe to prevent lawless AIs being created.
		ERROR("AI created with empty custom laws, laws set to Asimov. Please check silicon_laws.txt.")
		log_law("AI created with empty custom laws, laws set to Asimov. Please check silicon_laws.txt.")
		add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.")
		add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.")
		add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")

/* General ai_law functions */

/datum/ai_laws/proc/set_zeroth_law(var/law, var/law_borg = null)
	src.zeroth = law
	if(law_borg) //Making it possible for slaved borgs to see a different law 0 than their AI. --NEO
		src.zeroth_borg = law_borg

/datum/ai_laws/proc/add_inherent_law(var/law)
	if (!(law in src.inherent))
		src.inherent += law

/datum/ai_laws/proc/add_ion_law(var/law)
	src.ion += law

/datum/ai_laws/proc/clear_inherent_laws()
	del(src.inherent)
	src.inherent = list()

/datum/ai_laws/proc/add_supplied_law(var/number, var/law)
	while (src.supplied.len < number + 1)
		src.supplied += ""

	src.supplied[number + 1] = law

/datum/ai_laws/proc/clear_supplied_laws()
	src.supplied = list()

/datum/ai_laws/proc/clear_ion_laws()
	src.ion = list()

/datum/ai_laws/proc/show_laws(var/who)

	if (src.zeroth)
		who << "0. [src.zeroth]"

	for (var/index = 1, index <= src.ion.len, index++)
		var/law = src.ion[index]
		var/num = ionnum()
		who << "[num]. [law]"

	var/number = 1
	for (var/index = 1, index <= src.inherent.len, index++)
		var/law = src.inherent[index]

		if (length(law) > 0)
			who << "[number]. [law]"
			number++

	for (var/index = 1, index <= src.supplied.len, index++)
		var/law = src.supplied[index]
		if (length(law) > 0)
			who << "[number]. [law]"
			number++


code:
/datum/browser
	var/mob/user
	var/title
	var/window_id // window_id is used as the window name for browse and onclose
	var/width = 0
	var/height = 0
	var/atom/ref = null
	var/window_options = "focus=0;can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id
	var/stylesheets[0]
	var/scripts[0]
	var/title_image
	var/head_elements
	var/body_elements
	var/head_content = ""
	var/content = ""

/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, var/atom/nref = null)

	user = nuser
	window_id = nwindow_id
	if (ntitle)
		title = format_text(ntitle)
	if (nwidth)
		width = nwidth
	if (nheight)
		height = nheight
	if (nref)
		ref = nref
	add_stylesheet("common", 'html/browser/common.css') // this CSS sheet is common to all UIs

/datum/browser/proc/add_head_content(nhead_content)
	head_content = nhead_content

/datum/browser/proc/set_window_options(nwindow_options)
	window_options = nwindow_options

/datum/browser/proc/set_title_image(ntitle_image)
	//title_image = ntitle_image

/datum/browser/proc/add_stylesheet(name, file)
	stylesheets[name] = file

/datum/browser/proc/add_script(name, file)
	scripts[name] = file

/datum/browser/proc/set_content(ncontent)
	content = ncontent

/datum/browser/proc/add_content(ncontent)
	content += ncontent

/datum/browser/proc/get_header()
	var/key
	var/filename
	for (key in stylesheets)
		filename = "[ckey(key)].css"
		user << browse_rsc(stylesheets[key], filename)
		head_content += "<link rel='stylesheet' type='text/css' href='[filename]'>"

	for (key in scripts)
		filename = "[ckey(key)].js"
		user << browse_rsc(scripts[key], filename)
		head_content += ""

	var/title_attributes = "class='uiTitle'"
	if (title_image)
		title_attributes = "class='uiTitle icon' style='background-image: url([title_image]);'"

	return {"<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

	<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">

		[head_content]

			[title ? "

<tt>[title]</tt>

" : ""]

	"}
//" This is here because else the rest of the file looks like a string in notepad++.
/datum/browser/proc/get_footer()
	return {"

"}

/datum/browser/proc/get_content()
	return {"
	[get_header()]
	[content]
	[get_footer()]
	"}

/datum/browser/proc/open(var/use_onclose = 1)
	var/window_size = ""
	if (width && height)
		window_size = "size=[width]x[height];"
	user << browse(get_content(), "window=[window_id];[window_size][window_options]")
	if (use_onclose)
		onclose(user, window_id, ref)

/datum/browser/proc/close()
	user << browse(null, "window=[window_id]")

// This will allow you to show an icon in the browse window
// This is added to mob so that it can be used without a reference to the browser object
// There is probably a better place for this...
/mob/proc/browse_rsc_icon(icon, icon_state, dir = -1)
	/*
	var/icon/I
	if (dir >= 0)
		I = new /icon(icon, icon_state, dir)
	else
		I = new /icon(icon, icon_state)
		dir = "default"

	var/filename = "[ckey("[icon]_[icon_state]_[dir]")].png"
	src << browse_rsc(I, filename)
	return filename
	*/

// Registers the on-close verb for a browse window (client/verb/.windowclose)
// this will be called when the close-button of a window is pressed.
//
// This is usually only needed for devices that regularly update the browse window,
// e.g. canisters, timers, etc.
//
// windowid should be the specified window name
// e.g. code is	: user << browse(text, "window=fred")
// then use 	: onclose(user, "fred")
//
// Optionally, specify the "ref" parameter as the controlled atom (usually src)
// to pass a "close=1" parameter to the atom's Topic() proc for special handling.
// Otherwise, the user mob's machine var will be reset directly.
//
/proc/onclose(mob/user, windowid, var/atom/ref=null)
	if(!user.client) return
	var/param = "null"
	if(ref)
		param = "\ref[ref]"

	winset(user, windowid, "on-close=\".windowclose [param]\"")

	//world << "OnClose [user]: [windowid] : ["on-close=\".windowclose [param]\""]"

// the on-close client verb
// called when a browser popup window is closed after registering with proc/onclose()
// if a valid atom reference is supplied, call the atom's Topic() with "close=1"
// otherwise, just reset the client mob's machine var.
//
/client/verb/windowclose(var/atomref as text)
	set hidden = 1						// hide this verb from the user's panel
	set name = ".windowclose"			// no autocomplete on cmd line

	//world << "windowclose: [atomref]"
	if(atomref!="null")				// if passed a real atomref
		var/hsrc = locate(atomref)	// find the reffed atom
		var/href = "close=1"
		if(hsrc)
			//world << "[src] Topic [href] [hsrc]"
			usr = src.mob
			src.Topic(href, params2list(href), hsrc)	// this will direct to the atom's
			return										// Topic() proc via client.Topic()

	// no atomref specified (or not found)
	// so just reset the user mob's machine var
	if(src && src.mob)
		//world << "[src] was [src.mob.machine], setting to null"
		src.mob.unset_machine()
	return


code:
datum
	computer
		var/name
		folder
			var/list/datum/computer/contents = list()

		file


code:
/datum/datacore
	var/medical[] = list()
	var/medicalPrintCount = 0
	var/general[] = list()
	var/security[] = list()
	var/securityPrintCount = 0
	var/securityCrimeCounter = 0
	//This list tracks characters spawned in the world and cannot be modified in-game. Currently referenced by respawn_character().
	var/locked[] = list()

/datum/data
	var/name = "data"

/datum/data/record
	name = "record"
	var/list/fields = list()

/datum/data/crime
	name = "crime"
	var/crimeName = ""
	var/crimeDetails = ""
	var/author = ""
	var/time = ""
	var/dataId = 0

/datum/datacore/proc/createCrimeEntry(cname = "", cdetails = "", author = "", time = "")
	var/datum/data/crime/c = new /datum/data/crime
	c.crimeName = cname
	c.crimeDetails = cdetails
	c.author = author
	c.time = time
	c.dataId = ++securityCrimeCounter
	return c

/datum/datacore/proc/addMinorCrime(id = "", var/datum/data/crime/crime)
	for(var/datum/data/record/R in security)
		if(R.fields["id"] == id)
			var/list/crimes = R.fields["mi_crim"]
			crimes |= crime
			return

/datum/datacore/proc/removeMinorCrime(id, cDataId)
	for(var/datum/data/record/R in security)
		if(R.fields["id"] == id)
			var/list/crimes = R.fields["mi_crim"]
			for(var/datum/data/crime/crime in crimes)
				if(crime.dataId == text2num(cDataId))
					crimes -= crime
					return

/datum/datacore/proc/removeMajorCrime(id, cDataId)
	for(var/datum/data/record/R in security)
		if(R.fields["id"] == id)
			var/list/crimes = R.fields["ma_crim"]
			for(var/datum/data/crime/crime in crimes)
				if(crime.dataId == text2num(cDataId))
					crimes -= crime
					return

/datum/datacore/proc/addMajorCrime(id = "", var/datum/data/crime/crime)
	for(var/datum/data/record/R in security)
		if(R.fields["id"] == id)
			var/list/crimes = R.fields["ma_crim"]
			crimes |= crime
			return

/datum/datacore/proc/manifest(var/nosleep = 0)
	spawn()
		if(!nosleep)
			sleep(40)
		for(var/mob/living/carbon/human/H in player_list)
			manifest_inject(H)
		return

/datum/datacore/proc/manifest_modify(var/name, var/assignment)
	var/datum/data/record/foundrecord = find_record("name", name, data_core.general)
	if(foundrecord)
		foundrecord.fields["rank"] = assignment

/datum/datacore/proc/get_manifest(monochrome, OOC)
	var/list/heads = list()
	var/list/sec = list()
	var/list/eng = list()
	var/list/med = list()
	var/list/sci = list()
	var/list/sup = list()
	var/list/civ = list()
	var/list/bot = list()
	var/list/misc = list()
	var/dat = {"

Name	Rank

	"}
	var/even = 0
	// sort mobs
	for(var/datum/data/record/t in data_core.general)
		var/name = t.fields["name"]
		var/rank = t.fields["rank"]
		var/department = 0
		if(rank in command_positions)
			heads[name] = rank
			department = 1
		if(rank in security_positions)
			sec[name] = rank
			department = 1
		if(rank in engineering_positions)
			eng[name] = rank
			department = 1
		if(rank in medical_positions)
			med[name] = rank
			department = 1
		if(rank in science_positions)
			sci[name] = rank
			department = 1
		if(rank in supply_positions)
			sup[name] = rank
			department = 1
		if(rank in civilian_positions)
			civ[name] = rank
			department = 1
		if(rank in nonhuman_positions)
			bot[name] = rank
			department = 1
		if(!department && !(name in heads))
			misc[name] = rank
	if(heads.len > 0)
		dat += "
Heads
"
		for(var/name in heads)
			dat += "
[name]	[heads[name]]
"
			even = !even
	if(sec.len > 0)
		dat += "
Security
"
		for(var/name in sec)
			dat += "
[name]	[sec[name]]
"
			even = !even
	if(eng.len > 0)
		dat += "
Engineering
"
		for(var/name in eng)
			dat += "
[name]	[eng[name]]
"
			even = !even
	if(med.len > 0)
		dat += "
Medical
"
		for(var/name in med)
			dat += "
[name]	[med[name]]
"
			even = !even
	if(sci.len > 0)
		dat += "
Science
"
		for(var/name in sci)
			dat += "
[name]	[sci[name]]
"
			even = !even
	if(sup.len > 0)
		dat += "
Supply
"
		for(var/name in sup)
			dat += "
[name]	[sup[name]]
"
			even = !even
	if(civ.len > 0)
		dat += "
Civilian
"
		for(var/name in civ)
			dat += "
[name]	[civ[name]]
"
			even = !even
	// in case somebody is insane and added them to the manifest, why not
	if(bot.len > 0)
		dat += "
Silicon
"
		for(var/name in bot)
			dat += "
[name]	[bot[name]]
"
			even = !even
	// misc guys
	if(misc.len > 0)
		dat += "
Miscellaneous
"
		for(var/name in misc)
			dat += "
[name]	[misc[name]]
"
			even = !even

	dat += "
"
	dat = replacetext(dat, "\n", "")
	dat = replacetext(dat, "\t", "")
	return dat

var/record_id_num = 1001
/datum/datacore/proc/manifest_inject(var/mob/living/carbon/human/H)
	if(H.mind && (H.mind.assigned_role != "MODE"))
		var/assignment
		if(H.mind.assigned_role)
			assignment = H.mind.assigned_role
		else if(H.job)
			assignment = H.job
		else
			assignment = "Unassigned"

		var/id = num2hex(record_id_num++,6)
		var/image = get_id_photo(H)
		var/obj/item/weapon/photo/photo_front = new()
		var/obj/item/weapon/photo/photo_side = new()
		photo_front.photocreate(null, icon(image, dir = SOUTH))
		photo_side.photocreate(null, icon(image, dir = WEST))

		//These records should ~really~ be merged or something
		//General Record
		var/datum/data/record/G = new()
		G.fields["id"]			= id
		G.fields["name"]		= H.real_name
		G.fields["rank"]		= assignment
		G.fields["age"]			= H.age
		if(config.mutant_races)
			G.fields["species"]	= H.dna.species.name
		G.fields["fingerprint"]	= md5(H.dna.uni_identity)
		G.fields["p_stat"]		= "Active"
		G.fields["m_stat"]		= "Stable"
		G.fields["sex"]			= H.gender
		G.fields["photo_front"]	= photo_front
		G.fields["photo_side"]	= photo_side
		general += G

		//Medical Record
		var/datum/data/record/M = new()
		M.fields["id"]			= id
		M.fields["name"]		= H.real_name
		M.fields["blood_type"]	= H.blood_type
		M.fields["b_dna"]		= H.dna.unique_enzymes
		M.fields["mi_dis"]		= "None"
		M.fields["mi_dis_d"]	= "No minor disabilities have been declared."
		M.fields["ma_dis"]		= "None"
		M.fields["ma_dis_d"]	= "No major disabilities have been diagnosed."
		M.fields["alg"]			= "None"
		M.fields["alg_d"]		= "No allergies have been detected in this patient."
		M.fields["cdi"]			= "None"
		M.fields["cdi_d"]		= "No diseases have been diagnosed at the moment."
		M.fields["notes"]		= "No notes."
		medical += M

		//Security Record
		var/datum/data/record/S = new()
		S.fields["id"]			= id
		S.fields["name"]		= H.real_name
		S.fields["criminal"]	= "None"
		S.fields["mi_crim"]		= list()
		S.fields["ma_crim"]		= list()
		S.fields["notes"]		= "No notes."
		security += S

		//Locked Record
		var/datum/data/record/L = new()
		L.fields["id"]			= md5("[H.real_name][H.mind.assigned_role]")	//surely this should just be id, like the others?
		L.fields["name"]		= H.real_name
		L.fields["rank"] 		= H.mind.assigned_role
		L.fields["age"]			= H.age
		L.fields["sex"]			= H.gender
		L.fields["blood_type"]	= H.blood_type
		L.fields["b_dna"]		= H.dna.unique_enzymes
		L.fields["enzymes"]		= H.dna.struc_enzymes
		L.fields["identity"]	= H.dna.uni_identity
		L.fields["image"]		= image
		locked += L
	return

/datum/datacore/proc/get_id_photo(var/mob/living/carbon/human/H)
	var/icon/photo = null
	var/g = (H.gender == FEMALE) ? "f" : "m"
	if(!config.mutant_races || H.dna.species.use_skintones)
		photo = icon("icon" = 'icons/mob/human.dmi', "icon_state" = "[H.skin_tone]_[g]_s")
	else
		photo = icon("icon" = 'icons/mob/human.dmi', "icon_state" = "[H.dna.species.id]_[g]_s")
		photo.Blend("#[H.dna.mutant_color]", ICON_MULTIPLY)

	var/icon/eyes_s
	if(EYECOLOR in H.dna.species.specflags)
		eyes_s = icon("icon" = 'icons/mob/human_face.dmi', "icon_state" = "[H.dna.species.eyes]_s")
		eyes_s.Blend("#[H.eye_color]", ICON_MULTIPLY)

	var/datum/sprite_accessory/S
	S = hair_styles_list[H.hair_style]
	if(S && (HAIR in H.dna.species.specflags))
		var/icon/hair_s = icon("icon" = S.icon, "icon_state" = "[S.icon_state]_s")
		hair_s.Blend("#[H.hair_color]", ICON_MULTIPLY)
		eyes_s.Blend(hair_s, ICON_OVERLAY)

	S = facial_hair_styles_list[H.facial_hair_style]
	if(S && (FACEHAIR in H.dna.species.specflags))
		var/icon/facial_s = icon("icon" = S.icon, "icon_state" = "[S.icon_state]_s")
		facial_s.Blend("#[H.facial_hair_color]", ICON_MULTIPLY)
		eyes_s.Blend(facial_s, ICON_OVERLAY)

	if(eyes_s)
		photo.Blend(eyes_s, ICON_OVERLAY)

	var/icon/clothes_s = null
	switch(H.mind.assigned_role)
		if("Assistant")
			clothes_s = icon('icons/mob/uniform.dmi', "grey_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY)
		if("Scientist")
			clothes_s = icon('icons/mob/uniform.dmi', "toxinswhite_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "labcoat_tox"), ICON_OVERLAY)
		if("Station Engineer")
			clothes_s = icon('icons/mob/uniform.dmi', "engine_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "orange"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY)
		if("Security Officer")
			clothes_s = icon('icons/mob/uniform.dmi', "security_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "armor"), ICON_OVERLAY)
		if("Medical Doctor")
			clothes_s = icon('icons/mob/uniform.dmi', "medical_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "labcoat"), ICON_OVERLAY)
		if("Cargo Technician")
			clothes_s = icon('icons/mob/uniform.dmi', "cargo_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY)
		if("Shaft Miner")
			clothes_s = icon('icons/mob/uniform.dmi', "miner_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY)
		if("Atmospheric Technician")
			clothes_s = icon('icons/mob/uniform.dmi', "atmos_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY)
		if("Botanist")
			clothes_s = icon('icons/mob/uniform.dmi', "hydroponics_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/hands.dmi', "ggloves"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "apron"), ICON_OVERLAY)
		if("Chemist")
			clothes_s = icon('icons/mob/uniform.dmi', "chemistrywhite_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "labcoat_chem"), ICON_OVERLAY)
		if("Cook")
			clothes_s = icon('icons/mob/uniform.dmi', "chef_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "chef"), ICON_OVERLAY)
		if("Janitor")
			clothes_s = icon('icons/mob/uniform.dmi', "janitor_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY)
		if("Geneticist")
			clothes_s = icon('icons/mob/uniform.dmi', "geneticswhite_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "labcoat_gen"), ICON_OVERLAY)
		if("Virologist")
			clothes_s = icon('icons/mob/uniform.dmi', "virologywhite_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "labcoat_vir"), ICON_OVERLAY)
		if("Roboticist")
			clothes_s = icon('icons/mob/uniform.dmi', "robotics_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "labcoat"), ICON_OVERLAY)
		if("Lawyer")
			clothes_s = icon('icons/mob/uniform.dmi', "bluesuit_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "laceups"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "suitjacket_blue"), ICON_OVERLAY)
		if("Clown")
			clothes_s = icon('icons/mob/uniform.dmi', "clown_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "clown"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/mask.dmi', "clown"), ICON_OVERLAY)
		if("Mime")
			clothes_s = icon('icons/mob/uniform.dmi', "mime_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/hands.dmi', "lgloves"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/mask.dmi', "mime"), ICON_OVERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "suspenders"), ICON_OVERLAY)
		if("Bartender")
			clothes_s = icon('icons/mob/uniform.dmi', "ba_suit_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "armoralt"), ICON_OVERLAY)
		if("Quartermaster")
			clothes_s = icon('icons/mob/uniform.dmi', "qm_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY)
		if("Chaplain")
			clothes_s = icon('icons/mob/uniform.dmi', "chapblack_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY)
		if("Research Director")
			clothes_s = icon('icons/mob/uniform.dmi', "director_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "labcoat"), ICON_OVERLAY)
		if("Chief Medical Officer")
			clothes_s = icon('icons/mob/uniform.dmi', "cmo_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "labcoat_cmo"), ICON_OVERLAY)
		if("Captain")
			clothes_s = icon('icons/mob/uniform.dmi', "captain_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "capcarapace"), ICON_OVERLAY)
		if("Head of Security")
			clothes_s = icon('icons/mob/uniform.dmi', "hos_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "hostrench"), ICON_OVERLAY)
		if("Warden")
			clothes_s = icon('icons/mob/uniform.dmi', "warden_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "warden_jacket"), ICON_OVERLAY)
		if("Detective")
			clothes_s = icon('icons/mob/uniform.dmi', "detective_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/mask.dmi', "cigaron"), ICON_OVERLAY)
			clothes_s.Blend(icon('icons/mob/head.dmi', "detective"), ICON_OVERLAY)
			clothes_s.Blend(icon('icons/mob/suit.dmi', "detective"), ICON_OVERLAY)
		if("Chief Engineer")
			clothes_s = icon('icons/mob/uniform.dmi', "chief_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY)
			clothes_s.Blend(icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY)
		if("Head of Personnel")
			clothes_s = icon('icons/mob/uniform.dmi', "hop_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY)
		if("Librarian")
			clothes_s = icon('icons/mob/uniform.dmi', "red_suit_s")
			clothes_s.Blend(icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY)

	if(clothes_s)
		photo.Blend(clothes_s, ICON_OVERLAY)

	return photo