code:
var/obj/machinery/gateway/centerstation/the_gateway = null

/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/centerstation/New()
	..()
	if(!the_gateway)
		the_gateway = src

/obj/machinery/gateway/centerstation/Destroy()
	if(the_gateway == src)
		the_gateway = null
	return ..()

/obj/machinery/gateway/initialize()
	update_icon()
	switch(dir)
		if(SOUTH,SOUTHEAST,SOUTHWEST)
			density = 0

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

//prevents shuttles attempting to rotate this since it messes up sprites
/obj/machinery/gateway/shuttleRotate()
	return

//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)
	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)
	if(!ready)
		detect()
		return
	if(!active)
		toggleon(user)
		return
	toggleoff()

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

	if(awaygate.calibrated)
		AM.forceMove(get_step(awaygate.loc, SOUTH))
		AM.dir = SOUTH
		if (ismob(AM))
			var/mob/M = AM
			if (M.client)
				M.client.move_delay = max(world.time + 5, M.client.move_delay)
		return
	else
		var/obj/effect/landmark/dest = pick(awaydestinations)
		if(dest)
			AM.forceMove(get_turf(dest))
			AM.dir = SOUTH
			use_power(5000)
		return

/obj/machinery/gateway/centerstation/attackby(obj/item/device/W, mob/user, 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)
	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)
	if(!ready)
		detect()
		return
	if(!active)
		toggleon(user)
		return
	toggleoff()

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

/obj/machinery/gateway/centeraway/attackby(obj/item/device/W, mob/user, 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/x_offset as num, var/y_offset as num, 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).
		}
	}

license: mit


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

//global datum that will preload variables on atoms instanciation
var/global/use_preloader = FALSE
var/global/dmm_suite/preloader/_preloader = new

