code:
// AI (i.e. game AI, not the AI player) controlled bots

/obj/machinery/bot
	icon = 'icons/obj/aibots.dmi'
	layer = MOB_LAYER
	light_range = 3
	use_power = 0
	var/obj/item/weapon/card/id/botcard			// the ID card that the bot "holds"
	var/list/prev_access = list()
	var/on = 1
	var/health = 0 //do not forget to set health for your bot!
	var/maxhealth = 0
	var/fire_dam_coeff = 1.0
	var/brute_dam_coeff = 1.0
	var/open = 0//Maint panel
	var/locked = 1
	var/hacked = 0 //Used to differentiate between being hacked by silicons and emagged by humans.
	var/text_hack = ""		//Custom text returned to a silicon upon hacking a bot.
	var/text_dehack = "" 	//Text shown when resetting a bots hacked status to normal.
	var/text_dehack_fail = "" //Shown when a silicon tries to reset a bot emagged with the emag item, which cannot be reset.
	var/declare_message = "" //What the bot will display to the HUD user.
	var/frustration = 0 //Used by some bots for tracking failures to reach their target.
	var/speed = 2 //The speed at which the bot moves, or the number of times it moves per process() tick.
	var/turf/ai_waypoint //The end point of a bot's path, or the target location.
	var/list/path = list() //List of turfs through which a bot 'steps' to reach the waypoint.
	var/pathset = 0
	var/list/ignore_list = list() //List of unreachable targets for an ignore-list enabled bot to ignore.
	var/mode = 0 //Standardizes the vars that indicate the bot is busy with its function.
	var/tries = 0 //Number of times the bot tried and failed to move.
	var/remote_disabled = 0 //If enabled, the AI cannot *Remotely* control a bot. It can still control it through cameras.
	var/mob/living/silicon/ai/calling_ai //Links a bot to the AI calling it.
	var/obj/item/device/radio/Radio //The bot's radio, for speaking to people.
	var/radio_frequency //The bot's default radio speaking freqency. Recommended to be on a department frequency.
	//var/emagged = 0 //Urist: Moving that var to the general /bot tree as it's used by most bots
	var/auto_patrol = 0// set to make bot automatically patrol
	var/turf/patrol_target	// this is turf to navigate to (location of beacon)
	var/turf/summon_target	// The turf of a user summoning a bot.
	var/new_destination		// pending new destination (waiting for beacon response)
	var/destination			// destination description tag
	var/next_destination	// the next destination in the patrol route

	var/blockcount = 0		//number of times retried a blocked path
	var/awaiting_beacon	= 0	// count of pticks awaiting a beacon response

	var/nearest_beacon			// the nearest beacon's tag
	var/turf/nearest_beacon_loc	// the nearest beacon's location

	var/beacon_freq = 1445		// navigation beacon frequency
	var/control_freq = 1447		// bot control frequency

	var/bot_filter 				// The radio filter the bot uses to identify itself on the network.

	var/bot_type = 0 //The type of bot it is, for radio control.
	#define SEC_BOT				1	// Secutritrons (Beepsky) and ED-209s
	#define MULE_BOT			2	// MULEbots
	#define FLOOR_BOT			3	// Floorbots
	#define CLEAN_BOT			4	// Cleanbots
	#define MED_BOT				5	// Medibots

	#define DEFAULT_SCAN_RANGE		7	//default view range for finding targets.

	//Mode defines
	#define BOT_IDLE 			0	// idle
	#define BOT_HUNT 			1	// found target, hunting
	#define BOT_PREP_ARREST 	2	// at target, preparing to arrest
	#define BOT_ARREST			3	// arresting target
	#define BOT_START_PATROL	4	// start patrol
	#define BOT_PATROL			5	// patrolling
	#define BOT_SUMMON			6	// summoned by PDA
	#define BOT_CLEANING 		7	// cleaning (cleanbots)
	#define BOT_REPAIRING		8	// repairing hull breaches (floorbots)
	#define BOT_MOVING			9	// for clean/floor bots, when moving.
	#define BOT_HEALING			10	// healing people (medbots)
	#define BOT_RESPONDING		11	// responding to a call from the AI
	#define BOT_LOADING			12	// loading/unloading
	#define BOT_DELIVER			13	// moving to deliver
	#define BOT_GO_HOME			14	// returning to home
	#define BOT_BLOCKED			15	// blocked
	#define BOT_NAV				16	// computing navigation
	#define BOT_WAIT_FOR_NAV	17	// waiting for nav computation
	#define BOT_NO_ROUTE		18	// no destination beacon found (or no route)
	var/list/mode_name = list("In Pursuit","Preparing to Arrest","Arresting","Beginning Patrol","Patrolling","Summoned by PDA", \
	"Cleaning", "Repairing", "Proceeding to work site","Healing","Responding","Loading/Unloading","Navigating to Delivery Location","Navigating to Home", \
	"Waiting for clear path","Calculating navigation path","Pinging beacon network","Unable to reach destination")
	//This holds text for what the bot is mode doing, reported on the AI's bot control interface.

/obj/machinery/bot/proc/turn_on()
	if(stat)	return 0
	on = 1
	set_light(initial(luminosity))
	return 1

/obj/machinery/bot/proc/turn_off()
	on = 0
	set_light(0)
	bot_reset() //Resets an AI's call, should it exist.

/obj/machinery/bot/New()
	..()
	SSbot.processing += src //Global bot list
	botcard = new /obj/item/weapon/card/id(src)
	set_custom_texts()
	Radio = new /obj/item/device/radio(src)
	Radio.listening = 0 //Makes bot radios transmit only so no one hears things while adjacent to one.

/obj/machinery/bot/Destroy()
	if(radio_controller)
		radio_controller.remove_object(src,beacon_freq)
		if(bot_filter)
			radio_controller.remove_object(src,control_freq)
	qdel(Radio)
	qdel(botcard)
	..()

/obj/machinery/bot/proc/add_to_beacons(bot_filter) //Master filter control for bots. Must be placed in the bot's local New() to support map spawned bots.
	if(radio_controller)
		radio_controller.add_object(src, beacon_freq, filter = RADIO_NAVBEACONS)
		if(bot_filter)
			radio_controller.add_object(src, control_freq, filter = bot_filter)

/obj/machinery/bot/proc/explode()
	SSbot.processing -= src
	qdel(src)

/obj/machinery/bot/proc/healthcheck()
	if (health <= 0)
		explode()

/obj/machinery/bot/proc/Emag(mob/user as mob) //Master Emag proc. Ensure this is called in your bot before setting unique functions.
	if(locked) //First emag application unlocks the bot's interface. Apply a screwdriver to use the emag again.
		locked = 0
		emagged = 1
		user << "You bypass [src]'s controls."
	if(!locked && open) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging.
		emagged = 2
		remote_disabled = 1 //Manually emagging the bot locks out the AI built in panel.
		locked = 1 //Access denied forever!
		bot_reset()
		turn_on() //The bot automatically turns on when emagged, unless recently hit with EMP.
	else //Bot is unlocked, but the maint panel has not been opened with a screwdriver yet.
		user << "You need to open maintenance panel first."

/obj/machinery/bot/examine(mob/user)
	..()
	if (health < maxhealth)
		if (health > maxhealth/3)
			user << "[src]'s parts look loose."
		else
			user << "[src]'s parts look very loose."
	else
		user << "[src] is in pristine condition."

/obj/machinery/bot/attack_alien(var/mob/living/carbon/alien/user as mob)
	user.changeNext_move(CLICK_CD_MELEE)
	user.do_attack_animation(src)
	health -= rand(15,30)*brute_dam_coeff
	visible_message("[user] has slashed [src]!")
	playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1)
	if(prob(10))
		new /obj/effect/decal/cleanable/oil(loc)
	healthcheck()

/obj/machinery/bot/attack_animal(var/mob/living/simple_animal/M as mob)
	M.do_attack_animation(src)
	if(M.melee_damage_upper == 0)
		return
	M.changeNext_move(CLICK_CD_MELEE)
	health -= M.melee_damage_upper
	visible_message("[M] has [M.attacktext] [src]!")
	add_logs(M, src, "attacked", admin=0)
	if(prob(10))
		new /obj/effect/decal/cleanable/oil(loc)
	healthcheck()

/obj/machinery/bot/Topic(href, href_list) //Master Topic to handle common functions.
	. = ..()
	if (.)
		return
	if(topic_denied(usr))
		usr << "[src]'s interface is not responding!"
		href_list = list()
		return

	usr.set_machine(src)
	add_fingerprint(usr)
	if((href_list["power"]) && (allowed(usr) || !locked))
		if (on)
			turn_off()
		else
			turn_on()

	switch(href_list["operation"])
		if("patrol")
			auto_patrol = !auto_patrol
			mode = BOT_IDLE
		if("remote")
			remote_disabled = !remote_disabled
		if("hack")
			if(emagged != 2)
				emagged = 2
				hacked = 1
				locked = 1
				usr << "[text_hack]"
				bot_reset()
			else if(!hacked)
				usr << "[text_dehack_fail]"
			else
				emagged = 0
				hacked = 0
				usr << "[text_dehack]"
				bot_reset()
	updateUsrDialog()

/obj/machinery/bot/proc/topic_denied(mob/user) //Access check proc for bot topics! Remember to place in a bot's individual Topic if desired.
	// 0 for access, 1 for denied.
	if(emagged == 2) //An emagged bot cannot be controlled by humans, silicons can if one hacked it.
		if(!hacked) //Manually emagged by a human - access denied to all.
			return 1
		else if(!issilicon(user)) //Bot is hacked, so only silicons are allowed access.
			return 1
	else
		return 0

/obj/machinery/bot/proc/bot_process() //Master process which handles code common across most bots.

	set background = BACKGROUND_ENABLED

	if(!on)
		return

	switch(mode) //High-priority overrides are processed first. Bots can do nothing else while under direct command.
		if(BOT_RESPONDING)	//Called by the AI.
			call_mode()
			return
		if(BOT_SUMMON)		//Called by PDA
			bot_summon()
			return
	return 1 //Successful completion. Used to prevent child process() continuing if this one is ended early.

/obj/machinery/bot/attackby(obj/item/weapon/W as obj, mob/user as mob, params)
	if(istype(W, /obj/item/weapon/screwdriver))
		if(!locked)
			open = !open
			user << "Maintenance panel is now [open ? "opened" : "closed"]."
		else
			user << "Maintenance panel is locked."
	else
		user.changeNext_move(CLICK_CD_MELEE)
		if(istype(W, /obj/item/weapon/weldingtool) && user.a_intent != "harm")
			if(health >= maxhealth)
				user << "[src] does not need a repair."
				return
			if(!open)
				user << "Unable to repair with the maintenance panel closed."
				return
			var/obj/item/weapon/weldingtool/WT = W
			if(WT.remove_fuel(0, user))
				health = min(maxhealth, health+10)
				user.visible_message("[user] repairs [src]!","You repair [src]!")
			else
				user << "The welder must be on for this task."
		else
			if(W.force) //if force is non-zero
				var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
				s.set_up(5, 1, src)
				switch(W.damtype)
					if("fire")
						health -= W.force * fire_dam_coeff
						s.start()
					if("brute")
						health -= W.force * brute_dam_coeff
						s.start()
				..()
				healthcheck()

/obj/machinery/bot/emag_act(mob/user as mob)
	if(emagged < 2)
		Emag(user)

/obj/machinery/bot/bullet_act(var/obj/item/projectile/Proj)
	if((Proj.damage_type == BRUTE || Proj.damage_type == BURN))
		health -= Proj.damage
		if(prob(75) && Proj.damage > 0)
			var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
			s.set_up(5, 1, src)
			s.start()
		..()
		healthcheck()
	return

/obj/machinery/bot/blob_act()
	health -= rand(20,40)*fire_dam_coeff
	healthcheck()
	return

/obj/machinery/bot/ex_act(severity, target)
	switch(severity)
		if(1.0)
			explode()
			return
		if(2.0)
			health -= rand(5,10)*fire_dam_coeff
			health -= rand(10,20)*brute_dam_coeff
			healthcheck()
			return
		if(3.0)
			if (prob(50))
				health -= rand(1,5)*fire_dam_coeff
				health -= rand(1,5)*brute_dam_coeff
				healthcheck()
				return
	return

