code:
//Configuraton defines //TODO: Move all yes/no switches into bitflags

//Used by jobs_have_maint_access
#define ASSISTANTS_HAVE_MAINT_ACCESS 1
#define SECURITY_HAS_MAINT_ACCESS 2
#define EVERYONE_HAS_MAINT_ACCESS 4

//Not accessible from usual debug controller verb
/datum/protected_configuration
	var/autoadmin = 0
	var/autoadmin_rank = "Game Admin"

/datum/configuration
	var/name = "Configuration"			// datum name

	var/server_name = null				// server name (the name of the game window)
	var/station_name = null				// station name (the name of the station in-game)
	var/server_suffix = 0				// generate numeric suffix based on server port
	var/lobby_countdown = 120			// In between round countdown.
	var/round_end_countdown = 25		// Post round murder death kill countdown

	var/log_ooc = 0						// log OOC channel
	var/log_access = 0					// log login/logout
	var/log_say = 0						// log client say
	var/log_admin = 0					// log admin actions
	var/log_game = 0					// log game events
	var/log_vote = 0					// log voting
	var/log_whisper = 0					// log client whisper
	var/log_prayer = 0					// log prayers
	var/log_law = 0						// log lawchanges
	var/log_emote = 0					// log emotes
	var/log_attack = 0					// log attack messages
	var/log_adminchat = 0				// log admin chat messages
	var/log_pda = 0						// log pda messages
	var/log_hrefs = 0					// log all links clicked in-game. Could be used for debugging and tracking down exploits
	var/log_world_topic = 0				// log all world.Topic() calls
	var/sql_enabled = 0					// for sql switching
	var/allow_admin_ooccolor = 0		// Allows admins with relevant permissions to have their own ooc colour
	var/allow_vote_restart = 0 			// allow votes to restart
	var/allow_vote_mode = 0				// allow votes to change mode
	var/vote_delay = 6000				// minimum time between voting sessions (deciseconds, 10 minute default)
	var/vote_period = 600				// length of voting period (deciseconds, default 1 minute)
	var/vote_no_default = 0				// vote does not default to nochange/norestart (tbi)
	var/vote_no_dead = 0				// dead people can't vote (tbi)
	var/del_new_on_log = 1				// del's new players if they log before they spawn in
	var/allow_Metadata = 0				// Metadata is supported.
	var/popup_admin_pm = 0				//adminPMs to non-admins show in a pop-up 'reply' window when set to 1.
	var/fps = 10
	var/allow_holidays = 0				//toggles whether holiday-specific content should be used

	var/hostedby = null
	var/respawn = 1
	var/guest_jobban = 1
	var/usewhitelist = 0
	var/kick_inactive = 0				//force disconnect for inactive players
	var/load_jobs_from_txt = 0
	var/automute_on = 0					//enables automuting/spam prevention
	var/minimal_access_threshold = 0	//If the number of players is larger than this threshold, minimal access will be turned on.
	var/jobs_have_minimal_access = 0	//determines whether jobs use minimal access or expanded access.
	var/jobs_have_maint_access = 0 		//Who gets maint access?  See defines above.
	var/sec_start_brig = 0				//makes sec start in brig or dept sec posts

	var/server
	var/banappeals
	var/wikiurl = "http://www.tgstation13.org/wiki" // Default wiki link.
	var/forumurl = "http://tgstation13.org/phpBB/index.php" //default forums
	var/rulesurl = "http://www.tgstation13.org/wiki/Rules" // default rules
	var/githuburl = "https://www.github.com/tgstation/-tg-station" //default github

	var/forbid_singulo_possession = 0
	var/useircbot = 0

	var/admin_legacy_system = 0	//Defines whether the server uses the legacy admin system with admins.txt or the SQL system. Config option in config.txt
	var/ban_legacy_system = 0	//Defines whether the server uses the legacy banning system with the files in /data or the SQL system. Config option in config.txt
	var/use_age_restriction_for_jobs = 0 //Do jobs use account age restrictions? --requires database
	var/see_own_notes = 0 //Can players see their own admin notes (read-only)? Config option in config.txt

	//Population cap vars
	var/soft_popcap				= 0
	var/hard_popcap				= 0
	var/extreme_popcap			= 0
	var/soft_popcap_message		= "Be warned that the server is currently serving a high number of users, consider using alternative game servers."
	var/hard_popcap_message		= "The server is currently serving a high number of users, You cannot currently join. You may wait for the number of living crew to decline, observe, or find alternative servers."
	var/extreme_popcap_message	= "The server is currently serving a high number of users, find alternative servers."

	//game_options.txt configs
	var/force_random_names = 0
	var/list/mode_names = list()
	var/list/modes = list()				// allowed modes
	var/list/votable_modes = list()		// votable modes
	var/list/probabilities = list()		// relative probability of each mode

	var/humans_need_surnames = 0
	var/allow_ai = 0					// allow ai job
	var/forbid_secborg = 0				// disallow secborg module to be chosen.
	var/panic_bunker = 0				// prevents new people it hasn't seen before from connecting
	var/notify_new_player_age = 0		// how long do we notify admins of a new player
	var/irc_first_connection_alert = 0	// do we notify the irc channel when somebody is connecting for the first time?

	var/traitor_scaling_coeff = 6		//how much does the amount of players get divided by to determine traitors
	var/changeling_scaling_coeff = 6	//how much does the amount of players get divided by to determine changelings
	var/security_scaling_coeff = 8		//how much does the amount of players get divided by to determine open security officer positions
	var/abductor_scaling_coeff = 15 	//how many players per abductor team

	var/traitor_objectives_amount = 2
	var/protect_roles_from_antagonist = 0 //If security and such can be traitor/cult/other
	var/protect_assistant_from_antagonist = 0 //If assistants can be traitor/cult/other
	var/enforce_human_authority = 0		//If non-human species are barred from joining as a head of staff
	var/allow_latejoin_antagonists = 0 	// If late-joining players can be traitor/changeling
	var/list/continuous = list()		// which roundtypes continue if all antagonists die
	var/list/midround_antag = list() 	// which roundtypes use the midround antagonist system
	var/midround_antag_time_check = 60  // How late (in minutes) you want the midround antag system to stay on, setting this to 0 will disable the system
	var/midround_antag_life_check = 0.7 // A ratio of how many people need to be alive in order for the round not to immediately end in midround antagonist
	var/shuttle_refuel_delay = 12000
	var/show_game_type_odds = 0			//if set this allows players to see the odds of each roundtype on the get revision screen
	var/mutant_races = 0				//players can choose their mutant race before joining the game
	var/list/roundstart_races = list()	//races you can play as from the get go. If left undefined the game's roundstart var for species is used
	var/mutant_humans = 0				//players can pick mutant bodyparts for humans before joining the game

	var/no_summon_guns		//No
	var/no_summon_magic		//Fun
	var/no_summon_events	//Allowed

	var/intercept = 1					//Whether or not to send a communications intercept report roundstart. This may be overriden by gamemodes.
	var/alert_desc_green = "All threats to the station have passed. Security may not have weapons visible, privacy laws are once again fully enforced."
	var/alert_desc_blue_upto = "The station has received reliable information about possible hostile activity on the station. Security staff may have weapons visible, random searches are permitted."
	var/alert_desc_blue_downto = "The immediate threat has passed. Security may no longer have weapons drawn at all times, but may continue to have them visible. Random searches are still allowed."
	var/alert_desc_red_upto = "There is an immediate serious threat to the station. Security may have weapons unholstered at all times. Random searches are allowed and advised."
	var/alert_desc_red_downto = "The station's destruction has been averted. There is still however an immediate serious threat to the station. Security may have weapons unholstered at all times, random searches are allowed and advised."
	var/alert_desc_delta = "Destruction of the station is imminent. All crew are instructed to obey all instructions given by heads of staff. Any violations of these orders can be punished by death. This is not a drill."

	var/health_threshold_crit = 0
	var/health_threshold_dead = -100

	var/revival_pod_plants = 1
	var/revival_cloning = 1
	var/revival_brain_life = -1

	var/rename_cyborg = 0
	var/ooc_during_round = 0
	var/emojis = 0

	//Used for modifying movement speed for mobs.
	//Unversal modifiers
	var/run_speed = 0
	var/walk_speed = 0

	//Mob specific modifiers. NOTE: These will affect different mob types in different ways
	var/human_delay = 0
	var/robot_delay = 0
	var/monkey_delay = 0
	var/alien_delay = 0
	var/slime_delay = 0
	var/animal_delay = 0

	var/gateway_delay = 18000 //How long the gateway takes before it activates. Default is half an hour.
	var/ghost_interaction = 0

	var/silent_ai = 0
	var/silent_borg = 0

	var/allowwebclient = 0
	var/webclientmembersonly = 0

	var/sandbox_autoclose = 0 // close the sandbox panel after spawning an item, potentially reducing griff

	var/default_laws = 0 //Controls what laws the AI spawns with.
	var/silicon_max_law_amount = 12

	var/assistant_cap = -1

	var/starlight = 0
	var/generate_minimaps = 0
	var/grey_assistants = 0

	var/aggressive_changelog = 0

	var/reactionary_explosions = 0 //If we use reactionary explosions, explosions that react to walls and doors

	var/autoconvert_notes = 0 //if all connecting player's notes should attempt to be converted to the database

	var/announce_admin_logout = 0
	var/announce_admin_login = 0

	var/list/datum/votablemap/maplist = list()
	var/datum/votablemap/defaultmap = null
	var/maprotation = 1
	var/maprotatechancedelta = 0.75

	// Enables random events mid-round when set to 1
	var/allow_random_events = 0

	// Multipliers for random events minimal starting time and minimal players amounts
	var/events_min_time_mul = 1
	var/events_min_players_mul = 1

	// The object used for the clickable stat() button.
	var/obj/effect/statclick/statclick

/datum/configuration/New()
	var/list/L = subtypesof(/datum/game_mode)
	for(var/T in L)
		// I wish I didn't have to instance the game modes in order to look up
		// their information, but it is the only way (at least that I know of).
		var/datum/game_mode/M = new T()

		if(M.config_tag)
			if(!(M.config_tag in modes))		// ensure each mode is added only once
				diary << "Adding game mode [M.name] ([M.config_tag]) to configuration."
				modes += M.config_tag
				mode_names[M.config_tag] = M.name
				probabilities[M.config_tag] = M.probability
				if(M.votable)
					votable_modes += M.config_tag
		qdel(M)
	votable_modes += "secret"

