code:
//These are meant for spawning on maps, namely Away Missions.

//If someone can do this in a neater way, be my guest-Kor

//To do: Allow corpses to appear mangled, bloody, etc. Allow customizing the bodies appearance (they're all bald and white right now).

/obj/effect/landmark/corpse
	name = "Unknown"
	var/mobname = "Unknown"  //Unused now but it'd fuck up maps to remove it now
	var/mobgender = MALE //Set to male by default due to the patriarchy. Other options include FEMALE and NEUTER
	var/corpseuniform = null //Set this to an object path to have the slot filled with said object on the corpse.
	var/corpsesuit = null
	var/corpseshoes = null
	var/corpsegloves = null
	var/corpseradio = null
	var/corpseglasses = null
	var/corpsemask = null
	var/corpsehelmet = null
	var/corpsebelt = null
	var/corpsepocket1 = null
	var/corpsepocket2 = null
	var/corpseback = null
	var/corpseid = 0     //Just set to 1 if you want them to have an ID
	var/corpseidjob = null // Needs to be in quotes, such as "Clown" or "Chef." This just determines what the ID reads as, not their access
	var/corpseidaccess = null //This is for access. See access.dm for which jobs give what access. Again, put in quotes. Use "Captain" if you want it to be all access.
	var/corpseidicon = null //For setting it to be a gold, silver, centcom etc ID
	var/corpsehusk = null
	var/corpsebrute = null //set brute damage on the corpse
	var/corpseoxy = null //set suffocation damage on the corpse

/obj/effect/landmark/corpse/initialize()
	createCorpse()

/obj/effect/landmark/corpse/proc/createCorpse() //Creates a mob and checks for gear in each slot before attempting to equip it.
	var/mob/living/carbon/human/M = new /mob/living/carbon/human (src.loc)
	M.real_name = src.name
	M.gender = src.mobgender
	M.death(1) //Kills the new mob
	if(src.corpsehusk)
		M.Drain()
	M.adjustBruteLoss(src.corpsebrute)
	M.adjustOxyLoss(src.corpseoxy)
	if(src.corpseuniform)
		M.equip_to_slot_or_del(new src.corpseuniform(M), slot_w_uniform)
	if(src.corpsesuit)
		M.equip_to_slot_or_del(new src.corpsesuit(M), slot_wear_suit)
	if(src.corpseshoes)
		M.equip_to_slot_or_del(new src.corpseshoes(M), slot_shoes)
	if(src.corpsegloves)
		M.equip_to_slot_or_del(new src.corpsegloves(M), slot_gloves)
	if(src.corpseradio)
		M.equip_to_slot_or_del(new src.corpseradio(M), slot_ears)
	if(src.corpseglasses)
		M.equip_to_slot_or_del(new src.corpseglasses(M), slot_glasses)
	if(src.corpsemask)
		M.equip_to_slot_or_del(new src.corpsemask(M), slot_wear_mask)
	if(src.corpsehelmet)
		M.equip_to_slot_or_del(new src.corpsehelmet(M), slot_head)
	if(src.corpsebelt)
		M.equip_to_slot_or_del(new src.corpsebelt(M), slot_belt)
	if(src.corpsepocket1)
		M.equip_to_slot_or_del(new src.corpsepocket1(M), slot_r_store)
	if(src.corpsepocket2)
		M.equip_to_slot_or_del(new src.corpsepocket2(M), slot_l_store)
	if(src.corpseback)
		M.equip_to_slot_or_del(new src.corpseback(M), slot_back)
	if(src.corpseid == 1)
		var/obj/item/weapon/card/id/W = new(M)
		var/datum/job/jobdatum
		for(var/jobtype in typesof(/datum/job))
			var/datum/job/J = new jobtype
			if(J.title == corpseidaccess)
				jobdatum = J
				break
		if(src.corpseidicon)
			W.icon_state = corpseidicon
		if(src.corpseidaccess)
			if(jobdatum)
				W.access = jobdatum.get_access()
			else
				W.access = list()
		if(corpseidjob)
			W.assignment = corpseidjob
		W.registered_name = M.real_name
		W.update_label()
		M.equip_to_slot_or_del(W, slot_wear_id)
	qdel(src)

/obj/effect/landmark/corpse/AICorpse/createCorpse() //Creates a corrupted AI
	var/A = locate(/mob/living/silicon/ai) in loc //variable A looks for an AI at the location of the landmark
	if(A) //if variable A is true
		return //stop executing the proc
	var/L = new /datum/ai_laws/default/asimov/ //variable L is a new Asimov lawset
	var/B = new /obj/item/device/mmi/ //variable B is a new MMI
	var/mob/living/silicon/ai/M = new(src.loc, L, B, 1) //spawn new AI at landmark as var M
	M.name = src.name
	M.real_name = src.name
	M.aiPDA.toff = 1 //turns the AI's PDA messenger off, stopping it showing up on player PDAs
	M.death() //call the AI's death proc
	qdel(src)

/obj/effect/landmark/corpse/slimeCorpse
	var/mobcolour = "grey"
	icon = 'icons/mob/slimes.dmi'
	icon_state = "grey baby slime" //sets the icon in the map editor

/obj/effect/landmark/corpse/slimeCorpse/createCorpse() //proc creates a dead slime
	var/A = locate(/mob/living/carbon/slime/) in loc //variable A looks for a slime at the location of the landmark
	if(A) //if variable A is true
		return //stop executing the proc
	var/mob/living/carbon/slime/M = new(src.loc) //variable M is a new slime at the location of the landmark
	M.colour = src.mobcolour //slime colour is set by landmark's mobcolour var
	M.adjustToxLoss(9001) //kills the slime, death() doesn't update its icon correctly
	qdel(src)

/obj/effect/landmark/corpse/facehugCorpse/createCorpse() //Creates a squashed facehugger
	var/obj/item/clothing/mask/facehugger/O = new(src.loc) //variable O is a new facehugger at the location of the landmark
	O.name = src.name
	O.Die() //call the facehugger's death proc
	qdel(src)

// I'll work on making a list of corpses people request for maps, or that I think will be commonly used. Syndicate operatives for example.

/obj/effect/landmark/corpse/syndicatesoldier
	name = "Syndicate Operative"
	corpseuniform = /obj/item/clothing/under/syndicate
	corpsesuit = /obj/item/clothing/suit/armor/vest
	corpseshoes = /obj/item/clothing/shoes/combat
	corpsegloves = /obj/item/clothing/gloves/combat
	corpseradio = /obj/item/device/radio/headset
	corpsemask = /obj/item/clothing/mask/gas
	corpsehelmet = /obj/item/clothing/head/helmet/swat
	corpseback = /obj/item/weapon/storage/backpack
	corpseid = 1
	corpseidjob = "Operative"
	corpseidaccess = "Syndicate"

/obj/effect/landmark/corpse/syndicatecommando
	name = "Syndicate Commando"
	corpseuniform = /obj/item/clothing/under/syndicate
	corpsesuit = /obj/item/clothing/suit/space/hardsuit/syndi
	corpseshoes = /obj/item/clothing/shoes/combat
	corpsegloves = /obj/item/clothing/gloves/combat
	corpseradio = /obj/item/device/radio/headset
	corpsemask = /obj/item/clothing/mask/gas/syndicate
	corpsehelmet = /obj/item/clothing/head/helmet/space/hardsuit/syndi
	corpseback = /obj/item/weapon/tank/jetpack/oxygen
	corpsepocket1 = /obj/item/weapon/tank/internals/emergency_oxygen
	corpseid = 1
	corpseidjob = "Operative"
	corpseidaccess = "Syndicate"

///////////Civilians//////////////////////

/obj/effect/landmark/corpse/cook
	name = "Cook"
	corpseuniform = /obj/item/clothing/under/rank/chef
	corpsesuit = /obj/item/clothing/suit/apron/chef
	corpseshoes = /obj/item/clothing/shoes/sneakers/black
	corpsehelmet = /obj/item/clothing/head/chefhat
	corpseback = /obj/item/weapon/storage/backpack
	corpseradio = /obj/item/device/radio/headset
	corpseid = 1
	corpseidjob = "Cook"
	corpseidaccess = "Cook"

/obj/effect/landmark/corpse/doctor
	name = "Doctor"
	corpseradio = /obj/item/device/radio/headset/headset_med
	corpseuniform = /obj/item/clothing/under/rank/medical
	corpsesuit = /obj/item/clothing/suit/toggle/labcoat
	corpseback = /obj/item/weapon/storage/backpack/medic
	corpsepocket1 = /obj/item/device/flashlight/pen
	corpseshoes = /obj/item/clothing/shoes/sneakers/black
	corpseid = 1
	corpseidjob = "Medical Doctor"
	corpseidaccess = "Medical Doctor"

/obj/effect/landmark/corpse/engineer
	name = "Engineer"
	corpseradio = /obj/item/device/radio/headset/headset_eng
	corpseuniform = /obj/item/clothing/under/rank/engineer
	corpseback = /obj/item/weapon/storage/backpack/industrial
	corpseshoes = /obj/item/clothing/shoes/sneakers/orange
	corpsebelt = /obj/item/weapon/storage/belt/utility/full
	corpsegloves = /obj/item/clothing/gloves/color/yellow
	corpsehelmet = /obj/item/clothing/head/hardhat
	corpseid = 1
	corpseidjob = "Station Engineer"
	corpseidaccess = "Station Engineer"

/obj/effect/landmark/corpse/engineer/rig
	corpsesuit = /obj/item/clothing/suit/space/hardsuit
	corpsemask = /obj/item/clothing/mask/breath

/obj/effect/landmark/corpse/clown
	name = "Clown"
	corpseuniform = /obj/item/clothing/under/rank/clown
	corpseshoes = /obj/item/clothing/shoes/clown_shoes
	corpseradio = /obj/item/device/radio/headset
	corpsemask = /obj/item/clothing/mask/gas/clown_hat
	corpsepocket1 = /obj/item/weapon/bikehorn
	corpseback = /obj/item/weapon/storage/backpack/clown
	corpseid = 1
	corpseidjob = "Clown"
	corpseidaccess = "Clown"

/obj/effect/landmark/corpse/scientist
	name = "Scientist"
	corpseradio = /obj/item/device/radio/headset/headset_sci
	corpseuniform = /obj/item/clothing/under/rank/scientist
	corpsesuit = /obj/item/clothing/suit/toggle/labcoat/science
	corpseback = /obj/item/weapon/storage/backpack
	corpseshoes = /obj/item/clothing/shoes/sneakers/white
	corpseid = 1
	corpseidjob = "Scientist"
	corpseidaccess = "Scientist"

/obj/effect/landmark/corpse/miner
	corpseradio = /obj/item/device/radio/headset/headset_cargo
	corpseuniform = /obj/item/clothing/under/rank/miner
	corpsegloves = /obj/item/clothing/gloves/fingerless
	corpseback = /obj/item/weapon/storage/backpack/industrial
	corpseshoes = /obj/item/clothing/shoes/sneakers/black
	corpseid = 1
	corpseidjob = "Shaft Miner"
	corpseidaccess = "Shaft Miner"

/obj/effect/landmark/corpse/miner/rig
	corpsesuit = /obj/item/clothing/suit/space/hardsuit/mining
	corpsemask = /obj/item/clothing/mask/breath

/////////////////Officers//////////////////////

/obj/effect/landmark/corpse/bridgeofficer
	name = "Bridge Officer"
	corpseradio = /obj/item/device/radio/headset/heads/hop
	corpseuniform = /obj/item/clothing/under/rank/centcom_officer
	corpsesuit = /obj/item/clothing/suit/armor/bulletproof
	corpseshoes = /obj/item/clothing/shoes/sneakers/black
	corpseglasses = /obj/item/clothing/glasses/sunglasses
	corpseid = 1
	corpseidjob = "Bridge Officer"
	corpseidaccess = "Captain"

/obj/effect/landmark/corpse/commander
	name = "Commander"
	corpseuniform = /obj/item/clothing/under/rank/centcom_commander
	corpsesuit = /obj/item/clothing/suit/armor
	corpseradio = /obj/item/device/radio/headset/heads/captain
	corpseglasses = /obj/item/clothing/glasses/eyepatch
	corpsemask = /obj/item/clothing/mask/cigarette/cigar/cohiba
	corpsehelmet = /obj/item/clothing/head/centhat
	corpsegloves = /obj/item/clothing/gloves/combat
	corpseshoes = /obj/item/clothing/shoes/combat/swat
	corpsepocket1 = /obj/item/weapon/lighter/zippo
	corpseid = 1
	corpseidjob = "Commander"
	corpseidaccess = "Captain"


code:
//Exile implants will allow you to use the station gate, but not return home.
//This will allow security to exile badguys/for badguys to exile their kill targets

/obj/item/weapon/implanter/exile
	name = "implanter-exile"

/obj/item/weapon/implanter/exile/New()
	imp = new /obj/item/weapon/implant/exile( src )
	..()
	update_icon()

/obj/item/weapon/implant/exile
	name = "exile implant"
	desc = "Prevents you from returning from away missions"
	activated = 0