/obj/machinery/bot/emp_act(severity)
	var/was_on = on
	stat |= EMPED
	var/obj/effect/overlay/pulse2 = new/obj/effect/overlay ( loc )
	pulse2.icon = 'icons/effects/effects.dmi'
	pulse2.icon_state = "empdisable"
	pulse2.name = "emp sparks"
	pulse2.anchored = 1
	pulse2.dir = pick(cardinal)

	spawn(10)
		qdel(pulse2)
	if (on)
		turn_off()
	spawn(severity*300)
		stat &= ~EMPED
		if (was_on)
			turn_on()

/obj/machinery/bot/proc/hack(mob/user)
	var/hack
	if(issilicon(user)) //Allows silicons to toggle the emag status of a bot.
		hack += "[emagged == 2 ? "Software compromised! Unit may exhibit dangerous or erratic behavior." : "Unit operating normally. Release safety lock?"]
"
		hack += "Harm Prevention Safety System: [emagged ? "DANGER" : "Engaged"]
"
	else if(!locked) //Humans with access can use this option to hide a bot from the AI's remote control panel and PDA control.
		hack += "Remote network control radio: [remote_disabled ? "Disconnected" : "Connected"]

"
	return hack

/obj/machinery/bot/proc/set_custom_texts() //Superclass for setting hack texts. Appears only if a set is not given to a bot locally.
	text_hack = "You hack [name]."
	text_dehack = "You reset [name]."
	text_dehack_fail = "You fail to reset [name]."

/obj/machinery/bot/attack_ai(mob/user as mob)
	attack_hand(user)

/obj/machinery/bot/proc/speak(var/message, freq) //Pass a message to have the bot say() it. Pass a frequency to say it on the radio.
	if((!on) || (!message))
		return
	if(freq)
		Radio.set_frequency(radio_frequency)
		Radio.talk_into(src, message, radio_frequency)
	else
		say(message)
	return

	//Generalized behavior code, override where needed!

/*
scan() will search for a given type (such as turfs, human mobs, or objects) in the bot's view range, and return a single result.
Arguments: The object type to be searched (such as "/mob/living/carbon/human"), the old scan result to be ignored, if one exists,
and the view range, which defaults to 7 (full screen) if an override is not passed.
If the bot maintains an ignore list, it is also checked here.

Example usage: patient = scan(/mob/living/carbon/human, oldpatient, 1)
The proc would return a human next to the bot to be set to the patient var.
Pass the desired type path itself, declaring a temporary var beforehand is not required.
*/
obj/machinery/bot/proc/scan(var/scan_type, var/old_target, var/scan_range = DEFAULT_SCAN_RANGE)
	var/final_result
	for (var/scan in view (scan_range, src) ) //Search for something in range!
		if(!istype(scan, scan_type)) //Check that the thing we found is the type we want!
			continue //If not, keep searching!
		if( !(scan in ignore_list) && (scan != old_target) ) //Filter for blacklisted elements, usually unreachable or previously processed oness
			var/scan_result = process_scan(scan) //Some bots may require additional processing when a result is selected.
			if( scan_result )
				final_result = scan_result
			else
				continue //The current element failed assessment, move on to the next.
		return final_result

//When the scan finds a target, run bot specific processing to select it for the next step. Empty by default.
obj/machinery/bot/proc/process_scan(var/scan_target)
	return scan_target

/obj/machinery/bot/proc/add_to_ignore(var/subject)
	if(ignore_list.len < 50) //This will help keep track of them, so the bot is always trying to reach a blocked spot.
		ignore_list |= subject
	else if (ignore_list.len >= subject) //If the list is full, insert newest, delete oldest.
		ignore_list -= ignore_list[1]
		ignore_list |= subject

/*
Movement proc for stepping a bot through a path generated through A-star.
Pass a positive integer as an argument to override a bot's default speed.
*/
obj/machinery/bot/proc/bot_move(var/dest, var/move_speed)

	if(!dest || !path || path.len == 0) //A-star failed or a path/destination was not set.
		path = list()
		return 0
	if(get_turf(src) == get_turf(dest)) //We have arrived, no need to move again.
		return 1
	var/success
	var/step_count = move_speed ? move_speed : speed //If a value is passed into move_speed, use that instead of the default speed var.
	if(step_count >= 1 && tries < 4)
		for(step_count, step_count >= 1,step_count--)
			success = bot_step(dest)
			if (success)
				tries = 0
			else
				tries++
				break
			sleep(4)
	else
		return 0
	return 1

obj/machinery/bot/proc/bot_step(var/dest)
	if(path && path.len > 1)
		step_towards(src, path[1])
		if(get_turf(src) == path[1]) //Successful move
			path -= path[1]
		else
			return 0
	else if(path.len == 1)
		step_to(src, dest)
		path = list()
	return 1

/obj/machinery/bot/proc/check_bot_access()
	if(mode != BOT_SUMMON && mode != BOT_RESPONDING)
		botcard.access = prev_access

/obj/machinery/bot/proc/call_bot(var/caller, var/turf/waypoint)
	bot_reset() //Reset a bot before setting it to call mode.
	var/area/end_area = get_area(waypoint)

	//For giving the bot temporary all-access.
	var/obj/item/weapon/card/id/all_access = new /obj/item/weapon/card/id
	var/datum/job/captain/All = new/datum/job/captain
	all_access.access = All.get_access()

	path = get_path_to(src, waypoint, src, /turf/proc/Distance_cardinal, 0, 200, id=all_access)
	calling_ai = caller //Link the AI to the bot!
	ai_waypoint = waypoint

	if(path && path.len) //Ensures that a valid path is calculated!
		if(!on)
			turn_on() //Saves the AI the hassle of having to activate a bot manually.
		botcard = all_access //Give the bot all-access while under the AI's command.
		calling_ai << "\icon[src] [name] called to [end_area.name]. [path.len-1] meters to destination."
		pathset = 1
		mode = BOT_RESPONDING
		tries = 0
	else
		calling_ai << "Failed to calculate a valid route. Ensure destination is clear of obstructions and within range."
		calling_ai = null
		path = list()

/obj/machinery/bot/proc/call_mode() //Handles preparing a bot for a call, as well as calling the move proc.
//Handles the bot's movement during a call.
	var/success = bot_move(ai_waypoint, 3)
	if (!success)
		if(calling_ai)
			calling_ai << "\icon[src] [get_turf(src) == ai_waypoint ? "[src] successfully arrived to waypoint." : "[src] failed to reach waypoint."]"
			calling_ai = null
		bot_reset()

obj/machinery/bot/proc/bot_reset()
	if(calling_ai) //Simple notification to the AI if it called a bot. It will not know the cause or identity of the bot.
		calling_ai << "Call command to a bot has been reset."
		calling_ai = null
	path = list()
	summon_target = null
	pathset = 0
	botcard.access = prev_access
	tries = 0
	mode = BOT_IDLE

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Patrol and summon code!
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

/obj/machinery/bot/proc/bot_patrol()
	patrol_step()
	spawn(5)
		if(mode == BOT_PATROL)
			patrol_step()
	return

obj/machinery/bot/proc/start_patrol()

	if(tries >= 4) //Bot is trapped, so stop trying to patrol.
		auto_patrol = 0
		tries = 0
		speak("Unable to start patrol.")

		return

	if(!auto_patrol) //A bot not set to patrol should not be patrolling.
		mode = BOT_IDLE
		return

	if(patrol_target)		// has patrol target
		spawn(0)
			calc_path()		// Find a route to it
			if(path.len == 0)
				patrol_target = 0
				return
			mode = BOT_PATROL
	else					// no patrol target, so need a new one
		find_patrol_target()
		speak("Engaging patrol mode.")
		tries++
	return

// perform a single patrol step

/obj/machinery/bot/proc/patrol_step()

	if(loc == patrol_target)		// reached target

		at_patrol_target()
		return

	else if(path.len > 0 && patrol_target)		// valid path
		var/turf/next = path[1]
		if(next == loc)
			path -= next
			return

		var/moved = bot_move(patrol_target)//step_towards(src, next)	// attempt to move
		if(moved)	// successful move
			blockcount = 0

		else		// failed to move
			blockcount++

			if(blockcount > 5)	// attempt 5 times before recomputing
				// find new path excluding blocked turf

				spawn(2)
					calc_path(next)
					if(path.len == 0)
						find_patrol_target()
					else
						blockcount = 0
						tries = 0

				return

			return

	else	// no path, so calculate new one
		mode = BOT_START_PATROL

	return

// finds a new patrol target
/obj/machinery/bot/proc/find_patrol_target()
	send_status()
	if(awaiting_beacon)			// awaiting beacon response
		awaiting_beacon++
		if(awaiting_beacon > 5)	// wait 5 secs for beacon response
			find_nearest_beacon()	// then go to nearest instead
		return
	if(next_destination)
		set_destination(next_destination)
	else

		find_nearest_beacon()
	return

// finds the nearest beacon to self
// signals all beacons matching the patrol code
/obj/machinery/bot/proc/find_nearest_beacon()
	nearest_beacon = null
	new_destination = "__nearest__"
	post_signal(beacon_freq, "findbeacon", "patrol")
	awaiting_beacon = 1
	spawn(10)
		awaiting_beacon = 0
		if(nearest_beacon)
			set_destination(nearest_beacon)
			tries = 0
		else
			auto_patrol = 0
			mode = BOT_IDLE
			speak("Disengaging patrol mode.")
			send_status()

/obj/machinery/bot/proc/at_patrol_target()

	find_patrol_target()
	return

// sets the current destination
// signals all beacons matching the patrol code
// beacons will return a signal giving their locations
/obj/machinery/bot/proc/set_destination(var/new_dest)
	new_destination = new_dest
	post_signal(beacon_freq, "findbeacon", "patrol")
	awaiting_beacon = 1

// receive a radio signal
// used for beacon reception

/obj/machinery/bot/receive_signal(datum/signal/signal)
	//log_admin("DEBUG \[[// world.timeofday]\]: /obj/machinery/bot/receive_signal([signal.debug_print()])")
	if(!on)
		return
/*
	if(!signal.data["beacon"])

		for(var/x in signal.data)
			world << "* [x] = [signal.data[x]]"
	*/

	var/recv = signal.data["command"]
	// process all-bot input

	if(recv=="bot_status")
		send_status()

	// check to see if we are the commanded bot
	if(signal.data["active"] == src)
		if(emagged == 2 || remote_disabled) //Emagged bots do not respect anyone's authority! Bots with their remote controls off cannot get commands.
			return
	// process control input
		switch(recv)
			if("stop")
				bot_reset() //HOLD IT!!
				auto_patrol = 0
				return

			if("go")
				auto_patrol = 1
				return

			if("summon")
				bot_reset()
				var/list/user_access = signal.data["useraccess"]
				summon_target = signal.data["target"]	//Location of the user
				if(user_access.len != 0)
					botcard.access = user_access + prev_access //Adds the user's access, if any.
				mode = BOT_SUMMON
				calc_summon_path()
				speak("Responding.", radio_frequency)
				return

	// receive response from beacon
	recv = signal.data["beacon"]
	var/valid = signal.data["patrol"]
	if(!recv || !valid)
		return
	if(recv == new_destination)	// if the recvd beacon location matches the set destination
								// the we will navigate there
		destination = new_destination
		patrol_target = signal.source.loc
		next_destination = signal.data["next_patrol"]
		awaiting_beacon = 0

	// if looking for nearest beacon
	else if(new_destination == "__nearest__")
		var/dist = get_dist(src,signal.source.loc)
		if(nearest_beacon)
			// note we ignore the beacon we are located at
			if(dist>1 && dist<get_dist(src,nearest_beacon_loc))
				nearest_beacon = recv
				nearest_beacon_loc = signal.source.loc
				return
			else
				return
		else if(dist > 1)
			nearest_beacon = recv
			nearest_beacon_loc = signal.source.loc
	return

// send a radio signal with a single data key/value pair
/obj/machinery/bot/proc/post_signal(var/freq, var/key, var/value)
	post_signal_multiple(freq, list("[key]" = value) )

// send a radio signal with multiple data key/values
/obj/machinery/bot/proc/post_signal_multiple(var/freq, var/list/keyval)
	if(!z || z != 1) //Bot control will only work on station.
		return
	var/datum/radio_frequency/frequency = radio_controller.return_frequency(freq)

	if(!frequency) return

	var/datum/signal/signal = new()
	signal.source = src
	signal.transmission_method = 1
//	for(var/key in keyval)
//		signal.data[key] = keyval[key]
	signal.data = keyval
