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

/obj/singularity
	name = "gravitational singularity"
	desc = "A gravitational singularity."
	icon = 'icons/obj/singularity.dmi'
	icon_state = "singularity_s1"
	anchored = 1
	density = 1
	layer = 6
	luminosity = 6
	unacidable = 1 //Don't comment this out.
	var/current_size = 1
	var/allowed_size = 1
	var/contained = 1 //Are we going to move around?
	var/energy = 100 //How strong are we?
	var/dissipate = 1 //Do we lose energy over time?
	var/dissipate_delay = 10
	var/dissipate_track = 0
	var/dissipate_strength = 1 //How much energy do we lose?
	var/move_self = 1 //Do we move on our own?
	var/grav_pull = 4 //How many tiles out do we pull?
	var/consume_range = 0 //How many tiles out do we eat
	var/event_chance = 15 //Prob for event each tick
	var/target = null //its target. moves towards the target if it has one
	var/last_failed_movement = 0//Will not move in the same dir if it couldnt before, will help with the getting stuck on fields thing
	var/last_warning
	var/consumedSupermatter = 0 //If the singularity has eaten a supermatter shard and can go to stage six
	burn_state = LAVA_PROOF

/obj/singularity/New(loc, var/starting_energy = 50, var/temp = 0)
	//CARN: admin-alert for chuckle-fuckery.
	admin_investigate_setup()

	src.energy = starting_energy
	..()
	SSobj.processing |= src
	poi_list |= src
	for(var/obj/machinery/power/singularity_beacon/singubeacon in machines)
		if(singubeacon.active)
			target = singubeacon
			break
	return

/obj/singularity/Destroy()
	SSobj.processing.Remove(src)
	poi_list.Remove(src)
	return ..()

/obj/singularity/Move(atom/newloc, direct)
	if(current_size >= STAGE_FIVE || check_turfs_in(direct))
		last_failed_movement = 0//Reset this because we moved
		return ..()
	else
		last_failed_movement = direct
		return 0

/obj/singularity/attack_hand(mob/user)
	consume(user)
	return 1

/obj/singularity/Process_Spacemove() //The singularity stops drifting for no man!
	return 0

/obj/singularity/blob_act(severity)
	return

/obj/singularity/ex_act(severity, target)
	switch(severity)
		if(1)
			if(current_size <= STAGE_TWO)
				investigate_log("has been destroyed by a heavy explosion.","singulo")
				qdel(src)
				return
			else
				energy -= round(((energy+1)/2),1)
		if(2)
			energy -= round(((energy+1)/3),1)
		if(3)
			energy -= round(((energy+1)/4),1)
	return

/obj/singularity/bullet_act(obj/item/projectile/P)
	return 0 //Will there be an impact? Who knows.  Will we see it? No.

/obj/singularity/Bump(atom/A)
	consume(A)
	return

/obj/singularity/Bumped(atom/A)
	consume(A)
	return

/obj/singularity/process()
	if(current_size >= STAGE_TWO)
		move()
		pulse()
		if(prob(event_chance))//Chance for it to run a special event TODO:Come up with one or two more that fit
			event()
	eat()
	dissipate()
	check_energy()

	return

/obj/singularity/attack_ai() //to prevent ais from gibbing themselves when they click on one.
	return

/obj/singularity/proc/admin_investigate_setup()
	last_warning = world.time
	var/count = locate(/obj/machinery/field/containment) in urange(30, src, 1)
	if(!count)
		message_admins("A singulo has been created without containment fields active ([x],[y],[z])",1)
	investigate_log("was created. [count?"":"No containment fields were active"]","singulo")

/obj/singularity/proc/dissipate()
	if(!dissipate)
		return
	if(dissipate_track >= dissipate_delay)
		src.energy -= dissipate_strength
		dissipate_track = 0
	else
		dissipate_track++

/obj/singularity/proc/expand(force_size = 0)
	var/temp_allowed_size = src.allowed_size
	if(force_size)
		temp_allowed_size = force_size
	if(temp_allowed_size >= STAGE_SIX && !consumedSupermatter)
		temp_allowed_size = STAGE_FIVE
	switch(temp_allowed_size)
		if(STAGE_ONE)
			current_size = STAGE_ONE
			icon = 'icons/obj/singularity.dmi'
			icon_state = "singularity_s1"
			pixel_x = 0
			pixel_y = 0
			grav_pull = 4
			consume_range = 0
			dissipate_delay = 10
			dissipate_track = 0
			dissipate_strength = 1
		if(STAGE_TWO)//1 to 3 does not check for the turfs if you put the gens right next to a 1x1 then its going to eat them
			current_size = STAGE_TWO
			icon = 'icons/effects/96x96.dmi'
			icon_state = "singularity_s3"
			pixel_x = -32
			pixel_y = -32
			grav_pull = 6
			consume_range = 1
			dissipate_delay = 5
			dissipate_track = 0
			dissipate_strength = 5
		if(STAGE_THREE)
			if((check_turfs_in(1,2))&&(check_turfs_in(2,2))&&(check_turfs_in(4,2))&&(check_turfs_in(8,2)))
				current_size = STAGE_THREE
				icon = 'icons/effects/160x160.dmi'
				icon_state = "singularity_s5"
				pixel_x = -64
				pixel_y = -64
				grav_pull = 8
				consume_range = 2
				dissipate_delay = 4
				dissipate_track = 0
				dissipate_strength = 20
		if(STAGE_FOUR)
			if((check_turfs_in(1,3))&&(check_turfs_in(2,3))&&(check_turfs_in(4,3))&&(check_turfs_in(8,3)))
				current_size = STAGE_FOUR
				icon = 'icons/effects/224x224.dmi'
				icon_state = "singularity_s7"
				pixel_x = -96
				pixel_y = -96
				grav_pull = 10
				consume_range = 3
				dissipate_delay = 10
				dissipate_track = 0
				dissipate_strength = 10
		if(STAGE_FIVE)//this one also lacks a check for gens because it eats everything
			current_size = STAGE_FIVE
			icon = 'icons/effects/288x288.dmi'
			icon_state = "singularity_s9"
			pixel_x = -128
			pixel_y = -128
			grav_pull = 10
			consume_range = 4
			dissipate = 0 //It cant go smaller due to e loss
		if(STAGE_SIX) //This only happens if a stage 5 singulo consumes a supermatter shard.
			current_size = STAGE_SIX
			icon = 'icons/effects/352x352.dmi'
			icon_state = "singularity_s11"
			pixel_x = -160
			pixel_y = -160
			grav_pull = 15
			consume_range = 5
			dissipate = 0
	if(current_size == allowed_size)
		investigate_log("grew to size [current_size]","singulo")
		return 1
	else if(current_size < (--temp_allowed_size))
		expand(temp_allowed_size)
	else
		return 0

/obj/singularity/proc/check_energy()
	if(energy <= 0)
		investigate_log("collapsed.","singulo")
		qdel(src)
		return 0
	switch(energy)//Some of these numbers might need to be changed up later -Mport
		if(1 to 199)
			allowed_size = STAGE_ONE
		if(200 to 499)
			allowed_size = STAGE_TWO
		if(500 to 999)
			allowed_size = STAGE_THREE
		if(1000 to 1999)
			allowed_size = STAGE_FOUR
		if(2000 to INFINITY)
			if(energy >= 3000 && consumedSupermatter)
				allowed_size = STAGE_SIX
			else
				allowed_size = STAGE_FIVE
	if(current_size != allowed_size)
		expand()
	return 1

/obj/singularity/proc/eat()
	set background = BACKGROUND_ENABLED
	for(var/tile in spiral_range_turfs(grav_pull, src, 1))
		var/turf/T = tile
		if(!T)
			continue
		if(get_dist(T, src) > consume_range)
			T.singularity_pull(src, current_size)
		else
			consume(T)
		for(var/thing in T)
			var/atom/movable/X = thing
			if(get_dist(X, src) > consume_range)
				X.singularity_pull(src, current_size)
			else
				consume(X)
			CHECK_TICK
	return

/obj/singularity/proc/consume(atom/A)
	var/gain = A.singularity_act(current_size, src)
	src.energy += gain
	if(istype(A, /obj/machinery/power/supermatter_shard) && !consumedSupermatter)
		desc = "[initial(desc)] It glows fiercely with inner fire."
		name = "supermatter-charged [initial(name)]"
		consumedSupermatter = 1
		luminosity = 10
	return

/obj/singularity/proc/move(force_move = 0)
	if(!move_self)
		return 0

	var/movement_dir = pick(alldirs - last_failed_movement)

	if(force_move)
		movement_dir = force_move

	if(target && prob(60))
		movement_dir = get_dir(src,target) //moves to a singulo beacon, if there is one

	step(src, movement_dir)

/obj/singularity/proc/check_turfs_in(direction = 0, step = 0)
	if(!direction)
		return 0
	var/steps = 0
	if(!step)
		switch(current_size)
			if(STAGE_ONE)
				steps = 1
			if(STAGE_TWO)
				steps = 3//Yes this is right
			if(STAGE_THREE)
				steps = 3
			if(STAGE_FOUR)
				steps = 4
			if(STAGE_FIVE)
				steps = 5
	else
		steps = step
	var/list/turfs = list()
	var/turf/T = src.loc
	for(var/i = 1 to steps)
		T = get_step(T,direction)
	if(!isturf(T))
		return 0
	turfs.Add(T)
	var/dir2 = 0
	var/dir3 = 0
	switch(direction)
		if(NORTH||SOUTH)
			dir2 = 4
			dir3 = 8
		if(EAST||WEST)
			dir2 = 1
			dir3 = 2
	var/turf/T2 = T
	for(var/j = 1 to steps-1)
		T2 = get_step(T2,dir2)
		if(!isturf(T2))
			return 0
		turfs.Add(T2)
	for(var/k = 1 to steps-1)
		T = get_step(T,dir3)
		if(!isturf(T))
			return 0
		turfs.Add(T)
	for(var/turf/T3 in turfs)
		if(isnull(T3))
			continue
		if(!can_move(T3))
			return 0
	return 1

/obj/singularity/proc/can_move(turf/T)
	if(!T)
		return 0
	if((locate(/obj/machinery/field/containment) in T)||(locate(/obj/machinery/shieldwall) in T))
		return 0
	else if(locate(/obj/machinery/field/generator) in T)
		var/obj/machinery/field/generator/G = locate(/obj/machinery/field/generator) in T
		if(G && G.active)
			return 0
	else if(locate(/obj/machinery/shieldwallgen) in T)
		var/obj/machinery/shieldwallgen/S = locate(/obj/machinery/shieldwallgen) in T
		if(S && S.active)
			return 0
	return 1

/obj/singularity/proc/event()
	var/numb = pick(1,2,3,4,5,6)
	switch(numb)
		if(1)//EMP
			emp_area()
		if(2,3)//tox damage all carbon mobs in area
			toxmob()
		if(4)//Stun mobs who lack optic scanners
			mezzer()
		if(5,6) //Sets all nearby mobs on fire
			if(current_size < STAGE_SIX)
				return 0
			combust_mobs()
		else
			return 0
	return 1

/obj/singularity/proc/toxmob()
	var/toxrange = 10
	var/radiation = 15
	var/radiationmin = 3
	if (energy>200)
		radiation += round((energy-150)/10,1)
		radiationmin = round((radiation/5),1)
	for(var/mob/living/M in view(toxrange, src.loc))
		M.rad_act(rand(radiationmin,radiation))

/obj/singularity/proc/combust_mobs()
	for(var/mob/living/carbon/C in urange(20, src, 1))
		C.visible_message("[C]'s skin bursts into flame!", \
						  "You feel an inner fire as your skin bursts into flames!")
		C.adjust_fire_stacks(5)
		C.IgniteMob()
	return

/obj/singularity/proc/mezzer()
	for(var/mob/living/carbon/M in oviewers(8, src))
		if(istype(M, /mob/living/carbon/brain)) //Ignore brains
			continue

		if(M.stat == CONSCIOUS)
			if (istype(M,/mob/living/carbon/human))
				var/mob/living/carbon/human/H = M
				if(istype(H.glasses, /obj/item/clothing/glasses/meson))
					var/obj/item/clothing/glasses/meson/MS = H.glasses
					if(MS.vision_flags == SEE_TURFS)
						H << "You look directly into the [src.name], good thing you had your protective eyewear on!"
						return

		M.apply_effect(3, STUN)
		M.visible_message("[M] stares blankly at the [src.name]!", \
						"You look directly into the [src.name] and feel weak.")
	return

/obj/singularity/proc/emp_area()
	empulse(src, 8, 10)
	return

/obj/singularity/proc/pulse()

	for(var/obj/machinery/power/rad_collector/R in rad_collectors)
		if(get_dist(R, src) <= 15) // Better than using orange() every process
			R.receive_pulse(energy)
	return

/obj/singularity/singularity_act()
	var/gain = (energy/2)
	var/dist = max((current_size - 2),1)
	explosion(src.loc,(dist),(dist*2),(dist*4))
	qdel(src)
	return(gain)


code:
// the SMES
// stores power

#define SMESRATE 0.05			// rate of internal charge to external power

//Cache defines
#define SMES_CLEVEL_1		1
#define SMES_CLEVEL_2		2
#define SMES_CLEVEL_3		3
#define SMES_CLEVEL_4		4
#define SMES_CLEVEL_5		5
#define SMES_OUTPUTTING		6
#define SMES_NOT_OUTPUTTING 7
#define SMES_INPUTTING		8
#define SMES_INPUT_ATTEMPT	9

