code:
/* In this file:
 *
 * Plating
 * Airless
 * Airless plating
 * Engine floor
 */
// note that plating and engine floor do not call their parent attackby, unlike other flooring
// this is done in order to avoid inheriting the crowbar attackby

/turf/simulated/floor/plating
	name = "plating"
	icon_state = "plating"
	intact = 0
	cancable = 1
	broken_states = list("platingdmg1", "platingdmg2", "platingdmg3")
	burnt_states = list("panelscorched")

/turf/simulated/floor/plating/New()
	..()
	icon_plating = icon_state

/turf/simulated/floor/plating/update_icon()
	if(!..())
		return
	if(!broken && !burnt)
		icon_state = icon_plating //Because asteroids are 'platings' too.

/turf/simulated/floor/plating/attackby(obj/item/C as obj, mob/user as mob, params)
	if(..())
		return
	if(istype(C, /obj/item/stack/rods))
		if(broken || burnt)
			user << "Repair the plating first."
			return
		var/obj/item/stack/rods/R = C
		if (R.get_amount() < 2)
			user << "You need two rods to make a reinforced floor."
			return
		else
			user << "Reinforcing the floor..."
			if(do_after(user, 30))
				if (R.get_amount() >= 2)
					ChangeTurf(/turf/simulated/floor/engine)
					playsound(src, 'sound/items/Deconstruct.ogg', 80, 1)
					R.use(2)
					user << "You have reinforced the floor."
				return
	else if(istype(C, /obj/item/stack/tile))
		if(!broken && !burnt)
			var/obj/item/stack/tile/W = C
			var/turf/simulated/floor/T = ChangeTurf(W.turf_type)
			if(istype(W,/obj/item/stack/tile/light)) //TODO: get rid of this ugly check somehow
				var/obj/item/stack/tile/light/L = W
				var/turf/simulated/floor/light/F = T
				F.state = L.state
			W.use(1)
			playsound(src, 'sound/weapons/Genhit.ogg', 50, 1)
		else
			user << "This section is too damaged to support a tile. Use a welder to fix the damage."
	else if(istype(C, /obj/item/weapon/weldingtool))
		var/obj/item/weapon/weldingtool/welder = C
		if( welder.isOn() && (broken || burnt) )
			if(welder.remove_fuel(0,user))
				user << "You fix some dents on the broken plating."
				playsound(src, 'sound/items/Welder.ogg', 80, 1)
				icon_state = icon_plating
				burnt = 0
				broken = 0

/turf/simulated/floor/plating/airless
	icon_state = "plating"
	oxygen = 0
	nitrogen = 0
	temperature = TCMB

/turf/simulated/floor/engine
	name = "reinforced floor"
	icon_state = "engine"
	thermal_conductivity = 0.025
	heat_capacity = 325000
	floor_tile = /obj/item/stack/rods

/turf/simulated/floor/engine/break_tile()
	return //unbreakable

/turf/simulated/floor/engine/burn_tile()
	return //unburnable

/turf/simulated/floor/engine/make_plating(var/force = 0)
	if(force)
		..()
	return //unplateable

/turf/simulated/floor/engine/attackby(obj/item/weapon/C as obj, mob/user as mob, params)
	if(!C || !user)
		return
	if(istype(C, /obj/item/weapon/wrench))
		user << "Removing rods..."
		playsound(src, 'sound/items/Ratchet.ogg', 80, 1)
		if(do_after(user, 30))
			new /obj/item/stack/rods(src, 2)
			ChangeTurf(/turf/simulated/floor/plating)
			return

/turf/simulated/floor/engine/ex_act(severity,target)
	switch(severity)
		if(1.0)
			if(prob(80))
				ReplaceWithLattice()
			else if(prob(50))
				qdel(src)
			else
				make_plating(1)
		if(2.0)
			if(prob(50))
				make_plating(1)

/turf/simulated/floor/engine/cult
	name = "engraved floor"
	icon_state = "cult"

/turf/simulated/floor/engine/cult/narsie_act()
	return

/turf/simulated/floor/engine/n20/New()
	..()
	var/datum/gas_mixture/adding = new
	var/datum/gas/sleeping_agent/trace_gas = new

	trace_gas.moles = 6000
	adding.trace_gases += trace_gas
	adding.temperature = T20C

	assume_air(adding)

/turf/simulated/floor/engine/singularity_pull(S, current_size)
	if(current_size >= STAGE_FIVE)
		if(builtin_tile)
			if(prob(30))
				builtin_tile.loc = src
				make_plating()
		else if(prob(30))
			ReplaceWithLattice()

/turf/simulated/floor/engine/vacuum
	name = "vacuum floor"
	icon_state = "engine"
	oxygen = 0
	nitrogen = 0
	temperature = TCMB

/turf/simulated/floor/plasteel/airless
	oxygen = 0
	nitrogen = 0
	temperature = TCMB

/turf/simulated/floor/plating/lava
	icon_state = "lava"

/turf/simulated/floor/plating/abductor
	name = "alien floor"
	icon_state = "alienpod1"
	shuttle = 1

/turf/simulated/floor/plating/abductor/New()
	..()
	icon_state = "alienpod[rand(1,9)]"

/turf/simulated/floor/plating/shuttle
	shuttle = 1


code:
/turf/simulated/wall
	name = "wall"
	desc = "A huge chunk of metal used to separate rooms."
	icon = 'icons/turf/walls.dmi'
	var/mineral = "metal"
	opacity = 1
	density = 1
	blocks_air = 1
	explosion_block = 1

	thermal_conductivity = WALL_HEAT_TRANSFER_COEFFICIENT
	heat_capacity = 312500 //a little over 5 cm thick , 312500 for 1 m by 2.5 m by 0.25 m plasteel wall

	var/walltype = "metal"
	var/hardness = 40 //lower numbers are harder. Used to determine the probability of a hulk smashing through.
	var/slicing_duration = 100  //default time taken to slice the wall
	var/sheet_type = /obj/item/stack/sheet/metal
	var/obj/item/stack/sheet/builtin_sheet = null

	var/del_suppress_resmoothing = 0 // Do not resmooth neighbors on Destroy. (smoothwall.dm)
	canSmoothWith = list(
	/turf/simulated/wall,
	/obj/structure/falsewall,
	/obj/structure/falsewall/reinforced  // WHY DO WE SMOOTH WITH FALSE R-WALLS WHEN WE DON'T SMOOTH WITH REAL R-WALLS.
	)

/turf/simulated/wall/New()
	..()
	if (sheet_type)
		builtin_sheet = new sheet_type

/turf/simulated/wall/attack_tk()
	return

/turf/simulated/wall/proc/dismantle_wall(devastated=0, explode=0)
	if(devastated)
		devastate_wall()
	else
		playsound(src, 'sound/items/Welder.ogg', 100, 1)
		var/newgirder = break_wall()
		transfer_fingerprints_to(newgirder)

	for(var/obj/O in src.contents) //Eject contents!
		if(istype(O,/obj/structure/sign/poster))
			var/obj/structure/sign/poster/P = O
			P.roll_and_drop(src)
		else
			O.loc = src

	ChangeTurf(/turf/simulated/floor/plating)

/turf/simulated/wall/proc/break_wall()
		builtin_sheet.amount = 2
		builtin_sheet.loc = src
		return (new /obj/structure/girder(src))

/turf/simulated/wall/proc/devastate_wall()
		builtin_sheet.amount = 2
		builtin_sheet.loc = src
		new /obj/item/stack/sheet/metal(src)

/turf/simulated/wall/ex_act(severity, target)
	if(target == src)
		dismantle_wall(1,1)
		return
	switch(severity)
		if(1.0)
			//SN src = null
			src.ChangeTurf(src.baseturf)
			return
		if(2.0)
			if (prob(50))
				dismantle_wall(0,1)
			else
				dismantle_wall(1,1)
		if(3.0)
			if (prob(hardness))
				dismantle_wall(0,1)
			else
	if(!density)
		..()
	return

/turf/simulated/wall/blob_act()
	if(prob(50))
		dismantle_wall()

/turf/simulated/wall/mech_melee_attack(obj/mecha/M)
	if(M.damtype == "brute")
		playsound(src, 'sound/weapons/punch4.ogg', 50, 1)
		visible_message("[M.name] has hit [src]!")
		if(prob(5) && M.force > 20)
			dismantle_wall(1)
			visible_message("[src.name] smashes through the wall!")
			playsound(src, 'sound/effects/meteorimpact.ogg', 100, 1)

/turf/simulated/wall/attack_paw(mob/living/user as mob)
	user.changeNext_move(CLICK_CD_MELEE)
	return src.attack_hand(user)

/turf/simulated/wall/attack_animal(var/mob/living/simple_animal/M)
	M.changeNext_move(CLICK_CD_MELEE)
	M.do_attack_animation(src)
	if(M.environment_smash >= 2)
		playsound(src, 'sound/effects/meteorimpact.ogg', 100, 1)
		M << "You smash through the wall."
		dismantle_wall(1)
		return

/turf/simulated/wall/attack_hulk(mob/user)
	..(user, 1)
	if(prob(hardness))
		playsound(src, 'sound/effects/meteorimpact.ogg', 100, 1)
		user << text("You smash through the wall.")
		user.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ))
		dismantle_wall(1)

	else
		playsound(src, 'sound/effects/bang.ogg', 50, 1)
		user << text("You punch the wall.")
	return 1

/turf/simulated/wall/attack_hand(mob/user as mob)
	user.changeNext_move(CLICK_CD_MELEE)
	user << "You push the wall but nothing happens!"
	playsound(src, 'sound/weapons/Genhit.ogg', 25, 1)
	src.add_fingerprint(user)
	..()
	return

/turf/simulated/wall/attackby(obj/item/weapon/W as obj, mob/user as mob, params)
	user.changeNext_move(CLICK_CD_MELEE)
	if (!user.IsAdvancedToolUser())
		user << "You don't have the dexterity to do this!"
		return

	//get the user's location
	if( !istype(user.loc, /turf) )	return	//can't do this stuff whilst inside objects and such

	add_fingerprint(user)

	//THERMITE related stuff. Calls src.thermitemelt() which handles melting simulated walls and the relevant effects
	if( thermite )
		if(is_hot(W))
			thermitemelt(user)
		return

	var/turf/T = user.loc	//get user's location for delay checks

	//the istype cascade has been spread among various procs for easy overriding
	if(try_wallmount(W,user,T) || try_decon(W,user,T) || try_destroy(W,user,T))
		return

	return attack_hand(user)

/turf/simulated/wall/proc/try_wallmount(obj/item/weapon/W as obj, mob/user as mob, turf/T as turf)
	//check for wall mounted frames
	if(istype(W,/obj/item/apc_frame))
		var/obj/item/apc_frame/AH = W
		AH.try_build(src)
		return 1
	else if(istype(W,/obj/item/newscaster_frame))
		var/obj/item/newscaster_frame/AH = W
		AH.try_build(src)
		return 1
	else if(istype(W,/obj/item/alarm_frame))
		var/obj/item/alarm_frame/AH = W
		AH.try_build(src)
		return 1
	else if(istype(W,/obj/item/firealarm_frame))
		var/obj/item/firealarm_frame/AH = W
		AH.try_build(src)
		return 1
	else if(istype(W,/obj/item/light_fixture_frame))
		var/obj/item/light_fixture_frame/AH = W
		AH.try_build(src)
		return 1
	//Poster stuff
	else if(istype(W,/obj/item/weapon/contraband/poster))
		place_poster(W,user)
		return 1

	return 0

/turf/simulated/wall/proc/try_decon(obj/item/weapon/W as obj, mob/user as mob, turf/T as turf)
	if( istype(W, /obj/item/weapon/weldingtool) )
		var/obj/item/weapon/weldingtool/WT = W
		if( WT.remove_fuel(0,user) )
			user << "You begin slicing through the outer plating."
			playsound(src, 'sound/items/Welder.ogg', 100, 1)
			if(do_after(user, slicing_duration))
				if( !istype(src, /turf/simulated/wall) || !user || !WT || !WT.isOn() || !T )
					return 1
				if( user.loc == T && user.get_active_hand() == WT )
					user << "You remove the outer plating."
					dismantle_wall()
					return 1
	else if( istype(W, /obj/item/weapon/pickaxe/plasmacutter) )
		user << "You begin slicing through the outer plating."
		playsound(src, 'sound/items/Welder.ogg', 100, 1)
		if(do_after(user, slicing_duration*0.6))  // plasma cutter is faster than welding tool
			if( !istype(src, /turf/simulated/wall) || !user || !W || !T )
				return 1
			if( user.loc == T && user.get_active_hand() == W )
				user << "You remove the outer plating."
				dismantle_wall()
				visible_message("The wall was sliced apart by [user]!", "You hear metal being sliced apart.")
				return 1
	return 0