//	world << "sent [key],[keyval[key]] on [freq]"
	if(signal.data["findbeacon"])
		frequency.post_signal(src, signal, filter = RADIO_NAVBEACONS)
	else if(signal.data["type"] == bot_type)
		frequency.post_signal(src, signal, filter = bot_filter)
	else
		frequency.post_signal(src, signal)

// signals bot status etc. to controller
/obj/machinery/bot/proc/send_status()
	if(remote_disabled || emagged == 2)
		return
	var/list/kv = list(
	"type" = bot_type,
	"name" = name,
	"loca" = get_area(src),	// area
	"mode" = mode
	)
	post_signal_multiple(control_freq, kv)

obj/machinery/bot/proc/bot_summon()
		// summoned to PDA
	summon_step()
	return

// calculates a path to the current destination
// given an optional turf to avoid
/obj/machinery/bot/proc/calc_path(var/turf/avoid)
	check_bot_access()
	path = get_path_to(loc, patrol_target, src, /turf/proc/Distance_cardinal, 0, 120, id=botcard, exclude=avoid)

/obj/machinery/bot/proc/calc_summon_path(var/turf/avoid)
	check_bot_access()
	path = get_path_to(loc, summon_target, src, /turf/proc/Distance_cardinal, 0, 150, id=botcard, exclude=avoid)
	if(!path.len || tries >= 5) //Cannot reach target. Give up and announce the issue.
		speak("Summon command failed, destination unreachable.",radio_frequency)
		bot_reset()

/obj/machinery/bot/proc/summon_step()

	if(loc == summon_target)		// Arrived to summon location.
		bot_reset()
		return

	else if(path.len > 0 && summon_target)		//Proper path acquired!
		var/turf/next = path[1]
		if(next == loc)
			path -= next
			return

		var/moved = bot_move(summon_target, 3)	// Move attempt
		if(moved)
			blockcount = 0

		else		// failed to move
			blockcount++

			if(blockcount > 5)	// attempt 5 times before recomputing
				// find new path excluding blocked turf
				spawn(2)
					calc_summon_path(next)
					tries++
					return

				return

	else	// no path, so calculate new one
		calc_summon_path()

	return

/obj/machinery/bot/Bump(M as mob|obj) //Leave no door unopened!
	if((istype(M, /obj/machinery/door/airlock) ||  istype(M, /obj/machinery/door/window)) && (!isnull(botcard)))
		var/obj/machinery/door/D = M
		if(D.check_access(botcard))
			D.open()
			frustration = 0
	else if((istype(M, /mob/living/)) && (!anchored))
		var/mob/living/Mb = M
		loc = Mb.loc
		frustration = 0
	return


code:
//Cleanbot assembly
/obj/item/weapon/bucket_sensor
	desc = "It's a bucket. With a sensor attached."
	name = "proxy bucket"
	icon = 'icons/obj/aibots.dmi'
	icon_state = "bucket_proxy"
	force = 3.0
	throwforce = 5.0
	throw_speed = 2
	throw_range = 5
	w_class = 3.0
	var/created_name = "Cleanbot"

//Cleanbot
/obj/machinery/bot/cleanbot
	name = "\improper Cleanbot"
	desc = "A little cleaning robot, he looks so excited!"
	icon = 'icons/obj/aibots.dmi'
	icon_state = "cleanbot0"
	layer = 5.0
	density = 0
	anchored = 0
	//weight = 1.0E7
	health = 25
	maxhealth = 25
	var/blood = 1
	var/list/target_types = list()
	var/obj/effect/decal/cleanable/target
	var/obj/effect/decal/cleanable/oldtarget
	var/max_targets = 50 //Maximum number of targets a cleanbot can ignore.
	var/oldloc = null
	req_one_access = list(access_janitor, access_robotics)
//	var/patrol_path[] = null
//	var/beacon_freq = 1445		// navigation beacon frequency
	var/closest_dist
	var/closest_loc
	var/failed_steps
	var/next_dest
	var/next_dest_loc
	radio_frequency = SERV_FREQ //Service
	bot_type = CLEAN_BOT
	bot_filter = RADIO_CLEANBOT

/obj/machinery/bot/cleanbot/New()
	..()
	get_targets()
	icon_state = "cleanbot[on]"

	var/datum/job/janitor/J = new/datum/job/janitor
	botcard.access = J.get_access()
	prev_access = botcard.access

	spawn(5)
		add_to_beacons(bot_filter)

/obj/machinery/bot/cleanbot/turn_on()
	..()
	icon_state = "cleanbot[on]"
	updateUsrDialog()

/obj/machinery/bot/cleanbot/turn_off()
	..()
	icon_state = "cleanbot[on]"
	updateUsrDialog()

/obj/machinery/bot/cleanbot/bot_reset()
	..()
	ignore_list = list() //Allows the bot to clean targets it previously ignored due to being unreachable.
	target = null
	oldtarget = null
	oldloc = null

/obj/machinery/bot/cleanbot/set_custom_texts()
	text_hack = "You corrupt [name]'s cleaning software."
	text_dehack = "[name]'s software has been reset!"
	text_dehack_fail = "[name] does not seem to respond to your repair code!"

/obj/machinery/bot/cleanbot/attack_hand(mob/user as mob)
	. = ..()
	if (.)
		return
	usr.set_machine(src)
	interact(user)

