code:
//The advanced pea-green monochrome lcd of tomorrow.

GLOBAL_LIST_EMPTY(PDAs)

#define PDA_SCANNER_NONE 0
#define PDA_SCANNER_MEDICAL 1
#define PDA_SCANNER_FORENSICS 2 //unused
#define PDA_SCANNER_REAGENT 3
#define PDA_SCANNER_HALOGEN 4
#define PDA_SCANNER_GAS 5
#define PDA_SPAM_DELAY     2 MINUTES

/obj/item/pda
	name = "\improper standard PDA"
	desc = "A portable microcomputer by Thinktronic Systems, LTD. Functionality determined by a preprogrammed ROM cartridge."
	icon = 'icons/obj/pda.dmi'
	icon_state = "pda"
	inhand_icon_state = "electronic"
	worn_icon_state = "pda"
	lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
	righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
	greyscale_config = /datum/greyscale_config/pda
	greyscale_colors = "#999875#a92323"
	item_flags = NOBLUDGEON
	w_class = WEIGHT_CLASS_TINY
	slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT
	actions_types = list(/datum/action/item_action/toggle_light)
	armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 100, ACID = 100)
	resistance_flags = FIRE_PROOF | ACID_PROOF
	light_system = MOVABLE_LIGHT_DIRECTIONAL
	light_range = 2.3
	light_power = 0.6
	light_color = "#FFCC66"
	light_on = FALSE
	custom_materials = list(/datum/material/iron=300, /datum/material/glass=100, /datum/material/plastic=100)

	//Main variables
	var/owner = null // String name of owner
	var/default_cartridge = 0 // Access level defined by cartridge
	var/obj/item/cartridge/cartridge = null //current cartridge
	var/mode = 0 //Controls what menu the PDA will display. 0 is hub; the rest are either built in or based on cartridge.
	var/icon_alert = "pda-r" //Icon to be overlayed for message alerts. Taken from the pda icon file.
	var/font_index = 0 //This int tells DM which font is currently selected and lets DM know when the last font has been selected so that it can cycle back to the first font when "toggle font" is pressed again.
	var/font_mode = "font-family:monospace;" //The currently selected font.
	var/background_color = "#808000" //The currently selected background color.

	#define FONT_MONO "font-family:monospace;"
	#define FONT_SHARE "font-family:\"Share Tech Mono\", monospace;letter-spacing:0px;"
	#define FONT_ORBITRON "font-family:\"Orbitron\", monospace;letter-spacing:0px; font-size:15px"
	#define FONT_VT "font-family:\"VT323\", monospace;letter-spacing:1px;"
	#define MODE_MONO 0
	#define MODE_SHARE 1
	#define MODE_ORBITRON 2
	#define MODE_VT 3

	//Secondary variables
	var/scanmode = PDA_SCANNER_NONE
	var/silent = FALSE //To beep or not to beep, that is the question
	var/toff = FALSE //If TRUE, messenger disabled
	var/tnote = null //Current Texts
	var/last_text //No text spamming
	var/last_everyone //No text for everyone spamming
	var/last_noise //Also no honk spamming that's bad too
	var/ttone = "beep" //The ringtone!
	var/honkamt = 0 //How many honks left when infected with honk.exe
	var/mimeamt = 0 //How many silence left when infected with mime.exe
	var/note = "Congratulations, your station has chosen the Thinktronic 5230 Personal Data Assistant!" //Current note in the notepad function
	var/notehtml = ""
	var/notescanned = FALSE // True if what is in the notekeeper was from a paper.
	var/hidden = FALSE // Is the PDA hidden from the PDA list?
	var/emped = FALSE
	var/equipped = FALSE  //used here to determine if this is the first time its been picked up
	var/allow_emojis = FALSE //if the pda can send emojis and actually have them parsed as such
	var/sort_by_job = FALSE // If this is TRUE, will sort PDA list by job.

	var/obj/item/card/id/id = null //Making it possible to slot an ID card into the PDA so it can function as both.
	var/ownjob = null //related to above
	///account id of the ID held
	var/account_id

	var/obj/item/paicard/pai = null // A slot for a personal AI device

	var/datum/picture/picture //Scanned photo

	var/list/contained_item = list(/obj/item/pen, /obj/item/toy/crayon, /obj/item/lipstick, /obj/item/flashlight/pen, /obj/item/clothing/mask/cigarette)
	var/obj/item/inserted_item //Used for pen, crayon, and lipstick insertion or removal. Same as above.
	var/overlays_x_offset = 0 //x offset to use for certain overlays

	var/underline_flag = TRUE //flag for underline

/obj/item/pda/suicide_act(mob/living/carbon/user)
	var/deathMessage = msg_input(user)
	if (!deathMessage)
		deathMessage = "i ded"
	user.visible_message(span_suicide("[user] is sending a message to the Grim Reaper! It looks like [user.p_theyre()] trying to commit suicide!"))
	tnote += "→ To The Grim Reaper:
[deathMessage]
"//records a message in their PDA as being sent to the grim reaper
	return BRUTELOSS

/obj/item/pda/examine(mob/user)
	. = ..()
	if(!id && !inserted_item)
		return

	if(id)
		. += span_notice("Alt-click to remove the ID.") //won't name ID on examine in case it's stolen

	if(inserted_item && (!isturf(loc)))
		. += span_notice("Ctrl-click to remove [inserted_item].") //traitor pens are disguised so we're fine naming them on examine

	if((!isnull(cartridge)))
		. += span_notice("Ctrl+Shift-click to remove the cartridge.") //won't name cart on examine in case it's Detomatix

/obj/item/pda/Initialize(mapload)
	. = ..()

	GLOB.PDAs += src
	if(default_cartridge)
		cartridge = new default_cartridge(src)
	if(inserted_item)
		inserted_item = new inserted_item(src)
	else
		inserted_item = new /obj/item/pen(src)
	RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, .proc/on_light_eater)

	update_appearance()

/obj/item/pda/equipped(mob/user, slot)
	. = ..()
	if(!equipped)
		if(user.client)
			background_color = user.client.prefs.read_preference(/datum/preference/color/pda_color)
			switch(user.client.prefs.read_preference(/datum/preference/choiced/pda_style))
				if(MONO)
					font_index = MODE_MONO
					font_mode = FONT_MONO
				if(SHARE)
					font_index = MODE_SHARE
					font_mode = FONT_SHARE
				if(ORBITRON)
					font_index = MODE_ORBITRON
					font_mode = FONT_ORBITRON
				if(VT)
					font_index = MODE_VT
					font_mode = FONT_VT
				else
					font_index = MODE_MONO
					font_mode = FONT_MONO
			equipped = TRUE

/obj/item/pda/proc/update_label()
	name = "PDA-[owner] ([ownjob])" //Name generalisation

/obj/item/pda/GetAccess()
	if(id)
		return id.GetAccess()
	else
		return ..()

/obj/item/pda/get_id_examine_strings(mob/user)
	. = ..()
	if(id)
		. += "\The [src] is displaying [id]."
		. += id.get_id_examine_strings(user)

/obj/item/pda/GetID()
	return id

/obj/item/pda/RemoveID()
	return do_remove_id()

/obj/item/pda/InsertID(obj/item/inserting_item)
	var/obj/item/card/inserting_id = inserting_item.RemoveID()
	if(!inserting_id)
		return
	insert_id(inserting_id)
	if(id == inserting_id)
		return TRUE
	return FALSE

/obj/item/pda/update_overlays()
	. = ..()
	if(!initial(icon))
		return
	var/mutable_appearance/overlay = new(initial(icon))
	overlay.pixel_x = overlays_x_offset
	if(id)
		overlay.icon_state = "id_overlay"
		. += new /mutable_appearance(overlay)
	if(inserted_item)
		overlay.icon_state = "insert_overlay"
		. += new /mutable_appearance(overlay)
	if(light_on)
		overlay.icon_state = "light_overlay"
		. += new /mutable_appearance(overlay)
	if(pai)
		if(pai.pai)
			overlay.icon_state = "pai_overlay"
			. += new /mutable_appearance(overlay)
		else
			overlay.icon_state = "pai_off_overlay"
			. += new /mutable_appearance(overlay)