/obj/item/weapon/implant/exile/get_data()
	var/dat = {"Implant Specifications:

				Name: Nanotrasen Employee Exile Implant

				Implant Details: The onboard gateway system has been modified to reject entry by individuals containing this implant
"}
	return dat

/obj/item/weapon/implantcase/exile
	name = "glass case- 'Exile'"
	desc = "A case containing an exile implant."
	icon = 'icons/obj/items.dmi'
	icon_state = "implantcase-r"

/obj/item/weapon/implantcase/exile/New()
	imp = new /obj/item/weapon/implant/exile(src)
	..()

/obj/structure/closet/secure_closet/exile
	name = "exile implants"
	req_access = list(access_hos)

/obj/structure/closet/secure_closet/exile/New()
	..()
	new /obj/item/weapon/implanter/exile(src)
	new /obj/item/weapon/implantcase/exile(src)
	new /obj/item/weapon/implantcase/exile(src)
	new /obj/item/weapon/implantcase/exile(src)
	new /obj/item/weapon/implantcase/exile(src)
	new /obj/item/weapon/implantcase/exile(src)

license: mit


code:
/obj/machinery/gateway
	name = "gateway"
	desc = "A mysterious gateway built by unknown hands, it allows for faster than light travel to far-flung locations."
	icon = 'icons/obj/machines/gateway.dmi'
	icon_state = "off"
	density = 1
	anchored = 1
	unacidable = 1
	var/active = 0

/obj/machinery/gateway/initialize()
	update_icon()
	if(dir == 2)
		density = 0

/obj/machinery/gateway/update_icon()
	if(active)
		icon_state = "on"
		return
	icon_state = "off"

//this is da important part wot makes things go
/obj/machinery/gateway/centerstation
	density = 1
	icon_state = "offcenter"
	use_power = 1

	//warping vars
	var/list/linked = list()
	var/ready = 0				//have we got all the parts for a gateway?
	var/wait = 0				//this just grabs world.time at world start
	var/obj/machinery/gateway/centeraway/awaygate = null

/obj/machinery/gateway/centerstation/initialize()
	update_icon()
	wait = world.time + config.gateway_delay	//+ thirty minutes default
	awaygate = locate(/obj/machinery/gateway/centeraway)

/obj/machinery/gateway/centerstation/update_icon()
	if(active)
		icon_state = "oncenter"
		return
	icon_state = "offcenter"

obj/machinery/gateway/centerstation/process()
	if(stat & (NOPOWER))
		if(active) toggleoff()
		return

	if(active)
		use_power(5000)

/obj/machinery/gateway/centerstation/proc/detect()
	linked = list()	//clear the list
	var/turf/T = loc

	for(var/i in alldirs)
		T = get_step(loc, i)
		var/obj/machinery/gateway/G = locate(/obj/machinery/gateway) in T
		if(G)
			linked.Add(G)
			continue

		//this is only done if we fail to find a part
		ready = 0
		toggleoff()
		break

	if(linked.len == 8)
		ready = 1

/obj/machinery/gateway/centerstation/proc/toggleon(mob/user as mob)
	if(!ready)			return
	if(linked.len != 8)	return
	if(!powered())		return
	if(!awaygate)
		user << "Error: No destination found."
		return
	if(world.time < wait)
		user << "Error: Warpspace triangulation in progress. Estimated time to completion: [round(((wait - world.time) / 10) / 60)] minutes."
		return

	for(var/obj/machinery/gateway/G in linked)
		G.active = 1
		G.update_icon()
	active = 1
	update_icon()

/obj/machinery/gateway/centerstation/proc/toggleoff()
	for(var/obj/machinery/gateway/G in linked)
		G.active = 0
		G.update_icon()
	active = 0
	update_icon()

/obj/machinery/gateway/centerstation/attack_hand(mob/user as mob)
	if(!ready)
		detect()
		return
	if(!active)
		toggleon(user)
		return
	toggleoff()

//okay, here's the good teleporting stuff
/obj/machinery/gateway/centerstation/Bumped(atom/movable/M as mob|obj)
	if(!ready)		return
	if(!active)		return
	if(!awaygate)	return

	if(awaygate.calibrated)
		M.loc = get_step(awaygate.loc, SOUTH)
		M.dir = SOUTH
		return
	else
		var/obj/effect/landmark/dest = pick(awaydestinations)
		if(dest)
			M.loc = dest.loc
			M.dir = SOUTH
			use_power(5000)
		return

/obj/machinery/gateway/centerstation/attackby(obj/item/device/W as obj, mob/user as mob, params)
	if(istype(W,/obj/item/device/multitool))
		user << "\black The gate is already calibrated, there is no work for you to do here."
		return

/////////////////////////////////////Away////////////////////////

/obj/machinery/gateway/centeraway
	density = 1
	icon_state = "offcenter"
	use_power = 0
	var/calibrated = 1
	var/list/linked = list()	//a list of the connected gateway chunks
	var/ready = 0
	var/obj/machinery/gateway/centeraway/stationgate = null

/obj/machinery/gateway/centeraway/initialize()
	update_icon()
	stationgate = locate(/obj/machinery/gateway/centerstation)

/obj/machinery/gateway/centeraway/update_icon()
	if(active)
		icon_state = "oncenter"
		return
	icon_state = "offcenter"

/obj/machinery/gateway/centeraway/proc/detect()
	linked = list()	//clear the list
	var/turf/T = loc

	for(var/i in alldirs)
		T = get_step(loc, i)
		var/obj/machinery/gateway/G = locate(/obj/machinery/gateway) in T
		if(G)
			linked.Add(G)
			continue

		//this is only done if we fail to find a part
		ready = 0
		toggleoff()
		break

	if(linked.len == 8)
		ready = 1

/obj/machinery/gateway/centeraway/proc/toggleon(mob/user as mob)
	if(!ready)			return
	if(linked.len != 8)	return
	if(!stationgate)
		user << "Error: No destination found."
		return

	for(var/obj/machinery/gateway/G in linked)
		G.active = 1
		G.update_icon()
	active = 1
	update_icon()

/obj/machinery/gateway/centeraway/proc/toggleoff()
	for(var/obj/machinery/gateway/G in linked)
		G.active = 0
		G.update_icon()
	active = 0
	update_icon()

/obj/machinery/gateway/centeraway/attack_hand(mob/user as mob)
	if(!ready)
		detect()
		return
	if(!active)
		toggleon(user)
		return
	toggleoff()

/obj/machinery/gateway/centeraway/Bumped(atom/movable/M as mob|obj)
	if(!ready)	return
	if(!active)	return
	if(istype(M, /mob/living/carbon))
		for(var/obj/item/weapon/implant/exile/E in M)//Checking that there is an exile implant in the contents
			if(E.imp_in == M)//Checking that it's actually implanted vs just in their pocket
				M << "\black The station gate has detected your exile implant and is blocking your entry."
				return
	M.loc = get_step(stationgate.loc, SOUTH)
	M.dir = SOUTH

/obj/machinery/gateway/centeraway/attackby(obj/item/device/W as obj, mob/user as mob, params)
	if(istype(W,/obj/item/device/multitool))
		if(calibrated)
			user << "\black The gate is already calibrated, there is no work for you to do here."
			return
		else
			user << "Recalibration successful!: \black This gate's systems have been fine tuned.  Travel to this gate will now be on target."
			calibrated = 1
			return


code:
var/global/dmm_suite/maploader = new

dmm_suite{
	/*

		dmm_suite version 1.0
			Released January 30th, 2011.

		defines the object /dmm_suite
			- Provides the proc load_map()
				- Loads the specified map file onto the specified z-level.
			- provides the proc write_map()
				- Returns a text string of the map in dmm format
					ready for output to a file.
			- provides the proc save_map()
				- Returns a .dmm file if map is saved
				- Returns FALSE if map fails to save

		The dmm_suite provides saving and loading of map files in BYOND's native DMM map
		format. It approximates the map saving and loading processes of the Dream Maker
		and Dream Seeker programs so as to allow editing, saving, and loading of maps at
		runtime.

		------------------------

		To save a map at runtime, create an instance of /dmm_suite, and then call
		write_map(), which accepts three arguments:
			- A turf representing one corner of a three dimensional grid (Required).
			- Another turf representing the other corner of the same grid (Required).
			- Any, or a combination, of several bit flags (Optional, see documentation).

		The order in which the turfs are supplied does not matter, the /dmm_writer will
		determine the grid containing both, in much the same way as DM's block() function.
		write_map() will then return a string representing the saved map in dmm format;
		this string can then be saved to a file, or used for any other purose.

		------------------------

		To load a map at runtime, create an instance of /dmm_suite, and then call load_map(),
		which accepts two arguments:
			- A .dmm file to load (Required).
			- A number representing the z-level on which to start loading the map (Optional).

		The /dmm_suite will load the map file starting on the specified z-level. If no
		z-level	was specified, world.maxz will be increased so as to fit the map. Note
		that if you wish to load a map onto a z-level that already has objects on it,
		you will have to handle the removal of those objects. Otherwise the new map will
		simply load the new objects on top of the old ones.

		Also note that all type paths specified in the .dmm file must exist in the world's
		code, and that the /dmm_reader trusts that files to be loaded are in fact valid
		.dmm files. Errors in the .dmm format will cause runtime errors.

		*/

	verb/load_map(var/dmm_file as file, var/z_offset as num){
		// dmm_file: A .dmm file to load (Required).
		// z_offset: A number representing the z-level on which to start loading the map (Optional).
		}
	verb/write_map(var/turf/t1 as turf, var/turf/t2 as turf, var/flags as num){
		// t1: A turf representing one corner of a three dimensional grid (Required).
		// t2: Another turf representing the other corner of the same grid (Required).
		// flags: Any, or a combination, of several bit flags (Optional, see documentation).
		}

	// save_map is included as a legacy proc. Use write_map instead.
	verb/save_map(var/turf/t1 as turf, var/turf/t2 as turf, var/map_name as text, var/flags as num){
		// t1: A turf representing one corner of a three dimensional grid (Required).
		// t2: Another turf representing the other corner of the same grid (Required).
		// map_name: A valid name for the map to be saved, such as "castle" (Required).
		// flags: Any, or a combination, of several bit flags (Optional, see documentation).
		}
	}


code:
///////////////////////////////////////////////////////////////
//SS13 Optimized Map loader
//////////////////////////////////////////////////////////////

//global datum that will preload variables on atoms instanciation
var/global/dmm_suite/preloader/_preloader = null

/**
 * Construct the model map and control the loading process
 *
 * WORKING :
 *
 * 1) Makes an associative mapping of model_keys with model
 *		e.g aa = /turf/unsimulated/wall{icon_state = "rock"}
 * 2) Read the map line by line, parsing the result (using parse_grid)
 *
 */
/dmm_suite/load_map(var/dmm_file as file, var/z_offset as num)
	if(!z_offset)//what z_level we are creating the map on
		z_offset = world.maxz+1

	var/quote = ascii2text(34)
	var/tfile = file2text(dmm_file)//the map file we're creating
	var/tfile_len = length(tfile)
	var/lpos = 1 // the models definition index

	///////////////////////////////////////////////////////////////////////////////////////
	//first let's map model keys (e.g "aa") to their contents (e.g /turf/space{variables})
	///////////////////////////////////////////////////////////////////////////////////////
	var/list/grid_models = list()
	var/key_len = length(copytext(tfile,2,findtext(tfile,quote,2,0)))//the length of the model key (e.g "aa" or "aba")

	//proceed line by line
	for(lpos=1; lpos<tfile_len; lpos=findtext(tfile,"\n",lpos,0)+1)
		var/tline = copytext(tfile,lpos,findtext(tfile,"\n",lpos,0))
		if(copytext(tline,1,2) != quote)//we reached the map "layout"
			break
		var/model_key = copytext(tline,2,2+key_len)
		var/model_contents = copytext(tline,findtext(tfile,"=")+3,length(tline))
		grid_models[model_key] = model_contents
		sleep(-1)

	///////////////////////////////////////////////////////////////////////////////////////
	//now let's fill the map with turf and objects using the constructed model map
	///////////////////////////////////////////////////////////////////////////////////////

	//position of the currently processed square
	var/zcrd=-1
	var/ycrd=0
	var/xcrd=0

	for(var/zpos=findtext(tfile,"\n(1,1,",lpos,0);zpos!=0;zpos=findtext(tfile,"\n(1,1,",zpos+1,0))	//in case there's several maps to load

		zcrd++
		world.maxz = max(world.maxz, zcrd+z_offset)//create a new z_level if needed

		var/zgrid = copytext(tfile,findtext(tfile,quote+"\n",zpos,0)+2,findtext(tfile,"\n"+quote,zpos,0)+1) //copy the whole map grid
		var/z_depth = length(zgrid)

		//if exceeding the world max x or y, increase it
		var/x_depth = length(copytext(zgrid,1,findtext(zgrid,"\n",2,0)))
		if(world.maxx<x_depth)
			world.maxx=x_depth

		var/y_depth = z_depth / (x_depth+1)//x_depth + 1 because we're counting the '\n' characters in z_depth
		if(world.maxy<y_depth)
			world.maxy=y_depth

		//then proceed it line by line, starting from top
		ycrd = y_depth

		for(var/gpos=1;gpos!=0;gpos=findtext(zgrid,"\n",gpos,0)+1)
			var/grid_line = copytext(zgrid,gpos,findtext(zgrid,"\n",gpos,0))

			//fill the current square using the model map
			xcrd=0
			for(var/mpos=1;mpos<=x_depth;mpos+=key_len)
				xcrd++
				var/model_key = copytext(grid_line,mpos,mpos+key_len)
				parse_grid(grid_models[model_key],xcrd,ycrd,zcrd+z_offset)

			//reached end of current map
			if(gpos+x_depth+1>z_depth)
				break

			ycrd--

			sleep(-1)

		//reached End Of File
		if(findtext(tfile,quote+"}",zpos,0)+2==tfile_len)
			break
		sleep(-1)

/**
 * Fill a given tile with its area/turf/objects/mobs
 * Variable model is one full map line (e.g /turf/unsimulated/wall{icon_state = "rock"},/area/mine/explored)
 *
 * WORKING :
 *
 * 1) Read the model string, member by member (delimiter is ',')
 *
 * 2) Get the path of the atom and store it into a list
 *
 * 3) a) Check if the member has variables (text within '{' and '}')
 *
 * 3) b) Construct an associative list with found variables, if any (the atom index in members is the same as its variables in members_attributes)
 *
 * 4) Instanciates the atom with its variables
 *
 */
/dmm_suite/proc/parse_grid(var/model as text,var/xcrd as num,var/ycrd as num,var/zcrd as num)
	/*Method parse_grid()
	- Accepts a text string containing a comma separated list of type paths of the
		same construction as those contained in a .dmm file, and instantiates them.
	*/

	var/list/members = list()//will contain all members (paths) in model (in our example : /turf/unsimulated/wall and /area/mine/explored)
	var/list/members_attributes = list()//will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list())

	/////////////////////////////////////////////////////////
	//Constructing members and corresponding variables lists
	////////////////////////////////////////////////////////

	var/index=1
	var/old_position = 1
	var/dpos

	do
		//finding next member (e.g /turf/unsimulated/wall{icon_state = "rock"} or /area/mine/explored)
		dpos= find_next_delimiter_position(model,old_position,",","{","}")//find next delimiter (comma here) that's not within {...}

		var/full_def = copytext(model,old_position,dpos)//full definition, e.g : /obj/foo/bar{variables=derp}
		var/atom_def = text2path(copytext(full_def,1,findtext(full_def,"{")))//path definition, e.g /obj/foo/bar
		members.Add(atom_def)
		old_position = dpos + 1

		//transform the variables in text format into a list (e.g {var1="derp"; var2; var3=7} => list(var1="derp", var2, var3=7))
		var/list/fields = list()

		var/variables_start = findtext(full_def,"{")
		if(variables_start)//if there's any variable
			full_def = copytext(full_def,variables_start+1,length(full_def))//removing the last '}'
			fields = text2list(full_def,";")

		//then fill the members_attributes list with the corresponding variables
		members_attributes.len++
		members_attributes[index++] = fields

		sleep(-1)
	while(dpos != 0)

	////////////////
	//Instanciation
	////////////////

	//The next part of the code assumes there's ALWAYS an /area AND a /turf on a given tile

	//in case of multiples turfs on one tile,
	//will contains the images of all underlying turfs, to simulate the DMM multiple tiles piling
	var/list/turfs_underlays = list()

	//first instance the /area and remove it from the members list
	index = members.len
	var/atom/instance
	_preloader = new(members_attributes[index])//preloader for assigning  set variables on atom creation

	instance = locate(members[index])
	instance.contents.Add(locate(xcrd,ycrd,zcrd))

	if(_preloader && instance)
		_preloader.load(instance)

	members.Remove(members[index])

	//then instance the /turf and, if multiple tiles are presents, simulates the DMM underlays piling effect

	var/first_turf_index = 1
	while(!ispath(members[first_turf_index],/turf)) //find first /turf object in members
		first_turf_index++

	//instanciate the first /turf
	var/turf/T = instance_atom(members[first_turf_index],members_attributes[first_turf_index],xcrd,ycrd,zcrd)

	//if others /turf are presents, simulates the underlays piling effect
	index = first_turf_index + 1
	while(index <= members.len)
		turfs_underlays.Insert(1,image(T.icon,null,T.icon_state,T.layer,T.dir))//add the current turf image to the underlays list
		var/turf/UT = instance_atom(members[index],members_attributes[index],xcrd,ycrd,zcrd)//instance new turf
		add_underlying_turf(UT,T,turfs_underlays)//simulates the DMM piling effect
		T = UT
		index++

	//finally instance all remainings objects/mobs
	for(index=1,index < first_turf_index,index++)
		instance_atom(members[index],members_attributes[index],xcrd,ycrd,zcrd)

////////////////
//Helpers procs
////////////////

//Instance an atom at (x,y,z) and gives it the variables in attributes
/dmm_suite/proc/instance_atom(var/path,var/list/attributes, var/x, var/y, var/z)
	var/atom/instance
	_preloader = new(attributes, path)

	instance = new path (locate(x,y,z))//first preloader pass

	if(_preloader && instance)//second preloader pass, for those atoms that don't ..() in New()
		_preloader.load(instance)

	return instance

//text trimming (both directions) helper proc
//optionally removes quotes before and after the text (for variable name)
/dmm_suite/proc/trim_text(var/what as text,var/trim_quotes=0)
	while(length(what) && (findtext(what," ",1,2)))
		what=copytext(what,2,0)
	while(length(what) && (findtext(what," ",length(what),0)))
		what=copytext(what,1,length(what))
	if(trim_quotes)
		while(length(what) && (findtext(what,quote,1,2)))
			what=copytext(what,2,0)
		while(length(what) && (findtext(what,quote,length(what),0)))
			what=copytext(what,1,length(what))
	return what

//find the position of the next delimiter,skipping whatever is comprised between opening_escape and closing_escape
//returns 0 if reached the last delimiter
/dmm_suite/proc/find_next_delimiter_position(var/text as text,var/initial_position as num, var/delimiter=",",var/opening_escape=quote,var/closing_escape=quote)
	var/position = initial_position
	var/next_delimiter = findtext(text,delimiter,position,0)
	var/next_opening = findtext(text,opening_escape,position,0)

	while((next_opening != 0) && (next_opening < next_delimiter))
		position = findtext(text,closing_escape,next_opening + 1,0)+1
		next_delimiter = findtext(text,delimiter,position,0)
		next_opening = findtext(text,opening_escape,position,0)

	return next_delimiter

//build a list from variables in text form (e.g {var1="derp"; var2; var3=7} => list(var1="derp", var2, var3=7))
//return the filled list
/dmm_suite/proc/text2list(var/text as text,var/delimiter=",")

	var/list/to_return = list()

	var/position
	var/old_position = 1

	do
		//find next delimiter that is not within  "..."
		position = find_next_delimiter_position(text,old_position,delimiter)

		//check if this is a simple variable (as in list(var1, var2)) or an associative one (as in list(var1="foo",var2=7))
		var/equal_position = findtext(text,"=",old_position, position)

		var/trim_left = trim_text(copytext(text,old_position,(equal_position ? equal_position : position)),1)//the name of the variable, must trim quotes to build a BYOND compliant associatives list
		old_position = position + 1

		if(equal_position)//associative var, so do the association
			var/trim_right = trim_text(copytext(text,equal_position+1,position))//the content of the variable

			//Check for string
			if(findtext(trim_right,quote,1,2))
				trim_right = copytext(trim_right,2,findtext(trim_right,quote,3,0))

			//Check for number
			else if(isnum(text2num(trim_right)))
				trim_right = text2num(trim_right)

			//Check for null
			else if(trim_right == "null")
				trim_right = null

			//Check for list
			else if(copytext(trim_right,1,5) == "list")
				trim_right = text2list(copytext(trim_right,6,length(trim_right)))

			//Check for file
			else if(copytext(trim_right,1,2) == "'")
				trim_right = file(copytext(trim_right,2,length(trim_right)))

			to_return[trim_left] = trim_right

		else//simple var
			to_return[trim_left] = null

	while(position != 0)

	return to_return

//simulates the DM multiple turfs on one tile underlaying
/dmm_suite/proc/add_underlying_turf(var/turf/placed,var/turf/underturf, var/list/turfs_underlays)
	if(underturf.density)
		placed.density = 1
	if(underturf.opacity)
		placed.opacity = 1
	placed.underlays += turfs_underlays

//atom creation method that preloads variables at creation
/atom/New()
	if(_preloader && (src.type == _preloader.target_path))//in case the instanciated atom is creating other atoms in New()
		_preloader.load(src)

	. = ..()

//////////////////
//Preloader datum
//////////////////

/dmm_suite/preloader
	parent_type = /datum
	var/list/attributes
	var/target_path

/dmm_suite/preloader/New(var/list/the_attributes, var/path)
	.=..()
	if(!the_attributes.len)
		Del()
		return
	attributes = the_attributes
	target_path = path

/dmm_suite/preloader/proc/load(atom/what)
	for(var/attribute in attributes)
		what.vars[attribute] = attributes[attribute]
	Del()


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

/*
	SwapMaps library by Lummox JR
	developed for digitalBYOND
	http://www.digitalbyond.org

	Version 2.1

	The purpose of this library is to make it easy for authors to swap maps
	in and out of their game using savefiles. Swapped-out maps can be
	transferred between worlds for an MMORPG, sent to the client, etc.
	This is facilitated by the use of a special datum and a global list.

	Uses of swapmaps:

	- Temporary battle arenas
	- House interiors
	- Individual custom player houses
	- Virtually unlimited terrain
	- Sharing maps between servers running different instances of the same
	  game
	- Loading and saving pieces of maps for reusable room templates
 */

/*
	User Interface:

	VARS:

	swapmaps_iconcache
		An associative list of icon files with names, like
		'player.dmi' = "player"
	swapmaps_mode
		This must be set at runtime, like in world/New().

		SWAPMAPS_SAV	0	(default)
			Uses .sav files for raw /savefile output.
		SWAPMAPS_TEXT	1
			Uses .txt files via ExportText() and ImportText(). These maps
			are easily editable and appear to take up less space in the
			current version of BYOND.

	PROCS:

	SwapMaps_Find(id)
		Find a map by its id
	SwapMaps_Load(id)
		Load a map by its id
	SwapMaps_Save(id)
		Save a map by its id (calls swapmap.Save())
	SwapMaps_Unload(id)
		Save and unload a map by its id (calls swapmap.Unload())
	SwapMaps_Save_All()
		Save all maps
	SwapMaps_DeleteFile(id)
		Delete a map file
	SwapMaps_CreateFromTemplate(id)
		Create a new map by loading another map to use as a template.
		This map has id==src and will not be saved. To make it savable,
		  change id with swapmap.SetID(newid).
	SwapMaps_LoadChunk(id,turf/locorner)
		Load a swapmap as a "chunk", at a specific place. A new datum is
		created but it's not added to the list of maps to save or unload.
		The new datum can be safely deleted without affecting the turfs
		it loaded. The purpose of this is to load a map file onto part of
		another swapmap or an existing part of the world.
		locorner is the corner turf with the lowest x,y,z values.
	SwapMaps_SaveChunk(id,turf/corner1,turf/corner2)
		Save a piece of the world as a "chunk". A new datum is created
		for the chunk, but it can be deleted without destroying any turfs.
		The chunk file can be reloaded as a swapmap all its own, or loaded
		via SwapMaps_LoadChunk() to become part of another map.
	SwapMaps_GetSize(id)
		Return a list corresponding to the x,y,z sizes of a map file,
		without loading the map.
		Returns null if the map is not found.
	SwapMaps_AddIconToCache(name,icon)
		Cache an icon file by name for space-saving storage

	swapmap.New(id,x,y,z)
		Create a new map; specify id, width (x), height (y), and
		 depth (z)
		Default size is world.maxx,world.maxy,1
	swapmap.New(id,turf1,turf2)
		Create a new map; specify id and 2 corners
		This becomes a /swapmap for one of the compiled-in maps, for
		 easy saving.
	swapmap.New()
		Create a new map datum, but does not allocate space or assign an
		 ID (used for loading).
	swapmap.Del()
		Deletes a map but does not save
	swapmap.Save()
		Saves to map_[id].sav
		Maps with id==src are not saved.
	swapmap.Unload()
		Saves the map and then deletes it
		Maps with id==src are not saved.
	swapmap.SetID(id)
		Change the map's id and make changes to the lookup list
	swapmap.AllTurfs(z)
		Returns a block of turfs encompassing the entire map, or on just
		 one z-level
		z is in world coordinates; it is optional
	swapmap.Contains(turf/T)
		Returns nonzero if T is inside the map's boundaries.
		Also works for objs and mobs, but the proc is not area-safe.
	swapmap.InUse()
		Returns nonzero if a mob with a key is within the map's
		 boundaries.
	swapmap.LoCorner(z=z1)
		Returns locate(x1,y1,z), where z=z1 if none is specified.
	swapmap.HiCorner(z=z2)
		Returns locate(x2,y2,z), where z=z2 if none is specified.
	swapmap.BuildFilledRectangle(turf/corner1,turf/corner2,item)
		Builds a filled rectangle of item from one corner turf to the
		 other, on multiple z-levels if necessary. The corners may be
		 specified in any order.
		item is a type path like /turf/wall or /obj/barrel{full=1}.
	swapmap.BuildRectangle(turf/corner1,turf/corner2,item)
		Builds an unfilled rectangle of item from one corner turf to
		 the other, on multiple z-levels if necessary.
	swapmap.BuildInTurfs(list/turfs,item)
		Builds item on all of the turfs listed. The list need not
		 contain only turfs, or even only atoms.
 */

swapmap
	var/id		// a string identifying this map uniquely
	var/x1		// minimum x,y,z coords
	var/y1
	var/z1
	var/x2		// maximum x,y,z coords (also used as width,height,depth until positioned)
	var/y2
	var/z2
	var/tmp/locked	// don't move anyone to this map; it's saving or loading
	var/tmp/mode	// save as text-mode
	var/ischunk		// tells the load routine to load to the specified location

	New(_id,x,y,z)
		if(isnull(_id)) return
		id=_id
		mode=swapmaps_mode
		if(isturf(x) && isturf(y))
			/*
				Special format: Defines a map as an existing set of turfs;
				this is useful for saving a compiled map in swapmap format.
				Because this is a compiled-in map, its turfs are not deleted
				when the datum is deleted.
			 */
			x1=min(x:x,y:x);x2=max(x:x,y:x)
			y1=min(x:y,y:y);y2=max(x:y,y:y)
			z1=min(x:z,y:z);z2=max(x:z,y:z)
			InitializeSwapMaps()
			if(z2>swapmaps_compiled_maxz ||\
			   y2>swapmaps_compiled_maxy ||\
			   x2>swapmaps_compiled_maxx)
				del(src)
			return
		x2=x?(x):world.maxx
		y2=y?(y):world.maxy
		z2=z?(z):1
		AllocateSwapMap()

	Del()
		// a temporary datum for a chunk can be deleted outright
		// for others, some cleanup is necessary
		if(!ischunk)
			swapmaps_loaded-=src
			swapmaps_byname-=id
			if(z2>swapmaps_compiled_maxz ||\
			   y2>swapmaps_compiled_maxy ||\
			   x2>swapmaps_compiled_maxx)
				var/list/areas=new
				for(var/atom/A in block(locate(x1,y1,z1),locate(x2,y2,z2)))
					for(var/obj/O in A) qdel(O)
					for(var/mob/M in A)
						if(!M.key) qdel(M)
						else M.loc=null
					areas[A.loc]=null
					del(A)
				// delete areas that belong only to this map
				for(var/area/a in areas)
					if(a && !a.contents.len) del(a)
				if(x2>=world.maxx || y2>=world.maxy || z2>=world.maxz) CutXYZ()
				del(areas)
		..()

	/*
		Savefile format:
		map
		  id
		  x		// size, not coords
		  y
		  z
		  areas	// list of areas, not including default
		  [each z; 1 to depth]
		    [each y; 1 to height]
		      [each x; 1 to width]
		        type	// of turf
		        AREA    // if non-default; saved as a number (index into areas list)
		        vars    // all other changed vars
	 */
	Write(savefile/S)
		var/x
		var/y
		var/z
		var/n
		var/list/areas
		var/area/defarea=locate(world.area)
		if(!defarea) defarea=new world.area
		areas=list()
		for(var/turf/T in block(locate(x1,y1,z1),locate(x2,y2,z2)))
			areas[T.loc]=null
		for(n in areas)	// quickly eliminate associations for smaller storage
			areas-=n
			areas+=n
		areas-=defarea
		InitializeSwapMaps()
		locked=1
		S["id"] << id
		S["z"] << z2-z1+1
		S["y"] << y2-y1+1
		S["x"] << x2-x1+1
		S["areas"] << areas
		for(n in 1 to areas.len) areas[areas[n]]=n
		var/oldcd=S.cd
		for(z=z1,z<=z2,++z)
			S.cd="[z-z1+1]"
			for(y=y1,y<=y2,++y)
				S.cd="[y-y1+1]"
				for(x=x1,x<=x2,++x)
					S.cd="[x-x1+1]"
					var/turf/T=locate(x,y,z)
					S["type"] << T.type
					if(T.loc!=defarea) S["AREA"] << areas[T.loc]
					T.Write(S)
					S.cd=".."
				S.cd=".."
			sleep()
			S.cd=oldcd
		locked=0
		del(areas)

	Read(savefile/S,_id,turf/locorner)
		var/x
		var/y
		var/z
		var/n
		var/list/areas
		var/area/defarea=locate(world.area)
		id=_id
		if(locorner)
			ischunk=1
			x1=locorner.x
			y1=locorner.y
			z1=locorner.z
		if(!defarea) defarea=new world.area
		if(!_id)
			S["id"] >> id
		else
			var/dummy
			S["id"] >> dummy
		S["z"] >> z2		// these are depth,
		S["y"] >> y2		//   		 height,
		S["x"] >> x2		//			 width
		S["areas"] >> areas
		locked=1
		AllocateSwapMap()	// adjust x1,y1,z1 - x2,y2,z2 coords
		var/oldcd=S.cd
		for(z=z1,z<=z2,++z)
			S.cd="[z-z1+1]"
			for(y=y1,y<=y2,++y)
				S.cd="[y-y1+1]"
				for(x=x1,x<=x2,++x)
					S.cd="[x-x1+1]"
					var/tp
					S["type"]>>tp
					var/turf/T=locate(x,y,z)
					T.loc.contents-=T
					T=new tp(locate(x,y,z))
					if("AREA" in S.dir)
						S["AREA"]>>n
						var/area/A=areas[n]
						A.contents+=T
					else defarea.contents+=T
					// clear the turf
					for(var/obj/O in T) qdel(O)
					for(var/mob/M in T)
						if(!M.key) qdel(M)
						else M.loc=null
					// finish the read
					T.Read(S)
					S.cd=".."
				S.cd=".."
			sleep()
			S.cd=oldcd
		locked=0
		del(areas)

	/*
		Find an empty block on the world map in which to load this map.
		If no space is found, increase world.maxz as necessary. (If the
		map is greater in x,y size than the current world, expand
		world.maxx and world.maxy too.)

		Ignore certain operations if loading a map as a chunk. Use the
		x1,y1,z1 position for it, and *don't* count it as a loaded map.
	 */
	proc/AllocateSwapMap()
		InitializeSwapMaps()
		world.maxx=max(x2,world.maxx)	// stretch x/y if necessary
		world.maxy=max(y2,world.maxy)
		if(!ischunk)
			if(world.maxz<=swapmaps_compiled_maxz)
				z1=swapmaps_compiled_maxz+1
				x1=1;y1=1
			else
				var/list/l=ConsiderRegion(1,1,world.maxx,world.maxy,swapmaps_compiled_maxz+1)
				x1=l[1]
				y1=l[2]
				z1=l[3]
				del(l)
		x2+=x1-1
		y2+=y1-1
		z2+=z1-1
		world.maxz=max(z2,world.maxz)	// stretch z if necessary
		if(!ischunk)
			swapmaps_loaded[src]=null
			swapmaps_byname[id]=src

	proc/ConsiderRegion(X1,Y1,X2,Y2,Z1,Z2)
		while(1)
			var/nextz=0
			var/swapmap/M
			for(M in swapmaps_loaded)
				if(M.z2<Z1 || (Z2 && M.z1>Z2) || M.z1>=Z1+z2 ||\
				   M.x1>X2 || M.x2<X1 || M.x1>=X1+x2 ||\
				   M.y1>Y2 || M.y2<Y1 || M.y1>=Y1+y2) continue
				// look for sub-regions with a defined ceiling
				var/nz2=Z2?(Z2):Z1+z2-1+M.z2-M.z1
				if(M.x1>=X1+x2)
					.=ConsiderRegion(X1,Y1,M.x1-1,Y2,Z1,nz2)
					if(.) return
				else if(M.x2<=X2-x2)
					.=ConsiderRegion(M.x2+1,Y1,X2,Y2,Z1,nz2)
					if(.) return
				if(M.y1>=Y1+y2)
					.=ConsiderRegion(X1,Y1,X2,M.y1-1,Z1,nz2)
					if(.) return
				else if(M.y2<=Y2-y2)
					.=ConsiderRegion(X1,M.y2+1,X2,Y2,Z1,nz2)
					if(.) return
				nextz=nextz?min(nextz,M.z2+1):(M.z2+1)
			if(!M)
				/* If nextz is not 0, then at some point there was an overlap that
				   could not be resolved by using an area to the side */
				if(nextz) Z1=nextz
				if(!nextz || (Z2 && Z2-Z1+1<z2))
					return (!Z2 || Z2-Z1+1>=z2)?list(X1,Y1,Z1):null
				X1=1;X2=world.maxx
				Y1=1;Y2=world.maxy

	proc/CutXYZ()
		var/mx=swapmaps_compiled_maxx
		var/my=swapmaps_compiled_maxy
		var/mz=swapmaps_compiled_maxz
		for(var/swapmap/M in swapmaps_loaded)	// may not include src
			mx=max(mx,M.x2)
			my=max(my,M.y2)
			mz=max(mz,M.z2)
		world.maxx=mx
		world.maxy=my
		world.maxz=mz

	// save and delete
	proc/Unload()
		Save()
		del(src)

	proc/Save()
		if(id==src) return 0
		var/savefile/S=mode?(new):new("map_[id].sav")
		S << src
		while(locked) sleep(1)
		if(mode)
			fdel("map_[id].txt")
			S.ExportText("/","map_[id].txt")
		return 1

	// this will not delete existing savefiles for this map
	proc/SetID(newid)
		swapmaps_byname-=id
		id=newid
		swapmaps_byname[id]=src

	proc/AllTurfs(z)
		if(isnum(z) && (z<z1 || z>z2)) return null
		return block(LoCorner(z),HiCorner(z))

	// this could be safely called for an obj or mob as well, but
	// probably not an area
	proc/Contains(turf/T)
		return (T && T.x>=x1 && T.x<=x2\
		          && T.y>=y1 && T.y<=y2\
		          && T.z>=z1 && T.z<=z2)

	proc/InUse()
		for(var/turf/T in AllTurfs())
			for(var/mob/M in T) if(M.key) return 1

	proc/LoCorner(z=z1)
		return locate(x1,y1,z)
	proc/HiCorner(z=z2)
		return locate(x2,y2,z)

	/*
		Build procs: Take 2 turfs as corners, plus an item type.
		An item may be like:

		/turf/wall
		/obj/fence{icon_state="iron"}
	 */
	proc/BuildFilledRectangle(turf/T1,turf/T2,item)
		if(!Contains(T1) || !Contains(T2)) return
		var/turf/T=T1
		// pick new corners in a block()-friendly form
		T1=locate(min(T1.x,T2.x),min(T1.y,T2.y),min(T1.z,T2.z))
		T2=locate(max(T.x,T2.x),max(T.y,T2.y),max(T.z,T2.z))
		for(T in block(T1,T2)) new item(T)

	proc/BuildRectangle(turf/T1,turf/T2,item)
		if(!Contains(T1) || !Contains(T2)) return
		var/turf/T=T1
		// pick new corners in a block()-friendly form
		T1=locate(min(T1.x,T2.x),min(T1.y,T2.y),min(T1.z,T2.z))
		T2=locate(max(T.x,T2.x),max(T.y,T2.y),max(T.z,T2.z))
		if(T2.x-T1.x<2 || T2.y-T1.y<2) BuildFilledRectangle(T1,T2,item)
		else
			//for(T in block(T1,T2)-block(locate(T1.x+1,T1.y+1,T1.z),locate(T2.x-1,T2.y-1,T2.z)))
			for(T in block(T1,locate(T2.x,T1.y,T2.z))) new item(T)
			for(T in block(locate(T1.x,T2.y,T1.z),T2)) new item(T)
			for(T in block(locate(T1.x,T1.y+1,T1.z),locate(T1.x,T2.y-1,T2.z))) new item(T)
			for(T in block(locate(T2.x,T1.y+1,T1.z),locate(T2.x,T2.y-1,T2.z))) new item(T)

	/*
		Supplementary build proc: Takes a list of turfs, plus an item
		type. Actually the list doesn't have to be just turfs.
	 */
	proc/BuildInTurfs(list/turfs,item)
		for(var/T in turfs) new item(T)

atom
	Write(savefile/S)
		for(var/V in vars-"x"-"y"-"z"-"contents"-"icon"-"overlays"-"underlays")
			if(issaved(vars[V]))
				if(vars[V]!=initial(vars[V])) S[V]<<vars[V]
				else S.dir.Remove(V)
		if(icon!=initial(icon))
			if(swapmaps_iconcache && swapmaps_iconcache[icon])
				S["icon"]<<swapmaps_iconcache[icon]
			else S["icon"]<<icon
		// do not save mobs with keys; do save other mobs
		var/mob/M
		for(M in src) if(M.key) break
		if(overlays.len) S["overlays"]<<overlays
		if(underlays.len) S["underlays"]<<underlays
		if(contents.len && !isarea(src))
			var/list/l=contents
			if(M)
				l=l.Copy()
				for(M in src) if(M.key) l-=M
			if(l.len) S["contents"]<<l
			if(l!=contents) del(l)
	Read(savefile/S)
		var/list/l
		if(contents.len) l=contents
		..()
		// if the icon was a text string, it would not have loaded properly
		// replace it from the cache list
		if(!icon && ("icon" in S.dir))
			var/ic
			S["icon"]>>ic
			if(istext(ic)) icon=swapmaps_iconcache[ic]
		if(l && contents!=l)
			contents+=l
			del(l)

// set this up (at runtime) as follows:
// list(\
//     'player.dmi'="player",\
//     'monster.dmi'="monster",\
//     ...
//     'item.dmi'="item")
var/list/swapmaps_iconcache

// preferred mode; sav or text
var/const/SWAPMAPS_SAV=0
var/const/SWAPMAPS_TEXT=1
var/swapmaps_mode=SWAPMAPS_SAV

var/swapmaps_compiled_maxx
var/swapmaps_compiled_maxy
var/swapmaps_compiled_maxz
var/swapmaps_initialized
var/swapmaps_loaded
var/swapmaps_byname

proc/InitializeSwapMaps()
	if(swapmaps_initialized) return
	swapmaps_initialized=1
	swapmaps_compiled_maxx=world.maxx
	swapmaps_compiled_maxy=world.maxy
	swapmaps_compiled_maxz=world.maxz
	swapmaps_loaded=list()
	swapmaps_byname=list()
	if(swapmaps_iconcache)
		for(var/V in swapmaps_iconcache)
			// reverse-associate everything
			// so you can look up an icon file by name or vice-versa
			swapmaps_iconcache[swapmaps_iconcache[V]]=V

proc/SwapMaps_AddIconToCache(name,icon)
	if(!swapmaps_iconcache) swapmaps_iconcache=list()
	swapmaps_iconcache[name]=icon
	swapmaps_iconcache[icon]=name

proc/SwapMaps_Find(id)
	InitializeSwapMaps()
	return swapmaps_byname[id]

proc/SwapMaps_Load(id)
	InitializeSwapMaps()
	var/swapmap/M=swapmaps_byname[id]
	if(!M)
		var/savefile/S
		var/text=0
		if(swapmaps_mode==SWAPMAPS_TEXT && fexists("map_[id].txt"))
			text=1
		else if(fexists("map_[id].sav"))
			S=new("map_[id].sav")
		else if(swapmaps_mode!=SWAPMAPS_TEXT && fexists("map_[id].txt"))
			text=1
		else return	// no file found
		if(text)
			S=new
			S.ImportText("/",file("map_[id].txt"))
		S >> M
		while(M.locked) sleep(1)
		M.mode=text
	return M

proc/SwapMaps_Save(id)
	InitializeSwapMaps()
	var/swapmap/M=swapmaps_byname[id]
	if(M) M.Save()
	return M

proc/SwapMaps_Save_All()
	InitializeSwapMaps()
	for(var/swapmap/M in swapmaps_loaded)
		if(M) M.Save()

proc/SwapMaps_Unload(id)
	InitializeSwapMaps()
	var/swapmap/M=swapmaps_byname[id]
	if(!M) return	// return silently from an error
	M.Unload()
	return 1

proc/SwapMaps_DeleteFile(id)
	fdel("map_[id].sav")
	fdel("map_[id].txt")

proc/SwapMaps_CreateFromTemplate(template_id)
	var/swapmap/M=new
	var/savefile/S
	var/text=0
	if(swapmaps_mode==SWAPMAPS_TEXT && fexists("map_[template_id].txt"))
		text=1
	else if(fexists("map_[template_id].sav"))
		S=new("map_[template_id].sav")
	else if(swapmaps_mode!=SWAPMAPS_TEXT && fexists("map_[template_id].txt"))
		text=1
	else
		world.log << "SwapMaps error in SwapMaps_CreateFromTemplate(): map_[template_id] file not found."
		return
	if(text)
		S=new
		S.ImportText("/",file("map_[template_id].txt"))
	/*
		This hacky workaround is needed because S >> M will create a brand new
		M to fill with data. There's no way to control the Read() process
		properly otherwise. The //.0 path should always match the map, however.
	 */
	S.cd="//.0"
	M.Read(S,M)
	M.mode=text
	while(M.locked) sleep(1)
	return M

proc/SwapMaps_LoadChunk(chunk_id,turf/locorner)
	var/swapmap/M=new
	var/savefile/S
	var/text=0
	if(swapmaps_mode==SWAPMAPS_TEXT && fexists("map_[chunk_id].txt"))
		text=1
	else if(fexists("map_[chunk_id].sav"))
		S=new("map_[chunk_id].sav")
	else if(swapmaps_mode!=SWAPMAPS_TEXT && fexists("map_[chunk_id].txt"))
		text=1
	else
		world.log << "SwapMaps error in SwapMaps_LoadChunk(): map_[chunk_id] file not found."
		return
	if(text)
		S=new
		S.ImportText("/",file("map_[chunk_id].txt"))
	/*
		This hacky workaround is needed because S >> M will create a brand new
		M to fill with data. There's no way to control the Read() process
		properly otherwise. The //.0 path should always match the map, however.
	 */
	S.cd="//.0"
	M.Read(S,M,locorner)
	while(M.locked) sleep(1)
	del(M)
	return 1

proc/SwapMaps_SaveChunk(chunk_id,turf/corner1,turf/corner2)
	if(!corner1 || !corner2)
		world.log << "SwapMaps error in SwapMaps_SaveChunk():"
		if(!corner1) world.log << "  corner1 turf is null"
		if(!corner2) world.log << "  corner2 turf is null"
		return
	var/swapmap/M=new
	M.id=chunk_id
	M.ischunk=1		// this is a chunk
	M.x1=min(corner1.x,corner2.x)
	M.y1=min(corner1.y,corner2.y)
	M.z1=min(corner1.z,corner2.z)
	M.x2=max(corner1.x,corner2.x)
	M.y2=max(corner1.y,corner2.y)
	M.z2=max(corner1.z,corner2.z)
	M.mode=swapmaps_mode
	M.Save()
	while(M.locked) sleep(1)
	del(M)
	return 1

proc/SwapMaps_GetSize(id)
	var/savefile/S
	var/text=0
	if(swapmaps_mode==SWAPMAPS_TEXT && fexists("map_[id].txt"))
		text=1
	else if(fexists("map_[id].sav"))
		S=new("map_[id].sav")
	else if(swapmaps_mode!=SWAPMAPS_TEXT && fexists("map_[id].txt"))
		text=1
	else
		world.log << "SwapMaps error in SwapMaps_GetSize(): map_[id] file not found."
		return
	if(text)
		S=new
		S.ImportText("/",file("map_[id].txt"))
	/*
		The //.0 path should always be the map. There's no other way to
		read this data.
	 */
	S.cd="//.0"
	var/x
	var/y
	var/z
	S["x"] >> x
	S["y"] >> y
	S["z"] >> z
	return list(x,y,z)


code:
#define DMM_IGNORE_AREAS 1
#define DMM_IGNORE_TURFS 2
#define DMM_IGNORE_OBJS 4
#define DMM_IGNORE_NPCS 8
#define DMM_IGNORE_PLAYERS 16
#define DMM_IGNORE_MOBS 24
dmm_suite{
	var{
		quote = "\""
		list/letter_digits = list(
			"a","b","c","d","e",
			"f","g","h","i","j",
			"k","l","m","n","o",
			"p","q","r","s","t",
			"u","v","w","x","y",
			"z",
			"A","B","C","D","E",
			"F","G","H","I","J",
			"K","L","M","N","O",
			"P","Q","R","S","T",
			"U","V","W","X","Y",
			"Z"
			)
		}
	save_map(var/turf/t1 as turf, var/turf/t2 as turf, var/map_name as text, var/flags as num){
		//Check for illegal characters in file name... in a cheap way.
		if(!((ckeyEx(map_name)==map_name) && ckeyEx(map_name))){
			CRASH("Invalid text supplied to proc save_map, invalid characters or empty string.")
			}
		//Check for valid turfs.
		if(!isturf(t1) || !isturf(t2)){
			CRASH("Invalid arguments supplied to proc save_map, arguments were not turfs.")
			}
		var/file_text = write_map(t1,t2,flags)
		if(fexists("[map_name].dmm")){
			fdel("[map_name].dmm")
			}
		var/saved_map = file("[map_name].dmm")
		saved_map << file_text
		return saved_map
		}
	write_map(var/turf/t1 as turf, var/turf/t2 as turf, var/flags as num){
		//Check for valid turfs.
		if(!isturf(t1) || !isturf(t2)){
			CRASH("Invalid arguments supplied to proc write_map, arguments were not turfs.")
			}
		var/turf/nw = locate(min(t1.x,t2.x),max(t1.y,t2.y),min(t1.z,t2.z))
		var/turf/se = locate(max(t1.x,t2.x),min(t1.y,t2.y),max(t1.z,t2.z))
		var/list/templates[0]
		var/template_buffer = {""}
		var/dmm_text = {""}
		for(var/pos_z=nw.z;pos_z<=se.z;pos_z++){
			for(var/pos_y=nw.y;pos_y>=se.y;pos_y--){
				for(var/pos_x=nw.x;pos_x<=se.x;pos_x++){
					var/turf/test_turf = locate(pos_x,pos_y,pos_z)
					var/test_template = make_template(test_turf, flags)
					var/template_number = templates.Find(test_template)
					if(!template_number){
						templates.Add(test_template)
						template_number = templates.len
						}
					template_buffer += "[template_number],"
					}
				template_buffer += ";"
				}
			template_buffer += "."
			}
		var/key_length = round/*floor*/(log(letter_digits.len,templates.len-1)+1)
		var/list/keys[templates.len]
		for(var/key_pos=1;key_pos<=templates.len;key_pos++){
			keys[key_pos] = get_model_key(key_pos,key_length)
			dmm_text += {""[keys[key_pos]]" = ([templates[key_pos]])\n"}
			}
		var/z_level = 0
		for(var/z_pos=1;TRUE;z_pos=findtext(template_buffer,".",z_pos)+1){
			if(z_pos>=length(template_buffer)){break}
			if(z_level){dmm_text+={"\n"}}
			dmm_text += {"\n(1,1,[++z_level]) = {"\n"}
			var/z_block = copytext(template_buffer,z_pos,findtext(template_buffer,".",z_pos))
			for(var/y_pos=1;TRUE;y_pos=findtext(z_block,";",y_pos)+1){
				if(y_pos>=length(z_block)){break}
				var/y_block = copytext(z_block,y_pos,findtext(z_block,";",y_pos))
				for(var/x_pos=1;TRUE;x_pos=findtext(y_block,",",x_pos)+1){
					if(x_pos>=length(y_block)){break}
					var/x_block = copytext(y_block,x_pos,findtext(y_block,",",x_pos))
					var/key_number = text2num(x_block)
					var/temp_key = keys[key_number]
					dmm_text += temp_key
					sleep(-1)
					}
				dmm_text += {"\n"}
				sleep(-1)
				}
			dmm_text += {"\"}"}
			sleep(-1)
			}
		return dmm_text
		}
	proc{
		make_template(var/turf/model as turf, var/flags as num){
			var/template = ""
			var/obj_template = ""
			var/mob_template = ""
			var/turf_template = ""
			if(!(flags & DMM_IGNORE_TURFS)){
				turf_template = "[model.type][check_attributes(model)],"
				} else{ turf_template = "[world.turf],"}
			var/area_template = ""
			if(!(flags & DMM_IGNORE_OBJS)){
				for(var/obj/O in model.contents){
					obj_template += "[O.type][check_attributes(O)],"
					}
				}
			for(var/mob/M in model.contents){
				if(M.client){
					if(!(flags & DMM_IGNORE_PLAYERS)){
						mob_template += "[M.type][check_attributes(M)],"
						}
					}
				else{
					if(!(flags & DMM_IGNORE_NPCS)){
						mob_template += "[M.type][check_attributes(M)],"
						}
					}
				}
			if(!(flags & DMM_IGNORE_AREAS)){
				var/area/m_area = model.loc
				area_template = "[m_area.type][check_attributes(m_area)]"
				} else{ area_template = "[world.area]"}
			template = "[obj_template][mob_template][turf_template][area_template]"
			return template
			}
		check_attributes(var/atom/A){
			var/attributes_text = {"{"}
			for(var/V in A.vars){
				sleep(-1)
				if((!issaved(A.vars[V])) || (A.vars[V]==initial(A.vars[V]))){continue}
				if(istext(A.vars[V])){
					attributes_text += {"[V] = "[A.vars[V]]""}
					}
				else if(isnum(A.vars[V])||ispath(A.vars[V])){
					attributes_text += {"[V] = [A.vars[V]]"}
					}
				else if(isicon(A.vars[V])||isfile(A.vars[V])){
					attributes_text += {"[V] = '[A.vars[V]]'"}
					}
				else{
					continue
					}
				if(attributes_text != {"{"}){
					attributes_text+={"; "}
					}
				}
			if(attributes_text=={"{"}){
				return
				}
			if(copytext(attributes_text, length(attributes_text)-1, 0) == {"; "}){
				attributes_text = copytext(attributes_text, 1, length(attributes_text)-1)
				}
			attributes_text += {"}"}
			return attributes_text
			}
		get_model_key(var/which as num, var/key_length as num){
			var/key = ""
			var/working_digit = which-1
			for(var/digit_pos=key_length;digit_pos>=1;digit_pos--){
				var/place_value = round/*floor*/(working_digit/(letter_digits.len**(digit_pos-1)))
				working_digit-=place_value*(letter_digits.len**(digit_pos-1))
				key = "[key][letter_digits[place_value+1]]"
				}
			return key
			}
		}
	}


code:
//Academy Areas

/area/awaymission/academy
	name = "\improper Academy Asteroids"
	icon_state = "away"

/area/awaymission/academy/headmaster
	name = "\improper Academy Fore Block"
	icon_state = "away1"

/area/awaymission/academy/classrooms
	name = "\improper Academy Classroom Block"
	icon_state = "away2"

/area/awaymission/academy/academyaft
	name = "\improper Academy Ship Aft Block"
	icon_state = "away3"

/area/awaymission/academy/academygate
	name = "\improper Academy Gateway"
	icon_state = "away4"

//Academy Items

/obj/singularity/academy
	dissipate = 0
	move_self = 0
	grav_pull = 1

/obj/singularity/academy/admin_investigate_setup()
	return

/obj/singularity/academy/process()
	eat()
	if(prob(1))
		mezzer()

/obj/item/clothing/glasses/meson/truesight
	name = "The Lens of Truesight"
	desc = "I can see forever!"
	icon_state = "monocle"
	item_state = "headset"


code:
//Packer Ship Areas

/area/awaymission/BMPship
	name = "\improper BMP Asteroids"
	icon_state = "away"
	luminosity = 0

/area/awaymission/BMPship/Aft
	name = "\improper Aft Block"
	icon_state = "away1"
	requires_power = 1

/area/awaymission/BMPship/Midship
	name = "\improper Midship Block"
	icon_state = "away2"
	requires_power = 1

/area/awaymission/BMPship/Fore
	name = "\improper Fore Block"
	icon_state = "away3"
	requires_power = 1


code:
//centcomAway areas

/area/awaymission/centcomAway
	name = "XCC-P5831"
	icon_state = "away"
	requires_power = 0

/area/awaymission/centcomAway/general
	name = "XCC-P5831"
	music = "music/ambigen3.ogg"

/area/awaymission/centcomAway/maint
	name = "XCC-P5831 Maintenance"
	icon_state = "away1"
	music = "music/ambisin1.ogg"

/area/awaymission/centcomAway/thunderdome
	name = "XCC-P5831 Thunderdome"
	icon_state = "away2"
	music = "music/ambisin2.ogg"

/area/awaymission/centcomAway/cafe
	name = "XCC-P5831 Kitchen Arena"
	icon_state = "away3"
	music = "music/ambisin3.ogg"

/area/awaymission/centcomAway/courtroom
	name = "XCC-P5831 Courtroom"
	icon_state = "away4"
	music = "music/ambisin4.ogg"

/area/awaymission/centcomAway/hangar
	name = "XCC-P5831 Hangars"
	icon_state = "away4"
	music = "music/ambigen5.ogg"

//centcomAway items

/obj/item/weapon/paper/pamphlet/ccaInfo
	name = "Visitor Info Pamphlet"
	info = " XCC-P5831 Visitor Information
\
	Greetings, visitor, to  XCC-P5831! As you may know, this outpost was once \
	used as Nanotrasen's CENTRAL COMMAND STATION, organizing and coordinating company \
	projects across the vastness of space.
\
	Since the completion of the much more efficient CC-A5831 on March 8, 2553, XCC-P5831 no longer \
	acts as NT's base of operations but still plays a very important role its corporate affairs; \
	serving as a supply and repair depot, as well as being host to its most important legal proceedings\
	and the thrilling pay-per-view broadcasts of PLASTEEL CHEF and THUNDERDOME LIVE.
 \
	We hope you enjoy your stay!"

/obj/item/weapon/paper/ccaMemo
	name = "Memo to XCC-P5831 QM"
	info = "From: XCC-P5831 Management Office
\
	To: Rolf Ingram, XCC-P5831 Quartermaster
\
	Hey, Rolf, once you pack that gateway into the ferry hangar, make absolutely sure \
	to deactivate it! As you may know, SS13 has recently got its network up and running, \
	which means that until we get this gate shipped off to the next colonization staging \
	area, they'll be able to hop straight in here if its hooked up on our end.
\
	Obviously, that's something I'd very much rather avoid. Our forensics and medical \
	teams never did figure out what happened that last time... and I can't wrap my head \
	around it myself. Why would a shuttle full of evacuees all snap and beat each other \
	to death the moment they reached safety?
\
	- D. Cereza"


code:
//Challenge Areas

/area/awaymission/challenge/start
	name = "Where Am I?"
	icon_state = "away"

/area/awaymission/challenge/main
	name = "\improper Danger Room"
	icon_state = "away1"
	requires_power = 0

/area/awaymission/challenge/end
	name = "Administration"
	icon_state = "away2"
	requires_power = 0

/obj/machinery/power/emitter/energycannon
	name = "Energy Cannon"
	desc = "A heavy duty industrial laser"
	icon = 'icons/obj/singularity.dmi'
	icon_state = "emitter"
	anchored = 1
	density = 1

	use_power = 0
	idle_power_usage = 0
	active_power_usage = 0

	active = 1
	locked = 1
	state = 2


code:
//Spacebattle Areas

/area/awaymission/spacebattle
	name = "\improper Space Battle"
	icon_state = "away"
	requires_power = 0

/area/awaymission/spacebattle/cruiser
	name = "\improper Nanotrasen Cruiser"

/area/awaymission/spacebattle/syndicate1
	name = "\improper Syndicate Assault Ship 1"

/area/awaymission/spacebattle/syndicate2
	name = "\improper Syndicate Assault Ship 2"

/area/awaymission/spacebattle/syndicate3
	name = "\improper Syndicate Assault Ship 3"

/area/awaymission/spacebattle/syndicate4
	name = "\improper Syndicate War Sphere 1"

/area/awaymission/spacebattle/syndicate5
	name = "\improper Syndicate War Sphere 2"

/area/awaymission/spacebattle/syndicate6
	name = "\improper Syndicate War Sphere 3"

/area/awaymission/spacebattle/syndicate7
	name = "\improper Syndicate Fighter"

/area/awaymission/spacebattle/secret
	name = "\improper Hidden Chamber"


code:
/* Station-Collision(sc) away mission map specific stuff
 *
 * Notes:
 *		Feel free to use parts of this map, or even all of it for your own project. Just include me in the credits :)
 *
 *		Some of this code unnecessary, but the intent is to add a little bit of everything to serve as examples
 *		for anyone who wants to make their own stuff.
 *
 * Contains:
 *		Areas
 *		Landmarks
 *		Guns
 *		Safe code hints
 *		Captain's safe
 *		Modified Nar-Sie
 */

/*
 * Areas
 */
 //Gateroom gets its own APC specifically for the gate
 /area/awaymission/gateroom

 //Library, medbay, storage room
 /area/awaymission/southblock

 //Arrivals, security, hydroponics, shuttles (since they dont move, they dont need specific areas)
 /area/awaymission/arrivalblock

 //Crew quarters, cafeteria, chapel
 /area/awaymission/midblock

 //engineering, bridge (not really north but it doesnt really need its own APC)
 /area/awaymission/northblock

 //That massive research room
 /area/awaymission/research

//Syndicate shuttle
/area/awaymission/syndishuttle

/*
 * Landmarks - Instead of spawning a new object type, I'll spawn the bible using a landmark!
 */
/obj/effect/landmark/sc_bible_spawner
	name = "Safecode hint spawner"

/obj/effect/landmark/sc_bible_spawner/New()
	var/obj/item/weapon/storage/book/bible/B = new /obj/item/weapon/storage/book/bible/booze(src.loc)
	B.name = "The Holy book of the Geometer"
	B.deity_name = "Narsie"
	B.icon_state = "melted"
	B.item_state = "melted"
	new /obj/item/weapon/paper/sc_safehint_paper_bible(B)
	new /obj/item/weapon/pen(B)
	qdel(src)

/*
 * Guns - I'm making these specifically so that I dont spawn a pile of fully loaded weapons on the map.
 */
//Captain's retro laser - Fires practice laser shots instead.
obj/item/weapon/gun/energy/laser/retro/sc_retro
	name ="retro laser"
	icon_state = "retro"
	desc = "An older model of the basic lasergun, no longer used by Nanotrasen's security or military forces."
//	projectile_type = "/obj/item/projectile/practice"
	clumsy_check = 0 //No sense in having a harmless gun blow up in the clowns face

//Syndicate sub-machine guns.
/obj/item/weapon/gun/projectile/automatic/c20r/sc_c20r

/obj/item/weapon/gun/projectile/automatic/c20r/sc_c20r/New()
	..()
	for(var/ammo in magazine.stored_ammo)
		if(prob(95)) //95% chance
			magazine.stored_ammo -= ammo

//Barman's shotgun
/obj/item/weapon/gun/projectile/shotgun/sc_pump

/obj/item/weapon/gun/projectile/shotgun/sc_pump/New()
	..()
	for(var/ammo in magazine.stored_ammo)
		if(prob(95)) //95% chance
			magazine.stored_ammo -= ammo

//Lasers
/obj/item/weapon/gun/energy/laser/practice/sc_laser
	name = "Old laser"
	desc = "A once potent weapon, years of dust have collected in the chamber and lens of this weapon, weakening the beam significantly."
	clumsy_check = 0

/*
 * Safe code hints
 */

//These vars hold the code itself, they'll be generated at round-start
var/sc_safecode1 = "[rand(0,9)]"
var/sc_safecode2 = "[rand(0,9)]"
var/sc_safecode3 = "[rand(0,9)]"
var/sc_safecode4 = "[rand(0,9)]"
var/sc_safecode5 = "[rand(0,9)]"

//Pieces of paper actually containing the hints
/obj/item/weapon/paper/sc_safehint_paper_prison
	name = "smudged paper"

/obj/item/weapon/paper/sc_safehint_paper_prison/New()
	info = "The ink is smudged, you can only make out a couple numbers: '[sc_safecode1]**[sc_safecode4]*'"

/obj/item/weapon/paper/sc_safehint_paper_hydro
	name = "shredded paper"
/obj/item/weapon/paper/sc_safehint_paper_hydro/New()
	info = "Although the paper is shredded, you can clearly see the number: '[sc_safecode2]'"

/obj/item/weapon/paper/sc_safehint_paper_caf
	name = "blood-soaked paper"
	//This does not have to be in New() because it is a constant. There are no variables in it i.e. [sc_safcode]
	info = "This paper is soaked in blood, it is impossible to read any text."

/obj/item/weapon/paper/sc_safehint_paper_bible
	name = "hidden paper"
/obj/item/weapon/paper/sc_safehint_paper_bible/New()
	info = {"It would appear that the pen hidden with the paper had leaked ink over the paper.
			However you can make out the last three digits:'[sc_safecode3][sc_safecode4][sc_safecode5]'
			"}

/obj/item/weapon/paper/sc_safehint_paper_shuttle
	info = {"Target: Research-station Epsilon

			Objective: Prototype weaponry. The captain likely keeps them locked in her safe.

			Our on-board spy has learned the code and has hidden away a few copies of the code around the station. Unfortunatly he has been captured by security
			Your objective is to split up, locate any of the papers containing the captain's safe code, open the safe and
			secure anything found inside. If possible, recover the imprisioned syndicate operative and recieve the code from him.

			As always, eliminate anyone who gets in the way.

			Your assigned ship is designed specifically for penetrating the hull of another station or ship with minimal damage to operatives.
			It is completely fly-by-wire meaning you have just have to enjoy the ride and when the red light comes on... find something to hold onto!
			"}
/*
 * Captain's safe
 */
/obj/item/weapon/storage/secure/safe/sc_ssafe
	name = "Captain's secure safe"

/obj/item/weapon/storage/secure/safe/sc_ssafe/New()
	..()
	l_code = "[sc_safecode1][sc_safecode2][sc_safecode3][sc_safecode4][sc_safecode5]"
	l_set = 1
	new /obj/item/weapon/gun/energy/mindflayer(src)
	new /obj/item/device/soulstone(src)
	new /obj/item/clothing/head/helmet/space/cult(src)
	new /obj/item/clothing/suit/space/cult(src)
	//new /obj/item/weapon/teleportation_scroll(src)
	new /obj/item/weapon/ore/diamond(src)

/*
 * Modified Nar-Sie
 */
/obj/singularity/narsie/sc_Narsie
	desc = "Your body becomes weak and your feel your mind slipping away as you try to comprehend what you know can't be possible."
	move_self = 0 //Contianed narsie does not move!
	grav_pull = 0 //Contained narsie does not pull stuff in!
	var/uneatable = list(/turf/space, /obj/effect/overlay, /mob/living/simple_animal/construct)
//Override this to prevent no adminlog runtimes and admin warnings about a singularity without containment
/obj/singularity/narsie/sc_Narsie/admin_investigate_setup()
	return

/obj/singularity/narsie/sc_Narsie/process()
	eat()
	if(prob(25))
		mezzer()

/obj/singularity/narsie/sc_Narsie/consume(var/atom/A)
	if(is_type_in_list(A, uneatable))
		return 0
	if (istype(A,/mob/living))
		var/mob/living/L = A
		L.gib()
	else if(istype(A,/obj/))
		var/obj/O = A
		O.ex_act(1.0)
		if(O) qdel(O)
	else if(isturf(A))
		var/turf/T = A
		if(T.intact)
			for(var/obj/O in T.contents)
				if(O.level != 1)
					continue
				if(O.invisibility == 101)
					src.consume(O)
		T.ChangeTurf(/turf/space)
	return

/obj/singularity/narsie/sc_Narsie/ex_act()
	return


code:
/* Code for the Wild West map by Brotemis
 * Contains:
 *		Wish Granter
 *		Meat Grinder
 */

//Wild West Areas

/area/awaymission/wwmines
	name = "\improper Wild West Mines"
	icon_state = "away1"
	luminosity = 1
	requires_power = 0

/area/awaymission/wwgov
	name = "\improper Wild West Mansion"
	icon_state = "away2"
	luminosity = 1
	requires_power = 0

/area/awaymission/wwrefine
	name = "\improper Wild West Refinery"
	icon_state = "away3"
	luminosity = 1
	requires_power = 0

/area/awaymission/wwvault
	name = "\improper Wild West Vault"
	icon_state = "away3"
	luminosity = 0

/area/awaymission/wwvaultdoors
	name = "\improper Wild West Vault Doors"  // this is to keep the vault area being entirely lit because of requires_power
	icon_state = "away2"
	requires_power = 0
	luminosity = 0

/*
 * Wish Granter
 */
/obj/machinery/wish_granter_dark
	name = "Wish Granter"
	desc = "You're not so sure about this, anymore..."
	icon = 'icons/obj/device.dmi'
	icon_state = "syndbeacon"

	anchored = 1
	density = 1
	use_power = 0

	var/chargesa = 1
	var/insistinga = 0

/obj/machinery/wish_granter_dark/attack_hand(var/mob/living/carbon/human/user as mob)
	usr.set_machine(src)

	if(chargesa <= 0)
		user << "The Wish Granter lies silent."
		return

	else if(!istype(user, /mob/living/carbon/human))
		user << "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's."
		return

	else if(is_special_character(user))
		user << "Even to a heart as dark as yours, you know nothing good will come of this.  Something instinctual makes you pull away."

	else if (!insistinga)
		user << "Your first touch makes the Wish Granter stir, listening to you.  Are you really sure you want to do this?"
		insistinga++

	else
		chargesa--
		insistinga = 0
		var/wish = input("You want...","Wish") as null|anything in list("Power","Wealth","Immortality","To Kill","Peace")
		switch(wish)
			if("Power")
				user << "Your wish is granted, but at a terrible cost..."
				user << "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart."
				user.dna.add_mutation(LASEREYES)
				user.dna.add_mutation(COLDRES)
				user.dna.add_mutation(XRAY)
				hardset_dna(user, null, null, null, null, /datum/species/shadow)
				user.regenerate_icons()
			if("Wealth")
				user << "Your wish is granted, but at a terrible cost..."
				user << "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart."
				new /obj/structure/closet/syndicate/resources/everything(loc)
				hardset_dna(user, null, null, null, null, /datum/species/shadow)
				user.regenerate_icons()
			if("Immortality")
				user << "Your wish is granted, but at a terrible cost..."
				user << "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart."
				user.verbs += /mob/living/carbon/proc/immortality
				hardset_dna(user, null, null, null, null, /datum/species/shadow)
				user.regenerate_icons()
			if("To Kill")
				user << "Your wish is granted, but at a terrible cost..."
				user << "The Wish Granter punishes you for your wickedness, claiming your soul and warping your body to match the darkness in your heart."
				ticker.mode.traitors += user.mind
				user.mind.special_role = "traitor"
				var/datum/objective/hijack/hijack = new
				hijack.owner = user.mind
				user.mind.objectives += hijack
				user << "Your inhibitions are swept away, the bonds of loyalty broken, you are free to murder as you please!"
				var/obj_count = 1
				for(var/datum/objective/OBJ in user.mind.objectives)
					user << "Objective #[obj_count]: [OBJ.explanation_text]"
					obj_count++
				hardset_dna(user, null, null, null, null, /datum/species/shadow)
				user.regenerate_icons()
			if("Peace")
				user << "Whatever alien sentience that the Wish Granter possesses is satisfied with your wish. There is a distant wailing as the last of the Faithless begin to die, then silence."
				user << "You feel as if you just narrowly avoided a terrible fate..."
				for(var/mob/living/simple_animal/hostile/faithless/F in world)
					F.health = -10
					F.stat = 2
					F.icon_state = "faithless_dead"

///////////////Meatgrinder//////////////

/obj/effect/meatgrinder
	name = "Meat Grinder"
	desc = "What is that thing?"
	density = 1
	anchored = 1
	layer = 3
	icon = 'icons/mob/blob.dmi'
	icon_state = "blobpod"
	var/triggerproc = "explode" //name of the proc thats called when the mine is triggered
	var/triggered = 0

/obj/effect/meatgrinder/New()
	icon_state = "blobpod"

/obj/effect/meatgrinder/Crossed(AM as mob|obj)
	Bumped(AM)

/obj/effect/meatgrinder/Bumped(mob/M as mob|obj)

	if(triggered) return

	if(istype(M, /mob/living/carbon/human) || istype(M, /mob/living/carbon/monkey))
		for(var/mob/O in viewers(world.view, src.loc))
			O << "[M] triggered the \icon[src] [src]"
		triggered = 1
		call(src,triggerproc)(M)

/obj/effect/meatgrinder/proc/triggerrad1(mob)
	var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
	for(var/mob/O in viewers(world.view, src.loc))
		s.set_up(3, 1, src)
		s.start()
		explosion(mob, 1, 0, 0, 0)
		qdel(src)

/*/obj/effect/meatgrinder
	name = "Meat Grinder"
	icon_state = "blob"
	triggerproc = "triggerrad1"*/

/////For the Wishgranter///////////

/mob/living/carbon/proc/immortality()
	set category = "Immortality"
	set name = "Resurrection"

	var/mob/living/carbon/C = usr
	if(!C.stat)
		C << "You're not dead yet!"
		return
	C << "Death is not your end!"

	spawn(rand(800,1200))
		if(C.stat == DEAD)
			dead_mob_list -= C
			living_mob_list += C
		C.stat = CONSCIOUS
		C.tod = null
		C.setToxLoss(0)
		C.setOxyLoss(0)
		C.setCloneLoss(0)
		C.SetParalysis(0)
		C.SetStunned(0)
		C.SetWeakened(0)
		C.radiation = 0
		C.heal_overall_damage(C.getBruteLoss(), C.getFireLoss())
		C.reagents.clear_reagents()
		C << "You have regenerated."
		C.visible_message("[usr] appears to wake from the dead, having healed all wounds.")
		C.update_canmove()
	return 1


code:
/obj/item/weapon/paper/pamphlet
	name = "pamphlet"
	icon_state = "pamphlet"
	info = "Welcome to the Nanotrasen Gateway project...
\
			Congratulations! If you're reading this, you and your superiors have decided that you're \
			ready to commit to a life spent colonising the rolling hills of far away worlds. You \
			must be ready for a lifetime of adventure, a little bit of hard work, and an award \
			winning dental plan- but that's not all the Nanotrasen Gateway project has to offer.
\

Because we care about you, we feel it is only fair to make sure you know the risks \
			before you commit to joining the Nanotrasen Gateway project. All away destinations have \
			been fully scanned by a Nanotrasen expeditionary team, and are certified to be 100% safe. \
			We've even left a case of space beer along with the basic materials you'll need to expand \
			Nanotrasen's operational area and start your new life.

\
			Gateway Operation Basics
\
			All Nanotrasen approved Gateways operate on the same basic principals. They operate off \
			area equipment power as you would expect, but they also require a backup wire with at least \
			128, 000 Watts of power running through it. Without this supply, it cannot safely function \
			and will reject all attempts at operation.

\
			Once it is correctly setup, and once it has enough power to operate, the Gateway will begin \
			searching for an output location. The amount of time this takes is variable, but the Gateway \
			interface will give you an estimate accurate to the minute. Power loss will not interrupt the \
			searching process. Influenza will not interrupt the searching process. Temporal anomalies \
			may cause the estimate to be inaccurate, but will not interrupt the searching process.

 \
			Life On The Other Side
\
			Once you have traversed the Gateway, you may experience some disorientation. Do not panic. \
			This is a normal side effect of travelling vast distances in a short period of time. You should \
			survey the immediate area, and attempt to locate your complimentary case of space beer. Our \
			expeditionary teams have ensured the complete safety of all away locations, but in a small \
			number of cases, the Gateway they have established may not be immediately obvious. \
			Do not panic if you cannot locate the return Gateway. Begin colonisation of the destination. \

A New World
\
			As a participant in the Nanotrasen Gateway Project, you will be on the frontiers of space. \
			Though complete safety is assured, participants are advised to prepare for inhospitable \
			environs."

//we don't want the silly text overlay!
/obj/item/weapon/paper/pamphlet/update_icon()
	return


code:
/obj/effect/step_trigger/message
	var/message	//the message to give to the mob
	var/once = 1

/obj/effect/step_trigger/message/Trigger(mob/M as mob)
	if(M.client)
		M << "[message]"
		if(once)
			qdel(src)

/obj/effect/step_trigger/teleport_fancy
	var/locationx
	var/locationy
	var/uses = 1	//0 for infinite uses
	var/entersparks = 0
	var/exitsparks = 0
	var/entersmoke = 0
	var/exitsmoke = 0

/obj/effect/step_trigger/teleport_fancy/Trigger(mob/M as mob)
	var/dest = locate(locationx, locationy, z)
	M.Move(dest)

	if(entersparks)
		var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
		s.set_up(4, 1, src)
		s.start()
	if(exitsparks)
		var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
		s.set_up(4, 1, dest)
		s.start()

	if(entersmoke)
		var/datum/effect/effect/system/harmless_smoke_spread/s = new /datum/effect/effect/system/harmless_smoke_spread
		s.set_up(4, 1, src, 0)
		s.start()
	if(exitsmoke)
		var/datum/effect/effect/system/harmless_smoke_spread/s = new /datum/effect/effect/system/harmless_smoke_spread
		s.set_up(4, 1, dest, 0)
		s.start()

	uses--
	if(uses == 0)
		qdel(src)


code:
proc/createRandomZlevel()
	if(awaydestinations.len)	//crude, but it saves another var!
		return

	var/list/potentialRandomZlevels = list()
	world << "Searching for away missions..."
	var/list/Lines = file2list("_maps/RandomZLevels/fileList.txt")
	if(!Lines.len)	return
	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

		potentialRandomZlevels.Add(t)

	if(potentialRandomZlevels.len)
		world << "Loading away mission..."

		var/map = pick(potentialRandomZlevels)
		var/file = file(map)
		if(isfile(file))
			maploader.load_map(file)
			world.log << "away mission loaded: [map]"

		for(var/obj/effect/landmark/L in landmarks_list)
			if (L.name != "awaystart")
				continue
			awaydestinations.Add(L)

		world << "Away mission loaded."

	else
		world << "No away missions found."
		return


code:
/client
		////////////////
		//ADMIN THINGS//
		////////////////
	var/datum/admins/holder = null
	var/buildmode		= 0

	var/last_message	= "" //Contains the last message sent by this client - used to protect against copy-paste spamming.
	var/last_message_count = 0 //contins a number of how many times a message identical to last_message was sent.

		/////////
		//OTHER//
		/////////
	var/datum/preferences/prefs = null
	var/move_delay		= 1
	var/moving			= null
	var/adminobs		= null
	var/area			= null

		///////////////
		//SOUND STUFF//
		///////////////
	var/ambience_playing= null
	var/played			= 0

		////////////
		//SECURITY//
		////////////
	var/next_allowed_topic_time = 10
	// comment out the line below when debugging locally to enable the options & messages menu
	control_freak = 1

		////////////////////////////////////
		//things that require the database//
		////////////////////////////////////
	var/player_age = "Requires database"	//So admins know why it isn't working - Used to determine how old the account is - in days.
	var/related_accounts_ip = "Requires database"	//So admins know why it isn't working - Used to determine what other accounts previously logged in from this ip
	var/related_accounts_cid = "Requires database"	//So admins know why it isn't working - Used to determine what other accounts previously logged in from this computer id

	preload_rsc = PRELOAD_RSC

path: code/modules/client/client defines.dm


code:
////////////
	//SECURITY//
	////////////
#define TOPIC_SPAM_DELAY	2		//2 ticks is about 2/10ths of a second; it was 4 ticks, but that caused too many clicks to be lost due to lag
#define UPLOAD_LIMIT		10485760	//Restricts client uploads to the server to 10MB
#define MIN_CLIENT_VERSION	0		//Just an ambiguously low version for now, I don't want to suddenly stop people playing.
									//I would just like the code ready should it ever need to be used.
	/*
	When somebody clicks a link in game, this Topic is called first.
	It does the stuff in this proc and  then is redirected to the Topic() proc for the src=[0xWhatever]
	(if specified in the link). ie locate(hsrc).Topic()

	Such links can be spoofed.

	Because of this certain things MUST be considered whenever adding a Topic() for something:
		- Can it be fed harmful values which could cause runtimes?
		- Is the Topic call an admin-only thing?
		- If so, does it have checks to see if the person who called it (usr.client) is an admin?
		- Are the processes being called by Topic() particularly laggy?
		- If so, is there any protection against somebody spam-clicking a link?
	If you have any  questions about this stuff feel free to ask. ~Carn
	*/
/client/Topic(href, href_list, hsrc)
	if(!usr || usr != mob)	//stops us calling Topic for somebody else's client. Also helps prevent usr=null
		return

	//Reduces spamming of links by dropping calls that happen during the delay period
	if(next_allowed_topic_time > world.time)
		return
	next_allowed_topic_time = world.time + TOPIC_SPAM_DELAY

	//Admin PM
	if(href_list["priv_msg"])
		cmd_admin_pm(href_list["priv_msg"],null)
		return

	//Logs all hrefs
	if(config && config.log_hrefs && href_logfile)
		href_logfile << "[time2text(world.timeofday,"hh:mm")] [src] (usr:[usr]) || [hsrc ? "[hsrc] " : ""][href]
"

	switch(href_list["_src_"])
		if("holder")	hsrc = holder
		if("usr")		hsrc = mob
		if("prefs")		return prefs.process_link(usr,href_list)
		if("vars")		return view_var_Topic(href,href_list,hsrc)

	..()	//redirect to hsrc.Topic()

/client/proc/is_content_unlocked()
	if(!prefs.unlock_content)
		src << "Become a BYOND member to access member-perks and features, as well as support the engine that makes this game possible. Click Here to find out more."
		return 0
	return 1

/client/proc/handle_spam_prevention(var/message, var/mute_type)
	if(config.automute_on && !holder && src.last_message == message)
		src.last_message_count++
		if(src.last_message_count >= SPAM_TRIGGER_AUTOMUTE)
			src << "You have exceeded the spam filter limit for identical messages. An auto-mute was applied."
			cmd_admin_mute(src, mute_type, 1)
			return 1
		if(src.last_message_count >= SPAM_TRIGGER_WARNING)
			src << "You are nearing the spam filter limit for identical messages."
			return 0
	else
		last_message = message
		src.last_message_count = 0
		return 0

//This stops files larger than UPLOAD_LIMIT being sent from client to server via input(), client.Import() etc.
/client/AllowUpload(filename, filelength)
	if(filelength > UPLOAD_LIMIT)
		src << "Error: AllowUpload(): File Upload too large. Upload Limit: [UPLOAD_LIMIT/1024]KiB."
		return 0
/*	//Don't need this at the moment. But it's here if it's needed later.
	//Helps prevent multiple files being uploaded at once. Or right after eachother.
	var/time_to_wait = fileaccess_timer - world.time
	if(time_to_wait > 0)
		src << "Error: AllowUpload(): Spam prevention. Please wait [round(time_to_wait/10)] seconds."
		return 0
	fileaccess_timer = world.time + FTPDELAY	*/
	return 1

	///////////
	//CONNECT//
	///////////
#if (PRELOAD_RSC == 0)
var/list/external_rsc_urls
var/next_external_rsc = 0
#endif

/client/New(TopicData)

	TopicData = null							//Prevent calls to client.Topic from connect

	if(connection != "seeker")					//Invalid connection type.
		return null
	if(byond_version < MIN_CLIENT_VERSION)		//Out of date client.
		return null

#if (PRELOAD_RSC == 0)
	if(external_rsc_urls && external_rsc_urls.len)
		next_external_rsc = Wrap(next_external_rsc+1, 1, external_rsc_urls.len+1)
		preload_rsc = external_rsc_urls[next_external_rsc]
#endif

	clients += src
	directory[ckey] = src

	//Admin Authorisation
	holder = admin_datums[ckey]
	if(holder)
		admins += src
		holder.owner = src

	//preferences datum - also holds some persistant data for the client (because we may as well keep these datums to a minimum)
	prefs = preferences_datums[ckey]
	if(!prefs)
		prefs = new /datum/preferences(src)
		preferences_datums[ckey] = prefs
	prefs.last_ip = address				//these are gonna be used for banning
	prefs.last_id = computer_id			//these are gonna be used for banning

	. = ..()	//calls mob.Login()

	if( (world.address == address || !address) && !host )
		host = key
		world.update_status()

	if(holder)
		add_admin_verbs()
		admin_memo_show()
		if((global.comms_key == "default_pwd" || length(global.comms_key) <= 6) && global.comms_allowed) //It's the default value or less than 6 characters long, but it somehow didn't disable comms.
			src << "The server's API key is either too short or is the default value! Consider changing it immediately!"

	add_verbs_from_config()
	set_client_age_from_db()

	if (isnum(player_age) && player_age == -1) //first connection
		if (config.panic_bunker && !holder && !(ckey in deadmins))
			log_access("Failed Login: [key] - New account attempting to connect during panic bunker")
			message_admins("Failed Login: [key] - New account attempting to connect during panic bunker")
			src << "Sorry but the server is currently not accepting connections from never before seen players."
			del(src)
			return 0

		if (config.notify_new_player_age >= 0)
			message_admins("New user: [key_name_admin(src)] is connecting here for the first time.")
			if (config.irc_first_connection_alert)
				send2irc_adminless_only("New-user", "[key_name(src)] is connecting for the first time!")

		player_age = 0 // set it from -1 to 0 so the job selection code doesn't have a panic attack

	else if (isnum(player_age) && player_age < config.notify_new_player_age)
		message_admins("New user: [key_name_admin(src)] just connected with an age of [player_age] day[(player_age==1?"":"s")]")

	if (!ticker || ticker.current_state == GAME_STATE_PREGAME)
		spawn (rand(10,150))
			if (src)
				sync_client_with_db()
	else
		sync_client_with_db()

	send_resources()

	if(prefs.lastchangelog != changelog_hash) //bolds the changelog button on the interface so we know there are updates.
		src << "You have unread updates in the changelog."
		if(config.aggressive_changelog)
			src.changes()
		else
			winset(src, "rpane.changelogb", "background-color=#eaeaea;font-style=bold")

	//////////////
	//DISCONNECT//
	//////////////
/client/Del()
	if(holder)
		holder.owner = null
		admins -= src
	directory -= ckey
	clients -= src
	return ..()

/client/proc/set_client_age_from_db()
	if (IsGuestKey(src.key))
		return

	establish_db_connection()
	if(!dbcon.IsConnected())
		return

	var/sql_ckey = sanitizeSQL(src.ckey)

	var/DBQuery/query = dbcon.NewQuery("SELECT id, datediff(Now(),firstseen) as age FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'")
	if (!query.Execute())
		return

	while (query.NextRow())
		player_age = text2num(query.item[2])
		return

	//no match mark it as a first connection for use in client/New()
	player_age = -1

/client/proc/sync_client_with_db()
	if (IsGuestKey(src.key))
		return

	establish_db_connection()
	if (!dbcon.IsConnected())
		return

	var/sql_ckey = sanitizeSQL(src.ckey)

	var/DBQuery/query_ip = dbcon.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE ip = '[address]' AND ckey != '[sql_ckey]'")
	query_ip.Execute()
	related_accounts_ip = ""
	while(query_ip.NextRow())
		related_accounts_ip += "[query_ip.item[1]], "

	var/DBQuery/query_cid = dbcon.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE computerid = '[computer_id]' AND ckey != '[sql_ckey]'")
	query_cid.Execute()
	related_accounts_cid = ""
	while (query_cid.NextRow())
		related_accounts_cid += "[query_cid.item[1]], "

	var/admin_rank = "Player"
	if (src.holder && src.holder.rank)
		admin_rank = src.holder.rank.name

	var/sql_ip = sanitizeSQL(src.address)
	var/sql_computerid = sanitizeSQL(src.computer_id)
	var/sql_admin_rank = sanitizeSQL(admin_rank)

	var/DBQuery/query_insert = dbcon.NewQuery("INSERT INTO [format_table_name("player")] (id, ckey, firstseen, lastseen, ip, computerid, lastadminrank) VALUES (null, '[sql_ckey]', Now(), Now(), '[sql_ip]', '[sql_computerid]', '[sql_admin_rank]') ON DUPLICATE KEY UPDATE lastseen = VALUES(lastseen), ip = VALUES(ip), computerid = VALUES(computerid), lastadminrank = VALUES(lastadminrank)")
	query_insert.Execute()

	//Logging player access
	var/serverip = "[world.internet_address]:[world.port]"
	var/DBQuery/query_accesslog = dbcon.NewQuery("INSERT INTO `[format_table_name("connection_log")]` (`id`,`datetime`,`serverip`,`ckey`,`ip`,`computerid`) VALUES(null,Now(),'[serverip]','[sql_ckey]','[sql_ip]','[sql_computerid]');")
	query_accesslog.Execute()

/client/proc/add_verbs_from_config()
	if(config.see_own_notes)
		verbs += /client/proc/self_notes

#undef TOPIC_SPAM_DELAY
#undef UPLOAD_LIMIT
#undef MIN_CLIENT_VERSION

//checks if a client is afk
//3000 frames = 5 minutes
/client/proc/is_afk(duration=3000)
	if(inactivity > duration)	return inactivity
	return 0

//send resources to the client. It's here in its own proc so we can move it around easiliy if need be
/client/proc/send_resources()
	//Send nanoui files to client
	SSnano.send_resources(src)
	getFiles(
		'html/search.js',
		'html/panels.css',
		'html/browser/common.css',
		'html/browser/scannernew.css',
		'html/browser/playeroptions.css',
		'icons/pda_icons/pda_atmos.png',
		'icons/pda_icons/pda_back.png',
		'icons/pda_icons/pda_bell.png',
		'icons/pda_icons/pda_blank.png',
		'icons/pda_icons/pda_boom.png',
		'icons/pda_icons/pda_bucket.png',
		'icons/pda_icons/pda_chatroom.png',
		'icons/pda_icons/pda_medbot.png',
		'icons/pda_icons/pda_floorbot.png',
		'icons/pda_icons/pda_cleanbot.png',
		'icons/pda_icons/pda_crate.png',
		'icons/pda_icons/pda_cuffs.png',
		'icons/pda_icons/pda_eject.png',
		'icons/pda_icons/pda_exit.png',
		'icons/pda_icons/pda_flashlight.png',
		'icons/pda_icons/pda_honk.png',
		'icons/pda_icons/pda_mail.png',
		'icons/pda_icons/pda_medical.png',
		'icons/pda_icons/pda_menu.png',
		'icons/pda_icons/pda_mule.png',
		'icons/pda_icons/pda_notes.png',
		'icons/pda_icons/pda_power.png',
		'icons/pda_icons/pda_rdoor.png',
		'icons/pda_icons/pda_reagent.png',
		'icons/pda_icons/pda_refresh.png',
		'icons/pda_icons/pda_scanner.png',
		'icons/pda_icons/pda_signaler.png',
		'icons/pda_icons/pda_status.png',
		'icons/spideros_icons/sos_1.png',
		'icons/spideros_icons/sos_2.png',
		'icons/spideros_icons/sos_3.png',
		'icons/spideros_icons/sos_4.png',
		'icons/spideros_icons/sos_5.png',
		'icons/spideros_icons/sos_6.png',
		'icons/spideros_icons/sos_7.png',
		'icons/spideros_icons/sos_8.png',
		'icons/spideros_icons/sos_9.png',
		'icons/spideros_icons/sos_10.png',
		'icons/spideros_icons/sos_11.png',
		'icons/spideros_icons/sos_12.png',
		'icons/spideros_icons/sos_13.png',
		'icons/spideros_icons/sos_14.png',
		'icons/stamp_icons/large_stamp-clown.png',
		'icons/stamp_icons/large_stamp-deny.png',
		'icons/stamp_icons/large_stamp-ok.png',
		'icons/stamp_icons/large_stamp-hop.png',
		'icons/stamp_icons/large_stamp-cmo.png',
		'icons/stamp_icons/large_stamp-ce.png',
		'icons/stamp_icons/large_stamp-hos.png',
		'icons/stamp_icons/large_stamp-rd.png',
		'icons/stamp_icons/large_stamp-cap.png',
		'icons/stamp_icons/large_stamp-qm.png',
		'icons/stamp_icons/large_stamp-law.png'
		)

path: code/modules/client/client procs.dm