/turf/simulated/wall/proc/try_destroy(obj/item/weapon/W as obj, mob/user as mob, turf/T as turf)
	if(istype(W, /obj/item/weapon/pickaxe/drill/jackhammer) || istype(W, /obj/item/weapon/pickaxe/drill/diamonddrill) )
		var/obj/item/weapon/pickaxe/drill/D = W
		D.update_icon()
		if( !istype(src, /turf/simulated/wall) || !user || !W || !T )
			return 1
		if( user.loc == T && user.get_active_hand() == W )
			D.playDigSound()
			dismantle_wall()
			visible_message("[user] smashes through the [name] with the [W.name]!", "You hear the grinding of metal.")
			return 1
	return 0

/turf/simulated/wall/proc/thermitemelt(mob/user as mob)
	overlays = list()
	var/obj/effect/overlay/O = new/obj/effect/overlay( src )
	O.name = "thermite"
	O.desc = "Looks hot."
	O.icon = 'icons/effects/fire.dmi'
	O.icon_state = "2"
	O.anchored = 1
	O.opacity = 1
	O.density = 1
	O.layer = 5

	playsound(src, 'sound/items/Welder.ogg', 100, 1)

	if(thermite >= 50)
		var/turf/simulated/floor/F = ChangeTurf(/turf/simulated/floor/plating)
		F.burn_tile()
		F.icon_state = "wall_thermite"
		F.add_hiddenprint(user)
		spawn(max(100,300-thermite))
			if(O)	qdel(O)
	else
		thermite = 0
		spawn(50)
			if(O)	qdel(O)
	return

/turf/simulated/wall/singularity_pull(S, current_size)
	if(current_size >= STAGE_FIVE)
		if(prob(50))
			dismantle_wall()
		return
	if(current_size == STAGE_FOUR)
		if(prob(30))
			dismantle_wall()

/turf/simulated/wall/narsie_act()
	if(prob(20))
		ChangeTurf(/turf/simulated/wall/cult)

/turf/simulated/wall/melt()
	if(mineral == "diamond")
		return

	src.ChangeTurf(/turf/simulated/floor/plating)

	var/turf/simulated/floor/F = src
	if(!F)
		return
	F.burn_tile()
	F.icon_state = "wall_thermite"
	visible_message("\The [src] spontaenously combusts!.") //!!OH SHIT!!
	return

license: mit


code:
/turf/simulated/wall/mineral
	name = "mineral wall"
	desc = "This shouldn't exist"
	icon_state = ""
	var/last_event = 0
	var/active = null

/turf/simulated/wall/mineral/New()
	sheet_type = text2path("/obj/item/stack/sheet/mineral/[mineral]")
	..()

/turf/simulated/wall/mineral/gold
	name = "gold wall"
	desc = "A wall with gold plating. Swag!"
	icon_state = "gold0"
	walltype = "gold"
	mineral = "gold"
	explosion_block = 0 //gold is a soft metal you dingus.
	//var/electro = 1
	//var/shocked = null

/turf/simulated/wall/mineral/silver
	name = "silver wall"
	desc = "A wall with silver plating. Shiny!"
	icon_state = "silver0"
	walltype = "silver"
	mineral = "silver"
	//var/electro = 0.75
	//var/shocked = null

/turf/simulated/wall/mineral/diamond
	name = "diamond wall"
	desc = "A wall with diamond plating. You monster."
	icon_state = "diamond0"
	walltype = "diamond"
	mineral = "diamond"
	slicing_duration = 200   //diamond wall takes twice as much time to slice
	explosion_block = 3

/turf/simulated/wall/mineral/diamond/thermitemelt(mob/user as mob)
	return

/turf/simulated/wall/mineral/clown
	name = "bananium wall"
	desc = "A wall with bananium plating. Honk!"
	icon_state = "bananium0"
	walltype = "bananium"
	mineral = "bananium"

/turf/simulated/wall/mineral/sandstone
	name = "sandstone wall"
	desc = "A wall with sandstone plating."
	icon_state = "sandstone0"
	walltype = "sandstone"
	mineral = "sandstone"
	explosion_block = 0

/turf/simulated/wall/mineral/uranium
	name = "uranium wall"
	desc = "A wall with uranium plating. This is probably a bad idea."
	icon_state = "uranium0"
	walltype = "uranium"
	mineral = "uranium"

/turf/simulated/wall/mineral/uranium/proc/radiate()
	if(!active)
		if(world.time > last_event+15)
			active = 1
			for(var/mob/living/L in range(3,src))
				L.apply_effect(12,IRRADIATE,0)
			for(var/turf/simulated/wall/mineral/uranium/T in range(3,src))
				T.radiate()
			last_event = world.time
			active = null
			return
	return

/turf/simulated/wall/mineral/uranium/attack_hand(mob/user as mob)
	radiate()
	..()

/turf/simulated/wall/mineral/uranium/attackby(obj/item/weapon/W as obj, mob/user as mob, params)
	radiate()
	..()

/turf/simulated/wall/mineral/uranium/Bumped(AM as mob|obj)
	radiate()
	..()

/turf/simulated/wall/mineral/plasma
	name = "plasma wall"
	desc = "A wall with plasma plating. This is definately a bad idea."
	icon_state = "plasma0"
	walltype = "plasma"
	mineral = "plasma"
	thermal_conductivity = 0.04

/turf/simulated/wall/mineral/plasma/attackby(obj/item/weapon/W as obj, mob/user as mob, params)
	if(is_hot(W) > 300)//If the temperature of the object is over 300, then ignite
		message_admins("Plasma wall ignited by [key_name(user, user.client)](?) in ([x],[y],[z] - JMP)",0,1)
		log_game("Plasma wall ignited by [user.ckey]([user]) in ([x],[y],[z])")
		ignite(is_hot(W))
		return
	..()

/turf/simulated/wall/mineral/plasma/proc/PlasmaBurn(temperature)
	new /obj/structure/girder(src)
	src.ChangeTurf(/turf/simulated/floor/plasteel)
	atmos_spawn_air(SPAWN_HEAT | SPAWN_TOXINS, 400)

/turf/simulated/wall/mineral/plasma/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)//Doesn't fucking work because walls don't interact with air :(
	if(exposed_temperature > 300)
		PlasmaBurn(exposed_temperature)

/turf/simulated/wall/mineral/plasma/proc/ignite(exposed_temperature)
	if(exposed_temperature > 300)
		PlasmaBurn(exposed_temperature)

/turf/simulated/wall/mineral/plasma/bullet_act(var/obj/item/projectile/Proj)
	if(istype(Proj,/obj/item/projectile/beam))
		PlasmaBurn(2500)
	else if(istype(Proj,/obj/item/projectile/ion))
		PlasmaBurn(500)
	..()

/*
/turf/simulated/wall/mineral/proc/shock()
	if (electrocute_mob(user, C, src))
		var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
		s.set_up(5, 1, src)
		s.start()
		return 1
	else
		return 0

/turf/simulated/wall/mineral/proc/attackby(obj/item/weapon/W as obj, mob/user as mob)
	if((mineral == "gold") || (mineral == "silver"))
		if(shocked)
			shock()
*/

/turf/simulated/wall/mineral/wood
	name = "wooden wall"
	desc = "A wall with wooden plating."
	icon_state = "wood0"
	walltype = "wood"
	mineral = "wood"
	hardness = 70
	explosion_block = 0


code:
/turf/simulated/wall/cult
	name = "wall"
	desc = "The patterns engraved on the wall seem to shift as you try to focus on them. You feel sick"
	icon_state = "cult"
	walltype = "cult"
	builtin_sheet = null

/turf/simulated/wall/cult/break_wall()
	new /obj/effect/decal/cleanable/blood(src)
	new /obj/structure/cultgirder(src)

/turf/simulated/wall/cult/devastate_wall()
	new /obj/effect/decal/cleanable/blood(src)
	new /obj/effect/decal/remains/human(src)

/turf/simulated/wall/cult/narsie_act()
	return

/turf/simulated/wall/vault
	icon_state = "rockvault"

/turf/simulated/wall/rust
	name = "rusted wall"
	desc = "A rusted metal wall."
	icon_state = "arust"
	walltype = "arust"
	hardness = 45

/turf/simulated/wall/r_wall/rust
	name = "rusted reinforced wall"
	desc = "A huge chunk of rusted reinforced metal."
	icon_state = "rrust"
	walltype = "rrust"
	hardness = 15

/turf/simulated/wall/shuttle
	name = "wall"
	icon = 'icons/turf/shuttle.dmi'
	icon_state = "wall1"
	walltype = "shuttle"
	shuttle = 1

/turf/simulated/wall/shuttle/relativewall()
	return

//sub-type to be used for interior shuttle walls
//won't get an underlay of the destination turf on shuttle move
/turf/simulated/wall/shuttle/interior/copyTurf(turf/T)
	if(T.type != type)
		T = new type(T)
		if(underlays.len)
			T.underlays = underlays
	if(T.icon_state != icon_state)
		T.icon_state = icon_state
	if(T.lighting_overlay != lighting_overlay)
		T.lighting_overlay = lighting_overlay
		T.contents += lighting_overlay
		T.reconsider_lights()
	if(T.icon != icon)
		T.icon = icon
	if(T.color != color)
		T.color = color
	if(T.dir != dir)
		T.dir = dir
	return T


code:
/turf/simulated/wall/r_wall
	name = "reinforced wall"
	desc = "A huge chunk of reinforced metal used to seperate rooms."
	icon_state = "r_wall"
	opacity = 1
	density = 1
	explosion_block = 2

	walltype = "rwall"

	var/d_state = 0
	hardness = 10
	sheet_type = /obj/item/stack/sheet/plasteel

/turf/simulated/wall/r_wall/break_wall()
	builtin_sheet.loc = src
	return (new /obj/structure/girder/reinforced(src))

/turf/simulated/wall/r_wall/devastate_wall()
	builtin_sheet.loc = src
	new /obj/item/stack/sheet/metal(src, 2)

/turf/simulated/wall/r_wall/attack_animal(var/mob/living/simple_animal/M)
	M.changeNext_move(CLICK_CD_MELEE)
	if(M.environment_smash == 3)
		dismantle_wall(1)
		playsound(src, 'sound/effects/meteorimpact.ogg', 100, 1)
		M << "You smash through the wall."
	else
		M << "This wall is far too strong for you to destroy."

/turf/simulated/wall/r_wall/try_destroy(obj/item/weapon/W as obj, mob/user as mob, turf/T as turf)
	if(istype(W, /obj/item/weapon/pickaxe/drill/diamonddrill))
		var/obj/item/weapon/pickaxe/drill/jackhammer/D = W
		user << "You begin to smash though the [name]."
		if(do_after(user, 50))
			if( !istype(src, /turf/simulated/wall/r_wall) || !user || !W || !T )
				return 1
			if( user.loc == T && user.get_active_hand() == W )
				D.update_icon()
				D.playDigSound()
				visible_message("[user] smashes through the [name] with the [D.name]!", "You hear the grinding of metal.")
				dismantle_wall()
				return 1
	else if(istype(W, /obj/item/stack/sheet/metal) && d_state)
		var/obj/item/stack/sheet/metal/MS = W
		if (MS.get_amount() < 1)
			user << "You need one sheet of metal to repair the wall."
			return 1
		user << "You begin patching-up the wall with \a [MS]."
		if (do_after(user, max(20*d_state,100)))//time taken to repair is proportional to the damage! (max 10 seconds)
			if(loc == null || MS.get_amount() < 1)
				return 1
			MS.use(1)
			src.d_state = 0
			src.icon_state = "r_wall"
			relativewall_neighbours()	//call smoothwall stuff
			user << "You repair the last of the damage."
			return 1
	return 0