/obj/machinery/bot/cleanbot/interact(mob/user as mob)
	var/dat
	dat += hack(user)
	dat += text({"
<TT>Cleaner v1.1 controls</TT>

Status: []

Behaviour controls are [locked ? "locked" : "unlocked"]

Maintenance panel panel is [open ? "opened" : "closed"]"},
text("[on ? "On" : "Off"]"))
	if(!locked || issilicon(user))
		dat += text({"
Cleans Blood: []
"}, text("[blood ? "Yes" : "No"]"))
		dat += text({"
Patrol station: []
"}, text("[auto_patrol ? "Yes" : "No"]"))

	var/datum/browser/popup = new(user, "autoclean", "Automatic Station Cleaner v1.1")
	popup.set_content(dat)
	popup.open()
	return

/obj/machinery/bot/cleanbot/Topic(href, href_list)

	..()
	switch(href_list["operation"])
		if("blood")
			blood =!blood
			get_targets()
			updateUsrDialog()
		if("freq")
			var/freq = text2num(input("Select frequency for  navigation beacons", "Frequency", num2text(beacon_freq / 10))) * 10
			if (freq > 0)
				beacon_freq = freq
			updateUsrDialog()

/obj/machinery/bot/cleanbot/attackby(obj/item/weapon/W, mob/user as mob, params)
	if (istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda))
		if(allowed(user) && !open && !emagged)
			locked = !locked
			user << "You [ locked ? "lock" : "unlock"] \the [src] behaviour controls."
		else
			if(emagged)
				user << "ERROR"
			if(open)
				user << "Please close the access panel before locking it."
			else
				user << "\The [src] doesn't seem to respect your authority."
	else
		return ..()

/obj/machinery/bot/cleanbot/Emag(mob/user as mob)
	..()
	if(emagged == 2)
		if(user)
			user << "[src] buzzes and beeps."

/obj/machinery/bot/cleanbot/process_scan(var/obj/effect/decal/cleanable/D)
	for(var/T in target_types)
		if(istype(D, T))
			return D

/obj/machinery/bot/cleanbot/bot_process()
	if (!..())
		return

	if(mode == BOT_CLEANING)
		return

	if(emagged == 2) //Emag functions
		if(istype(loc,/turf/simulated))

			if(prob(10)) //Wets floors randomly
				var/turf/simulated/T = loc
				T.MakeSlippery()

			if(prob(5)) //Spawns foam!
				visible_message("[src] whirs and bubbles violently, before releasing a plume of froth!")
				PoolOrNew(/obj/effect/effect/foam, loc)

	else if (prob(5))
		visible_message("[src] makes an excited beeping booping sound!")

	if(!target) //Search for cleanables it can see.
		target = scan(/obj/effect/decal/cleanable/, oldtarget)
		oldtarget = target

	if(!target)
		if(loc != oldloc)
			oldtarget = null

		if(auto_patrol)
			if(mode == BOT_IDLE || mode == BOT_START_PATROL)
				start_patrol()

			if(mode == BOT_PATROL)
				bot_patrol()

		return

	if(target)
		if(!path || path.len == 0) //No path, need a new one
			//Try to produce a path to the target, and ignore airlocks to which it has access.
			path = get_path_to(loc, target.loc, src, /turf/proc/Distance, 0, 30, id=botcard)
			if (!bot_move(target))
				add_to_ignore(target)
				target = null
				path = list()
				return
			mode = BOT_MOVING
		else if (!bot_move(target))
			target = null
			mode = BOT_IDLE
			return

	if(target && loc == target.loc)
		clean(target)
		path = list()
		target = null

	oldloc = loc

/obj/machinery/bot/cleanbot/proc/get_targets()
	target_types = new/list()

	target_types += /obj/effect/decal/cleanable/oil
	target_types += /obj/effect/decal/cleanable/vomit
	target_types += /obj/effect/decal/cleanable/robot_debris
	target_types += /obj/effect/decal/cleanable/crayon
	target_types += /obj/effect/decal/cleanable/molten_item
	target_types += /obj/effect/decal/cleanable/tomato_smudge
	target_types += /obj/effect/decal/cleanable/egg_smudge
	target_types += /obj/effect/decal/cleanable/pie_smudge
	target_types += /obj/effect/decal/cleanable/flour
	target_types += /obj/effect/decal/cleanable/ash
	target_types += /obj/effect/decal/cleanable/greenglow
	target_types += /obj/effect/decal/cleanable/dirt

	if(blood)
		target_types += /obj/effect/decal/cleanable/xenoblood/
		target_types += /obj/effect/decal/cleanable/xenoblood/xgibs
		target_types += /obj/effect/decal/cleanable/blood/
		target_types += /obj/effect/decal/cleanable/blood/gibs/
		target_types += /obj/effect/decal/cleanable/blood/drip/
		target_types += /obj/effect/decal/cleanable/trail_holder

/obj/machinery/bot/cleanbot/proc/clean(var/obj/effect/decal/cleanable/target)
	anchored = 1
	icon_state = "cleanbot-c"
	visible_message("[src] begins to clean up [target]")
	mode = BOT_CLEANING
	spawn(50)
		if(mode == BOT_CLEANING)
			qdel(target)
			anchored = 0
			target = null
		mode = BOT_IDLE
		icon_state = "cleanbot[on]"

/obj/machinery/bot/cleanbot/explode()
	on = 0
	visible_message("[src] blows apart!")
	var/turf/Tsec = get_turf(src)

	new /obj/item/weapon/reagent_containers/glass/bucket(Tsec)

	new /obj/item/device/assembly/prox_sensor(Tsec)

	if (prob(50))
		new /obj/item/robot_parts/l_arm(Tsec)

	var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
	s.set_up(3, 1, src)
	s.start()
	qdel(src)
	return

/obj/item/weapon/bucket_sensor/attackby(var/obj/item/W, mob/user as mob, params)
	..()
	if(istype(W, /obj/item/robot_parts/l_arm) || istype(W, /obj/item/robot_parts/r_arm))
		user.drop_item()
		qdel(W)
		var/turf/T = get_turf(loc)
		var/obj/machinery/bot/cleanbot/A = new /obj/machinery/bot/cleanbot(T)
		A.name = created_name
		user << "You add the robot arm to the bucket and sensor assembly. Beep boop!"
		user.unEquip(src, 1)
		qdel(src)

	else if (istype(W, /obj/item/weapon/pen))
		var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN)
		if (!t)
			return
		if (!in_range(src, usr) && loc != usr)
			return
		created_name = t

license: mit


code:
/obj/machinery/bot/ed209
	name = "\improper ED-209 Security Robot"
	desc = "A security robot.  He looks less than thrilled."
	icon = 'icons/obj/aibots.dmi'
	icon_state = "ed2090"
	layer = 5.0
	density = 1
	anchored = 0
//	weight = 1.0E7
	req_access = list(access_security)
	health = 100
	maxhealth = 100
	fire_dam_coeff = 0.7
	brute_dam_coeff = 0.5

	var/lastfired = 0
	var/shot_delay = 3 //.3 seconds between shots
	var/lasercolor = ""
	var/disabled = 0//A holder for if it needs to be disabled, if true it will not seach for targets, shoot at targets, or move, currently only used for lasertag

	//var/lasers = 0

	var/mob/living/carbon/target
	var/oldtarget_name
	var/threatlevel = 0
	var/target_lastloc //Loc of target when arrested.
	var/last_found //There's a delay
//var/emagged = 0 //Emagged Secbots view everyone as a criminal
	var/declare_arrests = 1 //When making an arrest, should it notify everyone wearing sechuds?
	var/idcheck = 1 //If true, arrest people with no IDs
	var/weaponscheck = 1 //If true, arrest people for weapons if they don't have access
	var/check_records = 1 //Does it check security records?
	var/arrest_type = 0 //If true, don't handcuff
	var/projectile = /obj/item/projectile/energy/electrode //Holder for projectile type
	var/shoot_sound = 'sound/weapons/Taser.ogg'
	radio_frequency = SEC_FREQ
	bot_type = SEC_BOT
	bot_filter = RADIO_SECBOT

	//List of weapons that secbots will not arrest for
	var/safe_weapons = list(\
		/obj/item/weapon/gun/energy/laser/bluetag,\
		/obj/item/weapon/gun/energy/laser/redtag,\
		/obj/item/weapon/gun/energy/laser/practice,\
		/obj/item/weapon/melee/classic_baton/telescopic,\
		/obj/item/weapon/gun/energy/kinetic_accelerator)

/obj/item/weapon/ed209_assembly
	name = "\improper ED-209 assembly"
	desc = "Some sort of bizarre assembly."
	icon = 'icons/obj/aibots.dmi'
	icon_state = "ed209_frame"
	item_state = "ed209_frame"
	var/build_step = 0
	var/created_name = "ED-209 Security Robot" //To preserve the name if it's a unique securitron I guess
	var/lasercolor = ""

/obj/machinery/bot/ed209/New(loc,created_name,created_lasercolor)
	..()
	if(created_name)
		name = created_name
	if(created_lasercolor)
		lasercolor = created_lasercolor
	icon_state = "[lasercolor]ed209[on]"
	set_weapon() //giving it the right projectile and firing sound.
	spawn(3)
		var/datum/job/detective/J = new/datum/job/detective
		botcard.access = J.get_access()
		prev_access = botcard.access

		add_to_beacons(bot_filter)
		if(lasercolor)
			shot_delay = 6//Longer shot delay because JESUS CHRIST
			check_records = 0//Don't actively target people set to arrest
			arrest_type = 1//Don't even try to cuff
			req_access = list(access_maint_tunnels, access_theatre)
			arrest_type = 1
			if((lasercolor == "b") && (name == "\improper ED-209 Security Robot"))//Picks a name if there isn't already a custome one
				name = pick("BLUE BALLER","SANIC","BLUE KILLDEATH MURDERBOT")
			if((lasercolor == "r") && (name == "\improper ED-209 Security Robot"))
				name = pick("RED RAMPAGE","RED ROVER","RED KILLDEATH MURDERBOT")

/obj/machinery/bot/ed209/turn_on()
	. = ..()
	icon_state = "[lasercolor]ed209[on]"
	mode = BOT_IDLE
	updateUsrDialog()

/obj/machinery/bot/ed209/turn_off()
	..()
	icon_state = "[lasercolor]ed209[on]"
	updateUsrDialog()

/obj/machinery/bot/ed209/bot_reset()
	..()
	target = null
	oldtarget_name = null
	anchored = 0
	walk_to(src,0)
	last_found = world.time
	set_weapon()

/obj/machinery/bot/ed209/set_custom_texts()
	text_hack = "You disable [name]'s combat inhibitor."
	text_dehack = "You restore [name]'s combat inhibitor."
	text_dehack_fail = "[name] ignores your attempts to restrict him!"

/obj/machinery/bot/ed209/attack_hand(mob/user as mob)
	. = ..()
	if (.)
		return
	var/dat
	dat += hack(user)
	dat += text({"
<TT>Security Unit v2.6 controls</TT>

Status: []

Behaviour controls are [locked ? "locked" : "unlocked"]

Maintenance panel panel is [open ? "opened" : "closed"]
"},

"[on ? "On" : "Off"]" )

	if(!locked || issilicon(user))
		if(!lasercolor)
			dat += text({"

Arrest Unidentifiable Persons: []

Arrest for Unauthorized Weapons: []

Arrest for Warrant: []

Operating Mode: []

Report Arrests[]

Auto Patrol[]"},

"[idcheck ? "Yes" : "No"]",
"[weaponscheck ? "Yes" : "No"]",
"[check_records ? "Yes" : "No"]",
"[arrest_type ? "Detain" : "Arrest"]",
"[declare_arrests ? "Yes" : "No"]",
"[auto_patrol ? "On" : "Off"]" )
	var/datum/browser/popup = new(user, "autoed209", "Automatic Security Unit v2.6")
	popup.set_content(dat)
	popup.open()
	return

/obj/machinery/bot/ed209/Topic(href, href_list)
	if(lasercolor && (istype(usr,/mob/living/carbon/human)))
		var/mob/living/carbon/human/H = usr
		if((lasercolor == "b") && (istype(H.wear_suit, /obj/item/clothing/suit/redtag)))//Opposing team cannot operate it
			return
		else if((lasercolor == "r") && (istype(H.wear_suit, /obj/item/clothing/suit/bluetag)))
			return
	..()

	switch(href_list["operation"])
		if ("idcheck")
			idcheck = !idcheck
		if("weaponscheck")
			weaponscheck = !weaponscheck
			updateUsrDialog()
		if ("ignorerec")
			check_records = !check_records
			updateUsrDialog()
		if ("switchmode")
			arrest_type = !arrest_type
			updateUsrDialog()
		if("declarearrests")
			declare_arrests = !declare_arrests
			updateUsrDialog()

/obj/machinery/bot/ed209/attackby(obj/item/weapon/W as obj, mob/user as mob, params)
	if (istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda))
		if (allowed(user) && !open && !emagged)
			locked = !locked
			user << "Controls are now [locked ? "locked" : "unlocked"]."
		else
			if(emagged)
				user << "ERROR"
			if(open)
				user << "Please close the access panel before locking it."
			else
				user << "Access denied."
	else
		..()
		if(istype(W, /obj/item/weapon/weldingtool) && user.a_intent != "harm") // Any intent but harm will heal, so we shouldn't get angry.
			return
		if (!istype(W, /obj/item/weapon/screwdriver) && (!target)) // Added check for welding tool to fix #2432. Welding tool behavior is handled in superclass.
			if(W.force && W.damtype != STAMINA)//If force is non-zero and damage type isn't stamina.
				threatlevel = user.assess_threat(src)
				threatlevel += 6
				if(threatlevel >= 4)
					target = user
					if(lasercolor)//To make up for the fact that lasertag bots don't hunt
						shootAt(user)
					mode = BOT_HUNT

/obj/machinery/bot/ed209/Emag(mob/user as mob)
	..()
	if(emagged == 2)
		if(user)
			user << "You short out [src]'s target assessment circuits."
			oldtarget_name = user.name
		audible_message("[src] buzzes oddly!")
		declare_arrests = 0
		icon_state = "[lasercolor]ed209[on]"
		set_weapon()

/obj/machinery/bot/ed209/bot_process()
	if (!..())
		return

	if(disabled)
		return

	var/list/targets = list()
	for (var/mob/living/carbon/C in view(7,src)) //Let's find us a target
		var/threatlevel = 0
		if ((C.stat) || (C.lying))
			continue
		threatlevel = C.assess_threat(src, lasercolor)
		//speak(C.real_name + text(": threat: []", threatlevel))
		if (threatlevel < 4 )
			continue

		var/dst = get_dist(src, C)
		if ( dst <= 1 || dst > 7)
			continue

		targets += C
	if (targets.len>0)
		var/mob/living/carbon/t = pick(targets)
		if ((t.stat!=2) && (t.lying != 1) && (!t.handcuffed)) //we don't shoot people who are dead, cuffed or lying down.
			shootAt(t)
	switch(mode)

		if(BOT_IDLE)		// idle
			walk_to(src,0)
			if(!lasercolor) //lasertag bots don't want to arrest anyone
				look_for_perp()	// see if any criminals are in range
			if(!mode && auto_patrol)	// still idle, and set to patrol
				mode = BOT_START_PATROL	// switch to patrol mode

		if(BOT_HUNT)		// hunting for perp
			// if can't reach perp for long enough, go idle
			if (frustration >= 8)
				walk_to(src,0)
				back_to_idle()

			if(target)		// make sure target exists
				if(Adjacent(target) && isturf(target.loc)) // if right next to perp
					playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1)
					icon_state = "[lasercolor]ed209-c"
					spawn(2)
						icon_state = "[lasercolor]ed209[on]"
					var/mob/living/carbon/M = target
					if(istype(M, /mob/living/carbon/human))
						M.stuttering = 5
						M.Stun(5)
						M.Weaken(5)
					else
						M.Weaken(5)
						M.stuttering = 5
						M.Stun(5)

					if(declare_arrests)
						var/area/location = get_area(src)
						speak("[arrest_type ? "Detaining" : "Arresting"] level [threatlevel] scumbag [target] in [location].", radio_frequency)
					target.visible_message("[src] has stunned [target]!",\
											"[src] has stunned [target]!")

					mode = BOT_PREP_ARREST
					anchored = 1
					target_lastloc = M.loc
					return

				else								// not next to perp
					var/turf/olddist = get_dist(src, target)
					walk_to(src, target,1,4)
					if ((get_dist(src, target)) >= (olddist))
						frustration++
					else
						frustration = 0
			else
				back_to_idle()

		if(BOT_PREP_ARREST)		// preparing to arrest target

			// see if he got away. If he's no no longer adjacent or inside a closet or about to get up, we hunt again.
			if( !Adjacent(target) || !isturf(target.loc) ||  target.weakened < 2 )
				back_to_hunt()
				return

			if(iscarbon(target) && target.canBeHandcuffed())
				if(!arrest_type)
					if(!target.handcuffed)  //he's not cuffed? Try to cuff him!
						mode = BOT_ARREST
						playsound(loc, 'sound/weapons/cablecuff.ogg', 30, 1, -2)
						target.visible_message("[src] is trying to put zipties on [target]!",\
											"[src] is trying to put zipties on [target]!")

						spawn(60)
							if( !Adjacent(target) || !isturf(target.loc) ) //if he's in a closet or not adjacent, we cancel cuffing.
								return
							if(!target.handcuffed)
								target.handcuffed = new /obj/item/weapon/restraints/handcuffs/cable/zipties/used(target)
								target.update_inv_handcuffed(0)	//update the handcuffs overlay
								back_to_idle()
					else
						back_to_idle()
						return
			else
				back_to_idle()
				return

		if(BOT_ARREST)
			if (!target)
				anchored = 0
				mode = BOT_IDLE
				last_found = world.time
				frustration = 0
				return

			if(target.handcuffed) //no target or target cuffed? back to idle.
				back_to_idle()
				return

			if( !Adjacent(target) || !isturf(target.loc) || (target.loc != target_lastloc && target.weakened < 2) ) //if he's changed loc and about to get up or not adjacent or got into a closet, we prep arrest again.
				back_to_hunt()
				return
			else
				mode = BOT_PREP_ARREST
				anchored = 0

		if(BOT_START_PATROL)
			look_for_perp()
			start_patrol()

		if(BOT_PATROL)
			look_for_perp()
			bot_patrol()

	return

/obj/machinery/bot/ed209/proc/back_to_idle()
	anchored = 0
	mode = BOT_IDLE
	target = null
	last_found = world.time
	frustration = 0
	spawn(0)
		bot_process() //ensure bot quickly responds

/obj/machinery/bot/ed209/proc/back_to_hunt()
	anchored = 0
	frustration = 0
	mode = BOT_HUNT
	spawn(0)
		bot_process() //ensure bot quickly responds

// look for a criminal in view of the bot

/obj/machinery/bot/ed209/proc/look_for_perp()
	if(disabled)
		return
	anchored = 0
	threatlevel = 0
	for (var/mob/living/carbon/C in view(7,src)) //Let's find us a criminal
		if ((C.stat) || (C.handcuffed))
			continue

		if ((C.name == oldtarget_name) && (world.time < last_found + 100))
			continue

		threatlevel = C.assess_threat(src, lasercolor)

		if (!threatlevel)
			continue

		else if (threatlevel >= 4)
			target = C
			oldtarget_name = C.name
			speak("Level [threatlevel] infraction alert!")
			playsound(loc, pick('sound/voice/ed209_20sec.ogg', 'sound/voice/EDPlaceholder.ogg'), 50, 0)
			visible_message("[src] points at [C.name]!")
			mode = BOT_HUNT
			spawn(0)
				bot_process()	// ensure bot quickly responds to a perp
			break
		else
			continue

/obj/machinery/bot/ed209/proc/check_for_weapons(var/obj/item/slot_item)
	if(istype(slot_item, /obj/item/weapon/gun) || istype(slot_item, /obj/item/weapon/melee))
		if(!(slot_item.type in safe_weapons))
			return 1
	return 0

/* terrible
/obj/machinery/bot/ed209/Bumped(atom/movable/M as mob|obj)
	spawn(0)
		if (M)
			var/turf/T = get_turf(src)
			M:loc = T
*/

/obj/machinery/bot/ed209/explode()
	walk_to(src,0)
	visible_message("[src] blows apart!")
	var/turf/Tsec = get_turf(src)

	var/obj/item/weapon/ed209_assembly/Sa = new /obj/item/weapon/ed209_assembly(Tsec)
	Sa.build_step = 1
	Sa.overlays += image('icons/obj/aibots.dmi', "hs_hole")
	Sa.created_name = name
	new /obj/item/device/assembly/prox_sensor(Tsec)

	if(!lasercolor)
		var/obj/item/weapon/gun/energy/gun/advtaser/G = new /obj/item/weapon/gun/energy/gun/advtaser(Tsec)
		G.power_supply.charge = 0
		G.update_icon()
	else if(lasercolor == "b")
		var/obj/item/weapon/gun/energy/laser/bluetag/G = new /obj/item/weapon/gun/energy/laser/bluetag(Tsec)
		G.power_supply.charge = 0
		G.update_icon()
	else if(lasercolor == "r")
		var/obj/item/weapon/gun/energy/laser/redtag/G = new /obj/item/weapon/gun/energy/laser/redtag(Tsec)
		G.power_supply.charge = 0
		G.update_icon()

	if (prob(50))
		new /obj/item/robot_parts/l_leg(Tsec)
		if (prob(25))
			new /obj/item/robot_parts/r_leg(Tsec)
	if (prob(25))//50% chance for a helmet OR vest
		if (prob(50))
			new /obj/item/clothing/head/helmet(Tsec)
		else
			if(!lasercolor)
				new /obj/item/clothing/suit/armor/vest(Tsec)
			if(lasercolor == "b")
				new /obj/item/clothing/suit/bluetag(Tsec)
			if(lasercolor == "r")
				new /obj/item/clothing/suit/redtag(Tsec)

	var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
	s.set_up(3, 1, src)
	s.start()

	new /obj/effect/decal/cleanable/oil(loc)
	qdel(src)

/obj/machinery/bot/ed209/proc/set_weapon()  //used to update the projectile type and firing sound
	shoot_sound = 'sound/weapons/laser.ogg'
	if(emagged == 2)
		if(lasercolor)
			projectile = /obj/item/projectile/lasertag
		else
			projectile = /obj/item/projectile/beam
	else
		if(!lasercolor)
			shoot_sound = 'sound/weapons/Taser.ogg'
			projectile = /obj/item/projectile/energy/electrode
		else if(lasercolor == "b")
			projectile = /obj/item/projectile/lasertag/bluetag
		else if(lasercolor == "r")
			projectile = /obj/item/projectile/lasertag/redtag

/obj/machinery/bot/ed209/proc/shootAt(var/mob/target)
	if(lastfired && world.time - lastfired < shot_delay)
		return
	lastfired = world.time
	var/turf/T = loc
	var/atom/U = (istype(target, /atom/movable) ? target.loc : target)
	if ((!( U ) || !( T )))
		return
	while(!( istype(U, /turf) ))
		U = U.loc
	if (!( istype(T, /turf) ))
		return

	//if(lastfired && world.time - lastfired < 100)

	if(!projectile)
		return

	if (!( istype(U, /turf) ))
		return
	var/obj/item/projectile/A = new projectile (loc)
	playsound(loc, shoot_sound, 50, 1)
	A.current = U
	A.yo = U.y - T.y
	A.xo = U.x - T.x
	A.fire()

/obj/machinery/bot/ed209/attack_alien(var/mob/living/carbon/alien/user as mob)
	..()
	if (!isalien(target))
		target = user
		mode = BOT_HUNT

/obj/machinery/bot/ed209/emp_act(severity)

	if(severity==2 && prob(70))
		..(severity-1)
	else
		var/obj/effect/overlay/pulse2 = new/obj/effect/overlay ( loc )
		pulse2.icon = 'icons/effects/effects.dmi'
		pulse2.icon_state = "empdisable"
		pulse2.name = "emp sparks"
		pulse2.anchored = 1
		pulse2.dir = pick(cardinal)
		spawn(10)
			qdel(pulse2)
		var/list/mob/living/carbon/targets = new
		for (var/mob/living/carbon/C in view(12,src))
			if (C.stat==2)
				continue
			targets += C
		if(targets.len)
			if(prob(50))
				var/mob/toshoot = pick(targets)
				if (toshoot)
					targets-=toshoot
					if (prob(50) && emagged < 2)
						emagged = 2
						set_weapon()
						shootAt(toshoot)
						emagged = 0
						set_weapon()
					else
						shootAt(toshoot)
			else if(prob(50))
				if(targets.len)
					var/mob/toarrest = pick(targets)
					if (toarrest)
						target = toarrest
						mode = BOT_HUNT

/obj/item/weapon/ed209_assembly/attackby(obj/item/weapon/W as obj, mob/user as mob, params)
	..()

	if(istype(W, /obj/item/weapon/pen))
		var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN)
		if(!t)	return
		if(!in_range(src, usr) && loc != usr)	return
		created_name = t
		return

	switch(build_step)
		if(0,1)
			if(istype(W, /obj/item/robot_parts/l_leg) || istype(W, /obj/item/robot_parts/r_leg))
				user.drop_item()
				qdel(W)
				build_step++
				user << "You add the robot leg to [src]."
				name = "legs/frame assembly"
				if(build_step == 1)
					item_state = "ed209_leg"
					icon_state = "ed209_leg"
				else
					item_state = "ed209_legs"
					icon_state = "ed209_legs"

		if(2)
			if(istype(W, /obj/item/clothing/suit/redtag))
				lasercolor = "r"
			else if(istype(W, /obj/item/clothing/suit/bluetag))
				lasercolor = "b"
			if(lasercolor || istype(W, /obj/item/clothing/suit/armor/vest))
				user.drop_item()
				qdel(W)
				build_step++
				user << "You add the armor to [src]."
				name = "vest/legs/frame assembly"
				item_state = "[lasercolor]ed209_shell"
				icon_state = "[lasercolor]ed209_shell"

		if(3)
			if(istype(W, /obj/item/weapon/weldingtool))
				var/obj/item/weapon/weldingtool/WT = W
				if(WT.remove_fuel(0,user))
					build_step++
					name = "shielded frame assembly"
					user << "You welded the vest to [src]."
		if(4)
			switch(lasercolor)
				if("b")
					if(!istype(W, /obj/item/clothing/head/helmet/bluetaghelm))
						return

				if("r")
					if(!istype(W, /obj/item/clothing/head/helmet/redtaghelm))
						return

				if("")
					if(!istype(W, /obj/item/clothing/head/helmet))
						return

			user.drop_item()
			qdel(W)
			build_step++
			user << "You add the helmet to [src]."
			name = "covered and shielded frame assembly"
			item_state = "[lasercolor]ed209_hat"
			icon_state = "[lasercolor]ed209_hat"

		if(5)
			if(isprox(W))
				user.drop_item()
				qdel(W)
				build_step++
				user << "You add the prox sensor to [src]."
				name = "covered, shielded and sensored frame assembly"
				item_state = "[lasercolor]ed209_prox"
				icon_state = "[lasercolor]ed209_prox"

		if(6)
			if(istype(W, /obj/item/stack/cable_coil))
				var/obj/item/stack/cable_coil/coil = W
				if (coil.get_amount() < 1)
					user << "You need one length of cable to wire the ED-209."
					return
				user << "You start to wire [src]..."
				if (do_after(user, 40))
					if (coil.get_amount() >= 1 && build_step == 6)
						coil.use(1)
						build_step = 7
						user << "You wire the ED-209 assembly."
						name = "wired ED-209 assembly"

		if(7)
			switch(lasercolor)
				if("b")
					if(!istype(W, /obj/item/weapon/gun/energy/laser/bluetag))
						return
					name = "bluetag ED-209 assembly"
				if("r")
					if(!istype(W, /obj/item/weapon/gun/energy/laser/redtag))
						return
					name = "redtag ED-209 assembly"
				if("")
					if(!istype(W, /obj/item/weapon/gun/energy/gun/advtaser))
						return
					name = "taser ED-209 assembly"
				else
					return
			build_step++
			user << "You add [W] to [src]."
			item_state = "[lasercolor]ed209_taser"
			icon_state = "[lasercolor]ed209_taser"
			user.drop_item()
			qdel(W)

		if(8)
			if(istype(W, /obj/item/weapon/screwdriver))
				playsound(loc, 'sound/items/Screwdriver.ogg', 100, 1)
				var/turf/T = get_turf(user)
				user << "Now attaching the gun to the frame..."
				sleep(40)
				if(get_turf(user) == T)
					build_step++
					name = "armed [name]"
					user << "Taser gun attached."

		if(9)
			if(istype(W, /obj/item/weapon/stock_parts/cell))
				build_step++
				user << "You complete the ED-209."
				var/turf/T = get_turf(src)
				new /obj/machinery/bot/ed209(T,created_name,lasercolor)
				user.drop_item()
				qdel(W)
				user.unEquip(src, 1)
				qdel(src)

/obj/machinery/bot/ed209/bullet_act(var/obj/item/projectile/Proj)
	if(!disabled)
		var/lasertag_check = 0
		if((lasercolor == "b"))
			if(istype(Proj, /obj/item/projectile/lasertag/redtag))
				lasertag_check++
		else if((lasercolor == "r"))
			if(istype(Proj, /obj/item/projectile/lasertag/bluetag))
				lasertag_check++
		if(lasertag_check)
			icon_state = "[lasercolor]ed2090"
			disabled = 1
			target = null
			spawn(100)
				disabled = 0
				icon_state = "[lasercolor]ed2091"
			return 1
		else
			..(Proj)
	else
		..(Proj)

/obj/machinery/bot/ed209/bluetag/New()//If desired, you spawn red and bluetag bots easily
	new /obj/machinery/bot/ed209(get_turf(src),null,"b")
	qdel(src)

/obj/machinery/bot/ed209/redtag/New()
	new /obj/machinery/bot/ed209(get_turf(src),null,"r")
	qdel(src)


code:
//Floorbot assemblies
/obj/item/weapon/toolbox_tiles
	desc = "It's a toolbox with tiles sticking out the top"
	name = "tiles and toolbox"
	icon = 'icons/obj/aibots.dmi'
	icon_state = "toolbox_tiles"
	force = 3.0
	throwforce = 10.0
	throw_speed = 2
	throw_range = 5
	var/skin = null
	w_class = 3.0
	var/created_name = "Floorbot"

/obj/item/weapon/toolbox_tiles_sensor
	desc = "It's a toolbox with tiles sticking out the top and a sensor attached"
	name = "tiles, toolbox and sensor arrangement"
	icon = 'icons/obj/aibots.dmi'
	icon_state = "toolbox_tiles_sensor"
	force = 3.0
	throwforce = 10.0
	throw_speed = 2
	throw_range = 5
	var/skin = null
	w_class = 3.0
	var/created_name = "Floorbot"

//Floorbot
/obj/machinery/bot/floorbot
	name = "\improper Floorbot"
	desc = "A little floor repairing robot, he looks so excited!"
	icon = 'icons/obj/aibots.dmi'
	icon_state = "floorbot0"
	layer = 5.0
	density = 0
	anchored = 0
	health = 25
	maxhealth = 25
	//weight = 1.0E7

	var/skin = null //Set to "(r)ed", "(y)ellow" or "(s)yndicate" for the other toolkits.

	var/amount = 10
	var/replacetiles = 0
	var/eattiles = 0
	var/maketiles = 0
	var/fixfloors = 0
	var/autotile = 0
	var/nag_on_empty = 1
	var/nagged = 0 //Prevents the Floorbot nagging more than once per refill.
	var/max_targets = 50
	var/turf/target
	var/turf/oldtarget
	var/oldloc = null
	req_one_access = list(access_construction, access_robotics)
	var/targetdirection
	radio_frequency = ENG_FREQ //Engineering channel
	bot_type = FLOOR_BOT
	bot_filter = RADIO_FLOORBOT
	var/process_type //Determines what to do when process_scan() recieves a target. See process_scan() for details.
	#define HULL_BREACH		1
	#define BRIDGE_MODE		2
	#define FIX_TILE		3
	#define AUTO_TILE		4
	#define REPLACE_TILE	5
	#define TILE_EMAG		6

/obj/machinery/bot/floorbot/New()
	..()
	updateicon()
	var/datum/job/engineer/J = new/datum/job/engineer
	botcard.access = J.get_access()
	prev_access = botcard.access

	spawn(5)
		add_to_beacons(bot_filter)

/obj/machinery/bot/floorbot/turn_on()
	. = ..()
	updateicon()
	updateUsrDialog()

/obj/machinery/bot/floorbot/turn_off()
	..()
	updateicon()
	updateUsrDialog()

/obj/machinery/bot/floorbot/bot_reset()
	..()
	target = null
	oldtarget = null
	oldloc = null
	ignore_list = list()
	nagged = 0
	anchored = 0
	update_icon()

/obj/machinery/bot/floorbot/set_custom_texts()
	text_hack = "You corrupt [name]'s construction protocols."
	text_dehack = "You detect errors in [name] and reset his programming."
	text_dehack_fail = "[name] is not responding to reset commands!"

/obj/machinery/bot/floorbot/attack_hand(mob/user as mob)
	. = ..()
	if (.)
		return
	usr.set_machine(src)
	interact(user)

/obj/machinery/bot/floorbot/interact(mob/user as mob)
	var/dat
	dat += hack(user)
	dat += "<TT>Floor Repairer Controls v1.1</TT>

"
	dat += "Status: [on ? "On" : "Off"]
"
	dat += "Maintenance panel panel is [open ? "opened" : "closed"]
"
	dat += "Tiles left: [amount]
"
	dat += "Behvaiour controls are [locked ? "locked" : "unlocked"]
"
	if(!locked || issilicon(user))
		dat += "Add tiles to new hull plating: [autotile ? "Yes" : "No"]
"
		dat += "Replace floor tiles: [replacetiles ? "Yes" : "No"]
"
		dat += "Finds tiles: [eattiles ? "Yes" : "No"]
"
		dat += "Make pieces of metal into tiles when empty: [maketiles ? "Yes" : "No"]
"
		dat += "Transmit notice when empty: [nag_on_empty ? "Yes" : "No"]
"
		dat += "Repair damaged tiles and platings: [fixfloors ? "Yes" : "No"]
"
		dat += "Traction Magnets: [anchored ? "Engaged" : "Disengaged"]
"
		dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
"
		var/bmode
		if (targetdirection)
			bmode = dir2text(targetdirection)
		else
			bmode = "disabled"
		dat += "Bridge Mode : [bmode]
"

	var/datum/browser/popup = new(user, "autofloor", "Automatic Station Floor Repairer v1.1")
	popup.set_content(dat)
	popup.open()
	return

/obj/machinery/bot/floorbot/attackby(var/obj/item/W , mob/user as mob, params)
	if(istype(W, /obj/item/stack/tile/plasteel))
		var/obj/item/stack/tile/plasteel/T = W
		if(amount >= 50)
			return
		var/loaded = min(50-amount, T.amount)
		T.use(loaded)
		amount += loaded
		if (loaded > 0)
			user << "You load [loaded] tiles into the floorbot. He now contains [amount] tiles."
			nagged = 0
			updateicon()
		else
			user << "You need at least one floor tile to put into [src]"
	else if(istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda))
		if(allowed(user) && !open && !emagged)
			locked = !locked
			user << "You [locked ? "lock" : "unlock"] \the [src] behaviour controls."
		else
			if(emagged)
				user << "ERROR"
			if(open)
				user << "Please close the access panel before locking it."
			else
				user << "Access denied."
		updateUsrDialog()
	else
		..()

/obj/machinery/bot/floorbot/Emag(mob/user as mob)
	..()
	if(emagged == 2)
		if(user)
			user << "[src] buzzes and beeps."

/obj/machinery/bot/floorbot/Topic(href, href_list)
	..()
	switch(href_list["operation"])
		if("replace")
			replacetiles = !replacetiles
		if("tiles")
			eattiles = !eattiles
		if("make")
			maketiles = !maketiles
		if("fix")
			fixfloors = !fixfloors
		if("autotile")
			autotile = !autotile
		if("emptynag")
			nag_on_empty = !nag_on_empty
		if("anchor")
			anchored = !anchored

		if("bridgemode")
			var/setdir = input("Select construction direction:") as null|anything in list("north","east","south","west","disable")
			switch(setdir)
				if("north")
					targetdirection = 1
				if("south")
					targetdirection = 2
				if("east")
					targetdirection = 4
				if("west")
					targetdirection = 8
				if("disable")
					targetdirection = null
	updateUsrDialog()

/obj/machinery/bot/floorbot/bot_process()
	if (!..())
		return

	if(mode == BOT_REPAIRING)
		return

	if(amount <= 0 && !target) //Out of tiles! We must refill!
		if(eattiles) //Configured to find and consume floortiles!
			target = scan(/obj/item/stack/tile/plasteel, oldtarget)
			process_type = null

		if(!target && maketiles) //We did not manage to find any floor tiles! Scan for metal stacks and make our own!
			target = scan(/obj/item/stack/sheet/metal, oldtarget)
			process_type = null
			return
		else
			if(nag_on_empty) //Floorbot is empty and cannot acquire more tiles, nag the engineers for more!
				nag()

	if(prob(5))
		visible_message("[src] makes an excited booping beeping sound!")

	//Normal scanning procedure. We have tiles loaded, are not emagged.
	if(!target && emagged < 2 && amount > 0)
		if(targetdirection != null) //The bot is in bridge mode.
			//Try to find a space tile immediately in our selected direction.
			var/turf/T = get_step(src, targetdirection)
			if(istype(T, /turf/space))
				target = T

			else //Find a space tile farther way!
				target = scan(/turf/space, oldtarget)
			process_type = BRIDGE_MODE

		if(!target)
			process_type = HULL_BREACH //Ensures the floorbot does not try to "fix" space areas or shuttle docking zones.
			target = scan(/turf/space, oldtarget)

		if(!target && replacetiles) //Finds a floor without a tile and gives it one.
			process_type = REPLACE_TILE //The target must be the floor and not a tile. The floor must not already have a floortile.
			target = scan(/turf/simulated/floor, oldtarget)

		if(!target && fixfloors) //Repairs damaged floors and tiles.
			process_type = FIX_TILE
			target = scan(/turf/simulated/floor, oldtarget)

	if(!target && emagged == 2) //We are emagged! Time to rip up the floors!
		process_type = TILE_EMAG
		target = scan(/turf/simulated/floor, oldtarget)

	if(!target)

		if(auto_patrol)
			if(mode == BOT_IDLE || mode == BOT_START_PATROL)
				start_patrol()

			if(mode == BOT_PATROL)
				bot_patrol()

	if(!target)
		if(loc != oldloc)
			oldtarget = null
		return

	if(target)
		if(path.len == 0)
			if(!istype(target, /turf/))
				var/turf/TL = get_turf(target)
				path = get_path_to(loc, TL, src, /turf/proc/Distance, 0, 30, id=botcard)
			else
				path = get_path_to(loc, target, src, /turf/proc/Distance, 0, 30, id=botcard)

			if(!bot_move(target))
				add_to_ignore(target)
				oldtarget = target
				target = null
				mode = BOT_IDLE
				return
		else if( !bot_move(target) )
			oldtarget = target
			target = null
			mode = BOT_IDLE
			return
		if(!target)
			return
		if(loc == target || loc == target.loc)
			if(istype(target, /obj/item/stack/tile/plasteel))
				eattile(target)
			else if(istype(target, /obj/item/stack/sheet/metal))
				maketile(target)
			else if(istype(target, /turf/) && emagged < 2)
				repair(target)
			else if(emagged == 2 && istype(target,/turf/simulated/floor))
				var/turf/simulated/floor/F = target
				anchored = 1
				mode = BOT_REPAIRING
				if(prob(90))
					F.break_tile_to_plating()
				else
					F.ReplaceWithLattice()
				visible_message("[src] makes an excited booping sound.")
				spawn(50)
					amount ++
					anchored = 0
					mode = BOT_IDLE
					target = null
			path = list()
			return

	oldloc = loc

/obj/machinery/bot/floorbot/proc/nag() //Annoy everyone on the channel to refill us!
	if(!nagged)
		speak("Requesting refill at [get_area(src)]!", radio_frequency)
		nagged = 1

/obj/machinery/bot/floorbot/proc/is_hull_breach(var/turf/t) //Ignore space tiles not considered part of a structure, also ignores shuttle docking areas.
	var/area/t_area = get_area(t)
	if (t_area && (t_area.name == "Space" || findtext(t_area.name, "huttle")))
		return 0
	else
		return 1

//Floorbots, having several functions, need sort out special conditions here.
obj/machinery/bot/floorbot/process_scan(var/scan_target)
	var/result
	var/turf/simulated/floor/F
	switch(process_type)
		if(HULL_BREACH) //The most common job, patching breaches in the station's hull.
			if(is_hull_breach(scan_target)) //Ensure that the targeted space turf is actually part of the station, and not random space.
				result = scan_target
				anchored = 1 //Prevent the floorbot being blown off-course while trying to reach a hull breach.
		if(BRIDGE_MODE) //Only space turfs in our chosen direction are considered.
			if(get_dir(src, scan_target) == targetdirection)
				result = scan_target
				anchored = 1
		if(REPLACE_TILE)
			F = scan_target
			if(istype(F, /turf/simulated/floor/plating)) //The floor must not already have a tile.
				result = F
		if(FIX_TILE)	//Selects only damaged floors.
			F = scan_target
			if(istype(F) && (F.broken || F.burnt))
				result = F
		if(TILE_EMAG) //Emag mode! Rip up the floor and cause breaches to space!
			F = scan_target
			if(!istype(F, /turf/simulated/floor/plating))
				result = F
		else //If no special processing is needed, simply return the result.
			result = scan_target
	return result

/obj/machinery/bot/floorbot/proc/repair(var/turf/target_turf)

	if(istype(target_turf, /turf/space/))
		 //Must be a hull breach or in bridge mode to continue.
		if(!is_hull_breach(target_turf) && !targetdirection)
			target = null
			return
	else if(!istype(target_turf, /turf/simulated/floor))
		return
	if(amount <= 0)
		mode = BOT_IDLE
		target = null
		return
	anchored = 1
	icon_state = "[skin]floorbot-c"
	if(istype(target_turf, /turf/space/)) //If we are fixing an area not part of pure space, it is
		visible_message("[targetdirection ? "[src] begins installing a bridge plating." : "[src] begins to repair the hole."] ")
		mode = BOT_REPAIRING
		spawn(50)
			if(!istype(target_turf))
				return
			if(mode == BOT_REPAIRING)
				if(autotile) //Build the floor and include a tile.
					target_turf.ChangeTurf(/turf/simulated/floor/plasteel)
				else //Build a hull plating without a floor tile.
					target_turf.ChangeTurf(/turf/simulated/floor/plating)
				mode = BOT_IDLE
				amount -= 1
				updateicon()
				anchored = 0
				target = null
	else
		var/turf/simulated/floor/F = target_turf
		mode = BOT_REPAIRING
		visible_message("[src] begins repairing the floor.")
		spawn(50)
			if(!istype(F))
				return
			if(mode == BOT_REPAIRING)
				F.broken = 0
				F.burnt = 0
				F.ChangeTurf(/turf/simulated/floor/plasteel)
				mode = BOT_IDLE
				amount -= 1
				updateicon()
				anchored = 0
				target = null

/obj/machinery/bot/floorbot/proc/eattile(var/obj/item/stack/tile/plasteel/T)
	if(!istype(T, /obj/item/stack/tile/plasteel))
		return
	visible_message(" [src] begins to collect tiles.")
	mode = BOT_REPAIRING
	spawn(20)
		if(isnull(T))
			target = null
			mode = BOT_IDLE
			return
		if(amount + T.amount > 50)
			var/i = 50 - amount
			amount += i
			T.amount -= i
		else
			amount += T.amount
			qdel(T)
		updateicon()
		target = null
		mode = BOT_IDLE

/obj/machinery/bot/floorbot/proc/maketile(var/obj/item/stack/sheet/metal/M)
	if(!istype(M, /obj/item/stack/sheet/metal))
		return
	visible_message(" [src] begins to create tiles.")
	mode = BOT_REPAIRING
	spawn(20)
		if(isnull(M))
			target = null
			mode = BOT_IDLE
			return
		var/obj/item/stack/tile/plasteel/T = new /obj/item/stack/tile/plasteel
		T.amount = 4
		T.loc = M.loc
		if(M.amount > 1)
			M.amount--
		else
			qdel(M)
		target = null
		mode = BOT_IDLE

/obj/machinery/bot/floorbot/proc/updateicon()
	if(amount > 0)
		icon_state = "[skin]floorbot[on]"
	else
		icon_state = "[skin]floorbot[on]e"

/obj/machinery/bot/floorbot/explode()
	on = 0
	visible_message("[src] blows apart!")
	var/turf/Tsec = get_turf(src)

	var/obj/item/weapon/storage/toolbox/mechanical/N = new /obj/item/weapon/storage/toolbox/mechanical(Tsec)
	N.contents = list()

	new /obj/item/device/assembly/prox_sensor(Tsec)

	if (prob(50))
		new /obj/item/robot_parts/l_arm(Tsec)

	while (amount)//Dumps the tiles into the appropriate sized stacks
		if(amount >= 16)
			var/obj/item/stack/tile/plasteel/T = new (Tsec)
			T.amount = 16
			amount -= 16
		else
			var/obj/item/stack/tile/plasteel/T = new (Tsec)
			T.amount = amount
			amount = 0

	var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
	s.set_up(3, 1, src)
	s.start()
	qdel(src)
	return

/obj/item/weapon/storage/toolbox/attackby(var/obj/item/stack/tile/plasteel/T, mob/user as mob, params)
	if(!istype(T, /obj/item/stack/tile/plasteel))
		..()
		return
	if(contents.len >= 1)
		user << "They won't fit in, as there is already stuff inside."
		return
	if(T.use(10))
		if(user.s_active)
			user.s_active.close(user)
		var/obj/item/weapon/toolbox_tiles/B = new /obj/item/weapon/toolbox_tiles

		if(istype(src,/obj/item/weapon/storage/toolbox/emergency))
			B.skin = "r"
		else if(istype(src,/obj/item/weapon/storage/toolbox/mechanical))
			B.skin = null
		else if(istype(src,/obj/item/weapon/storage/toolbox/electrical))
			B.skin = "y"
		else if(istype(src,/obj/item/weapon/storage/toolbox/syndicate))
			B.skin = "s"
		else
			B.skin = "r"
		if(B.skin)
			B.icon_state = "[B.skin]toolbox_tiles"

		user.put_in_hands(B)
		user << "You add the tiles into the empty toolbox. They protrude from the top."
		user.unEquip(src, 1)
		qdel(src)
	else
		user << "You need 10 floor tiles to start building a floorbot."
		return

/obj/item/weapon/toolbox_tiles/attackby(var/obj/item/W, mob/user as mob, params)
	..()
	if(isprox(W))
		qdel(W)
		var/obj/item/weapon/toolbox_tiles_sensor/B = new /obj/item/weapon/toolbox_tiles_sensor()
		B.created_name = created_name

		B.skin = skin
		if(skin)
			B.icon_state = "[B.skin]toolbox_tiles_sensor"

		user.put_in_hands(B)
		user << "You add the sensor to the toolbox and tiles!"
		user.unEquip(src, 1)
		qdel(src)

	else if (istype(W, /obj/item/weapon/pen))
		var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN)
		if (!t)
			return
		if (!in_range(src, usr) && loc != usr)
			return

		created_name = t