/**
 * 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(dmm_file as file, x_offset as num, y_offset as num, z_offset as num)
	if(!z_offset)//what z_level we are creating the map on
		z_offset = world.maxz+1

	if(!x_offset)
		x_offset = 0

	if(!y_offset)
		y_offset = 0
	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/open/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=y_offset
	var/xcrd=x_offset

	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)))
		var/x_tilecount = x_depth/key_len
		if(world.maxx<x_tilecount)
			world.maxx=x_tilecount

		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 in 1 to x_depth step key_len)
				xcrd++
				var/model_key = copytext(grid_line,mpos,mpos+key_len)
				parse_grid(grid_models[model_key],xcrd+x_offset,ycrd+y_offset,zcrd+z_offset)

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

			ycrd--
#if DM_VERSION < 510
			sleep(-1)
#else
			CHECK_TICK
#endif

		//reached End Of File
		if(findtext(tfile,quote+"}",zpos,0)+2==tfile_len)
			break
#if DM_VERSION < 510
		sleep(-1)
#else
		CHECK_TICK
#endif

/**
 * 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(model as text,xcrd as num,ycrd as num,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 = readlist(full_def, ";")

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

#if DM_VERSION < 510
		sleep(-1)
#else
		CHECK_TICK
#endif
	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
	if(members[index] != /area/template_noop)
		var/atom/instance
		_preloader.setup(members_attributes[index])//preloader for assigning  set variables on atom creation

		instance = locate(members[index])
		var/turf/crds = locate(xcrd,ycrd,zcrd)
		if(crds)
			instance.contents.Add(crds)

		if(use_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
	if(members[first_turf_index] != /turf/template_noop)
		T = instance_atom(members[first_turf_index],members_attributes[first_turf_index],xcrd,ycrd,zcrd)

	if(T)
		//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 in 1 to first_turf_index-1)
		instance_atom(members[index],members_attributes[index],xcrd,ycrd,zcrd)
		CHECK_TICK

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

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

	var/turf/T = locate(x,y,z)
	if(T)
		instance = new path (T)//first preloader pass

	if(use_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(what as text,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(text as text,initial_position as num, delimiter=",",opening_escape=quote,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/readlist(text as text, 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 = readlist(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)))

			//Check for path
			else if(ispath(text2path(trim_right)))
				trim_right = text2path(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(turf/placed,turf/underturf, 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(use_preloader && (src.type == _preloader.target_path))//in case the instanciated atom is creating other atoms in New()
		_preloader.load(src)

	. = ..()

/dmm_suite/Destroy()
	..()
	return QDEL_HINT_HARDDEL_NOW

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

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

/dmm_suite/preloader/proc/setup(list/the_attributes, path)
	if(the_attributes.len)
		use_preloader = TRUE
		attributes = the_attributes
		target_path = path

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

/area/template_noop
	name = "Area Passthrough"

/turf/template_noop
	name = "Turf Passthrough"


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/closed/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)
				qdel(src)
			return
		x2=x?(x):world.maxx
		y2=y?(y):world.maxy
		z2=z?(z):1
		AllocateSwapMap()

	Destroy()
		// 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
					qdel(A)
				// delete areas that belong only to this map
				for(var/area/a in areas)
					if(a && !a.contents.len) qdel(a)
				if(x2>=world.maxx || y2>=world.maxy || z2>=world.maxz) CutXYZ()
				qdel(areas)
		..()
		return QDEL_HINT_HARDDEL_NOW

	/*
		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 in z1 to z2)
			S.cd="[z-z1+1]"
			for(y in y1 to y2)
				S.cd="[y-y1+1]"
				for(x in x1 to x2)
					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
		qdel(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 in z1 to z2)
			S.cd="[z-z1+1]"
			for(y in y1 to y2)
				S.cd="[y-y1+1]"
				for(x in x1 to x2)
					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
		qdel(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]
				qdel(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()
		qdel(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/closed/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) qdel(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
			qdel(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)
	qdel(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)
	qdel(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 in nw.z to se.z){
			for(var/pos_y in nw.y to se.y){
				for(var/pos_x in nw.x to se.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 in 1 to templates.len){
			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 in key_length to 1 step -1){
				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 = "Academy Asteroids"
	icon_state = "away"

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

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

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

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

/area/awaymission/academy/academycellar
	name = "Academy Cellar"
	icon_state = "away4"

/area/awaymission/academy/academyengine
	name = "Academy Engine"
	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"

/obj/structure/academy_wizard_spawner
	name = "Academy Defensive System"
	desc = "Made by Abjuration Inc"
	icon = 'icons/obj/cult.dmi'
	icon_state = "forge"
	anchored = 1
	var/health = 200
	var/mob/living/current_wizard = null
	var/next_check = 0
	var/cooldown = 600
	var/faction = "wizard"
	var/broken = 0
	var/braindead_check = 0

/obj/structure/academy_wizard_spawner/New()
	SSobj.processing |= src

/obj/structure/academy_wizard_spawner/process()
	if(next_check < world.time)
		if(!current_wizard)
			for(var/mob/living/L in player_list)
				if(L.z == src.z && L.stat != DEAD && !(faction in L.faction))
					summon_wizard()
					break
		else
			if(current_wizard.stat == DEAD)
				current_wizard = null
				summon_wizard()
			if(!current_wizard.client)
				if(!braindead_check)
					braindead_check = 1
				else
					braindead_check = 0
					give_control()
		next_check = world.time + cooldown

/obj/structure/academy_wizard_spawner/proc/give_control()
	if(!current_wizard)
		return
	spawn(0)
		var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as Wizard Academy Defender?", "wizard", null, ROLE_WIZARD)
		var/mob/dead/observer/chosen = null

		if(candidates.len)
			chosen = pick(candidates)
			message_admins("[key_name_admin(chosen)] was spawned as Wizard Academy Defender")
			current_wizard.ghostize() // on the off chance braindead defender gets back in
			current_wizard.key = chosen.key

/obj/structure/academy_wizard_spawner/proc/summon_wizard()
	var/turf/T = src.loc

	var/mob/living/carbon/human/wizbody = new(T)
	wizbody.equipOutfit(/datum/outfit/wizard/academy)
	var/obj/item/weapon/implant/exile/Implant = new/obj/item/weapon/implant/exile(wizbody)
	Implant.implant(wizbody)
	wizbody.faction |= "wizard"
	wizbody.real_name = "Academy Teacher"
	wizbody.name = "Academy Teacher"

	var/datum/mind/wizmind = new /datum/mind()
	wizmind.name = "Wizard Defender"
	wizmind.special_role = "Academy Defender"
	var/datum/objective/O = new("Protect Wizard Academy from the intruders")
	wizmind.objectives += O
	wizmind.transfer_to(wizbody)
	ticker.mode.wizards |= wizmind

	wizmind.AddSpell(new /obj/effect/proc_holder/spell/targeted/ethereal_jaunt)
	wizmind.AddSpell(new /obj/effect/proc_holder/spell/targeted/projectile/magic_missile)
	wizmind.AddSpell(new /obj/effect/proc_holder/spell/dumbfire/fireball)

	current_wizard = wizbody

	give_control()

/obj/structure/academy_wizard_spawner/proc/update_status()
	if(health<0)
		visible_message("[src] breaks down!")
		icon_state = "forge_off"
		SSobj.processing.Remove(src)
		broken = 1

/obj/structure/academy_wizard_spawner/attackby(obj/item/weapon/W, mob/living/user, params)
	add_fingerprint(user)
	user.changeNext_move(CLICK_CD_MELEE)
	if(!broken)
		health -= W.force
		update_status()
	..()

/obj/structure/academy_wizard_spawner/bullet_act(obj/item/projectile/Proj)
	if(!broken)
		if((Proj.damage_type == BRUTE || Proj.damage_type == BURN))
			health -= Proj.damage
			update_status()
	..()
	return

/datum/outfit/wizard/academy
	name = "Academy Wizard"
	r_pocket = null
	r_hand = null
	suit = /obj/item/clothing/suit/wizrobe/red
	head = /obj/item/clothing/head/wizard/red
	backpack_contents = list(/obj/item/weapon/storage/box/survival = 1)

/obj/item/weapon/dice/d20/fate
	name = "Die of Fate"
	desc = "A die with twenty sides. You can feel unearthly energies radiating from it. Using this might be VERY risky."
	icon_state = "d20"
	sides = 20
	var/reusable = 1
	var/used = 0
	var/rigged = -1

/obj/item/weapon/dice/d20/fate/one_use
	reusable = 0

/obj/item/weapon/dice/d20/fate/diceroll(mob/user)
	..()
	if(!used)
		if(!ishuman(user) || !user.mind || (user.mind in ticker.mode.wizards))
			user << "You feel the magic of the dice is restricted to ordinary humans!"
			return
		if(rigged > 0)
			effect(user,rigged)
		else
			effect(user,result)

/obj/item/weapon/dice/d20/fate/equipped(mob/user, slot)
	if(!ishuman(user) || !user.mind || (user.mind in ticker.mode.wizards))
		user << "You feel the magic of the dice is restricted to ordinary humans! You should leave it alone."
		user.drop_item()

/obj/item/weapon/dice/d20/fate/proc/effect(var/mob/living/carbon/human/user,roll)
	if(!reusable)
		used = 1
	visible_message("The die flare briefly.")
	switch(roll)
		if(1)
			//Dust
			user.dust()
		if(2)
			//Death
			user.death()
		if(3)
			//Swarm of creatures
			for(var/direction in alldirs)
				var/turf/T = get_turf(src)
				new /mob/living/simple_animal/hostile/creature(get_step(T,direction))
		if(4)
			//Destroy Equipment
			for (var/obj/item/I in user)
				if (istype(I, /obj/item/weapon/implant))
					continue
				qdel(I)
		if(5)
			//Monkeying
			user.monkeyize()
		if(6)
			//Cut speed
			var/datum/species/S = user.dna.species
			S.speedmod += 1
		if(7)
			//Throw
			user.Stun(3)
			user.adjustBruteLoss(50)
			var/throw_dir = pick(cardinal)
			var/atom/throw_target = get_edge_target_turf(user, throw_dir)
			user.throw_at(throw_target, 200, 4)
		if(8)
			//Fueltank Explosion
			explosion(src.loc,-1,0,2, flame_range = 2)
		if(9)
			//Cold
			var/datum/disease/D = new /datum/disease/cold
			user.ForceContractDisease(D)
		if(10)
			//Nothing
			visible_message("[src] roll perfectly.")
		if(11)
			//Cookie
			var/obj/item/weapon/reagent_containers/food/snacks/cookie/C = new(get_turf(src))
			C.name = "Cookie of Fate"
		if(12)
			//Healing
			user.revive(full_heal = 1, admin_revive = 1)
		if(13)
			//Mad Dosh
			var/turf/Start = get_turf(src)
			for(var/direction in alldirs)
				var/turf/T = get_step(Start,direction)
				if(rand(0,1))
					new /obj/item/stack/spacecash/c1000(T)
				else
					var/obj/item/weapon/moneybag/M = new(T)
					for(var/i in 1 to rand(5,50))
						new /obj/item/weapon/coin/gold(M)
		if(14)
			//Free Gun
			new /obj/item/weapon/gun/projectile/revolver/mateba(get_turf(src))
		if(15)
			//Random One-use spellbook
			new /obj/item/weapon/spellbook/oneuse/random(get_turf(src))
		if(16)
			//Servant & Servant Summon
			var/mob/living/carbon/human/H = new(get_turf(src))
			H.equipOutfit(/datum/outfit/butler)
			var/datum/mind/servant_mind = new /datum/mind()
			var/datum/objective/O = new("Serve [user.real_name].")
			servant_mind.objectives += O
			servant_mind.transfer_to(H)

			var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as [user.real_name] Servant?", "wizard")
			var/mob/dead/observer/chosen = null

			if(candidates.len)
				chosen = pick(candidates)
				message_admins("[key_name_admin(chosen)] was spawned as Dice Servant")
				H.key = chosen.key

			var/obj/effect/proc_holder/spell/targeted/summonmob/S = new
			S.target_mob = H
			user.mind.AddSpell(S)

		if(17)
			//Tator Kit
			new /obj/item/weapon/storage/box/syndicate/(get_turf(src))
		if(18)
			//Captain ID
			new /obj/item/weapon/card/id/captains_spare(get_turf(src))
		if(19)
			//Instrinct Resistance
			user << "You feel robust."
			var/datum/species/S = user.dna.species
			S.brutemod *= 0.5
			S.burnmod *= 0.5
			S.coldmod *= 0.5
		if(20)
			//Free wizard!
			user.mind.make_Wizard()

/datum/outfit/butler
	name = "Butler"
	uniform = /obj/item/clothing/under/suit_jacket/really_black
	shoes = /obj/item/clothing/shoes/laceup
	head = /obj/item/clothing/head/bowler
	glasses = /obj/item/clothing/glasses/monocle
	gloves = /obj/item/clothing/gloves/color/white

/obj/effect/proc_holder/spell/targeted/summonmob
	name = "Summon Servant"
	desc = "This spell can be used to call your servant, whenever you need it."
	charge_max = 100
	clothes_req = 0
	invocation = "JE VES"
	invocation_type = "whisper"
	range = -1
	level_max = 0 //cannot be improved
	cooldown_min = 100
	include_user = 1

	var/mob/living/target_mob

	action_icon_state = "summons"

/obj/effect/proc_holder/spell/targeted/summonmob/cast(list/targets,mob/user = usr)
	if(!target_mob)
		return
	var/turf/Start = get_turf(user)
	for(var/direction in alldirs)
		var/turf/T = get_step(Start,direction)
		if(!T.density)
			target_mob.Move(T)

/obj/structure/ladder/unbreakable/rune
	name = "Teleportation Rune"
	desc = "Could lead anywhere."
	icon = 'icons/obj/rune.dmi'
	icon_state = "1"
	color = rgb(0,0,255)

/obj/structure/ladder/unbreakable/rune/update_icon()
	return

/obj/structure/ladder/unbreakable/rune/show_fluff_message(up,mob/user)
	user.visible_message("[user] activates \the [src].","You activate \the [src].")

/obj/structure/ladder/can_use(mob/user)
	if(user.mind in ticker.mode.wizards)
		return 0
	return 1


code:
/*Cabin areas*/
/area/awaymission/snowforest
	name = "Snow Forest"
	icon_state = "away"
	requires_power = 0
	luminosity = 1
	lighting_use_dynamic = DYNAMIC_LIGHTING_ENABLED