/obj/machinery/power/smes
	name = "power storage unit"
	desc = "A high-capacity superconducting magnetic energy storage (SMES) unit."
	icon_state = "smes"
	density = 1
	anchored = 1
	use_power = 0
	var/capacity = 5e6 // maximum charge
	var/charge = 0 // actual charge

	var/input_attempt = 1 // 1 = attempting to charge, 0 = not attempting to charge
	var/inputting = 1 // 1 = actually inputting, 0 = not inputting
	var/input_level = 50000 // amount of power the SMES attempts to charge by
	var/input_level_max = 200000 // cap on input_level
	var/input_available = 0 // amount of charge available from input last tick

	var/output_attempt = 1 // 1 = attempting to output, 0 = not attempting to output
	var/outputting = 1 // 1 = actually outputting, 0 = not outputting
	var/output_level = 50000 // amount of power the SMES attempts to output
	var/output_level_max = 200000 // cap on output_level
	var/output_used = 0 // amount of power actually outputted. may be less than output_level if the powernet returns excess power

	var/obj/machinery/power/terminal/terminal = null

	var/static/list/smesImageCache

/obj/machinery/power/smes/New()
	..()
	component_parts = list()
	component_parts += new /obj/item/weapon/circuitboard/smes(null)
	for(var/i in 1 to 5)
		component_parts += new /obj/item/weapon/stock_parts/cell/high/empty(null)
	component_parts += new /obj/item/stack/cable_coil(null, 5)
	component_parts += new /obj/item/weapon/stock_parts/capacitor(null)
	RefreshParts()
	spawn(5)
		dir_loop:
			for(var/d in cardinal)
				var/turf/T = get_step(src, d)
				for(var/obj/machinery/power/terminal/term in T)
					if(term && term.dir == turn(d, 180))
						terminal = term
						break dir_loop

		if(!terminal)
			stat |= BROKEN
			return
		terminal.master = src
		update_icon()
	return

/obj/machinery/power/smes/RefreshParts()
	var/IO = 0
	var/MC = 0
	var/C
	for(var/obj/item/weapon/stock_parts/capacitor/CP in component_parts)
		IO += CP.rating
	input_level_max = initial(input_level_max) * IO
	output_level_max = initial(output_level_max) * IO
	for(var/obj/item/weapon/stock_parts/cell/PC in component_parts)
		MC += PC.maxcharge
		C += PC.charge
	capacity = MC / (15000) * 1e6
	if(!initial(charge) && !charge)
		charge = C / 15000 * 1e6

/obj/machinery/power/smes/attackby(obj/item/I, mob/user, params)
	//opening using screwdriver
	if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), I))
		update_icon()
		return

	//changing direction using wrench
	if(default_change_direction_wrench(user, I))
		terminal = null
		var/turf/T = get_step(src, dir)
		for(var/obj/machinery/power/terminal/term in T)
			if(term && term.dir == turn(dir, 180))
				terminal = term
				terminal.master = src
				user << "Terminal found."
				break
		if(!terminal)
			user << "No power source found."
			return
		stat &= ~BROKEN
		update_icon()
		return

	//exchanging parts using the RPE
	if(exchange_parts(user, I))
		return

	//building and linking a terminal
	if(istype(I, /obj/item/stack/cable_coil))
		var/dir = get_dir(user,src)
		if(dir & (dir-1))//we don't want diagonal click
			return

		if(terminal) //is there already a terminal ?
			user << "This SMES already have a power terminal!"
			return

		if(!panel_open) //is the panel open ?
			user << "You must open the maintenance panel first!"
			return

		var/turf/T = get_turf(user)
		if (T.intact) //is the floor plating removed ?
			user << "You must first remove the floor plating!"
			return

		var/obj/item/stack/cable_coil/C = I
		if(C.amount < 10)
			user << "You need more wires!"
			return

		user << "You start building the power terminal..."
		playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1)

		if(do_after(user, 20, target = src) && C.amount >= 10)
			var/obj/structure/cable/N = T.get_cable_node() //get the connecting node cable, if there's one
			if (prob(50) && electrocute_mob(usr, N, N)) //animate the electrocution if uncautious and unlucky
				var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
				s.set_up(5, 1, src)
				s.start()
				return

			C.use(10)
			user.visible_message(\
				"[user.name] has built a power terminal.",\
				"You build the power terminal.")

			//build the terminal and link it to the network
			make_terminal(T)
			terminal.connect_to_network()
		return

	//disassembling the terminal
	if(istype(I, /obj/item/weapon/wirecutters) && terminal && panel_open)
		terminal.dismantle(user)

	//crowbarring it !
	var/turf/T = get_turf(src)
	if(default_deconstruction_crowbar(I))
		message_admins("[src] has been deconstructed by [key_name_admin(user)](?) (FLW) in ([T.x],[T.y],[T.z] - JMP)",0,1)
		log_game("[src] has been deconstructed by [key_name(user)]")
		investigate_log("SMES deconstructed by [key_name(user)]","singulo")

/obj/machinery/power/smes/deconstruction()
	for(var/obj/item/weapon/stock_parts/cell/cell in component_parts)
		cell.charge = (charge / capacity) * cell.maxcharge

/obj/machinery/power/smes/Destroy()
	if(ticker && ticker.current_state == GAME_STATE_PLAYING)
		var/area/area = get_area(src)
		message_admins("SMES deleted at ([area.name])")
		log_game("SMES deleted at ([area.name])")
		investigate_log("deleted at ([area.name])","singulo")
	if(terminal)
		disconnect_terminal()
	return ..()

// create a terminal object pointing towards the SMES
// wires will attach to this
/obj/machinery/power/smes/proc/make_terminal(turf/T)
	terminal = new/obj/machinery/power/terminal(T)
	terminal.dir = get_dir(T,src)
	terminal.master = src

/obj/machinery/power/smes/disconnect_terminal()
	if(terminal)
		terminal.master = null
		terminal = null

/obj/machinery/power/smes/update_icon()
	overlays.Cut()
	if(stat & BROKEN)
		return

	if(panel_open)
		return

	if(!smesImageCache || !smesImageCache.len)
		smesImageCache = list()
		smesImageCache.len = 9

		smesImageCache[SMES_CLEVEL_1] = image('icons/obj/power.dmi',"smes-og1")
		smesImageCache[SMES_CLEVEL_2] = image('icons/obj/power.dmi',"smes-og2")
		smesImageCache[SMES_CLEVEL_3] = image('icons/obj/power.dmi',"smes-og3")
		smesImageCache[SMES_CLEVEL_4] = image('icons/obj/power.dmi',"smes-og4")
		smesImageCache[SMES_CLEVEL_5] = image('icons/obj/power.dmi',"smes-og5")

		smesImageCache[SMES_OUTPUTTING] = image('icons/obj/power.dmi', "smes-op1")
		smesImageCache[SMES_NOT_OUTPUTTING] = image('icons/obj/power.dmi',"smes-op0")
		smesImageCache[SMES_INPUTTING] = image('icons/obj/power.dmi', "smes-oc1")
		smesImageCache[SMES_INPUT_ATTEMPT] = image('icons/obj/power.dmi', "smes-oc0")

	if(outputting)
		overlays += smesImageCache[SMES_OUTPUTTING]
	else
		overlays += smesImageCache[SMES_NOT_OUTPUTTING]

	if(inputting)
		overlays += smesImageCache[SMES_INPUTTING]
	else
		if(input_attempt)
			overlays += smesImageCache[SMES_INPUT_ATTEMPT]

	var/clevel = chargedisplay()
	if(clevel>0)
		overlays += smesImageCache[clevel]
	return

/obj/machinery/power/smes/proc/chargedisplay()
	return round(5.5*charge/capacity)

/obj/machinery/power/smes/process()

	if(stat & BROKEN)
		return

	//store machine state to see if we need to update the icon overlays
	var/last_disp = chargedisplay()
	var/last_chrg = inputting
	var/last_onln = outputting

	//inputting
	if(terminal && input_attempt)
		input_available = terminal.surplus()

		if(inputting)
			if(input_available > 0 && input_available >= input_level)		// if there's power available, try to charge

				var/load = min((capacity-charge)/SMESRATE, input_level)		// charge at set rate, limited to spare capacity

				charge += load * SMESRATE	// increase the charge

				add_load(load)		// add the load to the terminal side network

			else					// if not enough capcity
				inputting = 0		// stop inputting

		else
			if(input_attempt && input_available > 0 && input_available >= input_level)
				inputting = 1
	else
		inputting = 0

	//outputting
	if(output_attempt)
		if(outputting)
			output_used = min( charge/SMESRATE, output_level)		//limit output to that stored

			charge -= output_used*SMESRATE		// reduce the storage (may be recovered in /restore() if excessive)

			add_avail(output_used)				// add output to powernet (smes side)

			if(output_used < 0.0001)		// either from no charge or set to 0
				outputting = 0
				investigate_log("lost power and turned off","singulo")
		else if(output_attempt && charge > output_level && output_level > 0)
			outputting = 1
		else
			output_used = 0
	else
		outputting = 0

	// only update icon if state changed
	if(last_disp != chargedisplay() || last_chrg != inputting || last_onln != outputting)
		update_icon()

// called after all power processes are finished
// restores charge level to smes if there was excess this ptick
/obj/machinery/power/smes/proc/restore()
	if(stat & BROKEN)
		return

	if(!outputting)
		output_used = 0
		return

	var/excess = powernet.netexcess		// this was how much wasn't used on the network last ptick, minus any removed by other SMESes

	excess = min(output_used, excess)				// clamp it to how much was actually output by this SMES last ptick

	excess = min((capacity-charge)/SMESRATE, excess)	// for safety, also limit recharge by space capacity of SMES (shouldn't happen)

	// now recharge this amount

	var/clev = chargedisplay()

	charge += excess * SMESRATE			// restore unused power
	powernet.netexcess -= excess		// remove the excess from the powernet, so later SMESes don't try to use it

	output_used -= excess

	if(clev != chargedisplay() ) //if needed updates the icons overlay
		update_icon()
	return

/obj/machinery/power/smes/add_load(amount)
	if(terminal && terminal.powernet)
		terminal.powernet.load += amount

/obj/machinery/power/smes/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
										datum/tgui/master_ui = null, datum/ui_state/state = default_state)
	ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
	if(!ui)
		ui = new(user, src, ui_key, "smes", name, 340, 440, master_ui, state)
		ui.open()

/obj/machinery/power/smes/ui_data()
	var/list/data = list(
		"capacityPercent" = round(100*charge/capacity, 0.1),
		"capacity" = capacity,
		"charge" = charge,

		"inputAttempt" = input_attempt,
		"inputting" = inputting,
		"inputLevel" = input_level,
		"inputLevelMax" = input_level_max,
		"inputAvailable" = input_available,

		"outputAttempt" = output_attempt,
		"outputting" = outputting,
		"outputLevel" = output_level,
		"outputLevelMax" = output_level_max,
		"outputUsed" = output_used
	)
	return data

/obj/machinery/power/smes/ui_act(action, params)
	if(..())
		return
	switch(action)
		if("tryinput")
			input_attempt = !input_attempt
			log_smes(usr.ckey)
			update_icon()
			. = TRUE
		if("tryoutput")
			output_attempt = !output_attempt
			log_smes(usr.ckey)
			update_icon()
			. = TRUE
		if("input")
			var/target = params["target"]
			var/adjust = text2num(params["adjust"])
			if(target == "input")
				target = input("New input target (0-[input_level_max]):", name, input_level) as num|null
				if(!isnull(target) && !..())
					. = TRUE
			else if(target == "min")
				target = 0
				. = TRUE
			else if(target == "max")
				target = input_level_max
				. = TRUE
			else if(adjust)
				target = input_level + adjust
				. = TRUE
			else if(text2num(target) != null)
				target = text2num(target)
				. = TRUE
			if(.)
				input_level = Clamp(target, 0, input_level_max)
				log_smes(usr.ckey)
		if("output")
			var/target = params["target"]
			var/adjust = text2num(params["adjust"])
			if(target == "input")
				target = input("New output target (0-[output_level_max]):", name, output_level) as num|null
				if(!isnull(target) && !..())
					. = TRUE
			else if(target == "min")
				target = 0
				. = TRUE
			else if(target == "max")
				target = output_level_max
				. = TRUE
			else if(adjust)
				target = output_level + adjust
				. = TRUE
			else if(text2num(target) != null)
				target = text2num(target)
				. = TRUE
			if(.)
				output_level = Clamp(target, 0, output_level_max)
				log_smes(usr.ckey)

/obj/machinery/power/smes/proc/log_smes(user = "")
	investigate_log("input/output; [input_level>output_level?"":""][input_level]/[output_level] | Charge: [charge] | Output-mode: [output_attempt?"on":"off"] | Input-mode: [input_attempt?"auto":"off"] by [user]", "singulo")

/obj/machinery/power/smes/emp_act(severity)
	input_attempt = rand(0,1)
	inputting = input_attempt
	output_attempt = rand(0,1)
	outputting = output_attempt
	output_level = rand(0, output_level_max)
	input_level = rand(0, input_level_max)
	charge -= 1e6/severity
	if (charge < 0)
		charge = 0
	update_icon()
	log_smes("an emp")
	..()

/obj/machinery/power/smes/engineering
	charge = 1.5e6 // Engineering starts with some charge for singulo

/obj/machinery/power/smes/magical
	name = "magical power storage unit"
	desc = "A high-capacity superconducting magnetic energy storage (SMES) unit. Magically produces power."

/obj/machinery/power/smes/magical/process()
	capacity = INFINITY
	charge = INFINITY
	..()

#undef SMESRATE