/obj/item/weapon/toolbox_tiles_sensor/attackby(var/obj/item/W, mob/user as mob, params)
	..()
	if(istype(W, /obj/item/robot_parts/l_arm) || istype(W, /obj/item/robot_parts/r_arm))
		qdel(W)
		var/turf/T = get_turf(user.loc)
		var/obj/machinery/bot/floorbot/A = new /obj/machinery/bot/floorbot(T)

		A.skin = skin
		A.icon_state = "[A.skin]floorbot0"

		A.name = created_name
		user << "You add the robot arm to the odd looking toolbox assembly! Boop beep!"
		user.unEquip(src, 1)
		qdel(src)
	else if (istype(W, /obj/item/weapon/pen))
		var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN)
		if (!t)
			return
		if (!in_range(src, usr) && loc != usr)
			return

		created_name = t


code:
//MEDBOT
//MEDBOT PATHFINDING
//MEDBOT ASSEMBLY

/obj/machinery/bot/medbot
	name = "\improper Medibot"
	desc = "A little medical robot. He looks somewhat underwhelmed."
	icon = 'icons/obj/aibots.dmi'
	icon_state = "medibot0"
	layer = 5.0
	density = 0
	anchored = 0
	health = 20
	maxhealth = 20
	req_one_access =list(access_medical, access_robotics)
	var/stunned = 0 //It can be stunned by tasers. Delicate circuits.