/area/awaymission/cabin
	name = "Cabin"
	icon_state = "away2"
	requires_power = 1
	luminosity = 0
	lighting_use_dynamic = DYNAMIC_LIGHTING_ENABLED

/area/awaymission/snowforest/lumbermill
	name = "Lumbermill"
	icon_state = "away3"

/*Cabin code*/
/obj/structure/fireplace
	name = "fireplace"
	desc = "warm and toasty"
	icon = 'icons/obj/stationobjs.dmi'
	icon_state = "fireplace-active"
	density = 0
	var/active = 1

/obj/structure/fireplace/initialize()
	..()
	toggleFireplace()

/obj/structure/fireplace/attack_hand(mob/living/user)
	if(active)
		active = 0
		toggleFireplace()
	else
		..()

/obj/structure/fireplace/attackby(obj/item/W,mob/living/user,params)
	if(!active)
		if(W.is_hot())
			active = 1
			toggleFireplace()
		else
			..()
	else
		W.fire_act()

/obj/structure/fireplace/proc/toggleFireplace()
	if(active)
		SetLuminosity(8)
		icon_state = "fireplace-active"
	else
		SetLuminosity(0)
		icon_state = "fireplace"

/obj/structure/fireplace/extinguish()
	if(active)
		active = 0
		toggleFireplace()