#undef SMES_CLEVEL_1
#undef SMES_CLEVEL_2
#undef SMES_CLEVEL_3
#undef SMES_CLEVEL_4
#undef SMES_CLEVEL_5
#undef SMES_OUTPUTTING
#undef SMES_NOT_OUTPUTTING
#undef SMES_INPUTTING
#undef SMES_INPUT_ATTEMPT

license: mit


code:
//Ported from /vg/station13, which was in turn forked from baystation12;
//Please do not bother them with bugs from this port, however, as it has been modified quite a bit.
//Modifications include removing the world-ending full supermatter variation, and leaving only the shard.

#define NITROGEN_RETARDATION_FACTOR 2        //Higher == N2 slows reaction more
#define THERMAL_RELEASE_MODIFIER 5                //Higher == less heat released during reaction
#define PLASMA_RELEASE_MODIFIER 750                //Higher == less plasma released by reaction
#define OXYGEN_RELEASE_MODIFIER 325        //Higher == less oxygen released at high temperature/power
#define REACTION_POWER_MODIFIER 0.55                //Higher == more overall power

//These would be what you would get at point blank, decreases with distance
#define DETONATION_RADS 200
#define DETONATION_HALLUCINATION 600

#define WARNING_DELAY 30 		//seconds between warnings.

/obj/machinery/power/supermatter_shard
	name = "supermatter shard"
	desc = "A strangely translucent and iridescent crystal that looks like it used to be part of a larger structure. You get headaches just from looking at it."
	icon = 'icons/obj/supermatter.dmi'
	icon_state = "darkmatter_shard"
	density = 1
	anchored = 0
	luminosity = 4

	var/gasefficency = 0.125

	var/base_icon_state = "darkmatter_shard"

	var/damage = 0
	var/damage_archived = 0
	var/safe_alert = "Crystalline hyperstructure returning to safe operating levels."
	var/warning_point = 50
	var/warning_alert = "Danger! Crystal hyperstructure instability!"
	var/emergency_point = 500
	var/emergency_alert = "CRYSTAL DELAMINATION IMMINENT."
	var/explosion_point = 900

	var/emergency_issued = 0

	var/explosion_power = 8

	var/lastwarning = 0				// Time in 1/10th of seconds since the last sent warning
	var/power = 0

	var/oxygen = 0					// Moving this up here for easier debugging.

	//Temporary values so that we can optimize this
	//How much the bullets damage should be multiplied by when it is added to the internal variables
	var/config_bullet_energy = 2
	//How much of the power is left after processing is finished?
//	var/config_power_reduction_per_tick = 0.5
	//How much hallucination should it produce per unit of power?
	var/config_hallucination_power = 0.1

	var/obj/item/device/radio/radio

	//for logging
	var/has_been_powered = 0
	var/has_reached_emergency = 0

/obj/machinery/power/supermatter_shard/New()
	. = ..()
	radio = new(src)
	radio.listening = 0
	investigate_log("has been created.", "supermatter")

/obj/machinery/power/supermatter_shard/Destroy()
	investigate_log("has been destroyed.", "supermatter")
	qdel(radio)
	. = ..()

/obj/machinery/power/supermatter_shard/proc/explode()
	investigate_log("has exploded.", "supermatter")
	explosion(get_turf(src), explosion_power, explosion_power * 2, explosion_power * 3, explosion_power * 4, 1, 1)
	qdel(src)
	return

/obj/machinery/power/supermatter_shard/process()
	var/turf/L = loc

	if(isnull(L))		// We have a null turf...something is wrong, stop processing this entity.
		return PROCESS_KILL

	if(!istype(L)) 	//We are in a crate or somewhere that isn't turf, if we return to turf resume processing but for now.
		return  //Yeah just stop.

	if(istype(L, /turf/open/space))	// Stop processing this stuff if we've been ejected.
		return

	if(damage > warning_point) // while the core is still damaged and it's still worth noting its status
		if((world.timeofday - lastwarning) / 10 >= WARNING_DELAY)
			var/stability = num2text(round((damage / explosion_point) * 100))

			if(damage > emergency_point)
				radio.talk_into(src, "[emergency_alert] Instability: [stability]%")
				lastwarning = world.timeofday
				if(!has_reached_emergency)
					investigate_log("has reached the emergency point for the first time.", "supermatter")
					message_admins("[src] has reached the emergency point (JMP).")
					has_reached_emergency = 1

			else if(damage >= damage_archived) // The damage is still going up
				radio.talk_into(src, "[warning_alert] Instability: [stability]%")
				lastwarning = world.timeofday - 150

			else                                                 // Phew, we're safe
				radio.talk_into(src, "[safe_alert]")
				lastwarning = world.timeofday

		if(damage > explosion_point)
			for(var/mob/living/mob in living_mob_list)
				if(istype(mob, /mob/living/carbon/human))
					//Hilariously enough, running into a closet should make you get hit the hardest.
					var/mob/living/carbon/human/H = mob
					H.hallucination += max(50, min(300, DETONATION_HALLUCINATION * sqrt(1 / (get_dist(mob, src) + 1)) ) )
				var/rads = DETONATION_RADS * sqrt( 1 / (get_dist(mob, src) + 1) )
				mob.rad_act(rads)

			explode()

	//Ok, get the air from the turf
	var/datum/gas_mixture/env = L.return_air()

	//Remove gas from surrounding area
	var/datum/gas_mixture/removed = env.remove(gasefficency * env.total_moles())

	if(!removed || !removed.total_moles())
		damage += max((power-1600)/10, 0)
		power = min(power, 1600)
		return 1

	damage_archived = damage
	damage = max( damage + ( (removed.temperature - 800) / 150 ) , 0 )
	//Ok, 100% oxygen atmosphere = best reaction
	//Maxes out at 100% oxygen pressure
	var/removed_nitrogen = 0
	if(removed.gases["n2"])
		removed_nitrogen = (removed.gases["n2"][MOLES] * NITROGEN_RETARDATION_FACTOR)

	removed.assert_gases("o2", "plasma")

	oxygen = max(min((removed.gases["o2"][MOLES] - removed_nitrogen) / MOLES_CELLSTANDARD, 1), 0)

	var/temp_factor = 50

	if(oxygen > 0.8)
		// with a perfect gas mix, make the power less based on heat
		icon_state = "[base_icon_state]_glow"
	else
		// in normal mode, base the produced energy around the heat
		temp_factor = 30
		icon_state = base_icon_state

	power = max( (removed.temperature * temp_factor / T0C) * oxygen + power, 0) //Total laser power plus an overload

	//We've generated power, now let's transfer it to the collectors for storing/usage
	transfer_energy()

	var/device_energy = power * REACTION_POWER_MODIFIER

	//To figure out how much temperature to add each tick, consider that at one atmosphere's worth
	//of pure oxygen, with all four lasers firing at standard energy and no N2 present, at room temperature
	//that the device energy is around 2140. At that stage, we don't want too much heat to be put out
	//Since the core is effectively "cold"

	//Also keep in mind we are only adding this temperature to (efficiency)% of the one tile the rock
	//is on. An increase of 4*C @ 25% efficiency here results in an increase of 1*C / (#tilesincore) overall.
	removed.temperature += (device_energy / THERMAL_RELEASE_MODIFIER)

	removed.temperature = max(0, min(removed.temperature, 2500))

	//Calculate how much gas to release
	removed.gases["plasma"][MOLES] += max(device_energy / PLASMA_RELEASE_MODIFIER, 0)

	removed.gases["o2"][MOLES] += max((device_energy + removed.temperature - T0C) / OXYGEN_RELEASE_MODIFIER, 0)

	env.merge(removed)

	for(var/mob/living/carbon/human/l in view(src, min(7, round(power ** 0.25)))) // If they can see it without mesons on.  Bad on them.
		if(!istype(l.glasses, /obj/item/clothing/glasses/meson))
			l.hallucination = max(0, min(200, l.hallucination + power * config_hallucination_power * sqrt( 1 / max(1, get_dist(l, src)) ) ) )

	for(var/mob/living/l in range(src, round((power / 100) ** 0.25)))
		var/rads = (power / 10) * sqrt( 1 / max(get_dist(l, src),1) )
		l.rad_act(rads)

	power -= (power/500)**3

	return 1

/obj/machinery/power/supermatter_shard

/obj/machinery/power/supermatter_shard/bullet_act(obj/item/projectile/Proj)
	var/turf/L = loc
	if(!istype(L))		// We don't run process() when we are in space
		return 0	// This stops people from being able to really power up the supermatter
				// Then bring it inside to explode instantly upon landing on a valid turf.

	if(Proj.flag != "bullet")
		power += Proj.damage * config_bullet_energy
		if(!has_been_powered)
			investigate_log("has been powered for the first time.", "supermatter")
			message_admins("[src] has been powered for the first time (JMP).")
			has_been_powered = 1
	else
		damage += Proj.damage * config_bullet_energy
	return 0

/obj/machinery/power/supermatter_shard/singularity_act()
	var/gain = 100
	investigate_log("Supermatter shard consumed by singularity.","singulo")
	message_admins("Singularity has consumed a supermatter shard and can now become stage six.")
	visible_message("[src] is consumed by the singularity!")
	for(var/mob/M in mob_list)
		M << 'sound/effects/supermatter.ogg' //everyone goan know bout this
		M << "A horrible screeching fills your ears, and a wave of dread washes over you..."
	qdel(src)
	return(gain)

/obj/machinery/power/supermatter_shard/attack_paw(mob/user)
	return attack_hand(user)

/obj/machinery/power/supermatter_shard/attack_robot(mob/user)
	if(Adjacent(user))
		return attack_hand(user)
	else
		user << "You attempt to interface with the control circuits but find they are not connected to your network. Maybe in a future firmware update."
	return

/obj/machinery/power/supermatter_shard/attack_ai(mob/user)
	user << "You attempt to interface with the control circuits but find they are not connected to your network. Maybe in a future firmware update."

/obj/machinery/power/supermatter_shard/attack_hand(mob/living/user)
	if(!istype(user))
		return
	user.visible_message("\The [user] reaches out and touches \the [src], inducing a resonance... \his body starts to glow and bursts into flames before flashing into ash.",\
		"You reach out and touch \the [src]. Everything starts burning and all you can hear is ringing. Your last thought is \"That was not a wise decision.\"",\
		"You hear an unearthly noise as a wave of heat washes over you.")

	playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, 1)

	Consume(user)

/obj/machinery/power/supermatter_shard/proc/transfer_energy()
	for(var/obj/machinery/power/rad_collector/R in rad_collectors)
		if(get_dist(R, src) <= 15) // Better than using orange() every process
			R.receive_pulse(power/10)
	return

/obj/machinery/power/supermatter_shard/attackby(obj/item/W, mob/living/user, params)
	if(!istype(W) || (W.flags & ABSTRACT) || !istype(user))
		return
	if(user.drop_item(W))
		Consume(W)
		user.visible_message("As [user] touches \the [src] with \a [W], silence fills the room...",\
			"You touch \the [src] with \the [W], and everything suddenly goes silent.\"\n\The [W] flashes into dust as you flinch away from \the [src].",\
			"Everything suddenly goes silent.")

		playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, 1)

		radiation_pulse(get_turf(src), 1, 1, 150, 1)

/obj/machinery/power/supermatter_shard/Bumped(atom/AM as mob|obj)
	if(istype(AM, /mob/living))
		AM.visible_message("\The [AM] slams into \the [src] inducing a resonance... \his body starts to glow and catch flame before flashing into ash.",\
		"You slam into \the [src] as your ears are filled with unearthly ringing. Your last thought is \"Oh, fuck.\"",\
		"You hear an unearthly noise as a wave of heat washes over you.")
	else if(isobj(AM) && !istype(AM, /obj/effect))
		AM.visible_message("\The [AM] smacks into \the [src] and rapidly flashes to ash.",\
		"You hear a loud crack as you are washed with a wave of heat.")
	else
		return

	playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, 1)

	Consume(AM)

/obj/machinery/power/supermatter_shard/proc/Consume(atom/movable/AM)
	if(istype(AM, /mob/living))
		var/mob/living/user = AM
		message_admins("[src] has consumed [key_name_admin(user)]? (FLW) (JMP).")
		investigate_log("has consumed [key_name(user)].", "supermatter")
		user.dust()
		power += 200
	else if(isobj(AM) && !istype(AM, /obj/effect))
		investigate_log("has consumed [AM].", "supermatter")
		qdel(AM)

	power += 200

	//Some poor sod got eaten, go ahead and irradiate people nearby.
	radiation_pulse(get_turf(src), 4, 10, 500, 1)
	for(var/mob/living/L in range(10))
		investigate_log("has irradiated [L] after consuming [AM].", "supermatter")
		if(L in view())
			L.show_message("As \the [src] slowly stops resonating, you find your skin covered in new radiation burns.", 1,\
				"The unearthly ringing subsides and you notice you have new radiation burns.", 2)
		else
			L.show_message("You hear an uneartly ringing and notice your skin is covered in fresh radiation burns.", 2)