//	var/emagged = 0
	var/list/botcard_access = list(access_medical)
	var/obj/item/weapon/reagent_containers/glass/reagent_glass = null //Can be set to draw from this for reagents.
	var/skin = null //Set to "tox", "ointment" or "o2" for the other two firstaid kits.
	var/mob/living/carbon/patient = null
	var/mob/living/carbon/oldpatient = null
	var/oldloc = null
	var/last_found = 0
	var/last_newpatient_speak = 0 //Don't spam the "HEY I'M COMING" messages
	var/injection_amount = 15 //How much reagent do we inject at a time?
	var/heal_threshold = 10 //Start healing when they have this much damage in a category
	var/use_beaker = 0 //Use reagents in beaker instead of default treatment agents.
	var/declare_crit = 1 //If active, the bot will transmit a critical patient alert to MedHUD users.
	var/declare_cooldown = 0 //Prevents spam of critical patient alerts.
	var/stationary_mode = 0 //If enabled, the Medibot will not move automatically.
	radio_frequency = MED_FREQ //Medical frequency
	//Setting which reagents to use to treat what by default. By id.
	var/treatment_brute = "bicaridine"
	var/treatment_oxy = "dexalin"
	var/treatment_fire = "kelotane"
	var/treatment_tox = "anti_toxin"
	var/treatment_virus = "spaceacillin"
	var/treat_virus = 1 //If on, the bot will attempt to treat viral infections, curing them if possible.
	var/shut_up = 0 //self explanatory :)
	bot_type = MED_BOT
	bot_filter = RADIO_MEDBOT