/obj/structure/fireplace/fire_act()
	if(!active)
		active = 1
		toggleFireplace()

/obj/machinery/recycler/lumbermill
	name = "lumbermill saw"
	desc = "Faster then the cartoons!"
	emagged = 2 //Always grinds people

/obj/machinery/recycler/lumbermill/recycle(obj/item/weapon/grown/log/L, sound = 1)
	L.loc = src.loc
	if(!istype(L))
		return
	if(sound)
		playsound(src.loc, 'sound/weapons/chainsawhit.ogg', 100, 1)
	new L.plank_type(src.loc, 1 + round(L.seed.potency / 25))
	qdel(L)

/mob/living/simple_animal/chicken/rabbit/normal
	icon_state = "b_rabbit"
	icon_living = "b_rabbit"
	icon_dead = "b_rabbit_dead"
	icon_prefix = "b_rabbit"
	minbodytemp = 0
	eggsleft = 0
	egg_type = null
	speak = list()

/*Cabin's forest*/
/datum/mapGenerator/snowy
	modules = list(/datum/mapGeneratorModule/snow/pineTrees, \
	/datum/mapGeneratorModule/snow/deadTrees, \
	/datum/mapGeneratorModule/snow/randBushes, \
	/datum/mapGeneratorModule/snow/randIceRocks, \
	/datum/mapGeneratorModule/snow/bunnies)

/datum/mapGeneratorModule/snow/checkPlaceAtom(turf/T)
	if(istype(T,/turf/open/floor/plating/asteroid/snow))
		return ..(T)
	return 0

/datum/mapGeneratorModule/snow/pineTrees
	spawnableAtoms = list(/obj/structure/flora/tree/pine = 30)

/datum/mapGeneratorModule/snow/deadTrees
	spawnableAtoms = list(/obj/structure/flora/tree/dead = 10)

/datum/mapGeneratorModule/snow/randBushes
	spawnableAtoms = list()

/datum/mapGeneratorModule/snow/randBushes/New()
	..()
	spawnableAtoms = typesof(/obj/structure/flora/ausbushes)
	for(var/i in spawnableAtoms)
		spawnableAtoms[i] = 1

/datum/mapGeneratorModule/snow/bunnies
	//spawnableAtoms = list(/mob/living/simple_animal/chicken/rabbit/normal = 0.1)
	spawnableAtoms = list(/mob/living/simple_animal/chicken/rabbit = 0.5)

/datum/mapGeneratorModule/snow/randIceRocks
	spawnableAtoms = list(/obj/structure/flora/rock/icy = 5, /obj/structure/flora/rock/pile/icy = 5)

/obj/effect/landmark/mapGenerator/snowy
	mapGeneratorType = /datum/mapGenerator/snowy


code:
//Packer Ship Areas

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

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

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

/area/awaymission/BMPship/Fore
	name = "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 = "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

/obj/machinery/power/emitter/energycannon/RefreshParts()
	return


code:
//Research Base Areas//--

/area/awaymission/research
	name = "Research Outpost"
	icon_state = "away"
	luminosity = 0
	lighting_use_dynamic = DYNAMIC_LIGHTING_ENABLED