/obj/item/pda/MouseDrop(mob/over, src_location, over_location)
	var/mob/M = usr
	if((M == over) && usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
		return attack_self(M)
	return ..()

/obj/item/pda/attack_self_tk(mob/user)
	to_chat(user, span_warning("The PDA's capacitive touch screen doesn't seem to respond!"))
	return COMPONENT_CANCEL_ATTACK_CHAIN

/obj/item/pda/interact(mob/user)
	if(!ISADVANCEDTOOLUSER(user))
		to_chat(user, span_warning("You don't have the dexterity to do this!"))
		return

	..()

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

	var/datum/asset/spritesheet/emoji_s = get_asset_datum(/datum/asset/spritesheet/chat)
	emoji_s.send(user) //Already sent by chat but no harm doing this

	user.set_machine(src)

	var/dat = "<!DOCTYPE html><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'><title>Personal Data Assistant</title><link href=\"https://fonts.googleapis.com/css?family=Orbitron|Share+Tech+Mono|VT323\" rel=\"stylesheet\">"
	dat += assets.css_tag()
	dat += emoji_s.css_tag()

	dat += "[PDAIMG(refresh)]Refresh"

	if ((!isnull(cartridge)) && (mode == 0))
		dat += " | [PDAIMG(eject)]Eject [cartridge]"
	if (mode)
		dat += " | [PDAIMG(menu)]Return"

	if (mode == 0)
		dat += "
"
		dat += "
Toggle Font"
		dat += " | Change Color"
		dat += " | Toggle Underline" //underline button

		dat += "
"

	dat += "
"

	if (!owner)
		dat += "Warning: No owner information entered.  Please swipe card.

"
		dat += "[PDAIMG(refresh)]Retry"
	else
		switch (mode)
			if (0)
				dat += "
PERSONAL DATA ASSISTANT v.1.2
"
				dat += "Owner: [owner], [ownjob]
"
				dat += text("ID: [id ? "[id.registered_name], [id.assignment]" : "----------"]")
				dat += text("
[id ? "Update PDA Info" : ""]

")

				dat += "[station_time_timestamp()]
" //:[world.time / 100 % 6][world.time / 100 % 10]"
				/* SEPTIC EDIT REMOVAL
				dat += "[time2text(world.realtime, "MMM DD")] [GLOB.year_integer+540]
"
				*/
				//SEPTIC EDIT BEGIN
				dat += "[time2text(world.realtime, "MMM DD")] [GLOB.current_station_year]
"
				//SEPTIC EDIT END
				dat += "It has been [ROUND_TIME] since the emergency shuttle was last called."

				dat += "

"

				dat += "
General Functions
"
				dat += "
"
				dat += "
[PDAIMG(notes)]Notekeeper
"
				dat += "
[PDAIMG(mail)]Messenger
"
				dat += "
[PDAIMG(skills)]Skill Tracker
"

				if (cartridge)
					if (cartridge.access & CART_CLOWN)
						dat += "
[PDAIMG(honk)]Honk Synthesizer
"
						dat += "
[PDAIMG(honk)]Sad Trombone
"
					if (cartridge.access & CART_MANIFEST)
						dat += "
[PDAIMG(notes)]View Crew Manifest
"
					if(cartridge.access & CART_STATUS_DISPLAY)
						dat += "
[PDAIMG(status)]Set Status Display
"
					dat += "
"
					if (cartridge.access & CART_ENGINE)
						dat += "
Engineering Functions
"
						dat += "
"
						dat += "
[PDAIMG(power)]Power Monitor
"
						dat += "
"
					if (cartridge.access & CART_MEDICAL)
						dat += "
Medical Functions
"
						dat += "
"
						dat += "
[PDAIMG(medical)]Medical Records
"
						dat += "
[PDAIMG(scanner)][scanmode == 1 ? "Disable" : "Enable"] Medical Scanner
"
						dat += "
"
					if (cartridge.access & CART_SECURITY)
						dat += "
Security Functions
"
						dat += "
"
						dat += "
[PDAIMG(cuffs)]Security Records
"
						dat += "
"
					if(cartridge.access & CART_QUARTERMASTER)
						dat += "
Quartermaster Functions:
"
						dat += "
"
						dat += "
[PDAIMG(crate)]Supply Records
"
						dat += "
[PDAIMG(crate)]Ore Silo Logs
"
						dat += "
"
				dat += "
"

				dat += "
Utilities
"
				dat += "
"
				if (cartridge)
					if(cartridge.bot_access_flags)
						dat += "
[PDAIMG(medbot)]Bots Access
"
					if (cartridge.access & CART_JANITOR)
						dat += "
[PDAIMG(bucket)]Custodial Locator
"
					if(cartridge.access & CART_MIME)
						dat += "
[PDAIMG(emoji)]Emoji Guidebook
"
					if (istype(cartridge.radio))
						dat += "
[PDAIMG(signaler)]Signaler System
"
					if (cartridge.access & CART_NEWSCASTER)
						dat += "
[PDAIMG(notes)]Newscaster Access
"
					if (cartridge.access & CART_REAGENT_SCANNER)
						dat += "
[PDAIMG(reagent)][scanmode == 3 ? "Disable" : "Enable"] Reagent Scanner
"
					if (cartridge.access & CART_ENGINE)
						dat += "
[PDAIMG(reagent)][scanmode == 4 ? "Disable" : "Enable"] Halogen Counter
"
					if (cartridge.access & CART_ATMOS)
						dat += "
[PDAIMG(reagent)][scanmode == 5 ? "Disable" : "Enable"] Gas Scanner
"
					if (cartridge.access & CART_REMOTE_DOOR)
						dat += "
[PDAIMG(rdoor)]Toggle Remote Door
"
					if (cartridge.access & CART_DRONEPHONE)
						dat += "
[PDAIMG(dronephone)]Drone Phone
"
					if (cartridge.access & CART_DRONEACCESS)
						var/blacklist_state = GLOB.drone_machine_blacklist_enabled
						dat += "
[PDAIMG(droneblacklist)][blacklist_state ? "Disable" : "Enable"] Drone Blacklist
"
				dat += "
[PDAIMG(atmos)]Atmospheric Scan
"
				dat += "
[PDAIMG(flashlight)][light_on ? "Disable" : "Enable"] Flashlight
"
				if (pai)
					if(pai.loc != src)
						pai = null
						update_appearance()
					else
						dat += "
pAI Device Configuration
"
						dat += "
Eject pAI Device
"
				dat += "
"

			if (1)
				dat += "
[PDAIMG(notes)] Notekeeper V2.2
"
				dat += "Edit
"
				if(notescanned)
					dat += "(This is a scanned image, editing it may cause some text formatting to change.)
"
				dat += "
[(!notehtml ? note : notehtml)]"

			if (2)
				/* SEPTIC EDIT REMOVAL
				dat += "
[PDAIMG(mail)] SpaceMessenger V3.9.6
"
				*/
				//SEPTIC EDIT BEGIN
				dat += "
[PDAIMG(mail)] WhatsApp Silver V1.2
"
				//SEPTIC EDIT END
				dat += "[PDAIMG(bell)]Ringer: [silent == 1 ? "Off" : "On"] | "
				dat += "[PDAIMG(mail)]Send / Receive: [toff == 1 ? "Off" : "On"] | "
				dat += "[PDAIMG(bell)]Set Ringtone | "
				dat += "[PDAIMG(mail)]Messages
"
				dat += "Sorted by: [sort_by_job ? "Job" : "Name"]"

				if(cartridge)
					dat += cartridge.message_header()

				dat += "
[PDAIMG(menu)] Detected PDAs
"

				dat += "
"
				var/count = 0

				if (!toff)
					for (var/obj/item/pda/P in get_viewable_pdas(sort_by_job))
						if (P == src)
							continue
						dat += "
[P.owner] ([P.ownjob])"
						if(cartridge)
							dat += cartridge.message_special(P)
						dat += "
"
						count++
				dat += "
"
				if (count == 0)
					dat += "None detected.
"
				else if(cartridge?.spam_enabled)
					dat += "Send To All"
			if(6)
				dat += "
[PDAIMG(mail)] ExperTrak® Skill Tracker V4.26.2
"
				dat += "Thank you for choosing ExperTrak® brand software! ExperTrak® inc. is proud to be a Nanotrasen employee expertise and effectiveness department subsidary!"
				dat += "

This software is designed to track and monitor your skill development as a Nanotrasen employee. Your job performance across different fields has been quantified and categorized below.
"
				var/datum/mind/targetmind = user.mind
				if(targetmind)
					for (var/type in GLOB.skill_types)
						var/datum/skill/S = GetSkillRef(type)
						var/lvl_num = targetmind.get_skill_level(type)
						var/lvl_name = uppertext(targetmind.get_skill_level_name(type))
						var/exp = targetmind.get_skill_exp(type)
						var/xp_prog_to_level = targetmind.exp_needed_to_level_up(type)
						var/xp_req_to_level = 0
						if (xp_prog_to_level)//is it even possible to level up?
							xp_req_to_level = SKILL_EXP_LIST[lvl_num+1] - SKILL_EXP_LIST[lvl_num]
						dat += "
[S.name]"
						dat += "
[S.desc]"
						dat += "

EMPLOYEE SKILL LEVEL: [lvl_name]"
						if (exp && xp_req_to_level)
							var/progress_percent = (xp_req_to_level-xp_prog_to_level)/xp_req_to_level
							var/overall_percent = exp / SKILL_EXP_LIST[length(SKILL_EXP_LIST)]
							dat += "
PROGRESS TO NEXT SKILL LEVEL:"
							dat += "
" + num2loadingbar(progress_percent) + "([progress_percent*100])%"
							dat += "
OVERALL DEVELOPMENT PROGRESS:"
							dat += "
" + num2loadingbar(overall_percent) + "([overall_percent*100])%"
						if (lvl_num >= length(SKILL_EXP_LIST) && !(type in targetmind.skills_rewarded))
							dat += "
Contact the Professional [S.title] Association"
						dat += "

"
			if(21)
				if(icon_alert && !istext(icon_alert))
					cut_overlay(icon_alert)
					icon_alert = initial(icon_alert)
				/* SEPTIC EDIT REMOVAL
				dat += "
[PDAIMG(mail)] SpaceMessenger V3.9.6
"
				*/
				//SEPTIC EDIT BEGIN
				dat += "
[PDAIMG(mail)] WhatsApp Silver V1.2
"
				//SEPTIC EDIT END
				dat += "[PDAIMG(blank)]Clear Messages"

				dat += "
[PDAIMG(mail)] Messages
"

				dat += tnote
				dat += "
"

			if (3)
				dat += "
[PDAIMG(atmos)] Atmospheric Readings
"

				var/turf/T = user.loc
				if (isnull(T))
					dat += "Unable to obtain a reading.
"
				else
					var/datum/gas_mixture/environment = T.return_air()
					var/list/env_gases = environment.gases

					var/pressure = environment.return_pressure()
					var/total_moles = environment.total_moles()

					dat += "Air Pressure: [round(pressure,0.1)] kPa
"

					if (total_moles)
						for(var/id in env_gases)
							var/gas_level = env_gases[id][MOLES]/total_moles
							if(gas_level > 0)
								dat += "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_level*100, 0.01)]%
"

					dat += "Temperature: [round(environment.temperature-T0C)]°C
"
				dat += "
"
			else//Else it links to the cart menu proc. Although, it really uses menu hub 4--menu 4 doesn't really exist as it simply redirects to hub.
				dat += cartridge.generate_menu()

	dat += ""

	if (underline_flag)
		dat = replacetext(dat, "text-decoration:none", "text-decoration:underline")
	if (!underline_flag)
		dat = replacetext(dat, "text-decoration:underline", "text-decoration:none")

	user << browse(dat, "window=pda;size=400x450;border=1;can_resize=1;can_minimize=0")
	onclose(user, "pda", src)

/obj/item/pda/Topic(href, href_list)
	..()
	var/mob/living/U = usr
	//Looking for master was kind of pointless since PDAs don't appear to have one.

	if(usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !href_list["close"])
		add_fingerprint(U)
		U.set_machine(src)

		switch(href_list["choice"])

//BASIC FUNCTIONS===================================

			if("Refresh")//Refresh, goes to the end of the proc.
				if(!silent)
					playsound(src, 'sound/machines/terminal_select.ogg', 15, TRUE)

			if ("Toggle_Font")
				//CODE REVISION 2
				font_index = (font_index + 1) % 4

				switch(font_index)
					if (MODE_MONO)
						font_mode = FONT_MONO
					if (MODE_SHARE)
						font_mode = FONT_SHARE
					if (MODE_ORBITRON)
						font_mode = FONT_ORBITRON
					if (MODE_VT)
						font_mode = FONT_VT
				if(!silent)
					playsound(src, 'sound/machines/terminal_select.ogg', 15, TRUE)
			if ("Change_Color")
				var/new_color = input("Please enter a color name or hex value (Default is \'#808000\').",background_color)as color
				background_color = new_color

			if ("Toggle_Underline")
				underline_flag = !underline_flag
				if(!silent)
					playsound(src, 'sound/machines/terminal_select.ogg', 15, TRUE)

			if("Return")//Return
				if(mode<=9)
					mode = 0
				else
					mode = round(mode/10)
					if(mode==4 || mode == 5)//Fix for cartridges. Redirects to hub.
						mode = 0
				if(!silent)
					playsound(src, 'sound/machines/terminal_select.ogg', 15, TRUE)
			if ("Authenticate")//Checks for ID
				id_check(U)
			if("UpdateInfo")
				ownjob = id.assignment
				update_label()
				if(!silent)
					playsound(src, 'sound/machines/terminal_processing.ogg', 15, TRUE)
					addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, src, 'sound/machines/terminal_success.ogg', 15, TRUE), 1.3 SECONDS)
			if("Eject")//Ejects the cart, only done from hub.
				eject_cart(U)
				if(!silent)
					playsound(src, 'sound/machines/terminal_eject.ogg', 50, TRUE)

//MENU FUNCTIONS===================================

			if("0")//Hub
				mode = 0
				if(!silent)
					playsound(src, 'sound/machines/terminal_select.ogg', 15, TRUE)
			if("1")//Notes
				mode = 1
				if(!silent)
					playsound(src, 'sound/machines/terminal_select.ogg', 15, TRUE)
			if("2")//Messenger
				mode = 2
				if(!silent)
					playsound(src, 'sound/machines/terminal_select.ogg', 15, TRUE)
			if("21")//Read messeges
				mode = 21
				if(!silent)
					playsound(src, 'sound/machines/terminal_select.ogg', 15, TRUE)
			if("3")//Atmos scan
				mode = 3
				if(!silent)
					playsound(src, 'sound/machines/terminal_select.ogg', 15, TRUE)
			if("4")//Redirects to hub
				mode = 0
				if(!silent)
					playsound(src, 'sound/machines/terminal_select.ogg', 15, TRUE)

//MAIN FUNCTIONS===================================

			if("Light")
				toggle_light(U)
				if(!silent)
					playsound(src, 'sound/machines/terminal_select.ogg', 15, TRUE)
			if("Medical Scan")
				if(scanmode == PDA_SCANNER_MEDICAL)
					scanmode = PDA_SCANNER_NONE
				else if((!isnull(cartridge)) && (cartridge.access & CART_MEDICAL))
					scanmode = PDA_SCANNER_MEDICAL
				if(!silent)
					playsound(src, 'sound/machines/terminal_select.ogg', 15, TRUE)
			if("Reagent Scan")
				if(scanmode == PDA_SCANNER_REAGENT)
					scanmode = PDA_SCANNER_NONE
				else if((!isnull(cartridge)) && (cartridge.access & CART_REAGENT_SCANNER))
					scanmode = PDA_SCANNER_REAGENT
			if("Halogen Counter")
				if(scanmode == PDA_SCANNER_HALOGEN)
					scanmode = PDA_SCANNER_NONE
				else if((!isnull(cartridge)) && (cartridge.access & CART_ENGINE))
					scanmode = PDA_SCANNER_HALOGEN
				if(!silent)
					playsound(src, 'sound/machines/terminal_select.ogg', 15, TRUE)
			if("Honk")
				if ( !(last_noise && world.time < last_noise + 20) )
					playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE)
					last_noise = world.time
			if("Trombone")
				if ( !(last_noise && world.time < last_noise + 20) )
					playsound(src, 'sound/misc/sadtrombone.ogg', 50, TRUE)
					last_noise = world.time
			if("Gas Scan")
				if(scanmode == PDA_SCANNER_GAS)
					scanmode = PDA_SCANNER_NONE
				else if((!isnull(cartridge)) && (cartridge.access & CART_ATMOS))
					scanmode = PDA_SCANNER_GAS
				if(!silent)
					playsound(src, 'sound/machines/terminal_select.ogg', 15, TRUE)
			if("Drone Phone")
				var/alert_s = input(U,"Alert severity level","Ping Drones",null) as null|anything in list("Low","Medium","High","Critical")
				var/area/A = get_area(U)
				if(A && alert_s && !QDELETED(U))
					var/msg = span_boldnotice("NON-DRONE PING: [U.name]: [alert_s] priority alert in [A.name]!")
					_alert_drones(msg, TRUE, U)
					to_chat(U, msg)
					if(!silent)
						playsound(src, 'sound/machines/terminal_success.ogg', 15, TRUE)
			if("Drone Access")
				var/mob/living/simple_animal/drone/drone_user = U
				if(isdrone(U) && drone_user.shy)
					to_chat(U, span_warning("Your laws prevent this action."))
					return
				var/new_state = text2num(href_list["drone_blacklist"])
				GLOB.drone_machine_blacklist_enabled = new_state
				if(!silent)
					playsound(src, 'sound/machines/terminal_select.ogg', 15, TRUE)

//NOTEKEEPER FUNCTIONS===================================

			if ("Edit")
				var/n = stripped_multiline_input(U, "Please enter message", name, note)
				if (in_range(src, U) && loc == U)
					if (mode == 1 && n)
						note = n
						notehtml = parsemarkdown(n, U)
						notescanned = FALSE
				else
					U << browse(null, "window=pda")
					return

//MESSENGER FUNCTIONS===================================

			if("Toggle Messenger")
				toff = !toff
			if("Toggle Ringer")//If viewing texts then erase them, if not then toggle silent status
				silent = !silent
			if("Clear")//Clears messages
				tnote = null
			if("Ringtone")
				var/t = stripped_input(U, "Please enter new ringtone", name, ttone, 20)
				if(in_range(src, U) && loc == U && t)
					if(SEND_SIGNAL(src, COMSIG_PDA_CHANGE_RINGTONE, U, t) & COMPONENT_STOP_RINGTONE_CHANGE)
						U << browse(null, "window=pda")
						return
					else
						ttone = t
				else
					U << browse(null, "window=pda")
					return
			if("Message")
				create_message(U, locate(href_list["target"]) in GLOB.PDAs)

			if("Sorting Mode")
				sort_by_job = !sort_by_job

			if("MessageAll")
				if(cartridge?.spam_enabled)
					send_to_all(U)

			if("cart")
				if(cartridge)
					cartridge.special(U, href_list)
				else
					U << browse(null, "window=pda")
					return

//SYNDICATE FUNCTIONS===================================

			if("Toggle Door")
				if(cartridge && cartridge.access & CART_REMOTE_DOOR)
					for(var/obj/machinery/door/poddoor/M in GLOB.machines)
						if(M.id == cartridge.remote_door_id)
							if(M.density)
								M.open()
							else
								M.close()