code:
//This is a power switch. When turned on it looks at the cables around the tile that it's on and notes which cables are trying to connect to it.
//After it knows this it creates the number of cables from the center to each of the cables attempting to conenct. These cables cannot be removed
//with wirecutters. When the switch is turned off it removes all the cables on the tile it's on.
//The switch uses a 5s delay to prevent powernet change spamming.
/*
/obj/structure/powerswitch
	name = "power switch"
	desc = "A switch that controls power."
	icon = 'icons/obj/power.dmi'
	icon_state = "switch-dbl-up"
	var/icon_state_on = "switch-dbl-down"
	var/icon_state_off = "switch-dbl-up"
	density = 0
	anchored = 1
	var/on = 0  //up is off, down is on
	var/busy = 0 //set to 1 when you start pulling

/obj/structure/powerswitch/simple
	icon_state = "switch-up"
	icon_state_on = "switch-down"
	icon_state_off = "switch-up"

/obj/structure/powerswitch/examine(mob/user)
	..()
	if(on)
		user << "The switch is in the on position"
	else
		user << "The switch is in the off position"

/obj/structure/powerswitch/attack_ai(mob/user)
	user << "\red You're an AI. This is a manual switch. It's not going to work."
	return

/obj/structure/powerswitch/attack_hand(mob/user)

	if(busy)
		user << "\red This switch is already being toggled."
		return

	..()

	busy = 1
	for(var/mob/O in viewers(user))
		O.show_message(text("\red [user] started pulling the [src]."), 1)

	if(do_after(user, 50))
		set_state(!on)
		for(var/mob/O in viewers(user))
			O.show_message(text("\red [user] flipped the [src] into the [on ? "on": "off"] position."), 1)
	busy = 0

/obj/structure/powerswitch/proc/set_state(var/state)
	on = state
	if(on)
		icon_state = icon_state_on
		var/list/connection_dirs = list()
		for(var/direction in list(1,2,4,8,5,6,9,10))
			for(var/obj/structure/cable/C in get_step(src,direction))
				if(C.d1 == turn(direction, 180) || C.d2 == turn(direction, 180))
					connection_dirs += direction
					break

		for(var/direction in connection_dirs)
			var/obj/structure/cable/C = new/obj/structure/cable(src.loc)
			C.d1 = 0
			C.d2 = direction
			C.icon_state = "[C.d1]-[C.d2]"
			C.power_switch = src

			var/datum/powernet/PN = new()
			PN.number = powernets.len + 1
			powernets += PN
			C.netnum = PN.number
			PN.cables += C

			C.mergeConnectedNetworks(C.d2)
			C.mergeConnectedNetworksOnTurf()

	else
		icon_state = icon_state_off
		for(var/obj/structure/cable/C in src.loc)
			qdel(C)
*/


code:
// the underfloor wiring terminal for the APC
// autogenerated when an APC is placed
// all conduit connects go to this object instead of the APC
// using this solves the problem of having the APC in a wall yet also inside an area

/obj/machinery/power/terminal
	name = "terminal"
	icon_state = "term"
	desc = "It's an underfloor wiring terminal for power equipment."
	level = 1
	layer = TURF_LAYER
	var/obj/machinery/power/master = null
	anchored = 1
	layer = 2.6 // a bit above wires

/obj/machinery/power/terminal/New()
	..()
	var/turf/T = src.loc
	if(level==1) hide(T.intact)
	return

/obj/machinery/power/terminal/Destroy()
	if(master)
		master.disconnect_terminal()
	return ..()

/obj/machinery/power/terminal/hide(i)
	if(i)
		invisibility = 101
		icon_state = "term-f"
	else
		invisibility = 0
		icon_state = "term"

/obj/machinery/power/proc/can_terminal_dismantle()
	. = 0

/obj/machinery/power/apc/can_terminal_dismantle()
	. = 0
	if(opened && has_electronics != 2)
		. = 1

/obj/machinery/power/smes/can_terminal_dismantle()
	. = 0
	if(panel_open)
		. = 1

/obj/machinery/power/terminal/proc/dismantle(mob/living/user)
	if(istype(loc, /turf))
		var/turf/T = loc
		if(T.intact)
			user << "You must first expose the power terminal!"
			return

		if(master && master.can_terminal_dismantle())
			user.visible_message("[user.name] dismantles the power terminal from [master].", \
								"You begin to cut the cables...")

			playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1)
			if(do_after(user, 50, target = src))
				if(master && master.can_terminal_dismantle())
					if(prob(50) && electrocute_mob(user, powernet, src))
						var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
						s.set_up(5, 1, master)
						s.start()
						return
					new /obj/item/stack/cable_coil(loc, 10)
					user << "You cut the cables and dismantle the power terminal."
					qdel(src)

/obj/machinery/power/terminal/attackby(obj/item/W, mob/living/user, params)
	if(istype(W, /obj/item/weapon/wirecutters))
		dismantle(user)
		return

	..()


code:
/obj/machinery/power/tesla_coil
	name = "tesla coil"
	desc = "For the union!"
	icon = 'icons/obj/tesla_engine/tesla_coil.dmi'
	icon_state = "coil"
	anchored = 0
	density = 1
	var/power_loss = 2
	var/input_power_multiplier = 1

/obj/machinery/power/tesla_coil/New()
	..()
	component_parts = list()
	component_parts += new /obj/item/weapon/circuitboard/tesla_coil(null)
	component_parts += new /obj/item/weapon/stock_parts/capacitor(null)
	RefreshParts()

/obj/machinery/power/tesla_coil/RefreshParts()
	var/power_multiplier = 0
	for(var/obj/item/weapon/stock_parts/capacitor/C in component_parts)
		power_multiplier += C.rating
	input_power_multiplier = power_multiplier

/obj/machinery/power/tesla_coil/attackby(obj/item/W, mob/user, params)
	if(default_deconstruction_screwdriver(user, "coil", "coil", W))
		return

	if(exchange_parts(user, W))
		return

	if(default_pry_open(W))
		return

	if(default_unfasten_wrench(user, W))
		if(!anchored)
			disconnect_from_network()
		else
			connect_to_network()
		return

	default_deconstruction_crowbar(W)

/obj/machinery/power/tesla_coil/tesla_act(var/power)
	being_shocked = 1
	var/power_produced = power / power_loss
	add_avail(power_produced*input_power_multiplier)
	flick("coilhit", src)
	playsound(src.loc, 'sound/magic/LightningShock.ogg', 100, 1, extrarange = 5)
	tesla_zap(src, 5, power_produced)
	addtimer(src, "reset_shocked", 10)

/obj/machinery/power/grounding_rod
	name = "Grounding Rod"
	desc = "Keep an area from being fried from Edison's Bane."
	icon = 'icons/obj/tesla_engine/tesla_coil.dmi'
	icon_state = "grounding_rod"
	anchored = 0
	density = 1

/obj/machinery/power/grounding_rod/New()
	..()
	component_parts = list()
	component_parts += new /obj/item/weapon/circuitboard/grounding_rod(null)
	component_parts += new /obj/item/weapon/stock_parts/capacitor(null)
	RefreshParts()

/obj/machinery/power/grounding_rod/attackby(obj/item/W, mob/user, params)
	if(default_deconstruction_screwdriver(user, "grounding_rod", "grounding_rod", W))
		return

	if(exchange_parts(user, W))
		return

	if(default_pry_open(W))
		return

	if(default_unfasten_wrench(user, W))
		return

	default_deconstruction_crowbar(W)

/obj/machinery/power/grounding_rod/tesla_act(var/power)
	flick("coil_shock_1", src)


code:
#define TESLA_DEFAULT_POWER 1738260
#define TESLA_MINI_POWER 869130

var/list/blacklisted_tesla_types = list(/obj/machinery/atmospherics,
										/obj/machinery/power/emitter,
										/obj/machinery/field/generator,
										/mob/living/simple_animal,
										/obj/machinery/particle_accelerator/control_box,
										/obj/structure/particle_accelerator/fuel_chamber,
										/obj/structure/particle_accelerator/particle_emitter/center,
										/obj/structure/particle_accelerator/particle_emitter/left,
										/obj/structure/particle_accelerator/particle_emitter/right,
										/obj/structure/particle_accelerator/power_box,
										/obj/structure/particle_accelerator/end_cap,
										/obj/machinery/field/containment,
										/obj/structure/disposalpipe,
										/obj/machinery/gateway)

/obj/singularity/energy_ball
	name = "energy ball"
	desc = "An energy ball."
	icon = 'icons/obj/tesla_engine/energy_ball.dmi'
	icon_state = "energy_ball"
	pixel_x = -32
	pixel_y = -32
	current_size = STAGE_TWO
	move_self = 1
	grav_pull = 0
	contained = 0
	density = 1
	energy = 0
	var/list/orbiting_balls = list()
	var/produced_power
	var/energy_to_raise = 32
	var/energy_to_lower = -20

/obj/singularity/energy_ball/Destroy()
	if(orbiting && istype(orbiting, /obj/singularity/energy_ball))
		var/obj/singularity/energy_ball/EB = orbiting
		EB.orbiting_balls -= src
		orbiting = null

	for(var/ball in orbiting_balls)
		var/obj/singularity/energy_ball/EB = ball
		qdel(EB)

	return ..()

/obj/singularity/energy_ball/process()
	if(!orbiting)
		handle_energy()

		move_the_basket_ball(4 + orbiting_balls.len * 2)

		playsound(src.loc, 'sound/magic/lightningbolt.ogg', 100, 1, extrarange = 30)

		pixel_x = 0
		pixel_y = 0

		dir = tesla_zap(src, 7, TESLA_DEFAULT_POWER)

		pixel_x = -32
		pixel_y = -32
		for (var/ball in orbiting_balls)
			tesla_zap(ball, rand(1, Clamp(orbiting_balls.len, 3, 7)), TESLA_MINI_POWER)
	else
		energy = 0 // ensure we dont have miniballs of miniballs

	return

/obj/singularity/energy_ball/examine(mob/user)
	..()
	if(orbiting_balls.len)
		user << "The amount of orbiting mini-balls is [orbiting_balls.len]."

/obj/singularity/energy_ball/proc/move_the_basket_ball(var/move_amount)
	//we face the last thing we zapped, so this lets us favor that direction a bit
	var/first_move = dir
	for(var/i in 0 to move_amount)
		var/move_dir = pick(alldirs + first_move) //give the first move direction a bit of favoring.
		if(target && prob(60))
			move_dir = get_dir(src,target)
		var/turf/T = get_step(src, move_dir)
		if(can_move(T))
			loc = T

/obj/singularity/energy_ball/proc/handle_energy()
	if(energy >= energy_to_raise)
		energy_to_lower = energy_to_raise - 20
		energy_to_raise = energy_to_raise * 1.5

		playsound(src.loc, 'sound/magic/lightning_chargeup.ogg', 100, 1, extrarange = 30)
		spawn(100)
			if (!loc)
				return
			var/obj/singularity/energy_ball/EB = new(loc)

			EB.transform *= pick(0.3, 0.4, 0.5, 0.6, 0.7)
			var/icon/I = icon(icon,icon_state,dir)

			var/orbitsize = (I.Width() + I.Height()) * pick(0.4, 0.5, 0.6, 0.7, 0.8)
			orbitsize -= (orbitsize / world.icon_size) * (world.icon_size * 0.25)

			EB.orbit(src, orbitsize, pick(FALSE, TRUE), rand(10, 25), pick(3, 4, 5, 6, 36))

	else if(energy < energy_to_lower && orbiting_balls.len)
		energy_to_raise = energy_to_raise / 1.5
		energy_to_lower = (energy_to_raise / 1.5) - 20

		var/Orchiectomy_target = pick(orbiting_balls)
		qdel(Orchiectomy_target)

	else if(orbiting_balls.len)
		energy -= orbiting_balls.len * 0.5

/obj/singularity/energy_ball/Bump(atom/A)
	dust_mobs(A)

/obj/singularity/energy_ball/Bumped(atom/A)
	dust_mobs(A)

/obj/singularity/energy_ball/orbit(obj/singularity/energy_ball/target)
	if (istype(target))
		target.orbiting_balls += src
		poi_list -= src

	. = ..()

	if (istype(target))
		target.orbiting_balls -= src
	if (!loc)
		qdel(src)

/obj/singularity/energy_ball/proc/dust_mobs(atom/A)
	if(istype(A, /mob/living/carbon))
		var/mob/living/carbon/C = A
		C.dust()
	return

/proc/tesla_zap(var/atom/source, zap_range = 3, power)
	. = source.dir
	if(power < 1000)
		return

	var/closest_dist = 0
	var/closest_atom
	var/obj/machinery/power/tesla_coil/closest_tesla_coil
	var/obj/machinery/power/grounding_rod/closest_grounding_rod
	var/mob/living/closest_mob
	var/obj/machinery/closest_machine
	var/obj/structure/closest_structure
	var/obj/effect/blob/closest_blob

	for(var/A in oview(source, zap_range))
		if(istype(A, /obj/machinery/power/tesla_coil))
			var/dist = get_dist(source, A)
			var/obj/machinery/power/tesla_coil/C = A
			if((dist < closest_dist || !closest_tesla_coil) && !C.being_shocked)
				closest_dist = dist

				//we use both of these to save on istype and typecasting overhead later on
				//while still allowing common code to run before hand
				closest_tesla_coil = C
				closest_atom = C

		else if(closest_tesla_coil)
			continue //no need checking these other things

		else if(istype(A, /obj/machinery/power/grounding_rod))
			var/dist = get_dist(source, A)
			if(dist < closest_dist || !closest_grounding_rod)
				closest_grounding_rod = A
				closest_atom = A
				closest_dist = dist

		else if(closest_grounding_rod || is_type_in_list(A, blacklisted_tesla_types))
			continue

		else if(istype(A, /mob/living))
			var/dist = get_dist(source, A)
			var/mob/living/L = A
			if((dist < closest_dist || !closest_mob) && L.stat != DEAD)
				closest_mob = L
				closest_atom = A
				closest_dist = dist

		else if(closest_mob)
			continue

		else if(istype(A, /obj/machinery))
			var/obj/machinery/M = A
			var/dist = get_dist(source, A)
			if((dist < closest_dist || !closest_machine) && !M.being_shocked)
				closest_machine = M
				closest_atom = A
				closest_dist = dist

		else if(closest_mob)
			continue

		else if(istype(A, /obj/effect/blob))
			var/obj/effect/blob/B = A
			var/dist = get_dist(source, A)
			if((dist < closest_dist || !closest_tesla_coil) && !B.being_shocked)
				closest_blob = B
				closest_atom = A
				closest_dist = dist

		else if(closest_blob)
			continue

		else if(istype(A, /obj/structure))
			var/obj/structure/S = A
			var/dist = get_dist(source, A)
			if((dist < closest_dist || !closest_tesla_coil) && !S.being_shocked)
				closest_structure = S
				closest_atom = A
				closest_dist = dist

	//Alright, we've done our loop, now lets see if was anything interesting in range
	if(closest_atom)
		//common stuff
		source.Beam(closest_atom, icon_state="lightning[rand(1,12)]", icon='icons/effects/effects.dmi', time=5)
		var/zapdir = get_dir(source, closest_atom)
		if(zapdir)
			. = zapdir

	//per type stuff:
	if(closest_tesla_coil)
		closest_tesla_coil.tesla_act(power)

	else if(closest_grounding_rod)
		closest_grounding_rod.tesla_act(power)

	else if(closest_mob)
		var/shock_damage = Clamp(round(power/400), 10, 90) + rand(-5, 5)
		closest_mob.electrocute_act(shock_damage, source, 1, tesla_shock = 1)
		if(istype(closest_mob, /mob/living/silicon))
			var/mob/living/silicon/S = closest_mob
			S.emp_act(2)
			tesla_zap(S, 7, power / 1.5) // metallic folks bounce it further
		else
			tesla_zap(closest_mob, 5, power / 1.5)

	else if(closest_machine)
		closest_machine.tesla_act(power)

	else if(closest_blob)
		closest_blob.tesla_act(power)

	else if(closest_structure)
		closest_structure.tesla_act(power)