/datum/configuration/proc/load(filename, type = "config") //the type can also be game_options, in which case it uses a different switch. not making it separate to not copypaste code - Urist
	var/list/Lines = file2list(filename)

	for(var/t in Lines)
		if(!t)
			continue

		t = trim(t)
		if(length(t) == 0)
			continue
		else if(copytext(t, 1, 2) == "#")
			continue

		var/pos = findtext(t, " ")
		var/name = null
		var/value = null

		if(pos)
			name = lowertext(copytext(t, 1, pos))
			value = copytext(t, pos + 1)
		else
			name = lowertext(t)

		if(!name)
			continue

		if(type == "config")
			switch(name)
				if("admin_legacy_system")
					config.admin_legacy_system = 1
				if("ban_legacy_system")
					config.ban_legacy_system = 1
				if("use_age_restriction_for_jobs")
					config.use_age_restriction_for_jobs = 1
				if("lobby_countdown")
					config.lobby_countdown = text2num(value)
				if("round_end_countdown")
					config.round_end_countdown = text2num(value)
				if("log_ooc")
					config.log_ooc = 1
				if("log_access")
					config.log_access = 1
				if("log_say")
					config.log_say = 1
				if("log_admin")
					config.log_admin = 1
				if("log_prayer")
					config.log_prayer = 1
				if("log_law")
					config.log_law = 1
				if("log_game")
					config.log_game = 1
				if("log_vote")
					config.log_vote = 1
				if("log_whisper")
					config.log_whisper = 1
				if("log_attack")
					config.log_attack = 1
				if("log_emote")
					config.log_emote = 1
				if("log_adminchat")
					config.log_adminchat = 1
				if("log_pda")
					config.log_pda = 1
				if("log_hrefs")
					config.log_hrefs = 1
				if("log_world_topic")
					config.log_world_topic = 1
				if("allow_admin_ooccolor")
					config.allow_admin_ooccolor = 1
				if("allow_vote_restart")
					config.allow_vote_restart = 1
				if("allow_vote_mode")
					config.allow_vote_mode = 1
				if("no_dead_vote")
					config.vote_no_dead = 1
				if("default_no_vote")
					config.vote_no_default = 1
				if("vote_delay")
					config.vote_delay = text2num(value)
				if("vote_period")
					config.vote_period = text2num(value)
				if("norespawn")
					config.respawn = 0
				if("servername")
					config.server_name = value
				if("stationname")
					config.station_name = value
				if("serversuffix")
					config.server_suffix = 1
				if("hostedby")
					config.hostedby = value
				if("server")
					config.server = value
				if("banappeals")
					config.banappeals = value
				if("wikiurl")
					config.wikiurl = value
				if("forumurl")
					config.forumurl = value
				if("rulesurl")
					config.rulesurl = value
				if("githuburl")
					config.githuburl = value
				if("guest_jobban")
					config.guest_jobban = 1
				if("guest_ban")
					guests_allowed = 0
				if("usewhitelist")
					config.usewhitelist = 1
				if("allow_metadata")
					config.allow_Metadata = 1
				if("kick_inactive")
					if(value < 1)
						value = INACTIVITY_KICK
					config.kick_inactive = value
				if("load_jobs_from_txt")
					load_jobs_from_txt = 1
				if("forbid_singulo_possession")
					forbid_singulo_possession = 1
				if("popup_admin_pm")
					config.popup_admin_pm = 1
				if("allow_holidays")
					config.allow_holidays = 1
				if("useircbot")
					useircbot = 1
				if("ticklag")
					var/ticklag = text2num(value)
					if(ticklag > 0)
						fps = 10 / ticklag
				if("fps")
					fps = text2num(value)
				if("automute_on")
					automute_on = 1
				if("comms_key")
					global.comms_key = value
					if(value != "default_pwd" && length(value) > 6) //It's the default value or less than 6 characters long, warn badmins
						global.comms_allowed = 1
				if("see_own_notes")
					config.see_own_notes = 1
				if("soft_popcap")
					config.soft_popcap = text2num(value)
				if("hard_popcap")
					config.hard_popcap = text2num(value)
				if("extreme_popcap")
					config.extreme_popcap = text2num(value)
				if("soft_popcap_message")
					config.soft_popcap_message = value
				if("hard_popcap_message")
					config.hard_popcap_message = value
				if("extreme_popcap_message")
					config.extreme_popcap_message = value
				if("panic_bunker")
					config.panic_bunker = 1
				if("notify_new_player_age")
					config.notify_new_player_age = text2num(value)
				if("irc_first_connection_alert")
					config.irc_first_connection_alert = 1
				if("aggressive_changelog")
					config.aggressive_changelog = 1
				if("log_runtimes")
					var/newlog = file("data/logs/runtimes/runtime-[time2text(world.realtime, "YYYY-MM-DD")].log")
					if (world.log != newlog)
						world.log << "Now logging runtimes to data/logs/runtimes/runtime-[time2text(world.realtime, "YYYY-MM-DD")].log"
						world.log = newlog
				if("autoconvert_notes")
					config.autoconvert_notes = 1
				if("allow_webclient")
					config.allowwebclient = 1
				if("webclient_only_byond_members")
					config.webclientmembersonly = 1
				if("announce_admin_logout")
					config.announce_admin_logout = 1
				if("announce_admin_login")
					config.announce_admin_login = 1
				if("maprotation")
					config.maprotation = 1
				if("maprotationchancedelta")
					config.maprotatechancedelta = text2num(value)
				if("autoadmin")
					protected_config.autoadmin = 1
					if(value)
						protected_config.autoadmin_rank = ckeyEx(value)
				if("generate_minimaps")
					config.generate_minimaps = 1
				else
					diary << "Unknown setting in configuration: '[name]'"

		else if(type == "game_options")
			switch(name)
				if("health_threshold_crit")
					config.health_threshold_crit	= text2num(value)
				if("health_threshold_dead")
					config.health_threshold_dead	= text2num(value)
				if("revival_pod_plants")
					config.revival_pod_plants		= text2num(value)
				if("revival_cloning")
					config.revival_cloning			= text2num(value)
				if("revival_brain_life")
					config.revival_brain_life		= text2num(value)
				if("rename_cyborg")
					config.rename_cyborg			= 1
				if("ooc_during_round")
					config.ooc_during_round			= 1
				if("emojis")
					config.emojis					= 1
				if("run_delay")
					config.run_speed				= text2num(value)
				if("walk_delay")
					config.walk_speed				= text2num(value)
				if("human_delay")
					config.human_delay				= text2num(value)
				if("robot_delay")
					config.robot_delay				= text2num(value)
				if("monkey_delay")
					config.monkey_delay				= text2num(value)
				if("alien_delay")
					config.alien_delay				= text2num(value)
				if("slime_delay")
					config.slime_delay				= text2num(value)
				if("animal_delay")
					config.animal_delay				= text2num(value)
				if("alert_red_upto")
					config.alert_desc_red_upto		= value
				if("alert_red_downto")
					config.alert_desc_red_downto	= value
				if("alert_blue_downto")
					config.alert_desc_blue_downto	= value
				if("alert_blue_upto")
					config.alert_desc_blue_upto		= value
				if("alert_green")
					config.alert_desc_green			= value
				if("alert_delta")
					config.alert_desc_delta			= value
				if("no_intercept_report")
					config.intercept				= 0
				if("assistants_have_maint_access")
					config.jobs_have_maint_access	|= ASSISTANTS_HAVE_MAINT_ACCESS
				if("security_has_maint_access")
					config.jobs_have_maint_access	|= SECURITY_HAS_MAINT_ACCESS
				if("everyone_has_maint_access")
					config.jobs_have_maint_access	|= EVERYONE_HAS_MAINT_ACCESS
				if("sec_start_brig")
					config.sec_start_brig			= 1
				if("gateway_delay")
					config.gateway_delay			= text2num(value)
				if("continuous")
					var/mode_name = lowertext(value)
					if(mode_name in config.modes)
						config.continuous[mode_name] = 1
					else
						diary << "Unknown continuous configuration definition: [mode_name]."
				if("midround_antag")
					var/mode_name = lowertext(value)
					if(mode_name in config.modes)
						config.midround_antag[mode_name] = 1
					else
						diary << "Unknown midround antagonist configuration definition: [mode_name]."
				if("midround_antag_time_check")
					config.midround_antag_time_check = text2num(value)
				if("midround_antag_life_check")
					config.midround_antag_life_check = text2num(value)
				if("shuttle_refuel_delay")
					config.shuttle_refuel_delay     = text2num(value)
				if("show_game_type_odds")
					config.show_game_type_odds		= 1
				if("ghost_interaction")
					config.ghost_interaction		= 1
				if("traitor_scaling_coeff")
					config.traitor_scaling_coeff	= text2num(value)
				if("changeling_scaling_coeff")
					config.changeling_scaling_coeff	= text2num(value)
				if("security_scaling_coeff")
					config.security_scaling_coeff	= text2num(value)
				if("abductor_scaling_coeff")
					config.abductor_scaling_coeff	= text2num(value)
				if("traitor_objectives_amount")
					config.traitor_objectives_amount = text2num(value)
				if("probability")
					var/prob_pos = findtext(value, " ")
					var/prob_name = null
					var/prob_value = null

					if(prob_pos)
						prob_name = lowertext(copytext(value, 1, prob_pos))
						prob_value = copytext(value, prob_pos + 1)
						if(prob_name in config.modes)
							config.probabilities[prob_name] = text2num(prob_value)
						else
							diary << "Unknown game mode probability configuration definition: [prob_name]."
					else
						diary << "Incorrect probability configuration definition: [prob_name]  [prob_value]."

				if("protect_roles_from_antagonist")
					config.protect_roles_from_antagonist	= 1
				if("protect_assistant_from_antagonist")
					config.protect_assistant_from_antagonist	= 1
				if("enforce_human_authority")
					config.enforce_human_authority	= 1
				if("allow_latejoin_antagonists")
					config.allow_latejoin_antagonists	= 1
				if("allow_random_events")
					config.allow_random_events		= 1

				if("events_min_time_mul")
					config.events_min_time_mul		= text2num(value)
				if("events_min_players_mul")
					config.events_min_players_mul	= text2num(value)

				if("minimal_access_threshold")
					config.minimal_access_threshold	= text2num(value)
				if("jobs_have_minimal_access")
					config.jobs_have_minimal_access	= 1
				if("humans_need_surnames")
					humans_need_surnames			= 1
				if("force_random_names")
					config.force_random_names		= 1
				if("allow_ai")
					config.allow_ai					= 1
				if("disable_secborg")
					config.forbid_secborg			= 1
				if("silent_ai")
					config.silent_ai 				= 1
				if("silent_borg")
					config.silent_borg				= 1
				if("sandbox_autoclose")
					config.sandbox_autoclose		= 1
				if("default_laws")
					config.default_laws				= text2num(value)
				if("silicon_max_law_amount")
					config.silicon_max_law_amount	= text2num(value)
				if("join_with_mutant_race")
					config.mutant_races				= 1
				if("roundstart_races")
					var/race_id = lowertext(value)
					for(var/species_id in species_list)
						if(species_id == race_id)
							roundstart_races += species_list[species_id]
							roundstart_species[species_id] = species_list[species_id]
				if("join_with_mutant_humans")
					config.mutant_humans			= 1
				if("assistant_cap")
					config.assistant_cap			= text2num(value)
				if("starlight")
					config.starlight			= 1
				if("grey_assistants")
					config.grey_assistants			= 1
				if("no_summon_guns")
					config.no_summon_guns			= 1
				if("no_summon_magic")
					config.no_summon_magic			= 1
				if("no_summon_events")
					config.no_summon_events			= 1
				if("reactionary_explosions")
					config.reactionary_explosions	= 1
				if("bombcap")
					var/BombCap = text2num(value)
					if (!BombCap)
						continue
					if (BombCap < 4)
						BombCap = 4

					MAX_EX_DEVESTATION_RANGE = round(BombCap/4)
					MAX_EX_HEAVY_RANGE = round(BombCap/2)
					MAX_EX_LIGHT_RANGE = BombCap
					MAX_EX_FLASH_RANGE = BombCap
					MAX_EX_FLAME_RANGE = BombCap
				else
					diary << "Unknown setting in configuration: '[name]'"

	fps = round(fps)
	if(fps <= 0)
		fps = initial(fps)

/datum/configuration/proc/loadmaplist(filename)
	var/list/Lines = file2list(filename)

	var/datum/votablemap/currentmap = null
	for(var/t in Lines)
		if(!t)
			continue

		t = trim(t)
		if(length(t) == 0)
			continue
		else if(copytext(t, 1, 2) == "#")
			continue

		var/pos = findtext(t, " ")
		var/command = null
		var/data = null

		if(pos)
			command = lowertext(copytext(t, 1, pos))
			data = copytext(t, pos + 1)
		else
			command = lowertext(t)

		if(!command)
			continue

		if (!currentmap && command != "map")
			continue

		switch (command)
			if ("map")
				currentmap = new (data)
			if ("friendlyname")
				currentmap.friendlyname = data
			if ("minplayers","minplayer")
				currentmap.minusers = text2num(data)
			if ("maxplayers","maxplayer")
				currentmap.maxusers = text2num(data)
			if ("friendlyname")
				currentmap.friendlyname = data
			if ("weight","voteweight")
				currentmap.voteweight = text2num(data)
			if ("default","defaultmap")
				config.defaultmap = currentmap
			if ("endmap")
				config.maplist[currentmap.name] = currentmap
				currentmap = null
			else
				diary << "Unknown command in map vote config: '[command]'"

/datum/configuration/proc/loadsql(filename)
	var/list/Lines = file2list(filename)
	for(var/t in Lines)
		if(!t)
			continue

		t = trim(t)
		if(length(t) == 0)
			continue
		else if(copytext(t, 1, 2) == "#")
			continue

		var/pos = findtext(t, " ")
		var/name = null
		var/value = null

		if(pos)
			name = lowertext(copytext(t, 1, pos))
			value = copytext(t, pos + 1)
		else
			name = lowertext(t)

		if(!name)
			continue

		switch(name)
			if("sql_enabled")
				config.sql_enabled = 1
			if("address")
				sqladdress = value
			if("port")
				sqlport = value
			if("feedback_database")
				sqlfdbkdb = value
			if("feedback_login")
				sqlfdbklogin = value
			if("feedback_password")
				sqlfdbkpass = value
			if("feedback_tableprefix")
				sqlfdbktableprefix = value
			else
				diary << "Unknown setting in configuration: '[name]'"

/datum/configuration/proc/pick_mode(mode_name)
	// I wish I didn't have to instance the game modes in order to look up
	// their information, but it is the only way (at least that I know of).
	for(var/T in subtypesof(/datum/game_mode))
		var/datum/game_mode/M = new T()
		if(M.config_tag && M.config_tag == mode_name)
			return M
		qdel(M)
	return new /datum/game_mode/extended()

/datum/configuration/proc/get_runnable_modes()
	var/list/datum/game_mode/runnable_modes = new
	for(var/T in subtypesof(/datum/game_mode))
		var/datum/game_mode/M = new T()
		//world << "DEBUG: [T], tag=[M.config_tag], prob=[probabilities[M.config_tag]]"
		if(!(M.config_tag in modes))
			qdel(M)
			continue
		if(probabilities[M.config_tag]<=0)
			qdel(M)
			continue
		if(M.can_start())
			runnable_modes[M] = probabilities[M.config_tag]
			//world << "DEBUG: runnable_mode\[[runnable_modes.len]\] = [M.config_tag]"
	return runnable_modes

/datum/configuration/proc/get_runnable_midround_modes(crew)
	var/list/datum/game_mode/runnable_modes = new
	for(var/T in (subtypesof(/datum/game_mode) - ticker.mode.type))
		var/datum/game_mode/M = new T()
		if(!(M.config_tag in modes))
			qdel(M)
			continue
		if(probabilities[M.config_tag]<=0)
			qdel(M)
			continue
		if(M.required_players <= crew)
			runnable_modes[M] = probabilities[M.config_tag]
	return runnable_modes

/datum/configuration/proc/stat_entry()
	if(!statclick)
		statclick = new/obj/effect/statclick/debug("Edit", src)

	stat("[name]:", statclick)