/obj/machinery/bot/medbot/mysterious
	name = "\improper Mysterious Medibot"
	desc = "International Medibot of mystery."
	skin = "bezerk"
	treatment_oxy = "tricordrazine"
	treatment_brute = "tricordrazine"
	treatment_fire = "tricordrazine"
	treatment_tox = "tricordrazine"

/obj/item/weapon/firstaid_arm_assembly
	name = "incomplete medibot assembly."
	desc = "A first aid kit with a robot arm permanently grafted to it."
	icon = 'icons/obj/aibots.dmi'
	icon_state = "firstaid_arm"
	var/build_step = 0
	var/created_name = "Medibot" //To preserve the name if it's a unique medbot I guess
	var/skin = null //Same as medbot, set to tox or ointment for the respective kits.
	w_class = 3.0

	/obj/item/weapon/firstaid_arm_assembly/New()
		..()
		spawn(5)
			if(skin)
				overlays += image('icons/obj/aibots.dmi', "kit_skin_[skin]")

/obj/machinery/bot/medbot/proc/updateicon()
	if(!on)
		icon_state = "medibot0"
		return
	if(mode == BOT_HEALING)
		icon_state = "medibots[stationary_mode]"
		return
	else if(stationary_mode) //Bot has yellow light to indicate stationary mode.
		icon_state = "medibot2"
	else
		icon_state = "medibot1"

/obj/machinery/bot/medbot/New()
	..()
	updateicon()

	spawn(4)
		if(skin)
			overlays += image('icons/obj/aibots.dmi', "medskin_[skin]")

		if(isnull(botcard_access) || (botcard_access.len < 1))
			var/datum/job/doctor/J = new/datum/job/doctor
			botcard.access = J.get_access()
		else
			botcard.access = botcard_access
		prev_access = botcard.access
		add_to_beacons(bot_filter)

/obj/machinery/bot/medbot/turn_on()
	. = ..()
	updateicon()
	updateUsrDialog()

/obj/machinery/bot/medbot/turn_off()
	..()
	updateicon()
	updateUsrDialog()

/obj/machinery/bot/medbot/bot_reset()
	..()
	patient = null
	oldpatient = null
	oldloc = null
	last_found = world.time
	declare_cooldown = 0

/obj/machinery/bot/medbot/set_custom_texts()

	text_hack = "You corrupt [name]'s reagent processor circuits."
	text_dehack = "You reset [name]'s reagent processor circuits."
	text_dehack_fail = "[name] seems damaged and does not respond to reprogramming!"

/obj/machinery/bot/medbot/attack_paw(mob/user as mob)
	return attack_hand(user)

/obj/machinery/bot/medbot/attack_hand(mob/user as mob)
	. = ..()
	if (.)
		return
	var/dat
	dat += hack(user)
	dat += "<TT>Medical Unit Controls v1.1</TT>

"
	dat += "Status: [on ? "On" : "Off"]
"
	dat += "Maintenance panel panel is [open ? "opened" : "closed"]
"
	dat += "Beaker: "
	if (reagent_glass)
		dat += "Loaded \[[reagent_glass.reagents.total_volume]/[reagent_glass.reagents.maximum_volume]\]"
	else
		dat += "None Loaded"
	dat += "
Behaviour controls are [locked ? "locked" : "unlocked"]
"
	if(!locked || issilicon(user))
		dat += "<TT>Healing Threshold: "
		dat += "-- "
		dat += "- "
		dat += "[heal_threshold] "
		dat += "+ "
		dat += "++"
		dat += "</TT>
"

		dat += "<TT>Injection Level: "
		dat += "- "
		dat += "[injection_amount] "
		dat += "+ "
		dat += "</TT>
"

		dat += "Reagent Source: "
		dat += "[use_beaker ? "Loaded Beaker (When available)" : "Internal Synthesizer"]
"

		dat += "Treat Viral Infections: [treat_virus ? "Yes" : "No"]
"
		dat += "The speaker switch is [shut_up ? "off" : "on"]. Toggle
"
		dat += "Critical Patient Alerts: [declare_crit ? "Yes" : "No"]
"
		dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
"
		dat += "Stationary Mode: [stationary_mode ? "Yes" : "No"]
"

	var/datum/browser/popup = new(user, "automed", "Automatic Medical Unit v1.1")
	popup.set_content(dat)
	popup.open()
	return

/obj/machinery/bot/medbot/Topic(href, href_list)
	..()

	if(href_list["adj_threshold"])
		var/adjust_num = text2num(href_list["adj_threshold"])
		heal_threshold += adjust_num
		if(heal_threshold < 5)
			heal_threshold = 5
		if(heal_threshold > 75)
			heal_threshold = 75

	else if(href_list["adj_inject"])
		var/adjust_num = text2num(href_list["adj_inject"])
		injection_amount += adjust_num
		if(injection_amount < 5)
			injection_amount = 5
		if(injection_amount > 15)
			injection_amount = 15

	else if(href_list["use_beaker"])
		use_beaker = !use_beaker

	else if (href_list["eject"] && (!isnull(reagent_glass)))
		reagent_glass.loc = get_turf(src)
		reagent_glass = null

	else if (href_list["togglevoice"])
		shut_up = !shut_up

	else if (href_list["critalerts"])
		declare_crit = !declare_crit

	else if (href_list["stationary"])
		stationary_mode = !stationary_mode
		path = list()
		updateicon()

	else if (href_list["virus"])
		treat_virus = !treat_virus

	updateUsrDialog()
	return