code:
/obj/machinery/the_singularitygen/tesla
	name = "energy ball generator"
	desc = "Makes the wardenclyffe look like a child's plaything when shot with a particle accelerator."
	icon = 'icons/obj/tesla_engine/tesla_generator.dmi'
	icon_state = "TheSingGen"
	creation_type = /obj/singularity/energy_ball


code:
//Solar tracker

//Machine that tracks the sun and reports it's direction to the solar controllers
//As long as this is working, solar panels on same powernet will track automatically

/obj/machinery/power/tracker
	name = "solar tracker"
	desc = "A solar directional tracker."
	icon = 'icons/obj/power.dmi'
	icon_state = "tracker"
	anchored = 1
	density = 1
	use_power = 0

	var/id = 0
	var/sun_angle = 0		// sun angle as set by sun datum
	var/obj/machinery/power/solar_control/control = null

/obj/machinery/power/tracker/New(var/turf/loc, var/obj/item/solar_assembly/S)
	..(loc)
	Make(S)
	connect_to_network()

/obj/machinery/power/tracker/Destroy()
	unset_control() //remove from control computer
	return ..()

//set the control of the tracker to a given computer if closer than SOLAR_MAX_DIST
/obj/machinery/power/tracker/proc/set_control(obj/machinery/power/solar_control/SC)
	if(!SC || (get_dist(src, SC) > SOLAR_MAX_DIST))
		return 0
	control = SC
	SC.connected_tracker = src
	return 1

//set the control of the tracker to null and removes it from the previous control computer if needed
/obj/machinery/power/tracker/proc/unset_control()
	if(control)
		control.connected_tracker = null
	control = null

/obj/machinery/power/tracker/proc/Make(obj/item/solar_assembly/S)
	if(!S)
		S = new /obj/item/solar_assembly(src)
		S.glass_type = /obj/item/stack/sheet/glass
		S.tracker = 1
		S.anchored = 1
	S.loc = src
	update_icon()

//updates the tracker icon and the facing angle for the control computer
/obj/machinery/power/tracker/proc/set_angle(angle)
	sun_angle = angle

	//set icon dir to show sun illumination
	dir = turn(NORTH, -angle - 22.5)	// 22.5 deg bias ensures, e.g. 67.5-112.5 is EAST

	if(powernet && (powernet == control.powernet)) //update if we're still in the same powernet
		control.currentdir = angle

/obj/machinery/power/tracker/attackby(obj/item/weapon/W, mob/user, params)

	if(istype(W, /obj/item/weapon/crowbar))
		playsound(src.loc, 'sound/machines/click.ogg', 50, 1)
		user.visible_message("[user] begins to take the glass off the solar tracker.", "You begin to take the glass off the solar tracker...")
		if(do_after(user, 50/W.toolspeed, target = src))
			var/obj/item/solar_assembly/S = locate() in src
			if(S)
				S.loc = src.loc
				S.give_glass(stat & BROKEN)
			playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1)
			user.visible_message("[user] takes the glass off the tracker.", "You take the glass off the tracker.")
			qdel(src)
		return
	..()

// Tracker Electronic

/obj/item/weapon/electronics/tracker
	name = "tracker electronics"


code:
// TURBINE v2 AKA rev4407 Engine reborn!

// How to use it? - Mappers
//
// This is a very good power generating mechanism. All you need is a blast furnace with soaring flames and output.
// Not everything is included yet so the turbine can run out of fuel quiet quickly. The best thing about the turbine is that even
// though something is on fire that passes through it, it won't be on fire as it passes out of it. So the exhaust fumes can still
// containt unreacted fuel - plasma and oxygen that needs to be filtered out and re-routed back. This of course requires smart piping
// For a computer to work with the turbine the compressor requires a comp_id matching with the turbine computer's id. This will be
// subjected to a change in the near future mind you. Right now this method of generating power is a good backup but don't expect it
// become a main power source unless some work is done. Have fun. At 50k RPM it generates 60k power. So more than one turbine is needed!
//
// - Numbers
//
// Example setup	 S - sparker
//					 B - Blast doors into space for venting
// *BBB****BBB*		 C - Compressor
// S    CT    *		 T - Turbine
// * ^ *  * V *		 D - Doors with firedoor
// **|***D**|**      ^ - Fuel feed (Not vent, but a gas outlet)
//   |      |        V - Suction vent (Like the ones in atmos
//

/obj/machinery/power/compressor
	name = "compressor"
	desc = "The compressor stage of a gas turbine generator."
	icon = 'icons/obj/atmospherics/pipes/simple.dmi'
	icon_state = "compressor"
	anchored = 1
	density = 1
	var/obj/machinery/power/turbine/turbine
	var/datum/gas_mixture/gas_contained
	var/turf/inturf
	var/starter = 0
	var/rpm = 0
	var/rpmtarget = 0
	var/capacity = 1e6
	var/comp_id = 0
	var/efficiency

/obj/machinery/power/turbine
	name = "gas turbine generator"
	desc = "A gas turbine used for backup power generation."
	icon = 'icons/obj/atmospherics/pipes/simple.dmi'
	icon_state = "turbine"
	anchored = 1
	density = 1
	var/opened = 0
	var/obj/machinery/power/compressor/compressor
	var/turf/outturf
	var/lastgen
	var/productivity = 1

/obj/machinery/computer/turbine_computer
	name = "gas turbine control computer"
	desc = "A computer to remotely control a gas turbine"
	icon_screen = "turbinecomp"
	icon_keyboard = "tech_key"
	circuit = /obj/item/weapon/circuitboard/turbine_computer
	var/obj/machinery/power/compressor/compressor
	var/id = 0

// the inlet stage of the gas turbine electricity generator

/obj/machinery/power/compressor/New()
	..()
	component_parts = list()
	component_parts += new /obj/item/weapon/circuitboard/power_compressor(null)
	component_parts += new /obj/item/weapon/stock_parts/manipulator(null)
	component_parts += new /obj/item/weapon/stock_parts/manipulator(null)
	component_parts += new /obj/item/weapon/stock_parts/manipulator(null)
	component_parts += new /obj/item/weapon/stock_parts/manipulator(null)
	component_parts += new /obj/item/weapon/stock_parts/manipulator(null)
	component_parts += new /obj/item/weapon/stock_parts/manipulator(null)
	component_parts += new /obj/item/stack/cable_coil(null, 5)
	RefreshParts()
// The inlet of the compressor is the direction it faces

	gas_contained = new
	inturf = get_step(src, dir)

/obj/machinery/power/compressor/initialize()
	..()
	locate_machinery()
	if(!turbine)
		stat |= BROKEN

#define COMPFRICTION 5e5
#define COMPSTARTERLOAD 2800

// Crucial to make things work!!!!
// OLD FIX - explanation given down below.
// /obj/machinery/power/compressor/CanPass(atom/movable/mover, turf/target, height=0)
// 		return !density

/obj/machinery/power/compressor/locate_machinery()
	if(turbine)
		return
	turbine = locate() in get_step(src, get_dir(inturf, src))
	if(turbine)
		turbine.locate_machinery()

/obj/machinery/power/compressor/RefreshParts()
	var/E = 0
	for(var/obj/item/weapon/stock_parts/manipulator/M in component_parts)
		E += M.rating
	efficiency = E / 6

/obj/machinery/power/compressor/attackby(obj/item/I, mob/user, params)
	if(default_deconstruction_screwdriver(user, initial(icon_state), initial(icon_state), I))
		return

	if(default_change_direction_wrench(user, I))
		turbine = null
		inturf = get_step(src, dir)
		locate_machinery()
		if(turbine)
			user << "Turbine connected."
			stat &= ~BROKEN
		else
			user << "Turbine not connected."
			stat |= BROKEN
		return

	if(exchange_parts(user, I))
		return

	default_deconstruction_crowbar(I)

/obj/machinery/power/compressor/CanAtmosPass(turf/T)
	return !density

/obj/machinery/power/compressor/process()
	if(!turbine)
		stat = BROKEN
	if(stat & BROKEN || panel_open)
		return
	if(!starter)
		return
	overlays.Cut()

	rpm = 0.9* rpm + 0.1 * rpmtarget
	var/datum/gas_mixture/environment = inturf.return_air()

	// It's a simplified version taking only 1/10 of the moles from the turf nearby. It should be later changed into a better version

	var/transfer_moles = environment.total_moles()/10
	//var/transfer_moles = rpm/10000*capacity
	var/datum/gas_mixture/removed = inturf.remove_air(transfer_moles)
	gas_contained.merge(removed)

// RPM function to include compression friction - be advised that too low/high of a compfriction value can make things screwy

	rpm = max(0, rpm - (rpm*rpm)/(COMPFRICTION/efficiency))

	if(starter && !(stat & NOPOWER))
		use_power(2800)
		if(rpm<1000)
			rpmtarget = 1000
	else
		if(rpm<1000)
			rpmtarget = 0

	if(rpm>50000)
		overlays += image('icons/obj/atmospherics/pipes/simple.dmi', "comp-o4", FLY_LAYER)
	else if(rpm>10000)
		overlays += image('icons/obj/atmospherics/pipes/simple.dmi', "comp-o3", FLY_LAYER)
	else if(rpm>2000)
		overlays += image('icons/obj/atmospherics/pipes/simple.dmi', "comp-o2", FLY_LAYER)
	else if(rpm>500)
		overlays += image('icons/obj/atmospherics/pipes/simple.dmi', "comp-o1", FLY_LAYER)
	 //TODO: DEFERRED

// These are crucial to working of a turbine - the stats modify the power output. TurbGenQ modifies how much raw energy can you get from
// rpms, TurbGenG modifies the shape of the curve - the lower the value the less straight the curve is.

#define TURBPRES 9000000
#define TURBGENQ 100000
#define TURBGENG 0.5

/obj/machinery/power/turbine/New()
	..()
	component_parts = list()
	component_parts += new /obj/item/weapon/circuitboard/power_turbine(src)
	component_parts += new /obj/item/weapon/stock_parts/capacitor(src)
	component_parts += new /obj/item/weapon/stock_parts/capacitor(src)
	component_parts += new /obj/item/weapon/stock_parts/capacitor(src)
	component_parts += new /obj/item/weapon/stock_parts/capacitor(src)
	component_parts += new /obj/item/weapon/stock_parts/capacitor(src)
	component_parts += new /obj/item/weapon/stock_parts/capacitor(src)
	component_parts += new /obj/item/stack/cable_coil(src, 5)
	RefreshParts()
// The outlet is pointed at the direction of the turbine component

	outturf = get_step(src, dir)

/obj/machinery/power/turbine/initialize()
	..()
	locate_machinery()
	if(!compressor)
		stat |= BROKEN

/obj/machinery/power/turbine/RefreshParts()
	var/P = 0
	for(var/obj/item/weapon/stock_parts/capacitor/C in component_parts)
		P += C.rating
	productivity = P / 6

/obj/machinery/power/turbine/locate_machinery()
	if(compressor)
		return
	compressor = locate() in get_step(src, get_dir(outturf, src))
	if(compressor)
		compressor.locate_machinery()

/obj/machinery/power/turbine/CanAtmosPass(turf/T)
	return !density

/obj/machinery/power/turbine/process()

	if(!compressor)
		stat = BROKEN

	if((stat & BROKEN) || panel_open)
		return
	if(!compressor.starter)
		return
	overlays.Cut()

	// This is the power generation function. If anything is needed it's good to plot it in EXCEL before modifying
	// the TURBGENQ and TURBGENG values

	lastgen = ((compressor.rpm / TURBGENQ)**TURBGENG) * TURBGENQ * productivity

	add_avail(lastgen)

	// Weird function but it works. Should be something else...

	var/newrpm = ((compressor.gas_contained.temperature) * compressor.gas_contained.total_moles())/4

	newrpm = max(0, newrpm)

	if(!compressor.starter || newrpm > 1000)
		compressor.rpmtarget = newrpm

	if(compressor.gas_contained.total_moles()>0)
		var/oamount = min(compressor.gas_contained.total_moles(), (compressor.rpm+100)/35000*compressor.capacity)
		var/datum/gas_mixture/removed = compressor.gas_contained.remove(oamount)
		outturf.assume_air(removed)