//pAI FUNCTIONS===================================

			if("pai")
				switch(href_list["option"])
					if("1") // Configure pAI device
						pai.attack_self(U)
					if("2") // Eject pAI device
						usr.put_in_hands(pai)
						to_chat(usr, span_notice("You remove the pAI from the [name]."))

//SKILL FUNCTIONS===================================

			if("SkillReward")
				var/type = text2path(href_list["skill"])
				var/datum/skill/S = GetSkillRef(type)
				var/datum/mind/mind = U.mind
				var/new_level = mind.get_skill_level(type)
				S.try_skill_reward(mind, new_level)

//LINK FUNCTIONS===================================

			else//Cartridge menu linking
				mode = max(text2num(href_list["choice"]), 0)

	else//If not in range, can't interact or not using the pda.
		U.unset_machine()
		U << browse(null, "window=pda")
		return

//EXTRA FUNCTIONS===================================

	if (mode == 2 || mode == 21)//To clear message overlays.
		update_appearance()

	if ((honkamt > 0) && (prob(60)))//For clown virus.
		honkamt--
		playsound(src, 'sound/items/bikehorn.ogg', 30, TRUE)

	if(U.machine == src && href_list["skiprefresh"]!="1")//Final safety.
		attack_self(U)//It auto-closes the menu prior if the user is not in range and so on.
	else
		U.unset_machine()
		U << browse(null, "window=pda")
	return

/obj/item/pda/proc/remove_id(mob/user)
	if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
		return
	do_remove_id(user)

/obj/item/pda/proc/do_remove_id(mob/user)
	if(!id)
		return
	if(user)
		user.put_in_hands(id)
		to_chat(user, span_notice("You remove the ID from the [name]."))
	else
		id.forceMove(get_turf(src))

	. = id
	id = null
	updateSelfDialog()
	update_appearance()
	playsound(src, 'sound/machines/terminal_eject.ogg', 50, TRUE)

	if(ishuman(loc))
		var/mob/living/carbon/human/H = loc
		if(H.wear_id == src)
			H.sec_hud_set_ID()

	update_slot_icon()

/obj/item/pda/proc/msg_input(mob/living/U = usr)
	var/t = stripped_input(U, "Please enter message", name)
	if (!t || toff)
		return
	if(!U.canUseTopic(src, BE_CLOSE))
		return
	if(emped)
		t = Gibberish(t, TRUE)
	return t

/obj/item/pda/proc/send_message(mob/living/user, list/obj/item/pda/targets, everyone)
	var/message = msg_input(user)
	if(!message || !targets.len)
		return
	if((last_text && world.time < last_text + 10) || (everyone && last_everyone && world.time < last_everyone + PDA_SPAM_DELAY))
		return

	var/list/filter_result = is_ic_filtered_for_pdas(message)
	if (filter_result)
		REPORT_CHAT_FILTER_TO_USER(user, filter_result)
		return

	var/list/soft_filter_result = is_soft_ic_filtered_for_pdas(message)
	if (soft_filter_result)
		if(tgui_alert(usr,"Your message contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to send it?", "Soft Blocked Word", list("Yes", "No")) != "Yes")
			return
		message_admins("[ADMIN_LOOKUPFLW(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term in PDA messages. Message: \"[html_encode(message)]\"")
		log_admin_private("[key_name(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term in PDA messages. Message: \"[message]\"")

	if(prob(1))
		message += "\nSent from my PDA"
	// Send the signal
	var/list/string_targets = list()
	for (var/obj/item/pda/P in targets)
		if (P.owner && P.ownjob)  // != src is checked by the UI
			string_targets += "[P.owner] ([P.ownjob])"
	for (var/obj/machinery/computer/message_monitor/M in targets)
		// In case of "Reply" to a message from a console, this will make the
		// message be logged successfully. If the console is impersonating
		// someone by matching their name and job, the reply will reach the
		// impersonated PDA.
		string_targets += "[M.customsender] ([M.customjob])"
	if (!string_targets.len)
		return

	var/datum/signal/subspace/messaging/pda/signal = new(src, list(
		"name" = "[owner]",
		"job" = "[ownjob]",
		"message" = message,
		"targets" = string_targets,
		"emojis" = allow_emojis,
	))
	if (picture)
		signal.data["photo"] = picture
	signal.send_to_receivers()

	// If it didn't reach, note that fact
	if (!signal.data["done"])
		to_chat(user, span_notice("ERROR: Server isn't responding."))
		if(!silent)
			playsound(src, 'sound/machines/terminal_error.ogg', 15, TRUE)
		return

	var/target_text = signal.format_target()
	if(allow_emojis)
		message = emoji_parse(message)//already sent- this just shows the sent emoji as one to the sender in the to_chat
		signal.data["message"] = emoji_parse(signal.data["message"])

	// Log it in our logs
	tnote += "→ To [target_text]:
[signal.format_message()]
"
	// Show it to ghosts
	var/ghost_message = span_name("[owner] PDA Message --> [span_name("[target_text]")]: [signal.format_message()]")
	for(var/mob/M in GLOB.player_list)
		if(isobserver(M) && (M.client?.prefs.chat_toggles & CHAT_GHOSTPDA))
			to_chat(M, "[FOLLOW_LINK(M, user)] [ghost_message]")
	// Log in the talk log
	user.log_talk(message, LOG_PDA, tag="PDA: [initial(name)] to [target_text]")
	to_chat(user, span_info("PDA message sent to [target_text]: \"[message]\""))
	if(!silent)
		playsound(src, 'sound/machines/terminal_success.ogg', 15, TRUE)
	// Reset the photo
	picture = null
	last_text = world.time
	if (everyone)
		last_everyone = world.time

/obj/item/pda/proc/receive_message(datum/signal/subspace/messaging/pda/signal)
	tnote += "← From [signal.data["name"]] ([signal.data["job"]]):
[signal.format_message()]
"

	if (!silent)
		if(HAS_TRAIT(SSstation, STATION_TRAIT_PDA_GLITCHED))
			playsound(src, pick('sound/machines/twobeep_voice1.ogg', 'sound/machines/twobeep_voice2.ogg'), 50, TRUE)
		else
			playsound(src, 'sound/machines/twobeep_high.ogg', 50, TRUE)
		audible_message("[icon2html(src, hearers(src))] *[ttone]*", null, 3)
	//Search for holder of the PDA.
	var/mob/living/L = null
	if(loc && isliving(loc))
		L = loc
	//Maybe they are a pAI!
	else
		L = get(src, /mob/living/silicon)

	if(L && (L.stat == CONSCIOUS || L.stat == SOFT_CRIT))
		var/reply = "(Reply)"
		var/hrefstart
		var/hrefend
		if (isAI(L))
			hrefstart = ""
			hrefend = ""

		if(signal.data["automated"])
			reply = "\[Automated Message\]"

		var/inbound_message = signal.format_message()
		if(signal.data["emojis"] == TRUE)//so will not parse emojis as such from pdas that don't send emojis
			inbound_message = emoji_parse(inbound_message)

		to_chat(L, "[icon2html(src)] PDA message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [inbound_message] [reply]")

	update_appearance()
	if(istext(icon_alert))
		icon_alert = mutable_appearance(initial(icon), icon_alert)
		add_overlay(icon_alert)

/obj/item/pda/proc/send_to_all(mob/living/U)
	if (last_everyone && world.time < last_everyone + PDA_SPAM_DELAY)
		to_chat(U,span_warning("Send To All function is still on cooldown."))
		return
	send_message(U,get_viewable_pdas(), TRUE)

/obj/item/pda/proc/create_message(mob/living/U, obj/item/pda/P)
	send_message(U,list(P))

/obj/item/pda/AltClick(mob/user)
	..()

	if(id)
		remove_id(user)
	else
		remove_pen(user)

/obj/item/pda/CtrlClick(mob/user)
	..()

	if(isturf(loc)) //stops the user from dragging the PDA by ctrl-clicking it.
		return

	remove_pen(user)

/obj/item/pda/CtrlShiftClick(mob/user)
	..()
	eject_cart(user)

/obj/item/pda/verb/verb_toggle_light()
	set name = "Toggle light"
	set category = "Object"
	set src in oview(1)

	toggle_light(usr)

/obj/item/pda/verb/verb_remove_id()
	set category = "Object"
	set name = "Eject ID"
	set src in usr

	if(id)
		remove_id(usr)
	else
		to_chat(usr, span_warning("This PDA does not have an ID in it!"))

/obj/item/pda/verb/verb_remove_pen()
	set category = "Object"
	set name = "Remove Pen"
	set src in usr

	remove_pen(usr)

/obj/item/pda/verb/verb_eject_cart()
	set category = "Object"
	set name = "Eject Cartridge"
	set src in usr

	eject_cart(usr)

/obj/item/pda/proc/toggle_light(mob/user)
	if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE))
		return
	if(light_on)
		set_light_on(FALSE)
	else if(light_range)
		set_light_on(TRUE)
	update_appearance()
	update_action_buttons(force = TRUE)

/// Special light eater handling
/obj/item/pda/proc/on_light_eater(obj/item/pda/source, datum/light_eater)
	SIGNAL_HANDLER
	set_light_on(FALSE)
	set_light_range(0) //We won't be turning on again.
	update_appearance()
	visible_message(span_danger("The light in [src] shorts out!"))
	return COMPONENT_BLOCK_LIGHT_EATER

/obj/item/pda/proc/remove_pen(mob/user)

	if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) //TK doesn't work even with this removed but here for readability
		return

	if(inserted_item)
		user.put_in_hands(inserted_item)
		to_chat(user, span_notice("You remove [inserted_item] from [src]."))
		inserted_item = null
		update_appearance()
		playsound(src, 'sound/machines/pda_button2.ogg', 50, TRUE)
	else
		to_chat(user, span_warning("This PDA does not have a pen in it!"))

/obj/item/pda/proc/eject_cart(mob/user)
	if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) //TK disabled to stop cartridge teleporting into hand
		return
	if (!isnull(cartridge))
		user.put_in_hands(cartridge)
		to_chat(user, span_notice("You eject [cartridge] from [src]."))
		scanmode = PDA_SCANNER_NONE
		cartridge.host_pda = null
		cartridge = null
		updateSelfDialog()
		update_appearance()

//trying to insert or remove an id
/obj/item/pda/proc/id_check(mob/user, obj/item/card/id/I)
	if(!I)
		if(id && (src in user.contents))
			remove_id(user)
			return TRUE
		else
			var/obj/item/card/id/C = user.get_active_held_item()
			if(istype(C))
				I = C

	if(I?.registered_name)
		if(!user.transferItemToLoc(I, src))
			return FALSE
		insert_id(I, user)
		update_appearance()
		playsound(src, 'sound/machines/pda_button1.ogg', 50, TRUE)
	return TRUE

/obj/item/pda/proc/insert_id(obj/item/card/id/inserting_id, mob/user)
	var/obj/old_id = id
	id = inserting_id
	if(ishuman(loc))
		var/mob/living/carbon/human/human_wearer = loc
		if(human_wearer.wear_id == src)
			human_wearer.sec_hud_set_ID()
	if(old_id)
		if(user)
			user.put_in_hands(old_id)
		else
			old_id.forceMove(get_turf(src))

	update_slot_icon()

/obj/item/pda/pre_attack(obj/target, mob/living/user, params)
	if(!ismachinery(target))
		return ..()
	var/obj/machinery/target_machine = target
	if(!target_machine.panel_open && !istype(target, /obj/machinery/computer))
		return ..()
	if(!istype(cartridge, /obj/item/cartridge/virus/clown))
		return ..()
	var/obj/item/cartridge/virus/installed_cartridge = cartridge

	if(installed_cartridge.charges <=0)
		to_chat(user, span_notice("Out of charges."))
		return ..()
	to_chat(user, span_notice("You upload the virus to the airlock controller!"))
	var/sig_list
	if(istype(target,/obj/machinery/door/airlock))
		sig_list += list(COMSIG_AIRLOCK_OPEN, COMSIG_AIRLOCK_CLOSE)
	else
		sig_list += list(COMSIG_ATOM_ATTACK_HAND)
	target.AddComponent(/datum/component/sound_player, amount = (rand(15,20)), signal_or_sig_list = sig_list)
	installed_cartridge.charges --
	return TRUE

// access to status display signals
/obj/item/pda/attackby(obj/item/C, mob/user, params)
	if(istype(C, /obj/item/cartridge))
		if(!user.transferItemToLoc(C, src))
			return
		eject_cart(user)
		cartridge = C
		cartridge.host_pda = src
		to_chat(user, span_notice("You insert [cartridge] into [src]."))
		updateSelfDialog()
		update_appearance()
		playsound(src, 'sound/machines/pda_button1.ogg', 50, TRUE)

	else if(istype(C, /obj/item/card/id))
		var/obj/item/card/id/idcard = C
		if(!idcard.registered_name)
			to_chat(user, span_warning("\The [src] rejects the ID!"))
			if(!silent)
				playsound(src, 'sound/machines/terminal_error.ogg', 50, TRUE)
			return
		if(!owner)
			owner = idcard.registered_name
			ownjob = idcard.assignment
			update_label()
			to_chat(user, span_notice("Card scanned."))
			if(!silent)
				playsound(src, 'sound/machines/terminal_success.ogg', 50, TRUE)
		else
			if(!id_check(user, idcard))
				return
			to_chat(user, span_notice("You put the ID into \the [src]'s slot."))
			updateSelfDialog()//Update self dialog on success.

			return //Return in case of failed check or when successful.
		updateSelfDialog()//For the non-input related code.
	else if(istype(C, /obj/item/paicard) && !pai)
		if(!user.transferItemToLoc(C, src))
			return
		pai = C
		to_chat(user, span_notice("You slot \the [C] into [src]."))
		update_appearance()
		updateUsrDialog()
	else if(is_type_in_list(C, contained_item)) //Checks if there is a pen
		if(inserted_item)
			to_chat(user, span_warning("There is already \a [inserted_item] in \the [src]!"))
		else
			if(!user.transferItemToLoc(C, src))
				return
			to_chat(user, span_notice("You slide \the [C] into \the [src]."))
			inserted_item = C
			update_appearance()
			playsound(src, 'sound/machines/pda_button1.ogg', 50, TRUE)
	else if(istype(C, /obj/item/photo))
		var/obj/item/photo/P = C
		picture = P.picture
		to_chat(user, span_notice("You scan \the [C]."))
	// Check to see if we have an ID inside, and a valid input for money
	else if(id && iscash(C))
		id.attackby(C, user) // If we do, try and put that attacking object in
	else
		return ..()