/obj/machinery/bot/medbot/attackby(obj/item/weapon/W as obj, mob/user as mob, params)
	if (istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda))
		if (allowed(user) && !open && !emagged)
			locked = !locked
			user << "Controls are now [locked ? "locked." : "unlocked."]"
			updateUsrDialog()
		else
			if(emagged)
				user << "ERROR"
			if(open)
				user << "Please close the access panel before locking it."
			else
				user << "Access denied."

	else if (istype(W, /obj/item/weapon/reagent_containers/glass))
		if(locked)
			user << "You cannot insert a beaker because the panel is locked."
			return
		if(!isnull(reagent_glass))
			user << "There is already a beaker loaded."
			return

		user.drop_item()
		W.loc = src
		reagent_glass = W
		user << "You insert [W]."
		updateUsrDialog()
		return

	else
		var/current_health = health
		..()
		if (health < current_health) //if medbot took some damage
			step_to(src, (get_step_away(src,user)))

/obj/machinery/bot/medbot/Emag(mob/user as mob)
	..()
	if(emagged == 2)
		declare_crit = 0
		if(user)
			user << "You short out [src]'s reagent synthesis circuits."
		spawn(0)
			audible_message("[src] buzzes oddly!")
		flick("medibot_spark", src)
		if(user)
			oldpatient = user

/obj/machinery/bot/medbot/process_scan(var/mob/living/carbon/human/H)
	if (H.stat == 2)
		return

	if ((H == oldpatient) && (world.time < last_found + 100))
		return

	if(assess_patient(H))
		last_found = world.time
		if((last_newpatient_speak + 300) < world.time) //Don't spam these messages!
			var/message = pick("Hey, [H.name]! Hold on, I'm coming.","Wait [H.name]! I want to help!","[H.name], you appear to be injured!")
			speak(message)
			last_newpatient_speak = world.time
		return H
	else
		return

/obj/machinery/bot/medbot/bot_process()
	if (!..())
		return

	if(mode == BOT_HEALING)
		return

	if(stunned)
		icon_state = "medibota"
		stunned--

		oldpatient = patient
		patient = null
		mode = BOT_IDLE

		if(stunned <= 0)
			updateicon()
			stunned = 0
		return

	if(frustration > 8)
		oldpatient = patient
		patient = null
		mode = BOT_IDLE
		last_found = world.time
		path = list()

	if(!patient)
		if(!shut_up && prob(1))
			var/message = pick("Radar, put a mask on!","There's always a catch, and it's the best there is.","I knew it, I should've been a plastic surgeon.","What kind of medbay is this? Everyone's dropping like dead flies.","Delicious!")
			speak(message)
		var/scan_range = (stationary_mode ? 1 : DEFAULT_SCAN_RANGE) //If in stationary mode, scan range is limited to adjacent patients.
		patient = scan(/mob/living/carbon/human, oldpatient, scan_range)
		oldpatient = patient

	if(patient && (get_dist(src,patient) <= 1)) //Patient is next to us, begin treatment!
		if(mode != BOT_HEALING)
			mode = BOT_HEALING
			updateicon()
			frustration = 0
			medicate_patient(patient)
		return

	//Patient has moved away from us!
	else if(patient && path.len && (get_dist(patient,path[path.len]) > 2))
		path = list()
		mode = BOT_IDLE
		last_found = world.time

	else if(stationary_mode && patient) //Since we cannot move in this mode, ignore the patient and wait for another.
		patient = null
		mode = BOT_IDLE
		last_found = world.time
		return

	if(patient && path.len == 0 && (get_dist(src,patient) > 1))
		spawn(0)
			path = get_path_to(loc, get_turf(patient), src, /turf/proc/Distance_cardinal, 0, 30,id=botcard)

	if(path.len > 0 && patient)
		if(!bot_move(patient))
			oldpatient = patient
			patient = null
			mode = BOT_IDLE
			last_found = world.time
		return

	if(path.len > 8 && patient)
		frustration++

	if(auto_patrol && !stationary_mode && !patient)
		if(mode == BOT_IDLE || mode == BOT_START_PATROL)
			start_patrol()

		if(mode == BOT_PATROL)
			bot_patrol()

	return

/obj/machinery/bot/medbot/proc/assess_patient(mob/living/carbon/C as mob)
	//Time to see if they need medical help!
	if(C.stat == 2)
		return 0 //welp too late for them!

	if(C.suiciding)
		return 0 //Kevorkian school of robotic medical assistants.

	if(emagged == 2) //Everyone needs our medicine. (Our medicine is toxins)
		return 1

	if(declare_crit && C.health <= 0) //Critical condition! Call for help!
		declare(C)

	//If they're injured, we're using a beaker, and don't have one of our WONDERCHEMS.
	if((reagent_glass) && (use_beaker) && ((C.getBruteLoss() >= heal_threshold) || (C.getToxLoss() >= heal_threshold) || (C.getToxLoss() >= heal_threshold) || (C.getOxyLoss() >= (heal_threshold + 15))))
		for(var/datum/reagent/R in reagent_glass.reagents.reagent_list)
			if(!C.reagents.has_reagent(R.id))
				return 1

	//They're injured enough for it!
	if((C.getBruteLoss() >= heal_threshold) && (!C.reagents.has_reagent(treatment_brute)))
		return 1 //If they're already medicated don't bother!

	if((C.getOxyLoss() >= (15 + heal_threshold)) && (!C.reagents.has_reagent(treatment_oxy)))
		return 1

	if((C.getFireLoss() >= heal_threshold) && (!C.reagents.has_reagent(treatment_fire)))
		return 1

	if((C.getToxLoss() >= heal_threshold) && (!C.reagents.has_reagent(treatment_tox)))
		return 1

	if(treat_virus)
		for(var/datum/disease/D in C.viruses)
			//the medibot can't detect viruses that are undetectable to Health Analyzers or Pandemic machines.
			if(D.visibility_flags & HIDDEN_SCANNER || D.visibility_flags & HIDDEN_PANDEMIC)
				return 0
			if(D.severity == NONTHREAT) // medibot doesn't try to heal truly harmless viruses
				return 0
			if((D.stage > 1) || (D.spread_flags & AIRBORNE)) // medibot can't detect a virus in its initial stage unless it spreads airborne.

				if (!C.reagents.has_reagent(treatment_virus))
					return 1 //STOP DISEASE FOREVER

	return 0

/obj/machinery/bot/medbot/proc/medicate_patient(mob/living/carbon/C as mob)
	if(!on)
		return

	if(!istype(C))
		oldpatient = patient
		patient = null
		mode = BOT_IDLE
		last_found = world.time
		return

	if(C.stat == 2)
		var/death_message = pick("No! NO!","Live, damnit! LIVE!","I...I've never lost a patient before. Not today, I mean.")
		speak(death_message)
		oldpatient = patient
		patient = null
		mode = BOT_IDLE
		last_found = world.time
		return

	var/reagent_id = null

	if(emagged == 2) //Emagged! Time to poison everybody.
		reagent_id = "toxin"

	else
		if(treat_virus)
			var/virus = 0
			for(var/datum/disease/D in C.viruses)
				//detectable virus
				if((!(D.visibility_flags & HIDDEN_SCANNER)) || (!(D.visibility_flags & HIDDEN_PANDEMIC)))
					if(D.severity != NONTHREAT)      //virus is harmful
						if((D.stage > 1) || (D.spread_flags & AIRBORNE))
							virus = 1

			if (!reagent_id && (virus))
				if(!C.reagents.has_reagent(treatment_virus))
					reagent_id = treatment_virus

		if (!reagent_id && (C.getBruteLoss() >= heal_threshold))
			if(!C.reagents.has_reagent(treatment_brute))
				reagent_id = treatment_brute

		if (!reagent_id && (C.getOxyLoss() >= (15 + heal_threshold)))
			if(!C.reagents.has_reagent(treatment_oxy))
				reagent_id = treatment_oxy

		if (!reagent_id && (C.getFireLoss() >= heal_threshold))
			if(!C.reagents.has_reagent(treatment_fire))
				reagent_id = treatment_fire

		if (!reagent_id && (C.getToxLoss() >= heal_threshold))
			if(!C.reagents.has_reagent(treatment_tox))
				reagent_id = treatment_tox

		//If the patient is injured but doesn't have our special reagent in them then we should give it to them first
		if(reagent_id && use_beaker && reagent_glass && reagent_glass.reagents.total_volume)
			for(var/datum/reagent/R in reagent_glass.reagents.reagent_list)
				if(!C.reagents.has_reagent(R.id))
					reagent_id = "internal_beaker"
					break

	if(!reagent_id) //If they don't need any of that they're probably cured!
		oldpatient = patient
		patient = null
		mode = BOT_IDLE
		last_found = world.time
		var/message = pick("All patched up!","An apple a day keeps me away.","Feel better soon!")
		speak(message)
		updateicon()
		return
	else
		C.visible_message("[src] is trying to inject [patient]!", \
			"[src] is trying to inject [patient]!")

		spawn(30)
			if ((get_dist(src, patient) <= 1) && (on))
				if(reagent_id == "internal_beaker")
					if(use_beaker && reagent_glass && reagent_glass.reagents.total_volume)
						reagent_glass.reagents.trans_to(patient,injection_amount) //Inject from beaker instead.
						reagent_glass.reagents.reaction(patient, INGEST)
				else
					patient.reagents.add_reagent(reagent_id,injection_amount)
				C.visible_message("[src] injects [patient] with the syringe!", \
					"[src] injects [patient] with the syringe!")
				patient = null

			mode = BOT_IDLE
			updateicon()
			return

	reagent_id = null
	return

/obj/machinery/bot/medbot/bullet_act(var/obj/item/projectile/Proj)
	if(Proj.flag == "taser")
		stunned = min(stunned+10,20)
	..()

/obj/machinery/bot/medbot/explode()
	on = 0
	visible_message("[src] blows apart!")
	var/turf/Tsec = get_turf(src)

	new /obj/item/weapon/storage/firstaid(Tsec)

	new /obj/item/device/assembly/prox_sensor(Tsec)

	new /obj/item/device/healthanalyzer(Tsec)

	if(reagent_glass)
		reagent_glass.loc = Tsec
		reagent_glass = null

	if (prob(50))
		new /obj/item/robot_parts/l_arm(Tsec)

	var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
	s.set_up(3, 1, src)
	s.start()
	qdel(src)
	return

/obj/machinery/bot/medbot/proc/declare(var/crit_patient)
	if(declare_cooldown)
		return
	var/area/location = get_area(src)
	speak("Medical emergency! [crit_patient ? "[crit_patient]" : "A patient"] is in critical condition at [location]!",radio_frequency)
	declare_cooldown = 1
	spawn(200) //Twenty seconds
		declare_cooldown = 0

/*
 *	Medbot Assembly -- Can be made out of all three medkits.
 */

/obj/item/weapon/storage/firstaid/attackby(var/obj/item/robot_parts/S, mob/user as mob, params)

	if ((!istype(S, /obj/item/robot_parts/l_arm)) && (!istype(S, /obj/item/robot_parts/r_arm)))
		..()
		return

	//Making a medibot!
	if(contents.len >= 1)
		user << "You need to empty [src] out first."
		return

	var/obj/item/weapon/firstaid_arm_assembly/A = new /obj/item/weapon/firstaid_arm_assembly
	if(istype(src,/obj/item/weapon/storage/firstaid/fire))
		A.skin = "ointment"
	else if(istype(src,/obj/item/weapon/storage/firstaid/toxin))
		A.skin = "tox"
	else if(istype(src,/obj/item/weapon/storage/firstaid/o2))
		A.skin = "o2"
	else if(istype(src,/obj/item/weapon/storage/firstaid/brute))
		A.skin = "brute"

	qdel(S)
	user.put_in_hands(A)
	user << "You add the robot arm to the first aid kit."
	user.unEquip(src, 1)
	qdel(src)

/obj/item/weapon/firstaid_arm_assembly/attackby(obj/item/weapon/W as obj, mob/user as mob, params)
	..()
	if(istype(W, /obj/item/weapon/pen))
		var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN)
		if (!t)
			return
		if (!in_range(src, usr) && loc != usr)
			return
		created_name = t
	else
		switch(build_step)
			if(0)
				if(istype(W, /obj/item/device/healthanalyzer))
					user.drop_item()
					qdel(W)
					build_step++
					user << "You add the health sensor to [src]."
					name = "First aid/robot arm/health analyzer assembly"
					overlays += image('icons/obj/aibots.dmi', "na_scanner")

			if(1)
				if(isprox(W))
					user.drop_item()
					qdel(W)
					build_step++
					user << "You complete the Medibot! Beep boop."
					var/turf/T = get_turf(src)
					var/obj/machinery/bot/medbot/S = new /obj/machinery/bot/medbot(T)
					S.skin = skin
					S.name = created_name
					user.unEquip(src, 1)
					qdel(src)