/turf/simulated/wall/r_wall/try_decon(obj/item/weapon/W as obj, mob/user as mob, turf/T as turf)
	//DECONSTRUCTION
	switch(d_state)
		if(0)
			if (istype(W, /obj/item/weapon/wirecutters))
				playsound(src, 'sound/items/Wirecutter.ogg', 100, 1)
				src.d_state = 1
				src.icon_state = "r_wall-1"
				new /obj/item/stack/rods( src )
				user << "You cut the outer grille."
				return 1

		if(1)
			if (istype(W, /obj/item/weapon/screwdriver))
				user << "You begin removing the support lines."
				playsound(src, 'sound/items/Screwdriver.ogg', 100, 1)

				if(do_after(user, 40))
					if( !istype(src, /turf/simulated/wall/r_wall) || !user || !W || !T )
						return 1

					if( d_state == 1 && user.loc == T && user.get_active_hand() == W )
						src.d_state = 2
						src.icon_state = "r_wall-2"
						user << "You remove the support lines."
				return 1

			//REPAIRING (replacing the outer grille for cosmetic damage)
			else if(istype(W, /obj/item/stack/rods))
				var/obj/item/stack/rods/O = W
				if (O.use(1))
					src.d_state = 0
					src.icon_state = "r_wall"
					relativewall_neighbours()	//call smoothwall stuff
					user << "You replace the outer grille."
				else
					user << "You need one rod to repair the wall."
					return 1
				return 1

		if(2)
			if( istype(W, /obj/item/weapon/weldingtool) )
				var/obj/item/weapon/weldingtool/WT = W
				if( WT.remove_fuel(0,user) )

					user << "You begin slicing through the metal cover."
					playsound(src, 'sound/items/Welder.ogg', 100, 1)

					if(do_after(user, 60))
						if( !istype(src, /turf/simulated/wall/r_wall) || !user || !WT || !WT.isOn() || !T )
							return 0

						if( d_state == 2 && user.loc == T && user.get_active_hand() == WT )
							src.d_state = 3
							src.icon_state = "r_wall-3"
							user << "You press firmly on the cover, dislodging it."
				return 1

			if( istype(W, /obj/item/weapon/pickaxe/plasmacutter) )

				user << "You begin slicing through the metal cover."
				playsound(src, 'sound/items/Welder.ogg', 100, 1)

				if(do_after(user, 60))
					if( !istype(src, /turf/simulated/wall/r_wall) || !user || !W || !T )
						return 1

					if( d_state == 2 && user.loc == T && user.get_active_hand() == W )
						src.d_state = 3
						src.icon_state = "r_wall-3"
						user << "You press firmly on the cover, dislodging it."
				return 1

		if(3)
			if (istype(W, /obj/item/weapon/crowbar))

				user << "You struggle to pry off the cover."
				playsound(src, 'sound/items/Crowbar.ogg', 100, 1)

				if(do_after(user, 100))
					if( !istype(src, /turf/simulated/wall/r_wall) || !user || !W || !T )
						return 1

					if( d_state == 3 && user.loc == T && user.get_active_hand() == W )
						src.d_state = 4
						src.icon_state = "r_wall-4"
						user << "You pry off the cover."
				return 1

		if(4)
			if (istype(W, /obj/item/weapon/wrench))

				user << "You start loosening the anchoring bolts which secure the support rods to their frame."
				playsound(src, 'sound/items/Ratchet.ogg', 100, 1)

				if(do_after(user, 40))
					if( !istype(src, /turf/simulated/wall/r_wall) || !user || !W || !T )
						return 1

					if( d_state == 4 && user.loc == T && user.get_active_hand() == W )
						src.d_state = 5
						src.icon_state = "r_wall-5"
						user << "You remove the bolts anchoring the support rods."
				return 1

		if(5)
			if( istype(W, /obj/item/weapon/weldingtool) )
				var/obj/item/weapon/weldingtool/WT = W
				if( WT.remove_fuel(0,user) )

					user << "You begin slicing through the support rods."
					playsound(src, 'sound/items/Welder.ogg', 100, 1)

					if(do_after(user, 100))
						if( !istype(src, /turf/simulated/wall/r_wall) || !user || !WT || !WT.isOn() || !T )
							return 1

						if( d_state == 5 && user.loc == T && user.get_active_hand() == WT )
							src.d_state = 6
							src.icon_state = "r_wall-6"
							new /obj/item/stack/rods( src )
							user << "The support rods drop out as you cut them loose from the frame."
				return 1

			if( istype(W, /obj/item/weapon/pickaxe/plasmacutter) )

				user << "You begin slicing through the support rods."
				playsound(src, 'sound/items/Welder.ogg', 100, 1)

				if(do_after(user, 70))
					if( !istype(src, /turf/simulated/wall/r_wall) || !user || !W || !T )
						return 1

					if( d_state == 5 && user.loc == T && user.get_active_hand() == W )
						src.d_state = 6
						src.icon_state = "r_wall-6"
						new /obj/item/stack/rods( src )
						user << "The support rods drop out as you cut them loose from the frame."
				return 1

		if(6)
			if( istype(W, /obj/item/weapon/crowbar) )

				user << "You struggle to pry off the outer sheath."
				playsound(src, 'sound/items/Crowbar.ogg', 100, 1)

				if(do_after(user, 100))
					if( !istype(src, /turf/simulated/wall/r_wall) || !user || !W || !T )
						return 1

					if( user.loc == T && user.get_active_hand() == W )
						user << "You pry off the outer sheath."
						dismantle_wall()
				return 1
	return 0

/turf/simulated/wall/r_wall/singularity_pull(S, current_size)
	if(current_size >= STAGE_FIVE)
		if(prob(30))
			dismantle_wall()


code:
/turf/space
	icon = 'icons/turf/space.dmi'
	name = "\proper space"
	icon_state = "0"
	intact = 0

	temperature = TCMB
	thermal_conductivity = OPEN_HEAT_TRANSFER_COEFFICIENT
	heat_capacity = 700000
	luminosity = 1

	var/transition //These is used in transistions as a way to tell where on the "cube" of space you're transitioning from/to
	var/destination_x
	var/destination_y
	dynamic_lighting = 0

/turf/space/New()
	turfs.Add(src)
	if(!istype(src, /turf/space/transit))
		icon_state = "[((x + y) ^ ~(x * y) + z) % 25]"
	if(config)
		if(config.starlight)
			update_starlight()

/turf/space/Destroy()
	return QDEL_HINT_LETMELIVE

/turf/space/proc/update_starlight()
	if(config)
		if(config.starlight)
			for(var/turf/T in orange(src,1))
				if(istype(T,/turf/simulated))
					set_light(3,2)
					return
			set_light(0)
	return

/turf/space/attack_paw(mob/user as mob)
	return src.attack_hand(user)

/turf/space/attackby(obj/item/C, mob/user, params)
	..()
	if(istype(C, /obj/item/stack/rods))
		var/obj/item/stack/rods/R = C
		var/obj/structure/lattice/L = locate(/obj/structure/lattice, src)
		var/obj/structure/lattice/catwalk/W = locate(/obj/structure/lattice/catwalk, src)
		if(W)
			user << "There is already a catwalk here."
			return
		if(L)
			if(R.use(1))
				user << "Constructing catwalk..."
				playsound(src, 'sound/weapons/Genhit.ogg', 50, 1)
				if(do_after(user, 10, 5, 0))
					qdel(L)
					ReplaceWithCatwalk()
					user << "Catwalk constructed"

			else
				user << "You need two rods to build a catwalk."
			return
		if(R.use(1))
			user << "Constructing support lattice..."
			playsound(src, 'sound/weapons/Genhit.ogg', 50, 1)
			ReplaceWithLattice()
		else
			user << "You need one rod to build a lattice."
		return
	if(istype(C, /obj/item/stack/tile/plasteel))
		var/obj/structure/lattice/L = locate(/obj/structure/lattice, src)
		if(L)
			var/obj/item/stack/tile/plasteel/S = C
			if(S.use(1))
				qdel(L)
				playsound(src, 'sound/weapons/Genhit.ogg', 50, 1)
				user << "You build a floor."
				ChangeTurf(/turf/simulated/floor/plating)
			else
				user << "You need one floor tile to build a floor."
		else
			user << "The plating is going to need some support. Place metal rods first."

/turf/space/Entered(atom/movable/A)
	..()
	if ((!(A) || src != A.loc))
		return

	if(transition)

		if(destination_x)
			A.x = destination_x

		if(destination_y)
			A.y = destination_y

		A.z =  text2num(transition)

		if(isliving(A))
			var/mob/living/L = A
			if(L.pulling)
				var/turf/T = get_step(L.loc,turn(A.dir, 180))
				L.pulling.loc = T

		//now we're on the new z_level, proceed the space drifting
		sleep(0)//Let a diagonal move finish, if necessary
		A.newtonian_move(A.inertia_dir)

/turf/space/proc/Sandbox_Spacemove(atom/movable/A)
	var/cur_x
	var/cur_y
	var/next_x = src.x
	var/next_y = src.y
	var/target_z
	var/list/y_arr
	var/list/cur_pos = src.get_global_map_pos()
	if(!cur_pos)
		return
	cur_x = cur_pos["x"]
	cur_y = cur_pos["y"]

	if(src.x <= 1)
		next_x = (--cur_x||global_map.len)
		y_arr = global_map[next_x]
		target_z = y_arr[cur_y]
		next_x = world.maxx - 2
	else if (src.x >= world.maxx)
		next_x = (++cur_x > global_map.len ? 1 : cur_x)
		y_arr = global_map[next_x]
		target_z = y_arr[cur_y]
		next_x = 3
	else if (src.y <= 1)
		y_arr = global_map[cur_x]
		next_y = (--cur_y||y_arr.len)
		target_z = y_arr[next_y]
		next_y = world.maxy - 2
	else if (src.y >= world.maxy)
		y_arr = global_map[cur_x]
		next_y = (++cur_y > y_arr.len ? 1 : cur_y)
		target_z = y_arr[next_y]
		next_y = 3

	var/turf/T = locate(next_x, next_y, target_z)
	A.Move(T)

/turf/space/handle_slip()
	return

/turf/space/proc/Assign_Destination()

	if(transition)
		if(x <= TRANSITIONEDGE) 							//west
			destination_x = world.maxx - TRANSITIONEDGE - 2
		else if (x >= (world.maxx - TRANSITIONEDGE - 1)) 	//east
			destination_x = TRANSITIONEDGE + 1
		else if (y <= TRANSITIONEDGE) 						//south
			destination_y = world.maxy - TRANSITIONEDGE - 2
		else if (y >= (world.maxy - TRANSITIONEDGE - 1)) 	//north
			destination_y = TRANSITIONEDGE + 1

/*
  Set the space turf transitions for the "space cube"

  Connections:
     ___     ___
   /_A_/|  /_F_/|
  |   |C| |   |E|
  |_B_|/  |_D_|/

  Note that all maps except F are oriented with north towards A. A and F are oriented with north towards D.
  The characters on the second cube should be upside down in this illustration, but aren't because of a lack of unicode support.
*/
proc/setup_map_transitions() //listamania

	var/list/unplaced_z_levels = 			accessable_z_levels
	var/list/free_zones = 					list("A", "B", "C", "D", "E", "F")
	var/list/zone_connections = 			list("D ","C ","B ","E ","A ","C ","F ","E ","A ","D ","F ","B ","A ","E ","F ","C ","A ","B ","F ","D ","D ","C ","B ","E") //This describes the borders of a cube based on free zones, really!
	var/text_zone_connections = 			list2text(zone_connections)
	var/list/final_zone_connections =		list()
	var/list/turfs_needing_transition =		list()
	var/list/turfs_needing_destinations = 	list()
	var/list/z_level_order = 				list()
	var/z_level
	var/placement
	var/total_processed = 0

	for(var/turf/space/S in world) //Define the transistions of the z levels
		total_processed++
		if (S.x == TRANSITIONEDGE || S.x == (world.maxx - TRANSITIONEDGE - 1) || S.y == TRANSITIONEDGE || S.y == (world.maxy - TRANSITIONEDGE - 1))
			turfs_needing_transition += S

	//if we've processed lots of turfs, switch to background processing to prevent being mistaken for an infinite loop
	if(total_processed > 450000)
		set background = 1

	while(free_zones.len != 0) //Assign the sides of the cube
		if(!unplaced_z_levels || !unplaced_z_levels.len) //if we're somehow unable to fill the cube, pad with deep space
			z_level =  6
		else
			z_level = pick(unplaced_z_levels)
		if(z_level > world.maxz) //A safety if one of the unplaced_z_levels doesn't actually exist
			z_level =  6
		placement = pick(free_zones)
		text_zone_connections = replacetext(text_zone_connections, placement, "[z_level]")

		for(var/turf/space/S in turfs_needing_transition) //pass the identity zone to the relevent turfs
			if(S.transition && prob(50)) //In z = 6 (deep space) it's a bit of a crapshoot in terms of navigation
				continue
			if(S.z == z_level)
				S.transition = num2text(z_level)
				if(!(S in turfs_needing_destinations))
					turfs_needing_destinations += S
				if(S.z != 6) //deep space turfs need to hang around in case they get reassigned a zone
					turfs_needing_transition -= S

		z_level_order += num2text(z_level)
		unplaced_z_levels -= z_level
		free_zones -= placement

	zone_connections = text2list(replacetext(text_zone_connections, " ", "\n")) //Convert the string back into a list

	final_zone_connections.len = z_level_order.len

	var/list/temp = list()

	for(var/j=1, j<= 24, j++)
		temp += zone_connections[j]
		if(temp.len == 4) //Chunks of cardinal directions
			final_zone_connections[z_level_order[j/4]] += temp
			temp = list()

	for(var/turf/space/S in turfs_needing_destinations) //replace the identity zone with the destination z-level
		var/list/directions = final_zone_connections[S.transition]
		if(S.x <= TRANSITIONEDGE)
			S.transition = directions[Z_WEST]
		else if(S.x >= (world.maxx - TRANSITIONEDGE - 1))
			S.transition = directions[Z_EAST]
		else if(S.y <= TRANSITIONEDGE)
			S.transition = directions[Z_SOUTH]
		else
			S.transition = directions[Z_NORTH]

		S.Assign_Destination()