/area/awaymission/research/interior
	name = "Research Inside"
	requires_power = 1
	icon_state = "away2"

/area/awaymission/research/interior/cryo
	name = "Research Cryostasis Room"
	icon_state = "medbay"

/area/awaymission/research/interior/clonestorage
	name = "Research Clone Storage"
	icon_state = "cloning"

/area/awaymission/research/interior/genetics
	name = "Research Genetics Research"
	icon_state = "genetics"

/area/awaymission/research/interior/engineering
	name = "Research Engineering"
	icon_state = "engine"

/area/awaymission/research/interior/security
	name = "Research Security"
	icon_state = "security"

/area/awaymission/research/interior/secure
	name = "Research Secure Vault"

/area/awaymission/research/interior/maint
	name = "Research Maintenance"
	icon_state = "maintcentral"

/area/awaymission/research/interior/dorm
	name = "Research Dorms"
	icon_state = "Sleep"

/area/awaymission/research/interior/escapepods
	name = "Research Escape Wing"
	icon_state = "exit"

/area/awaymission/research/interior/gateway
	name = "Research Gateway"
	icon_state = "start"

/area/awaymission/research/interior/bathroom
	name = "Research Bathrooms"
	icon_state = "restrooms"

/area/awaymission/research/interior/medbay
	name = "Research Medbay"
	icon_state = "medbay"

/area/awaymission/research/exterior
	name = "Research Exterior"
	icon_state = "unknown"


code:
//Snow Valley Areas//--

/area/awaymission/snowdin
	name = "Snowdin Tundra Plains"
	icon_state = "away"
	requires_power = 0
	luminosity = 1
	lighting_use_dynamic = DYNAMIC_LIGHTING_ENABLED

/area/awaymission/snowdin/post
	name = "Snowdin Outpost"
	requires_power = 1

/area/awaymission/snowdin/igloo
	name = "Snowdin Igloos"
	icon_state = "away2"

/area/awaymission/snowdin/cave
	name = "Snowdin Caves"
	icon_state = "away2"
	luminosity = 0

/area/awaymission/snowdin/base
	name = "Snowdin Main Base"
	icon_state = "away3"
	requires_power = 1

/area/awaymission/snowdin/dungeon1
	name = "Snowdin Depths"
	icon_state = "away2"
	luminosity = 0

/area/awaymission/snowdin/sekret
	name = "Snowdin Operations"
	icon_state = "away3"
	requires_power = 1

//notes for lore or treasure hints wow//--