code:
/datum/controller
	var/name
	// The object used for the clickable stat() button.
	var/obj/effect/statclick/statclick

license: mit


code:
/**
  * Failsafe
  *
  * Pretty much pokes the MC to make sure it's still alive.
 **/

var/datum/controller/failsafe/Failsafe

/datum/controller/failsafe // This thing pretty much just keeps poking the master controller
	name = "Failsafe"

	// The length of time to check on the MC (in deciseconds).
	// Set to 0 to disable.
	var/processing_interval = 100
	// The alert level. For every failed poke, we drop a DEFCON level. Once we hit DEFCON 1, restart the MC.
	var/defcon = 0

	// Track the MC iteration to make sure its still on track.
	var/master_iteration = 0

/datum/controller/failsafe/New()
	// Highlander-style: there can only be one! Kill off the old and replace it with the new.
	if(Failsafe != src)
		if(istype(Failsafe))
			qdel(Failsafe)
	Failsafe = src
	Failsafe.process()

/datum/controller/failsafe/process()
	spawn(0)
		while(1) // More efficient than recursion, 1 to avoid an infinite loop.
			if(!Master)
				// Replace the missing Master! This should never, ever happen.
				new /datum/controller/master()
			// Only poke it if overrides are not in effect.
			if(processing_interval > 0)
				if(Master.processing)
					// Check if processing is done yet.
					if(Master.iteration == master_iteration)
						switch(defcon)
							if(0 to 2)
								++defcon
							if(3)
								admins << "Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [defcon * processing_interval] ticks. Automatic restart in [processing_interval] ticks."
								defcon = 4
							if(4)
								admins << "Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [defcon * processing_interval] ticks. Killing and restarting..."
								// Replace the old master controller by creating a new one.
								new/datum/controller/master()
								// Get it rolling again.
								Master.process()
								defcon = 0
					else
						defcon = 0
						master_iteration = Master.iteration
				sleep(processing_interval)
			else
				defcon = 0
				sleep(initial(processing_interval))

/datum/controller/failsafe/proc/defcon_pretty()
	return 5 - Failsafe.defcon

/datum/controller/failsafe/proc/stat_entry()
	if(!statclick)
		statclick = new/obj/effect/statclick/debug("Initializing...", src)

	stat("Failsafe Controller:", statclick.update("Defcon: [Failsafe.defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])"))


code:
/**
  * CarnMC
  *
  * Simplified MC; designed to fail fast and respawn.
  * This ensures Master.process() never doubles up.
  * It will kill itself and any sleeping procs if needed.
  *
  * All core systems are subsystems.
  * They are process()'d by this Master Controller.
 **/
var/global/datum/controller/master/Master = new()

/datum/controller/master
	name = "Master"

	// are we processing (higher values increase the processing delay)
	var/processing = 1
	// The iteration of the MC.
	var/iteration = 0
	// The cost (in deciseconds) of the MC loop.
	var/cost = 0
#if DM_VERSION < 510
	// The old fps when we slow it down to prevent lag.
	var/old_fps
#endif
	// A list of subsystems to process().
	var/list/subsystems = list()
	// The cost of running the subsystems (in deciseconds).
	var/subsystem_cost = 0
	// The type of the last subsystem to be process()'d.
	var/last_type_processed
#if DM_VERSION >= 510
	var/list/priority_queue = list() //any time we pause or skip a ss for tick reasons, we run it first next tick
#endif

/datum/controller/master/New()
	// Highlander-style: there can only be one! Kill off the old and replace it with the new.
	if(Master != src)
		if(istype(Master))
			Recover()
			qdel(Master)
		else
			init_subtypes(/datum/subsystem, subsystems)
		Master = src

/datum/controller/master/Destroy()
	..()
	// Tell qdel() to Del() this object.
	return QDEL_HINT_HARDDEL_NOW

/datum/controller/master/proc/Recover()
	var/msg = "## DEBUG: [time2text(world.timeofday)] MC restarted. Reports:\n"
	for(var/varname in Master.vars)
		switch(varname)
			if("name", "tag", "bestF", "type", "parent_type", "vars", "statclick") // Built-in junk.
				continue
			else
				var/varval = Master.vars[varname]
				if(istype(varval, /datum)) // Check if it has a type var.
					var/datum/D = varval
					msg += "\t [varname] = [D.type]\n"
				else
					msg += "\t [varname] = [varval]\n"
	world.log << msg

	subsystems = Master.subsystems

/datum/controller/master/proc/Setup(zlevel)
	// Per-Z-level subsystems.
	if(zlevel && zlevel > 0 && zlevel <= world.maxz)
		for(var/datum/subsystem/SS in subsystems)
			SS.Initialize(world.timeofday, zlevel)
			CHECK_TICK
		return
	world << "Initializing subsystems..."

	preloadTemplates()
	// Pick a random away mission.
	createRandomZlevel()
	// Generate mining.

	var/mining_type = MINETYPE
	if(mining_type == "lavaland")
		seedRuins(5, 5, /area/lavaland/surface/outdoors, lava_ruins_templates)
		spawn_rivers()
	else
		make_mining_asteroid_secrets()

	// Set up Z-level transistions.
	setup_map_transitions()

	// Sort subsystems by priority, so they initialize in the correct order.
	sortTim(subsystems, /proc/cmp_subsystem_priority)

	// Initialize subsystems.
	for(var/datum/subsystem/SS in subsystems)
		SS.Initialize(world.timeofday, zlevel)
		CHECK_TICK

	world << "Initializations complete!"

	// Sort subsystems by display setting for easy access.
	sortTim(subsystems, /proc/cmp_subsystem_display)

	// Set world options.
	world.sleep_offline = 1
	world.fps = config.fps

	sleep(1)

	// Loop.
	Master.process()

// Notify the MC that the round has started.
/datum/controller/master/proc/RoundStart()
	var/timer = world.time
	for(var/datum/subsystem/SS in subsystems)
		timer += world.tick_lag
		SS.can_fire = 1
		SS.next_fire = timer + rand(0, SS.wait) // Stagger subsystems.

// Used to smooth out costs to try and avoid oscillation.
#define MC_AVERAGE_FAST(average, current) (0.7 * (average) + 0.3 * (current))
#define MC_AVERAGE(average, current) (0.8 * (average) + 0.2 * (current))
#define MC_AVERAGE_SLOW(average, current) (0.9 * (average) + 0.1 * (current))

/datum/controller/master/process()
	if(!Failsafe)
		new/datum/controller/failsafe() // (re)Start the failsafe.
	spawn(0)
		// Schedule the first run of the Subsystems.
		var/timer = world.time
		for(var/datum/subsystem/SS in subsystems)
			if (SS.can_fire)
				timer += world.tick_lag
				SS.next_fire = timer

		var/list/subsystemstorun = subsystems.Copy()
		var/start_time
		while(1) // More efficient than recursion.
			if(processing) //are we processing
				++iteration
				start_time = world.timeofday
				if (!subsystemstorun.len)
					subsystemstorun = subsystems.Copy()
				var/priorityrunning = 0 //so we know if there are priority queue items
#if DM_VERSION >= 510
				//this is a queue for any SS skipped or paused for tick reasons, to be ran first next tick
				if (priority_queue.len)
					priorityrunning = priority_queue.len
					subsystemstorun = priority_queue | subsystemstorun
#endif
				var/ran_subsystems = 0
				while(subsystemstorun.len)
					var/datum/subsystem/SS = subsystemstorun[1]
					subsystemstorun.Cut(1, 2)
#if DM_VERSION >= 510
					if (world.tick_usage > TICK_LIMIT_MC)
#else
					if(world.cpu >= 100)
#endif
						break
#if DM_VERSION >= 510
					if(priorityrunning)
						if(!priority_queue.len || !(SS in priority_queue))
							priorityrunning = 0 //end of priority queue items
						else
							priority_queue -= SS
#endif
					if(SS.can_fire > 0)
						if(priorityrunning || ((SS.next_fire <= world.time) && (SS.last_fire + (SS.wait * 0.75) <= world.time)))
#if DM_VERSION >= 510
							if(!priorityrunning && (world.tick_usage + SS.tick_usage > TICK_LIMIT_TO_RUN) && (SS.last_fire + (SS.wait*1.25) > world.time))
								if(!SS.dynamic_wait)
									priority_queue += SS
								continue
#endif
							//we can't reset SS.paused after we fire, incase it pauses again, so we cache it and
							//	send it to SS.fire()
							var/paused = SS.paused
							SS.paused = 0
							ran_subsystems = 1
							timer = world.timeofday
							last_type_processed = SS.type
							SS.last_fire = world.time
#if DM_VERSION >= 510
							var/tick_usage = world.tick_usage
#endif
							SS.fire(paused) // Fire the subsystem
#if DM_VERSION >= 510
							if(priorityrunning)
								priorityrunning--
							var/newusage = max(world.tick_usage - tick_usage, 0)
							if(newusage < SS.tick_usage)
								SS.tick_usage = MC_AVERAGE_SLOW(SS.tick_usage,world.tick_usage - tick_usage)
							else
								SS.tick_usage = MC_AVERAGE_FAST(SS.tick_usage,world.tick_usage - tick_usage)
#endif
							SS.cost = max(MC_AVERAGE(SS.cost, world.timeofday - timer), 0)
							if(SS.dynamic_wait) // Adjust wait depending on lag.
								var/oldwait = SS.wait
								var/global_delta = (subsystem_cost - (SS.cost / (SS.wait / 10))) - 1
								var/newwait = (SS.cost - SS.dwait_buffer + global_delta) * SS.dwait_delta
								newwait = newwait * (world.cpu / 100 + 1)
								//smooth out wait changes, but only if going down
								if(newwait < oldwait)
									newwait = MC_AVERAGE(oldwait, newwait)
								SS.wait = Clamp(newwait, SS.dwait_lower, SS.dwait_upper)
								if(SS.paused)
									SS.next_fire = world.time //run it next tick if we can, but don't priority queue it, as we are a dynamic wait subsystem
								else //not paused, it ran all the way thru, normal wait time
									SS.next_fire = world.time + SS.wait
							else
								if(!paused)
									SS.next_fire += SS.wait
							if(!SS.paused)
								SS.times_fired++
#if DM_VERSION < 510
							sleep(0)
#endif

				cost = max(MC_AVERAGE(cost, world.timeofday - start_time), 0)
				if(ran_subsystems)
					var/oldcost = subsystem_cost
					var/newcost = 0
					for(var/datum/subsystem/SS in subsystems)
						if (!SS.can_fire)
							continue
						newcost += SS.cost / (SS.wait / 10)
						subsystem_cost = MC_AVERAGE(oldcost, newcost)

				var/extrasleep = 0
				// If we are loading the server too much, sleep a bit extra...
				if(world.cpu >= 75)
					extrasleep += (extrasleep + world.tick_lag) * ((world.cpu-50)/10)
#if DM_VERSION < 510
				if(world.cpu >= 100)
					if(!old_fps)
						old_fps = world.fps
						//byond bug, if we go over 120 fps and world.fps is higher then 10, the bad things that happen are made worst.
						world.fps = 10
				else if(old_fps && world.cpu < 50)
					world.fps = old_fps
					old_fps = null
#endif
				sleep(world.tick_lag*processing+extrasleep)

			else
				sleep(50)

#undef MC_AVERAGE_FAST
#undef MC_AVERAGE
#undef MC_AVERAGE_SLOW

/datum/controller/master/proc/stat_entry()
	if(!statclick)
		statclick = new/obj/effect/statclick/debug("Initializing...", src)

	stat("Master Controller:", statclick.update("[round(Master.cost, 0.01)]ds (Iteration:[Master.iteration])"))


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

/datum/subsystem
	// Metadata; you should define these.
	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/display = 100		//display affects order the subsystem is displayed in the MC tab

	// Dynamic Wait
	// A system for scaling a subsystem's fire rate based on lag.
	// The algorithm is: (cost - dwait_buffer + subsystem_cost) * dwait_delta
	// Defaults are pretty sane for most use cases.
	// You can change how quickly it starts scaling back with dwait_buffer,
	// and you can change how much it scales back with dwait_delta.

	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 = 7		//How much should processing time effect dwait. or basically: wait = cost*dwait_delta
	var/dwait_buffer = 0.7	//This number is subtracted from the processing time before calculating its new wait

	// Bookkeeping variables; probably shouldn't mess with these.
	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
#if DM_VERSION >= 510
	var/tick_usage = 0		//average tick usage
#endif
	var/paused =0			//was this subsystem paused mid fire.
	var/times_fired = 0		//number of times we have called fire()

	// The object used for the clickable stat() button.
	var/obj/effect/statclick/statclick

// 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(resumed = 0)
	set waitfor = 0 //this should not be depended upon, this is just to solve issues with sleeps messing up tick tracking
	can_fire = 0

#if DM_VERSION >= 510
/datum/subsystem/proc/pause()
	. = 1
	if (!dynamic_wait)
		Master.priority_queue += src
	paused = 1
#endif

//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) // If only initialized for one Z-level.
		testing(msg)
		return time
	world << "[msg]"
	return time

//hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc.
/datum/subsystem/proc/stat_entry(msg)
	if(!statclick)
		statclick = new/obj/effect/statclick/debug("Initializing...", src)

	var/dwait = ""
	if(dynamic_wait)
		dwait = "DWait:[round(wait,0.1)]ds "

	if(can_fire)