// If it works, put an overlay that it works!

	if(lastgen > 100)
		overlays += image('icons/obj/atmospherics/pipes/simple.dmi', "turb-o", FLY_LAYER)

	updateDialog()

/obj/machinery/power/turbine/attack_hand(mob/user)

	if(..())
		return

	interact(user)

/obj/machinery/power/turbine/attackby(obj/item/I, mob/user, params)
	if(default_deconstruction_screwdriver(user, initial(icon_state), initial(icon_state), I))
		return

	if(default_change_direction_wrench(user, I))
		compressor = null
		outturf = get_step(src, dir)
		locate_machinery()
		if(compressor)
			user << "Compressor connected."
			stat &= ~BROKEN
		else
			user << "Compressor not connected."
			stat |= BROKEN
		return

	if(exchange_parts(user, I))
		return

	default_deconstruction_crowbar(I)

/obj/machinery/power/turbine/interact(mob/user)

	if ( !Adjacent(user)  || (stat & (NOPOWER|BROKEN)) && (!istype(user, /mob/living/silicon)) )
		user.unset_machine(src)
		user << browse(null, "window=turbine")
		return

	var/t = "<TT>Gas Turbine Generator

"

	t += "Generated power : [round(lastgen)] W

"

	t += "Turbine: [round(compressor.rpm)] RPM
"

	t += "Starter: [ compressor.starter ? "Off On" : "Off On"]"

	t += "

Close"

	t += "</TT>"
	var/datum/browser/popup = new(user, "turbine", name)
	popup.set_content(t)
	popup.open()

	return

/obj/machinery/power/turbine/Topic(href, href_list)
	if(..())
		return

	if( href_list["close"] )
		usr << browse(null, "window=turbine")
		usr.unset_machine(src)
		return

	else if( href_list["str"] )
		if(compressor)
			compressor.starter = !compressor.starter

	updateDialog()

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// COMPUTER NEEDS A SERIOUS REWRITE.

/obj/machinery/computer/turbine_computer/initialize()
	..()
	spawn(10)
		locate_machinery()

/obj/machinery/computer/turbine_computer/locate_machinery()
	compressor = locate(/obj/machinery/power/compressor) in range(5, src)

/obj/machinery/computer/turbine_computer/attack_hand(var/mob/user as mob)
	if(..())
		return

	interact(user)

/obj/machinery/computer/turbine_computer/interact(mob/user)

	var/dat
	if(compressor && compressor.turbine)
		dat += "
Gas turbine remote control system
"
		if(compressor.stat || compressor.turbine.stat)
			dat += "[compressor.stat ? "Compressor is inoperable
