code:
/obj/item/organ
	name = "organ"
	icon = 'icons/obj/surgery.dmi'
	var/mob/living/carbon/owner = null
	var/status = ORGAN_ORGANIC

//Old Datum Limbs:
// code/modules/unused/limbs.dm

/obj/item/organ/limb
	name = "limb"
	var/body_part = null
	var/brutestate = 0
	var/burnstate = 0
	var/brute_dam = 0
	var/burn_dam = 0
	var/max_damage = 0
	var/list/embedded_objects = list()

/obj/item/organ/limb/chest
	name = "chest"
	desc = "why is it detached..."
	icon_state = "chest"
	max_damage = 200
	body_part = CHEST

/obj/item/organ/limb/head
	name = "head"
	desc = "what a way to get a head in life..."
	icon_state = "head"
	max_damage = 200
	body_part = HEAD

/obj/item/organ/limb/l_arm
	name = "l_arm"
	desc = "why is it detached..."
	icon_state = "l_arm"
	max_damage = 75
	body_part = ARM_LEFT

/obj/item/organ/limb/l_leg
	name = "l_leg"
	desc = "why is it detached..."
	icon_state = "l_leg"
	max_damage = 75
	body_part = LEG_LEFT

/obj/item/organ/limb/r_arm
	name = "r_arm"
	desc = "why is it detached..."
	icon_state = "r_arm"
	max_damage = 75
	body_part = ARM_RIGHT

/obj/item/organ/limb/r_leg
	name = "r_leg"
	desc = "why is it detached..."
	icon_state = "r_leg"
	max_damage = 75
	body_part = LEG_RIGHT

/obj/item/organ/severedtail
	name = "tail"
	desc = "A severed tail."
	icon_state = "severedtail"
	color = "#161"
	var/markings = "Smooth"

//Applies brute and burn damage to the organ. Returns 1 if the damage-icon states changed at all.
//Damage will not exceed max_damage using this proc
//Cannot apply negative damage
/obj/item/organ/limb/proc/take_damage(brute, burn)
	if(owner && (owner.status_flags & GODMODE))
		return 0	//godmode
	brute	= max(brute,0)
	burn	= max(burn,0)

	if(status == ORGAN_ROBOTIC) //This makes robolimbs not damageable by chems and makes it stronger
		brute = max(0, brute - 5)
		burn = max(0, burn - 4)

	var/can_inflict = max_damage - (brute_dam + burn_dam)
	if(!can_inflict)
		return 0

	if((brute + burn) < can_inflict)
		brute_dam	+= brute
		burn_dam	+= burn
	else
		if(brute > 0)
			if(burn > 0)
				brute	= round( (brute/(brute+burn)) * can_inflict, 1 )
				burn	= can_inflict - brute	//gets whatever damage is left over
				brute_dam	+= brute
				burn_dam	+= burn
			else
				brute_dam	+= can_inflict
		else
			if(burn > 0)
				burn_dam	+= can_inflict
			else
				return 0
	if(owner)
		owner.updatehealth()
	return update_organ_icon()

//Heals brute and burn damage for the organ. Returns 1 if the damage-icon states changed at all.
//Damage cannot go below zero.
//Cannot remove negative damage (i.e. apply damage)
/obj/item/organ/limb/proc/heal_damage(brute, burn, robotic)

	if(robotic && status != ORGAN_ROBOTIC) // This makes organic limbs not heal when the proc is in Robotic mode.
		brute = max(0, brute - 3)
		burn = max(0, burn - 3)

	if(!robotic && status == ORGAN_ROBOTIC) // This makes robolimbs not healable by chems.
		brute = max(0, brute - 3)
		burn = max(0, burn - 3)

	brute_dam	= max(brute_dam - brute, 0)
	burn_dam	= max(burn_dam - burn, 0)
	if(owner)
		owner.updatehealth()
	return update_organ_icon()

//Returns total damage...kinda pointless really
/obj/item/organ/limb/proc/get_damage()
	return brute_dam + burn_dam

//Updates an organ's brute/burn states for use by update_damage_overlays()
//Returns 1 if we need to update overlays. 0 otherwise.
/obj/item/organ/limb/proc/update_organ_icon()
	if(status == ORGAN_ORGANIC) //Robotic limbs show no damage - RR
		var/tbrute	= round( (brute_dam/max_damage)*3, 1 )
		var/tburn	= round( (burn_dam/max_damage)*3, 1 )
		if((tbrute != brutestate) || (tburn != burnstate))
			brutestate = tbrute
			burnstate = tburn
			return 1
		return 0

//Returns a display name for the organ
/obj/item/organ/limb/proc/getDisplayName() //Added "Chest" and "Head" just in case, this may not be needed
	switch(name)
		if("l_leg")
			return "left leg"
		if("r_leg")
			return "right leg"
		if("l_arm")
			return "left arm"
		if("r_arm")
			return "right arm"
		if("chest")
			return "chest"
		if("head")
			return "head"
		else
			return name

//Remove all embedded objects from all limbs on the human mob
/mob/living/carbon/human/proc/remove_all_embedded_objects()
	var/turf/T = get_turf(src)

	for(var/obj/item/organ/limb/L in organs)
		for(var/obj/item/I in L.embedded_objects)
			L.embedded_objects -= I
			I.loc = T

	clear_alert("embeddedobject")

/mob/living/carbon/human/proc/has_embedded_objects()
	. = 0
	for(var/obj/item/organ/limb/L in organs)
		for(var/obj/item/I in L.embedded_objects)
			return 1


code:
/obj/item/organ/internal
	origin_tech = "biotech=2"
	force = 1
	w_class = 2
	throwforce = 0
	var/zone = "chest"
	var/slot
	// DO NOT add slots with matching names to different zones - it will break internal_organs_slot list!
	var/vital = 0

/obj/item/organ/internal/proc/Insert(mob/living/carbon/M, special = 0)
	if(!iscarbon(M) || owner == M)
		return

	var/obj/item/organ/internal/replaced = M.getorganslot(slot)
	if(replaced)
		replaced.Remove(M, special = 1)

	owner = M
	M.internal_organs |= src
	M.internal_organs_slot[slot] = src
	loc = null
	for(var/X in actions)
		var/datum/action/A = X
		A.Grant(M)

/obj/item/organ/internal/proc/Remove(mob/living/carbon/M, special = 0)
	owner = null
	if(M)
		M.internal_organs -= src
		if(M.internal_organs_slot[slot] == src)
			M.internal_organs_slot.Remove(slot)
		if(vital && !special)
			M.death()
	for(var/X in actions)
		var/datum/action/A = X
		A.Remove(M)

/obj/item/organ/internal/proc/on_find(mob/living/finder)
	return

/obj/item/organ/internal/proc/on_life()
	return

/obj/item/organ/internal/examine(mob/user)
	..()
	if(status == ORGAN_ROBOTIC && crit_fail)
		user << "[src] seems to be broken!"

/obj/item/organ/internal/proc/prepare_eat()
	var/obj/item/weapon/reagent_containers/food/snacks/organ/S = new
	S.name = name
	S.desc = desc
	S.icon = icon
	S.icon_state = icon_state
	S.origin_tech = origin_tech
	S.w_class = w_class

	return S

/obj/item/weapon/reagent_containers/food/snacks/organ
	name = "appendix"
	icon_state = "appendix"
	icon = 'icons/obj/surgery.dmi'
	list_reagents = list("nutriment" = 5)

/obj/item/organ/internal/Destroy()
	if(owner)
		Remove(owner, 1)
	return ..()

/obj/item/organ/internal/attack(mob/living/carbon/M, mob/user)
	if(M == user && ishuman(user))
		var/mob/living/carbon/human/H = user
		if(status == ORGAN_ORGANIC)
			var/obj/item/weapon/reagent_containers/food/snacks/S = prepare_eat()
			if(S)
				H.drop_item()
				H.put_in_active_hand(S)
				S.attack(H, H)
				qdel(src)
	else
		..()

/obj/item/organ/internal/item_action_slot_check(slot,mob/user)
	return //so we don't grant the organ's action to mobs who pick up the organ.

//Looking for brains?
//Try code/modules/mob/living/carbon/brain/brain_item.dm

/obj/item/organ/internal/heart
	name = "heart"
	icon_state = "heart-on"
	zone = "chest"
	slot = "heart"
	origin_tech = "biotech=3"
	var/beating = 1
	var/icon_base = "heart"

/obj/item/organ/internal/heart/update_icon()
	if(beating)
		icon_state = "[icon_base]-on"
	else
		icon_state = "[icon_base]-off"

/obj/item/organ/internal/heart/Remove(mob/living/carbon/M, special = 0)
	..()
	if(ishuman(M))
		var/mob/living/carbon/human/H = M
		if(H.stat == DEAD || H.heart_attack)
			Stop()
			return
		if(!special)
			H.heart_attack = 1

	spawn(120)
		if(!owner)
			Stop()

/obj/item/organ/internal/heart/attack_self(mob/user)
	..()
	if(!beating)
		Restart()
		spawn(80)
			if(!owner)
				Stop()

/obj/item/organ/internal/heart/Insert(mob/living/carbon/M, special = 0)
	..()
	if(ishuman(M) && beating)
		var/mob/living/carbon/human/H = M
		if(H.heart_attack)
			H.heart_attack = 0
			return

/obj/item/organ/internal/heart/proc/Stop()
	beating = 0
	update_icon()
	return 1

/obj/item/organ/internal/heart/proc/Restart()
	beating = 1
	update_icon()
	return 1

/obj/item/organ/internal/heart/prepare_eat()
	var/obj/S = ..()
	S.icon_state = "heart-off"
	return S

/obj/item/organ/internal/heart/cursed
	name = "cursed heart"
	desc = "it needs to be pumped..."
	icon_state = "cursedheart-off"
	icon_base = "cursedheart"
	origin_tech = "biotech=5"
	actions_types = list(/datum/action/item_action/organ_action/cursed_heart)
	var/last_pump = 0
	var/pump_delay = 30 //you can pump 1 second early, for lag, but no more (otherwise you could spam heal)
	var/blood_loss = 100 //600 blood is human default, so 5 failures (below 122 blood is where humans die because reasons?)

	//How much to heal per pump, negative numbers would HURT the player
	var/heal_brute = 0
	var/heal_burn = 0
	var/heal_oxy = 0

/obj/item/organ/internal/heart/cursed/attack(mob/living/carbon/human/H, mob/living/carbon/human/user, obj/target)
	if(H == user && istype(H))
		playsound(user,'sound/effects/singlebeat.ogg',40,1)
		user.drop_item()
		Insert(user)
	else
		return ..()

/obj/item/organ/internal/heart/cursed/on_life()
	if(world.time > (last_pump + pump_delay))
		if(ishuman(owner) && owner.client) //While this entire item exists to make people suffer, they can't control disconnects.
			var/mob/living/carbon/human/H = owner
			H.vessel.remove_reagent("blood",blood_loss)
			H << "You have to keep pumping your blood!"
			if(H.client)
				H.client.color = "red" //bloody screen so real
		else
			last_pump = world.time //lets be extra fair *sigh*

/obj/item/organ/internal/heart/cursed/Insert(mob/living/carbon/M, special = 0)
	..()
	if(owner)
		owner << "Your heart has been replaced with a cursed one, you have to pump this one manually otherwise you'll die!"

/datum/action/item_action/organ_action/cursed_heart
	name = "pump your blood"