/turf/space/singularity_act()
	return


code:
/turf/space/transit
	icon_state = "black"
	dir = SOUTH

/turf/space/transit/horizontal
	dir = WEST

/turf/space/transit/Entered(atom/movable/AM, atom/OldLoc)
	if(!AM)
		return
	var/max = world.maxx-TRANSITIONEDGE
	var/min = 1+TRANSITIONEDGE

	var/_z = rand(ZLEVEL_SPACEMIN,ZLEVEL_SPACEMAX)	//select a random space zlevel

	//now select coordinates for a border turf
	var/_x
	var/_y
	switch(dir)
		if(SOUTH)
			_x = rand(min,max)
			_y = max
		if(WEST)
			_x = max
			_y = rand(min,max)
		if(EAST)
			_x = min
			_y = rand(min,max)
		else
			_x = rand(min,max)
			_y = min

	var/turf/T = locate(_x, _y, _z)
	AM.loc = T
	AM.newtonian_move(dir)

//Overwrite because we dont want people building rods in space.
/turf/space/transit/attackby()
	return

/turf/space/transit/New()
	update_icon()
	..()

/turf/space/transit/proc/update_icon()
	var/p = 9
	var/angle = 0
	var/state = 1
	switch(dir)
		if(NORTH)
			angle = 180
			state = ((-p*x+y) % 15) + 1
			if(state < 1)
				state += 15
		if(EAST)
			angle = 90
			state = ((x+p*y) % 15) + 1
		if(WEST)
			angle = -90
			state = ((x-p*y) % 15) + 1
			if(state < 1)
				state += 15
		else
			state =	((p*x+y) % 15) + 1

	icon_state = "speedspace_ns_[state]"
	transform = turn(matrix(), angle)


code:
/turf
	icon = 'icons/turf/floors.dmi'
	level = 1.0

	var/intact = 1
	var/cancable = 0
	var/baseturf = /turf/space

	//Properties for open tiles (/floor)
	var/oxygen = 0
	var/carbon_dioxide = 0
	var/nitrogen = 0
	var/toxins = 0

	//Properties for airtight tiles (/wall)
	var/thermal_conductivity = 0.05
	var/heat_capacity = 1

	//Properties for both
	var/temperature = T20C

	var/holy = 0 //Will be probably used for supernatural shit
	var/shuttle = 0 //If it counts as a shuttle tile for specific shuttles

	var/blocks_air = 0

	var/PathNode/PNode = null //associated PathNode in the A* algorithm

	flags = 0

	var/dynamic_lighting = 1

/turf/New()
	..()
	turfs.Add(src)
	for(var/atom/movable/AM in src)
		Entered(AM)
	set_opacity(src.opacity)
	reconsider_lights()
	return

// Adds the adjacent turfs to the current atmos processing
/turf/Del()
	turfs.Remove(src)
	set_opacity(0)
	reconsider_lights()
	for(var/direction in cardinal)
		if(atmos_adjacent_turfs & direction)
			var/turf/simulated/T = get_step(src, direction)
			if(istype(T))
				SSair.add_to_active(T)
	..()

/turf/Destroy()
	return QDEL_HINT_HARDDEL_NOW

/turf/attack_hand(mob/user as mob)
	user.Move_Pulled(src)

/turf/attackby(obj/item/C, mob/user, params)
	if(cancable && istype(C, /obj/item/stack/cable_coil))
		var/obj/item/stack/cable_coil/coil = C
		for(var/obj/structure/cable/LC in src)
			if((LC.d1==0)||(LC.d2==0))
				LC.attackby(C,user)
				return
		coil.place_turf(src, user)
		return 1

	return 0

/turf/Enter(atom/movable/mover as mob|obj, atom/forget as mob|obj|turf|area)
	if (!mover)
		return 1
	// First, make sure it can leave its square
	if(isturf(mover.loc))
		// Nothing but border objects stop you from leaving a tile, only one loop is needed
		for(var/obj/obstacle in mover.loc)
			if(!obstacle.CheckExit(mover, src) && obstacle != mover && obstacle != forget)
				mover.Bump(obstacle, 1)
				return 0

	var/list/large_dense = list()
	//Next, check objects to block entry that are on the border
	for(var/atom/movable/border_obstacle in src)
		if(border_obstacle.flags&ON_BORDER)
			if(!border_obstacle.CanPass(mover, mover.loc, 1) && (forget != border_obstacle))
				mover.Bump(border_obstacle, 1)
				return 0
		else
			large_dense += border_obstacle

	//Then, check the turf itself
	if (!src.CanPass(mover, src))
		mover.Bump(src, 1)
		return 0

	//Finally, check objects/mobs to block entry that are not on the border
	for(var/atom/movable/obstacle in large_dense)
		if(!obstacle.CanPass(mover, mover.loc, 1) && (forget != obstacle))
			mover.Bump(obstacle, 1)
			return 0
	return 1 //Nothing found to block so return success!

/turf/Entered(atom/movable/M)
	if(ismob(M))
		var/mob/O = M
		if(!O.lastarea)
			O.lastarea = get_area(O.loc)
//		O.update_gravity(O.mob_has_gravity())

	var/loopsanity = 100
	for(var/atom/A in range(1))
		if(loopsanity == 0)
			break
		loopsanity--
		A.HasProximity(M, 1)

/turf/proc/is_plasteel_floor()
	return 0
/turf/proc/return_siding_icon_state()		//used for grass floors, which have siding.
	return 0

/turf/proc/levelupdate()
	for(var/obj/O in src)
		if(O.level == 1)
			O.hide(src.intact)

// override for space turfs, since they should never hide anything
/turf/space/levelupdate()
	for(var/obj/O in src)
		if(O.level == 1)
			O.hide(0)

// Removes all signs of lattice on the pos of the turf -Donkieyo
/turf/proc/RemoveLattice()
	var/obj/structure/lattice/L = locate(/obj/structure/lattice, src)
	if(L)
		qdel(L)

//Creates a new turf
/turf/proc/ChangeTurf(var/path)
	if(!path)			return
	if(path == type)	return src
	var/old_dynamic_lighting = dynamic_lighting
	var/list/old_affecting_lights = affecting_lights
	#if LIGHTING_RESOLUTION == 1
	var/old_lighting_overlay = lighting_overlay
	#else
	var/old_lighting_overlays = lighting_overlays
	#endif

//	var/old_opacity = opacity
	var/old_baseturf = baseturf
	SSair.remove_from_active(src)

	var/turf/W = new path(src)
	W.baseturf = old_baseturf
//	W.opacity = old_opacity
	W.dynamic_lighting = old_dynamic_lighting
	W.affecting_lights = old_affecting_lights
	#if LIGHTING_RESOLUTION == 1
	W.lighting_overlay = old_lighting_overlay
	#else
	W.lighting_overlays = old_lighting_overlays
	#endif
	W.reconsider_lights()

	if(istype(W, /turf/simulated))
		W:Assimilate_Air()
		W.RemoveLattice()

//	W.lighting_lumcount += old_lumcount
//	if(old_lumcount != W.lighting_lumcount)	//light levels of the turf have changed. We need to shift it to another lighting-subarea
//		W.lighting_changed = 1
//		SSlighting.changed_turfs += W

//	if(old_opacity != W.opacity)			//opacity has changed. Need to update surrounding lights
//		if(W.lighting_lumcount)				//unless we're being illuminated, don't bother (may be buggy, hard to test)
//			W.UpdateAffectingLights()

	for(var/turf/space/S in range(W,1))
		S.update_starlight()

	W.levelupdate()
	W.CalculateAdjacentTurfs()
	universe.OnTurfChange(W)
	return W

//////Assimilate Air//////
/turf/simulated/proc/Assimilate_Air()
	if(air)
		var/aoxy = 0//Holders to assimilate air from nearby turfs
		var/anitro = 0
		var/aco = 0
		var/atox = 0
		var/atemp = 0
		var/turf_count = 0

		for(var/direction in cardinal)//Only use cardinals to cut down on lag
			var/turf/T = get_step(src,direction)
			if(istype(T,/turf/space))//Counted as no air
				turf_count++//Considered a valid turf for air calcs
				continue
			else if(istype(T,/turf/simulated/floor))
				var/turf/simulated/S = T
				if(S.air)//Add the air's contents to the holders
					aoxy += S.air.oxygen
					anitro += S.air.nitrogen
					aco += S.air.carbon_dioxide
					atox += S.air.toxins
					atemp += S.air.temperature
				turf_count ++
		air.oxygen = (aoxy/max(turf_count,1))//Averages contents of the turfs, ignoring walls and the like
		air.nitrogen = (anitro/max(turf_count,1))
		air.carbon_dioxide = (aco/max(turf_count,1))
		air.toxins = (atox/max(turf_count,1))
		air.temperature = (atemp/max(turf_count,1))//Trace gases can get bant
		SSair.add_to_active(src)

/turf/proc/ReplaceWithLattice()
	src.ChangeTurf(/turf/space)
	new /obj/structure/lattice(locate(src.x, src.y, src.z) )

/turf/proc/ReplaceWithCatwalk()
	src.ChangeTurf(/turf/space)
	src.cancable = 1//so cables can be laid
	new /obj/structure/lattice/catwalk(locate(src.x, src.y, src.z) )

/turf/proc/phase_damage_creatures(damage,mob/U = null)//>Ninja Code. Hurts and knocks out creatures on this turf //NINJACODE
	for(var/mob/living/M in src)
		if(M==U)
			continue//Will not harm U. Since null != M, can be excluded to kill everyone.
		M.adjustBruteLoss(damage)
		M.Paralyse(damage/5)
	for(var/obj/mecha/M in src)
		M.take_damage(damage*2, "brute")

/turf/proc/Bless()
	flags |= NOJAUNT

//////////////////////////////
//Distance procs
//////////////////////////////

//Distance associates with all directions movement
/turf/proc/Distance(var/turf/T)
	return get_dist(src,T)

//  This Distance proc assumes that only cardinal movement is
//  possible. It results in more efficient (CPU-wise) pathing
//  for bots and anything else that only moves in cardinal dirs.
/turf/proc/Distance_cardinal(turf/T)
	if(!src || !T) return 0
	return abs(src.x - T.x) + abs(src.y - T.y)

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

/turf/handle_fall(mob/faller, forced)
	faller.lying = pick(90, 270)
	if(!forced)
		return
	if(has_gravity(src))
		playsound(src, "bodyfall", 50, 1)

/turf/handle_slip(mob/slipper, s_amount, w_amount, obj/O, lube)
	if(has_gravity(src))
		var/mob/living/carbon/M = slipper
		if (M.m_intent=="walk" && (lube&NO_SLIP_WHEN_WALKING))
			return 0
		if(!M.lying && (M.status_flags & CANWEAKEN)) // we slip those who are standing and can fall.
			var/olddir = M.dir
			M.Stun(s_amount)
			M.Weaken(w_amount)
			M.stop_pulling()
			if(lube&SLIDE)
				for(var/i=1, i<5, i++)
					spawn (i)
						step(M, olddir)
						M.spin(1,1)
				if(M.lying) //did I fall over?
					M.adjustBruteLoss(2)
			if(O)
				M << "You slipped on the [O.name]!"
			else
				M << "You slipped!"
			playsound(M.loc, 'sound/misc/slip.ogg', 50, 1, -3)
			return 1
	return 0 // no success. Used in clown pda and wet floors