" : "Turbine is inoperable"]"
		else
			dat += {"Turbine status: [ src.compressor.starter ? "Off On" : "Off On"]
			\n

			\nTurbine speed: [src.compressor.rpm]rpm

			\nPower currently being generated: [src.compressor.turbine.lastgen]W

			\nInternal gas temperature: [src.compressor.gas_contained.temperature]K

			\n

Close
			\n

			\n"}
	else
		dat += "There is [!compressor ? "no compressor" : " compressor[!compressor.turbine ? " but no turbine" : ""]"].
"
		if(!compressor)
			dat += "Search for compressor"

	var/datum/browser/popup = new(user, "turbinecomputer", name)
	popup.set_content(dat)
	popup.open()
	return

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

	else if( href_list["str"] )
		if(compressor && compressor.turbine)
			compressor.starter = !compressor.starter
	else if( href_list["close"] )
		usr << browse(null, "window=turbinecomputer")
		usr.unset_machine(src)
		return
	else if(href_list["search"])
		locate_machinery()

	src.updateUsrDialog()
	return

/obj/machinery/computer/turbine_computer/process()
	src.updateDialog()
	return


code:
//clusterCheckFlags defines
//All based on clusterMin and clusterMax as guides

//Individual defines
#define CLUSTER_CHECK_NONE				0  //No checks are done, cluster as much as possible
#define CLUSTER_CHECK_DIFFERENT_TURFS	2  //Don't let turfs of DIFFERENT types cluster
#define CLUSTER_CHECK_DIFFERENT_ATOMS	4  //Don't let atoms of DIFFERENT types cluster
#define CLUSTER_CHECK_SAME_TURFS		8  //Don't let turfs of the SAME type cluster
#define CLUSTER_CHECK_SAME_ATOMS		16 //Don't let atoms of the SAME type cluster

//Combined defines
#define CLUSTER_CHECK_SAMES				24 //Don't let any of the same type cluster
#define CLUSTER_CHECK_DIFFERENTS		6  //Don't let any of different types cluster
#define CLUSTER_CHECK_ALL_TURFS			10 //Don't let ANY turfs cluster same and different types
#define CLUSTER_CHECK_ALL_ATOMS			20 //Don't let ANY atoms cluster same and different types

//All
#define CLUSTER_CHECK_ALL				30 //Don't let anything cluster, like, at all

/datum/mapGenerator

	//Map information
	var/list/map = list()

	//mapGeneratorModule information
	var/list/modules = list()

/datum/mapGenerator/New()
	..()
	initialiseModules()

//Defines the region the map represents, sets map
//Returns the map
/datum/mapGenerator/proc/defineRegion(turf/Start, turf/End, replace = 0)
	if(!checkRegion(Start, End))
		return 0

	if(replace)
		undefineRegion()
	map |= block(Start,End)
	return map

//Defines the region the map represents, as a CIRCLE!, sets map
//Returns the map
/datum/mapGenerator/proc/defineCircularRegion(turf/Start, turf/End, replace = 0)
	if(!checkRegion(Start, End))
		return 0

	var/centerX = max(abs((End.x+Start.x)/2),1)
	var/centerY = max(abs((End.y+Start.y)/2),1)

	var/lilZ = min(Start.z,End.z)
	var/bigZ = max(Start.z,End.z)

	var/sphereMagic = max(abs(bigZ-(lilZ/2)),1) //Spherical maps! woo!

	var/radius = abs(max(centerX,centerY)) //take the biggest displacement as the radius

	if(replace)
		undefineRegion()

	//Even sphere correction engage
	var/offByOneOffset = 1
	if(bigZ % 2 == 0)
		offByOneOffset = 0

	for(var/i = lilZ, i <= bigZ+offByOneOffset, i++)
		var/theRadius = radius
		if(i != sphereMagic)
			theRadius = max(radius/max((2*abs(sphereMagic-i)),1),1)

		map |= circlerange(locate(centerX,centerY,i),theRadius)

	return map

//Empties the map list, he's dead jim.
/datum/mapGenerator/proc/undefineRegion()
	map = list() //bai bai

//Checks for and Rejects bad region coordinates
//Returns 1/0
/datum/mapGenerator/proc/checkRegion(turf/Start, turf/End)
	. = 1

	if(!Start || !End)
		return 0 //Just bail

	if(Start.x > world.maxx || End.x > world.maxx)
		. = 0
	if(Start.y > world.maxy || End.y > world.maxy)
		. = 0
	if(Start.z > world.maxz || End.z > world.maxz)
		. = 0

//Requests the mapGeneratorModule(s) to (re)generate
/datum/mapGenerator/proc/generate()
	set background = 1 //this can get beefy

	syncModules()
	if(!modules || !modules.len)
		return
	for(var/datum/mapGeneratorModule/mod in modules)
		spawn(0)
			mod.generate()

//Requests the mapGeneratorModule(s) to (re)generate this one turf
/datum/mapGenerator/proc/generateOneTurf(turf/T)
	if(!T)
		return
	syncModules()
	if(!modules || !modules.len)
		return
	for(var/datum/mapGeneratorModule/mod in modules)
		spawn(0)
			mod.place(T)

//Replaces all paths in the module list with actual module datums
/datum/mapGenerator/proc/initialiseModules()
	for(var/path in modules)
		if(ispath(path))
			modules.Remove(path)
			modules |= new path
	syncModules()

//Sync mapGeneratorModule(s) to mapGenerator
/datum/mapGenerator/proc/syncModules()
	for(var/datum/mapGeneratorModule/mod in modules)
		mod.sync(src)

///////////////////////////
// HERE BE DEBUG DRAGONS //
///////////////////////////

/client/proc/debugNatureMapGenerator()
	set name = "Test Nature Map Generator"
	set category = "Debug"

	var/datum/mapGenerator/nature/N = new()
	var/startInput = input(usr,"Start turf of Map, (X;Y;Z)", "Map Gen Settings", "1;1;1") as text
	var/endInput = input(usr,"End turf of Map (X;Y;Z)", "Map Gen Settings", "[world.maxx];[world.maxy];[mob ? mob.z : 1]") as text
	//maxx maxy and current z so that if you fuck up, you only fuck up one entire z level instead of the entire universe
	if(!startInput || !endInput)
		src << "Missing Input"
		return

	var/list/startCoords = splittext(startInput, ";")
	var/list/endCoords = splittext(endInput, ";")
	if(!startCoords || !endCoords)
		src << "Invalid Coords"
		src << "Start Input: [startInput]"
		src << "End Input: [endInput]"
		return

	var/turf/Start = locate(text2num(startCoords[1]),text2num(startCoords[2]),text2num(startCoords[3]))
	var/turf/End = locate(text2num(endCoords[1]),text2num(endCoords[2]),text2num(endCoords[3]))
	if(!Start || !End)
		src << "Invalid Turfs"
		src << "Start Coords: [startCoords[1]] - [startCoords[2]] - [startCoords[3]]"
		src << "End Coords: [endCoords[1]] - [endCoords[2]] - [endCoords[3]]"
		return

	var/list/clusters = list("None"=CLUSTER_CHECK_NONE,"All"=CLUSTER_CHECK_ALL,"Sames"=CLUSTER_CHECK_SAMES,"Differents"=CLUSTER_CHECK_DIFFERENTS, \
	"Same turfs"=CLUSTER_CHECK_SAME_TURFS, "Same atoms"=CLUSTER_CHECK_SAME_ATOMS, "Different turfs"=CLUSTER_CHECK_DIFFERENT_TURFS, \
	"Different atoms"=CLUSTER_CHECK_DIFFERENT_ATOMS, "All turfs"=CLUSTER_CHECK_ALL_TURFS,"All atoms"=CLUSTER_CHECK_ALL_ATOMS)

	var/moduleClusters = input("Cluster Flags (Cancel to leave unchanged from defaults)","Map Gen Settings") as null|anything in clusters
	//null for default

	var/theCluster = 0
	if(moduleClusters != "None")
		if(!clusters[moduleClusters])
			src << "Invalid Cluster Flags"
			return
		theCluster = clusters[moduleClusters]
	else
		theCluster =  CLUSTER_CHECK_NONE

	if(theCluster)
		for(var/datum/mapGeneratorModule/M in N.modules)
			M.clusterCheckFlags = theCluster

	src << "Defining Region"
	N.defineRegion(Start, End)
	src << "Region Defined"
	src << "Generating Region"
	N.generate()
	src << "Generated Region"


code:
/datum/mapGeneratorModule
	var/datum/mapGenerator/mother = null
	var/list/spawnableAtoms = list()
	var/list/spawnableTurfs = list()
	var/clusterMax = 5
	var/clusterMin = 1
	var/clusterCheckFlags = CLUSTER_CHECK_SAME_ATOMS
	var/allowAtomsOnSpace = FALSE

//Syncs the module up with it's mother
/datum/mapGeneratorModule/proc/sync(datum/mapGenerator/mum)
	mother = null
	if(mum)
		mother = mum

//Generates it's spawnable atoms and turfs
/datum/mapGeneratorModule/proc/generate()
	if(!mother)
		return
	var/list/map = mother.map
	for(var/turf/T in map)
		place(T)

//Place a spawnable atom or turf on this turf
/datum/mapGeneratorModule/proc/place(turf/T)
	if(!T)
		return 0

	var/clustering = 0
	var/skipLoopIteration = FALSE

	//Turfs don't care whether atoms can be placed here
	for(var/turfPath in spawnableTurfs)

		//Clustering!
		if(clusterMax && clusterMin)

			//You're the same as me? I hate you I'm going home
			if(clusterCheckFlags & CLUSTER_CHECK_SAME_TURFS)
				clustering = rand(clusterMin,clusterMax)
				for(var/turf/F in RANGE_TURFS(clustering,T))
					if(istype(F,turfPath))
						skipLoopIteration = TRUE
						break
				if(skipLoopIteration)
					skipLoopIteration = FALSE
					continue

			//You're DIFFERENT to me? I hate you I'm going home
			if(clusterCheckFlags & CLUSTER_CHECK_DIFFERENT_TURFS)
				clustering = rand(clusterMin,clusterMax)
				for(var/turf/F in RANGE_TURFS(clustering,T))
					if(!(istype(F,turfPath)))
						skipLoopIteration = TRUE
						break
				if(skipLoopIteration)
					skipLoopIteration = FALSE
					continue

		//Success!
		if(prob(spawnableTurfs[turfPath]))
			T.ChangeTurf(turfPath)

	//Atoms DO care whether atoms can be placed here
	if(checkPlaceAtom(T))

		for(var/atomPath in spawnableAtoms)

			//Clustering!
			if(clusterMax && clusterMin)

				//You're the same as me? I hate you I'm going home
				if(clusterCheckFlags & CLUSTER_CHECK_SAME_ATOMS)
					clustering = rand(clusterMin, clusterMax)
					for(var/atom/movable/M in range(clustering,T))
						if(istype(M,atomPath))
							skipLoopIteration = TRUE
							break
					if(skipLoopIteration)
						skipLoopIteration = FALSE
						continue

				//You're DIFFERENT from me? I hate you I'm going home
				if(clusterCheckFlags & CLUSTER_CHECK_DIFFERENT_ATOMS)
					clustering = rand(clusterMin, clusterMax)
					for(var/atom/movable/M in range(clustering,T))
						if(!(istype(M,atomPath)))
							skipLoopIteration = TRUE
							break
					if(skipLoopIteration)
						skipLoopIteration = FALSE
						continue

			//Success!
			if(prob(spawnableAtoms[atomPath]))
				new atomPath(T)

	. = 1

//Checks and Rejects dense turfs
/datum/mapGeneratorModule/proc/checkPlaceAtom(turf/T)
	. = 1
	if(!T)
		return 0
	if(T.density)
		. = 0
	for(var/atom/A in T)
		if(A.density)
			. = 0
			break
	if(!allowAtomsOnSpace && (istype(T,/turf/open/space)))
		. = 0

///////////////////////////////////////////////////////////
//                 PREMADE BASE TEMPLATES                //
//           Appropriate settings for usable types       //
// Not usable types themselves, use them as parent types //
// Seriously, don't use these on their own, just parents //
///////////////////////////////////////////////////////////
//The /atom and /turf examples are just so these compile, replace those with your typepaths in your subtypes.

//Settings appropriate for a turf that covers the entire map region, eg a fill colour on a bottom layer in a graphics program.
//Should only have one of these in your mapGenerator unless you want to waste CPU
/datum/mapGeneratorModule/bottomLayer
	clusterCheckFlags = CLUSTER_CHECK_NONE
	spawnableAtoms = list()//Recommended: No atoms.
	spawnableTurfs = list(/turf = 100)

//Settings appropriate for turfs/atoms that cover SOME of the map region, sometimes referred to as a splatter layer.
/datum/mapGeneratorModule/splatterLayer
	clusterCheckFlags = CLUSTER_CHECK_ALL
	spawnableAtoms = list(/atom = 30)
	spawnableTurfs = list(/turf = 30)

//Settings appropriate for turfs/atoms that cover a lot of the map region, eg a dense forest.
/datum/mapGeneratorModule/denseLayer
	clusterCheckFlags = CLUSTER_CHECK_NONE
	spawnableAtoms = list(/atom = 75)
	spawnableTurfs = list(/turf = 75)


code:
//Helper Modules

// Helper to repressurize the area in case it was run in space
/datum/mapGeneratorModule/bottomLayer/repressurize
	spawnableAtoms = list()
	spawnableTurfs = list()

/datum/mapGeneratorModule/bottomLayer/repressurize/generate()
	if(!mother)
		return
	var/list/map = mother.map
	for(var/turf/T in map)
		SSair.remove_from_active(T)
	for(var/turf/open/T in map)
		if(T.air)
			T.air.copy_from_turf(T)
		SSair.add_to_active(T)

//Only places atoms/turfs on area borders
/datum/mapGeneratorModule/border
	clusterCheckFlags = CLUSTER_CHECK_NONE

/datum/mapGeneratorModule/border/generate()
	if(!mother)
		return
	var/list/map = mother.map
	for(var/turf/T in map)
		if(is_border(T))
			place(T)

/datum/mapGeneratorModule/border/proc/is_border(turf/T)
	for(var/direction in list(SOUTH,EAST,WEST,NORTH))
		if (get_step(T,direction) in mother.map)
			continue
		return 1
	return 0

/datum/mapGenerator/repressurize
	modules = list(/datum/mapGeneratorModule/bottomLayer/repressurize)


code:
//Contents exist primarily for the nature generator test type.

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

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

//Random assortment of bushes
/datum/mapGeneratorModule/randBushes
	spawnableAtoms = list()

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

//Random assortment of rocks and rockpiles
/datum/mapGeneratorModule/randRocks
	spawnableAtoms = list(/obj/structure/flora/rock = 40, /obj/structure/flora/rock/pile = 20)

//Grass turfs
/datum/mapGeneratorModule/bottomLayer/grassTurfs
	spawnableTurfs = list(/turf/open/floor/grass = 100)

//Grass tufts with a high spawn chance
/datum/mapGeneratorModule/denseLayer/grassTufts
	spawnableTurfs = list()
	spawnableAtoms = list(/obj/structure/flora/ausbushes/grassybush = 75)


code:
/obj/effect/landmark/mapGenerator
	var/startTurfX = 0
	var/startTurfY = 0
	var/startTurfZ = -1
	var/endTurfX = 0
	var/endTurfY = 0
	var/endTurfZ = -1
	var/mapGeneratorType = /datum/mapGenerator/nature
	var/datum/mapGenerator/mapGenerator

/obj/effect/landmark/mapGenerator/New()
	..()
	if(startTurfZ < 0)
		startTurfZ = z
	if(endTurfZ < 0)
		endTurfZ = z
	mapGenerator = new mapGeneratorType()
	mapGenerator.defineRegion(locate(startTurfX,startTurfY,startTurfZ), locate(endTurfX,endTurfY,endTurfZ))
	mapGenerator.generate()


code:
/*
by RemieRichards

//////////////////////////////
// CODER INFORMATIVE README //
//////////////////////////////
(See below for Mapper Friendly Readme)

mapGenerator:
	Desc: a mapGenerator is a master datum that collects
	and syncs all mapGeneratorModules in it's modules list

	defineRegion(var/turf/Start, var/turf/End, var/replace = 0)
		Example: defineRegion(locate(1,1,1),locate(5,5,5),0)
		Desc: Sets the bounds of the mapGenerator's "map"

	defineCircularRegion(var/turf/Start, var/turf/End, var/replace = 0)
		Example: defineCircularRegion(locate(1,1,1),locate(5,5,5),0)
		Desc: Sets the mapGenerator's "map" as a circle, with center in the middle of Start and End's X,Y,Z coordinates

	undefineRegion()
		Example: undefineRegion()
		Desc: Empties the map generator list

	checkRegion(var/turf/Start, var/turf/End)
		Example: checkRegion(locate(1,1,1), locate(5,5,5))
		Desc: Checks if a rectangle between Start's coords and End's coords is valid
		Existing Calls: mapGenerator/defineRegion(), mapGenerator/defineCircularRegion()

	generate()
		Example: generate()
		Desc: Orders all mapGeneratorModules in the modules list to generate()

	generateOneTurf(var/turf/T)
		Example: generateOneTurf(locate(1,1,1))
		Desc: Orders all mapGeneratorModules in the modules list to place(T) on this turf

	initialiseModules()
		Example: initialiseModules()
		Desc: Replaces all typepaths in the modules list with actual /datum/mapGenerator/Module types
		Existing Calls: mapGenerator/New()

	syncModules()
		Example: syncModules()
		Desc: Sets the Mother variable on all mapGeneratorModules in the modules list to this mapGenerator
		Existing Calls: initialiseModules(),generate(),generateOneTurf()

mapGeneratorModule
	Desc: a mapGeneratorModule has spawnableAtoms and spawnableTurfs lists
	which it will generate on turfs in it's mother's map based on cluster variables

	sync(var/datum/mapGenerator/mum)
		Example: sync(a_mapGenerator_as_a_variable)
		Desc: Sets the Mother variable to the mum argument
		Existing Calls: mapGenerator/syncModules()

	generate()
		Example: generate()
		Desc: Calls place(T) on all turfs in it's mother's map
		Existing Calls: mapGenerator/generate()

	place(var/turf/T)
		Example: place(locate(1,1,1))
		Desc: Run this mapGeneratorModule's effects on this turf (Spawning atoms, Changing turfs)
		Existing Calls: mapGenerator/generate(), mapGenerator/generateOneTurf()

	checkPlaceAtom(var/turf/T)
		Example: checkPlace(locate(1,1,1))
		Desc: Checks if the turf is valid for placing atoms
		Existing Calls: place()

////////////////////////////
// MAPPER FRIENDLY README //
////////////////////////////

Simple Workflow:

	1. Define a/some mapGeneratorModule(s) to your liking, choosing atoms and turfs to spawn
	 #Note: I chose to split Turfs and Atoms off into seperate modules, but this is NOT required.
	 #Note: A mapGeneratorModule may have turfs AND atoms, so long as each is in it's appropriate list

	2. Define a mapGenerator type who's modules list contains the typepath(s) of all the module(s) you wish to use
	 #Note: The order of the typepaths in the modules list is the order they will happen in, this is important for clusterCheckFlags.

	3. Take notes of the Bottom Left and Top Right turfs of your rectangular "map"'s coordinates
	 #Note: X,Y AND Z, Yes you can created 3D "maps" by having differing Z coords

	4. Create the mapGenerator type you created

	5. Call yourMapGeneratorType.defineRegion(locate(X,Y,Z), locate(X,Y,Z))
	 #Note: The above X/Y/Zs are the coordinates of the start and end turfs, the locate() simply finds the turf for the code

	6. Call yourMapGeneratorType.generate(), this will cause all the modules in the generator to build within the map bounds

Option Suggestions:

	* Have seperate modules for Turfs and Atoms, this is not enforced, but it is how I have structured my nature example.
	* If your map doesn't look quite to your liking, simply jiggle with the variables on your modules and the type probabilities
	* You can mix and map premade areas with the procedural generation, for example mapping an entire flat land but having code generate just the grass tufts

Using the Modules list

	Simply think of it like each module is a layer in a graphics editing program!
	To help you do this templates such as /mapGeneratorModule/bottomLayer have been provided with appropriate default settings.
	These are located near the bottom of mapGeneratorModule.dm
	you would order your list left to right, top to bottom, e.g:
	modules = list(bottomLayer,nextLayer,nextNextLayer) etc.

Variable Breakdown (For Mappers):

	mapGenerator
		map - INTERNAL, do not touch
		modules - A list of typepaths of mapGeneratorModules

	mapGeneratorModule
		mother - INTERNAL, do not touch
		spawnableAtoms - A list of typepaths and their probability to spawn, eg: spawnableAtoms = list(/obj/structure/flora/tree/pine = 30)
		spawnableTurfs - A list of typepaths and their probability to spawn, eg: spawnableTurfs = list(/turf/unsimulated/floor/grass = 100)
		clusterMax - The max range to check for something being "too close" for this atom/turf to spawn, the true value is random between clusterMin and clusterMax
		clusterMin - The min range to check for something being "too close" for this atom/turf to spawn, the true value is random between clusterMin and clusterMax
		clusterCheckFlags - A Bitfield that controls how the cluster checks work, All based on clusterMin and clusterMax guides
		allowAtomsOnSpace - A Boolean for if we allow atoms to spawn on space tiles

		clusterCheckFlags flags:
			CLUSTER_CHECK_NONE	0 			   //No checks are done, cluster as much as possible
			CLUSTER_CHECK_DIFFERENT_TURFS	2  //Don't let turfs of DIFFERENT types cluster
			CLUSTER_CHECK_DIFFERENT_ATOMS	4  //Don't let atoms of DIFFERENT types cluster
			CLUSTER_CHECK_SAME_TURFS		8  //Don't let turfs of the SAME type cluster
			CLUSTER_CHECK_SAME_ATOMS		16 //Don't let atoms of the SAME type cluster

			CLUSTER_CHECK_SAMES				24 //Don't let any of the same type cluster
			CLUSTER_CHECK_DIFFERENTS		6  //Don't let any different types cluster
			CLUSTER_CHECK_ALL_TURFS			10 //Don't let ANY turfs cluster same and different types
			CLUSTER_CHECK_ALL_ATOMS			20 //Don't let ANY atoms cluster same and different types

			CLUSTER_CHECK_ALL				30 //Don't let anything cluster, like, at all

*/


code:
//Asteroid turfs
/datum/mapGeneratorModule/bottomLayer/asteroidTurfs
	spawnableTurfs = list(/turf/open/floor/plating/asteroid = 100)

/datum/mapGeneratorModule/bottomLayer/asteroidWalls
	spawnableTurfs = list(/turf/closed/mineral = 100)

//Border walls
/datum/mapGeneratorModule/border/asteroidWalls
	spawnableAtoms = list()
	spawnableTurfs = list(/turf/closed/mineral = 100)

//Random walls
/datum/mapGeneratorModule/splatterLayer/asteroidWalls
	clusterCheckFlags = CLUSTER_CHECK_NONE
	spawnableAtoms = list()
	spawnableTurfs = list(/turf/closed/mineral = 30)

//Monsters
/datum/mapGeneratorModule/splatterLayer/asteroidMonsters
	spawnableTurfs = list()
	spawnableAtoms = list(/mob/living/simple_animal/hostile/asteroid/basilisk = 10, \
		/mob/living/simple_animal/hostile/asteroid/hivelord = 10, \
		/mob/living/simple_animal/hostile/asteroid/goliath = 10)

// GENERATORS

/datum/mapGenerator/asteroid/hollow
	modules = list(/datum/mapGeneratorModule/bottomLayer/asteroidTurfs, \
		/datum/mapGeneratorModule/border/asteroidWalls)

/datum/mapGenerator/asteroid/hollow/random
	modules = list(/datum/mapGeneratorModule/bottomLayer/asteroidTurfs, \
		/datum/mapGeneratorModule/border/asteroidWalls, \
		/datum/mapGeneratorModule/splatterLayer/asteroidWalls)

/datum/mapGenerator/asteroid/hollow/random/monsters
	modules = list(/datum/mapGeneratorModule/bottomLayer/asteroidTurfs, \
		/datum/mapGeneratorModule/border/asteroidWalls, \
		/datum/mapGeneratorModule/splatterLayer/asteroidWalls, \
		/datum/mapGeneratorModule/splatterLayer/asteroidMonsters)

/datum/mapGenerator/asteroid/filled
	modules = list(/datum/mapGeneratorModule/bottomLayer/asteroidWalls)


code:
// Very Simple Cellular Automata generators
// Mostly for better caves
// Should probably move these up and refactor modules so these can be mixed with other ones

/datum/mapGenerator/ca
	var/list/b_rule = list() // 0 -> 1, 1 -> 1
	var/list/s_rule = list() // 0 -> 0, 1 -> 1
	var/iterations = 1
	var/loop_edges = 0
	var/edge_value = 1 //if loop_edges = 0
	var/list/old_state
	var/list/current_state
	var/width = 10
	var/height = 10
	var/list/type_map = list(/turf/open/floor/plating,/turf/closed/wall)
	var/turf/start = null

/datum/mapGenerator/ca/defineRegion(turf/Start, turf/End, replace = 0)
	. = ..()

	var/min_x = min(Start.x,End.x)
	var/max_x = max(Start.x,End.x)
	var/min_y = min(Start.y,End.y)
	var/max_y = max(Start.y,End.y)
	width = max_x - min_x
	height = max_y - min_y

	//We assume 2D everywhere anyway
	start = locate(min_x,min_y,Start.z)

/datum/mapGenerator/ca/proc/initialize()
	old_state = new/list(width)
	for(var/i = 1,i<=width,i++)
		old_state[i] = new/list(height)
		for(var/j = 1,j<=height,j++)
			old_state[i][j] = rand(0,1)

	current_state = old_state.Copy()

/datum/mapGenerator/ca/generate()
	set background = 1

	//Abandon all hope for efficency all who enter here
	//Maybe some less basic implemetation later, but this is just simple admin tool
	initialize()

	for(var/generation = 0,generation<iterations,generation++)
		for(var/i = 1,i<=width,i++)
			for(var/j = 1,j<=height,j++)
				current_state[i][j] = apply_rule(i,j)
		//copy state over
		old_state = current_state.Copy()

	for(var/i=1,i<=width,i++)
		for(var/j=1,j<=height,j++)
			var/turf/T = locate(start.x+i-1,start.y+j-1,start.z)
			if(T)
				T.ChangeTurf(type_map[current_state[i][j]+1])

/datum/mapGenerator/ca/proc/apply_rule(i,j)
	var/value = 0
	for(var/dx=-1,dx<=1,dx++)
		for(var/dy=-1,dy<=1,dy++)
			var/n_x = i+dx
			var/n_y = j+dy
			if(n_x < 1 || n_x > width || n_y <1 || n_y > height)
				if(loop_edges)
					if(n_x < 1)
						n_x = width
					else if(n_x > width)
						n_x = 1
					if(n_y < 1)
						n_y = height
					else if(n_y > height)
						n_y = 1
				else
					value += edge_value
					continue
			value += old_state[n_x][n_y]
	value -= old_state[i][j]

	if(value in b_rule)
		return 1
	if(value in s_rule)
		return old_state[i][j]
	return 0

/datum/mapGenerator/ca/caves
	b_rule = list(5,6,7,8)
	s_rule = list(4)
	type_map = list(/turf/open/floor/plating/asteroid/basalt,/turf/closed/mineral/volcanic)
	iterations = 5

/datum/mapGenerator/ca/maze
	b_rule = list(3)
	s_rule = list(1,2,3,4,5)
	iterations = 20
	edge_value = 0


code:
/datum/mapGenerator/lavaland
	var/start_z = 5
	modules = list(/datum/mapGeneratorModule/river)

/datum/mapGenerator/lavaland/defineRegion(turf/Start, turf/End, replace = 0)
	start_z = Start.z
	..()

/datum/mapGeneratorModule/river
	var/river_type = /turf/open/floor/plating/lava/smooth
	var/river_nodes = 4
	var/start_z = 5

/datum/mapGeneratorModule/river/generate()
	if(istype(mother, /datum/mapGenerator/lavaland))
		var/datum/mapGenerator/lavaland/L = mother
		start_z = L.start_z
	spawn_rivers(start_z, river_nodes, river_type)


code:
//Exists primarily as a test type.

/datum/mapGenerator/nature
	modules = list(/datum/mapGeneratorModule/bottomLayer/grassTurfs, \
	/datum/mapGeneratorModule/pineTrees, \
	/datum/mapGeneratorModule/deadTrees, \
	/datum/mapGeneratorModule/randBushes, \
	/datum/mapGeneratorModule/randRocks, \
	/datum/mapGeneratorModule/denseLayer/grassTufts)


code:
/datum/mapGeneratorModule/bottomLayer/shuttleFloor
	spawnableTurfs = list(/turf/open/floor/plasteel/shuttle = 100)

/datum/mapGeneratorModule/border/shuttleWalls
	spawnableAtoms = list()
	spawnableTurfs = list(/turf/closed/wall/shuttle = 100)
// Generators

/datum/mapGenerator/shuttle/full
		modules = list(/datum/mapGeneratorModule/bottomLayer/shuttleFloor, \
		/datum/mapGeneratorModule/border/shuttleWalls,\
		/datum/mapGeneratorModule/bottomLayer/repressurize)

/datum/mapGenerator/shuttle/floor
	modules = list(/datum/mapGeneratorModule/bottomLayer/shuttleFloor)


code:
// Modules

/turf/open/floor/plasteel/shuttle/red/syndicate
	name = "floor" //Not Brig Floor

/datum/mapGeneratorModule/bottomLayer/syndieFloor
	spawnableTurfs = list(/turf/open/floor/plasteel/shuttle/red/syndicate = 100)

/datum/mapGeneratorModule/border/syndieWalls
	spawnableAtoms = list()
	spawnableTurfs = list(/turf/closed/wall/r_wall = 100)

/datum/mapGeneratorModule/syndieFurniture
	clusterCheckFlags = CLUSTER_CHECK_ALL
	spawnableTurfs = list()
	spawnableAtoms = list(/obj/structure/table = 20,/obj/structure/chair = 15,/obj/structure/chair/stool = 10, \
		/obj/structure/computerframe = 15, /obj/item/weapon/storage/toolbox/syndicate = 15 ,\
		/obj/structure/closet/syndicate = 25, /obj/machinery/suit_storage_unit/syndicate = 15)

/datum/mapGeneratorModule/splatterLayer/syndieMobs
	spawnableAtoms = list(/mob/living/simple_animal/hostile/syndicate = 30, \
		/mob/living/simple_animal/hostile/syndicate/melee = 20, \
		/mob/living/simple_animal/hostile/syndicate/ranged = 20, \
		/mob/living/simple_animal/hostile/viscerator = 30)
	spawnableTurfs = list()

// Generators

/datum/mapGenerator/syndicate/empty //walls and floor only
		modules = list(/datum/mapGeneratorModule/bottomLayer/syndieFloor, \
		/datum/mapGeneratorModule/border/syndieWalls,\
		/datum/mapGeneratorModule/bottomLayer/repressurize)

/datum/mapGenerator/syndicate/mobsonly
	modules = list(/datum/mapGeneratorModule/bottomLayer/syndieFloor, \
		/datum/mapGeneratorModule/border/syndieWalls,\
		/datum/mapGeneratorModule/splatterLayer/syndieMobs, \
		/datum/mapGeneratorModule/bottomLayer/repressurize)

/datum/mapGenerator/syndicate/furniture
	modules = list(/datum/mapGeneratorModule/bottomLayer/syndieFloor, \
		/datum/mapGeneratorModule/border/syndieWalls,\
		/datum/mapGeneratorModule/syndieFurniture, \
		/datum/mapGeneratorModule/bottomLayer/repressurize)

/datum/mapGenerator/syndicate/full
	modules = list(/datum/mapGeneratorModule/bottomLayer/syndieFloor, \
		/datum/mapGeneratorModule/border/syndieWalls,\
		/datum/mapGeneratorModule/syndieFurniture, \
		/datum/mapGeneratorModule/splatterLayer/syndieMobs, \
		/datum/mapGeneratorModule/bottomLayer/repressurize)


code:
/obj/item/ammo_casing
	name = "bullet casing"
	desc = "A bullet casing."
	icon = 'icons/obj/ammo.dmi'
	icon_state = "s-casing"
	flags = CONDUCT
	slot_flags = SLOT_BELT
	throwforce = 0
	w_class = 1
	var/fire_sound = null						//What sound should play when this ammo is fired
	var/caliber = null							//Which kind of guns it can be loaded into
	var/projectile_type = null					//The bullet type to create when New() is called
	var/obj/item/projectile/BB = null 			//The loaded bullet
	var/pellets = 0								//Pellets for spreadshot
	var/variance = 0							//Variance for inaccuracy fundamental to the casing
	var/delay = 0								//Delay for energy weapons

/obj/item/ammo_casing/New()
	..()
	if(projectile_type)
		BB = new projectile_type(src)
	pixel_x = rand(-10, 10)
	pixel_y = rand(-10, 10)
	dir = pick(alldirs)
	update_icon()

/obj/item/ammo_casing/update_icon()
	..()
	icon_state = "[initial(icon_state)][BB ? "-live" : ""]"
	desc = "[initial(desc)][BB ? "" : " This one is spent"]"

/obj/item/ammo_casing/proc/newshot() //For energy weapons, shotgun shells and wands (!).
	if (!BB)
		BB = new projectile_type(src)
	return

/obj/item/ammo_casing/attackby(obj/item/I, mob/user, params)
	if(istype(I, /obj/item/ammo_box))
		var/obj/item/ammo_box/box = I
		if(isturf(loc))
			var/boolets = 0
			for(var/obj/item/ammo_casing/bullet in loc)
				if (box.stored_ammo.len >= box.max_ammo)
					break
				if (bullet.BB)
					if (box.give_round(bullet, 0))
						boolets++
				else
					continue
			if (boolets > 0)
				box.update_icon()
				user << "You collect [boolets] shell\s. [box] now contains [box.stored_ammo.len] shell\s."
			else
				user << "You fail to collect anything!"
	else
		..()

//Boxes of ammo
/obj/item/ammo_box
	name = "ammo box (null_reference_exception)"
	desc = "A box of ammo."
	icon_state = "357"
	icon = 'icons/obj/ammo.dmi'
	flags = CONDUCT
	slot_flags = SLOT_BELT
	item_state = "syringe_kit"
	materials = list(MAT_METAL=30000)
	throwforce = 2
	w_class = 1
	throw_speed = 3
	throw_range = 7
	var/list/stored_ammo = list()
	var/ammo_type = /obj/item/ammo_casing
	var/max_ammo = 7
	var/multiple_sprites = 0
	var/caliber
	var/multiload = 1

/obj/item/ammo_box/New()
	for(var/i = 1, i <= max_ammo, i++)
		stored_ammo += new ammo_type(src)
	update_icon()

/obj/item/ammo_box/proc/get_round(keep = 0)
	if (!stored_ammo.len)
		return null
	else
		var/b = stored_ammo[stored_ammo.len]
		stored_ammo -= b
		if (keep)
			stored_ammo.Insert(1,b)
		return b

/obj/item/ammo_box/proc/give_round(obj/item/ammo_casing/R, replace_spent = 0)
	// Boxes don't have a caliber type, magazines do. Not sure if it's intended or not, but if we fail to find a caliber, then we fall back to ammo_type.
	if(!R || (caliber && R.caliber != caliber) || (!caliber && R.type != ammo_type))
		return 0

	if (stored_ammo.len < max_ammo)
		stored_ammo += R
		R.loc = src
		return 1

	//for accessibles magazines (e.g internal ones) when full, start replacing spent ammo
	else if(replace_spent)
		for(var/obj/item/ammo_casing/AC in stored_ammo)
			if(!AC.BB)//found a spent ammo
				stored_ammo -= AC
				AC.loc = get_turf(src.loc)

				stored_ammo += R
				R.loc = src
				return 1

	return 0

/obj/item/ammo_box/proc/can_load(mob/user)
	return 1

/obj/item/ammo_box/attackby(obj/item/A, mob/user, params, silent = 0, replace_spent = 0)
	var/num_loaded = 0
	if(!can_load(user))
		return
	if(istype(A, /obj/item/ammo_box))
		var/obj/item/ammo_box/AM = A
		for(var/obj/item/ammo_casing/AC in AM.stored_ammo)
			var/did_load = give_round(AC, replace_spent)
			if(did_load)
				AM.stored_ammo -= AC
				num_loaded++
			if(!did_load || !multiload)
				break
	if(istype(A, /obj/item/ammo_casing))
		var/obj/item/ammo_casing/AC = A
		if(give_round(AC, replace_spent))
			user.drop_item()
			AC.loc = src
			num_loaded++

	if(num_loaded)
		if(!silent)
			user << "You load [num_loaded] shell\s into \the [src]!"
		A.update_icon()
		update_icon()

	return num_loaded

/obj/item/ammo_box/attack_self(mob/user)
	var/obj/item/ammo_casing/A = get_round()
	if(A)
		user.put_in_hands(A)
		user << "You remove a round from \the [src]!"
		update_icon()

/obj/item/ammo_box/update_icon()
	switch(multiple_sprites)
		if(1)
			icon_state = "[initial(icon_state)]-[stored_ammo.len]"
		if(2)
			icon_state = "[initial(icon_state)]-[stored_ammo.len ? "[max_ammo]" : "0"]"
	desc = "[initial(desc)] There are [stored_ammo.len] shell\s left!"

//Behavior for magazines
/obj/item/ammo_box/magazine/proc/ammo_count()
	return stored_ammo.len