//You are now brea- pumping blood manually
/datum/action/item_action/organ_action/cursed_heart/Trigger()
	. = ..()
	if(. && istype(target,/obj/item/organ/internal/heart/cursed))
		var/obj/item/organ/internal/heart/cursed/cursed_heart = target

		if(world.time < (cursed_heart.last_pump + (cursed_heart.pump_delay-10))) //no spam
			owner << "Too soon!"
			return

		cursed_heart.last_pump = world.time
		playsound(owner,'sound/effects/singlebeat.ogg',40,1)
		owner << "Your heart beats."

		var/mob/living/carbon/human/H = owner
		if(istype(H))
			H.vessel.add_reagent("blood",(cursed_heart.blood_loss*0.5))//gain half the blood back from a failure
			if(owner.client)
				owner.client.color = ""

			H.adjustBruteLoss(-cursed_heart.heal_brute)
			H.adjustFireLoss(-cursed_heart.heal_burn)
			H.adjustOxyLoss(-cursed_heart.heal_oxy)

/obj/item/organ/internal/lungs
	name = "lungs"
	icon_state = "lungs"
	zone = "chest"
	slot = "lungs"
	gender = PLURAL
	w_class = 3

/obj/item/organ/internal/lungs/prepare_eat()
	var/obj/S = ..()
	S.reagents.add_reagent("salbutamol", 5)
	return S

/obj/item/organ/internal/appendix
	name = "appendix"
	icon_state = "appendix"
	zone = "groin"
	slot = "appendix"
	var/inflamed = 0

/obj/item/organ/internal/appendix/update_icon()
	if(inflamed)
		icon_state = "appendixinflamed"
		name = "inflamed appendix"
	else
		icon_state = "appendix"
		name = "appendix"

/obj/item/organ/internal/appendix/Remove(mob/living/carbon/M, special = 0)
	for(var/datum/disease/appendicitis/A in M.viruses)
		A.cure()
		inflamed = 1
	update_icon()
	..()

/obj/item/organ/internal/appendix/Insert(mob/living/carbon/M, special = 0)
	..()
	if(inflamed)
		M.AddDisease(new /datum/disease/appendicitis)

/obj/item/organ/internal/appendix/prepare_eat()
	var/obj/S = ..()
	if(inflamed)
		S.reagents.add_reagent("????", 5)
	return S

/obj/item/organ/internal/shadowtumor
	name = "black tumor"
	desc = "A tiny black mass with red tendrils trailing from it. It seems to shrivel in the light."
	icon_state = "blacktumor"
	origin_tech = "biotech=4"
	w_class = 1
	zone = "head"
	slot = "brain_tumor"
	var/health = 3

/obj/item/organ/internal/shadowtumor/New()
	..()
	SSobj.processing |= src

/obj/item/organ/internal/shadowtumor/Destroy()
	SSobj.processing.Remove(src)
	..()

/obj/item/organ/internal/shadowtumor/process()
	if(isturf(loc))
		var/turf/T = loc
		var/light_count = T.get_lumcount()
		if(light_count > LIGHT_DAM_THRESHOLD && health > 0) //Die in the light
			health--
		else if(light_count < LIGHT_HEAL_THRESHOLD && health < 3) //Heal in the dark
			health++
		if(health <= 0)
			visible_message("[src] collapses in on itself!")
			qdel(src)

license: mit


code:
/datum/surgery/plastic_surgery
	name = "plastic surgery"
	steps = list(/datum/surgery_step/incise, /datum/surgery_step/retract_skin, /datum/surgery_step/reshape_face, /datum/surgery_step/close)
	possible_locs = list("head")

//reshape_face
/datum/surgery_step/reshape_face
	name = "reshape face"
	implements = list(/obj/item/weapon/scalpel = 100, /obj/item/weapon/kitchen/knife = 50, /obj/item/weapon/wirecutters = 35)
	time = 64

/datum/surgery_step/reshape_face/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	user.visible_message("[user] begins to alter [target]'s appearance.", "You begin to alter [target]'s appearance...")

/datum/surgery_step/reshape_face/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	if(target.status_flags & DISFIGURED)
		target.status_flags &= ~DISFIGURED
		user.visible_message("[user] successfully restores [target]'s appearance!", "You successfully restore [target]'s appearance.")
	else
		var/oldname = target.real_name
		target.real_name = target.dna.species.random_name(target.gender,1)
		var/newname = target.real_name	//something about how the code handles names required that I use this instead of target.real_name
		user.visible_message("[user] alters [oldname]'s appearance completely, they are now [newname]!", "You alter [oldname]'s appearance completely, they are now [newname].")
	if(ishuman(target))
		var/mob/living/carbon/human/H = target
		H.sec_hud_set_ID()
	return 1


code:
/datum/surgery/embedded_removal
	name = "removal of embedded objects"
	steps = list(/datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/retract_skin, /datum/surgery_step/remove_object)
	possible_locs = list("r_arm","l_arm","r_leg","l_leg","chest","head")

/datum/surgery_step/remove_object
	name = "remove embedded objects"
	time = 32
	accept_hand = 1
	var/obj/item/organ/limb/L = null

/datum/surgery_step/remove_object/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	L = surgery.organ
	if(L)
		user.visible_message("[user] looks for objects embedded in [target]'s [parse_zone(user.zone_selected)].", "You look for objects embedded in [target]'s [parse_zone(user.zone_selected)]...")
	else
		user.visible_message("[user] looks for [target]'s [parse_zone(user.zone_selected)].", "You look for [target]'s [parse_zone(user.zone_selected)]...")

/datum/surgery_step/remove_object/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	if(L)
		if(ishuman(target))
			var/mob/living/carbon/human/H = target
			var/objects = 0
			for(var/obj/item/I in L.embedded_objects)
				objects++
				I.loc = get_turf(H)
				L.embedded_objects -= I

			if(objects > 0)
				user.visible_message("[user] sucessfully removes [objects] objects from [H]'s [L.getDisplayName()]!", "You sucessfully remove [objects] objects from [H]'s [L.getDisplayName()].")
			else
				user << "You find no objects embedded in [H]'s [L.getDisplayName()]!"

	else
		user << "You can't find [target]'s [parse_zone(user.zone_selected)], let alone any objects embedded in it!"

	return 1


code:
/datum/surgery
	var/name = "surgery"
	var/status = 1
	var/list/steps = list()										//Steps in a surgery
	var/step_in_progress = 0									//Actively performing a Surgery
	var/can_cancel = 1											//Can cancel this surgery after step 1 with cautery
	var/list/species = list(/mob/living/carbon/human)			//Acceptable Species
	var/location = "chest"										//Surgery location
	var/requires_organic_bodypart = 1							//Prevents you from performing an operation on robotic limbs
	var/list/possible_locs = list() 							//Multiple locations -- c0
	var/ignore_clothes = 0										//This surgery ignores clothes
	var/obj/item/organ/organ									//Operable body part

/datum/surgery/proc/can_start(mob/user, mob/living/carbon/target)
	// if 0 surgery wont show up in list
	// put special restrictions here
	return 1

/datum/surgery/proc/next_step(mob/user, mob/living/carbon/target)
	if(step_in_progress)
		return

	var/datum/surgery_step/S = get_surgery_step()
	if(S)
		if(S.try_op(user, target, user.zone_selected, user.get_active_hand(), src))
			return 1
	return 0

/datum/surgery/proc/get_surgery_step()
	var/step_type = steps[status]
	return new step_type

/datum/surgery/proc/complete(mob/living/carbon/human/target)
	target.surgeries -= src
	src = null

//INFO
//Check /mob/living/carbon/attackby for how surgery progresses, and also /mob/living/carbon/attack_hand.
//As of Feb 21 2013 they are in code/modules/mob/living/carbon/carbon.dm, lines 459 and 51 respectively.
//Other important variables are var/list/surgeries (/mob/living) and var/list/internal_organs (/mob/living/carbon)
// var/list/organs (/mob/living/carbon/human) is the LIMBS of a Mob.
//Surgical procedures are initiated by attempt_initiate_surgery(), which is called by surgical drapes and bedsheets.
// /code/modules/surgery/multiple_location_example.dm contains steps to setup a multiple location operation.

//TODO
//specific steps for some surgeries (fluff text)
//R&D researching new surgeries (especially for non-humans)
//more interesting failure options
//randomised complications
//more surgeries!
//add a probability modifier for the state of the surgeon- health, twitching, etc. blindness, god forbid.
//helper for converting a zone_sel.selecting to body part (for damage)

//RESOLVED ISSUES //"Todo" jobs that have been completed
//combine hands/feet into the arms - Hands/feet were removed - RR
//surgeries (not steps) that can be initiated on any body part (corresponding with damage locations) - Call this one done, see multiple_location_example.dm - RR


code:
/datum/surgery_step
	var/list/implements = list()	//format is path = probability of success. alternatively
	var/implement_type = null		//the current type of implement used. This has to be stored, as the actual typepath of the tool may not match the list type.
	var/accept_hand = 0				//does the surgery step require an open hand? If true, ignores implements. Compatible with accept_any_item.
	var/accept_any_item = 0			//does the surgery step accept any item? If true, ignores implements. Compatible with require_hand.
	var/time = 10					//how long does the step take?
	var/name