#if DM_VERSION >= 510
		msg = "[round(cost,0.01)]ds|[round(tick_usage,1)]%\t[dwait][msg]"
#else
		msg = "[round(cost,0.01)]ds\t[dwait][msg]"
#endif
	else
		msg = "OFFLINE\t[msg]"

	stat(name, statclick.update(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(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()

//this is so the subsystem doesn't rapid fire to make up missed ticks causing more lag
/datum/subsystem/on_varedit(edited_var)
	if (edited_var == "can_fire" && can_fire)
		next_fire = world.time + wait


code:
#define SSAIR_PIPENETS 1
#define SSAIR_ATMOSMACHINERY 2
#define SSAIR_ACTIVETURFS 3
#define SSAIR_EXCITEDGROUPS 4
#define SSAIR_HIGHPRESSURE 5
#define SSAIR_HOTSPOTS 6
#define SSAIR_SUPERCONDUCTIVITY 7
var/datum/subsystem/air/SSair

/datum/subsystem/air
	name = "Air"
	priority = -1
	wait = 5
	dynamic_wait = 1
	dwait_upper = 300
	dwait_delta = 7
	display = 1

	var/cost_turfs = 0
	var/cost_groups = 0
	var/cost_highpressure = 0
	var/cost_hotspots = 0
	var/cost_superconductivity = 0
	var/cost_pipenets = 0
	var/cost_atmos_machinery = 0

	var/list/excited_groups = list()
	var/list/active_turfs = list()
	var/list/hotspots = list()
	var/list/networks = list()
	var/list/obj/machinery/atmos_machinery = list()

	//Special functions lists
	var/list/turf/active_super_conductivity = list()
	var/list/turf/open/high_pressure_delta = list()

	var/list/currentrun = list()
	var/currentpart = SSAIR_PIPENETS

/datum/subsystem/air/New()
	NEW_SS_GLOBAL(SSair)

/datum/subsystem/air/stat_entry(msg)
	msg += "C:{"
	msg += "AT:[round(cost_turfs,0.01)]|"
	msg += "EG:[round(cost_groups,0.01)]|"
	msg += "HP:[round(cost_highpressure,0.01)]|"
	msg += "HS:[round(cost_hotspots,0.01)]|"
	msg += "SC:[round(cost_superconductivity,0.01)]|"
	msg += "PN:[round(cost_pipenets,0.01)]|"
	msg += "AM:[round(cost_atmos_machinery,0.01)]"
	msg += "} "
	msg +=  "AT:[active_turfs.len]|"
	msg +=  "EG:[excited_groups.len]|"
	msg +=  "HS:[hotspots.len]|"
	msg +=  "AS:[active_super_conductivity.len]"
	..(msg)

/datum/subsystem/air/Initialize(timeofday, zlevel)
	setup_allturfs(zlevel)
	setup_atmos_machinery(zlevel)
	setup_pipenets(zlevel)
	..()

#define MC_AVERAGE(average, current) (0.8*(average) + 0.2*(current))
/datum/subsystem/air/fire(resumed = 0)
	var/timer = world.timeofday

	if(currentpart == SSAIR_PIPENETS || !resumed)
		process_pipenets(resumed)
		cost_pipenets = MC_AVERAGE(cost_pipenets, (world.timeofday - timer))
		if(paused)
			return
		resumed = 0
		currentpart = SSAIR_ATMOSMACHINERY

	if(currentpart == SSAIR_ATMOSMACHINERY)
		timer = world.timeofday
		process_atmos_machinery(resumed)
		cost_atmos_machinery = MC_AVERAGE(cost_atmos_machinery, (world.timeofday - timer))
		if(paused)
			return
		resumed = 0
		currentpart = SSAIR_ACTIVETURFS

	if(currentpart == SSAIR_ACTIVETURFS)
		timer = world.timeofday
		process_active_turfs(resumed)
		cost_turfs = MC_AVERAGE(cost_turfs, (world.timeofday - timer))
		if(paused)
			return
		resumed = 0
		currentpart = SSAIR_EXCITEDGROUPS

	if(currentpart == SSAIR_EXCITEDGROUPS)
		timer = world.timeofday
		process_excited_groups(resumed)
		cost_groups = MC_AVERAGE(cost_groups, (world.timeofday - timer))
		if(paused)
			return
		resumed = 0
		currentpart = SSAIR_HIGHPRESSURE

	if(currentpart == SSAIR_HIGHPRESSURE)
		timer = world.timeofday
		process_high_pressure_delta(resumed)
		cost_highpressure = MC_AVERAGE(cost_highpressure, (world.timeofday - timer))
		if(paused)
			return
		resumed = 0
		currentpart = SSAIR_HOTSPOTS

	if(currentpart == SSAIR_HOTSPOTS)
		timer = world.timeofday
		process_hotspots(resumed)
		cost_hotspots = MC_AVERAGE(cost_hotspots, (world.timeofday - timer))
		if(paused)
			return
		resumed = 0
		currentpart = SSAIR_SUPERCONDUCTIVITY

	if(currentpart == SSAIR_SUPERCONDUCTIVITY)
		timer = world.timeofday
		process_super_conductivity(resumed)
		cost_superconductivity = MC_AVERAGE(cost_superconductivity, (world.timeofday - timer))
		if(paused)
			return
		resumed = 0
	currentpart = SSAIR_PIPENETS

#undef MC_AVERAGE

/datum/subsystem/air/proc/process_pipenets(resumed = 0)
	if (!resumed)
		src.currentrun = networks.Copy()
	//cache for sanic speed (lists are references anyways)
	var/list/currentrun = src.currentrun
	while(currentrun.len)
		var/datum/thing = currentrun[1]
		currentrun.Cut(1, 2)
		if(thing)
			thing.process()
		else
			networks.Remove(thing)
		if(MC_TICK_CHECK)
			return

/datum/subsystem/air/proc/process_atmos_machinery(resumed = 0)
	var/seconds = wait * 0.1
	if (!resumed)
		src.currentrun = atmos_machinery.Copy()
	//cache for sanic speed (lists are references anyways)
	var/list/currentrun = src.currentrun
	while(currentrun.len)
		var/obj/machinery/M = currentrun[1]
		currentrun.Cut(1, 2)
		if(!M || (M.process_atmos(seconds) == PROCESS_KILL))
			atmos_machinery.Remove(M)
		if(MC_TICK_CHECK)
			return

/datum/subsystem/air/proc/process_super_conductivity(resumed = 0)
	if (!resumed)
		src.currentrun = active_super_conductivity.Copy()
	//cache for sanic speed (lists are references anyways)
	var/list/currentrun = src.currentrun
	while(currentrun.len)
		var/turf/T = currentrun[1]
		currentrun.Cut(1, 2)
		T.super_conduct()
		if(MC_TICK_CHECK)
			return

/datum/subsystem/air/proc/process_hotspots(resumed = 0)
	if (!resumed)
		src.currentrun = hotspots.Copy()
	//cache for sanic speed (lists are references anyways)
	var/list/currentrun = src.currentrun
	while(currentrun.len)
		var/obj/effect/hotspot/H = currentrun[1]
		currentrun.Cut(1, 2)
		if (H)
			H.process()
		else
			hotspots -= H
		if(MC_TICK_CHECK)
			return

/datum/subsystem/air/proc/process_high_pressure_delta(resumed = 0)
	while (high_pressure_delta.len)
		var/turf/open/T = high_pressure_delta[1]
		high_pressure_delta.Cut(1,2)
		T.high_pressure_movements()
		T.pressure_difference = 0
		if(MC_TICK_CHECK)
			return

/datum/subsystem/air/proc/process_active_turfs(resumed = 0)
	//cache for sanic speed
	var/fire_count = times_fired
	if (!resumed)
		src.currentrun = active_turfs.Copy()
	//cache for sanic speed (lists are references anyways)
	var/list/currentrun = src.currentrun
	while(currentrun.len)
		var/turf/open/T = currentrun[1]
		currentrun.Cut(1, 2)
		if (T)
			T.process_cell(fire_count)
		if (MC_TICK_CHECK)
			return

/datum/subsystem/air/proc/process_excited_groups(resumed = 0)
	if (!resumed)
		src.currentrun = excited_groups.Copy()
	//cache for sanic speed (lists are references anyways)
	var/list/currentrun = src.currentrun
	while(currentrun.len)
		var/datum/excited_group/EG = currentrun[1]
		currentrun.Cut(1, 2)
		EG.breakdown_cooldown++
		EG.dismantle_cooldown++
		if(EG.breakdown_cooldown >= EXCITED_GROUP_BREAKDOWN_CYCLES)
			EG.self_breakdown()
		else if(EG.dismantle_cooldown >= EXCITED_GROUP_DISMANTLE_CYCLES)
			EG.dismantle()
		if (MC_TICK_CHECK)
			return

/datum/subsystem/air/proc/remove_from_active(turf/open/T)
	active_turfs -= T
	if(istype(T))
		T.excited = 0
		if(T.excited_group)
			T.excited_group.garbage_collect()

/datum/subsystem/air/proc/add_to_active(turf/open/T, blockchanges = 1)
	if(istype(T) && T.air)
		T.excited = 1
		active_turfs |= T
		if(blockchanges && T.excited_group)
			T.excited_group.garbage_collect()
	else
		for(var/turf/S in T.atmos_adjacent_turfs)
			add_to_active(S)

/datum/subsystem/air/proc/setup_allturfs(z_level)
	var/z_start = 1
	var/z_finish = world.maxz
	var/times_fired = ++src.times_fired
	if(1 <= z_level && z_level <= world.maxz)
		z_level = round(z_level)
		z_start = z_level
		z_finish = z_level

	var/list/turfs_to_init = block(locate(1, 1, z_start), locate(world.maxx, world.maxy, z_finish))
	var/list/active_turfs = src.active_turfs

	for(var/thing in turfs_to_init)
		var/turf/T = thing
		active_turfs -= T
		T.Initalize_Atmos(times_fired)

	if(active_turfs.len)
		warning("There are [active_turfs.len] active turfs at roundstart, this is a mapping error caused by a difference of the air between the adjacent turfs. You can see its coordinates using \"Mapping -> Show roundstart AT list\" verb (debug verbs required)")
		for(var/turf/T in active_turfs)
			active_turfs_startlist += text("[T.x], [T.y], [T.z]\n")

/datum/subsystem/air/proc/setup_atmos_machinery(z_level)
	for (var/obj/machinery/atmospherics/AM in atmos_machinery)
		if (z_level && AM.z != z_level)
			CHECK_TICK
			continue
		AM.atmosinit()
		CHECK_TICK

//this can't be done with setup_atmos_machinery() because
//	all atmos machinery has to initalize before the first
//	pipenet can be built.
/datum/subsystem/air/proc/setup_pipenets(z_level)
	for (var/obj/machinery/atmospherics/AM in atmos_machinery)
		if (z_level && AM.z != z_level)
			CHECK_TICK
			continue
		AM.build_network()
		CHECK_TICK

/datum/subsystem/air/proc/setup_template_machinery(list/atmos_machines)
	for(var/A in atmos_machines)
		var/obj/machinery/atmospherics/AM = A
		AM.atmosinit()
		CHECK_TICK

	for(var/A in atmos_machines)
		var/obj/machinery/atmospherics/AM = A
		AM.build_network()
		CHECK_TICK

#undef SSAIR_PIPENETS
#undef SSAIR_ATMOSMACHINERY
#undef SSAIR_ACTIVETURFS
#undef SSAIR_EXCITEDGROUPS
#undef SSAIR_HIGHPRESSURE
#undef SSAIR_HOTSPOT
#undef SSAIR_SUPERCONDUCTIVITY


code:
var/datum/subsystem/assets/SSasset

/datum/subsystem/assets
	name = "Assets"
	priority = -3

	var/list/cache = list()

/datum/subsystem/assets/New()
	NEW_SS_GLOBAL(SSasset)

/datum/subsystem/assets/Initialize(timeofday, zlevel)
	if (zlevel)
		return ..()
	for(var/type in typesof(/datum/asset) - list(/datum/asset, /datum/asset/simple))
		var/datum/asset/A = new type()
		A.register()

	for(var/client/C in clients)
		// Doing this to a client too soon after they've connected can cause issues, also the proc we call sleeps.
		spawn(10)
			getFilesSlow(C, cache, FALSE)
	..()


code:
var/datum/subsystem/diseases/SSdisease

/datum/subsystem/diseases
	name = "Diseases"
	priority = 7
	var/list/currentrun = list()
	var/list/processing = list()

/datum/subsystem/diseases/New()
	NEW_SS_GLOBAL(SSdisease)

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

/datum/subsystem/diseases/fire(resumed = 0)
	if(!resumed)
		src.currentrun = processing.Copy()
	//cache for sanic speed (lists are references anyways)
	var/list/currentrun = src.currentrun

	while(currentrun.len)
		var/datum/thing = currentrun[1]
		currentrun.Cut(1, 2)
		if(thing)
			thing.process()
		else
			processing.Remove(thing)
		if (MC_TICK_CHECK)
			return


code:
var/datum/subsystem/events/SSevent

/datum/subsystem/events
	name = "Events"
	priority = 6

	var/list/control = list()	//list of all datum/round_event_control. Used for selecting events based on weight and occurrences.
	var/list/running = list()	//list of all existing /datum/round_event
	var/list/currentrun = list()

	var/scheduled = 0			//The next world.time that a naturally occuring random event can be selected.
	var/frequency_lower = 1800	//3 minutes lower bound.
	var/frequency_upper = 6000	//10 minutes upper bound. Basically an event will happen every 3 to 10 minutes.

	var/list/holidays			//List of all holidays occuring today or null if no holidays
	var/wizardmode = 0

/datum/subsystem/events/New()
	NEW_SS_GLOBAL(SSevent)

/datum/subsystem/events/Initialize(time, zlevel)
	if (zlevel)
		return ..()
	for(var/type in typesof(/datum/round_event_control))
		var/datum/round_event_control/E = new type()
		if(!E.typepath)
			continue				//don't want this one! leave it for the garbage collector
		control += E				//add it to the list of all events (controls)
	reschedule()
	getHoliday()
	..()

/datum/subsystem/events/fire(resumed = 0)
	if(!resumed)
		checkEvent() //only check these if we aren't resuming a paused fire
		src.currentrun = running.Copy()

	//cache for sanic speed (lists are references anyways)
	var/list/currentrun = src.currentrun

	while(currentrun.len)
		var/datum/thing = currentrun[1]
		currentrun.Cut(1, 2)
		if(thing)
			thing.process()
		else
			running.Remove(thing)
		if (MC_TICK_CHECK)
			return

//checks if we should select a random event yet, and reschedules if necessary
/datum/subsystem/events/proc/checkEvent()
	if(scheduled <= world.time)
		spawnEvent()
		reschedule()

//decides which world.time we should select another random event at.
/datum/subsystem/events/proc/reschedule()
	scheduled = world.time + rand(frequency_lower, max(frequency_lower,frequency_upper))

//selects a random event based on whether it can occur and it's 'weight'(probability)
/datum/subsystem/events/proc/spawnEvent()
	if(!config.allow_random_events)
//		var/datum/round_event_control/E = locate(/datum/round_event_control/dust) in control
//		if(E)	E.runEvent()
		return

	var/gamemode = ticker.mode.config_tag
	var/players_amt = get_active_player_count(alive_check = 1, afk_check = 1, human_check = 1)
	// Only alive, non-AFK human players count towards this.

	var/sum_of_weights = 0
	for(var/datum/round_event_control/E in control)
		if(!E.canSpawnEvent(players_amt, gamemode))
			continue
		if(E.weight < 0)						//for round-start events etc.
			if(E.runEvent() == PROCESS_KILL)
				E.max_occurrences = 0
				continue
			if (E.alertadmins)
				message_admins("Random Event triggering: [E.name] ([E.typepath])")
			log_game("Random Event triggering: [E.name] ([E.typepath])")
			return
		sum_of_weights += E.weight

	sum_of_weights = rand(0,sum_of_weights)	//reusing this variable. It now represents the 'weight' we want to select

	for(var/datum/round_event_control/E in control)
		if(!E.canSpawnEvent(players_amt, gamemode))
			continue
		sum_of_weights -= E.weight

		if(sum_of_weights <= 0)				//we've hit our goal
			if(E.runEvent() == PROCESS_KILL)//we couldn't run this event for some reason, set its max_occurrences to 0
				E.max_occurrences = 0
				continue
			if (E.alertadmins)
				message_admins("Random Event triggering: [E.name] ([E.typepath])")
			log_game("Random Event triggering: [E.name] ([E.typepath])")
			return

/datum/round_event/proc/findEventArea() //Here's a nice proc to use to find an area for your event to land in!
	var/list/safe_areas = list(
	/area/turret_protected/ai,
	/area/turret_protected/ai_upload,
	/area/engine,
	/area/solar,
	/area/holodeck,
	/area/shuttle
	)

	//These are needed because /area/engine has to be removed from the list, but we still want these areas to get fucked up.
	var/list/danger_areas = list(
	/area/engine/break_room,
	/area/engine/chiefs_office)

	//Need to locate() as it's just a list of paths.
	return locate(pick((the_station_areas - safe_areas) + danger_areas))

//allows a client to trigger an event
//aka Badmin Central
/client/proc/forceEvent()
	set name = "Trigger Event"
	set category = "Fun"

	if(!holder ||!check_rights(R_FUN))
		return

	holder.forceEvent()

/datum/admins/proc/forceEvent()
	var/dat 	= ""
	var/normal 	= ""
	var/magic 	= ""
	var/holiday = ""
	for(var/datum/round_event_control/E in SSevent.control)
		dat = "
[E]"
		if(E.holidayID)
			holiday	+= dat
		else if(E.wizardevent)
			magic 	+= dat
		else
			normal 	+= dat

	dat = normal + "
" + magic + "
" + holiday

	var/datum/browser/popup = new(usr, "forceevent", "Force Random Event", 300, 750)
	popup.set_content(dat)
	popup.open()

/*
//////////////
// HOLIDAYS //
//////////////
//Uncommenting ALLOW_HOLIDAYS in config.txt will enable holidays

//It's easy to add stuff. Just add a holiday datum in code/modules/holiday/holidays.dm
//You can then check if it's a special day in any code in the game by doing if(SSevent.holidays["Groundhog Day"])

//You can also make holiday random events easily thanks to Pete/Gia's system.
//simply make a random event normally, then assign it a holidayID string which matches the holiday's name.
//Anything with a holidayID, which isn't in the holidays list, will never occur.

//Please, Don't spam stuff up with stupid stuff (key example being april-fools Pooh/ERP/etc),
//And don't forget: CHECK YOUR CODE!!!! We don't want any zero-day bugs which happen only on holidays and never get found/fixed!

//////////////////////////////////////////////////////////////////////////////////////////////////////////
//ALSO, MOST IMPORTANTLY: Don't add stupid stuff! Discuss bonus content with Project-Heads first please!//
//////////////////////////////////////////////////////////////////////////////////////////////////////////
*/

//sets up the holidays and holidays list
/datum/subsystem/events/proc/getHoliday()
	if(!config.allow_holidays)
		return		// Holiday stuff was not enabled in the config!

	var/YY = text2num(time2text(world.timeofday, "YY")) 	// get the current year
	var/MM = text2num(time2text(world.timeofday, "MM")) 	// get the current month
	var/DD = text2num(time2text(world.timeofday, "DD")) 	// get the current day

	for(var/H in subtypesof(/datum/holiday))
		var/datum/holiday/holiday = new H()
		if(holiday.shouldCelebrate(DD, MM, YY))
			holiday.celebrate()
			if(!holidays)
				holidays = list()
			holidays[holiday.name] = holiday

	if(holidays)
		holidays = shuffle(holidays)
		world.update_status()

/datum/subsystem/events/proc/toggleWizardmode()
	wizardmode = !wizardmode
	message_admins("Summon Events has been [wizardmode ? "enabled, events will occur every [SSevent.frequency_lower / 600] to [SSevent.frequency_upper / 600] minutes" : "disabled"]!")
	log_game("Summon Events was [wizardmode ? "enabled" : "disabled"]!")

/datum/subsystem/events/proc/resetFrequency()
	frequency_lower = initial(frequency_lower)
	frequency_upper = initial(frequency_upper)


code:
var/datum/subsystem/fastprocess/SSfastprocess

/datum/subsystem/fastprocess
	name = "Fast Process"
	priority = 12
	wait = 1
	dynamic_wait = 1
	dwait_upper = 10
	dwait_buffer = 0
	dwait_lower = 1
	dwait_delta = 6

	var/list/processing = list()
	var/list/currentrun = list()

/datum/subsystem/fastprocess/New()
	NEW_SS_GLOBAL(SSfastprocess)

/datum/subsystem/fastprocess/stat_entry()
	..("FP:[processing.len]")

/datum/subsystem/fastprocess/fire(resumed = 0)
	if (!resumed)
		src.currentrun = processing.Copy()
	//cache for sanic speed (lists are references anyways)
	var/list/currentrun = src.currentrun

	while(currentrun.len)
		var/datum/thing = currentrun[1]
		currentrun.Cut(1, 2)
		if(thing)
			thing.process(wait)
		else
			SSfastprocess.processing -= thing
		if (MC_TICK_CHECK)
			return


code:
var/datum/subsystem/garbage_collector/SSgarbage

/datum/subsystem/garbage_collector
	name = "Garbage"
	priority = -1
	wait = 5
	dynamic_wait = 1
	dwait_upper = 50
	dwait_delta = 10
	dwait_buffer = 0
	display = 2

	can_fire = 1 // This needs to fire before round start.

	var/collection_timeout = 300// deciseconds to wait to let running procs finish before we just say fuck it and force del() the object
	var/max_run_time = 1		// how long, in deciseconds, can we run before waiting for the next tick
	var/delslasttick = 0		// number of del()'s we've done this tick
	var/gcedlasttick = 0		// number of things that gc'ed last tick
	var/totaldels = 0
	var/totalgcs = 0

	var/list/queue = list() 	// list of refID's of things that should be garbage collected
								// refID's are associated with the time at which they time out and need to be manually del()
								// we do this so we aren't constantly locating them and preventing them from being gc'd

	var/list/tobequeued = list()	//We store the references of things to be added to the queue seperately so we can spread out GC overhead over a few ticks

	var/list/didntgc = list()	// list of all types that have failed to GC associated with the number of times that's happened.
								// the types are stored as strings

	var/list/noqdelhint = list()// list of all types that do not return a QDEL_HINT

#ifdef TESTING
	var/list/qdel_list = list()	// list of all types that have been qdel()eted
#endif

/datum/subsystem/garbage_collector/New()
	NEW_SS_GLOBAL(SSgarbage)

/datum/subsystem/garbage_collector/stat_entry(msg)
	msg += "Q:[queue.len]|D:[delslasttick]|G:[gcedlasttick]|"
	msg += "GR:"
	if (!(delslasttick+gcedlasttick))
		msg += "n/a|"
	else
		msg += "[round((gcedlasttick/(delslasttick+gcedlasttick))*100, 0.01)]%|"

	msg += "TD:[totaldels]|TG:[totalgcs]|"
	if (!(totaldels+totalgcs))
		msg += "n/a|"
	else
		msg += "TGR:[round((totalgcs/(totaldels+totalgcs))*100, 0.01)]%"
	..(msg)

/datum/subsystem/garbage_collector/fire()
	var/time_to_stop = world.timeofday + max_run_time
	HandleToBeQueued(time_to_stop)
	HandleQueue(time_to_stop)

//If you see this proc high on the profile, what you are really seeing is the garbage collection/soft delete overhead in byond.
//Don't attempt to optimize, not worth the effort.
/datum/subsystem/garbage_collector/proc/HandleToBeQueued(time_to_stop)
	var/list/tobequeued = src.tobequeued
	while(tobequeued.len && world.timeofday < time_to_stop)
		if (MC_TICK_CHECK)
			break
		var/ref = tobequeued[1]
		Queue(ref)
		tobequeued.Cut(1, 2)

/datum/subsystem/garbage_collector/proc/HandleQueue(time_to_stop)
	delslasttick = 0
	gcedlasttick = 0
	var/time_to_kill = world.time - collection_timeout // Anything qdel() but not GC'd BEFORE this time needs to be manually del()
	var/list/queue = src.queue
	while(queue.len && world.timeofday < time_to_stop)
		if (MC_TICK_CHECK)
			break
		var/refID = queue[1]
		if (!refID)
			queue.Cut(1, 2)
			continue

		var/GCd_at_time = queue[refID]
		if(GCd_at_time > time_to_kill)
			break // Everything else is newer, skip them

		var/datum/A
		if (!istext(refID))
			del(A)
		else
			A = locate(refID)
			if (A && A.gc_destroyed == GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake
				// Something's still referring to the qdel'd object.  Kill it.
				testing("GC: -- \ref[A] | [A.type] was unable to be GC'd and was deleted --")
				didntgc["[A.type]"]++
				del(A)
				++delslasttick
				++totaldels
			else
				++gcedlasttick
				++totalgcs
		queue.Cut(1, 2)

/datum/subsystem/garbage_collector/proc/QueueForQueuing(datum/A)
	if (istype(A) && isnull(A.gc_destroyed))
		tobequeued += A
		A.gc_destroyed = -1

/datum/subsystem/garbage_collector/proc/Queue(datum/A)
	if (!istype(A) || (!isnull(A.gc_destroyed) && A.gc_destroyed >= 0))
		return
	var/gctime = world.time
	var/refid = "\ref[A]"

	A.gc_destroyed = gctime

	if (queue[refid])
		queue -= refid // Removing any previous references that were GC'd so that the current object will be at the end of the list.

	queue[refid] = gctime

/datum/subsystem/garbage_collector/proc/HardQueue(datum/A)
	if (!istype(A) || !isnull(A.gc_destroyed))
		return
	A.gc_destroyed = world.time
	queue -= A // Removing any previous references that were GC'd so that the current object will be at the end of the list.
	queue[A] = world.time

// Should be treated as a replacement for the 'del' keyword.
// Datums passed to this will be given a chance to clean up references to allow the GC to collect them.
/proc/qdel(datum/D)
	if(!D)
		return
#ifdef TESTING
	SSgarbage.qdel_list += "[A.type]"
#endif
	if(!istype(D))
		del(D)
	else if(isnull(D.gc_destroyed))
		var/hint = D.Destroy() // Let our friend know they're about to get fucked up.
		if(!D)
			return
		switch(hint)
			if (QDEL_HINT_QUEUE)		//qdel should queue the object for deletion.
				SSgarbage.QueueForQueuing(D)
			if (QDEL_HINT_LETMELIVE)	//qdel should let the object live after calling destory.
				return
			if (QDEL_HINT_IWILLGC)		//functionally the same as the above. qdel should assume the object will gc on its own, and not check it.
				return
			if (QDEL_HINT_HARDDEL)		//qdel should assume this object won't gc, and queue a hard delete using a hard reference to save time from the locate()
				SSgarbage.HardQueue(D)
			if (QDEL_HINT_HARDDEL_NOW)	//qdel should assume this object won't gc, and hard del it post haste.
				del(D)
			if (QDEL_HINT_PUTINPOOL)	//qdel will put this object in the pool.
				PlaceInPool(D, 0)
			if (QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion.
				SSgarbage.QueueForQueuing(D)
				#ifdef TESTING
				A.find_references()
				#endif
			else
				if(!("[D.type]" in SSgarbage.noqdelhint))
					SSgarbage.noqdelhint += "[D.type]"
					testing("WARNING: [D.type] is not returning a qdel hint. It is being placed in the queue. Further instances of this type will also be queued.")
				SSgarbage.QueueForQueuing(D)

// Returns 1 if the object has been queued for deletion.
/proc/qdeleted(datum/D)
	if(!istype(D))
		return FALSE
	if(D.gc_destroyed)
		return TRUE
	return FALSE

// Default implementation of clean-up code.
// This should be overridden to remove all references pointing to the object being destroyed.
// Return the appropriate QDEL_HINT; in most cases this is QDEL_HINT_QUEUE.
/datum/proc/Destroy()
	tag = null
	return QDEL_HINT_QUEUE

/datum/var/gc_destroyed //Time when this object was destroyed.

#ifdef TESTING
/client/var/running_find_references
/datum/var/running_find_references

/datum/verb/find_references()
	set category = "Debug"
	set name = "Find References"
	set background = 1
	set src in world

	running_find_references = type
	if(usr && usr.client)
		if(usr.client.running_find_references)
			testing("CANCELLED search for references to a [usr.client.running_find_references].")
			usr.client.running_find_references = null
			running_find_references = null
			//restart the garbage collector
			SSgarbage.can_fire = 1
			SSgarbage.next_fire = world.time + world.tick_lag
			return

		if(alert("Running this will create a lot of lag until it finishes.  You can cancel it by running it again.  Would you like to begin the search?", "Find References", "Yes", "No") == "No")
			running_find_references = null
			return

	//this keeps the garbage collector from failing to collect objects being searched for in here
	SSgarbage.can_fire = 0

	if(usr && usr.client)
		usr.client.running_find_references = type

	testing("Beginning search for references to a [type].")
	var/list/things = list()
	for(var/client/thing)
		things |= thing
	for(var/datum/thing)
		things |= thing
	testing("Collected list of things in search for references to a [type]. ([things.len] Thing\s)")
	for(var/datum/thing in things)
		if(usr && usr.client && !usr.client.running_find_references) return
		for(var/varname in thing.vars)
			var/variable = thing.vars[varname]
			if(variable == src)
				testing("Found [src.type] \ref[src] in [thing.type]'s [varname] var.")
			else if(islist(variable))
				if(src in variable)
					testing("Found [src.type] \ref[src] in [thing.type]'s [varname] list var.")
	testing("Completed search for references to a [type].")
	if(usr && usr.client)
		usr.client.running_find_references = null
	running_find_references = null

	//restart the garbage collector
	SSgarbage.can_fire = 1
	SSgarbage.next_fire = world.time + world.tick_lag

/client/verb/purge_all_destroyed_objects()
	set category = "Debug"
	if(SSgarbage)
		while(SSgarbage.queue.len)
			var/datum/o = locate(SSgarbage.queue[1])
			if(istype(o) && o.gc_destroyed)
				del(o)
				SSgarbage.totaldels++
			SSgarbage.queue.Cut(1, 2)

/datum/verb/qdel_then_find_references()
	set category = "Debug"
	set name = "qdel() then Find References"
	set background = 1
	set src in world

	qdel(src)
	if(!running_find_references)
		find_references()

/client/verb/show_qdeleted()
	set category = "Debug"
	set name = "Show qdel() Log"
	set desc = "Render the qdel() log and display it"

	var/dat = "List of things that have been qdel()eted this round

"

	var/tmplist = list()
	for(var/elem in SSgarbage.qdel_list)
		if(!(elem in tmplist))
			tmplist[elem] = 0
		tmplist[elem]++

	for(var/path in tmplist)
		dat += "[path] - [tmplist[path]] times
"

	usr << browse(dat, "window=qdeletedlog")
#endif


code:
var/datum/subsystem/icon_smooth/SSicon_smooth

/datum/subsystem/icon_smooth
	name = "Icon Smoothing"
	priority = -5
	wait = 5
	var/list/smooth_queue = list()

/datum/subsystem/icon_smooth/New()
	NEW_SS_GLOBAL(SSicon_smooth)

/datum/subsystem/icon_smooth/fire()
	for(var/atom in smooth_queue)
		ss_smooth_icon(atom)
		smooth_queue -= atom
		if (MC_TICK_CHECK)
			return

/datum/subsystem/icon_smooth/Initialize()
	smooth_zlevel(1,TRUE)
	smooth_zlevel(2,TRUE)
	for(var/V in smooth_queue)
		var/atom/A = V
		if(A.z == 1 || A.z == 2)
			smooth_queue -= A
	..()


code:
var/datum/subsystem/job/SSjob

/datum/subsystem/job
	name = "Jobs"
	priority = 5

	var/list/occupations = list()		//List of all jobs
	var/list/unassigned = list()		//Players who need jobs
	var/list/job_debug = list()			//Debug info
	var/initial_players_to_assign = 0 	//used for checking against population caps

/datum/subsystem/job/New()
	NEW_SS_GLOBAL(SSjob)

/datum/subsystem/job/Initialize(timeofday, zlevel)
	if (zlevel)
		return ..()
	SetupOccupations()
	if(config.load_jobs_from_txt)
		LoadJobs()
	..()

/datum/subsystem/job/proc/SetupOccupations(faction = "Station")
	occupations = list()
	var/list/all_jobs = subtypesof(/datum/job)
	if(!all_jobs.len)
		world << "Error setting up jobs, no job datums found"
		return 0

	for(var/J in all_jobs)
		var/datum/job/job = new J()
		if(!job)
			continue
		if(job.faction != faction)
			continue
		if(!job.config_check())
			continue
		occupations += job

	return 1

/datum/subsystem/job/proc/Debug(text)
	if(!Debug2)
		return 0
	job_debug.Add(text)
	return 1

/datum/subsystem/job/proc/GetJob(rank)
	if(!rank)
		return null
	for(var/datum/job/J in occupations)
		if(!J)
			continue
		if(J.title == rank)
			return J
	return null

/datum/subsystem/job/proc/AssignRole(mob/new_player/player, rank, latejoin=0)
	Debug("Running AR, Player: [player], Rank: [rank], LJ: [latejoin]")
	if(player && player.mind && rank)
		var/datum/job/job = GetJob(rank)
		if(!job)
			return 0
		if(jobban_isbanned(player, rank))
			return 0
		if(!job.player_old_enough(player.client))
			return 0
		var/position_limit = job.total_positions
		if(!latejoin)
			position_limit = job.spawn_positions
		Debug("Player: [player] is now Rank: [rank], JCP:[job.current_positions], JPL:[position_limit]")
		player.mind.assigned_role = rank
		unassigned -= player
		job.current_positions++
		return 1
	Debug("AR has failed, Player: [player], Rank: [rank]")
	return 0

/datum/subsystem/job/proc/FindOccupationCandidates(datum/job/job, level, flag)
	Debug("Running FOC, Job: [job], Level: [level], Flag: [flag]")
	var/list/candidates = list()
	for(var/mob/new_player/player in unassigned)
		if(jobban_isbanned(player, job.title))
			Debug("FOC isbanned failed, Player: [player]")
			continue
		if(!job.player_old_enough(player.client))
			Debug("FOC player not old enough, Player: [player]")
			continue
		if(flag && (!(flag in player.client.prefs.be_special)))
			Debug("FOC flag failed, Player: [player], Flag: [flag], ")
			continue
		if(player.mind && job.title in player.mind.restricted_roles)
			Debug("FOC incompatible with antagonist role, Player: [player]")
			continue
		if(config.enforce_human_authority && !player.client.prefs.pref_species.qualifies_for_rank(job.title, player.client.prefs.features))
			Debug("FOC non-human failed, Player: [player]")
			continue
		if(player.client.prefs.GetJobDepartment(job, level) & job.flag)
			Debug("FOC pass, Player: [player], Level:[level]")
			candidates += player
	return candidates

/datum/subsystem/job/proc/GiveRandomJob(mob/new_player/player)
	Debug("GRJ Giving random job, Player: [player]")
	for(var/datum/job/job in shuffle(occupations))
		if(!job)
			continue

		if(istype(job, GetJob("Assistant"))) // We don't want to give him assistant, that's boring!
			continue

		if(job.title in command_positions) //If you want a command position, select it!
			continue

		if(jobban_isbanned(player, job.title))
			Debug("GRJ isbanned failed, Player: [player], Job: [job.title]")
			continue

		if(!job.player_old_enough(player.client))
			Debug("GRJ player not old enough, Player: [player]")
			continue

		if(player.mind && job.title in player.mind.restricted_roles)
			Debug("GRJ incompatible with antagonist role, Player: [player], Job: [job.title]")
			continue

		if(config.enforce_human_authority && !player.client.prefs.pref_species.qualifies_for_rank(job.title, player.client.prefs.features))
			Debug("GRJ non-human failed, Player: [player]")
			continue

		if((job.current_positions < job.spawn_positions) || job.spawn_positions == -1)
			Debug("GRJ Random job given, Player: [player], Job: [job]")
			AssignRole(player, job.title)
			unassigned -= player
			break

/datum/subsystem/job/proc/ResetOccupations()
	for(var/mob/new_player/player in player_list)
		if((player) && (player.mind))
			player.mind.assigned_role = null
			player.mind.special_role = null
	SetupOccupations()
	unassigned = list()
	return

//This proc is called before the level loop of DivideOccupations() and will try to select a head, ignoring ALL non-head preferences for every level until
//it locates a head or runs out of levels to check
//This is basically to ensure that there's atleast a few heads in the round
/datum/subsystem/job/proc/FillHeadPosition()
	for(var/level = 1 to 3)
		for(var/command_position in command_positions)
			var/datum/job/job = GetJob(command_position)
			if(!job)
				continue
			if((job.current_positions >= job.total_positions) && job.total_positions != -1)
				continue
			var/list/candidates = FindOccupationCandidates(job, level)
			if(!candidates.len)
				continue
			var/mob/new_player/candidate = pick(candidates)
			if(AssignRole(candidate, command_position))
				return 1
	return 0

//This proc is called at the start of the level loop of DivideOccupations() and will cause head jobs to be checked before any other jobs of the same level
//This is also to ensure we get as many heads as possible
/datum/subsystem/job/proc/CheckHeadPositions(level)
	for(var/command_position in command_positions)
		var/datum/job/job = GetJob(command_position)
		if(!job)
			continue
		if((job.current_positions >= job.total_positions) && job.total_positions != -1)
			continue
		var/list/candidates = FindOccupationCandidates(job, level)
		if(!candidates.len)
			continue
		var/mob/new_player/candidate = pick(candidates)
		AssignRole(candidate, command_position)
	return

/datum/subsystem/job/proc/FillAIPosition()
	var/ai_selected = 0
	var/datum/job/job = GetJob("AI")
	if(!job)
		return 0
	for(var/i = job.total_positions, i > 0, i--)
		for(var/level = 1 to 3)
			var/list/candidates = list()
			candidates = FindOccupationCandidates(job, level)
			if(candidates.len)
				var/mob/new_player/candidate = pick(candidates)
				if(AssignRole(candidate, "AI"))
					ai_selected++
					break
	if(ai_selected)
		return 1
	return 0

/** Proc DivideOccupations
 *  fills var "assigned_role" for all ready players.
 *  This proc must not have any side effect besides of modifying "assigned_role".
 **/
/datum/subsystem/job/proc/DivideOccupations()
	//Setup new player list and get the jobs list
	Debug("Running DO")

	//Holder for Triumvirate is stored in the ticker, this just processes it
	if(ticker)
		for(var/datum/job/ai/A in occupations)
			if(ticker.triai)
				A.spawn_positions = 3

	//Get the players who are ready
	for(var/mob/new_player/player in player_list)
		if(player.ready && player.mind && !player.mind.assigned_role)
			unassigned += player

	initial_players_to_assign = unassigned.len

	Debug("DO, Len: [unassigned.len]")
	if(unassigned.len == 0)
		return 0

	//Scale number of open security officer slots to population
	setup_officer_positions()

	//Jobs will have fewer access permissions if the number of players exceeds the threshold defined in game_options.txt
	if(config.minimal_access_threshold)
		if(config.minimal_access_threshold > unassigned.len)
			config.jobs_have_minimal_access = 0
		else
			config.jobs_have_minimal_access = 1

	//Shuffle players and jobs
	unassigned = shuffle(unassigned)

	HandleFeedbackGathering()

	//People who wants to be assistants, sure, go on.
	Debug("DO, Running Assistant Check 1")
	var/datum/job/assist = new /datum/job/assistant()
	var/list/assistant_candidates = FindOccupationCandidates(assist, 3)
	Debug("AC1, Candidates: [assistant_candidates.len]")
	for(var/mob/new_player/player in assistant_candidates)
		Debug("AC1 pass, Player: [player]")
		AssignRole(player, "Assistant")
		assistant_candidates -= player
	Debug("DO, AC1 end")

	//Select one head
	Debug("DO, Running Head Check")
	FillHeadPosition()
	Debug("DO, Head Check end")

	//Check for an AI
	Debug("DO, Running AI Check")
	FillAIPosition()
	Debug("DO, AI Check end")

	//Other jobs are now checked
	Debug("DO, Running Standard Check")

	// New job giving system by Donkie
	// This will cause lots of more loops, but since it's only done once it shouldn't really matter much at all.
	// Hopefully this will add more randomness and fairness to job giving.

	// Loop through all levels from high to low
	var/list/shuffledoccupations = shuffle(occupations)
	for(var/level = 1 to 3)
		//Check the head jobs first each level
		CheckHeadPositions(level)

		// Loop through all unassigned players
		for(var/mob/new_player/player in unassigned)
			if(PopcapReached())
				RejectPlayer(player)

			// Loop through all jobs
			for(var/datum/job/job in shuffledoccupations) // SHUFFLE ME BABY
				if(!job)
					continue

				if(jobban_isbanned(player, job.title))
					Debug("DO isbanned failed, Player: [player], Job:[job.title]")
					continue

				if(!job.player_old_enough(player.client))
					Debug("DO player not old enough, Player: [player], Job:[job.title]")
					continue

				if(player.mind && job.title in player.mind.restricted_roles)
					Debug("DO incompatible with antagonist role, Player: [player], Job:[job.title]")
					continue

				if(config.enforce_human_authority && !player.client.prefs.pref_species.qualifies_for_rank(job.title, player.client.prefs.features))
					Debug("DO non-human failed, Player: [player], Job:[job.title]")
					continue

				// If the player wants that job on this level, then try give it to him.
				if(player.client.prefs.GetJobDepartment(job, level) & job.flag)

					// If the job isn't filled
					if((job.current_positions < job.spawn_positions) || job.spawn_positions == -1)
						Debug("DO pass, Player: [player], Level:[level], Job:[job.title]")
						AssignRole(player, job.title)
						unassigned -= player
						break

	// Hand out random jobs to the people who didn't get any in the last check
	// Also makes sure that they got their preference correct
	for(var/mob/new_player/player in unassigned)
		if(PopcapReached())
			RejectPlayer(player)
		else if(jobban_isbanned(player, "Assistant"))
			GiveRandomJob(player) //you get to roll for random before everyone else just to be sure you don't get assistant. you're so speshul

	for(var/mob/new_player/player in unassigned)
		if(PopcapReached())
			RejectPlayer(player)
		else if(player.client.prefs.userandomjob)
			GiveRandomJob(player)

	Debug("DO, Standard Check end")

	Debug("DO, Running AC2")

	// For those who wanted to be assistant if their preferences were filled, here you go.
	for(var/mob/new_player/player in unassigned)
		if(PopcapReached())
			RejectPlayer(player)
		Debug("AC2 Assistant located, Player: [player]")
		AssignRole(player, "Assistant")
	return 1

//Gives the player the stuff he should have with his rank
/datum/subsystem/job/proc/EquipRank(mob/living/H, rank, joined_late=0)
	var/datum/job/job = GetJob(rank)

	H.job = rank

	//If we joined at roundstart we should be positioned at our workstation
	if(!joined_late)
		var/obj/S = null
		for(var/obj/effect/landmark/start/sloc in start_landmarks_list)
			if(sloc.name != rank)
				S = sloc //so we can revert to spawning them on top of eachother if something goes wrong
				continue
			if(locate(/mob/living) in sloc.loc)
				continue
			S = sloc
			break
		if(!S) //if there isn't a spawnpoint send them to latejoin, if there's no latejoin go yell at your mapper
			world.log << "Couldn't find a round start spawn point for [rank]"
			S = pick(latejoin)
		if(!S) //final attempt, lets find some area in the arrivals shuttle to spawn them in to.
			world.log << "Couldn't find a round start latejoin spawn point."
			for(var/turf/T in get_area_turfs(/area/shuttle/arrival))
				if(!T.density)
					var/clear = 1
					for(var/obj/O in T)
						if(O.density)
							clear = 0
							break
					if(clear)
						S = T
						continue
		if(istype(S, /obj/effect/landmark) && istype(S.loc, /turf))
			H.loc = S.loc

	if(H.mind)
		H.mind.assigned_role = rank

	if(job)
		var/new_mob = job.equip(H)
		if(ismob(new_mob))
			H = new_mob
		job.apply_fingerprints(H)

	H << "You are the [rank]."
	H << "As the [rank] you answer directly to [job.supervisors]. Special circumstances may change this."
	H << "To speak on your departments radio, use the :h button. To see others, look closely at your headset."
	if(job.req_admin_notify)
		H << "You are playing a job that is important for Game Progression. If you have to disconnect, please notify the admins via adminhelp."
	if(config.minimal_access_threshold)
		H << "As this station was initially staffed with a [config.jobs_have_minimal_access ? "full crew, only your job's necessities" : "skeleton crew, additional access may"] have been added to your ID card."
	return 1

/datum/subsystem/job/proc/setup_officer_positions()
	var/datum/job/J = SSjob.GetJob("Security Officer")
	if(!J)
		throw EXCEPTION("setup_officer_positions(): Security officer job is missing")

	if(config.security_scaling_coeff > 0)
		if(J.spawn_positions > 0)
			var/officer_positions = min(12, max(J.spawn_positions, round(unassigned.len/config.security_scaling_coeff))) //Scale between configured minimum and 12 officers
			Debug("Setting open security officer positions to [officer_positions]")
			J.total_positions = officer_positions
			J.spawn_positions = officer_positions

	//Spawn some extra eqipment lockers if we have more than 5 officers
	var/equip_needed = J.total_positions
	if(equip_needed < 0) // -1: infinite available slots
		equip_needed = 12
	for(var/i=equip_needed-5, i>0, i--)
		if(secequipment.len)
			var/spawnloc = secequipment[1]
			new /obj/structure/closet/secure_closet/security/sec(spawnloc)
			secequipment -= spawnloc
		else //We ran out of spare locker spawns!
			break

/datum/subsystem/job/proc/LoadJobs()
	var/jobstext = return_file_text("config/jobs.txt")
	for(var/datum/job/J in occupations)
		var/regex/jobs = new("[J.title]=(-1|\\d+),(-1|\\d+)")
		jobs.Find(jobstext)
		J.total_positions = text2num(jobs.group[1])
		J.spawn_positions = text2num(jobs.group[2])

/datum/subsystem/job/proc/HandleFeedbackGathering()
	for(var/datum/job/job in occupations)
		var/tmp_str = "|[job.title]|"

		var/level1 = 0 //high
		var/level2 = 0 //medium
		var/level3 = 0 //low
		var/level4 = 0 //never
		var/level5 = 0 //banned
		var/level6 = 0 //account too young
		for(var/mob/new_player/player in player_list)
			if(!(player.ready && player.mind && !player.mind.assigned_role))
				continue //This player is not ready
			if(jobban_isbanned(player, job.title))
				level5++
				continue
			if(!job.player_old_enough(player.client))
				level6++
				continue
			if(player.client.prefs.GetJobDepartment(job, 1) & job.flag)
				level1++
			else if(player.client.prefs.GetJobDepartment(job, 2) & job.flag)
				level2++
			else if(player.client.prefs.GetJobDepartment(job, 3) & job.flag)
				level3++
			else level4++ //not selected

		tmp_str += "HIGH=[level1]|MEDIUM=[level2]|LOW=[level3]|NEVER=[level4]|BANNED=[level5]|YOUNG=[level6]|-"
		feedback_add_details("job_preferences",tmp_str)

/datum/subsystem/job/proc/PopcapReached()
	if(config.hard_popcap || config.extreme_popcap)
		var/relevent_cap = max(config.hard_popcap, config.extreme_popcap)
		if((initial_players_to_assign - unassigned.len) >= relevent_cap)
			return 1
	return 0

/datum/subsystem/job/proc/RejectPlayer(mob/new_player/player)
	if(player.mind && player.mind.special_role)
		return
	Debug("Popcap overflow Check observer located, Player: [player]")
	player << "You have failed to qualify for any job you desired."
	unassigned -= player
	player.ready = 0


code:
var/datum/subsystem/lighting/SSlighting

#define MC_AVERAGE(average, current) (0.8*(average) + 0.2*(current))

/datum/subsystem/lighting
	name = "Lighting"
	priority = 1
	wait = 1
	dynamic_wait = 1
	dwait_delta = 4
	dwait_buffer = 0.1
	dwait_lower = 1
	dwait_upper = 5
	display = 5

	var/list/changed_lights = list()		//list of all datum/light_source that need updating
	var/changed_lights_workload = 0			//stats on the largest number of lights (max changed_lights.len)
	var/list/changed_turfs = list()			//list of all turfs which may have a different light level
	var/changed_turfs_workload = 0			//stats on the largest number of turfs changed (max changed_turfs.len)

/datum/subsystem/lighting/New()
	NEW_SS_GLOBAL(SSlighting)

	return ..()

/datum/subsystem/lighting/stat_entry()
	..("L:[round(changed_lights_workload,1)]|T:[round(changed_turfs_workload,1)]")

//Workhorse of lighting. It cycles through each light that needs updating. It updates their
//effects and then processes every turf in the queue, updating their lighting object's appearance
//Any light that returns 1 in check() deletes itself
//By using queues we are ensuring we don't perform more updates than are necessary
/datum/subsystem/lighting/fire(resumed = 0)
	var/list/changed_lights = src.changed_lights
	if (!resumed)
		changed_lights_workload = MC_AVERAGE(changed_lights_workload, changed_lights.len)
	while (changed_lights.len)
		var/datum/light_source/LS = changed_lights[1]
		changed_lights.Cut(1, 2)
		LS.check()
		if (MC_TICK_CHECK)
			return

	var/list/changed_turfs = src.changed_turfs
	if (!resumed)
		changed_turfs_workload = MC_AVERAGE(changed_turfs_workload, changed_turfs.len)
	while (changed_turfs.len)
		var/turf/T = changed_turfs[1]
		changed_turfs.Cut(1, 2)
		if(T.lighting_changed)
			T.redraw_lighting()
		if (MC_TICK_CHECK)
			return

//same as above except it attempts to shift ALL turfs in the world regardless of lighting_changed status
//Does not loop. Should be run prior to process() being called for the first time.
//Note: if we get additional z-levels at runtime (e.g. if the gateway thin ever gets finished) we can initialize specific
//z-levels with the z_level argument
/datum/subsystem/lighting/Initialize(timeofday, z_level)
	for(var/area/A in world)
		if (A.lighting_use_dynamic == DYNAMIC_LIGHTING_IFSTARLIGHT)
			if (config.starlight)
				A.SetDynamicLighting()
		CHECK_TICK

	for(var/thing in changed_lights)
		var/datum/light_source/LS = thing
		LS.check()
		CHECK_TICK
	changed_lights.Cut()

	var/z_start = 1
	var/z_finish = world.maxz
	if(z_level >= 1 && z_level <= world.maxz)
		z_level = round(z_level)
		z_start = z_level
		z_finish = z_level

	var/list/turfs_to_init = block(locate(1, 1, z_start), locate(world.maxx, world.maxy, z_finish))

	for(var/thing in turfs_to_init)
		var/turf/T = thing
		T.init_lighting()
		CHECK_TICK

	if(z_level)
		//we need to loop through to clear only shifted turfs from the list. or we will cause errors
		for(var/thing in changed_turfs)
			var/turf/T = thing
			if(T.z in z_start to z_finish)
				continue
			changed_turfs.Remove(thing)
			CHECK_TICK
	else
		changed_turfs.Cut()

	..()

//Used to strip valid information from an existing instance and transfer it to the replacement. i.e. when a crash occurs
//It works by using spawn(-1) to transfer the data, if there is a runtime the data does not get transfered but the loop
//does not crash
/datum/subsystem/lighting/Recover()
	if(!istype(SSlighting.changed_turfs))
		SSlighting.changed_turfs = list()
	if(!istype(SSlighting.changed_lights))
		SSlighting.changed_lights = list()

	for(var/thing in SSlighting.changed_lights)
		var/datum/light_source/LS = thing
		spawn(-1)			//so we don't crash the loop (inefficient)
			LS.check()

	for(var/thing in changed_turfs)
		var/turf/T = thing
		if(T.lighting_changed)
			spawn(-1)
				T.redraw_lighting()

	var/msg = "## DEBUG: [time2text(world.timeofday)] [name] subsystem restarted. Reports:\n"
	for(var/varname in SSlighting.vars)
		switch(varname)
			if("tag","bestF","type","parent_type","vars")
				continue
			else
				var/varval1 = SSlighting.vars[varname]
				var/varval2 = vars[varname]
				if(istype(varval1,/list))
					varval1 = "/list([length(varval1)])"
					varval2 = "/list([length(varval2)])"
				msg += "\t [varname] = [varval1] -> [varval2]\n"
	world.log << msg


code:
var/datum/subsystem/machines/SSmachine

/datum/subsystem/machines
	name = "Machines"
	priority = 9
	display = 3

	var/list/processing = list()
	var/list/currentrun = list()
	var/list/powernets = list()

/datum/subsystem/machines/Initialize()
	makepowernets()
	fire()
	..()

/datum/subsystem/machines/proc/makepowernets(zlevel)
	for(var/datum/powernet/PN in powernets)
		qdel(PN)
	powernets.Cut()

	for(var/obj/structure/cable/PC in cable_list)
		if(!PC.powernet)
			var/datum/powernet/NewPN = new()
			NewPN.add_cable(PC)
			propagate_network(PC,PC.powernet)

/datum/subsystem/machines/New()
	NEW_SS_GLOBAL(SSmachine)

/datum/subsystem/machines/stat_entry()
	..("M:[processing.len]|PN:[powernets.len]")

/datum/subsystem/machines/fire(resumed = 0)
	if (!resumed)
		for(var/datum/powernet/Powernet in powernets)
			Powernet.reset() //reset the power state.
		src.currentrun = processing.Copy()

	//cache for sanic speed (lists are references anyways)
	var/list/currentrun = src.currentrun

	var/seconds = wait * 0.1
	while(currentrun.len)
		var/datum/thing = currentrun[1]
		currentrun.Cut(1, 2)
		if(thing && thing.process(seconds) != PROCESS_KILL)
			if(thing:use_power)
				thing:auto_use_power() //add back the power state
		else
			processing.Remove(thing)
		if (MC_TICK_CHECK)
			return

/datum/subsystem/machines/proc/setup_template_powernets(list/cables)
	for(var/A in cables)
		var/obj/structure/cable/PC = A
		if(!PC.powernet)
			var/datum/powernet/NewPN = new()
			NewPN.add_cable(PC)
			propagate_network(PC,PC.powernet)


code:
var/datum/subsystem/minimap/SSminimap

/datum/subsystem/minimap
	name = "Minimap"
	priority = -2

	var/const/MINIMAP_SIZE = 2048
	var/const/TILE_SIZE = 8

	var/list/z_levels = list(ZLEVEL_STATION)

/datum/subsystem/minimap/New()
	NEW_SS_GLOBAL(SSminimap)

/datum/subsystem/minimap/Initialize(timeofday, zlevel)
	if(zlevel)
		return ..()
	if(!config.generate_minimaps)
		world << "Minimap generation disabled... Skipping"
		return
	var/hash = md5(file2text("_maps/[MAP_PATH]/[MAP_FILE]"))
	if(hash == trim(file2text(hash_path())))
		return ..()

	for(var/z in z_levels)
		generate(z)
		register_asset("minimap_[z].png", fcopy_rsc(map_path(z)))
	fdel(hash_path())
	text2file(hash, hash_path())
	..()

/datum/subsystem/minimap/proc/hash_path()
	return "data/minimaps/[MAP_NAME].md5"

/datum/subsystem/minimap/proc/map_path(z)
	return "data/minimaps/[MAP_NAME]_[z].png"

/datum/subsystem/minimap/proc/send(client/client)
	for(var/z in z_levels)
		send_asset(client, "minimap_[z].png")

/datum/subsystem/minimap/proc/generate(z = 1, x1 = 1, y1 = 1, x2 = world.maxx, y2 = world.maxy)
	// Load the background.
	var/icon/minimap = new /icon('icons/minimap.dmi')
	// Scale it up to our target size.
	minimap.Scale(MINIMAP_SIZE, MINIMAP_SIZE)

	var/counter = 128
	// Loop over turfs and generate icons.
	for(var/T in block(locate(x1, y1, z), locate(x2, y2, z)))
		generate_tile(T, minimap)

		//byond bug, this fixes OOM crashes by flattening and reseting the minimap icon holder every 128 tiles
		counter--
		if(counter <= 0)
			counter = 128
			var/icon/flatten = new /icon()
			flatten.Insert(minimap, "", SOUTH, 1, 0)
			del(minimap)
			minimap = flatten
			stoplag() //we have to sleep in order to get byond to clear out the proc's garbage bin

		CHECK_TICK

	// Create a new icon and insert the generated minimap, so that BYOND doesn't generate different directions.
	var/icon/final = new /icon()
	final.Insert(minimap, "", SOUTH, 1, 0)
	fcopy(final, map_path(z))

/datum/subsystem/minimap/proc/generate_tile(turf/tile, icon/minimap)
	var/icon/tile_icon
	var/obj/obj
	var/list/obj_icons = list()
	// Don't use icons for space, just add objects in space if they exist.
	if(istype(tile, /turf/open/space))
		obj = locate(/obj/structure/lattice/catwalk) in tile
		if(obj)
			tile_icon = new /icon('icons/obj/smooth_structures/catwalk.dmi', "catwalk", SOUTH)
		obj = locate(/obj/structure/lattice) in tile
		if(obj)
			tile_icon = new /icon('icons/obj/smooth_structures/lattice.dmi', "lattice", SOUTH)
		obj = locate(/obj/structure/grille) in tile
		if(obj)
			tile_icon = new /icon('icons/obj/structures.dmi', "grille", SOUTH)
		obj = locate(/obj/structure/transit_tube) in tile
		if(obj)
			tile_icon = new /icon('icons/obj/atmospherics/pipes/transit_tube.dmi', obj.icon_state, obj.dir)
	else
		tile_icon = new /icon(tile.icon, tile.icon_state, tile.dir)
		obj_icons.Cut()

		obj = locate(/obj/structure) in tile
		if(obj)
			obj_icons += getFlatIcon(obj)
		obj = locate(/obj/machinery) in tile
		if(obj)
			obj_icons += new /icon(obj.icon, obj.icon_state, obj.dir, 1, 0)
		obj = locate(/obj/structure/window) in tile
		if(obj)
			obj_icons += new /icon('icons/obj/smooth_structures/window.dmi', "window", SOUTH)

		for(var/I in obj_icons)
			var/icon/obj_icon = I
			tile_icon.Blend(obj_icon, ICON_OVERLAY)

	if(tile_icon)
		// Scale the icon.
		tile_icon.Scale(TILE_SIZE, TILE_SIZE)
		// Add the tile to the minimap.
		minimap.Blend(tile_icon, ICON_OVERLAY, ((tile.x - 1) * TILE_SIZE), ((tile.y - 1) * TILE_SIZE))
		del(tile_icon)


code:
var/datum/subsystem/mobs/SSmob

/datum/subsystem/mobs
	name = "Mobs"
	priority = 4
	display = 4

	var/list/currentrun = list()

/datum/subsystem/mobs/New()
	NEW_SS_GLOBAL(SSmob)

/datum/subsystem/mobs/stat_entry()
	..("P:[mob_list.len]")

/datum/subsystem/mobs/fire(resumed = 0)
	var/seconds = wait * 0.1
	if (!resumed)
		src.currentrun = mob_list.Copy()

	//cache for sanic speed (lists are references anyways)
	var/list/currentrun = src.currentrun

	while(currentrun.len)
		var/mob/M = currentrun[1]
		currentrun.Cut(1, 2)
		if(M)
			M.Life(seconds)
		else
			mob_list.Remove(M)
		if (MC_TICK_CHECK)
			return


code:
var/datum/subsystem/npcpool/SSnpc

/datum/subsystem/npcpool
	name = "NPC Pool"
	priority = 17
	display = 6

	var/list/canBeUsed = list()
	var/list/canBeUsed_non = list()
	var/list/needsDelegate = list()
	var/list/needsAssistant = list()
	var/list/needsHelp_non = list()
	var/list/botPool_l = list() //list of all npcs using the pool
	var/list/botPool_l_non = list() //list of all non SNPC mobs using the pool

/datum/subsystem/npcpool/proc/insertBot(toInsert)
	if(istype(toInsert,/mob/living/carbon/human/interactive))
		botPool_l |= toInsert

/datum/subsystem/npcpool/New()
	NEW_SS_GLOBAL(SSnpc)

/datum/subsystem/npcpool/stat_entry()
	..("T:[botPool_l.len + botPool_l_non.len]|D:[needsDelegate.len]|A:[needsAssistant.len + needsHelp_non.len]|U:[canBeUsed.len + canBeUsed_non.len]")

/datum/subsystem/npcpool/proc/cleanNull()
		//cleanup nulled bots
	listclearnulls(botPool_l)
	listclearnulls(needsDelegate)
	listclearnulls(canBeUsed)
	listclearnulls(needsAssistant)

/datum/subsystem/npcpool/fire()
	//bot delegation and coordination systems
	//General checklist/Tasks for delegating a task or coordinating it (for SNPCs)
	// 1. Bot proximity to task target: if too far, delegate, if close, coordinate
	// 2. Bot Health/status: check health with bots in local area, if their health is higher, delegate task to them, else coordinate
	// 3. Process delegation: if a bot (or bots) has been delegated, assign them to the task.
	// 4. Process coordination: if a bot(or bots) has been asked to coordinate, assign them to help.
	// 5. Do all assignments: goes through the delegated/coordianted bots and assigns the right variables/tasks to them.
	var/npcCount = 1

	cleanNull()

	//SNPC handling
	for(var/mob/living/carbon/human/interactive/check in botPool_l)
		if(!check)
			botPool_l.Cut(npcCount,npcCount+1)
			continue
		var/checkInRange = view(MAX_RANGE_FIND,check)
		if(!(locate(check.TARGET) in checkInRange))
			needsDelegate |= check

		else if(check.IsDeadOrIncap(FALSE))
			needsDelegate |= check

		else if(check.doing & FIGHTING)
			needsAssistant |= check

		else
			canBeUsed |= check
		npcCount++

	if(needsDelegate.len)

		needsDelegate -= pick(needsDelegate) // cheapo way to make sure stuff doesn't pingpong around in the pool forever. delegation runs seperately to each loop so it will work much smoother

		npcCount = 1 //reset the count
		for(var/mob/living/carbon/human/interactive/check in needsDelegate)
			if(!check)
				needsDelegate.Cut(npcCount,npcCount+1)
				continue
			if(canBeUsed.len)
				var/mob/living/carbon/human/interactive/candidate = pick(canBeUsed)
				var/facCount = 0
				var/helpProb = 0
				for(var/C in check.faction)
					for(var/D in candidate.faction)
						if(D == C)
							helpProb = min(100,helpProb + 25)
						facCount++
				if(facCount == 1 && helpProb > 0)
					helpProb = 100
				if(prob(helpProb))
					if(candidate.takeDelegate(check))
						needsDelegate -= check
						canBeUsed -= candidate
						candidate.eye_color = "red"
						candidate.update_icons()
			npcCount++

	if(needsAssistant.len)

		needsAssistant -= pick(needsAssistant)

		npcCount = 1 //reset the count
		for(var/mob/living/carbon/human/interactive/check in needsAssistant)
			if(!check)
				needsAssistant.Cut(npcCount,npcCount+1)
				continue
			if(canBeUsed.len)
				var/mob/living/carbon/human/interactive/candidate = pick(canBeUsed)
				var/facCount = 0
				var/helpProb = 0
				for(var/C in check.faction)
					for(var/D in candidate.faction)
						if(D == C)
							helpProb = min(100,helpProb + 25)
						facCount++
				if(facCount == 1 && helpProb > 0)
					helpProb = 100
				if(prob(helpProb))
					if(candidate.takeDelegate(check,FALSE))
						needsAssistant -= check
						canBeUsed -= candidate
						candidate.eye_color = "yellow"
						candidate.update_icons()
			npcCount++


code:
var/datum/subsystem/objects/SSobj

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

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

	var/list/atom_spawners = list()
	var/list/processing = list()
	var/list/currentrun = list()
	var/list/burning = list()

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

/datum/subsystem/objects/Initialize(timeofday, zlevel)
	trigger_atom_spawners()
	setupGenetics()
	for(var/V in world)
		var/atom/A = V
		if (zlevel && A.z != zlevel)
			continue
		A.initialize()
		CHECK_TICK
	. = ..()

/datum/subsystem/objects/proc/trigger_atom_spawners(zlevel, ignore_z=FALSE)
	for(var/V in atom_spawners)
		var/atom/A = V
		if (!ignore_z && (zlevel && A.z != zlevel))
			continue
		A.spawn_atom_to_world()

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

/datum/subsystem/objects/fire(resumed = 0)
	if (!resumed)
		src.currentrun = processing.Copy()
	//cache for sanic speed (lists are references anyways)
	var/list/currentrun = src.currentrun

	while(currentrun.len)
		var/datum/thing = currentrun[1]
		currentrun.Cut(1, 2)
		if(thing)
			thing.process(wait)
		else
			SSobj.processing.Remove(thing)
		if (MC_TICK_CHECK)
			return

	for(var/obj/burningobj in SSobj.burning)
		if(burningobj && (burningobj.burn_state == ON_FIRE))
			if(burningobj.burn_world_time < world.time)
				burningobj.burn()
		else
			SSobj.burning.Remove(burningobj)

/datum/subsystem/objects/proc/setup_template_objects(list/objects)
	trigger_atom_spawners(0, ignore_z=TRUE)
	for(var/A in objects)
		var/atom/B = A
		B.initialize()