/turf/singularity_act()
	if(intact)
		for(var/obj/O in contents) //this is for deleting things like wires contained in the turf
			if(O.level != 1)
				continue
			if(O.invisibility == 101)
				O.singularity_act()
	ChangeTurf(/turf/space)
	return(2)

/turf/rift_act(rift)
	var/turf/T = src
	var/obj/singularity/narsie/large/exit/R = rift
	var/dist = get_dist(T, R)
	if (dist <= R.consume_range && T.density)
		T.density = 0

	for (var/atom/movable/AM in T.contents)
		if (AM == R) // This is the snowflake.
			continue

		if (dist <= R.consume_range)
			R.consume(AM)
			continue

/turf/indestructible
	name = "wall"
	icon = 'icons/turf/walls.dmi'
	density = 1
	blocks_air = 1
	opacity = 1

/turf/indestructible/splashscreen
	name = "Space Station 13"
	icon = 'icons/misc/fullscreen.dmi'
	icon_state = "title"
	layer = FLY_LAYER

/turf/indestructible/riveted
	icon_state = "riveted"

/turf/indestructible/abductor
	icon_state = "alien1"
	shuttle = 1

/turf/indestructible/fakeglass
	name = "window"
	icon_state = "fakewindows"
	opacity = 0

/turf/indestructible/fakedoor
	name = "Centcom Access"
	icon = 'icons/obj/doors/Doorele.dmi'
	icon_state = "door_closed"


code:
/client/verb/ooc(msg as text)
	set name = "OOC" //Gave this shit a shorter name so you only have to time out "ooc" rather than "ooc message" to use it --NeoFite
	set category = "OOC"

	if(say_disabled)	//This is here to try to identify lag problems
		usr << "Speech is currently admin-disabled."
		return

	if(!mob)	return
	if(IsGuestKey(key))
		src << "Guests may not use OOC."
		return

	msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
	if(!msg)	return

	if(!(prefs.chat_toggles & CHAT_OOC))
		src << "You have OOC muted."
		return

	if(!holder)
		if(!ooc_allowed)
			src << "OOC is globally muted."
			return
		if(!dooc_allowed && (mob.stat == DEAD))
			usr << "OOC for dead mobs has been turned off."
			return
		if(prefs.muted & MUTE_OOC)
			src << "You cannot use OOC (muted)."
			return
		if(handle_spam_prevention(msg,MUTE_OOC))
			return
		if(findtext(msg, "byond://"))
			src << "Advertising other servers is not allowed."
			log_admin("[key_name(src)] has attempted to advertise in OOC: [msg]")
			message_admins("[key_name_admin(src)] has attempted to advertise in OOC: [msg]")
			return

	log_ooc("[mob.name]/[key] : [msg]")

	var/keyname = key
	if(prefs.unlock_content)
		if(prefs.toggles & MEMBER_PUBLIC)
			keyname = "[keyname]"

	msg = emoji_parse(msg)

	for(var/client/C in clients)
		if(C.prefs.chat_toggles & CHAT_OOC)
			if(holder)
				if(!holder.fakekey || C.holder)
					if(check_rights_for(src, R_ADMIN))
						C << "OOC: [keyname][holder.fakekey ? "/([holder.fakekey])" : ""]: [msg]"
					else
						C << "OOC: [keyname][holder.fakekey ? "/([holder.fakekey])" : ""]: [msg]"
				else
					C << "OOC: [holder.fakekey ? holder.fakekey : key]: [msg]"
			else
				C << "OOC: [keyname]: [msg]"

/proc/toggle_ooc()
	ooc_allowed = !( ooc_allowed )
	if (ooc_allowed)
		world << "The OOC channel has been globally enabled!"
	else
		world << "The OOC channel has been globally disabled!"

/proc/auto_toggle_ooc(var/on)
	if(!config.ooc_during_round && ooc_allowed != on)
		toggle_ooc()

var/global/normal_ooc_colour = "#002eb8"

/client/proc/set_ooc(newColor as color)
	set name = "Set Player OOC Color"
	set desc = "Modifies player OOC Color"
	set category = "Fun"
	normal_ooc_colour = sanitize_ooccolor(newColor)