/datum/surgery_step/proc/try_op(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	var/success = 0
	if(accept_hand)
		if(!tool)
			success = 1
	if(accept_any_item)
		if(tool && tool_check(user, tool))
			success = 1
	else
		for(var/path in implements)
			if(istype(tool, path))
				implement_type = path
				if(tool_check(user, tool))
					success = 1

	if(success)
		if(target_zone == surgery.location)
			if(get_location_accessible(target, target_zone) || surgery.ignore_clothes)
				initiate(user, target, target_zone, tool, surgery)
				return 1
			else
				user << "You need to expose [target]'s [parse_zone(target_zone)] to perform surgery on it!"
				return 1	//returns 1 so we don't stab the guy in the dick or wherever.
	if(isrobot(user) && user.a_intent != "harm") //to save asimov borgs a LOT of heartache
		return 1
	return 0

/datum/surgery_step/proc/initiate(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	surgery.step_in_progress = 1

	if(preop(user, target, target_zone, tool, surgery) == -1)
		surgery.step_in_progress = 0
		return

	if(do_after(user, time, target = target))
		var/advance = 0
		var/prob_chance = 100

		if(implement_type)	//this means it isn't a require hand or any item step.
			prob_chance = implements[implement_type]
		prob_chance *= get_location_modifier(target)

		if(prob(prob_chance) || isrobot(user))
			if(success(user, target, target_zone, tool, surgery))
				advance = 1
		else
			if(failure(user, target, target_zone, tool, surgery))
				advance = 1

		if(advance)
			surgery.status++
			if(surgery.status > surgery.steps.len)
				surgery.complete(target)

	surgery.step_in_progress = 0

/datum/surgery_step/proc/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	user.visible_message("[user] begins to perform surgery on [target].", "You begin to perform surgery on [target]...")

/datum/surgery_step/proc/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	user.visible_message("[user] succeeds!", "You succeed.")
	return 1

/datum/surgery_step/proc/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	user.visible_message("[user] screws up!", "You screw up!")
	return 0

/datum/surgery_step/proc/tool_check(mob/user, obj/item/tool)
	return 1


code:
// The detachment and attachment of lizard tails
// Dismemberment lite; can/should be generalized aka augs when we get more mutant parts and/or for actual dismemberment

// TAIL REMOVAL

/datum/surgery/tail_removal
	name = "tail removal"
	steps = list(/datum/surgery_step/sever_tail, /datum/surgery_step/close)
	species = list(/mob/living/carbon/human)
	possible_locs = list("groin")

/datum/surgery/tail_removal/can_start(mob/user, mob/living/carbon/target)
	var/mob/living/carbon/human/L = target
	if(("tail_lizard" in L.dna.species.mutant_bodyparts) || ("waggingtail_lizard" in L.dna.species.mutant_bodyparts))
		return 1
	return 0

/datum/surgery_step/sever_tail
	name = "sever tail"
	implements = list(/obj/item/weapon/scalpel = 100, /obj/item/weapon/circular_saw = 100, /obj/item/weapon/melee/energy/sword/cyborg/saw = 100, /obj/item/weapon/melee/arm_blade = 80, /obj/item/weapon/twohanded/required/chainsaw = 80, /obj/item/weapon/mounted_chainsaw = 80, /obj/item/weapon/twohanded/fireaxe = 50, /obj/item/weapon/hatchet = 40, /obj/item/weapon/kitchen/knife/butcher = 25)
	time = 64

/datum/surgery_step/sever_tail/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	user.visible_message("[user] begins to sever [target]'s tail!", "You begin to sever [target]'s tail...")

/datum/surgery_step/sever_tail/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	var/mob/living/carbon/human/L = target
	user.visible_message("[user] severs [L]'s tail!", "You sever [L]'s tail.")
	if("tail_lizard" in L.dna.species.mutant_bodyparts)
		L.dna.species.mutant_bodyparts -= "tail_lizard"
	else if("waggingtail_lizard" in L.dna.species.mutant_bodyparts)
		L.dna.species.mutant_bodyparts -= "waggingtail_lizard"
	if("spines" in L.dna.features)
		L.dna.features -= "spines"
	var/obj/item/organ/severedtail/S = new(get_turf(target))
	S.color = "#[L.dna.features["mcolor"]]"
	S.markings = "[L.dna.features["tail"]]"
	L.update_body()
	return 1

// TAIL ATTACHMENT

/datum/surgery/tail_attachment
	name = "tail attachment"
	steps = list(/datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/retract_skin, /datum/surgery_step/replace, /datum/surgery_step/attach_tail, /datum/surgery_step/close)
	species = list(/mob/living/carbon/human)
	possible_locs = list("groin")

/datum/surgery/tail_attachment/can_start(mob/user, mob/living/carbon/target)
	var/mob/living/carbon/human/L = target
	if(!("tail_lizard" in L.dna.species.mutant_bodyparts) && !("waggingtail_lizard" in L.dna.species.mutant_bodyparts))
		return 1
	return 0

/datum/surgery_step/attach_tail
	name = "attach tail"
	implements = list(/obj/item/organ/severedtail = 100)
	time = 64

/datum/surgery_step/attach_tail/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	user.visible_message("[user] begins to attach a tail to [target]!", "You begin to attach the tail to [target]...")

/datum/surgery_step/attach_tail/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
	var/mob/living/carbon/human/L = target
	user.visible_message("[user] gives [L] a tail!", "You give [L] a tail. It adjusts to [L]'s melanin.") // fluff for color
	if(!(L.dna.features["mcolor"]))
		L.dna.features["mcolor"] = tool.color
	var/obj/item/organ/severedtail/T = tool
	L.dna.features["tail_lizard"] = T.markings
	L.dna.species.mutant_bodyparts += "tail_lizard"
	qdel(tool)
	L.update_body()
	return 1


code:
/obj/item/weapon/retractor
	name = "retractor"
	desc = "Retracts stuff."
	icon = 'icons/obj/surgery.dmi'
	icon_state = "retractor"
	materials = list(MAT_METAL=6000, MAT_GLASS=3000)
	flags = CONDUCT
	w_class = 1
	origin_tech = "materials=1;biotech=1"

/obj/item/weapon/hemostat
	name = "hemostat"
	desc = "You think you have seen this before."
	icon = 'icons/obj/surgery.dmi'
	icon_state = "hemostat"
	materials = list(MAT_METAL=5000, MAT_GLASS=2500)
	flags = CONDUCT
	w_class = 1
	origin_tech = "materials=1;biotech=1"
	attack_verb = list("attacked", "pinched")

/obj/item/weapon/cautery
	name = "cautery"
	desc = "This stops bleeding."
	icon = 'icons/obj/surgery.dmi'
	icon_state = "cautery"
	materials = list(MAT_METAL=2500, MAT_GLASS=750)
	flags = CONDUCT
	w_class = 1
	origin_tech = "materials=1;biotech=1"
	attack_verb = list("burnt")

/obj/item/weapon/surgicaldrill
	name = "surgical drill"
	desc = "You can drill using this item. You dig?"
	icon = 'icons/obj/surgery.dmi'
	icon_state = "drill"
	hitsound = 'sound/weapons/circsawhit.ogg'
	materials = list(MAT_METAL=10000, MAT_GLASS=6000)
	flags = CONDUCT
	force = 15
	w_class = 3
	origin_tech = "materials=1;biotech=1"
	attack_verb = list("drilled")

/obj/item/weapon/scalpel
	name = "scalpel"
	desc = "Cut, cut, and once more cut."
	icon = 'icons/obj/surgery.dmi'
	icon_state = "scalpel"
	flags = CONDUCT
	force = 10
	w_class = 1
	throwforce = 5
	throw_speed = 3
	throw_range = 5
	materials = list(MAT_METAL=4000, MAT_GLASS=1000)
	origin_tech = "materials=1;biotech=1"
	attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
	hitsound = 'sound/weapons/bladeslice.ogg'
	sharpness = IS_SHARP_ACCURATE

/obj/item/weapon/scalpel/suicide_act(mob/user)
	user.visible_message(pick("[user] is slitting \his wrists with [src]! It looks like \he's trying to commit suicide.", \
						"[user] is slitting \his throat with [src]! It looks like \he's trying to commit suicide.", \
						"[user] is slitting \his stomach open with [src]! It looks like \he's trying to commit seppuku."))
	return (BRUTELOSS)

/obj/item/weapon/circular_saw
	name = "circular saw"
	desc = "For heavy duty cutting."
	icon = 'icons/obj/surgery.dmi'
	icon_state = "saw"
	hitsound = 'sound/weapons/circsawhit.ogg'
	throwhitsound =  'sound/weapons/pierce.ogg'
	flags = CONDUCT
	force = 15
	w_class = 3
	throwforce = 9
	throw_speed = 2
	throw_range = 5
	materials = list(MAT_METAL=10000, MAT_GLASS=6000)
	origin_tech = "materials=1;biotech=1"
	attack_verb = list("attacked", "slashed", "sawed", "cut")
	sharpness = IS_SHARP

/obj/item/weapon/surgical_drapes
	name = "surgical drapes"
	desc = "Nanotrasen brand surgical drapes provide optimal safety and infection control."
	icon = 'icons/obj/surgery.dmi'
	icon_state = "surgical_drapes"
	w_class = 1
	origin_tech = "biotech=1"
	attack_verb = list("slapped")

/obj/item/weapon/surgical_drapes/attack(mob/living/M, mob/user)
	if(!attempt_initiate_surgery(src, M, user))
		..()


code:
// Bluespace crystals, used in telescience and when crushed it will blink you to a random turf.

/obj/item/weapon/ore/bluespace_crystal
	name = "bluespace crystal"
	desc = "A glowing bluespace crystal, not much is known about how they work. It looks very delicate."
	icon = 'icons/obj/telescience.dmi'
	icon_state = "bluespace_crystal"
	w_class = 1
	origin_tech = "bluespace=4;materials=3"
	points = 50
	var/blink_range = 8 // The teleport range when crushed/thrown at someone.

/obj/item/weapon/ore/bluespace_crystal/New()
	..()
	pixel_x = rand(-5, 5)
	pixel_y = rand(-5, 5)

/obj/item/weapon/ore/bluespace_crystal/attack_self(mob/user)
	user.visible_message("[user] crushes [src]!", "You crush [src]!")
	PoolOrNew(/obj/effect/particle_effect/sparks, loc)
	playsound(src.loc, "sparks", 50, 1)
	blink_mob(user)
	user.unEquip(src)
	qdel(src)

/obj/item/weapon/ore/bluespace_crystal/proc/blink_mob(mob/living/L)
	do_teleport(L, get_turf(L), blink_range, asoundin = 'sound/effects/phasein.ogg')

/obj/item/weapon/ore/bluespace_crystal/throw_impact(atom/hit_atom)
	if(!..()) // not caught in mid-air
		visible_message("[src] fizzles and disappears upon impact!")
		var/turf/T = get_turf(hit_atom)
		PoolOrNew(/obj/effect/particle_effect/sparks, T)
		playsound(src.loc, "sparks", 50, 1)
		if(isliving(hit_atom))
			blink_mob(hit_atom)
		qdel(src)

// Artifical bluespace crystal, doesn't give you much research.

/obj/item/weapon/ore/bluespace_crystal/artificial
	name = "artificial bluespace crystal"
	desc = "An artificially made bluespace crystal, it looks delicate."
	origin_tech = "bluespace=2"
	blink_range = 4 // Not as good as the organic stuff!
	points = 0 // nice try


code:
var/list/GPS_list = list()
/obj/item/device/gps
	name = "global positioning system"
	desc = "Helping lost spacemen find their way through the planets since 2016. Alt+click to toggle power."
	icon = 'icons/obj/telescience.dmi'
	icon_state = "gps-c"
	w_class = 2
	slot_flags = SLOT_BELT
	origin_tech = "programming=2;engineering=2"
	var/gpstag = "COM0"
	var/emped = 0
	var/turf/locked_location
	var/tracking = TRUE

/obj/item/device/gps/New()
	..()
	GPS_list.Add(src)
	name = "global positioning system ([gpstag])"
	overlays += "working"

/obj/item/device/gps/Destroy()
	GPS_list.Remove(src)
	return ..()

/obj/item/device/gps/emp_act(severity)
	emped = 1
	overlays -= "working"
	overlays += "emp"
	spawn(300)
		emped = 0
		overlays -= "emp"
		overlays += "working"

/obj/item/device/gps/AltClick(mob/user)
	if(!in_range(src, user))
		return
	if(emped)
		user << "It's busted!"
	if(tracking)
		overlays -= "working"
		user << "[src] is no longer tracking, or visible to other GPS devices."
		tracking = FALSE
	else
		overlays += "working"
		user << "[src] is now tracking, and visible to other GPS devices."
		tracking = TRUE

/obj/item/device/gps/attack_self(mob/user)
	if(!tracking)
		user << "[src] is turned off. Use alt+click to toggle it back on."
		return

	var/obj/item/device/gps/t = ""
	var/gps_window_height = 110 + GPS_list.len * 20 // Variable window height, depending on how many GPS units there are to show
	if(emped)
		t += "ERROR"
	else
		t += "
Set Tag "
		t += "
Tag: [gpstag]"
		if(locked_location && locked_location.loc)
			t += "
Bluespace coordinates saved: [locked_location.loc]"
			gps_window_height += 20

		for(var/obj/item/device/gps/G in GPS_list)
			var/turf/pos = get_turf(G)
			var/area/gps_area = get_area(G)
			var/tracked_gpstag = G.gpstag
			if(G.emped == 1)
				t += "
[tracked_gpstag]: ERROR"
			else if(G.tracking)
				t += "
[tracked_gpstag]: [format_text(gps_area.name)] ([pos.x], [pos.y], [pos.z])"
			else
				continue
	var/datum/browser/popup = new(user, "GPS", name, 360, min(gps_window_height, 800))
	popup.set_content(t)
	popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
	popup.open()

/obj/item/device/gps/Topic(href, href_list)
	..()
	if(href_list["tag"] )
		var/a = input("Please enter desired tag.", name, gpstag) as text
		a = uppertext(copytext(sanitize(a), 1, 5))
		if(in_range(src, usr))
			gpstag = a
			name = "global positioning system ([gpstag])"
			attack_self(usr)

/obj/item/device/gps/science
	icon_state = "gps-s"
	gpstag = "SCI0"

/obj/item/device/gps/engineering
	icon_state = "gps-e"
	gpstag = "ENG0"

/obj/item/device/gps/mining
	icon_state = "gps-m"
	gpstag = "MINE0"
	desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life."

/obj/item/device/gps/internal
	icon_state = null
	gpstag = "Eerie Signal"
	desc = "Report to a coder immediately."
	invisibility = 100


code:
///SCI TELEPAD///
/obj/machinery/telepad
	name = "telepad"
	desc = "A bluespace telepad used for teleporting objects to and from a location."
	icon = 'icons/obj/telescience.dmi'
	icon_state = "pad-idle"
	anchored = 1
	use_power = 1
	idle_power_usage = 200
	active_power_usage = 5000
	var/efficiency

/obj/machinery/telepad/New()
	..()
	component_parts = list()
	component_parts += new /obj/item/weapon/circuitboard/telesci_pad(null)
	component_parts += new /obj/item/weapon/ore/bluespace_crystal/artificial(null)
	component_parts += new /obj/item/weapon/ore/bluespace_crystal/artificial(null)
	component_parts += new /obj/item/weapon/stock_parts/capacitor(null)
	component_parts += new /obj/item/weapon/stock_parts/console_screen(null)
	component_parts += new /obj/item/stack/cable_coil(null, 1)
	RefreshParts()

/obj/machinery/telepad/RefreshParts()
	var/E
	for(var/obj/item/weapon/stock_parts/capacitor/C in component_parts)
		E += C.rating
	efficiency = E

/obj/machinery/telepad/attackby(obj/item/I, mob/user, params)
	if(default_deconstruction_screwdriver(user, "pad-idle-o", "pad-idle", I))
		return

	if(panel_open)
		if(istype(I, /obj/item/device/multitool))
			var/obj/item/device/multitool/M = I
			M.buffer = src
			user << "You save the data in the [I.name]'s buffer."

	if(exchange_parts(user, I))
		return

	default_deconstruction_crowbar(I)

//CARGO TELEPAD//
/obj/machinery/telepad_cargo
	name = "cargo telepad"
	desc = "A telepad used by the Rapid Crate Sender."
	icon = 'icons/obj/telescience.dmi'
	icon_state = "pad-idle"
	anchored = 1
	use_power = 1
	idle_power_usage = 20
	active_power_usage = 500
	var/stage = 0
/obj/machinery/telepad_cargo/attackby(obj/item/weapon/W, mob/user, params)
	if(istype(W, /obj/item/weapon/wrench))
		anchored = 0
		playsound(src, 'sound/items/Ratchet.ogg', 50, 1)
		if(anchored)
			anchored = 0
			user << "\The [src] can now be moved."
		else if(!anchored)
			anchored = 1
			user << "\The [src] is now secured."
	if(istype(W, /obj/item/weapon/screwdriver))
		if(stage == 0)
			playsound(src, 'sound/items/Screwdriver.ogg', 50, 1)
			user << "You unscrew the telepad's tracking beacon."
			stage = 1
		else if(stage == 1)
			playsound(src, 'sound/items/Screwdriver.ogg', 50, 1)
			user << "You screw in the telepad's tracking beacon."
			stage = 0
	if(istype(W, /obj/item/weapon/weldingtool) && stage == 1)
		playsound(src, 'sound/items/Welder.ogg', 50, 1)
		user << "You disassemble the telepad."
		new /obj/item/stack/sheet/metal(get_turf(src))
		new /obj/item/stack/sheet/glass(get_turf(src))
		qdel(src)

///TELEPAD CALLER///
/obj/item/device/telepad_beacon
	name = "telepad beacon"
	desc = "Use to warp in a cargo telepad."
	icon = 'icons/obj/radio.dmi'
	icon_state = "beacon"
	item_state = "beacon"
	origin_tech = "bluespace=3"

/obj/item/device/telepad_beacon/attack_self(mob/user)
	if(user)
		user << "Locked In"
		new /obj/machinery/telepad_cargo(user.loc)
		playsound(src, 'sound/effects/pop.ogg', 100, 1, 1)
		qdel(src)
	return

///HANDHELD TELEPAD USER///
/obj/item/weapon/rcs
	name = "rapid-crate-sender (RCS)"
	desc = "Use this to send crates and closets to cargo telepads."
	icon = 'icons/obj/telescience.dmi'
	icon_state = "rcs"
	flags = CONDUCT
	force = 10
	throwforce = 10
	throw_speed = 2
	throw_range = 5
	var/rcharges = 10
	var/obj/machinery/pad = null
	var/last_charge = 30
	var/mode = 0
	var/rand_x = 0
	var/rand_y = 0
	var/emagged = 0
	var/teleporting = 0

/obj/item/weapon/rcs/New()
	..()
	SSobj.processing |= src

/obj/item/weapon/rcs/examine(mob/user)
	..()
	user << "There are [rcharges] charge\s left."

/obj/item/weapon/rcs/Destroy()
	SSobj.processing.Remove(src)
	return ..()
/obj/item/weapon/rcs/process()
	if(rcharges > 10)
		rcharges = 10
	if(last_charge == 0)
		rcharges++
		last_charge = 30
	else
		last_charge--

/obj/item/weapon/rcs/attack_self(mob/user)
	if(emagged)
		if(mode == 0)
			mode = 1
			playsound(src.loc, 'sound/effects/pop.ogg', 50, 0)
			user << "The telepad locator has become uncalibrated."
		else
			mode = 0
			playsound(src.loc, 'sound/effects/pop.ogg', 50, 0)
			user << "You calibrate the telepad locator."

/obj/item/weapon/rcs/emag_act(mob/user)
	if(!emagged)
		emagged = 1
		var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
		s.set_up(5, 1, src)
		s.start()
		user << "You emag the RCS. Click on it to toggle between modes."


code:
/obj/machinery/computer/telescience
	name = "\improper Telepad Control Console"
	desc = "Used to teleport objects to and from the telescience telepad."
	icon_screen = "teleport"
	icon_keyboard = "teleport_key"
	circuit = /obj/item/weapon/circuitboard/telesci_console
	var/sending = 1
	var/obj/machinery/telepad/telepad = null
	var/temp_msg = "Telescience control console initialized.
Welcome."

	// VARIABLES //
	var/teles_left	// How many teleports left until it becomes uncalibrated
	var/datum/projectile_data/last_tele_data = null
	var/z_co = 1
	var/power_off
	var/rotation_off
	//var/angle_off
	var/last_target

	var/rotation = 0
	var/angle = 45
	var/power = 5

	// Based on the power used
	var/teleport_cooldown = 0 // every index requires a bluespace crystal
	var/list/power_options = list(5, 10, 20, 25, 30, 40, 50, 80, 100)
	var/teleporting = 0
	var/starting_crystals = 3
	var/max_crystals = 4
	var/list/crystals = list()
	var/obj/item/device/gps/inserted_gps

/obj/machinery/computer/telescience/New()
	..()
	recalibrate()

/obj/machinery/computer/telescience/Destroy()
	eject()
	if(inserted_gps)
		inserted_gps.loc = loc
		inserted_gps = null
	return ..()

/obj/machinery/computer/telescience/examine(mob/user)
	..()
	user << "There are [crystals.len ? crystals.len : "no"] bluespace crystal\s in the crystal slots."

/obj/machinery/computer/telescience/initialize()
	..()
	for(var/i = 1; i <= starting_crystals; i++)
		crystals += new /obj/item/weapon/ore/bluespace_crystal/artificial(null) // starting crystals

/obj/machinery/computer/telescience/attack_paw(mob/user)
	user << "You are too primitive to use this computer!"
	return

/obj/machinery/computer/telescience/attackby(obj/item/W, mob/user, params)
	if(istype(W, /obj/item/weapon/ore/bluespace_crystal))
		if(crystals.len >= max_crystals)
			user << "There are not enough crystal slots."
			return
		if(!user.drop_item())
			return
		crystals += W
		W.loc = null
		user.visible_message("[user] inserts [W] into \the [src]'s crystal slot.", "You insert [W] into \the [src]'s crystal slot.")
		updateDialog()
	else if(istype(W, /obj/item/device/gps))
		if(!inserted_gps)
			inserted_gps = W
			user.unEquip(W)
			W.loc = src
			user.visible_message("[user] inserts [W] into \the [src]'s GPS device slot.", "You insert [W] into \the [src]'s GPS device slot.")
	else if(istype(W, /obj/item/device/multitool))
		var/obj/item/device/multitool/M = W
		if(M.buffer && istype(M.buffer, /obj/machinery/telepad))
			telepad = M.buffer
			M.buffer = null
			user << "You upload the data from the [W.name]'s buffer."
	else
		..()

/obj/machinery/computer/telescience/attack_ai(mob/user)
	src.attack_hand(user)

/obj/machinery/computer/telescience/attack_hand(mob/user)
	if(..())
		return
	interact(user)

/obj/machinery/computer/telescience/interact(mob/user)
	var/t
	if(!telepad)
		in_use = 0     //Yeah so if you deconstruct teleporter while its in the process of shooting it wont disable the console
		t += "
No telepad located.
Please add telepad data.

"
	else
		if(inserted_gps)
			t += "Eject GPS"
			t += "Set GPS memory"
		else
			t += "Eject GPS"
			t += "Set GPS memory"
		t += "
[temp_msg]

"
		t += "Set Bearing"
		t += "
[rotation]�
"
		t += "Set Elevation"
		t += "
[angle]�
"
		t += "Set Power"
		t += "
"

		for(var/i = 1; i <= power_options.len; i++)
			if(crystals.len + telepad.efficiency  < i)
				t += "[power_options[i]]"
				continue
			if(power == power_options[i])
				t += "[power_options[i]]"
				continue
			t += "[power_options[i]]"
		t += "
"

		t += "Set Sector"
		t += "
[z_co ? z_co : "NULL"]
"

		t += "
Send"
		t += " Receive"
		t += "
Recalibrate Crystals Eject Crystals"

		// Information about the last teleport
		t += "

"
		if(!last_tele_data)
			t += "No teleport data found."
		else
			t += "Source Location: ([last_tele_data.src_x], [last_tele_data.src_y])
"
			//t += "Distance: [round(last_tele_data.distance, 0.1)]m
"
			t += "Time: [round(last_tele_data.time, 0.1)] secs
"
		t += "
"

	var/datum/browser/popup = new(user, "telesci", name, 300, 500)
	popup.set_content(t)
	popup.open()
	return

/obj/machinery/computer/telescience/proc/sparks()
	if(telepad)
		var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
		s.set_up(5, 1, get_turf(telepad))
		s.start()
	else
		return

/obj/machinery/computer/telescience/proc/telefail()
	sparks()
	visible_message("The telepad weakly fizzles.")
	return

/obj/machinery/computer/telescience/proc/doteleport(mob/user)

	if(teleport_cooldown > world.time)
		temp_msg = "Telepad is recharging power.
Please wait [round((teleport_cooldown - world.time) / 10)] seconds."
		return

	if(teleporting)
		temp_msg = "Telepad is in use.
Please wait."
		return

	if(telepad)

		var/truePower = Clamp(power + power_off, 1, 1000)
		var/trueRotation = rotation + rotation_off
		var/trueAngle = Clamp(angle, 1, 90)

		var/datum/projectile_data/proj_data = projectile_trajectory(telepad.x, telepad.y, trueRotation, trueAngle, truePower)
		last_tele_data = proj_data

		var/trueX = Clamp(round(proj_data.dest_x, 1), 1, world.maxx)
		var/trueY = Clamp(round(proj_data.dest_y, 1), 1, world.maxy)
		var/spawn_time = round(proj_data.time) * 10

		var/turf/target = locate(trueX, trueY, z_co)
		last_target = target
		var/area/A = get_area(target)
		flick("pad-beam", telepad)

		if(spawn_time > 15) // 1.5 seconds
			playsound(telepad.loc, 'sound/weapons/flash.ogg', 25, 1)
			// Wait depending on the time the projectile took to get there
			teleporting = 1
			temp_msg = "Powering up bluespace crystals.
Please wait."

		spawn(round(proj_data.time) * 10) // in seconds
			if(!telepad)
				return
			if(telepad.stat & NOPOWER)
				return
			teleporting = 0
			teleport_cooldown = world.time + (power * 2)
			teles_left -= 1

			// use a lot of power
			use_power(power * 10)

			var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
			s.set_up(5, 1, get_turf(telepad))
			s.start()

			temp_msg = "Teleport successful.
"
			if(teles_left < 10)
				temp_msg += "
Calibration required soon."
			else
				temp_msg += "Data printed below."

			var/sparks = get_turf(target)
			var/datum/effect_system/spark_spread/y = new /datum/effect_system/spark_spread
			y.set_up(5, 1, sparks)
			y.start()

			var/turf/source = target
			var/turf/dest = get_turf(telepad)
			var/log_msg = ""
			log_msg += ": [key_name(user)] has teleported "

			if(sending)
				source = dest
				dest = target

			flick("pad-beam", telepad)
			playsound(telepad.loc, 'sound/weapons/emitter2.ogg', 25, 1, extrarange = 3, falloff = 5)
			for(var/atom/movable/ROI in source)
				// if is anchored, don't let through
				if(ROI.anchored)
					if(isliving(ROI))
						var/mob/living/L = ROI
						if(L.buckled)
							// TP people on office chairs
							if(L.buckled.anchored)
								continue

							log_msg += "[key_name(L)] (on a chair), "
						else
							continue
					else if(!isobserver(ROI))
						continue
				if(ismob(ROI))
					var/mob/T = ROI
					log_msg += "[key_name(T)], "
				else
					log_msg += "[ROI.name]"
					if (istype(ROI, /obj/structure/closet))
						var/obj/structure/closet/C = ROI
						log_msg += " ("
						for(var/atom/movable/Q as mob|obj in C)
							if(ismob(Q))
								log_msg += "[key_name(Q)], "
							else
								log_msg += "[Q.name], "
						if (dd_hassuffix(log_msg, "("))
							log_msg += "empty)"
						else
							log_msg = dd_limittext(log_msg, length(log_msg) - 2)
							log_msg += ")"
					log_msg += ", "
				do_teleport(ROI, dest)

			if (dd_hassuffix(log_msg, ", "))
				log_msg = dd_limittext(log_msg, length(log_msg) - 2)
			else
				log_msg += "nothing"
			log_msg += " [sending ? "to" : "from"] [trueX], [trueY], [z_co] ([A ? A.name : "null area"])"
			investigate_log(log_msg, "telesci")
			updateDialog()

/obj/machinery/computer/telescience/proc/teleport(mob/user)
	if(rotation == null || angle == null || z_co == null)
		temp_msg = "ERROR!
Set a angle, rotation and sector."
		return
	if(power <= 0)
		telefail()
		temp_msg = "ERROR!
No power selected!"
		return
	if(angle < 1 || angle > 90)
		telefail()
		temp_msg = "ERROR!
Elevation is less than 1 or greater than 90."
		return
	if(z_co == 2 || z_co < 1 || z_co > 6)
		telefail()
		temp_msg = "ERROR! Sector is less than 1,
greater than 6, or equal to 2."
		return
	if(teles_left > 0)
		doteleport(user)
	else
		telefail()
		temp_msg = "ERROR!
Calibration required."
		return
	return

/obj/machinery/computer/telescience/proc/eject()
	for(var/obj/item/I in crystals)
		I.loc = src.loc
		crystals -= I
	power = 0

/obj/machinery/computer/telescience/Topic(href, href_list)
	if(..())
		return
	if(!telepad)
		updateDialog()
		return
	if(telepad.panel_open)
		temp_msg = "Telepad undergoing physical maintenance operations."

	if(href_list["setrotation"])
		var/new_rot = input("Please input desired bearing in degrees.", name, rotation) as num
		if(..()) // Check after we input a value, as they could've moved after they entered something
			return
		rotation = Clamp(new_rot, -900, 900)
		rotation = round(rotation, 0.01)

	if(href_list["setangle"])
		var/new_angle = input("Please input desired elevation in degrees.", name, angle) as num
		if(..())
			return
		angle = Clamp(round(new_angle, 0.1), 1, 9999)

	if(href_list["setpower"])
		var/index = href_list["setpower"]
		index = text2num(index)
		if(index != null && power_options[index])
			if(crystals.len + telepad.efficiency >= index)
				power = power_options[index]

	if(href_list["setz"])
		var/new_z = input("Please input desired sector.", name, z_co) as num
		if(..())
			return
		z_co = Clamp(round(new_z), 1, 10)

	if(href_list["ejectGPS"])
		if(inserted_gps)
			inserted_gps.loc = loc
			inserted_gps = null

	if(href_list["setMemory"])
		if(last_target && inserted_gps)
			inserted_gps.locked_location = last_target
			temp_msg = "Location saved."
		else
			temp_msg = "ERROR!
No data was stored."

	if(href_list["send"])
		sending = 1
		teleport(usr)

	if(href_list["receive"])
		sending = 0
		teleport(usr)

	if(href_list["recal"])
		recalibrate()
		sparks()
		temp_msg = "NOTICE:
Calibration successful."

	if(href_list["eject"])
		eject()
		temp_msg = "NOTICE:
Bluespace crystals ejected."

	updateDialog()

/obj/machinery/computer/telescience/proc/recalibrate()
	teles_left = rand(30, 40)
	//angle_off = rand(-25, 25)
	power_off = rand(-4, 0)
	rotation_off = rand(-10, 10)


code:
 /**
  * tgui external
  *
  * Contains all external tgui declarations.
 **/

 /**
  * public
  *
  * Used to open and update UIs.
  * If this proc is not implemented properly, the UI will not update correctly.
  *
  * required user mob The mob who opened/is using the UI.
  * optional ui_key string The ui_key of the UI.
  * optional ui datum/tgui The UI to be updated, if it exists.
  * optional force_open bool If the UI should be re-opened instead of updated.
  * optional master_ui datum/tgui The parent UI.
  * optional state datum/ui_state The state used to determine status.
 **/
/datum/proc/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)
	return -1 // Not implemented.

 /**
  * public
  *
  * Data to be sent to the UI.
  * This must be implemented for a UI to work.
  *
  * required user mob The mob interacting with the UI.
  *
  * return list Data to be sent to the UI.
 **/
/datum/proc/ui_data(mob/user)
	return list() // Not implemented.

 /**
  * public
  *
  * Called on a UI when the UI receieves a href.
  * Think of this as Topic().
  *
  * required action string The action/button that has been invoked by the user.
  * required params list A list of parameters attached to the button.
  *
  * return bool If the UI should be updated or not.
 **/
/datum/proc/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
	if(!ui || ui.status != UI_INTERACTIVE)
		return 1 // If UI is not interactive or usr calling Topic is not the UI user, bail.

 /**
  * private
  *
  * The UI's host object (usually src_object).
  * This allows modules/datums to have the UI attached to them,
  * and be a part of another object.
 **/
/datum/proc/ui_host()
	return src // Default src.

 /**
  * global
  *
  * Used to track the current screen.
 **/
/datum/var/ui_screen = "home"

 /**
  * global
  *
  * Used to track UIs for a mob.
 **/
/mob/var/list/open_uis = list()

 /**
  * verb
  *
  * Called by UIs when they are closed.
  * Must be a verb so winset() can call it.
  *
  * required uiref ref The UI that was closed.
 **/
/client/verb/uiclose(ref as text)
	// Name the verb, and hide it from the user panel.
	set name = "uiclose"
	set hidden = 1

	// Get the UI based on the ref.
	var/datum/tgui/ui = locate(ref)

	// If we found the UI, close it.
	if(istype(ui))
		ui.close()
		// Unset machine just to be sure.
		if(src && src.mob)
			src.mob.unset_machine()


code:
 /**
  * tgui states
  *
  * Base state and helpers for states. Just does some sanity checks, implement a state for in-depth checks.
 **/

 /**
  * public
  *
  * Checks the UI state for a mob.
  *
  * required user mob The mob who opened/is using the UI.
  * required state datum/ui_state The state to check.
  *
  * return UI_state The state of the UI.
 **/
/datum/proc/ui_status(mob/user, datum/ui_state/state)
	var/src_object = ui_host()

	if(istype(user, /mob/dead/observer)) // Special-case ghosts.
		if(IsAdminGhost(user))
			return UI_INTERACTIVE // If they turn it on, admins can interact.
		if(get_dist(src_object, src) < user.client.view)
			return UI_UPDATE // Regular ghosts can only view.
		return UI_CLOSE // To keep too many UIs from being opened.
	return state.can_use_topic(src_object, user) // Check if the state allows interaction.

 /**
  * private
  *
  * Checks if a user can use src_object's UI, and returns the state.
  * Can call a mob proc, which allows overrides for each mob.
  *
  * required src_object datum The object/datum which owns the UI.
  * required user mob The mob who opened/is using the UI.
  *
  * return UI_state The state of the UI.
 **/
/datum/ui_state/proc/can_use_topic(src_object, mob/user)
	return UI_CLOSE // Don't allow interaction by default.

 /**
  * public
  *
  * Standard interaction/sanity checks. Different mob types may have overrides.
  *
  * return UI_state The state of the UI.
 **/
/mob/proc/shared_ui_interaction(src_object)
	if(!client) // Close UIs if mindless.
		return UI_CLOSE
	else if(stat) // Disable UIs if unconcious.
		return UI_DISABLED
	else if(incapacitated() || lying) // Update UIs if incapicitated but concious.
		return UI_UPDATE
	return UI_INTERACTIVE

/mob/living/silicon/ai/shared_ui_interaction(src_object)
	if(lacks_power()) // Disable UIs if the AI is unpowered.
		return UI_DISABLED
	return ..()

/mob/living/silicon/robot/shared_ui_interaction(src_object)
	if(cell.charge <= 0 || lockcharge) // Disable UIs if the Borg is unpowered or locked.
		return UI_DISABLED
	return ..()

/**
  * public
  *
  * Check the distance for a living mob.
  * Really only used for checks outside the context of a mob.
  * Otherwise, use shared_living_ui_distance().
  *
  * required src_object The object which owns the UI.
  * required user mob The mob who opened/is using the UI.
  *
  * return UI_state The state of the UI.
 **/
/atom/proc/contents_ui_distance(src_object, mob/living/user)
	return user.shared_living_ui_distance(src_object) // Just call this mob's check.

 /**
  * public
  *
  * Distance versus interaction check.
  *
  * required src_object atom/movable The object which owns the UI.
  *
  * return UI_state The state of the UI.
 **/
/mob/living/proc/shared_living_ui_distance(atom/movable/src_object)
	if(!(src_object in view(src))) // If the object is obscured, close it.
		return UI_CLOSE

	var/dist = get_dist(src_object, src)
	if(dist <= 1) // Open and interact if 1-0 tiles away.
		return UI_INTERACTIVE
	else if(dist <= 2) // View only if 2-3 tiles away.
		return UI_UPDATE
	else if(dist <= 5) // Disable if 5 tiles away.
		return UI_DISABLED
	return UI_CLOSE // Otherwise, we got nothing.

/mob/living/carbon/human/shared_living_ui_distance(atom/movable/src_object)
	if(dna.check_mutation(TK) && tkMaxRangeCheck(src, src_object))
		return UI_INTERACTIVE
	return ..()


code:
 /**
  * tgui state: admin_state
  *
  * Checks that the user is an admin, end-of-story.
 **/

/var/global/datum/ui_state/admin_state/admin_state = new()

/datum/ui_state/admin_state/can_use_topic(src_object, mob/user)
	if(check_rights_for(user.client, R_ADMIN))
		return UI_INTERACTIVE
	return UI_CLOSE


code:
 /**
  * tgui state: conscious_state
  *
  * Only checks if the user is conscious.
 **/

/var/global/datum/ui_state/conscious_state/conscious_state = new()

/datum/ui_state/conscious_state/can_use_topic(src_object, mob/user)
	if(user.stat == CONSCIOUS)
		return UI_INTERACTIVE
	return UI_CLOSE


code:
 /**
  * tgui state: contained_state
  *
  * Checks that the user is inside the src_object.
 **/

/var/global/datum/ui_state/contained_state/contained_state = new()

/datum/ui_state/contained_state/can_use_topic(atom/src_object, mob/user)
	if(!src_object.contains(user))
		return UI_CLOSE
	return user.shared_ui_interaction(src_object)


code:
 /**
  * tgui state: deep_inventory_state
  *
  * Checks that the src_object is in the user's deep (backpack, box, toolbox, etc) inventory.
 **/

/var/global/datum/ui_state/deep_inventory_state/deep_inventory_state = new()

/datum/ui_state/deep_inventory_state/can_use_topic(src_object, mob/user)
	if(!user.contains(src_object))
		return UI_CLOSE
	return user.shared_ui_interaction(src_object)


code:
 /**
  * tgui state: default_state
  *
  * Checks a number of things -- mostly physical distance for humans and view for robots.
 **/

/var/global/datum/ui_state/default/default_state = new()

/datum/ui_state/default/can_use_topic(src_object, mob/user)
	return user.default_can_use_topic(src_object) // Call the individual mob-overriden procs.

/mob/proc/default_can_use_topic(src_object)
	return UI_CLOSE // Don't allow interaction by default.

/mob/living/default_can_use_topic(src_object)
	. = shared_ui_interaction(src_object)
	if(. > UI_CLOSE && loc)
		. = min(., loc.contents_ui_distance(src_object, src)) // Check the distance...
	if(. == UI_INTERACTIVE) // Non-human living mobs can only look, not touch.
		return UI_UPDATE

/mob/living/carbon/human/default_can_use_topic(src_object)
	. = shared_ui_interaction(src_object)
	if(. > UI_CLOSE)
		. = min(., shared_living_ui_distance(src_object)) // Check the distance...
		// Derp a bit if we have brain loss.
		if(prob(getBrainLoss()))
			return UI_UPDATE

/mob/living/silicon/robot/default_can_use_topic(src_object)
	. = shared_ui_interaction(src_object)
	if(. <= UI_DISABLED)
		return

	// Robots can interact with anything they can see.
	if(get_dist(src, src_object) <= client.view)
		return UI_INTERACTIVE
	return UI_DISABLED // Otherwise they can keep the UI open.

/mob/living/silicon/ai/default_can_use_topic(src_object)
	. = shared_ui_interaction(src_object)
	if(. < UI_INTERACTIVE)
		return

	// The AI can interact with anything it can see nearby, or with cameras.
	if((get_dist(src, src_object) <= client.view) || cameranet.checkTurfVis(get_turf_pixel(src_object)))
		return UI_INTERACTIVE
	return UI_CLOSE

/mob/living/simple_animal/drone/default_can_use_topic(src_object)
	. = shared_ui_interaction(src_object)
	if(. > UI_CLOSE)
		. = min(., shared_living_ui_distance(src_object)) // Drones can only use things they're near.

/mob/living/silicon/pai/default_can_use_topic(src_object)
	// pAIs can only use themselves and the owner's radio.
	if((src_object == src || src_object == radio) && !stat)
		return UI_INTERACTIVE
	else
		return ..()


code:
 /**
  * tgui state: hands_state
  *
  * Checks that the src_object is in the user's hands.
 **/

/var/global/datum/ui_state/hands_state/hands_state = new()

/datum/ui_state/hands_state/can_use_topic(src_object, mob/user)
	. = user.shared_ui_interaction(src_object)
	if(. > UI_CLOSE)
		return min(., user.hands_can_use_topic(src_object))

/mob/proc/hands_can_use_topic(src_object)
	return UI_CLOSE

/mob/living/hands_can_use_topic(src_object)
	if(src_object in get_both_hands(src))
		return UI_INTERACTIVE
	return UI_CLOSE


code:
 /**
  * tgui state: inventory_state
  *
  * Checks that the src_object is in the user's top-level (hand, ear, pocket, belt, etc) inventory.
 **/

/var/global/datum/ui_state/inventory_state/inventory_state = new()

/datum/ui_state/inventory_state/can_use_topic(src_object, mob/user)
	if(!(src_object in user))
		return UI_CLOSE
	return user.shared_ui_interaction(src_object)


code:
 /**
  * tgui state: notcontained_state
  *
  * Checks that the user is not inside src_object, and then makes the default checks.
 **/

/var/global/datum/ui_state/notcontained_state/notcontained_state = new()

/datum/ui_state/notcontained_state/can_use_topic(atom/src_object, mob/user)
	. = user.shared_ui_interaction(src_object)
	if(. > UI_CLOSE)
		return min(., user.notcontained_can_use_topic(src_object))

/mob/proc/notcontained_can_use_topic(src_object)
	return UI_CLOSE

/mob/living/notcontained_can_use_topic(atom/src_object)
	if(src_object.contains(src))
		return UI_CLOSE // Close if we're inside it.
	return default_can_use_topic(src_object)

/mob/living/silicon/notcontained_can_use_topic(src_object)
	return default_can_use_topic(src_object) // Silicons use default bevhavior.

/mob/living/simple_animal/drone/notcontained_can_use_topic(src_object)
	return default_can_use_topic(src_object) // Drones use default bevhavior.


code:
 /**
  * tgui state: physical_state
  *
  * Short-circuits the default state to only check physical distance.
 **/

/var/global/datum/ui_state/physical/physical_state = new()

/datum/ui_state/physical/can_use_topic(src_object, mob/user)
	. = user.shared_ui_interaction(src_object)
	if(. > UI_CLOSE)
		return min(., user.physical_can_use_topic(src_object))

/mob/proc/physical_can_use_topic(src_object)
	return UI_CLOSE

/mob/living/physical_can_use_topic(src_object)
	return shared_living_ui_distance(src_object)

/mob/living/silicon/physical_can_use_topic(src_object)
	return max(UI_UPDATE, shared_living_ui_distance(src_object)) // Silicons can always see.

/mob/living/silicon/ai/physical_can_use_topic(src_object)
	return UI_UPDATE // AIs are not physical.


code:
 /**
  * tgui state: self_state
  *
  * Only checks that the user and src_object are the same.
 **/

/var/global/datum/ui_state/self_state/self_state = new()

/datum/ui_state/self_state/can_use_topic(src_object, mob/user)
	if(src_object != user)
		return UI_CLOSE
	return user.shared_ui_interaction(src_object)


code:
 /**
  * tgui state: z_state
  *
  * Only checks that the Z-level of the user and src_object are the same.
 **/

/var/global/datum/ui_state/z_state/z_state = new()

/datum/ui_state/z_state/can_use_topic(src_object, mob/user)
	var/turf/turf_obj = get_turf(src_object)
	var/turf/turf_usr = get_turf(user)
	if(turf_obj && turf_usr && turf_obj.z == turf_usr.z)
		return UI_INTERACTIVE
	return UI_CLOSE


code:
 /**
  * tgui subsystem
  *
  * Contains all tgui state and subsystem code.
 **/

 /**
  * public
  *
  * Get a open UI given a user, src_object, and ui_key and try to update it with data.
  *
  * required user mob The mob who opened/is using the UI.
  * required src_object datum The object/datum which owns the UI.
  * required ui_key string The ui_key of the UI.
  * optional ui datum/tgui The UI to be updated, if it exists.
  * optional force_open bool If the UI should be re-opened instead of updated.
  *
  * return datum/tgui The found UI.
 **/
/datum/subsystem/tgui/proc/try_update_ui(mob/user, datum/src_object, ui_key, datum/tgui/ui, force_open = 0)
	if(isnull(ui)) // No UI was passed, so look for one.
		ui = get_open_ui(user, src_object, ui_key)

	if(!isnull(ui))
		var/data = src_object.ui_data(user) // Get data from the src_object.
		if(!force_open) // UI is already open; update it.
			ui.push_data(data)
		else // Re-open it anyways.
			ui.reinitialize(null, data)
		return ui // We found the UI, return it.
	else
		return null // We couldn't find a UI.

 /**
  * private
  *
  * Get a open UI given a user, src_object, and ui_key.
  *
  * required user mob The mob who opened/is using the UI.
  * required src_object datum The object/datum which owns the UI.
  * required ui_key string The ui_key of the UI.
  *
  * return datum/tgui The found UI.
 **/
/datum/subsystem/tgui/proc/get_open_ui(mob/user, datum/src_object, ui_key)
	var/src_object_key = "\ref[src_object]"
	if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
		return null // No UIs open.
	else if(isnull(open_uis[src_object_key][ui_key]) || !istype(open_uis[src_object_key][ui_key], /list))
		return null // No UIs open for this object.

	for(var/datum/tgui/ui in open_uis[src_object_key][ui_key]) // Find UIs for this object.
		if(ui.user == user) // Make sure we have the right user
			return ui

	return null // Couldn't find a UI!

 /**
  * private
  *
  * Update all UIs attached to src_object.
  *
  * required src_object datum The object/datum which owns the UIs.
  *
  * return int The number of UIs updated.
 **/
/datum/subsystem/tgui/proc/update_uis(datum/src_object)
	var/src_object_key = "\ref[src_object]"
	if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
		return 0 // Couldn't find any UIs for this object.

	var/update_count = 0
	for(var/ui_key in open_uis[src_object_key])
		for(var/datum/tgui/ui in open_uis[src_object_key][ui_key])
			if(ui && ui.src_object && ui.user && ui.src_object.ui_host()) // Check the UI is valid.
				ui.process(force = 1) // Update the UI.
				update_count++ // Count each UI we update.
	return update_count

 /**
  * private
  *
  * Close all UIs attached to src_object.
  *
  * required src_object datum The object/datum which owns the UIs.
  *
  * return int The number of UIs closed.
 **/
/datum/subsystem/tgui/proc/close_uis(datum/src_object)
	var/src_object_key = "\ref[src_object]"
	if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
		return 0 // Couldn't find any UIs for this object.

	var/close_count = 0
	for(var/ui_key in open_uis[src_object_key])
		for(var/datum/tgui/ui in open_uis[src_object_key][ui_key])
			if(ui && ui.src_object && ui.user && ui.src_object.ui_host()) // Check the UI is valid.
				ui.close() // Close the UI.
				close_count++ // Count each UI we close.
	return close_count

 /**
  * private
  *
  * Update all UIs belonging to a user.
  *
  * required user mob The mob who opened/is using the UI.
  * optional src_object datum If provided, only update UIs belonging this src_object.
  * optional ui_key string If provided, only update UIs with this UI key.
  *
  * return int The number of UIs updated.
 **/
/datum/subsystem/tgui/proc/update_user_uis(mob/user, datum/src_object = null, ui_key = null)
	if(isnull(user.open_uis) || !istype(user.open_uis, /list) || open_uis.len == 0)
		return 0 // Couldn't find any UIs for this user.

	var/update_count = 0
	for(var/datum/tgui/ui in user.open_uis)
		if((isnull(src_object) || !isnull(src_object) && ui.src_object == src_object) && (isnull(ui_key) || !isnull(ui_key) && ui.ui_key == ui_key))
			ui.process(force = 1) // Update the UI.
			update_count++ // Count each UI we upadte.
	return update_count

 /**
  * private
  *
  * Close all UIs belonging to a user.
  *
  * required user mob The mob who opened/is using the UI.
  * optional src_object datum If provided, only close UIs belonging this src_object.
  * optional ui_key string If provided, only close UIs with this UI key.
  *
  * return int The number of UIs closed.
 **/
/datum/subsystem/tgui/proc/close_user_uis(mob/user, datum/src_object = null, ui_key = null)
	if(isnull(user.open_uis) || !istype(user.open_uis, /list) || open_uis.len == 0)
		return 0 // Couldn't find any UIs for this user.

	var/close_count = 0
	for(var/datum/tgui/ui in user.open_uis)
		if((isnull(src_object) || !isnull(src_object) && ui.src_object == src_object) && (isnull(ui_key) || !isnull(ui_key) && ui.ui_key == ui_key))
			ui.close() // Close the UI.
			close_count++ // Count each UI we close.
	return close_count

 /**
  * private
  *
  * Add a UI to the list of open UIs.
  *
  * required ui datum/tgui The UI to be added.
 **/
/datum/subsystem/tgui/proc/on_open(datum/tgui/ui)
	var/src_object_key = "\ref[ui.src_object]"
	if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
		open_uis[src_object_key] = list(ui.ui_key = list()) // Make a list for the ui_key and src_object.
	else if(isnull(open_uis[src_object_key][ui.ui_key]) || !istype(open_uis[src_object_key][ui.ui_key], /list))
		open_uis[src_object_key][ui.ui_key] = list() // Make a list for the ui_key.

	// Append the UI to all the lists.
	ui.user.open_uis |= ui
	var/list/uis = open_uis[src_object_key][ui.ui_key]
	uis |= ui
	processing_uis |= ui

 /**
  * private
  *
  * Remove a UI from the list of open UIs.
  *
  * required ui datum/tgui The UI to be removed.
  *
  * return bool If the UI was removed or not.
 **/
/datum/subsystem/tgui/proc/on_close(datum/tgui/ui)
	var/src_object_key = "\ref[ui.src_object]"
	if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
		return 0 // It wasn't open.
	else if(isnull(open_uis[src_object_key][ui.ui_key]) || !istype(open_uis[src_object_key][ui.ui_key], /list))
		return 0 // It wasn't open.

	processing_uis.Remove(ui) // Remove it from the list of processing UIs.
	if(ui.user)	// If the user exists, remove it from them too.
		ui.user.open_uis.Remove(ui)
	var/list/uis = open_uis[src_object_key][ui.ui_key] // Remove it from the list of open UIs.
	uis.Remove(ui)
	return 1 // Let the caller know we did it.

 /**
  * private
  *
  * Handle client logout, by closing all their UIs.
  *
  * required user mob The mob which logged out.
  *
  * return int The number of UIs closed.
 **/
/datum/subsystem/tgui/proc/on_logout(mob/user)
	return close_user_uis(user)

 /**
  * private
  *
  * Handle clients switching mobs, by transfering their UIs.
  *
  * required user source The client's original mob.
  * required user target The client's new mob.
  *
  * return bool If the UIs were transferred.
 **/
/datum/subsystem/tgui/proc/on_transfer(mob/source, mob/target)
	if(!source || isnull(source.open_uis) || !istype(source.open_uis, /list) || open_uis.len == 0)
		return 0 // The old mob had no open UIs.

	if(isnull(target.open_uis) || !istype(target.open_uis, /list))
		target.open_uis = list() // Create a list for the new mob if needed.

	for(var/datum/tgui/ui in source.open_uis)
		ui.user = target // Inform the UIs of their new owner.
		target.open_uis.Add(ui) // Transfer all the UIs.

	source.open_uis.Cut() // Clear the old list.
	return 1 // Let the caller know we did it.


code:
 /**
  * tgui
  *
  * /tg/station user interface library
 **/

 /**
  * tgui datum (represents a UI).
 **/
/datum/tgui
	var/mob/user // The mob who opened/is using the UI.
	var/datum/src_object // The object which owns the UI.
	var/title // The title of te UI.
	var/ui_key // The ui_key of the UI. This allows multiple UIs for one src_object.
	var/window_id // The window_id for browse() and onclose().
	var/width = 0 // The window width.
	var/height = 0 // The window height
	var/window_options = list( // Extra options to winset().
	  "focus" = FALSE,
	  "titlebar" = TRUE,
	  "can_resize" = TRUE,
	  "can_minimize" = TRUE,
	  "can_maximize" = FALSE,
	  "can_close" = TRUE,
	  "auto_format" = FALSE
	)
	var/style = "nanotrasen" // The style to be used for this UI.
	var/interface // The interface (template) to be used for this UI.
	var/autoupdate = TRUE // Update the UI every MC tick.
	var/initialized = FALSE // If the UI has been initialized yet.
	var/list/initial_data // The data (and datastructure) used to initialize the UI.
	var/status = UI_INTERACTIVE // The status/visibility of the UI.
	var/datum/ui_state/state = null // Topic state used to determine status/interactability.
	var/datum/tgui/master_ui // The parent UI.
	var/list/datum/tgui/children = list() // Children of this UI.

 /**
  * public
  *
  * Create a new UI.
  *
  * required user mob The mob who opened/is using the UI.
  * required src_object datum The object or datum which owns the UI.
  * required ui_key string The ui_key of the UI.
  * required interface string The interface used to render the UI.
  * optional title string The title of the UI.
  * optional width int The window width.
  * optional height int The window height.
  * optional master_ui datum/tgui The parent UI.
  * optional state datum/ui_state The state used to determine status.
  *
  * return datum/tgui The requested UI.
 **/
/datum/tgui/New(mob/user, datum/src_object, ui_key, interface, title, width = 0, height = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state)
	src.user = user
	src.src_object = src_object
	src.ui_key = ui_key
	src.window_id = "\ref[src_object]-[ui_key]"

	set_interface(interface)

	if(title)
		src.title = sanitize(title)
	if(width)
		src.width = width
	if(height)
		src.height = height

	src.master_ui = master_ui
	if(master_ui)
		master_ui.children += src
	src.state = state

	var/datum/asset/assets = get_asset_datum(/datum/asset/simple/tgui)
	assets.send(user)

 /**
  * public
  *
  * Open this UI (and initialize it with data).
 **/
/datum/tgui/proc/open()
	if(!user.client)
		return // Bail if there is no client.

	update_status(push = 0) // Update the window status.
	if(status < UI_UPDATE)
		return // Bail if we're not supposed to open.

	if(!initial_data)
		set_initial_data(src_object.ui_data(user)) // Get the UI data.

	var/window_size = ""
	if(width && height) // If we have a width and height, use them.
		window_size = "size=[width]x[height];"

	var/debugable = check_rights_for(user.client, R_DEBUG)
	user << browse(get_html(debugable), "window=[window_id];[window_size][list2params(window_options)]") // Open the window.
	winset(user, window_id, "on-close=\"uiclose \ref[src]\"") // Instruct the client to signal UI when the window is closed.
	SStgui.on_open(src)

 /**
  * public
  *
  * Reinitialize the UI.
  * (Possibly with a new interface and/or data).
  *
  * optional template string The name of the new interface.
  * optional data list The new initial data.
 **/
/datum/tgui/proc/reinitialize(interface, list/data)
	if(interface)
		set_interface(interface) // Set a new interface.
	if(data)
		set_initial_data(data) // Replace the initial_data.
	open()

 /**
  * public
  *
  * Close the UI, and all its children.
 **/
/datum/tgui/proc/close()
	user << browse(null, "window=[window_id]") // Close the window.
	SStgui.on_close(src)
	for(var/datum/tgui/child in children) // Loop through and close all children.
		child.close()
	children.Cut()
	state = null
	master_ui = null
	qdel(src)

 /**
  * public
  *
  * Sets the browse() window options for this UI.
  *
  * required window_options list The window options to set.
 **/
/datum/tgui/proc/set_window_options(list/window_options)
	src.window_options = window_options

 /**
  * public
  *
  * Set the style for this UI.
  *
  * required style string The new UI style.
 **/
/datum/tgui/proc/set_style(style)
	src.style = lowertext(style)

 /**
  * public
  *
  * Set the interface (template) for this UI.
  *
  * required interface string The new UI interface.
 **/
/datum/tgui/proc/set_interface(interface)
	src.interface = lowertext(interface)

 /**
  * public
  *
  * Enable/disable auto-updating of the UI.
  *
  * required state bool Enable/disable auto-updating.
 **/
/datum/tgui/proc/set_autoupdate(state = 1)
	autoupdate = state

 /**
  * private
  *
  * Set the data to initialize the UI with.
  * The datastructure cannot be changed by subsequent updates.
  *
  * optional data list The data/datastructure to initialize the UI with.
 **/
/datum/tgui/proc/set_initial_data(list/data)
	initial_data = data

 /**
  * private
  *
  * Generate HTML for this UI.
  *
  * optional bool inline If the JSON should be inlined into the HTML (for debugging).
  *
  * return string UI HTML output.
 **/
/datum/tgui/proc/get_html(var/inline)
	var/html
	// Poplate HTML with JSON if we're supposed to inline.
	if(inline)
		html = replacetextEx(SStgui.basehtml, "{}", get_json(initial_data))
	else
		html = SStgui.basehtml
	html = replacetextEx(html, "\[ref]", "\ref[src]")
	html = replacetextEx(html, "\[style]", style)
	return html

 /**
  * private
  *
  * Get the config data/datastructure to initialize the UI with.
  *
  * return list The config data.
 **/
/datum/tgui/proc/get_config_data()
	var/list/config_data = list(
			"title"     = title,
			"status"    = status,
			"screen"	= src_object.ui_screen,
			"style"     = style,
			"interface" = interface,
			"fancy"     = user.client.prefs.tgui_fancy,
			"locked"    = user.client.prefs.tgui_lock,
			"window"    = window_id,
			"ref"       = "\ref[src]",
			"user"      = list(
				"name"  = user.name,
				"ref"   = "\ref[user]"
			),
			"srcObject" = list(
				"name" = "[src_object]",
				"ref"  = "\ref[src_object]"
			)
		)
	return config_data

 /**
  * private
  *
  * Package the data to send to the UI, as JSON.
  * This includes the UI data and config_data.
  *
  * return string The packaged JSON.
 **/
/datum/tgui/proc/get_json(list/data)
	var/list/json_data = list()

	json_data["config"] = get_config_data()
	if(!isnull(data))
		json_data["data"] = data

	// Generate the JSON.
	var/json = json_encode(json_data)
	// Strip #255/improper.
	json = replacetext(json, "\proper", "")
	json = replacetext(json, "\improper", "")
	return json

 /**
  * private
  *
  * Handle clicks from the UI.
  * Call the src_object's ui_act() if status is UI_INTERACTIVE.
  * If the src_object's ui_act() returns 1, update all UIs attacked to it.
 **/
/datum/tgui/Topic(href, href_list)
	if(user != usr)
		return // Something is not right here.

	var/action = href_list["action"]
	var/params = href_list; params -= "action"

	switch(action)
		if("tgui:initialize")
			user << output(url_encode(get_json(initial_data)), "[window_id].browser:initialize")
			initialized = TRUE
		if("tgui:view")
			if(params["screen"])
				src_object.ui_screen = params["screen"]
			SStgui.update_uis(src_object)
		if("tgui:link")
			user << link(params["url"])
		if("tgui:fancy")
			user.client.prefs.tgui_fancy = TRUE
		if("tgui:nofrills")
			user.client.prefs.tgui_fancy = FALSE
		else
			update_status(push = 0) // Update the window state.
			if(src_object.ui_act(action, params, src, state)) // Call ui_act() on the src_object.
				SStgui.update_uis(src_object) // Update if the object requested it.

 /**
  * private
  *
  * Update the UI.
  * Only updates the data if update is true, otherwise only updates the status.
  *
  * optional force bool If the UI should be forced to update.
 **/
/datum/tgui/process(force = 0)
	var/datum/host = src_object.ui_host()
	if(!src_object || !host || !user) // If the object or user died (or something else), abort.
		close()
		return

	if(status && (force || autoupdate))
		update() // Update the UI if the status and update settings allow it.
	else
		update_status(push = 1) // Otherwise only update status.

 /**
  * private
  *
  * Push data to an already open UI.
  *
  * required data list The data to send.
  * optional force bool If the update should be sent regardless of state.
 **/
/datum/tgui/proc/push_data(data, force = 0)
	update_status(push = 0) // Update the window state.
	if(!initialized)
		return // Cannot update UI if it is not set up yet.
	if(status <= UI_DISABLED && !force)
		return // Cannot update UI, we have no visibility.

	// Send the new JSON to the update() Javascript function.
	user << output(url_encode(get_json(data)), "[window_id].browser:update")

 /**
  * private
  *
  * Updates the UI by interacting with the src_object again, which will hopefully
  * call try_ui_update on it.
  *
  * optional force_open bool If force_open should be passed to ui_interact.
 **/
/datum/tgui/proc/update(force_open = 0)
	src_object.ui_interact(user, ui_key, src, force_open, master_ui, state)

 /**
  * private
  *
  * Update the status/visibility of the UI for its user.
  *
  * optional push bool Push an update to the UI (an update is always sent for UI_DISABLED).
 **/
/datum/tgui/proc/update_status(push = 0)
	var/status = src_object.ui_status(user, state)
	if(master_ui)
		status = min(status, master_ui.status)

	set_status(status, push)
	if(status == UI_CLOSE)
		close()

 /**
  * private
  *
  * Set the status/visibility of the UI.
  *
  * required status int The status to set (UI_CLOSE/UI_DISABLED/UI_UPDATE/UI_INTERACTIVE).
  * optional push bool Push an update to the UI (an update is always sent for UI_DISABLED).
 **/
/datum/tgui/proc/set_status(status, push = 0)
	if(src.status != status) // Only update if status has changed.
		if(src.status == UI_DISABLED)
			src.status = status
			if(push)
				update()
		else
			src.status = status
			if(status == UI_DISABLED || push) // Update if the UI just because disabled, or a push is requested.
				push_data(null, force = 1)


code:
/*
Tooltips v1.1 - 22/10/15
Developed by Wire (#goonstation on irc.synirc.net)
- Added support for screen_loc pixel offsets. Should work. Maybe.
- Added init function to more efficiently send base vars

Configuration:
- Set control to the correct skin element (remember to actually place the skin element)
- Set file to the correct path for the .html file (remember to actually place the html file)
- Attach the datum to the user client on login, e.g.
	/client/New()
		src.tooltips = new /datum/tooltip(src)

Usage:
- Define mouse event procs on your (probably HUD) object and simply call the show and hide procs respectively:
	/obj/screen/hud
		MouseEntered(location, control, params)
			usr.client.tooltip.show(params, title = src.name, content = src.desc)

		MouseExited()
			usr.client.tooltip.hide()

Customization:
- Theming can be done by passing the theme var to show() and using css in the html file to change the look
- For your convenience some pre-made themes are included

Notes:
- You may have noticed 90% of the work is done via javascript on the client. Gotta save those cycles man.
- This is entirely untested in any other codebase besides goonstation so I have no idea if it will port nicely. Good luck!
	- After testing and discussion (Wire, Remie, MrPerson, AnturK) ToolTips are ok and work for /tg/station13
*/

/datum/tooltip
	var/client/owner
	var/control = "mainwindow.tooltip"
	var/file = 'code/modules/tooltip/tooltip.html'
	var/showing = 0
	var/queueHide = 0
	var/init = 0

/datum/tooltip/New(client/C)
	if (C)
		src.owner = C
		src.owner << browse(file2text(src.file), "window=[src.control]")

	..()

/datum/tooltip/proc/show(atom/movable/thing, params = null, title = null, content = null, theme = "default", special = "none")
	if (!thing || !params || (!title && !content) || !src.owner || !isnum(world.icon_size))
		return 0
	if (!src.init)
		//Initialize some vars
		src.init = 1
		src.owner << output(list2params(list(world.icon_size, src.control)), "[src.control]:tooltip.init")

	src.showing = 1

	if (title && content)
		title = "
[title]
"
		content = "
[content]
"
	else if (title && !content)
		title = "
[title]
"
	else if (!title && content)
		content = "
[content]
"

	//Make our dumb param object
	params = {"{ "cursor": "[params]", "screenLoc": "[thing.screen_loc]" }"}

	//Send stuff to the tooltip
	src.owner << output(list2params(list(params, src.owner.view, "[title][content]", theme, special)), "[src.control]:tooltip.update")

	//If a hide() was hit while we were showing, run hide() again to avoid stuck tooltips
	src.showing = 0
	if (src.queueHide)
		src.hide()

	return 1

/datum/tooltip/proc/hide()
	if (src.queueHide)
		spawn(1)
			winshow(src.owner, src.control, 0)
	else
		winshow(src.owner, src.control, 0)

	src.queueHide = src.showing ? 1 : 0

	return 1

/* TG SPECIFIC CODE */

//Open a tooltip for user, at a location based on params
//Theme is a CSS class in tooltip.html, by default this wrapper chooses a CSS class based on the user's UI_style (Midnight, Plasmafire, Retro, etc)
//Includes sanity.checks
/proc/openToolTip(mob/user = null, atom/movable/tip_src = null, params = null,title = "",content = "",theme = "")
	if(istype(user))
		if(user.client && user.client.tooltips)
			if(!theme && user.client.prefs && user.client.prefs.UI_style)
				theme = lowertext(user.client.prefs.UI_style)
			if(!theme)
				theme = "default"
			user.client.tooltips.show(tip_src, params,title,content,theme)

//Arbitrarily close a user's tooltip
//Includes sanity checks.
/proc/closeToolTip(mob/user)
	if(istype(user))
		if(user.client && user.client.tooltips)
			user.client.tooltips.hide()


code:
<!DOCTYPE html>

	<title>Tooltip</title>
	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />


code:
var/global/list/uplinks = list()

/**
 * Uplinks
 *
 * All /obj/item(s) have a hidden_uplink var. By default it's null. Give the item one with 'new(src') (it must be in it's contents). Then add 'uses.'
 * Use whatever conditionals you want to check that the user has an uplink, and then call interact() on their uplink.
 * You might also want the uplink menu to open if active. Check if the uplink is 'active' and then interact() with it.
**/
/obj/item/device/uplink
	name = "syndicate uplink"
	desc = "There is something wrong if you're examining this."
	var/active = FALSE
	var/lockable = TRUE
	var/telecrystals = 20

	var/owner = null
	var/datum/game_mode/gamemode = null
	var/spent_telecrystals = 0
	var/purchase_log = ""

/obj/item/device/uplink/New()
	..()
	uplinks += src

/obj/item/device/uplink/Destroy()
	uplinks -= src
	return ..()

/obj/item/device/uplink/interact(mob/user)
	active = TRUE
	ui_interact(user)

/obj/item/device/uplink/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
									datum/tgui/master_ui = null, datum/ui_state/state = inventory_state)
	ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
	if(!ui)
		ui = new(user, src, ui_key, "uplink", name, 450, 750, master_ui, state)
		ui.set_autoupdate(FALSE) // This UI is only ever opened by one person, and never is updated outside of user input.
		ui.set_style("syndicate")
		ui.open()