/obj/item/weapon/paper/crumpled/snowdin/snowdingatewaynotice
	name = "scribbled note"
	info = {"The gateway has been inactive for months, engineers think its due to the recent drop in tempature fucking with the
	circuitry or something. Without a constant supply of resources from central command, our stock is getting awfully low. Some of the security members have taken to
	using the sparse rifle ammo left to hunting some of the wildlife to try and keep our food supply from emptying. God forbid if the heating goes out, I don't want to
	die as a fucking popsicle down here."}

/obj/item/weapon/paper/crumpled/snowdin/misc1
	name = "Mission Prologe"
	info = {"Holy shit, what a rush! Those Nanotrasen bastards didn't even know what hit 'em! All five of us dropped in right on the captain, didn't even have time to yell! We were in and out with that disk in mere minutes!
	Crew didn't even know what was happening till the delta alert went down and by then were were already gone. We got a case to drink on the way home to celebrate, fuckin' job well done!"}

/obj/item/weapon/paper/crumpled/snowdin/keys
	name = "scribbled note"
	info = {"As a notice for anyone looking to borrow an ATV, some asshat lost the key set for all the vehicles. Nobody has yet to actually come forward about the potential where-abouts, either due to embarrassment or fear of
	reprecussions. I hope they enjoy walking through that shit snow during the next shipment because I sure as hell ain't."}

/obj/item/weapon/paper/snowdin/snowdinlog
	name = "Activity Log"
	info = {"ACTIVITY LOG

June 3rd
We've moved to the main base in the valley finally, apparently establishing a listening system on a planet
	that never stops fucking snowing is a great idea. There's a few outposts further south we'll be supplying from the main gateway. The summer months are enough already, I can only imagine how bad it'll be during winter.

August 23rd

	The colder months are finally hitting, some of the machinery seems to be having trouble starting up sometimes. Central sent some portable heaters to help keep the airlocks from
	freezing shut along with a couple storage crates with supplies. Nothing on the radio so far, what the hell do they even expect to hear down here, anyway?

September 15th

Another supply shipment through the gateway, they've sent some heavier sets of clothes for the coming winter months. Central said they might encounter issues with shipments
	during December to Feburary, so we should try to be frugal with the next shipment.

November 20th
Final shipment from central for the next few months. Going outside
	for more than 10-15 minutes without losing feeling in your fingers is difficult. We've finally gotten a signal on the radio, its mostly some weird static though. One of the researchers is trying to decypher it.

December 10th
Signal has gotten much stronger, it almost seems like its coming from under us according to what the researcher managed to decypher. We're waiting from the go from central before investigating.

	The rest of the paper seems to be a mixture of scribles and smudged ink. "}

/obj/item/weapon/paper/snowdin/snowdinlog2
	name = "Activity Log"
	info = {"ACTIVITY LOG

June 14th
Movement to the second post is finally done. We're located on the most-southern area of the valley with a similar objective as the northen post.
	Theres two mid-way stops on the eastern and western sides of the valley so movement inbetween bases isn't horrible. Not too big of a fan of relying on the northen base for
	equal supply distribution, though.

August 27h
First shipment arrived finally, about 4 days after the gateway shipped. Insulation on these buildings is awful, thank god for the spare heaters at least.

	September 20th
Another shipment arrival, standard shit. Our radios have been picking up a weird signal during the nights recently, we've sent the transcripts over to the northen
	base to be decyphered. Probably some drunk russians or something equally stupid.

November 24th
We've lost communications with the northern base after recieving the last
	shipment of supplies. The snow has really kicked up recently, shits almost like a constant blizzard right now. Maybe it'll drop down soon so we can get a word in.

	The rest of the paper seems to be a mixture of scribles and smudged ink. "}

obj/item/weapon/paper/snowdin/secnotice
	name = "Security Notice"
	info = {"You have been assigned a postion on a listening outpost. Here you'll be watching over a several crewmembers assigned to watching signals of the general area.
	 As not much is expected in terms of issues, we've only assigned one guard per outpost. Crewmembers are expected to keep to their regulated work schedules and may be
	 disciplined properly if found slacking. Food hording is heavily discouraged as all outposts will be sharing from the same shipment every 2-3 months. Hording of supplies
	 should be punished severely as to prevent future incidients. Mutiny and/or rioting should be reported to central and dealt with swiftly. You're here to secure and protect
	 Nanotrasen assets, not be a police officer. Do what you must, but make sure its not messy."}

obj/item/weapon/paper/snowdin/syndienotice
	name = "Assignment Notice"
	info = {"You've been assigned as an agent to listen in on Nanotrasen activities from passing ships and nearby stations. The outpost you've been assigned to is under lays of solid
	ice and we've supplied you with a scrambler to help avoid Nanotrasen discovery, as they've recently built a listening post of their own aboveground. Get aquainted with your new
	crewmates, because you're gonna be here for awhile. Enjoy the free syndicakes."}

obj/item/weapon/paper/crumpled/snowdin/syndielava
	name = "scribbled note"
	info = {"Some cracks in the ice nearby have exposed some sort of hidden magma stream under all this shit ice. I don't know whats worse at this point honestly; freezing to death or
	burning alive."}

obj/item/weapon/paper/crumpled/snowdin/lootstructures
	name = "scribbled note"
	info = {"From what we've seen so far, theres a ton of iced over ruins down here in the caves. We sent a few men out to check things out and they never came back, so we decided to
	border up majority of the ruins. We've heard some weird shit coming out of these caves and I'm not gonna find out the hard way myself."}

obj/item/weapon/paper/crumpled/snowdin/shovel
	name = "shoveling duties"
	info = {"Snow piles up bad here all-year round, even worse during the winter months. Keeping a constant rotation of shoveling that shit out of the way of the airlocks and keeping the paths decently clear
	is a good step towards not getting stuck walking through knee-deep snow."}

//lootspawners//--

/obj/effect/spawner/lootdrop/snowdin
	name = "why are you using this dummy"
	lootdoubles = 0
	lootcount = 1
	loot = list(/obj/item/weapon/bikehorn = 100)

/obj/effect/spawner/lootdrop/snowdin/dungeonlite
	name = "dungeon lite"
	loot = list(/obj/item/weapon/melee/classic_baton = 11,
				/obj/item/weapon/melee/classic_baton/telescopic = 12,
				/obj/item/weapon/spellbook/oneuse/smoke = 10,
				/obj/item/weapon/spellbook/oneuse/blind = 10,
				/obj/item/weapon/storage/firstaid/regular = 45,
				/obj/item/weapon/storage/firstaid/toxin = 35,
				/obj/item/weapon/storage/firstaid/brute = 27,
				/obj/item/weapon/storage/firstaid/fire = 27,
				/obj/item/weapon/storage/toolbox/syndicate = 12,
				/obj/item/weapon/c4 = 7,
				/obj/item/weapon/grenade/clusterbuster/smoke = 15,
				/obj/item/clothing/under/chameleon = 13,
				/obj/item/clothing/shoes/chameleon = 10,
				/obj/item/borg/upgrade/ddrill = 3,
				/obj/item/borg/upgrade/soh = 3)

/obj/effect/spawner/lootdrop/snowdin/dungeonmid
	name = "dungeon mid"
	loot = list(/obj/item/weapon/defibrillator/compact = 6,
				/obj/item/weapon/storage/firstaid/tactical = 35,
				/obj/item/weapon/shield/energy = 6,
				/obj/item/weapon/shield/riot/tele = 12,
				/obj/item/weapon/dnainjector/lasereyesmut = 7,
				/obj/item/weapon/gun/magic/wand/fireball/inert = 3,
				/obj/item/weapon/pneumatic_cannon = 15,
				/obj/item/weapon/melee/energy/sword = 7,
				/obj/item/weapon/spellbook/oneuse/knock = 15,
				/obj/item/weapon/spellbook/oneuse/summonitem = 20,
				/obj/item/weapon/spellbook/oneuse/forcewall = 17,
				/obj/item/weapon/storage/backpack/holding = 12,
				/obj/item/weapon/grenade/spawnergrenade/manhacks = 6,
				/obj/item/weapon/grenade/spawnergrenade/spesscarp = 7,
				/obj/item/weapon/grenade/clusterbuster/inferno = 3,
				/obj/item/stack/sheet/mineral/diamond{amount = 15} = 10,
				/obj/item/stack/sheet/mineral/uranium{amount = 15} = 10,
				/obj/item/stack/sheet/mineral/plasma{amount = 15} = 10,
				/obj/item/stack/sheet/mineral/gold{amount = 15} = 10,
				/obj/item/weapon/spellbook/oneuse/barnyard = 4,
				/obj/item/weapon/pickaxe/drill/diamonddrill = 6,
				/obj/item/borg/upgrade/vtec = 7,
				/obj/item/borg/upgrade/disablercooler = 7)

/obj/effect/spawner/lootdrop/snowdin/dungeonheavy
	name = "dungeon heavy"
	loot = list(/obj/item/weapon/twohanded/singularityhammer = 25,
				/obj/item/weapon/twohanded/mjollnir = 10,
				/obj/item/weapon/twohanded/fireaxe = 25,
				/obj/item/organ/internal/brain/alien = 17,
				/obj/item/weapon/twohanded/dualsaber = 15,
				/obj/item/organ/internal/heart/demon = 7,
				/obj/item/weapon/gun/projectile/automatic/c20r/unrestricted = 16,
				/obj/item/weapon/gun/magic/wand/resurrection/inert = 15,
				/obj/item/weapon/gun/magic/wand/resurrection = 10,
				/obj/item/device/radio/uplink/old = 2,
				/obj/item/weapon/spellbook/oneuse/charge = 12,
				/obj/item/weapon/grenade/clusterbuster/spawner_manhacks = 15,
				/obj/item/weapon/spellbook/oneuse/fireball = 10,
				/obj/item/weapon/pickaxe/drill/jackhammer = 30,
				/obj/item/borg/upgrade/syndicate = 13,
				/obj/item/borg/upgrade/selfrepair = 17)

/obj/effect/spawner/lootdrop/snowdin/dungeonmisc
	name = "dungeon misc"
	lootdoubles = 2
	lootcount = 1

	loot = list(/obj/item/stack/sheet/mineral/snow{amount = 25} = 10,
				/obj/item/toy/snowball = 15,
				/obj/item/weapon/shovel = 10,
				/obj/item/weapon/twohanded/spear = 8,
				)

//special items//--

/obj/item/clothing/under/syndicate/coldres
	name = "insulated tactical turtleneck"
	desc = "A non-descript and slightly suspicious looking turtleneck with digital camouflage cargo pants. The interior has been padded with special insulation for both warmth and protection"
	armor = list(melee = 20, bullet = 10, laser = 0,energy = 5, bomb = 0, bio = 0, rad = 0)
	cold_protection = CHEST|GROIN|ARMS|LEGS
	min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT
/obj/item/clothing/shoes/combat/coldres
	name = "insulated combat boots"
	desc = "High speed, low drag combat boots, now with an added layer of insulation."
	min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT

/obj/item/weapon/gun/magic/wand/fireball/inert
	name = "weakened wand of fireball"
	desc = "This wand shoots scorching balls of fire that explode into destructive flames. The years of the cold have weakened the magic inside the wand."
	max_charges = 4

/obj/item/weapon/gun/magic/wand/resurrection/inert
	name = "weakened wand of healing"
	desc = "This wand uses healing magics to heal and revive. The years of the cold have weakened the magic inside the wand."
	max_charges = 5

/obj/item/device/radio/uplink/old
	name = "dusty radio"
	desc = "A dusty looking radio."

/obj/item/device/radio/uplink/old/New()
	..()
	hidden_uplink.name = "dusty radio"
	hidden_uplink.telecrystals = 10

obj/effect/mob_spawn/human/syndicatesoldier/coldres
	name = "Syndicate Snow Operative"
	uniform = /obj/item/clothing/under/syndicate/coldres
	shoes = /obj/item/clothing/shoes/combat/coldres
	radio = /obj/item/device/radio/headset/syndicate/alt
	pocket1 = /obj/item/weapon/gun/projectile/automatic/pistol
	pocket2 = /obj/item/weapon/card/id/syndicate
	has_id = 0

obj/effect/mob_spawn/human/syndicatesoldier/coldres/alive
	name = "sleeper"
	mob_name = "Syndicate Snow Operative"
	icon = 'icons/obj/Cryogenic2.dmi'
	icon_state = "sleeper"
	roundstart = FALSE
	death = FALSE
	implants = list(/obj/item/weapon/implant/exile)
	faction = "syndicate"
	flavour_text = {"You are a syndicate operative recently awoken from cyrostatis in an underground outpost. Monitor Nanotrasen communications and record infomation. All intruders should be
	disposed of swirfly to assure no gathered infomation is stolen or lost. Try not to wander too far from the outpost as the caves can be a deadly place even for a trained operative such as yourself."}

obj/effect/mob_spawn/human/syndicatesoldier/coldres/alive/female
	mob_gender = FEMALE

//mobs//--

/mob/living/simple_animal/hostile/poison/giant_spider/ice //spiders dont usually like tempatures of 140 kelvin who knew
	name = "giant ice spider"
	atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
	minbodytemp = 0
	maxbodytemp = 1500
	color = rgb(114,228,250)

/mob/living/simple_animal/hostile/poison/giant_spider/nurse/ice
	name = "giant ice spider"
	atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
	minbodytemp = 0
	maxbodytemp = 1500
	color = rgb(114,228,250)

/mob/living/simple_animal/hostile/poison/giant_spider/hunter/ice
	name = "giant ice spider"
	atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
	minbodytemp = 0
	maxbodytemp = 1500
	color = rgb(114,228,250)

//objs//--

/obj/structure/flora/rock/icy
	name = "icy rock"
	color = rgb(114,228,250)

/obj/structure/flora/rock/pile/icy
	name = "icey rocks"
	color = rgb(114,228,250)


code:
//Spacebattle Areas

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

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

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

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

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

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

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

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

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

/area/awaymission/spacebattle/secret
	name = "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 receive 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/open/space, /obj/effect/overlay, /mob/living/simple_animal/hostile/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(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)
		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/open/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 = "Wild West Mines"
	icon_state = "away1"
	luminosity = 1
	requires_power = 0

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

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

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

/area/awaymission/wwvaultdoors
	name = "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(mob/living/carbon/human/user)
	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)
				user.set_species(/datum/species/shadow)
			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)
				user.set_species(/datum/species/shadow)
			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
				user.set_species(/datum/species/shadow)
			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++
				user.set_species(/datum/species/shadow)
			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 mob_list)
					F.death()

///////////////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/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) && M.stat != DEAD && M.ckey)
		for(var/mob/O in viewers(world.view, src.loc))
		visible_message("[M] triggered the [src]!")
		triggered = 1

		var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
		s.set_up(3, 1, src)
		s.start()
		explosion(M, 1, 0, 0, 0)
		qdel(src)

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

/mob/living/carbon/proc/immortality() //Mob proc so people cant just clone themselves to get rid of the shadowperson race. No hiding your wickedness.
	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(80,120))
		C.revive(full_heal = 1, admin_revive = 1)
		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, and without this supply, it cannot safely function, \
			causinng it to 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:
/*An alternative to exit gateways, signposts send you back to the arrivals shuttle with their semiotic magic.*/
/obj/structure/signpost
	icon = 'icons/obj/stationobjs.dmi'
	icon_state = "signpost"
	anchored = 1
	density = 1

/obj/structure/signpost/attackby(obj/item/weapon/W, mob/user, params)
	return attack_hand(user)

/obj/structure/signpost/attack_hand(mob/user)
	switch(alert("Travel back to ss13?",,"Yes","No"))
		if("Yes")
			if(user.z != src.z)
				return
			user.loc.loc.Exited(user)
			user.loc = pick(latejoin)
		if("No")
			return


code:
var/global/list/potentialRandomZlevels = generateMapList(filename = "config/awaymissionconfig.txt")

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

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

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

		map_transition_config.Add(AWAY_MISSION_LIST)

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

		world << "Away mission loaded."

		SortAreas() //To add recently loaded areas
	else
		world << "No away missions found."
		return

/proc/generateMapList(filename)
	var/list/potentialMaps = list()
	var/list/Lines = file2list(filename)
	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

		if (pos)
			name = lowertext(copytext(t, 1, pos))

		else
			name = lowertext(t)

		if (!name)
			continue

		potentialMaps.Add(t)

	return potentialMaps

/proc/seedRuins(z_level = 1, ruin_number = 0, whitelist = /area/space, list/potentialRuins = space_ruins_templates)
	ruin_number = min(ruin_number, potentialRuins.len)

	while(ruin_number)
		var/sanity = 0
		var/valid = FALSE
		var/datum/map_template/template = potentialRuins[pick(potentialRuins)]
		while(!valid)
			valid = TRUE
			sanity++
			if(sanity > 100)
				ruin_number--
				break
			var/turf/T = locate(rand(25, world.maxx - 25), rand(25, world.maxy - 25), z_level)

			for(var/turf/check in template.get_affected_turfs(T,1))
				var/area/new_area = get_area(check)
				if(!(istype(new_area, whitelist)))
					valid = FALSE
					break

			if(valid)
				world.log << "Ruins marker placed at [T.x][T.y][T.z]"
				var/obj/effect/ruin_loader/R = new /obj/effect/ruin_loader(T)
				R.Load(potentialRuins,template)
				ruin_number --

	return

/obj/effect/ruin_loader
	name = "random ruin"
	icon = 'icons/obj/weapons.dmi'
	icon_state = "syndballoon"
	invisibility = 0

/obj/effect/ruin_loader/proc/Load(list/potentialRuins = space_ruins_templates, datum/map_template/template = null)
	var/list/possible_ruins = list()
	for(var/A in potentialRuins)
		var/datum/map_template/T = potentialRuins[A]
		if(!T.loaded)
			possible_ruins += T
	world << "Loading ruins..."
	if(!template && possible_ruins.len)
		template = safepick(possible_ruins)
	if(!template)
		world << "No ruins found."
		return
	template.load(get_turf(src),centered = TRUE)
	template.loaded++
	world << "Ruins loaded."
	qdel(src)