/client/verb/colorooc()
	set name = "Set Your OOC Color"
	set category = "Preferences"

	if(!holder || check_rights_for(src, R_ADMIN))
		if(!is_content_unlocked())	return

	var/new_ooccolor = input(src, "Please select your OOC color.", "OOC color", prefs.ooccolor) as color|null
	if(new_ooccolor)
		prefs.ooccolor = sanitize_ooccolor(new_ooccolor)
		prefs.save_preferences()
	feedback_add_details("admin_verb","OC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
	return

//Checks admin notice
/client/verb/admin_notice()
	set name = "Adminnotice"
	set category = "Admin"
	set desc ="Check the admin notice if it has been set"

	if(admin_notice)
		src << "Admin Notice:\n \t [admin_notice]"
	else
		src << "There are no admin notices at the moment."

/client/verb/motd()
	set name = "MOTD"
	set category = "OOC"
	set desc ="Check the Message of the Day"

	if(join_motd)
		src << "
[join_motd]
"
	else
		src << "The Message of the Day has not been set."

/client/proc/self_notes()
	set name = "View Admin Notes"
	set category = "OOC"
	set desc = "View the notes that admins have written about you"

	if(!config.see_own_notes)
		usr << "Sorry, that function is not enabled on this server."
		return

	see_own_notes()


code:
/mob/var/suiciding = 0

/mob/living/carbon/human/verb/suicide()
	set hidden = 1
	if(!canSuicide())
		return
	var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No")
	if(!canSuicide())
		return
	if(confirm == "Yes")
		suiciding = 1
		var/obj/item/held_item = get_active_hand()
		if(held_item)
			var/damagetype = held_item.suicide_act(src)
			if(damagetype)
				var/damage_mod = 1
				switch(damagetype) //Sorry about the magic numbers.
								   //brute = 1, burn = 2, tox = 4, oxy = 8
					if(15) //4 damage types
						damage_mod = 4

					if(6, 11, 13, 14) //3 damage types
						damage_mod = 3

					if(3, 5, 7, 9, 10, 12) //2 damage types
						damage_mod = 2

					if(1, 2, 4, 8) //1 damage type
						damage_mod = 1

					else //This should not happen, but if it does, everything should still work
						damage_mod = 1

				//Do 175 damage divided by the number of damage types applied.
				if(damagetype & BRUTELOSS)
					adjustBruteLoss(175/damage_mod)

				if(damagetype & FIRELOSS)
					adjustFireLoss(175/damage_mod)

				if(damagetype & TOXLOSS)
					adjustToxLoss(175/damage_mod)

				if(damagetype & OXYLOSS)
					adjustOxyLoss(175/damage_mod)

				//If something went wrong, just do normal oxyloss
				if(!(damagetype | BRUTELOSS) && !(damagetype | FIRELOSS) && !(damagetype | TOXLOSS) && !(damagetype | OXYLOSS))
					adjustOxyLoss(max(175 - getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0))

				updatehealth()
				return

		var/suicide_message = pick("[src] is attempting to bite \his tongue off! It looks like \he's trying to commit suicide.", \
							"[src] is jamming \his thumbs into \his eye sockets! It looks like \he's trying to commit suicide.", \
							"[src] is twisting \his own neck! It looks like \he's trying to commit suicide.", \
							"[src] is holding \his breath! It looks like \he's trying to commit suicide.")

		visible_message("[suicide_message]", "[suicide_message]")

		adjustOxyLoss(max(175 - getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0))
		updatehealth()

/mob/living/carbon/brain/verb/suicide()
	set hidden = 1
	if(!canSuicide())
		return
	var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No")
	if(!canSuicide())
		return
	if(confirm == "Yes")
		suiciding = 1
		visible_message("[src]'s brain is growing dull and lifeless. It looks like it's lost the will to live.", \
						"[src]'s brain is growing dull and lifeless. It looks like it's lost the will to live.")
		spawn(50)
			death(0)
			suiciding = 0

/mob/living/carbon/monkey/verb/suicide()
	set hidden = 1
	if(!canSuicide())
		return
	var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No")
	if(!canSuicide())
		return
	if(confirm == "Yes")
		suiciding = 1
		//instead of killing them instantly, just put them at -175 health and let 'em gasp for a while
		visible_message("[src] is attempting to bite \his tongue. It looks like \he's trying to commit suicide.", \
				"[src] is attempting to bite \his tongue. It looks like \he's trying to commit suicide.")
		adjustOxyLoss(max(175- getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0))
		updatehealth()

/mob/living/silicon/ai/verb/suicide()
	set hidden = 1
	if(!canSuicide())
		return
	var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No")
	if(!canSuicide())
		return
	if(confirm == "Yes")
		suiciding = 1
		visible_message("[src] is powering down. It looks like \he's trying to commit suicide.", \
				"[src] is powering down. It looks like \he's trying to commit suicide.")
		//put em at -175
		adjustOxyLoss(max(maxHealth * 2 - getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0))
		updatehealth()

/mob/living/silicon/robot/verb/suicide()
	set hidden = 1
	if(!canSuicide())
		return
	var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No")
	if(!canSuicide())
		return
	if(confirm == "Yes")
		suiciding = 1
		visible_message("[src] is powering down. It looks like \he's trying to commit suicide.", \
				"[src] is powering down. It looks like \he's trying to commit suicide.")
		//put em at -175
		adjustOxyLoss(max(maxHealth * 2 - getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0))
		updatehealth()

/mob/living/silicon/pai/verb/suicide()
	set category = "pAI Commands"
	set desc = "Kill yourself and become a ghost (You will receive a confirmation prompt)"
	set name = "pAI Suicide"
	var/answer = input("REALLY kill yourself? This action can't be undone.", "Suicide", "No") in list ("Yes", "No")
	if(answer == "Yes")
		card.removePersonality()
		var/turf/T = get_turf(src.loc)
		T.visible_message("[src] flashes a message across its screen, \"Wiping core files. Please acquire a new personality to continue using pAI device functions.\"", "[src] bleeps electronically.")
		death(0)
	else
		src << "Aborting suicide attempt."

/mob/living/carbon/alien/humanoid/verb/suicide()
	set hidden = 1
	if(!canSuicide())
		return
	var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No")
	if(!canSuicide())
		return
	if(confirm == "Yes")
		suiciding = 1
		visible_message("[src] is thrashing wildly! It looks like \he's trying to commit suicide.", \
				"[src] is thrashing wildly! It looks like \he's trying to commit suicide.", \
				"You hear thrashing")
		//put em at -175
		adjustOxyLoss(max(175 - getFireLoss() - getBruteLoss() - getOxyLoss(), 0))
		updatehealth()

/mob/living/carbon/slime/verb/suicide()
	set hidden = 1
	if(!canSuicide())
		return
	var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No")
	if(!canSuicide())
		return
	if(confirm == "Yes")
		suiciding = 1
		visible_message("[src] is growing dull and lifeless. It looks like it's lost the will to live.", \
						"[src] is growing dull and lifeless. It looks like it's lost the will to live.")
		setOxyLoss(100)
		adjustBruteLoss(100 - getBruteLoss())
		setToxLoss(100)
		setCloneLoss(100)

		updatehealth()

/mob/living/simple_animal/verb/suicide()
	set hidden = 1
	if(!canSuicide())
		return
	var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No")
	if(!canSuicide())
		return
	if(confirm == "Yes")
		suiciding = 1
		visible_message("[src] begins to fall down. It looks like \he's lost the will to live.", \
						"[src] begins to fall down. It looks like \he's lost the will to live.")
		death(0)

/mob/living/proc/canSuicide()
	if(stat == CONSCIOUS)
		return 1
	else if(stat == DEAD)
		src << "You're already dead!"
	else if(stat == UNCONSCIOUS)
		src << "You need to be conscious to suicide"
	return

/mob/living/carbon/canSuicide()
	if(!..())
		return
	if(restrained() && is_muzzled())	//just while I finish up the new 'fun' suiciding verb. This is to prevent metagaming via suicide
		src << "You can't commit suicide whilst restrained and muzzled! ((You can type Ghost instead however.))"
		return
	return 1


code:
/client/verb/who()
	set name = "Who"
	set category = "OOC"

	var/msg = "Current Players:\n"

	var/list/Lines = list()

	if(holder)
		if(check_rights(R_ADMIN,0))//If they have +ADMIN, show hidden admins, player IC names and IC status
			for(var/client/C in clients)
				var/entry = "\t[C.key]"
				if(C.holder && C.holder.fakekey)
					entry += " (as [C.holder.fakekey])"
				entry += " - Playing as [C.mob.real_name]"
				switch(C.mob.stat)
					if(UNCONSCIOUS)
						entry += " - Unconscious"
					if(DEAD)
						if(isobserver(C.mob))
							var/mob/dead/observer/O = C.mob
							if(O.started_as_observer)
								entry += " - Observing"
							else
								entry += " - DEAD"
						else
							entry += " - DEAD"
				if(is_special_character(C.mob))
					entry += " - Antagonist"
				entry += " (?)"
				Lines += entry
		else//If they don't have +ADMIN, only show hidden admins
			for(var/client/C in clients)
				var/entry = "\t[C.key]"
				if(C.holder && C.holder.fakekey)
					entry += " (as [C.holder.fakekey])"
				Lines += entry
	else
		for(var/client/C in clients)
			if(C.holder && C.holder.fakekey)
				Lines += C.holder.fakekey
			else
				Lines += C.key

	for(var/line in sortList(Lines))
		msg += "[line]\n"

	msg += "Total Players: [length(Lines)]"
	src << msg

/client/verb/adminwho()
	set category = "Admin"
	set name = "Adminwho"

	var/msg = "Current Admins:\n"
	if(holder)
		for(var/client/C in admins)
			msg += "\t[C] is a [C.holder.rank]"

			if(C.holder.fakekey)
				msg += " (as [C.holder.fakekey])"

			if(isobserver(C.mob))
				msg += " - Observing"
			else if(istype(C.mob,/mob/new_player))
				msg += " - Lobby"
			else
				msg += " - Playing"

			if(C.is_afk())
				msg += " (AFK)"
			msg += "\n"
	else
		for(var/client/C in admins)
			if(!C.holder.fakekey)
				msg += "\t[C] is a [C.holder.rank]\n"

	src << msg


code:
/world

	hub = "Exadv1.spacestation13"
	hub_password = "SORRYNOPASSWORD"
	name = "/tg/ Station 13"

/*
This is for any host that would like their server to appear on the main SS13 hub.
To use it, simply replace the password above with the password found below, and recompile, and it should work.
If not, let us know on the main tgstation IRC channel of irc.rizon.net #tgstation13: we can help you there.

	hub = "Exadv1.spacestation13"
	hub_password = "kMZy3U5jJHSiBQjr"
	name = "Space Station 13"

*/


code:
//this function places received data into element with specified id.
var/const/js_byjax = {"

function replaceContent() {
	var args = Array.prototype.slice.call(arguments);
	var id = args\[0\];
	var content = args\[1\];
	var callback  = null;
	if(args\[2\]){
		callback = args\[2\];
		if(args\[3\]){
			args = args.slice(3);
		}
	}
	var parent = document.getElementById(id);
	if(typeof(parent)!=='undefined' && parent!=null){
		parent.innerHTML = content?content:'';
	}
	if(callback && window\[callback\]){
		window\[callback\].apply(null,args);
	}
}
"}

/*
sends data to control_id:replaceContent

receiver - mob
control_id - window id (for windows opened with browse(), it'll be "windowname.browser")
target_element - HTML element id
new_content - HTML content
callback - js function that will be called after the data is sent
callback_args - arguments for callback function

Be sure to include required js functions in your page, or it'll raise an exception.
*/
proc/send_byjax(receiver, control_id, target_element, new_content=null, callback=null, list/callback_args=null)
	if(receiver && target_element && control_id) // && winexists(receiver, control_id))
		var/list/argums = list(target_element, new_content)
		if(callback)
			argums += callback
			if(callback_args)
				argums += callback_args
		argums = list2params(argums)
/*		if(callback_args)
			argums += "&[list2params(callback_args)]"
*/
		receiver << output(argums,"[control_id]:replaceContent")
	return


code:
var/const/js_dropdowns = {"
function dropdowns() {
    var divs = document.getElementsByTagName('div');
    var headers = new Array();
    var links = new Array();
    for(var i=0;i<divs.length;i++){
        if(divs\[i\].className=='header') {
            divs\[i\].className='header closed';
            divs\[i\].innerHTML = divs\[i\].innerHTML+' +';
            headers.push(divs\[i\]);
        }
        if(divs\[i\].className=='links') {
            divs\[i\].className='links hidden';
            links.push(divs\[i\]);
        }
    }
    for(var i=0;i<headers.length;i++){
        if(typeof(links\[i\])!== 'undefined' && links\[i\]!=null) {
            headers\[i\].onclick = (function(elem) {
                return function() {
                    if(elem.className.search('visible')>=0) {
                        elem.className = elem.className.replace('visible','hidden');
                        this.className = this.className.replace('open','closed');
                        this.innerHTML = this.innerHTML.replace('-','+');
                    }
                    else {
                        elem.className = elem.className.replace('hidden','visible');
                        this.className = this.className.replace('closed','open');
                        this.innerHTML = this.innerHTML.replace('+','-');
                    }
                return false;
                }
            })(links\[i\]);
        }
    }
}
"}


code:
#define MAX_ADMIN_BANS_PER_ADMIN 1

//Either pass the mob you wish to ban in the 'banned_mob' attribute, or the banckey, banip and bancid variables. If both are passed, the mob takes priority! If a mob is not passed, banckey is the minimum that needs to be passed! banip and bancid are optional.
datum/admins/proc/DB_ban_record(var/bantype, var/mob/banned_mob, var/duration = -1, var/reason, var/job = "", var/rounds = 0, var/banckey = null, var/banip = null, var/bancid = null)

	if(!check_rights(R_BAN))	return

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

	var/serverip = "[world.internet_address]:[world.port]"
	var/bantype_pass = 0
	var/bantype_str
	var/maxadminbancheck	//Used to limit the number of active bans of a certein type that each admin can give. Used to protect against abuse or mutiny.
	var/announceinirc		//When set, it announces the ban in irc. Intended to be a way to raise an alarm, so to speak.
	var/blockselfban		//Used to prevent the banning of yourself.
	var/kickbannedckey		//Defines whether this proc should kick the banned person, if they are connected (if banned_mob is defined).
							//some ban types kick players after this proc passes (tempban, permaban), but some are specific to db_ban, so
							//they should kick within this proc.
	switch(bantype)
		if(BANTYPE_PERMA)
			bantype_str = "PERMABAN"
			duration = -1
			bantype_pass = 1
			blockselfban = 1
		if(BANTYPE_TEMP)
			bantype_str = "TEMPBAN"
			bantype_pass = 1
			blockselfban = 1
		if(BANTYPE_JOB_PERMA)
			bantype_str = "JOB_PERMABAN"
			duration = -1
			bantype_pass = 1
		if(BANTYPE_JOB_TEMP)
			bantype_str = "JOB_TEMPBAN"
			bantype_pass = 1
		if(BANTYPE_APPEARANCE)
			bantype_str = "APPEARANCE_PERMABAN"
			duration = -1
			bantype_pass = 1
		if(BANTYPE_ADMIN_PERMA)
			bantype_str = "ADMIN_PERMABAN"
			duration = -1
			bantype_pass = 1
			maxadminbancheck = 1
			announceinirc = 1
			blockselfban = 1
			kickbannedckey = 1
		if(BANTYPE_ADMIN_TEMP)
			bantype_str = "ADMIN_TEMPBAN"
			bantype_pass = 1
			maxadminbancheck = 1
			announceinirc = 1
			blockselfban = 1
			kickbannedckey = 1
	if( !bantype_pass ) return
	if( !istext(reason) ) return
	if( !isnum(duration) ) return

	var/ckey
	var/computerid
	var/ip

	if(ismob(banned_mob))
		ckey = banned_mob.ckey
		if(banned_mob.client)
			computerid = banned_mob.client.computer_id
			ip = banned_mob.client.address
		else
			computerid = banned_mob.computer_id
			ip = banned_mob.lastKnownIP
	else if(banckey)
		ckey = ckey(banckey)
		computerid = bancid
		ip = banip

	var/DBQuery/query = dbcon.NewQuery("SELECT id FROM [format_table_name("player")] WHERE ckey = '[ckey]'")
	query.Execute()
	var/validckey = 0
	if(query.NextRow())
		validckey = 1
	if(!validckey)
		if(!banned_mob || (banned_mob && !IsGuestKey(banned_mob.key)))
			message_admins("[key_name_admin(usr)] attempted to ban [ckey], but [ckey] has not been seen yet. Please only ban actual players.",1)
			return

	var/a_ckey
	var/a_computerid
	var/a_ip

	if(src.owner && istype(src.owner, /client))
		a_ckey = src.owner:ckey
		a_computerid = src.owner:computer_id
		a_ip = src.owner:address

	if(blockselfban)
		if(a_ckey == ckey)
			usr << "You cannot apply this ban type on yourself."
			return

	var/who
	for(var/client/C in clients)
		if(!who)
			who = "[C]"
		else
			who += ", [C]"

	var/adminwho
	for(var/client/C in admins)
		if(!adminwho)
			adminwho = "[C]"
		else
			adminwho += ", [C]"

	reason = sanitizeSQL(reason)

	if(maxadminbancheck)
		var/DBQuery/adm_query = dbcon.NewQuery("SELECT count(id) AS num FROM [format_table_name("ban")] WHERE (a_ckey = '[a_ckey]') AND (bantype = 'ADMIN_PERMABAN'  OR (bantype = 'ADMIN_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)")
		adm_query.Execute()
		if(adm_query.NextRow())
			var/adm_bans = text2num(adm_query.item[1])
			if(adm_bans >= MAX_ADMIN_BANS_PER_ADMIN)
				usr << "You already logged [MAX_ADMIN_BANS_PER_ADMIN] admin ban(s) or more. Do not abuse this function!"
				return

	var/sql = "INSERT INTO [format_table_name("ban")] (`id`,`bantime`,`serverip`,`bantype`,`reason`,`job`,`duration`,`rounds`,`expiration_time`,`ckey`,`computerid`,`ip`,`a_ckey`,`a_computerid`,`a_ip`,`who`,`adminwho`,`edits`,`unbanned`,`unbanned_datetime`,`unbanned_ckey`,`unbanned_computerid`,`unbanned_ip`) VALUES (null, Now(), '[serverip]', '[bantype_str]', '[reason]', '[job]', [(duration)?"[duration]":"0"], [(rounds)?"[rounds]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[ckey]', '[computerid]', '[ip]', '[a_ckey]', '[a_computerid]', '[a_ip]', '[who]', '[adminwho]', '', null, null, null, null, null)"
	var/DBQuery/query_insert = dbcon.NewQuery(sql)
	query_insert.Execute()
	usr << "Ban saved to database."
	message_admins("[key_name_admin(usr)] has added a [bantype_str] for [ckey] [(job)?"([job])":""] [(duration > 0)?"([duration] minutes)":""] with the reason: \"[reason]\" to the ban database.",1)

	if(announceinirc)
		send2irc("BAN ALERT","[a_ckey] applied a [bantype_str] on [ckey]")

	if(kickbannedckey)
		if(banned_mob && banned_mob.client && banned_mob.client.ckey == banckey)
			del(banned_mob.client)

datum/admins/proc/DB_ban_unban(var/ckey, var/bantype, var/job = "")

	if(!check_rights(R_BAN))	return

	var/bantype_str
	if(bantype)
		var/bantype_pass = 0
		switch(bantype)
			if(BANTYPE_PERMA)
				bantype_str = "PERMABAN"
				bantype_pass = 1
			if(BANTYPE_TEMP)
				bantype_str = "TEMPBAN"
				bantype_pass = 1
			if(BANTYPE_JOB_PERMA)
				bantype_str = "JOB_PERMABAN"
				bantype_pass = 1
			if(BANTYPE_JOB_TEMP)
				bantype_str = "JOB_TEMPBAN"
				bantype_pass = 1
			if(BANTYPE_APPEARANCE)
				bantype_str = "APPEARANCE_PERMABAN"
				bantype_pass = 1
			if(BANTYPE_ADMIN_PERMA)
				bantype_str = "ADMIN_PERMABAN"
				bantype_pass = 1
			if(BANTYPE_ADMIN_TEMP)
				bantype_str = "ADMIN_TEMPBAN"
				bantype_pass = 1
			if(BANTYPE_ANY_FULLBAN)
				bantype_str = "ANY"
				bantype_pass = 1
		if( !bantype_pass ) return

	var/bantype_sql
	if(bantype_str == "ANY")
		bantype_sql = "(bantype = 'PERMABAN' OR (bantype = 'TEMPBAN' AND expiration_time > Now() ) )"
	else
		bantype_sql = "bantype = '[bantype_str]'"

	var/sql = "SELECT id FROM [format_table_name("ban")] WHERE ckey = '[ckey]' AND [bantype_sql] AND (unbanned is null OR unbanned = false)"
	if(job)
		sql += " AND job = '[job]'"

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

	var/ban_id
	var/ban_number = 0 //failsafe

	var/DBQuery/query = dbcon.NewQuery(sql)
	query.Execute()
	while(query.NextRow())
		ban_id = query.item[1]
		ban_number++;

	if(ban_number == 0)
		usr << "Database update failed due to no bans fitting the search criteria. If this is not a legacy ban you should contact the database admin."
		return

	if(ban_number > 1)
		usr << "Database update failed due to multiple bans fitting the search criteria. Note down the ckey, job and current time and contact the database admin."
		return

	if(istext(ban_id))
		ban_id = text2num(ban_id)
	if(!isnum(ban_id))
		usr << "Database update failed due to a ban ID mismatch. Contact the database admin."
		return

	DB_ban_unban_by_id(ban_id)

datum/admins/proc/DB_ban_edit(var/banid = null, var/param = null)

	if(!check_rights(R_BAN))	return

	if(!isnum(banid) || !istext(param))
		usr << "Cancelled"
		return

	var/DBQuery/query = dbcon.NewQuery("SELECT ckey, duration, reason FROM [format_table_name("ban")] WHERE id = [banid]")
	query.Execute()

	var/eckey = usr.ckey	//Editing admin ckey
	var/pckey				//(banned) Player ckey
	var/duration			//Old duration
	var/reason				//Old reason

	if(query.NextRow())
		pckey = query.item[1]
		duration = query.item[2]
		reason = query.item[3]
	else
		usr << "Invalid ban id. Contact the database admin"
		return

	reason = sanitizeSQL(reason)
	var/value

	switch(param)
		if("reason")
			if(!value)
				value = input("Insert the new reason for [pckey]'s ban", "New Reason", "[reason]", null) as null|text
				value = sanitizeSQL(value)
				if(!value)
					usr << "Cancelled"
					return

			var/DBQuery/update_query = dbcon.NewQuery("UPDATE [format_table_name("ban")] SET reason = '[value]', edits = CONCAT(edits,'- [eckey] changed ban reason from \\\"[reason]\\\" to \\\"[value]\\\"
') WHERE id = [banid]")
			update_query.Execute()
			message_admins("[key_name_admin(usr)] has edited a ban for [pckey]'s reason from [reason] to [value]",1)
		if("duration")
			if(!value)
				value = input("Insert the new duration (in minutes) for [pckey]'s ban", "New Duration", "[duration]", null) as null|num
				if(!isnum(value) || !value)
					usr << "Cancelled"
					return

			var/DBQuery/update_query = dbcon.NewQuery("UPDATE [format_table_name("ban")] SET duration = [value], edits = CONCAT(edits,'- [eckey] changed ban duration from [duration] to [value]
'), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]")
			message_admins("[key_name_admin(usr)] has edited a ban for [pckey]'s duration from [duration] to [value]",1)
			update_query.Execute()
		if("unban")
			if(alert("Unban [pckey]?", "Unban?", "Yes", "No") == "Yes")
				DB_ban_unban_by_id(banid)
				return
			else
				usr << "Cancelled"
				return
		else
			usr << "Cancelled"
			return

datum/admins/proc/DB_ban_unban_by_id(var/id)

	if(!check_rights(R_BAN))	return

	var/sql = "SELECT ckey FROM [format_table_name("ban")] WHERE id = [id]"

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

	var/ban_number = 0 //failsafe

	var/pckey
	var/DBQuery/query = dbcon.NewQuery(sql)
	query.Execute()
	while(query.NextRow())
		pckey = query.item[1]
		ban_number++;

	if(ban_number == 0)
		usr << "Database update failed due to a ban id not being present in the database."
		return

	if(ban_number > 1)
		usr << "Database update failed due to multiple bans having the same ID. Contact the database admin."
		return

	if(!src.owner || !istype(src.owner, /client))
		return

	var/unban_ckey = src.owner:ckey
	var/unban_computerid = src.owner:computer_id
	var/unban_ip = src.owner:address

	var/sql_update = "UPDATE [format_table_name("ban")] SET unbanned = 1, unbanned_datetime = Now(), unbanned_ckey = '[unban_ckey]', unbanned_computerid = '[unban_computerid]', unbanned_ip = '[unban_ip]' WHERE id = [id]"
	message_admins("[key_name_admin(usr)] has lifted [pckey]'s ban.",1)

	var/DBQuery/query_update = dbcon.NewQuery(sql_update)
	query_update.Execute()

/client/proc/DB_ban_panel()
	set category = "Admin"
	set name = "Banning Panel"
	set desc = "Edit admin permissions"

	if(!holder)
		return

	holder.DB_ban_panel()

/datum/admins/proc/DB_ban_panel(var/playerckey = null, var/adminckey = null)
	if(!usr.client)
		return

	if(!check_rights(R_BAN))	return

	establish_db_connection()
	if(!dbcon.IsConnected())
		usr << "Failed to establish database connection."
		return

	var/output = "

"

	output += ""
	output += "
Banning panel
"
	output += "	"

	output += ""

	output += "<form method='GET' action='?src=\ref[src]'>Add custom ban: (ONLY use this if you can't ban through any other method)"
	output += "<input type='hidden' name='src' value='\ref[src]'>"
	output += "

"
	output += "Ban type:<select name='dbbanaddtype'>"
	output += "<option value=''>--</option>"
	output += "<option value='[BANTYPE_PERMA]'>PERMABAN</option>"
	output += "<option value='[BANTYPE_TEMP]'>TEMPBAN</option>"
	output += "<option value='[BANTYPE_JOB_PERMA]'>JOB PERMABAN</option>"
	output += "<option value='[BANTYPE_JOB_TEMP]'>JOB TEMPBAN</option>"
	output += "<option value='[BANTYPE_APPEARANCE]'>IDENTITY BAN</option>"
	output += "<option value='[BANTYPE_ADMIN_PERMA]'>ADMIN PERMABAN</option>"
	output += "<option value='[BANTYPE_ADMIN_TEMP]'>ADMIN TEMPBAN</option>"
	output += "</select>	"
	output += "Ckey: <input type='text' name='dbbanaddckey'>
"
	output += "
IP: <input type='text' name='dbbanaddip'>	"
	output += "Computer id: <input type='text' name='dbbanaddcid'>
"
	output += "
Duration: <input type='text' name='dbbaddduration'>	"
	output += "Job:<select name='dbbanaddjob'>"
	output += "<option value=''>--</option>"
	for(var/j in get_all_jobs())
		output += "<option value='[j]'>[j]</option>"
	for(var/j in nonhuman_positions)
		output += "<option value='[j]'>[j]</option>"
	for(var/j in list("traitor","changeling","operative","revolutionary", "gangster","cultist","wizard"))
		output += "<option value='[j]'>[j]</option>"
	output += "</select>

"
	output += "Reason:
<textarea name='dbbanreason' cols='50'></textarea>
"
	output += "<input type='submit' value='Add ban'>"
	output += "</form>"

	output += "	"
	output += "
"
	output += "
"

	output += "<form method='GET' action='?src=\ref[src]'>Search: "
	output += "<input type='hidden' name='src' value='\ref[src]'>"
	output += "Ckey: <input type='text' name='dbsearchckey' value='[playerckey]'>"
	output += "Admin ckey: <input type='text' name='dbsearchadmin' value='[adminckey]'>"
	output += "<input type='submit' value='search'>"
	output += "</form>"
	output += "Please note that all jobban bans or unbans are in-effect the following round."

	if(adminckey || playerckey)

		var/blcolor = "#ffeeee" //banned light
		var/bdcolor = "#ffdddd" //banned dark
		var/ulcolor = "#eeffee" //unbanned light
		var/udcolor = "#ddffdd" //unbanned dark

		output += "
"
		output += "
"
		output += "TYPE	"
		output += "CKEY	"
		output += "TIME APPLIED	"
		output += "ADMIN	"
		output += "OPTIONS	"
		output += "
"

		adminckey = ckey(adminckey)
		playerckey = ckey(playerckey)
		var/adminsearch = ""
		var/playersearch = ""
		if(adminckey)
			adminsearch = "AND a_ckey = '[adminckey]' "
		if(playerckey)
			playersearch = "AND ckey = '[playerckey]' "

		var/DBQuery/select_query = dbcon.NewQuery("SELECT id, bantime, bantype, reason, job, duration, expiration_time, ckey, a_ckey, unbanned, unbanned_ckey, unbanned_datetime, edits FROM [format_table_name("ban")] WHERE 1 [playersearch] [adminsearch] ORDER BY bantime DESC")
		select_query.Execute()

		while(select_query.NextRow())
			var/banid = select_query.item[1]
			var/bantime = select_query.item[2]
			var/bantype  = select_query.item[3]
			var/reason = select_query.item[4]
			var/job = select_query.item[5]
			var/duration = select_query.item[6]
			var/expiration = select_query.item[7]
			var/ckey = select_query.item[8]
			var/ackey = select_query.item[9]
			var/unbanned = select_query.item[10]
			var/unbanckey = select_query.item[11]
			var/unbantime = select_query.item[12]
			var/edits = select_query.item[13]

			var/lcolor = blcolor
			var/dcolor = bdcolor
			if(unbanned)
				lcolor = ulcolor
				dcolor = udcolor

			var/typedesc =""
			switch(bantype)
				if("PERMABAN")
					typedesc = "PERMABAN"
				if("TEMPBAN")
					typedesc = "TEMPBAN
([duration] minutes [(unbanned) ? "" : "(Edit))"]
Expires [expiration]"
				if("JOB_PERMABAN")
					typedesc = "JOBBAN
([job])"
				if("JOB_TEMPBAN")
					typedesc = "TEMP JOBBAN
([job])
([duration] minutes
Expires [expiration]"
				if("APPEARANCE_PERMABAN")
					typedesc = "IDENTITY PERMABAN"
				if("ADMIN_PERMABAN")
					typedesc = "ADMIN PERMABAN"
				if("ADMIN_TEMPBAN")
					typedesc = "ADMIN TEMPBAN
([duration] minutes [(unbanned) ? "" : "(Edit))"]
Expires [expiration]"

			output += "
"
			output += "[typedesc]	"
			output += "[ckey]	"
			output += "[bantime]	"
			output += "[ackey]	"
			output += "[(unbanned) ? "" : "Unban"]	"
			output += "
"
			output += "
"
			output += "Reason: [(unbanned) ? "" : "(Edit)"] \"[reason]\"	"
			output += "
"
			if(edits)
				output += "
"
				output += "EDITS	"
				output += "
"
				output += "
"
				output += "[edits]	"
				output += "
"
			if(unbanned)
				output += "
"
				output += "UNBANNED by admin [unbanckey] on [unbantime]	"
				output += "
"
			output += "
"
			output += "&nbsp	"
			output += "
"

		output += "

"

	usr << browse(output,"window=lookupbans;size=900x500")

path: code/modules/admin/DB ban/functions.dm


code:
//Blocks an attempt to connect before even creating our client datum thing.

world/IsBanned(key,address,computer_id)
	if (!key || !address || !computer_id)
		log_access("Failed Login (invalid data): [key] [address]-[computer_id]")
		return list("reason"="invalid login data", "desc"="Your computer provided invalid or blank information to the server on connection (byond username, IP, and Computer ID.) Provided information for reference: Username:'[key]' IP:'[address]' Computer ID:'[computer_id]' If you continue to get this error, please restart byond or contact byond support.")
	if(ckey(key) in admin_datums)
		//It has proven to be a bad idea to make admins completely immune to bans, making them have to wait for someone with daemon access
		//to add a daemon ban to finally stop them. Admin tempbans and admin permabans are special, high-level ban types, which are there to help
		//deal with rogue admins quicker. If admin tempbans or admin permabans are ever needed, it should be consider a big deal. The same applies if
		//admin bans are ever abused. This ban type does NOT check for IP or Computer ID. The reason for this is so a player cannot find/steal an admin's
		//computer id, set it on his computer, get himself banned, resulting in the admin getting banned aswell. - this happens to also be the reason why
		//admins were immune to bans in the first place.
		if(!config.ban_legacy_system)
			var/ckeytext = ckey(key)

			if(!establish_db_connection())
				world.log << "Ban database connection failure. Admin [ckeytext] not checked"
				diary << "Ban database connection failure. Admin [ckeytext] not checked"
				return

			var/DBQuery/query = dbcon.NewQuery("SELECT ckey, ip, computerid, a_ckey, reason, expiration_time, duration, bantime, bantype FROM [format_table_name("ban")] WHERE (ckey = '[ckeytext]') AND (bantype = 'ADMIN_PERMABAN'  OR (bantype = 'ADMIN_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)")

			query.Execute()

			while(query.NextRow())
				var/pckey = query.item[1]
				//var/pip = query.item[2]
				//var/pcid = query.item[3]
				var/ackey = query.item[4]
				var/reason = query.item[5]
				var/expiration = query.item[6]
				var/duration = query.item[7]
				var/bantime = query.item[8]
				var/bantype = query.item[9]

				var/expires = ""
				if(text2num(duration) > 0)
					expires = " The ban is for [duration] minutes and expires on [expiration] (server time)."

				var/desc = "\nReason: You, or another user of this computer or connection ([pckey]) is banned from playing here. The ban reason is:\n[reason]\nThis ban was applied by [ackey] on [bantime], [expires]"

				return list("reason"="[bantype]", "desc"="[desc]")

		return ..()

	//Guest Checking
	if(IsGuestKey(key))
		if (!guests_allowed)
			log_access("Failed Login: [key] - Guests not allowed")
			message_admins("Failed Login: [key] - Guests not allowed")
			return list("reason"="guest", "desc"="\nReason: Guests not allowed. Please sign in with a byond account.")
		if (config.panic_bunker && dbcon && dbcon.IsConnected())
			log_access("Failed Login: [key] - Guests not allowed during panic bunker")
			message_admins("Failed Login: [key] - Guests not allowed during panic bunker")
			return list("reason"="guest", "desc"="\nReason: Sorry but the server is currently not accepting connections from never before seen players or guests. If you have played on this server with a byond account before, please log in to the byond account you have played from.")

	//Population Cap Checking
	if(config.extreme_popcap && living_player_count() >= config.extreme_popcap && !(ckey(key) in admin_datums))
		log_access("Failed Login: [key] - Population cap reached")
		return list("reason"="popcap", "desc"= "\nReason: [config.extreme_popcap_message]")

	if(config.ban_legacy_system)

		//Ban Checking
		. = CheckBan( ckey(key), computer_id, address )
		if(.)
			log_access("Failed Login: [key] [computer_id] [address] - Banned [.["reason"]]")
			message_admins("Failed Login: [key] id:[computer_id] ip:[address] - Banned [.["reason"]]")
			return .

		return ..()	//default pager ban stuff

	else

		var/ckeytext = ckey(key)

		if(!establish_db_connection())
			world.log << "Ban database connection failure. Key [ckeytext] not checked"
			diary << "Ban database connection failure. Key [ckeytext] not checked"
			return

		var/ipquery = ""
		var/cidquery = ""
		if(address)
			ipquery = " OR ip = '[address]' "

		if(computer_id)
			cidquery = " OR computerid = '[computer_id]' "

		var/DBQuery/query = dbcon.NewQuery("SELECT ckey, ip, computerid, a_ckey, reason, expiration_time, duration, bantime, bantype FROM [format_table_name("ban")] WHERE (ckey = '[ckeytext]' [ipquery] [cidquery]) AND (bantype = 'PERMABAN'  OR (bantype = 'TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)")

		query.Execute()

		while(query.NextRow())
			var/pckey = query.item[1]
			//var/pip = query.item[2]
			//var/pcid = query.item[3]
			var/ackey = query.item[4]
			var/reason = query.item[5]
			var/expiration = query.item[6]
			var/duration = query.item[7]
			var/bantime = query.item[8]
			var/bantype = query.item[9]

			var/expires = ""
			if(text2num(duration) > 0)
				expires = " The ban is for [duration] minutes and expires on [expiration] (server time)."

			var/desc = "\nReason: You, or another user of this computer or connection ([pckey]) is banned from playing here. The ban reason is:\n[reason]\nThis ban was applied by [ackey] on [bantime], [expires]"

			return list("reason"="[bantype]", "desc"="[desc]")
		return ..()	//default pager ban stuff


code:
var/CMinutes = null
var/savefile/Banlist

/proc/CheckBan(var/ckey, var/id, var/address)
	if(!Banlist)		// if Banlist cannot be located for some reason
		LoadBans()		// try to load the bans
		if(!Banlist)	// uh oh, can't find bans!
			return 0	// ABORT ABORT ABORT

	. = list()
	var/appeal
	if(config && config.banappeals)
		appeal = "\nFor more information on your ban, or to appeal, head to [config.banappeals]"
	Banlist.cd = "/base"
	if( "[ckey][id]" in Banlist.dir )
		Banlist.cd = "[ckey][id]"
		if (Banlist["temp"])
			if (!GetExp(Banlist["minutes"]))
				ClearTempbans()
				return 0
			else
				.["desc"] = "\nReason: [Banlist["reason"]]\nExpires: [GetExp(Banlist["minutes"])]\nBy: [Banlist["bannedby"]][appeal]"
		else
			Banlist.cd	= "/base/[ckey][id]"
			.["desc"]	= "\nReason: [Banlist["reason"]]\nExpires: PERMENANT\nBy: [Banlist["bannedby"]][appeal]"
		.["reason"]	= "ckey/id"
		return .
	else
		for (var/A in Banlist.dir)
			Banlist.cd = "/base/[A]"
			var/matches
			if( ckey == Banlist["key"] )
				matches += "ckey"
			if( id == Banlist["id"] )
				if(matches)
					matches += "/"
				matches += "id"
			if( address == Banlist["ip"] )
				if(matches)
					matches += "/"
				matches += "ip"

			if(matches)
				if(Banlist["temp"])
					if (!GetExp(Banlist["minutes"]))
						ClearTempbans()
						return 0
					else
						.["desc"] = "\nReason: [Banlist["reason"]]\nExpires: [GetExp(Banlist["minutes"])]\nBy: [Banlist["bannedby"]][appeal]"
				else
					.["desc"] = "\nReason: [Banlist["reason"]]\nExpires: PERMENANT\nBy: [Banlist["bannedby"]][appeal]"
				.["reason"] = matches
				return .
	return 0

/proc/UpdateTime() //No idea why i made this a proc.
	CMinutes = (world.realtime / 10) / 60
	return 1

/proc/LoadBans()

	Banlist = new("data/banlist.bdb")
	log_admin("Loading Banlist")

	if (!length(Banlist.dir)) log_admin("Banlist is empty.")

	if (!Banlist.dir.Find("base"))
		log_admin("Banlist missing base dir.")
		Banlist.dir.Add("base")
		Banlist.cd = "/base"
	else if (Banlist.dir.Find("base"))
		Banlist.cd = "/base"

	ClearTempbans()
	return 1

/proc/ClearTempbans()
	UpdateTime()

	Banlist.cd = "/base"
	for (var/A in Banlist.dir)
		Banlist.cd = "/base/[A]"
		if (!Banlist["key"] || !Banlist["id"])
			RemoveBan(A)
			log_admin("Invalid Ban.")
			message_admins("Invalid Ban.")
			continue

		if (!Banlist["temp"]) continue
		if (CMinutes >= Banlist["minutes"]) RemoveBan(A)

	return 1

/proc/AddBan(ckey, computerid, reason, bannedby, temp, minutes, address)

	var/bantimestamp

	if (temp)
		UpdateTime()
		bantimestamp = CMinutes + minutes

	Banlist.cd = "/base"
	if ( Banlist.dir.Find("[ckey][computerid]") )
		usr << text("Ban already exists.")
		return 0
	else
		Banlist.dir.Add("[ckey][computerid]")
		Banlist.cd = "/base/[ckey][computerid]"
		Banlist["key"] << ckey
		Banlist["id"] << computerid
		Banlist["ip"] << address
		Banlist["reason"] << reason
		Banlist["bannedby"] << bannedby
		Banlist["temp"] << temp
		if (temp)
			Banlist["minutes"] << bantimestamp
		if(!temp)
			notes_add(ckey, "Permanently banned - [reason]")
		else
			notes_add(ckey, "Banned for [minutes] minutes - [reason]")
	return 1

/proc/RemoveBan(foldername)
	var/key
	var/id

	Banlist.cd = "/base/[foldername]"
	Banlist["key"] >> key
	Banlist["id"] >> id
	Banlist.cd = "/base"

	if (!Banlist.dir.Remove(foldername)) return 0

	if(!usr)
		log_admin("Ban Expired: [key]")
		message_admins("Ban Expired: [key]")
	else
		ban_unban_log_save("[key_name(usr)] unbanned [key]")
		log_admin("[key_name(usr)] unbanned [key]")
		message_admins("[key_name_admin(usr)] unbanned: [key]")
		feedback_inc("ban_unban",1)
		usr.client.holder.DB_ban_unban( ckey(key), BANTYPE_ANY_FULLBAN)
	for (var/A in Banlist.dir)
		Banlist.cd = "/base/[A]"
		if (key == Banlist["key"] /*|| id == Banlist["id"]*/)
			Banlist.cd = "/base"
			Banlist.dir.Remove(A)
			continue

	return 1

/proc/GetExp(minutes as num)
	UpdateTime()
	var/exp = minutes - CMinutes
	if (exp <= 0)
		return 0
	else
		var/timeleftstring
		if (exp >= 1440) //1440 = 1 day in minutes
			timeleftstring = "[round(exp / 1440, 0.1)] Days"
		else if (exp >= 60) //60 = 1 hour in minutes
			timeleftstring = "[round(exp / 60, 0.1)] Hours"
		else
			timeleftstring = "[exp] Minutes"
		return timeleftstring

/datum/admins/proc/unbanpanel()
	var/count = 0
	var/dat
	//var/dat = "
Unban Player: \blue(U) = Unban , (E) = Edit Ban\green (Total

"
	Banlist.cd = "/base"
	for (var/A in Banlist.dir)
		count++
		Banlist.cd = "/base/[A]"
		var/ref		= "\ref[src]"
		var/key		= Banlist["key"]
		var/id		= Banlist["id"]
		var/ip		= Banlist["ip"]
		var/reason	= Banlist["reason"]
		var/by		= Banlist["bannedby"]
		var/expiry
		if(Banlist["temp"])
			expiry = GetExp(Banlist["minutes"])
			if(!expiry)		expiry = "Removal Pending"
		else				expiry = "Permaban"

		dat += text("
(U)(E) Key: [key]	ComputerID: [id]	IP: [ip]	 [expiry]	(By: [by])	(Reason: [reason])
")

	dat += "
"
	dat = "
Bans: (U) = Unban , (E) = Edit Ban - ([count] Bans)

[dat]"
	usr << browse(dat, "window=unbanp;size=875x400")

//////////////////////////////////// DEBUG ////////////////////////////////////

/proc/CreateBans()

	UpdateTime()

	var/i
	var/last

	for(i=0, i<1001, i++)
		var/a = pick(1,0)
		var/b = pick(1,0)
		if(b)
			Banlist.cd = "/base"
			Banlist.dir.Add("trash[i]trashid[i]")
			Banlist.cd = "/base/trash[i]trashid[i]"
			Banlist["key"] << "trash[i]"
		else
			Banlist.cd = "/base"
			Banlist.dir.Add("[last]trashid[i]")
			Banlist.cd = "/base/[last]trashid[i]"
			Banlist["key"] << last
		Banlist["id"] << "trashid[i]"
		Banlist["reason"] << "Trashban[i]."
		Banlist["temp"] << a
		Banlist["minutes"] << CMinutes + rand(1,2000)
		Banlist["bannedby"] << "trashmin"
		last = "trash[i]"

	Banlist.cd = "/base"

/proc/ClearAllBans()
	Banlist.cd = "/base"
	for (var/A in Banlist.dir)
		RemoveBan(A)