/obj/item/device/uplink/ui_data(mob/user)
	var/list/data = list()
	data["telecrystals"] = telecrystals
	data["lockable"] = lockable

	var/list/uplink_items = get_uplink_items(gamemode)
	data["categories"] = list()
	for(var/category in uplink_items)
		var/list/cat = list(
			"name" = category,
			"items" = list(),
		)
		for(var/item in uplink_items[category])
			var/datum/uplink_item/I = uplink_items[category][item]
			cat["items"] += list(list(
				"name" = I.name,
				"cost" = I.cost,
				"desc" = I.desc,
			))
		data["categories"] += list(cat)
	return data

/obj/item/device/uplink/ui_act(action, params)
	if(!active)
		return

	switch(action)
		if("buy")
			var/item = params["item"]

			var/list/uplink_items = get_uplink_items(gamemode)
			var/list/buyable_items = list()
			for(var/category in uplink_items)
				buyable_items += uplink_items[category]

			if(item in buyable_items)
				var/datum/uplink_item/I = buyable_items[item]
				I.buy(usr, src)
				. = TRUE
		if("lock")
			active = FALSE
			SStgui.close_uis(src)

/obj/item/device/uplink/ui_host()
	return loc

// Refund certain items by hitting the uplink with it.
/obj/item/device/radio/uplink/attackby(obj/item/I, mob/user, params)
	for(var/item in subtypesof(/datum/uplink_item))
		var/datum/uplink_item/UI = item
		var/path = initial(UI.item)
		var/cost = initial(UI.cost)
		var/refundable = initial(UI.refundable)
		if(I.type == path && refundable)
			hidden_uplink.telecrystals += cost
			hidden_uplink.spent_telecrystals -= cost
			user << "[I] refunded."
			qdel(I)
			return
	..()

// A collection of pre-set uplinks, for admin spawns.
/obj/item/device/radio/uplink/New()
	..()
	icon_state = "radio"
	hidden_uplink = new(src)
	hidden_uplink.active = TRUE
	hidden_uplink.lockable = FALSE

/obj/item/device/radio/uplink/nuclear/New()
	..()
	hidden_uplink.gamemode = /datum/game_mode/nuclear

/obj/item/device/multitool/uplink/New()
	..()
	hidden_uplink = new(src)
	hidden_uplink.active = TRUE
	hidden_uplink.lockable = FALSE