/obj/item/pda/attack(mob/living/carbon/C, mob/living/user)
	if(istype(C))
		switch(scanmode)

			if(PDA_SCANNER_MEDICAL)
				C.visible_message(span_notice("[user] analyzes [C]'s vitals."))
				healthscan(user, C, 1)
				add_fingerprint(user)

			if(PDA_SCANNER_HALOGEN)
				C.visible_message(span_notice("[user] analyzes [C]'s radiation levels."))

				user.show_message(span_notice("Analyzing Results for [C]:"))
				if(C.radiation)
					user.show_message("\green Radiation Level: \black [C.radiation]")
				else
					user.show_message(span_notice("No radiation detected."))

/obj/item/pda/afterattack(atom/A as mob|obj|turf|area, mob/user, proximity)
	. = ..()
	if(!proximity)
		return
	switch(scanmode)
		if(PDA_SCANNER_REAGENT)
			if(!isnull(A.reagents))
				if(A.reagents.reagent_list.len > 0)
					var/reagents_length = A.reagents.reagent_list.len
					to_chat(user, span_notice("[reagents_length] chemical agent[reagents_length > 1 ? "s" : ""] found."))
					for (var/re in A.reagents.reagent_list)
						to_chat(user, span_notice("\t [re]"))
				else
					to_chat(user, span_notice("No active chemical agents found in [A]."))
			else
				to_chat(user, span_notice("No significant chemical agents found in [A]."))

		if(PDA_SCANNER_GAS)
			A.analyzer_act(user, src)

	if (!scanmode && istype(A, /obj/item/paper) && owner)
		var/obj/item/paper/paper = A
		if (!paper.get_info_length())
			to_chat(user, span_warning("Unable to scan! Paper is blank."))
			return
		notehtml = paper.info
		if(paper.add_info)
			for(var/index in 1 to length(paper.add_info))
				var/list/style = paper.add_info_style[index]
				notehtml += PAPER_MARK_TEXT(paper.add_info[index], style[ADD_INFO_COLOR], style[ADD_INFO_FONT])
		note = replacetext(notehtml, "
", "\[br\]")
		note = replacetext(note, "
", "\[*\]")
		note = replacetext(note, "
", "\[list\]")
		note = replacetext(note, "
", "\[/list\]")
		note = html_encode(note)
		notescanned = TRUE
		to_chat(user, span_notice("Paper scanned. Saved to PDA's notekeeper.") )

/obj/item/pda/proc/explode() //This needs tuning.
	var/turf/T = get_turf(src)

	if (ismob(loc))
		var/mob/M = loc
		M.show_message(span_userdanger("Your [src] explodes!"), MSG_VISUAL, span_warning("You hear a loud *pop*!"), MSG_AUDIBLE)
	else
		visible_message(span_danger("[src] explodes!"), span_warning("You hear a loud *pop*!"))

	if(T)
		T.hotspot_expose(700,125)
		if(istype(cartridge, /obj/item/cartridge/virus/syndicate))
			explosion(src, devastation_range = -1, heavy_impact_range = 1, light_impact_range = 3, flash_range = 4)
		else
			explosion(src, devastation_range = -1, heavy_impact_range = -1, light_impact_range = 2, flash_range = 3)
	qdel(src)
	return

/obj/item/pda/Destroy()
	GLOB.PDAs -= src
	if(istype(id))
		QDEL_NULL(id)
	if(istype(cartridge))
		QDEL_NULL(cartridge)
	if(istype(pai))
		QDEL_NULL(pai)
	if(istype(inserted_item))
		QDEL_NULL(inserted_item)
	return ..()

//AI verb and proc for sending PDA messages.

/obj/item/pda/ai/verb/cmd_toggle_pda_receiver()
	set category = "AI Commands"
	set name = "PDA - Toggle Sender/Receiver"

	if(usr.stat == DEAD)
		return //won't work if dead
	var/mob/living/silicon/S = usr
	if(istype(S) && !isnull(S.aiPDA))
		S.aiPDA.toff = !S.aiPDA.toff
		to_chat(usr, span_notice("PDA sender/receiver toggled [(S.aiPDA.toff ? "Off" : "On")]!"))
	else
		to_chat(usr, "You do not have a PDA. You should make an issue report about this.")

/obj/item/pda/ai/verb/cmd_toggle_pda_silent()
	set category = "AI Commands"
	set name = "PDA - Toggle Ringer"

	if(usr.stat == DEAD)
		return //won't work if dead
	var/mob/living/silicon/S = usr
	if(istype(S) && !isnull(S.aiPDA))
		//0
		S.aiPDA.silent = !S.aiPDA.silent
		to_chat(usr, span_notice("PDA ringer toggled [(S.aiPDA.silent ? "Off" : "On")]!"))
	else
		to_chat(usr, "You do not have a PDA. You should make an issue report about this.")

/mob/living/silicon/proc/cmd_send_pdamesg(mob/user)
	var/list/plist = list()
	var/list/namecounts = list()

	if(aiPDA.toff)
		to_chat(user, span_alert("Turn on your receiver in order to send messages."))
		return

	for (var/obj/item/pda/pda as anything in get_viewable_pdas())
		if (pda == src)
			continue
		else if (pda == aiPDA)
			continue

		plist[avoid_assoc_duplicate_keys(pda.owner, namecounts)] = pda

	var/choice = tgui_input_list(user, "Please select a PDA", "PDA Messenger", sort_list(plist))

	if (!choice)
		return

	var/selected = plist[choice]

	if(aicamera.stored.len)
		var/add_photo = tgui_alert(user,"Do you want to attach a photo?", "PDA Messenger", list("Yes","No"))
		if(add_photo == "Yes")
			var/datum/picture/Pic = aicamera.selectpicture(user)
			aiPDA.picture = Pic

	if(incapacitated())
		return

	aiPDA.create_message(src, selected)

/mob/living/silicon/proc/cmd_show_message_log(mob/user)
	if(incapacitated())
		return
	if(!isnull(aiPDA))
		var/HTML = "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'><title>AI PDA Message Log</title>[aiPDA.tnote]"
		user << browse(HTML, "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0")
	else
		to_chat(user, span_warning("You do not have a PDA! You should make an issue report about this."))

// Pass along the pulse to atoms in contents, largely added so pAIs are vulnerable to EMP
/obj/item/pda/emp_act(severity)
	. = ..()
	if (!(. & EMP_PROTECT_CONTENTS))
		for(var/atom/A in src)
			A.emp_act(severity)
	if (!(. & EMP_PROTECT_SELF))
		emped++
		addtimer(CALLBACK(src, .proc/emp_end), 200 * severity)

/obj/item/pda/proc/emp_end()
	emped--

/proc/get_viewable_pdas(sort_by_job = FALSE)
	. = list()
	// Returns a list of PDAs which can be viewed from another PDA/message monitor.,
	var/sortmode
	if(sort_by_job)
		sortmode = /proc/cmp_pdajob_asc
	else
		sortmode = /proc/cmp_pdaname_asc

	for(var/obj/item/pda/P in sort_list(GLOB.PDAs, sortmode))
		if(!P.owner || P.toff || P.hidden)
			continue
		. += P

/obj/item/pda/proc/pda_no_detonate()
	SIGNAL_HANDLER
	return COMPONENT_PDA_NO_DETONATE

#undef PDA_SCANNER_NONE
#undef PDA_SCANNER_MEDICAL
#undef PDA_SCANNER_FORENSICS
#undef PDA_SCANNER_REAGENT
#undef PDA_SCANNER_HALOGEN
#undef PDA_SCANNER_GAS
#undef PDA_SPAM_DELAY


code:
//Clown PDA is slippery.
/obj/item/pda/clown
	name = "clown PDA"
	default_cartridge = /obj/item/cartridge/virus/clown
	inserted_item = /obj/item/toy/crayon/rainbow
	icon_state = "pda-clown"
	greyscale_config = null
	greyscale_config = null
	desc = "A portable microcomputer by Thinktronic Systems, LTD. The surface is coated with polytetrafluoroethylene and banana drippings."
	ttone = "honk"

/obj/item/pda/clown/ComponentInitialize()
	. = ..()
	AddComponent(/datum/component/slippery/clowning, 120, NO_SLIP_WHEN_WALKING, CALLBACK(src, .proc/AfterSlip), slot_whitelist = list(ITEM_SLOT_ID, ITEM_SLOT_BELT))
	AddComponent(/datum/component/wearertargeting/sitcomlaughter, CALLBACK(src, .proc/after_sitcom_laugh))

/obj/item/pda/clown/proc/AfterSlip(mob/living/carbon/human/M)
	if (istype(M) && (M.real_name != owner))
		var/obj/item/cartridge/virus/clown/cart = cartridge
		if(istype(cart) && cart.charges < 5)
			cart.charges++
			playsound(src,'sound/machines/ping.ogg',30,TRUE)

/obj/item/pda/clown/proc/after_sitcom_laugh(mob/victim)
	victim.visible_message("[src] lets out a burst of laughter!")

//Mime PDA sends "silent" messages.
/obj/item/pda/mime
	name = "mime PDA"
	default_cartridge = /obj/item/cartridge/virus/mime
	inserted_item = /obj/item/toy/crayon/mime
	greyscale_config = /datum/greyscale_config/pda/mime
	greyscale_colors = "#e2e2e2#cc4242"
	desc = "A portable microcomputer by Thinktronic Systems, LTD. The hardware has been modified for compliance with the vows of silence."
	allow_emojis = TRUE
	silent = TRUE
	ttone = "silence"

/obj/item/pda/mime/msg_input(mob/living/U = usr)
	if(emped || toff)
		return
	var/emojis = emoji_sanitize(stripped_input(U, "Please enter emojis", name))
	if(!emojis)
		return
	if(!U.canUseTopic(src, BE_CLOSE))
		return
	return emojis

// Special AI/pAI PDAs that cannot explode.
/obj/item/pda/ai
	icon = null
	greyscale_config = null
	greyscale_colors = null
	ttone = "data"

/obj/item/pda/ai/attack_self(mob/user)
	if ((honkamt > 0) && (prob(60)))//For clown virus.
		honkamt--
		playsound(loc, 'sound/items/bikehorn.ogg', 30, TRUE)
	return

/obj/item/pda/ai/pai
	ttone = "assist"

/obj/item/pda/ai/Initialize(mapload)
	. = ..()
	RegisterSignal(src, COMSIG_PDA_CHECK_DETONATE, .proc/pda_no_detonate)

/obj/item/pda/medical
	name = "medical PDA"
	default_cartridge = /obj/item/cartridge/medical
	greyscale_config = /datum/greyscale_config/pda/stripe_thick
	greyscale_colors = "#e2e2e2#000099#5d99be"

/obj/item/pda/viro
	name = "virology PDA"
	default_cartridge = /obj/item/cartridge/medical
	greyscale_config = /datum/greyscale_config/pda/stripe_split
	greyscale_colors = "#e2e2e2#355FAC#789876"

/obj/item/pda/engineering
	name = "engineering PDA"
	default_cartridge = /obj/item/cartridge/engineering
	greyscale_config = /datum/greyscale_config/pda/stripe_thick
	greyscale_colors = "#C5994C#69DBF3#D9D65B"

/obj/item/pda/security
	name = "security PDA"
	default_cartridge = /obj/item/cartridge/security
	greyscale_colors = "#cc4242#0000cc"

/obj/item/pda/detective
	name = "detective PDA"
	default_cartridge = /obj/item/cartridge/detective
	greyscale_colors = "#90714F#990202"

/obj/item/pda/warden
	name = "warden PDA"
	default_cartridge = /obj/item/cartridge/security
	greyscale_config = /datum/greyscale_config/pda/stripe_split
	greyscale_colors = "#cc4242#0000cc#666666"

/obj/item/pda/janitor
	name = "janitor PDA"
	default_cartridge = /obj/item/cartridge/janitor
	greyscale_colors = "#933ea8#235AB2"
	ttone = "slip"

/obj/item/pda/science
	name = "scientist PDA"
	default_cartridge = /obj/item/cartridge/signal/ordnance
	greyscale_config = /datum/greyscale_config/pda/stripe_thick
	greyscale_colors = "#e2e2e2#000099#9F5CA5"
	ttone = "boom"

/obj/item/pda/heads
	default_cartridge = /obj/item/cartridge/head
	greyscale_config = /datum/greyscale_config/pda/head
	greyscale_colors = "#789876#a92323"

/obj/item/pda/heads/hop
	name = "head of personnel PDA"
	default_cartridge = /obj/item/cartridge/hop

/obj/item/pda/heads/hos
	name = "head of security PDA"
	default_cartridge = /obj/item/cartridge/hos
	greyscale_config = /datum/greyscale_config/pda/head
	greyscale_colors = "#cc4242#0000cc"

/obj/item/pda/heads/ce
	name = "chief engineer PDA"
	default_cartridge = /obj/item/cartridge/ce
	greyscale_config = /datum/greyscale_config/pda/stripe_thick/head
	greyscale_colors = "#C4A56D#69DBF3#e2e2e2"

/obj/item/pda/heads/cmo
	name = "chief medical officer PDA"
	default_cartridge = /obj/item/cartridge/cmo
	greyscale_config = /datum/greyscale_config/pda/stripe_thick/head
	greyscale_colors = "#e2e2e2#000099#5d99be"

/obj/item/pda/heads/rd
	name = "research director PDA"
	default_cartridge = /obj/item/cartridge/rd
	inserted_item = /obj/item/pen/fountain
	greyscale_config = /datum/greyscale_config/pda/stripe_thick/head
	greyscale_colors = "#e2e2e2#000099#9F5CA5"

/obj/item/pda/captain
	name = "captain PDA"
	default_cartridge = /obj/item/cartridge/captain
	inserted_item = /obj/item/pen/fountain/captain
	greyscale_config = /datum/greyscale_config/pda/captain
	greyscale_colors = "#2C7CB2#FF0000#FFFFFF#F5D67B"

/obj/item/pda/captain/Initialize(mapload)
	. = ..()
	RegisterSignal(src, COMSIG_PDA_CHECK_DETONATE, .proc/pda_no_detonate)

/obj/item/pda/cargo
	name = "cargo technician PDA"
	default_cartridge = /obj/item/cartridge/quartermaster
	greyscale_colors = "#D6B328#6506ca"

/obj/item/pda/quartermaster
	name = "quartermaster PDA"
	default_cartridge = /obj/item/cartridge/quartermaster
	inserted_item = /obj/item/pen/survival
	greyscale_config = /datum/greyscale_config/pda/stripe_thick
	greyscale_colors = "#D6B328#6506ca#927444"

/obj/item/pda/shaftminer
	name = "shaft miner PDA"
	greyscale_config = /datum/greyscale_config/pda/stripe_thick
	greyscale_colors = "#927444#D6B328#6C3BA1"

/obj/item/pda/syndicate
	default_cartridge = /obj/item/cartridge/virus/syndicate
	greyscale_colors = "#891417#80FF80"
	name = "military PDA"
	owner = "John Doe"
	hidden = 1

/obj/item/pda/chaplain
	name = "chaplain PDA"
	greyscale_config = /datum/greyscale_config/pda/chaplain
	greyscale_colors = "#333333#d11818"
	ttone = "holy"

/obj/item/pda/lawyer
	name = "lawyer PDA"
	default_cartridge = /obj/item/cartridge/lawyer
	inserted_item = /obj/item/pen/fountain
	greyscale_colors = "#5B74A5#f7e062"
	ttone = "objection"

/obj/item/pda/botanist
	name = "botanist PDA"
	greyscale_config = /datum/greyscale_config/pda/stripe_thick
	greyscale_colors = "#50E193#E26F41#71A7CA"

/obj/item/pda/roboticist
	name = "roboticist PDA"
	greyscale_config = /datum/greyscale_config/pda/stripe_split
	greyscale_colors = "#484848#0099cc#d33725"
	default_cartridge = /obj/item/cartridge/roboticist

/obj/item/pda/curator
	name = "curator PDA"
	greyscale_config = null
	greyscale_colors = null
	icon_state = "pda-library"
	icon_alert = "pda-r-library"
	default_cartridge = /obj/item/cartridge/curator
	inserted_item = /obj/item/pen/fountain
	desc = "A portable microcomputer by Thinktronic Systems, LTD. This model is a WGW-11 series e-reader."
	note = "Congratulations, your station has chosen the Thinktronic 5290 WGW-11 Series E-reader and Personal Data Assistant!"
	silent = TRUE //Quiet in the library!
	overlays_x_offset = -3

/obj/item/pda/clear
	name = "clear PDA"
	icon_state = "pda-clear"
	greyscale_config = null
	greyscale_colors = null
	desc = "A portable microcomputer by Thinktronic Systems, LTD. This model is a special edition with a transparent case."
	note = "Congratulations, you have chosen the Thinktronic 5230 Personal Data Assistant Deluxe Special Max Turbo Limited Edition!"

/obj/item/pda/cook
	name = "cook PDA"
	greyscale_colors = "#e2e2e2#a92323"

/obj/item/pda/bar
	name = "bartender PDA"
	greyscale_colors = "#333333#c7c7c7"
	inserted_item = /obj/item/pen/fountain

/obj/item/pda/atmos
	name = "atmospherics PDA"
	default_cartridge = /obj/item/cartridge/atmos
	greyscale_config = /datum/greyscale_config/pda/stripe_thick
	greyscale_colors = "#ceca2b#00E5DA#727272"

/obj/item/pda/chemist
	name = "chemist PDA"
	default_cartridge = /obj/item/cartridge/chemistry
	greyscale_config = /datum/greyscale_config/pda/stripe_thick
	greyscale_colors = "#e2e2e2#355FAC#ea6400"

/obj/item/pda/geneticist
	name = "geneticist PDA"
	default_cartridge = /obj/item/cartridge/medical
	greyscale_config = /datum/greyscale_config/pda/stripe_split
	greyscale_colors = "#e2e2e2#000099#0097ca"

repo_name: AnalWerewolf/sundown

license: mit


code:
/obj/item/cartridge
	name = "generic cartridge"
	desc = "A data cartridge for portable microcomputers."
	icon = 'icons/obj/pda.dmi'
	icon_state = "cart"
	inhand_icon_state = "electronic"
	lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
	righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
	w_class = WEIGHT_CLASS_TINY

	var/obj/item/integrated_signaler/radio = null

	var/access = 0 //Bit flags for cartridge access

	var/remote_door_id = ""

	var/bot_access_flags = 0 //Bit flags. Selection: SEC_BOT | MULE_BOT | FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT | VIBE_BOT
	var/spam_enabled = 0 //Enables "Send to All" Option

	var/obj/item/pda/host_pda = null
	var/menu
	var/datum/data/record/active1 = null //General
	var/datum/data/record/active2 = null //Medical
	var/datum/data/record/active3 = null //Security
	var/obj/machinery/computer/monitor/powmonitor = null // Power Monitor
	var/list/powermonitors = list()
	var/message1 // used for status_displays
	var/message2
	var/list/stored_data = list()
	var/current_channel

	var/mob/living/simple_animal/bot/active_bot
	var/list/botlist = list()

/obj/item/cartridge/Initialize(mapload)
	. = ..()
	var/obj/item/pda/pda = loc
	if(istype(pda))
		host_pda = pda

/obj/item/cartridge/engineering
	name = "\improper Power-ON cartridge"
	icon_state = "cart-e"
	access = CART_ENGINE | CART_DRONEPHONE
	bot_access_flags = FLOOR_BOT

/obj/item/cartridge/atmos
	name = "\improper BreatheDeep cartridge"
	icon_state = "cart-a"
	access = CART_ATMOS | CART_DRONEPHONE
	bot_access_flags = FLOOR_BOT | FIRE_BOT

/obj/item/cartridge/medical
	name = "\improper Med-U cartridge"
	icon_state = "cart-m"
	access = CART_MEDICAL
	bot_access_flags = MED_BOT

/obj/item/cartridge/chemistry
	name = "\improper ChemWhiz cartridge"
	icon_state = "cart-chem"
	access = CART_REAGENT_SCANNER
	bot_access_flags = MED_BOT

/obj/item/cartridge/security
	name = "\improper R.O.B.U.S.T. cartridge"
	icon_state = "cart-s"
	access = CART_SECURITY | CART_MANIFEST
	bot_access_flags = SEC_BOT

/obj/item/cartridge/detective
	name = "\improper D.E.T.E.C.T. cartridge"
	icon_state = "cart-s"
	access = CART_SECURITY | CART_MEDICAL | CART_MANIFEST
	bot_access_flags = SEC_BOT

/obj/item/cartridge/janitor
	name = "\improper CustodiPRO cartridge"
	desc = "The ultimate in clean-room design."
	icon_state = "cart-j"
	access = CART_JANITOR | CART_DRONEPHONE
	bot_access_flags = CLEAN_BOT

/obj/item/cartridge/lawyer
	name = "\improper P.R.O.V.E. cartridge"
	icon_state = "cart-s"
	access = CART_SECURITY
	spam_enabled = 1

/obj/item/cartridge/curator
	name = "\improper Lib-Tweet cartridge"
	icon_state = "cart-s"
	access = CART_NEWSCASTER

/obj/item/cartridge/roboticist
	name = "\improper B.O.O.P. Remote Control cartridge"
	desc = "Packed with heavy duty quad-bot interlink!"
	bot_access_flags = FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT | VIBE_BOT
	access = CART_DRONEPHONE

/obj/item/cartridge/signal
	name = "generic signaler cartridge"
	desc = "A data cartridge with an integrated radio signaler module."

/obj/item/cartridge/signal/ordnance
	name = "\improper Signal Ace 2 cartridge"
	desc = "Complete with integrated radio signaler!"
	icon_state = "cart-ord"
	access = CART_REAGENT_SCANNER | CART_ATMOS

/obj/item/cartridge/signal/Initialize(mapload)
	. = ..()
	radio = new(src)

/obj/item/cartridge/quartermaster
	name = "space parts & space vendors cartridge"
	desc = "Perfect for the Quartermaster on the go!"
	icon_state = "cart-q"
	access = CART_QUARTERMASTER
	bot_access_flags = MULE_BOT

/obj/item/cartridge/head
	name = "\improper Easy-Record DELUXE cartridge"
	icon_state = "cart-h"
	access = CART_MANIFEST | CART_STATUS_DISPLAY

/obj/item/cartridge/hop
	name = "\improper HumanResources9001 cartridge"
	icon_state = "cart-h"
	access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_JANITOR | CART_SECURITY | CART_NEWSCASTER | CART_QUARTERMASTER | CART_DRONEPHONE
	bot_access_flags = MULE_BOT | CLEAN_BOT | VIBE_BOT

/obj/item/cartridge/hos
	name = "\improper R.O.B.U.S.T. DELUXE cartridge"
	icon_state = "cart-hos"
	access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_SECURITY
	bot_access_flags = SEC_BOT

/obj/item/cartridge/ce
	name = "\improper Power-On DELUXE cartridge"
	icon_state = "cart-ce"
	access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_ENGINE | CART_ATMOS | CART_DRONEPHONE | CART_DRONEACCESS
	bot_access_flags = FLOOR_BOT | FIRE_BOT

/obj/item/cartridge/cmo
	name = "\improper Med-U DELUXE cartridge"
	icon_state = "cart-cmo"
	access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_REAGENT_SCANNER | CART_MEDICAL
	bot_access_flags = MED_BOT

/obj/item/cartridge/rd
	name = "\improper Signal Ace DELUXE cartridge"
	icon_state = "cart-rd"
	access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_REAGENT_SCANNER | CART_ATMOS | CART_DRONEPHONE
	bot_access_flags = FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT | VIBE_BOT

/obj/item/cartridge/rd/Initialize(mapload)
	. = ..()
	radio = new(src)

/obj/item/cartridge/captain
	name = "\improper Value-PAK cartridge"
	desc = "Now with 350% more value!" //Give the Captain...EVERYTHING! (Except Mime, Clown, and Syndie)
	icon_state = "cart-c"
	access = ~(CART_CLOWN | CART_MIME | CART_REMOTE_DOOR)
	bot_access_flags = SEC_BOT | MULE_BOT | FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT | VIBE_BOT
	spam_enabled = 1

/obj/item/cartridge/captain/Initialize(mapload)
	. = ..()
	radio = new(src)

/obj/item/cartridge/proc/post_status(command, data1, data2)

	var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS)

	if(!frequency)
		return

	var/datum/signal/status_signal = new(list("command" = command))
	switch(command)
		if("message")
			status_signal.data["msg1"] = data1
			status_signal.data["msg2"] = data2
		if("alert")
			status_signal.data["picture_state"] = data1

	frequency.post_signal(src, status_signal)

/obj/item/cartridge/proc/generate_menu(mob/user)
	if(!host_pda)
		return
	switch(host_pda.mode)
		if(40) //signaller
			menu = "
[PDAIMG(signaler)] Remote Signaling System
"

			menu += {"
Send Signal

Frequency:
-
-
[format_frequency(radio.frequency)]
+
+

Code:
-
-
[radio.code]
+
+
"}
		if (41) //crew manifest
			menu = "
[PDAIMG(notes)] Crew Manifest
"
			menu += "[GLOB.data_core.get_manifest_html(monochrome=TRUE)]"

		if (42) //status displays
			menu = "
[PDAIMG(status)] Station Status Display Interlink
"

			menu += "\[ Clear \]
"
			menu += "\[ Shuttle ETA \]
"
			menu += "\[ Message \]"
			menu += "

 Line 1: [ message1 ? message1 : "(none)"]"
			menu += "
 Line 2: [ message2 ? message2 : "(none)"]

"
			menu += "\[ Alert: None |"
			menu += " Red Alert |"
			menu += " Lockdown |"
			menu += " Biohazard \]
"

		if (43)
			menu = "
[PDAIMG(power)] Power Monitors - Please select one

"
			powmonitor = null
			powermonitors = list()
			var/powercount = 0

			var/turf/pda_turf = get_turf(src)
			for(var/obj/machinery/computer/monitor/pMon in GLOB.machines)
				if(pMon.machine_stat & (NOPOWER | BROKEN)) //check to make sure the computer is functional
					continue
				if(pda_turf.z != pMon.z) //and that we're on the same zlevel as the computer (lore: limited signal strength)
					continue
				if(pMon.is_secret_monitor) //make sure it isn't a secret one (ie located on a ruin), allowing people to metagame that the location exists
					continue
				powercount++
				powermonitors += pMon

			if(!powercount)
				menu += span_danger("No connection
")
			else

				menu += ""
				var/count = 0
				for(var/obj/machinery/computer/monitor/pMon in powermonitors)
					count++
					menu += "[pMon] - [get_area_name(pMon, TRUE)]
"

				menu += ""

		if (433)
			menu = "
[PDAIMG(power)] Power Monitor

"
			if(!powmonitor || !powmonitor.get_powernet())
				menu += span_danger("No connection
")
			else
				var/list/L = list()
				var/datum/powernet/connected_powernet = powmonitor.get_powernet()
				for(var/obj/machinery/power/terminal/term in connected_powernet.nodes)
					if(istype(term.master, /obj/machinery/power/apc))
						var/obj/machinery/power/apc/A = term.master
						L += A

				menu += "
Location: [get_area_name(powmonitor, TRUE)]
Total power: [display_power(connected_powernet.viewavail)]
Total load:  [display_power(connected_powernet.viewload)]
"

				menu += ""

				if(L.len > 0)
					menu += "Area                           Eqp./Lgt./Env.  Load   Cell
"

					var/list/S = list(" Off","AOff","  On", " AOn")
					var/list/chg = list("N","C","F")
//Neither copytext nor copytext_char is appropriate here; neither 30 UTF-8 code units nor 30 code points equates to 30 columns of output.
//Some glyphs are very tall or very wide while others are small or even take up no space at all.
//Emojis can take modifiers which are many characters but render as only one glyph.
//A proper solution here (as far as Unicode goes, maybe not ideal as far as markup goes, a table would be better)
//would be to use [A.area.name]
					for(var/obj/machinery/power/apc/A in L)
						menu += copytext_char(add_trailing(A.area.name, 30, " "), 1, 30)
						menu += " [S[A.equipment+1]] [S[A.lighting+1]] [S[A.environ+1]] [add_leading(display_power(A.lastused_total), 6, " ")]  [A.cell ? "[add_leading(round(A.cell.percent()), 3, " ")]% [chg[A.charging+1]]" : "  N/C"]
"

				menu += "
"

		if (44) //medical records //This thing only displays a single screen so it's hard to really get the sub-menu stuff working.
			menu = "
[PDAIMG(medical)] Medical Record List
"
			if(GLOB.data_core.general)
				for(var/datum/data/record/R in sort_record(GLOB.data_core.general))
					menu += "[R.fields["id"]]: [R.fields["name"]]
"
			menu += "
"
		if(441)
			menu = "
[PDAIMG(medical)] Medical Record
"

			if(active1 in GLOB.data_core.general)
				menu += "Name: [active1.fields["name"]] ID: [active1.fields["id"]]
"
				menu += "Gender: [active1.fields["gender"]]
"
				menu += "Age: [active1.fields["age"]]
"
				menu += "Rank: [active1.fields["rank"]]
"
				menu += "Fingerprint: [active1.fields["fingerprint"]]
"
				menu += "Physical Status: [active1.fields["p_stat"]]
"
				menu += "Mental Status: [active1.fields["m_stat"]]
"
			else
				menu += "Record Lost!
"

			menu += "
"

			menu += "
[PDAIMG(medical)] Medical Data
"
			if(active2 in GLOB.data_core.medical)
				menu += "Blood Type: [active2.fields["blood_type"]]

"

				menu += "Minor Disabilities: [active2.fields["mi_dis"]]
"
				menu += "Details: [active2.fields["mi_dis_d"]]

"

				menu += "Major Disabilities: [active2.fields["ma_dis"]]
"
				menu += "Details: [active2.fields["ma_dis_d"]]

"

				menu += "Allergies: [active2.fields["alg"]]
"
				menu += "Details: [active2.fields["alg_d"]]

"

				menu += "Current Diseases: [active2.fields["cdi"]]
"
				menu += "Details: [active2.fields["cdi_d"]]

"

				menu += "Important Notes: [active2.fields["notes"]]
"
			else
				menu += "Record Lost!
"

			menu += "
"
		if (45) //security records
			menu = "
[PDAIMG(cuffs)] Security Record List
"
			if(GLOB.data_core.general)
				for (var/datum/data/record/R in sort_record(GLOB.data_core.general))
					menu += "[R.fields["id"]]: [R.fields["name"]]
"

			menu += "
"
		if(451)
			menu = "
[PDAIMG(cuffs)] Security Record
"

			if(active1 in GLOB.data_core.general)
				menu += "Name: [active1.fields["name"]] ID: [active1.fields["id"]]
"
				menu += "Gender: [active1.fields["gender"]]
"
				menu += "Age: [active1.fields["age"]]
"
				menu += "Rank: [active1.fields["rank"]]
"
				menu += "Fingerprint: [active1.fields["fingerprint"]]
"
				menu += "Physical Status: [active1.fields["p_stat"]]
"
				menu += "Mental Status: [active1.fields["m_stat"]]
"
			else
				menu += "Record Lost!
"

			menu += "
"

			menu += "
[PDAIMG(cuffs)] Security Data
"
			if(active3 in GLOB.data_core.security)
				menu += "Criminal Status: [active3.fields["criminal"]]
"

				menu += text("
\nCrimes:")

				menu +={"

Crime
Details
Author
Time Added

"}
				for(var/datum/data/crime/c in active3.fields["crim"])
					menu += "
[c.crimeName]	"
					menu += "[c.crimeDetails]	"
					menu += "[c.author]	"
					menu += "[c.time]	"
					menu += "
"
				menu += "
"
				menu += "
\nImportant Notes:
"
				menu += "[active3.fields["notes"]]"
			else
				menu += "Record Lost!
"

			menu += "
"

		if (47) //quartermaster order records
			menu = "
[PDAIMG(crate)] Supply Record Interlink
"

			menu += "
Supply shuttle
"
			menu += "Location: "
			switch(SSshuttle.supply.mode)
				if(SHUTTLE_CALL)
					menu += "Moving to "
					if(!is_station_level(SSshuttle.supply.z))
						menu += "station"
					else
						menu += "CentCom"
					menu += " ([SSshuttle.supply.timeLeft(600)] Mins)"
				else
					menu += "At "
					if(!is_station_level(SSshuttle.supply.z))
						menu += "CentCom"
					else
						menu += "station"
			menu += "
Current approved orders:

"
			for(var/S in SSshuttle.shoppinglist)
				var/datum/supply_order/SO = S
				menu += "
#[SO.id] - [SO.pack.name] approved by [SO.orderer] [SO.reason ? "([SO.reason])":""]
"
			menu += "
"

			menu += "Current requests:

"
			for(var/S in SSshuttle.requestlist)
				var/datum/supply_order/SO = S
				menu += "
#[SO.id] - [SO.pack.name] requested by [SO.orderer]
"
			menu += "
Upgrade NOW to Space Parts & Space Vendors PLUS for full remote order control and inventory management."

		if (48) // quartermaster ore logs
			menu = list("
[PDAIMG(crate)] Ore Silo Logs
")
			if (GLOB.ore_silo_default)
				var/list/logs = GLOB.silo_access_logs[REF(GLOB.ore_silo_default)]
				var/len = LAZYLEN(logs)
				var/i = 0
				for(var/M in logs)
					if (++i > 30)
						menu += "(... older logs not shown ...)"
						break
					var/datum/ore_silo_log/entry = M
					menu += "[len - i]. [entry.formatted]

"
				if(i == 0)
					menu += "Nothing!"
			else
				menu += "No ore silo detected!"
			menu = jointext(menu, "")

		if (49) //janitorial locator
			menu = "
[PDAIMG(bucket)] Persistent Custodial Object Locator
"

			var/turf/cl = get_turf(src)
			if (cl)
				menu += "Current Orbital Location: \[[cl.x],[cl.y]\]"

				menu += "
Located Mops:
"

				var/ldat
				for (var/obj/item/mop/M in world)
					var/turf/ml = get_turf(M)

					if(ml)
						if (ml.z != cl.z)
							continue
						var/direction = get_dir(src, M)
						ldat += "Mop - \[[ml.x],[ml.y] ([uppertext(dir2text(direction))])\] - [M.reagents.total_volume ? "Wet" : "Dry"]
"

				if (!ldat)
					menu += "None"
				else
					menu += "[ldat]"

				menu += "
Located Janitorial Cart:
"

				ldat = null
				for (var/obj/structure/janitorialcart/B in world)
					var/turf/bl = get_turf(B)

					if(bl)
						if (bl.z != cl.z)
							continue
						var/direction = get_dir(src, B)
						ldat += "Cart - \[[bl.x],[bl.y] ([uppertext(dir2text(direction))])\] - Water level: [B.reagents.total_volume]/100
"

				if (!ldat)
					menu += "None"
				else
					menu += "[ldat]"

				menu += "
Located Cleanbots:
"

				ldat = null
				for (var/mob/living/simple_animal/bot/cleanbot/B in GLOB.alive_mob_list)
					var/turf/bl = get_turf(B)

					if(bl)
						if (bl.z != cl.z)
							continue
						var/direction = get_dir(src, B)
						ldat += "Cleanbot - \[[bl.x],[bl.y] ([uppertext(dir2text(direction))])\] - [B.on ? "Online" : "Offline"]
"

				if (!ldat)
					menu += "None"
				else
					menu += "[ldat]"

			else
				menu += "ERROR: Unable to determine current location."
			menu += "

Refresh GPS Locator"

		if (53) // Newscaster
			menu = "
[PDAIMG(notes)] Newscaster Access
"
			menu += "
 Current Newsfeed: [current_channel ? current_channel : "None"]
"
			var/datum/newscaster/feed_channel/current
			for(var/datum/newscaster/feed_channel/chan in GLOB.news_network.network_channels)
				if (chan.channel_name == current_channel)
					current = chan
			if(!current)
				menu += "
 ERROR : NO CHANNEL FOUND
"
				return menu
			var/i = 1
			for(var/datum/newscaster/feed_message/msg in current.messages)
				menu +="-[msg.returnBody(-1)]
\[Story by [msg.returnAuthor(-1)]\]
"
				menu +="[msg.comments.len] comment[msg.comments.len > 1 ? "s" : ""]
"
				if(msg.img)
					user << browse_rsc(msg.img, "tmp_photo[i].png")
					menu +="
"
				i++
				for(var/datum/newscaster/feed_comment/comment in msg.comments)
					menu +="[comment.body]
[comment.author] [comment.time_stamp]
"
			menu += "
 Post Message"

		if (54) // Beepsky, Medibot, Floorbot, and Cleanbot access
			menu = "
[PDAIMG(medbot)] Bots Interlink
"
			bot_control()
		if (55) // Emoji Guidebook for mimes
			menu = "
[PDAIMG(emoji)] Emoji Guidebook
"
			var/static/list/emoji_icon_states
			var/static/emoji_table
			if(!emoji_table)
				var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat)
				var/list/collate = list("

")
				for(var/emoji in sort_list(icon_states(icon(EMOJI_SET))))
					var/tag = sheet.icon_tag("emoji-[emoji]")
					collate += "
[emoji]	[tag]
"
				collate += "

"
				emoji_table = collate.Join()

			menu += "
 To use an emoji in a pda message, refer to the guide and add \":\" around the emoji. Your PDA supports the following emoji:
"
			menu += emoji_table

		if (99) //Newscaster message permission error
			menu = "
 ERROR : NOT AUTHORIZED [host_pda.id ? "" : "- ID SLOT EMPTY"]
"

	return menu

/obj/item/cartridge/Topic(href, href_list)
	..()

	if(!usr.canUseTopic(src, !issilicon(usr)))
		usr.unset_machine()
		usr << browse(null, "window=pda")
		return

	switch(href_list["choice"])
		if("Medical Records")
			active1 = find_record("id", href_list["target"], GLOB.data_core.general)
			if(active1)
				active2 = find_record("id", href_list["target"], GLOB.data_core.medical)
			host_pda.mode = 441
			if(!active2)
				active1 = null

		if("Security Records")
			active1 = find_record("id", href_list["target"], GLOB.data_core.general)
			if(active1)
				active3 = find_record("id", href_list["target"], GLOB.data_core.security)
			host_pda.mode = 451
			if(!active3)
				active1 = null

		if("Send Signal")
			INVOKE_ASYNC(radio, /obj/item/integrated_signaler.proc/send_activation)

		if("Signal Frequency")
			var/new_frequency = sanitize_frequency(radio.frequency + text2num(href_list["sfreq"]))
			radio.set_frequency(new_frequency)

		if("Signal Code")
			radio.code += text2num(href_list["scode"])
			radio.code = round(radio.code)
			radio.code = min(100, radio.code)
			radio.code = max(1, radio.code)

		if("Status")
			switch(href_list["statdisp"])
				if("message")
					post_status("message", message1, message2)
				if("alert")
					post_status("alert", href_list["alert"])
				if("setmsg1")
					message1 = reject_bad_text(input("Line 1", "Enter Message Text", message1) as text|null, 40)
					updateSelfDialog()
				if("setmsg2")
					message2 = reject_bad_text(input("Line 2", "Enter Message Text", message2) as text|null, 40)
					updateSelfDialog()
				else
					post_status(href_list["statdisp"])
		if("Power Select")
			var/pnum = text2num(href_list["target"])
			powmonitor = powermonitors[pnum]
			host_pda.mode = 433

		if("Supply Orders")
			host_pda.mode =47

		if("Newscaster Access")
			host_pda.mode = 53

		if("Newscaster Message")
			var/host_pda_owner_name = host_pda.id ? "[host_pda.id.registered_name] ([host_pda.id.assignment])" : "Unknown"
			var/message = host_pda.msg_input()
			var/datum/newscaster/feed_channel/current
			for(var/datum/newscaster/feed_channel/chan in GLOB.news_network.network_channels)
				if (chan.channel_name == current_channel)
					current = chan
			if(current.locked && current.author != host_pda_owner_name)
				host_pda.mode = 99
				host_pda.Topic(null,list("choice"="Refresh"))
				return
			GLOB.news_network.SubmitArticle(message,host_pda.owner,current_channel)
			host_pda.Topic(null,list("choice"=num2text(host_pda.mode)))
			return

		if("Newscaster Switch Channel")
			current_channel = host_pda.msg_input()
			host_pda.Topic(null,list("choice"=num2text(host_pda.mode)))
			return

	//emoji previews
	if(href_list["emoji"])
		var/parse = emoji_parse(":[href_list["emoji"]]:")
		to_chat(usr, parse)

	//Bot control section! Viciously ripped from radios for being laggy and terrible.
	if(href_list["op"])
		switch(href_list["op"])

			if("control")
				active_bot = locate(href_list["bot"]) in GLOB.bots_list

			if("botlist")
				active_bot = null
			if("summon") //Args are in the correct order, they are stated here just as an easy reminder.
				active_bot.bot_control("summon", usr, host_pda.GetAccess())
			else //Forward all other bot commands to the bot itself!
				active_bot.bot_control(href_list["op"], usr)

	if(href_list["mule"]) //MULEbots are special snowflakes, and need different args due to how they work.
		var/mob/living/simple_animal/bot/mulebot/mule = active_bot
		if (istype(mule))
			mule.bot_control(href_list["mule"], usr, pda=TRUE)

	if(!host_pda)
		return
	host_pda.attack_self(usr)

/obj/item/cartridge/proc/bot_control()
	if(active_bot)
		menu += "[active_bot]
 Status: ([PDAIMG(refresh)]refresh)
"
		menu += "Model: [active_bot.model]
"
		menu += "Location: [get_area(active_bot)]
"
		menu += "Mode: [active_bot.get_mode()]"
		if(active_bot.allow_pai)
			menu += "
pAI: "
			if(active_bot.paicard && active_bot.paicard.pai)
				menu += "[active_bot.paicard.pai.name]"
				if(active_bot.bot_core.allowed(usr))
					menu += " (eject)"
			else
				menu += "none"

		//MULEs!
		if(active_bot.bot_type == MULE_BOT)
			var/mob/living/simple_animal/bot/mulebot/MULE = active_bot
			var/atom/Load = MULE.load
			menu += "
Current Load: [ !Load ? "none" : "[Load.name] (unload)" ]
"
			menu += "Destination: [MULE.destination ? MULE.destination : "None"] (set)
"
			menu += "Set ID: [MULE.suffix]  Modify
"
			menu += "Power: [MULE.cell ? MULE.cell.percent() : 0]%
"
			menu += "Home: [!MULE.home_destination ? "none" : MULE.home_destination ]
"
			menu += "Delivery Reporting: [MULE.report_delivery ? "(On)": "(Off)"]
"
			menu += "Auto Return Home: [MULE.auto_return ? "(On)": "(Off)"]
"
			menu += "Auto Pickup Crate: [MULE.auto_pickup ? "(On)": "(Off)"]

" //Hue.

			menu += "\[Stop\] "
			menu += "\[Proceed\] "
			menu += "\[Return Home\]
"

		else
			menu += "
\[Stop Patrol\] " //patrolon
			menu += "\[Start Patrol\] " //patroloff
			menu += "\[Summon Bot\]
" //summon
			menu += "Keep an ID inserted to upload access codes upon summoning."

		menu += "
[PDAIMG(back)]Return to bot list"
	else
		menu += "
[PDAIMG(refresh)]Scan for active bots

"
		var/turf/current_turf = get_turf(src)
		var/zlevel = current_turf.z
		var/botcount = 0
		for(var/B in GLOB.bots_list) //Git da botz
			var/mob/living/simple_animal/bot/Bot = B
			if(!Bot.on || Bot.z != zlevel || Bot.remote_disabled || !(bot_access_flags & Bot.bot_type)) //Only non-emagged bots on the same Z-level are detected!
				continue //Also, the PDA must have access to the bot type.
			menu += "[Bot.name] ([Bot.get_mode()])
"
			botcount++
		if(!botcount) //No bots at all? Lame.
			menu += "No bots found.
"
			return

	return menu

//If the cartridge adds a special line to the top of the messaging app
/obj/item/cartridge/proc/message_header()
	return ""

//If the cartridge adds something to each potetial messaging target
/obj/item/cartridge/proc/message_special(obj/item/pda/target)
	return ""

//This is called for special abilities of cartridges
/obj/item/cartridge/proc/special(mob/living/user, list/params)


code:
// Radio Cartridge, essentially a remote signaler with limited spectrum.
/obj/item/integrated_signaler
	name = "\improper PDA radio module"
	desc = "An electronic radio system of Nanotrasen origin."
	icon = 'icons/obj/module.dmi'
	icon_state = "power_mod"

/obj/item/integrated_signaler
	var/frequency = FREQ_SIGNALER
	var/code = DEFAULT_SIGNALER_CODE
	var/last_transmission
	var/datum/radio_frequency/radio_connection

/obj/item/integrated_signaler/Destroy()
	radio_connection = null
	return ..()

/obj/item/integrated_signaler/Initialize(mapload)
	. = ..()
	if (frequency < MIN_FREE_FREQ || frequency > MAX_FREE_FREQ)
		frequency = sanitize_frequency(frequency)
	set_frequency(frequency)

/obj/item/integrated_signaler/proc/set_frequency(new_frequency)
	frequency = new_frequency
	radio_connection = SSradio.return_frequency(frequency)

/obj/item/integrated_signaler/proc/send_activation()
	if(last_transmission && world.time < (last_transmission + 5))
		return
	last_transmission = world.time

	var/time = time2text(world.realtime,"hh:mm:ss")
	var/turf/T = get_turf(src)
	GLOB.lastsignalers.Add("[time] : [usr.key] used [src] @ location [AREACOORD(T)] : [format_frequency(frequency)]/[code]")

	var/datum/signal/signal = new(list("code" = code))
	radio_connection.post_signal(src, signal, filter = RADIO_SIGNALER)


code:
/obj/item/cartridge/virus
	name = "Generic Virus PDA cart"
	var/charges = 5

/obj/item/cartridge/virus/proc/send_virus(obj/item/pda/target, mob/living/U)
	return

/obj/item/cartridge/virus/message_header()
	return "[charges] viral files left.
"

/obj/item/cartridge/virus/message_special(obj/item/pda/target)
	if (!istype(loc, /obj/item/pda))
		return ""  //Sanity check, this shouldn't be possible.
	return " (*Send Virus*)"

/obj/item/cartridge/virus/special(mob/living/user, list/params)
	var/obj/item/pda/P = locate(params["target"]) in GLOB.PDAs  //Leaving it alone in case it may do something useful, I guess.
	send_virus(P,user)

/obj/item/cartridge/virus/clown
	name = "\improper Honkworks 5.0 cartridge"
	icon_state = "cart-clown"
	desc = "A data cartridge for portable microcomputers. It smells vaguely of bananas."
	access = CART_CLOWN

/obj/item/cartridge/virus/clown/send_virus(obj/item/pda/target, mob/living/U)
	if(charges <= 0)
		to_chat(U, span_notice("Out of charges."))
		return
	if(!isnull(target) && !target.toff)
		charges--
		to_chat(U, span_notice("Virus Sent!"))
		target.honkamt = (rand(15,20))
	else
		to_chat(U, span_alert("PDA not found."))

/obj/item/cartridge/virus/mime
	name = "\improper Gestur-O 1000 cartridge"
	icon_state = "cart-mi"
	access = CART_MIME

/obj/item/cartridge/virus/mime/send_virus(obj/item/pda/target, mob/living/U)
	if(charges <= 0)
		to_chat(U, span_alert("Out of charges."))
		return
	if(!isnull(target) && !target.toff)
		charges--
		to_chat(U, span_notice("Virus Sent!"))
		target.silent = TRUE
		target.ttone = "silence"
	else
		to_chat(U, span_alert("PDA not found."))

/obj/item/cartridge/virus/syndicate
	name = "\improper Detomatix cartridge"
	icon_state = "cart"
	access = CART_REMOTE_DOOR
	remote_door_id = "smindicate" //Make sure this matches the syndicate shuttle's shield/door id!! //don't ask about the name, testing.
	charges = 4

/obj/item/cartridge/virus/syndicate/send_virus(obj/item/pda/target, mob/living/U)
	if(charges <= 0)
		to_chat(U, span_notice("Out of charges."))
		return
	if(!isnull(target) && !target.toff)
		charges--
		var/difficulty = 0
		if(target.cartridge)
			difficulty += bit_count(target.cartridge.access&(CART_MEDICAL | CART_SECURITY | CART_ENGINE | CART_CLOWN | CART_JANITOR | CART_MANIFEST))
			if(target.cartridge.access & CART_MANIFEST)
				difficulty++ //if cartridge has manifest access it has extra snowflake difficulty
		if(SEND_SIGNAL(target, COMSIG_PDA_CHECK_DETONATE) & COMPONENT_PDA_NO_DETONATE || prob(difficulty * 15))
			U.show_message(span_danger("An error flashes on your [src]."), MSG_VISUAL)
		else
			log_bomber(U, "triggered a PDA explosion on", target, "[!is_special_character(U) ? "(TRIGGED BY NON-ANTAG)" : ""]")
			U.show_message(span_notice("Success!"), MSG_VISUAL)
			target.explode()
	else
		to_chat(U, span_alert("PDA not found."))

/obj/item/cartridge/virus/frame
	name = "\improper F.R.A.M.E. cartridge"
	icon_state = "cart"
	var/telecrystals = 0

/obj/item/cartridge/virus/frame/send_virus(obj/item/pda/target, mob/living/U)
	if(charges <= 0)
		to_chat(U, span_alert("Out of charges."))
		return
	if(!isnull(target) && !target.toff)
		charges--
		var/lock_code = "[rand(100,999)] [pick(GLOB.phonetic_alphabet)]"
		to_chat(U, span_notice("Virus Sent! The unlock code to the target is: [lock_code]"))
		var/datum/component/uplink/hidden_uplink = target.GetComponent(/datum/component/uplink)
		if(!hidden_uplink)
			hidden_uplink = target.AddComponent(/datum/component/uplink)
			hidden_uplink.unlock_code = lock_code
		else
			hidden_uplink.hidden_crystals += hidden_uplink.telecrystals //Temporarially hide the PDA's crystals, so you can't steal telecrystals.
		hidden_uplink.telecrystals = telecrystals
		telecrystals = 0
		hidden_uplink.locked = FALSE
		hidden_uplink.active = TRUE
	else
		to_chat(U, span_alert("PDA not found."))

/obj/item/cartridge/virus/frame/attackby(obj/item/I, mob/user, params)
	. = ..()
	if(istype(I, /obj/item/stack/telecrystal))
		if(!charges)
			to_chat(user, span_notice("[src] is out of charges, it's refusing to accept [I]."))
			return
		var/obj/item/stack/telecrystal/telecrystalStack = I
		telecrystals += telecrystalStack.amount
		to_chat(user, span_notice("You slot [telecrystalStack] into [src]. The next time it's used, it will also give telecrystals."))
		telecrystalStack.use(telecrystalStack.amount)


code:
/obj/item/aicard
	name = "intelliCard"
	desc = "A storage device for AIs. Patent pending."
	icon = 'icons/obj/aicards.dmi'
	icon_state = "aicard" // aicard-full
	inhand_icon_state = "electronic"
	worn_icon_state = "electronic"
	lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
	righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
	w_class = WEIGHT_CLASS_SMALL
	slot_flags = ITEM_SLOT_BELT
	item_flags = NOBLUDGEON
	var/flush = FALSE
	var/mob/living/silicon/ai/AI

/obj/item/aicard/aitater
	name = "intelliTater"
	desc = "A stylish upgrade (?) to the intelliCard."
	icon_state = "aitater"

/obj/item/aicard/aispook
	name = "intelliLantern"
	desc = "A spoOoOoky upgrade to the intelliCard."
	icon_state = "aispook"

/obj/item/aicard/suicide_act(mob/living/user)
	user.visible_message(span_suicide("[user] is trying to upload [user.p_them()]self into [src]! That's not going to work out well!"))
	return BRUTELOSS

/obj/item/aicard/afterattack(atom/target, mob/user, proximity)
	. = ..()
	if(!proximity || !target)
		return
	if(AI) //AI is on the card, implies user wants to upload it.
		log_combat(user, AI, "uploaded", src, "to [target].")
		target.transfer_ai(AI_TRANS_FROM_CARD, user, AI, src)
	else //No AI on the card, therefore the user wants to download one.
		target.transfer_ai(AI_TRANS_TO_CARD, user, null, src)
		if(AI)
			log_combat(user, AI, "carded", src)
	update_appearance() //Whatever happened, update the card's state (icon, name) to match.

/obj/item/aicard/update_icon_state()
	if(!AI)
		name = initial(name)
		icon_state = initial(icon_state)
		return ..()
	name = "[initial(name)] - [AI.name]"
	icon_state = "[initial(icon_state)][AI.stat == DEAD ? "-404" : "-full"]"
	AI.cancel_camera()
	return ..()

/obj/item/aicard/update_overlays()
	. = ..()
	if(!AI?.control_disabled)
		return
	. += "[initial(icon_state)]-on"

/obj/item/aicard/ui_state(mob/user)
	return GLOB.hands_state

/obj/item/aicard/ui_interact(mob/user, datum/tgui/ui)
	ui = SStgui.try_update_ui(user, src, ui)
	if(!ui)
		ui = new(user, src, "Intellicard", name)
		ui.open()

/obj/item/aicard/ui_data()
	var/list/data = list()
	if(AI)
		data["name"] = AI.name
		data["laws"] = AI.laws.get_law_list(include_zeroth = TRUE, render_html = FALSE)
		data["health"] = (AI.health + 100) / 2
		data["wireless"] = !AI.control_disabled //todo disabled->enabled
		data["radio"] = AI.radio_enabled
		data["isDead"] = AI.stat == DEAD
		data["isBraindead"] = AI.client ? FALSE : TRUE
	data["wiping"] = flush
	return data

/obj/item/aicard/ui_act(action,params)
	. = ..()
	if(.)
		return
	switch(action)
		if("wipe")
			if(flush)
				flush = FALSE
			else
				var/confirm = tgui_alert(usr, "Are you sure you want to wipe this card's memory?", name, list("Yes", "No"))
				if(confirm == "Yes" && !..())
					flush = TRUE
					if(AI && AI.loc == src)
						to_chat(AI, span_userdanger("Your core files are being wiped!"))
						while(AI.stat != DEAD && flush)
							AI.adjustOxyLoss(5)
							AI.updatehealth()
							sleep(5)
						flush = FALSE
			. = TRUE
		if("wireless")
			AI.control_disabled = !AI.control_disabled
			if(!AI.control_disabled)
				AI.interaction_range = null
			else
				AI.interaction_range = 0
			to_chat(AI, span_warning("[src]'s wireless port has been [AI.control_disabled ? "disabled" : "enabled"]!"))
			. = TRUE
		if("radio")
			AI.radio_enabled = !AI.radio_enabled
			to_chat(AI, span_warning("Your Subspace Transceiver has been [AI.radio_enabled ? "enabled" : "disabled"]!"))
			. = TRUE
	update_appearance()


code:
/obj/item/anomaly_neutralizer
	name = "anomaly neutralizer"
	desc = "A one-use device capable of instantly neutralizing anomalies."
	icon = 'icons/obj/device.dmi'
	icon_state = "memorizer2"
	inhand_icon_state = "electronic"
	worn_icon_state = "electronic"
	lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
	righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
	w_class = WEIGHT_CLASS_SMALL
	slot_flags = ITEM_SLOT_BELT
	item_flags = NOBLUDGEON

/obj/item/anomaly_neutralizer/afterattack(atom/target, mob/user, proximity)
	..()
	if(!proximity || !target)
		return
	if(istype(target, /obj/effect/anomaly))
		var/obj/effect/anomaly/A = target
		to_chat(user, span_notice("The circuitry of [src] fries from the strain of neutralizing [A]!"))
		A.anomalyNeutralize()
		qdel(src)


code:
/obj/item/beacon
	name = "\improper tracking beacon"
	desc = "A beacon used by a teleporter."
	icon = 'icons/obj/device.dmi'
	icon_state = "beacon"
	inhand_icon_state = "beacon"
	lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
	righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
	var/enabled = TRUE
	var/renamed = FALSE

/obj/item/beacon/Initialize(mapload)
	. = ..()
	if (enabled)
		GLOB.teleportbeacons += src
	else
		icon_state = "beacon-off"

/obj/item/beacon/Destroy()
	GLOB.teleportbeacons -= src
	return ..()

/obj/item/beacon/attack_self(mob/user)
	enabled = !enabled
	if (enabled)
		icon_state = "beacon"
		GLOB.teleportbeacons += src
	else
		icon_state = "beacon-off"
		GLOB.teleportbeacons -= src
	to_chat(user, span_notice("You [enabled ? "enable" : "disable"] the beacon."))
	return

/obj/item/beacon/attackby(obj/item/W, mob/user)
	if(istype(W, /obj/item/pen)) // needed for things that use custom names like the locator
		var/new_name = stripped_input(user, "What would you like the name to be?")
		if(!user.canUseTopic(src, BE_CLOSE))
			return
		if(new_name)
			name = new_name
			renamed = TRUE
		return
	else
		return ..()


code:
#define BUGMODE_LIST 0
#define BUGMODE_MONITOR 1
#define BUGMODE_TRACK 2

/obj/item/camera_bug
	name = "camera bug"
	desc = "For illicit snooping through the camera network."
	icon = 'icons/obj/device.dmi'
	icon_state = "camera_bug"
	w_class = WEIGHT_CLASS_TINY
	inhand_icon_state = "camera_bug"
	throw_speed = 4
	throw_range = 20
	item_flags = NOBLUDGEON

	var/obj/machinery/camera/current = null

	var/last_net_update = 0
	var/list/bugged_cameras = list()

	var/track_mode = BUGMODE_LIST
	var/last_tracked = 0
	var/refresh_interval = 50

	var/tracked_name = null
	var/atom/tracking = null

	var/last_found = null
	var/last_seen = null

/obj/item/camera_bug/Initialize(mapload)
	. = ..()
	START_PROCESSING(SSobj, src)

/obj/item/camera_bug/Destroy()
	STOP_PROCESSING(SSobj, src)
	get_cameras()
	for(var/cam_tag in bugged_cameras)
		var/datum/weakref/camera_ref = bugged_cameras[cam_tag]
		var/obj/machinery/camera/camera = camera_ref.resolve()
		if(camera && camera.bug == src)
			camera.bug = null
	bugged_cameras = list()
	if(tracking)
		tracking = null
	return ..()

/obj/item/camera_bug/interact(mob/user)
	ui_interact(user)

/obj/item/camera_bug/ui_interact(mob/user = usr)
	. = ..()
	var/datum/browser/popup = new(user, "camerabug","Camera Bug",nref=src)
	popup.set_content(menu(get_cameras()))
	popup.open()

/obj/item/camera_bug/attack_self(mob/user)
	user.set_machine(src)
	interact(user)

/obj/item/camera_bug/check_eye(mob/user)
	if ( loc != user || user.incapacitated() || user.is_blind() || !current )
		user.unset_machine()
		return FALSE
	var/turf/T_user = get_turf(user.loc)
	var/turf/T_current = get_turf(current)
	if(T_user.z != T_current.z || !current.can_use())
		to_chat(user, span_danger("[src] has lost the signal."))
		current = null
		user.unset_machine()
		return FALSE
	return TRUE
/obj/item/camera_bug/on_unset_machine(mob/user)
	user.reset_perspective(null)

/obj/item/camera_bug/proc/get_cameras()
	if( world.time > (last_net_update + 100))
		bugged_cameras = list()
		for(var/obj/machinery/camera/camera in GLOB.cameranet.cameras)
			if(camera.machine_stat || !camera.can_use())
				continue
			if(length(list("ss13","mine", "rd", "labor", "ordnance", "minisat") & camera.network))
				var/datum/weakref/camera_ref = WEAKREF(camera)
				if(!camera_ref || !camera.c_tag)
					continue
				bugged_cameras[camera.c_tag] = camera_ref
	return sort_list(bugged_cameras)

/obj/item/camera_bug/proc/menu(list/cameras)
	if(!cameras || !cameras.len)
		return "No bugged cameras found."

	var/html
	switch(track_mode)
		if(BUGMODE_LIST)
			html = "
Select a camera:
 \[Cancel camera view\]

"
			for(var/entry in cameras)
				var/datum/weakref/camera_ref = cameras[entry]
				var/obj/machinery/camera/camera = camera_ref.resolve()
				if(!camera)
					cameras -= camera_ref
					continue
				var/functions = ""
				if(camera.bug == src)
					functions = " - \[Monitor\] \[Disable\]"
				else
					functions = " - \[Monitor\]"
				html += "
[entry]	[functions]
"

		if(BUGMODE_MONITOR)
			if(current)
				html = "Analyzing Camera '[current.c_tag]' \[Select Camera\]
"
				html += camera_report()
			else
				track_mode = BUGMODE_LIST
				return .(cameras)
		if(BUGMODE_TRACK)
			if(tracking)
				html = "Tracking '[tracked_name]'  \[Cancel Tracking\]  \[Cancel camera view\]
"
				if(last_found)
					var/time_diff = round((world.time - last_seen) / 150)
					var/datum/weakref/camera_ref = bugged_cameras[last_found]
					var/obj/machinery/camera/camera = camera_ref.resolve()
					var/outstring
					if(camera)
						outstring = "[last_found]"
					else
						outstring = last_found
					if(!time_diff)
						html += "Last seen near [outstring] (now)
"
					else
						// 15 second intervals ~ 1/4 minute
						var/m = round(time_diff/4)
						var/s = (time_diff - 4*m) * 15
						if(!s)
							s = "00"
						html += "Last seen near [outstring] ([m]:[s] minute\s ago)
"
					if(camera && (camera.bug == src)) //Checks to see if the camera has a bug
						html += "\[Disable\]"

				else
					html += "Not yet seen."
			else
				track_mode = BUGMODE_LIST
				return .(cameras)
	return html

/obj/item/camera_bug/proc/get_seens()
	if(current?.can_use())
		var/list/seen = current.can_see()
		return seen

/obj/item/camera_bug/proc/camera_report()
	// this should only be called if current exists
	var/dat = ""
	var/list/seen = get_seens()
	if(seen && seen.len >= 1)
		var/list/names = list()
		for(var/obj/singularity/S in seen) // god help you if you see more than one
			if(S.name in names)
				names[S.name]++
				dat += "[S.name] ([names[S.name]])"
			else
				names[S.name] = 1
				dat += "[S.name]"
			var/stage = round(S.current_size / 2)+1
			dat += " (Stage [stage])"
			dat += " \[Track\]
"

		for(var/obj/vehicle/sealed/mecha/M in seen)
			if(M.name in names)
				names[M.name]++
				dat += "[M.name] ([names[M.name]])"
			else
				names[M.name] = 1
				dat += "[M.name]"
			dat += " \[Track\]
"

		for(var/mob/living/M in seen)
			if(M.name in names)
				names[M.name]++
				dat += "[M.name] ([names[M.name]])"
			else
				names[M.name] = 1
				dat += "[M.name]"
			if(M.body_position == LYING_DOWN)
				if(M.buckled)
					dat += " (Sitting)"
				else
					dat += " (Laying down)"
			dat += " \[Track\]
"
		if(length(dat) == 0)
			dat += "No motion detected."
		return dat
	else
		return "Camera Offline
"

/obj/item/camera_bug/Topic(href,list/href_list)
	if(usr != loc)
		usr.unset_machine()
		usr << browse(null, "window=camerabug")
		return
	usr.set_machine(src)
	if("mode" in href_list)
		track_mode = text2num(href_list["mode"])
	if("monitor" in href_list)
		//You can't locate on a list with keys
		var/list/cameras = flatten_list(bugged_cameras)
		var/datum/weakref/camera_ref = locate(href_list["monitor"]) in cameras
		var/obj/machinery/camera/camera = camera_ref.resolve()
		if(camera && istype(camera))
			if(!same_z_level(camera))
				return
			track_mode = BUGMODE_MONITOR
			current = camera
			usr.reset_perspective(null)
			interact()
	if("track" in href_list)
		var/list/seen = get_seens()
		if(seen && seen.len >= 1)
			var/atom/A = locate(href_list["track"]) in seen
			if(A && istype(A))
				tracking = A
				tracked_name = A.name
				last_found = current.c_tag
				last_seen = world.time
				track_mode = BUGMODE_TRACK
	if("emp" in href_list)
		//You can't locate on a list with keys
		var/list/cameras = flatten_list(bugged_cameras)
		var/datum/weakref/camera_ref = locate(href_list["emp"]) in cameras
		var/obj/machinery/camera/camera = camera_ref.resolve()
		if(camera && istype(camera) && camera.bug == src)
			if(!same_z_level(camera))
				return
			camera.emp_act(EMP_HEAVY)
			camera.bug = null
			bugged_cameras -= camera.c_tag
		interact()
		return
	if("close" in href_list)
		usr.unset_machine()
		current = null
		return
	if("view" in href_list)
		//You can't locate on a list with keys
		var/list/cameras = flatten_list(bugged_cameras)
		var/datum/weakref/camera_ref = locate(href_list["view"]) in cameras
		var/obj/machinery/camera/camera = camera_ref.resolve()
		if(camera && istype(camera))
			if(!same_z_level(camera))
				return
			if(!camera.can_use())
				to_chat(usr, span_warning("Something's wrong with that camera! You can't get a feed."))
				return
			current = camera
			spawn(6)
				if(src.check_eye(usr))
					usr.reset_perspective(camera)
					interact()
				else
					usr.unset_machine()
					usr << browse(null, "window=camerabug")
			return
		else
			usr.unset_machine()

	interact()

/obj/item/camera_bug/process()
	if(track_mode == BUGMODE_LIST || (world.time < (last_tracked + refresh_interval)))
		return
	last_tracked = world.time
	if(track_mode == BUGMODE_TRACK ) // search for user
		// Note that it will be tricked if your name appears to change.
		// This is not optimal but it is better than tracking you relentlessly despite everything.
		if(!tracking)
			src.updateSelfDialog()
			return

		if(tracking.name != tracked_name) // Hiding their identity, tricksy
			var/mob/M = tracking
			if(istype(M))
				if(!(tracked_name == "Unknown" && findtext(tracking.name,"Unknown"))) // we saw then disguised before
					if(!(tracked_name == M.real_name && findtext(tracking.name,M.real_name))) // or they're still ID'd
						src.updateSelfDialog()//But if it's neither of those cases
						return // you won't find em on the cameras
			else
				src.updateSelfDialog()
				return

		var/list/tracking_cams = list()
		var/list/b_cams = get_cameras()
		for(var/entry in b_cams)
			tracking_cams += b_cams[entry]
		var/list/target_region = view(tracking)

		for(var/obj/machinery/camera/C in (target_region & tracking_cams))
			if(!can_see(C,tracking)) // target may have xray, that doesn't make them visible to cameras
				continue
			if(C.can_use())
				last_found = C.c_tag
				last_seen = world.time
				break
	src.updateSelfDialog()

/obj/item/camera_bug/proc/same_z_level(obj/machinery/camera/C)
	var/turf/T_cam = get_turf(C)
	var/turf/T_bug = get_turf(loc)
	if(!T_bug || T_cam.z != T_bug.z)
		to_chat(usr, span_warning("You can't get a signal!"))
		return FALSE
	return TRUE

#undef BUGMODE_LIST
#undef BUGMODE_MONITOR
#undef BUGMODE_TRACK