code:
var/list/department_radio_keys = list(
	  ":r" = "right hand",	"#r" = "right hand",	".r" = "right hand",
	  ":l" = "left hand",	"#l" = "left hand",		".l" = "left hand",
	  ":i" = "intercom",	"#i" = "intercom",		".i" = "intercom",
	  ":h" = "department",	"#h" = "department",	".h" = "department",
	  ":c" = "Command",		"#c" = "Command",		".c" = "Command",
	  ":n" = "Science",		"#n" = "Science",		".n" = "Science",
	  ":m" = "Medical",		"#m" = "Medical",		".m" = "Medical",
	  ":e" = "Engineering", "#e" = "Engineering",	".e" = "Engineering",
	  ":s" = "Security",	"#s" = "Security",		".s" = "Security",
	  ":w" = "whisper",		"#w" = "whisper",		".w" = "whisper",
	  ":b" = "binary",		"#b" = "binary",		".b" = "binary",
	  ":a" = "alientalk",	"#a" = "alientalk",		".a" = "alientalk",
	  ":t" = "Syndicate",	"#t" = "Syndicate",		".t" = "Syndicate",
	  ":u" = "Supply",		"#u" = "Supply",		".u" = "Supply",
	  ":v" = "Service",		"#v" = "Service",		".v" = "Service",
	  ":o" = "AI Private",	"#o" = "AI Private",	".o" = "AI Private",
	  ":g" = "changeling",	"#g" = "changeling",	".g" = "changeling",
	  ":y" = "Centcom",		"#y" = "Centcom",		".y" = "Centcom",

	  ":R" = "right hand",	"#R" = "right hand",	".R" = "right hand",
	  ":L" = "left hand",	"#L" = "left hand",		".L" = "left hand",
	  ":I" = "intercom",	"#I" = "intercom",		".I" = "intercom",
	  ":H" = "department",	"#H" = "department",	".H" = "department",
	  ":C" = "Command",		"#C" = "Command",		".C" = "Command",
	  ":N" = "Science",		"#N" = "Science",		".N" = "Science",
	  ":M" = "Medical",		"#M" = "Medical",		".M" = "Medical",
	  ":E" = "Engineering",	"#E" = "Engineering",	".E" = "Engineering",
	  ":S" = "Security",	"#S" = "Security",		".S" = "Security",
	  ":W" = "whisper",		"#W" = "whisper",		".W" = "whisper",
	  ":B" = "binary",		"#B" = "binary",		".B" = "binary",
	  ":A" = "alientalk",	"#A" = "alientalk",		".A" = "alientalk",
	  ":T" = "Syndicate",	"#T" = "Syndicate",		".T" = "Syndicate",
	  ":U" = "Supply",		"#U" = "Supply",		".U" = "Supply",
	  ":V" = "Service",		"#V" = "Service",		".V" = "Service",
	  ":O" = "AI Private",	"#O" = "AI Private",	".O" = "AI Private",
	  ":G" = "changeling",	"#G" = "changeling",	".G" = "changeling",
	  ":Y" = "Centcom",		"#Y" = "Centcom",		".Y" = "Centcom",

	  //kinda localization -- rastaf0
	  //same keys as above, but on russian keyboard layout. This file uses cp1251 as encoding.
	  ":�" = "right hand",	"#�" = "right hand",	".�" = "right hand",
	  ":�" = "left hand",	"#�" = "left hand",		".�" = "left hand",
	  ":�" = "intercom",	"#�" = "intercom",		".�" = "intercom",
	  ":�" = "department",	"#�" = "department",	".�" = "department",
	  ":�" = "Command",		"#�" = "Command",		".�" = "Command",
	  ":�" = "Science",		"#�" = "Science",		".�" = "Science",
	  ":�" = "Medical",		"#�" = "Medical",		".�" = "Medical",
	  ":�" = "Engineering",	"#�" = "Engineering",	".�" = "Engineering",
	  ":�" = "Security",	"#�" = "Security",		".�" = "Security",
	  ":�" = "whisper",		"#�" = "whisper",		".�" = "whisper",
	  ":�" = "binary",		"#�" = "binary",		".�" = "binary",
	  ":�" = "alientalk",	"#�" = "alientalk",		".�" = "alientalk",
	  ":�" = "Syndicate",	"#�" = "Syndicate",		".�" = "Syndicate",
	  ":�" = "Supply",		"#�" = "Supply",		".�" = "Supply",
	  ":�" = "changeling",	"#�" = "changeling",	".�" = "changeling"
)

var/list/crit_allowed_modes = list(MODE_WHISPER,MODE_CHANGELING,MODE_ALIEN)

/mob/living/say(message, bubble_type,)
	message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))

	if(stat == DEAD)
		say_dead(message)
		return

	if(check_emote(message))
		return

	if(!can_speak_basic(message)) //Stat is seperate so I can handle whispers properly.
		return

	var/message_mode = get_message_mode(message)

	if(stat && !(message_mode in crit_allowed_modes))
		return

	if(message_mode == MODE_HEADSET || message_mode == MODE_ROBOT)
		message = copytext(message, 2)
	else if(message_mode)
		message = copytext(message, 3)
	if(findtext(message, " ", 1, 2))
		message = copytext(message, 2)

	if(handle_inherent_channels(message, message_mode)) //Hiveminds, binary chat & holopad.
		return

	if(!can_speak_vocal(message))
		src << "You find yourself unable to speak!"
		return

	if(message_mode != MODE_WHISPER) //whisper() calls treat_message(); double process results in "hisspering"
		message = treat_message(message)
	var/spans = list()
	spans += get_spans()

	if(!message)
		return

	//Log of what we've said, plain message, no spans or junk
	say_log += message

	var/message_range = 7
	var/radio_return = radio(message, message_mode, spans)
	if(radio_return & NOPASS) //There's a whisper() message_mode, no need to continue the proc if that is called
		return
	if(radio_return & ITALICS)
		spans |= SPAN_ITALICS
	if(radio_return & REDUCE_RANGE)
		message_range = 1

	//No screams in space, unless you're next to someone.
	var/turf/T = get_turf(src)
	var/datum/gas_mixture/environment = T.return_air()
	var/pressure = (environment)? environment.return_pressure() : 0
	if(pressure < SOUND_MINIMUM_PRESSURE)
		message_range = 1

	if(pressure < ONE_ATMOSPHERE*0.4) //Thin air, let's italicise the message
		spans |= SPAN_ITALICS

	send_speech(message, message_range, src, bubble_type, spans)

	log_say("[name]/[key] : [message]")
	return 1

/mob/living/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans)
	if(!client)
		return
	var/deaf_message
	var/deaf_type
	if(speaker != src)
		if(!radio_freq) //These checks have to be seperate, else people talking on the radio will make "You can't hear yourself!" appear when hearing people over the radio while deaf.
			deaf_message = "[speaker] [speaker.verb_say] something but you cannot hear them."
			deaf_type = 1
	else
		deaf_message = "You can't hear yourself!"
		deaf_type = 2 // Since you should be able to hear yourself without looking
	if(!(message_langs & languages) || force_compose) //force_compose is so AIs don't end up without their hrefs.
		message = compose_message(speaker, message_langs, raw_message, radio_freq, spans)
	show_message(message, 2, deaf_message, deaf_type)
	return message

/mob/living/send_speech(message, message_range = 7, obj/source = src, bubble_type = bubble_icon, list/spans)
	var/list/listening = get_hearers_in_view(message_range, source)
	for(var/mob/M in player_list)
		if(M.stat == DEAD && M.client && ((M.client.prefs.chat_toggles & CHAT_GHOSTEARS) || (get_dist(M, src) <= 7)) && client) // client is so that ghosts don't have to listen to mice
			listening |= M

	var/rendered = compose_message(src, languages, message, , spans)
	for(var/atom/movable/AM in listening)
		AM.Hear(rendered, src, languages, message, , spans)

	//speech bubble
	var/list/speech_bubble_recipients = list()
	for(var/mob/M in listening)
		if(M.client)
			speech_bubble_recipients.Add(M.client)
	var/image/I = image('icons/mob/talk.dmi', src, "[bubble_type][say_test(message)]", MOB_LAYER+1)
	I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
	spawn(0)
		flick_overlay(I, speech_bubble_recipients, 30)

/mob/proc/binarycheck()
	return 0

/mob/living/can_speak(message) //For use outside of Say()
	if(can_speak_basic(message) && can_speak_vocal(message))
		return 1

/mob/living/proc/can_speak_basic(message) //Check BEFORE handling of xeno and ling channels
	if(client)
		if(client.prefs.muted & MUTE_IC)
			src << "You cannot speak in IC (muted)."
			return 0
		if(client.handle_spam_prevention(message,MUTE_IC))
			return 0

	return 1

/mob/living/proc/can_speak_vocal(message) //Check AFTER handling of xeno and ling channels
	if(disabilities & MUTE)
		return 0

	if(is_muzzled())
		return 0

	if(!IsVocal())
		return 0

	return 1

/mob/living/proc/check_emote(message)
	if(copytext(message, 1, 2) == "*")
		emote(copytext(message, 2))
		return 1

/mob/living/proc/get_message_mode(message)
	if(copytext(message, 1, 2) == ";")
		return MODE_HEADSET
	else if(length(message) > 2)
		return department_radio_keys[copytext(message, 1, 3)]

/mob/living/proc/handle_inherent_channels(message, message_mode)
	if(message_mode == MODE_CHANGELING)
		switch(lingcheck())
			if(2)
				var/msg = "[mind.changeling.changelingID]: [message]"
				log_say("[mind.changeling.changelingID]/[src.key] : [message]")
				for(var/mob/M in mob_list)
					if(M in dead_mob_list)
						M << "(F) [msg]"
					else
						switch(M.lingcheck())
							if(2)
								M << msg
							if(1)
								if(prob(40))
									M << "We can faintly sense another of our kind trying to communicate through the hivemind..."
				return 1
			if(1)
				src << "Our senses have not evolved enough to be able to communicate this way..."
				return 1
	if(message_mode == MODE_ALIEN)
		if(hivecheck())
			alien_talk(message)
			return 1
	return 0

/mob/living/proc/treat_message(message)
	if(getBrainLoss() >= 60)
		message = derpspeech(message, stuttering)

	if(stuttering)
		message = stutter(message)

	if(slurring)
		message = slur(message)

	if(cultslurring)
		message = cultslur(message)

	message = capitalize(message)

	return message

/mob/living/proc/radio(message, message_mode, list/spans)
	switch(message_mode)
		if(MODE_R_HAND)
			if (r_hand)
				r_hand.talk_into(src, message, , spans)
			return ITALICS | REDUCE_RANGE

		if(MODE_L_HAND)
			if (l_hand)
				l_hand.talk_into(src, message, , spans)
			return ITALICS | REDUCE_RANGE

		if(MODE_INTERCOM)
			for (var/obj/item/device/radio/intercom/I in view(1, null))
				I.talk_into(src, message, , spans)
			return ITALICS | REDUCE_RANGE

		if(MODE_BINARY)
			if(binarycheck())
				robot_talk(message)
			return ITALICS | REDUCE_RANGE //Does not return 0 since this is only reached by humans, not borgs or AIs.

		if(MODE_WHISPER)
			whisper(message)
			return NOPASS
	return 0

/mob/living/lingcheck() //Returns 1 if they are a changeling. Returns 2 if they are a changeling that can communicate through the hivemind
	if(mind && mind.changeling)
		if(mind.changeling.changeling_speak)
			return 2
		return 1
	return 0

/mob/living/say_quote(input, list/spans)
	var/tempinput = attach_spans(input, spans)
	if (stuttering)
		return "stammers, \"[tempinput]\""
	if (getBrainLoss() >= 60)
		return "gibbers, \"[tempinput]\""
	return ..()


code:
var/list/ai_list = list()

//Not sure why this is necessary...
/proc/AutoUpdateAI(obj/subject)
	var/is_in_use = 0
	if (subject!=null)
		for(var/A in ai_list)
			var/mob/living/silicon/ai/M = A
			if ((M.client && M.machine == subject))
				is_in_use = 1
				subject.attack_ai(M)
	return is_in_use

/mob/living/silicon/ai
	name = "AI"
	icon = 'icons/mob/AI.dmi'//
	icon_state = "ai"
	anchored = 1
	density = 1
	status_flags = CANSTUN|CANPUSH
	force_compose = 1 //This ensures that the AI always composes it's own hear message. Needed for hrefs and job display.
	sight = SEE_TURFS | SEE_MOBS | SEE_OBJS
	see_in_dark = 8
	med_hud = DATA_HUD_MEDICAL_BASIC
	sec_hud = DATA_HUD_SECURITY_BASIC
	mob_size = MOB_SIZE_LARGE
	var/list/network = list("SS13")
	var/obj/machinery/camera/current = null
	var/list/connected_robots = list()
	var/aiRestorePowerRoutine = 0
	//var/list/laws = list()
	var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list(), "Burglar"=list())
	var/viewalerts = 0
	var/icon/holo_icon//Default is assigned when AI is created.
	var/obj/mecha/controlled_mech //For controlled_mech a mech, to determine whether to relaymove or use the AI eye.
	var/radio_enabled = 1 //Determins if a carded AI can speak with its built in radio or not.
	radiomod = ";" //AIs will, by default, state their laws on the internal radio.
	var/obj/item/device/pda/ai/aiPDA = null
	var/obj/item/device/multitool/aiMulti = null
	var/mob/living/simple_animal/bot/Bot
	var/tracking = 0 //this is 1 if the AI is currently tracking somebody, but the track has not yet been completed.
	var/datum/effect_system/spark_spread/spark_system//So they can initialize sparks whenever/N

	//MALFUNCTION
	var/datum/module_picker/malf_picker
	var/list/datum/AI_Module/current_modules = list()
	var/fire_res_on_core = 0
	var/can_dominate_mechs = 0
	var/shunted = 0 //1 if the AI is currently shunted. Used to differentiate between shunted and ghosted/braindead

	var/control_disabled = 0 // Set to 1 to stop AI from interacting via Click()
	var/malfhacking = 0 // More or less a copy of the above var, so that malf AIs can hack and still get new cyborgs -- NeoFite
	var/malf_cooldown = 0 //Cooldown var for malf modules

	var/obj/machinery/power/apc/malfhack = null
	var/explosive = 0 //does the AI explode when it dies?

	var/mob/living/silicon/ai/parent = null
	var/camera_light_on = 0
	var/list/obj/machinery/camera/lit_cameras = list()

	var/datum/trackable/track = new()

	var/last_paper_seen = null
	var/can_shunt = 1
	var/last_announcement = "" // For AI VOX, if enabled
	var/turf/waypoint //Holds the turf of the currently selected waypoint.
	var/waypoint_mode = 0 //Waypoint mode is for selecting a turf via clicking.
	var/apc_override = 0 //hack for letting the AI use its APC even when visionless
	var/nuking = FALSE
	var/obj/machinery/doomsday_device/doomsday_device

	var/mob/camera/aiEye/eyeobj = new()
	var/sprint = 10
	var/cooldown = 0
	var/acceleration = 1

	var/obj/machinery/camera/portable/builtInCamera

/mob/living/silicon/ai/New(loc, var/datum/ai_laws/L, var/obj/item/device/mmi/B, var/safety = 0)
	..()
	rename_self("ai")
	name = real_name
	anchored = 1
	canmove = 0
	density = 1
	loc = loc

	holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo1"))

	spark_system = new /datum/effect_system/spark_spread()
	spark_system.set_up(5, 0, src)
	spark_system.attach(src)

	if(L)
		if (istype(L, /datum/ai_laws))
			laws = L
	else
		make_laws()

	verbs += /mob/living/silicon/ai/proc/show_laws_verb

	aiPDA = new/obj/item/device/pda/ai(src)
	aiPDA.owner = name
	aiPDA.ownjob = "AI"
	aiPDA.name = name + " (" + aiPDA.ownjob + ")"

	aiMulti = new(src)
	radio = new /obj/item/device/radio/headset/ai(src)
	aicamera = new/obj/item/device/camera/siliconcam/ai_camera(src)

	if (istype(loc, /turf))
		verbs.Add(/mob/living/silicon/ai/proc/ai_network_change, \
		/mob/living/silicon/ai/proc/ai_statuschange, /mob/living/silicon/ai/proc/ai_hologram_change, \
		/mob/living/silicon/ai/proc/toggle_camera_light, /mob/living/silicon/ai/proc/botcall,\
		/mob/living/silicon/ai/proc/control_integrated_radio, /mob/living/silicon/ai/proc/set_automatic_say_channel)

	if(!safety)//Only used by AIize() to successfully spawn an AI.
		if (!B)//If there is no player/brain inside.
			new/obj/structure/AIcore/deactivated(loc)//New empty terminal.
			qdel(src)//Delete AI.
			return
		else
			if (B.brainmob.mind)
				B.brainmob.mind.transfer_to(src)
				rename_self("ai")
				if(mind.special_role)
					mind.store_memory("As an AI, you must obey your silicon laws above all else. Your objectives will consider you to be dead.")
					src << "You have been installed as an AI! "
					src << "You must obey your silicon laws above all else. Your objectives will consider you to be dead."

			src << "You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras)."
			src << "To look at other parts of the station, click on yourself to get a camera menu."
			src << "While observing through a camera, you can use most (networked) devices which you can see, such as computers, APCs, intercoms, doors, etc."
			src << "To use something, simply click on it."
			src << "Use say :b to speak to your cyborgs through binary."
			src << "For department channels, use the following say commands:"
			src << ":o - AI Private, :c - Command, :s - Security, :e - Engineering, :u - Supply, :v - Service, :m - Medical, :n - Science."
			show_laws()
			src << "These laws may be changed by other players, or by you being the traitor."

			job = "AI"
	ai_list += src
	shuttle_caller_list += src

	eyeobj.ai = src
	eyeobj.name = "[src.name] (AI Eye)" // Give it a name
	eyeobj.loc = src.loc

	builtInCamera = new /obj/machinery/camera/portable(src)
	builtInCamera.network = list("SS13")

/mob/living/silicon/ai/Destroy()
	ai_list -= src
	shuttle_caller_list -= src
	SSshuttle.autoEvac()
	qdel(eyeobj) // No AI, no Eye
	return ..()

/mob/living/silicon/ai/verb/pick_icon()
	set category = "AI Commands"
	set name = "Set AI Core Display"
	if(stat || aiRestorePowerRoutine)
		return

		//if(icon_state == initial(icon_state))
	var/icontype = input("Please, select a display!", "AI", null/*, null*/) in list("Clown", "Monochrome", "Blue", "Inverted", "Firewall", "Green", "Red", "Static", "Red October", "House", "Heartline", "Hades", "Helios", "President", "Syndicat Meow", "Alien", "Too Deep", "Triumvirate", "Triumvirate-M", "Text", "Matrix", "Dorf", "Bliss", "Not Malf", "Fuzzy", "Goon", "Database", "Glitchman", "Murica", "Nanotrasen", "Gentoo")
	if(icontype == "Clown")
		icon_state = "ai-clown2"
	else if(icontype == "Monochrome")
		icon_state = "ai-mono"
	else if(icontype == "Blue")
		icon_state = "ai"
	else if(icontype == "Inverted")
		icon_state = "ai-u"
	else if(icontype == "Firewall")
		icon_state = "ai-magma"
	else if(icontype == "Green")
		icon_state = "ai-wierd"
	else if(icontype == "Red")
		icon_state = "ai-malf"
	else if(icontype == "Static")
		icon_state = "ai-static"
	else if(icontype == "Red October")
		icon_state = "ai-redoctober"
	else if(icontype == "House")
		icon_state = "ai-house"
	else if(icontype == "Heartline")
		icon_state = "ai-heartline"
	else if(icontype == "Hades")
		icon_state = "ai-hades"
	else if(icontype == "Helios")
		icon_state = "ai-helios"
	else if(icontype == "President")
		icon_state = "ai-pres"
	else if(icontype == "Syndicat Meow")
		icon_state = "ai-syndicatmeow"
	else if(icontype == "Alien")
		icon_state = "ai-alien"
	else if(icontype == "Too Deep")
		icon_state = "ai-toodeep"
	else if(icontype == "Triumvirate")
		icon_state = "ai-triumvirate"
	else if(icontype == "Triumvirate-M")
		icon_state = "ai-triumvirate-malf"
	else if(icontype == "Text")
		icon_state = "ai-text"
	else if(icontype == "Matrix")
		icon_state = "ai-matrix"
	else if(icontype == "Dorf")
		icon_state = "ai-dorf"
	else if(icontype == "Bliss")
		icon_state = "ai-bliss"
	else if(icontype == "Not Malf")
		icon_state = "ai-notmalf"
	else if(icontype == "Fuzzy")
		icon_state = "ai-fuzz"
	else if(icontype == "Goon")
		icon_state = "ai-goon"
	else if(icontype == "Database")
		icon_state = "ai-database"
	else if(icontype == "Glitchman")
		icon_state = "ai-glitchman"
	else if(icontype == "Murica")
		icon_state = "ai-murica"
	else if(icontype == "Nanotrasen")
		icon_state = "ai-nanotrasen"
	else if(icontype == "Gentoo")
		icon_state = "ai-gentoo"
	//else
			//usr <<"You can only change your display once!"
			//return

/mob/living/silicon/ai/Stat()
	..()
	if(statpanel("Status"))
		if(!stat)
			stat(null, text("System integrity: [(health+100)/2]%"))
			stat(null, "Station Time: [worldtime2text()]")
			stat(null, text("Connected cyborgs: [connected_robots.len]"))
			var/area/borg_area
			for(var/mob/living/silicon/robot/R in connected_robots)
				borg_area = get_area(R)
				var/robot_status = "Nominal"
				if(R.stat || !R.client)
					robot_status = "OFFLINE"
				else if(!R.cell || R.cell.charge <= 0)
					robot_status = "DEPOWERED"
				//Name, Health, Battery, Module, Area, and Status! Everything an AI wants to know about its borgies!
				stat(null, text("[R.name] | S.Integrity: [R.health]% | Cell: [R.cell ? "[R.cell.charge]/[R.cell.maxcharge]" : "Empty"] | \
 				Module: [R.designation] | Loc: [borg_area.name] | Status: [robot_status]"))
		else
			stat(null, text("Systems nonfunctional"))

/mob/living/silicon/ai/proc/ai_alerts()
	var/dat = "<TITLE>Current Station Alerts</TITLE><META HTTP-EQUIV='Refresh' CONTENT='10'>\n"
	dat += "Close

"
	for (var/cat in alarms)
		dat += text("[]
\n", cat)
		var/list/L = alarms[cat]
		if (L.len)
			for (var/alarm in L)
				var/list/alm = L[alarm]
				var/area/A = alm[1]
				var/C = alm[2]
				var/list/sources = alm[3]
				dat += "<NOBR>"
				if (C && istype(C, /list))
					var/dat2 = ""
					for (var/obj/machinery/camera/I in C)
						dat2 += text("[][]", (dat2=="") ? "" : " | ", src, I, I.c_tag)
					dat += text("-- [] ([])", A.name, (dat2!="") ? dat2 : "No Camera")
				else if (C && istype(C, /obj/machinery/camera))
					var/obj/machinery/camera/Ctmp = C
					dat += text("-- [] ([])", A.name, src, C, Ctmp.c_tag)
				else
					dat += text("-- [] (No Camera)", A.name)
				if (sources.len > 1)
					dat += text("- [] sources", sources.len)
				dat += "</NOBR>
\n"
		else
			dat += "-- All Systems Nominal
\n"
		dat += "
\n"

	viewalerts = 1
	src << browse(dat, "window=aialerts&can_close=0")

/mob/living/silicon/ai/proc/ai_roster()
	var/dat = "<title>Crew Roster</title>Crew Roster:

"

	for(var/datum/data/record/t in sortRecord(data_core.general))
		dat += t.fields["name"] + " - " + t.fields["rank"] + "
"
	dat += ""

	src << browse(dat, "window=airoster")
	onclose(src, "airoster")

/mob/living/silicon/ai/proc/ai_call_shuttle()
	if(stat == DEAD)
		return //won't work if dead
	if(istype(usr,/mob/living/silicon/ai))
		var/mob/living/silicon/ai/AI = src
		if(AI.control_disabled)
			usr << "Wireless control is disabled!"
			return

	var/reason = input(src, "What is the nature of your emergency? ([CALL_SHUTTLE_REASON_LENGTH] characters required.)", "Confirm Shuttle Call") as null|text

	if(trim(reason))
		SSshuttle.requestEvac(src, reason)

	// hack to display shuttle timer
	if(SSshuttle.emergency.mode >= SHUTTLE_CALL)
		var/obj/machinery/computer/communications/C = locate() in machines
		if(C)
			C.post_status("shuttle")

	return

/mob/living/silicon/ai/cancel_camera()
	src.view_core()

/mob/living/silicon/ai/verb/toggle_anchor()
	set category = "AI Commands"
	set name = "Toggle Floor Bolts"
	if(!isturf(loc)) // if their location isn't a turf
		return // stop
	if(stat == DEAD)
		return //won't work if dead
	anchored = !anchored // Toggles the anchor

	src << "[anchored ? "You are now anchored." : "You are now unanchored."]"
	// the message in the [] will change depending whether or not the AI is anchored

/mob/living/silicon/ai/update_canmove() //If the AI dies, mobs won't go through it anymore
	return 0

/mob/living/silicon/ai/proc/ai_cancel_call()
	set category = "Malfunction"
	if(stat == DEAD)
		return //won't work if dead
	if(istype(usr,/mob/living/silicon/ai))
		var/mob/living/silicon/ai/AI = src
		if(AI.control_disabled)
			src	 << "Wireless control is disabled!"
			return
	SSshuttle.cancelEvac(src)
	return

/mob/living/silicon/ai/blob_act()
	if (stat != DEAD)
		adjustBruteLoss(60)
		updatehealth()
		return 1
	return 0

/mob/living/silicon/ai/restrained()
	return 0

/mob/living/silicon/ai/emp_act(severity)
	if (prob(30))
		switch(pick(1,2))
			if(1)
				view_core()
			if(2)
				SSshuttle.requestEvac(src,"ALERT: Energy surge detected in AI core! Station integrity may be compromised! Initiati--%m091#ar-BZZT")
	..()

/mob/living/silicon/ai/ex_act(severity, target)
	..()

	switch(severity)
		if(1)
			gib()
		if(2)
			if (stat != DEAD)
				adjustBruteLoss(60)
				adjustFireLoss(60)
		if(3)
			if (stat != DEAD)
				adjustBruteLoss(30)

	return

/mob/living/silicon/ai/Topic(href, href_list)
	if(usr != src)
		return
	..()
	if (href_list["mach_close"])
		if (href_list["mach_close"] == "aialerts")
			viewalerts = 0
		var/t1 = text("window=[]", href_list["mach_close"])
		unset_machine()
		src << browse(null, t1)
	if (href_list["switchcamera"])
		switchCamera(locate(href_list["switchcamera"])) in cameranet.cameras
	if (href_list["showalerts"])
		ai_alerts()
#ifdef AI_VOX
	if(href_list["say_word"])
		play_vox_word(href_list["say_word"], null, src)
		return
#endif
	if(href_list["show_paper"])
		if(last_paper_seen)
			src << browse(last_paper_seen, "window=show_paper")
	//Carn: holopad requests
	if(href_list["jumptoholopad"])
		var/obj/machinery/hologram/holopad/H = locate(href_list["jumptoholopad"])
		if(stat == CONSCIOUS)
			if(H)
				H.attack_ai(src) //may as well recycle
			else
				src << "Unable to locate the holopad."
	if(href_list["track"])
		var/string = href_list["track"]
		trackable_mobs()
		var/list/trackeable = list()
		trackeable += track.humans + track.others
		var/list/target = list()
		for(var/I in trackeable)
			var/mob/M = trackeable[I]
			if(M.name == string)
				target += M
		if(name == string)
			target += src
		if(target.len)
			ai_actual_track(pick(target))
		else
			src << "Target is not on or near any active cameras on the station."
		return
	if(href_list["callbot"]) //Command a bot to move to a selected location.
		Bot = locate(href_list["callbot"]) in living_mob_list
		if(!Bot || Bot.remote_disabled || src.control_disabled)
			return //True if there is no bot found, the bot is manually emagged, or the AI is carded with wireless off.
		waypoint_mode = 1
		src << "Set your waypoint by clicking on a valid location free of obstructions."
		return
	if(href_list["interface"]) //Remotely connect to a bot!
		Bot = locate(href_list["interface"]) in living_mob_list
		if(!Bot || Bot.remote_disabled || src.control_disabled)
			return
		Bot.attack_ai(src)
	if(href_list["botrefresh"]) //Refreshes the bot control panel.
		botcall()
		return

	if (href_list["ai_take_control"]) //Mech domination
		var/obj/mecha/M = locate(href_list["ai_take_control"])
		if(controlled_mech)
			src << "You are already loaded into an onboard computer!"
			return
		if(M)
			M.transfer_ai(AI_MECH_HACK,src, usr) //Called om the mech itself.

/mob/living/silicon/ai/bullet_act(obj/item/projectile/Proj)
	..(Proj)
	updatehealth()
	return 2

/mob/living/silicon/ai/attack_alien(mob/living/carbon/alien/humanoid/M)
	if(!ticker || !ticker.mode)
		M << "You cannot attack people before the game has started."
		return

	..()
	return

/mob/living/silicon/ai/proc/switchCamera(obj/machinery/camera/C)

	if(!tracking)
		cameraFollow = null

	if (!C || stat == DEAD) //C.can_use())
		return 0

	if(!src.eyeobj)
		view_core()
		return
	// ok, we're alive, camera is good and in our network...
	eyeobj.setLoc(get_turf(C))
	//machine = src

	return 1

/mob/living/silicon/ai/proc/botcall()
	set category = "AI Commands"
	set name = "Access Robot Control"
	set desc = "Wirelessly control various automatic robots."
	if(stat == 2)
		return //won't work if dead

	if(control_disabled)
		src << "Wireless communication is disabled."
		return
	var/turf/ai_current_turf = get_turf(src)
	var/ai_Zlevel = ai_current_turf.z
	var/d
	var/area/bot_area
	d += "Query network status
"
	d += "

Name

Status

Location

Control

"

	for (Bot in living_mob_list)
		if(Bot.z == ai_Zlevel && !Bot.remote_disabled) //Only non-emagged bots on the same Z-level are detected!
			bot_area = get_area(Bot)
			var/bot_mode = Bot.get_mode()
			d += "
[Bot.hacked ? "(!)" : ""] [Bot.name] ([Bot.model])	"
			//If the bot is on, it will display the bot's current mode status. If the bot is not mode, it will just report "Idle". "Inactive if it is not on at all.
			d += "[bot_mode]	"
			d += "[bot_area.name]	"
			d += "Interface	"
			d += "Call	"
			d += "
"
			d = format_text(d)

	var/datum/browser/popup = new(src, "botcall", "Remote Robot Control", 700, 400)
	popup.set_content(d)
	popup.open()

/mob/living/silicon/ai/proc/set_waypoint(atom/A)
	var/turf/turf_check = get_turf(A)
		//The target must be in view of a camera or near the core.
	if(turf_check in range(get_turf(src)))
		call_bot(turf_check)
	else if(cameranet && cameranet.checkTurfVis(turf_check))
		call_bot(turf_check)
	else
		src << "Selected location is not visible."

/mob/living/silicon/ai/proc/call_bot(turf/waypoint)

	if(!Bot)
		return

	if(Bot.calling_ai && Bot.calling_ai != src) //Prevents an override if another AI is controlling this bot.
		src << "Interface error. Unit is already in use."
		return

	Bot.call_bot(src, waypoint)

/mob/living/silicon/ai/triggerAlarm(class, area/A, O, obj/alarmsource)
	if(alarmsource.z != z)
		return
	if (stat == 2)
		return 1
	var/list/L = alarms[class]
	for (var/I in L)
		if (I == A.name)
			var/list/alarm = L[I]
			var/list/sources = alarm[3]
			if (!(alarmsource in sources))
				sources += alarmsource
			return 1
	var/obj/machinery/camera/C = null
	var/list/CL = null
	if (O && istype(O, /list))
		CL = O
		if (CL.len == 1)
			C = CL[1]
	else if (O && istype(O, /obj/machinery/camera))
		C = O
	L[A.name] = list(A, (C) ? C : O, list(alarmsource))
	if (O)
		if (C && C.can_use())
			queueAlarm("--- [class] alarm detected in [A.name]! ([C.c_tag])", class)
		else if (CL && CL.len)
			var/foo = 0
			var/dat2 = ""
			for (var/obj/machinery/camera/I in CL)
				dat2 += text("[][]", (!foo) ? "" : " | ", src, I, I.c_tag)	//I'm not fixing this shit...
				foo = 1
			queueAlarm(text ("--- [] alarm detected in []! ([])", class, A.name, dat2), class)
		else
			queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, A.name), class)
	else
		queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, A.name), class)
	if (viewalerts) ai_alerts()
	return 1

/mob/living/silicon/ai/cancelAlarm(class, area/A, obj/origin)
	var/list/L = alarms[class]
	var/cleared = 0
	for (var/I in L)
		if (I == A.name)
			var/list/alarm = L[I]
			var/list/srcs  = alarm[3]
			if (origin in srcs)
				srcs -= origin
			if (srcs.len == 0)
				cleared = 1
				L -= I
	if (cleared)
		queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0)
		if (viewalerts) ai_alerts()
	return !cleared

//Replaces /mob/living/silicon/ai/verb/change_network() in ai.dm & camera.dm
//Adds in /mob/living/silicon/ai/proc/ai_network_change() instead
//Addition by Mord_Sith to define AI's network change ability
/mob/living/silicon/ai/proc/ai_network_change()
	set category = "AI Commands"
	set name = "Jump To Network"
	unset_machine()
	cameraFollow = null
	var/cameralist[0]

	if(stat == 2)
		return //won't work if dead

	var/mob/living/silicon/ai/U = usr

	for (var/obj/machinery/camera/C in cameranet.cameras)
		if(!C.can_use())
			continue

		var/list/tempnetwork = C.network
		tempnetwork.Remove("CREED", "thunder", "RD", "toxins", "Prison")
		if(tempnetwork.len)
			for(var/i in C.network)
				cameralist[i] = i
	var/old_network = network
	network = input(U, "Which network would you like to view?") as null|anything in cameralist

	if(!U.eyeobj)
		U.view_core()
		return

	if(isnull(network))
		network = old_network // If nothing is selected
	else
		for(var/obj/machinery/camera/C in cameranet.cameras)
			if(!C.can_use())
				continue
			if(network in C.network)
				U.eyeobj.setLoc(get_turf(C))
				break
	src << "Switched to [network] camera network."
//End of code by Mord_Sith

/mob/living/silicon/ai/proc/choose_modules()
	set category = "Malfunction"
	set name = "Choose Module"

	malf_picker.use(src)

/mob/living/silicon/ai/proc/ai_statuschange()
	set category = "AI Commands"
	set name = "AI Status"

	if(stat == 2)
		return //won't work if dead
	var/list/ai_emotions = list("Very Happy", "Happy", "Neutral", "Unsure", "Confused", "Sad", "BSOD", "Blank", "Problems?", "Awesome", "Facepalm", "Friend Computer", "Dorfy", "Blue Glow", "Red Glow")
	var/emote = input("Please, select a status!", "AI Status", null, null) in ai_emotions
	for (var/obj/machinery/M in machines) //change status
		if(istype(M, /obj/machinery/ai_status_display))
			var/obj/machinery/ai_status_display/AISD = M
			AISD.emotion = emote
		//if Friend Computer, change ALL displays
		else if(istype(M, /obj/machinery/status_display))

			var/obj/machinery/status_display/SD = M
			if(emote=="Friend Computer")
				SD.friendc = 1
			else
				SD.friendc = 0
	return

//I am the icon meister. Bow fefore me.	//>fefore
/mob/living/silicon/ai/proc/ai_hologram_change()
	set name = "Change Hologram"
	set desc = "Change the default hologram available to AI to something else."
	set category = "AI Commands"

	if(stat == 2)
		return //won't work if dead
	var/input
	if(alert("Would you like to select a hologram based on a crew member or switch to unique avatar?",,"Crew Member","Unique")=="Crew Member")

		var/personnel_list[] = list()

		for(var/datum/data/record/t in data_core.locked)//Look in data core locked.
			personnel_list["[t.fields["name"]]: [t.fields["rank"]]"] = t.fields["image"]//Pull names, rank, and image.

		if(personnel_list.len)
			input = input("Select a crew member:") as null|anything in personnel_list
			var/icon/character_icon = personnel_list[input]
			if(character_icon)
				qdel(holo_icon)//Clear old icon so we're not storing it in memory.
				holo_icon = getHologramIcon(icon(character_icon))
		else
			alert("No suitable records found. Aborting.")

	else
		var/icon_list[] = list(
		"default",
		"floating face",
		"xeno queen",
		"space carp"
		)
		input = input("Please select a hologram:") as null|anything in icon_list
		if(input)
			qdel(holo_icon)
			switch(input)
				if("default")
					holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo1"))
				if("floating face")
					holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo2"))
				if("xeno queen")
					holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo3"))
				if("space carp")
					holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo4"))
	return

/mob/living/silicon/ai/proc/corereturn()
	set category = "Malfunction"
	set name = "Return to Main Core"

	var/obj/machinery/power/apc/apc = src.loc
	if(!istype(apc))
		src << "You are already in your Main Core."
		return
	apc.malfvacate()

/mob/living/silicon/ai/proc/toggle_camera_light()
	if(stat != CONSCIOUS)
		return

	camera_light_on = !camera_light_on

	if (!camera_light_on)
		src << "Camera lights deactivated."

		for (var/obj/machinery/camera/C in lit_cameras)
			C.SetLuminosity(0)
			lit_cameras = list()

		return

	light_cameras()

	src << "Camera lights activated."
	return

//AI_CAMERA_LUMINOSITY

/mob/living/silicon/ai/proc/light_cameras()
	var/list/obj/machinery/camera/add = list()
	var/list/obj/machinery/camera/remove = list()
	var/list/obj/machinery/camera/visible = list()
	for (var/datum/camerachunk/CC in eyeobj.visibleCameraChunks)
		for (var/obj/machinery/camera/C in CC.cameras)
			if (!C.can_use() || get_dist(C, eyeobj) > 7)
				continue
			visible |= C

	add = visible - lit_cameras
	remove = lit_cameras - visible

	for (var/obj/machinery/camera/C in remove)
		lit_cameras -= C //Removed from list before turning off the light so that it doesn't check the AI looking away.
		C.Togglelight(0)
	for (var/obj/machinery/camera/C in add)
		C.Togglelight(1)
		lit_cameras |= C

/mob/living/silicon/ai/proc/control_integrated_radio()
	set name = "Transceiver Settings"
	set desc = "Allows you to change settings of your radio."
	set category = "AI Commands"

	if(stat == 2)
		return //won't work if dead

	src << "Accessing Subspace Transceiver control..."
	if (radio)
		radio.interact(src)

/mob/living/silicon/ai/proc/set_syndie_radio()
	if(radio)
		radio.make_syndie()

/mob/living/silicon/ai/proc/set_automatic_say_channel()
	set name = "Set Auto Announce Mode"
	set desc = "Modify the default radio setting for your automatic announcements."
	set category = "AI Commands"

	if(stat == 2)
		return //won't work if dead
	set_autosay()

/mob/living/silicon/ai/attack_slime(mob/living/simple_animal/slime/user)
	return

/mob/living/silicon/ai/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/device/aicard/card)
	if(!..())
		return
	if(interaction == AI_TRANS_TO_CARD)//The only possible interaction. Upload AI mob to a card.
		if(!mind)
			user << "No intelligence patterns detected."    //No more magical carding of empty cores, AI RETURN TO BODY!!!11
			return
		new /obj/structure/AIcore/deactivated(loc)//Spawns a deactivated terminal at AI location.
		ai_restore_power()//So the AI initially has power.
		control_disabled = 1//Can't control things remotely if you're stuck in a card!
		radio_enabled = 0 	//No talking on the built-in radio for you either!
		loc = card//Throw AI into the card.
		card.AI = src
		src << "You have been downloaded to a mobile storage device. Remote device connection severed."
		user << "Transfer successful: [name] ([rand(1000,9999)].exe) removed from host terminal and stored within local memory."

/mob/living/silicon/ai/flash_eyes(intensity = 1, override_blindness_check = 0, affect_silicon = 0)
	return // no eyes, no flashing

/mob/living/silicon/ai/attackby(obj/item/weapon/W, mob/user, params)
	if(W.force && W.damtype != STAMINA && src.stat != DEAD) //only sparks if real damage is dealt.
		spark_system.start()
	return ..()

/mob/living/silicon/ai/can_buckle()
	return 0

/mob/living/silicon/ai/canUseTopic(atom/movable/M, be_close = 0)
	if(stat)
		return
	if(be_close && !in_range(M, src))
		return
	//stop AIs from leaving windows open and using then after they lose vision
	//apc_override is needed here because AIs use their own APC when powerless
	//get_turf_pixel() is because APCs in maint aren't actually in view of the inner camera
	if(M && cameranet && !cameranet.checkTurfVis(get_turf_pixel(M)) && !apc_override)
		return
	return 1

/mob/living/silicon/ai/proc/relay_speech(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans)
	raw_message = lang_treat(speaker, message_langs, raw_message, spans)
	var/name_used = speaker.GetVoice()
	var/rendered = "Relayed Speech: [name_used] [raw_message]"
	show_message(rendered, 2)

/mob/living/silicon/ai/fully_replace_character_name(oldname,newname)
	..()
	if(oldname != real_name)
		if(eyeobj)
			eyeobj.name = "[newname] (AI Eye)"

		// Notify Cyborgs
		for(var/mob/living/silicon/robot/Slave in connected_robots)
			Slave.show_laws()

/mob/living/silicon/ai/replace_identification_name(oldname,newname)
	if(aiPDA)
		aiPDA.owner = newname
		aiPDA.name = newname + " (" + aiPDA.ownjob + ")"

/mob/living/silicon/ai/proc/add_malf_picker()
	src << "In the top right corner of the screen you will find the Malfunctions tab, where you can purchase various abilities, from upgraded surveillance to station ending doomsday devices."
	src << "You are also capable of hacking APCs, which grants you more points to spend on your Malfunction powers. The drawback is that a hacked APC will give you away if spotted by the crew. Hacking an APC takes 60 seconds."
	view_core() //A BYOND bug requires you to be viewing your core before your verbs update
	verbs += /mob/living/silicon/ai/proc/choose_modules
	malf_picker = new /datum/module_picker

/mob/living/silicon/ai/reset_perspective(atom/A)
	if(camera_light_on)
		light_cameras()
	if(istype(A,/obj/machinery/camera))
		current = A
	if(client)
		if(istype(A, /atom/movable))
			client.perspective = EYE_PERSPECTIVE
			client.eye = A
		else
			if(isturf(loc))
				if(eyeobj)
					client.eye = eyeobj
					client.perspective = MOB_PERSPECTIVE
				else
					client.eye = client.mob
					client.perspective = MOB_PERSPECTIVE
			else
				client.perspective = EYE_PERSPECTIVE
				client.eye = loc
		update_sight()
		if(client.eye != src)
			var/atom/AT = client.eye
			AT.get_remote_view_fullscreens(src)
		else
			clear_fullscreen("remote_view", 0)

/mob/living/silicon/ai/revive(full_heal = 0, admin_revive = 0)
	if(..()) //successfully ressuscitated from death
		icon_state = "ai"
		. = 1

license: mit


code:
/mob/living/silicon/ai/death(gibbed)
	if(stat == DEAD)
		return
	if(!gibbed)
		visible_message("<b>[src]</b> lets out a flurry of sparks, its screen flickering as its systems slowly halt.")
	stat = DEAD

	if("[icon_state]_dead" in icon_states(src.icon,1))
		icon_state = "[icon_state]_dead"
	else
		icon_state = "ai_dead"

	cameraFollow = null

	anchored = 0 //unbolt floorbolts
	update_canmove()
	if(eyeobj)
		eyeobj.setLoc(get_turf(src))

	shuttle_caller_list -= src
	SSshuttle.autoEvac()

	if(nuking)
		set_security_level("red")
		nuking = 0
		SSshuttle.emergencyNoEscape = 0
		if(SSshuttle.emergency.mode == SHUTTLE_STRANDED)
			SSshuttle.emergency.mode = SHUTTLE_DOCKED
			SSshuttle.emergency.timer = world.time
			priority_announce("Hostile enviroment resolved. You have 3 minutes to board the Emergency Shuttle.", null, 'sound/AI/shuttledock.ogg', "Priority")
		for(var/obj/item/weapon/pinpointer/point in pinpointer_list)
			point.the_disk = null //Point back to the disk.

	if(doomsday_device)
		doomsday_device.timing = 0
		qdel(doomsday_device)
	if(explosive)
		spawn(10)
			explosion(src.loc, 3, 6, 12, 15)

	for(var/obj/machinery/ai_status_display/O in world) //change status
		if(src.key)
			O.mode = 2
			if(istype(loc, /obj/item/device/aicard))
				loc.icon_state = "aicard-404"

	return ..()


code:
/mob/living/silicon/ai/examine(mob/user)
	var/msg = "*---------*\nThis is \icon[src] [src]!\n"
	if (src.stat == DEAD)
		msg += "It appears to be powered-down.\n"
	else
		msg += ""
		if (src.getBruteLoss())
			if (src.getBruteLoss() < 30)
				msg += "It looks slightly dented.\n"
			else
				msg += "It looks severely dented!\n"
		if (src.getFireLoss())
			if (src.getFireLoss() < 30)
				msg += "It looks slightly charred.\n"
			else
				msg += "Its casing is melted and heat-warped!\n"
		msg += ""
		if (shunted == 0 && !src.client)
			msg += "[src]Core.exe has stopped responding! NTOS is searching for a solution to the problem...\n"
	msg += "*---------*"

	user << msg


code:
// CAMERA NET
//
// The datum containing all the chunks.

var/const/CHUNK_SIZE = 16 // Only chunk sizes that are to the power of 2. E.g: 2, 4, 8, 16, etc..

var/datum/cameranet/cameranet = new()

/datum/cameranet
	var/name = "Camera Net" // Name to show for VV and stat()

	// The cameras on the map, no matter if they work or not. Updated in obj/machinery/camera.dm by New() and Del().
	var/list/cameras = list()
	// The chunks of the map, mapping the areas that the cameras can see.
	var/list/chunks = list()
	var/ready = 0

	// The object used for the clickable stat() button.
	var/obj/effect/statclick/statclick

// Checks if a chunk has been Generated in x, y, z.
/datum/cameranet/proc/chunkGenerated(x, y, z)
	x &= ~(CHUNK_SIZE - 1)
	y &= ~(CHUNK_SIZE - 1)
	var/key = "[x],[y],[z]"
	return (chunks[key])

// Returns the chunk in the x, y, z.
// If there is no chunk, it creates a new chunk and returns that.
/datum/cameranet/proc/getCameraChunk(x, y, z)
	x &= ~(CHUNK_SIZE - 1)
	y &= ~(CHUNK_SIZE - 1)
	var/key = "[x],[y],[z]"
	if(!chunks[key])
		chunks[key] = new /datum/camerachunk(null, x, y, z)

	return chunks[key]

// Updates what the aiEye can see. It is recommended you use this when the aiEye moves or it's location is set.

/datum/cameranet/proc/visibility(mob/camera/aiEye/ai)
	// 0xf = 15
	var/x1 = max(0, ai.x - 16) & ~(CHUNK_SIZE - 1)
	var/y1 = max(0, ai.y - 16) & ~(CHUNK_SIZE - 1)
	var/x2 = min(world.maxx, ai.x + 16) & ~(CHUNK_SIZE - 1)
	var/y2 = min(world.maxy, ai.y + 16) & ~(CHUNK_SIZE - 1)

	var/list/visibleChunks = list()

	for(var/x = x1; x <= x2; x += CHUNK_SIZE)
		for(var/y = y1; y <= y2; y += CHUNK_SIZE)
			visibleChunks |= getCameraChunk(x, y, ai.z)

	var/list/remove = ai.visibleCameraChunks - visibleChunks
	var/list/add = visibleChunks - ai.visibleCameraChunks

	for(var/chunk in remove)
		var/datum/camerachunk/c = chunk
		c.remove(ai)

	for(var/chunk in add)
		var/datum/camerachunk/c = chunk
		c.add(ai)

// Updates the chunks that the turf is located in. Use this when obstacles are destroyed or	when doors open.

/datum/cameranet/proc/updateVisibility(atom/A, opacity_check = 1)

	if(!ticker || (opacity_check && !A.opacity))
		return
	majorChunkChange(A, 2)

/datum/cameranet/proc/updateChunk(x, y, z)
	// 0xf = 15
	if(!chunkGenerated(x, y, z))
		return
	var/datum/camerachunk/chunk = getCameraChunk(x, y, z)
	chunk.hasChanged()

// Removes a camera from a chunk.

/datum/cameranet/proc/removeCamera(obj/machinery/camera/c)
	if(c.can_use())
		majorChunkChange(c, 0)

// Add a camera to a chunk.

/datum/cameranet/proc/addCamera(obj/machinery/camera/c)
	if(c.can_use())
		majorChunkChange(c, 1)

// Used for Cyborg cameras. Since portable cameras can be in ANY chunk.

/datum/cameranet/proc/updatePortableCamera(obj/machinery/camera/c)
	if(c.can_use())
		majorChunkChange(c, 1)
	//else
	//	majorChunkChange(c, 0)

// Never access this proc directly!!!!
// This will update the chunk and all the surrounding chunks.
// It will also add the atom to the cameras list if you set the choice to 1.
// Setting the choice to 0 will remove the camera from the chunks.
// If you want to update the chunks around an object, without adding/removing a camera, use choice 2.

/datum/cameranet/proc/majorChunkChange(atom/c, choice)
	// 0xf = 15
	if(!c)
		return

	var/turf/T = get_turf(c)
	if(T)
		var/x1 = max(0, T.x - (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1)
		var/y1 = max(0, T.y - (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1)
		var/x2 = min(world.maxx, T.x + (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1)
		var/y2 = min(world.maxy, T.y + (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1)

		//world << "X1: [x1] - Y1: [y1] - X2: [x2] - Y2: [y2]"

		for(var/x = x1; x <= x2; x += CHUNK_SIZE)
			for(var/y = y1; y <= y2; y += CHUNK_SIZE)
				if(chunkGenerated(x, y, T.z))
					var/datum/camerachunk/chunk = getCameraChunk(x, y, T.z)
					if(choice == 0)
						// Remove the camera.
						chunk.cameras -= c
					else if(choice == 1)
						// You can't have the same camera in the list twice.
						chunk.cameras |= c
					chunk.hasChanged()

// Will check if a mob is on a viewable turf. Returns 1 if it is, otherwise returns 0.

/datum/cameranet/proc/checkCameraVis(mob/living/target)

	// 0xf = 15
	var/turf/position = get_turf(target)
	return checkTurfVis(position)

/datum/cameranet/proc/checkTurfVis(turf/position)
	var/datum/camerachunk/chunk = getCameraChunk(position.x, position.y, position.z)
	if(chunk)
		if(chunk.changed)
			chunk.hasChanged(1) // Update now, no matter if it's visible or not.
		if(chunk.visibleTurfs[position])
			return 1
	return 0

/datum/cameranet/proc/stat_entry()
	if(!statclick)
		statclick = new/obj/effect/statclick/debug("Initializing...", src)

	stat(name, statclick.update("Cameras: [cameranet.cameras.len] | Chunks: [cameranet.chunks.len]"))

// Debug verb for VVing the chunk that the turf is in.
/*
/turf/verb/view_chunk()
	set src in world

	if(cameranet.chunkGenerated(x, y, z))
		var/datum/camerachunk/chunk = cameranet.getCameraChunk(x, y, z)
		usr.client.debug_variables(chunk)
*/


code:
#define UPDATE_BUFFER 25 // 2.5 seconds

// CAMERA CHUNK
//
// A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed.
// Allows the AI Eye to stream these chunks and know what it can and cannot see.

/datum/camerachunk
	var/list/obscuredTurfs = list()
	var/list/visibleTurfs = list()
	var/list/obscured = list()
	var/list/cameras = list()
	var/list/turfs = list()
	var/list/seenby = list()
	var/visible = 0
	var/changed = 0
	var/updating = 0
	var/x = 0
	var/y = 0
	var/z = 0

// Add an AI eye to the chunk, then update if changed.

/datum/camerachunk/proc/add(mob/camera/aiEye/eye)
	var/client/client = eye.GetViewerClient()
	if(client)
		client.images += obscured
	eye.visibleCameraChunks += src
	visible++
	seenby += eye
	if(changed && !updating)
		update()

// Remove an AI eye from the chunk, then update if changed.

/datum/camerachunk/proc/remove(mob/camera/aiEye/eye)
	var/client/client = eye.GetViewerClient()
	if(client)
		client.images -= obscured
	eye.visibleCameraChunks -= src
	seenby -= eye
	if(visible > 0)
		visible--

// Called when a chunk has changed. I.E: A wall was deleted.

/datum/camerachunk/proc/visibilityChanged(turf/loc)
	if(!visibleTurfs[loc])
		return
	hasChanged()

// Updates the chunk, makes sure that it doesn't update too much. If the chunk isn't being watched it will
// instead be flagged to update the next time an AI Eye moves near it.

/datum/camerachunk/proc/hasChanged(update_now = 0)
	if(visible || update_now)
		if(!updating)
			updating = 1
			spawn(UPDATE_BUFFER) // Batch large changes, such as many doors opening or closing at once
				update()
				updating = 0
	else
		changed = 1

// The actual updating. It gathers the visible turfs from cameras and puts them into the appropiate lists.

/datum/camerachunk/proc/update()

	set background = BACKGROUND_ENABLED

	var/list/newVisibleTurfs = list()

	for(var/camera in cameras)
		var/obj/machinery/camera/c = camera

		if(!c)
			continue

		if(!c.can_use())
			continue

		var/turf/point = locate(src.x + (CHUNK_SIZE / 2), src.y + (CHUNK_SIZE / 2), src.z)
		if(get_dist(point, c) > CHUNK_SIZE + (CHUNK_SIZE / 2))
			continue

		for(var/turf/t in c.can_see())
			// Possible optimization: if(turfs[t]) here, rather than &= turfs afterwards.
			// List associations use a tree or hashmap of some sort (alongside the list itself)
			//  so are surprisingly fast. (significantly faster than var/thingy/x in list, in testing)
			newVisibleTurfs[t] = t

	// Removes turf that isn't in turfs.
	newVisibleTurfs &= turfs

	var/list/visAdded = newVisibleTurfs - visibleTurfs
	var/list/visRemoved = visibleTurfs - newVisibleTurfs

	visibleTurfs = newVisibleTurfs
	obscuredTurfs = turfs - newVisibleTurfs

	for(var/turf in visAdded)
		var/turf/t = turf
		if(t.obscured)
			obscured -= t.obscured
			for(var/eye in seenby)
				var/mob/camera/aiEye/m = eye
				if(!m)
					continue
				var/client/client = m.GetViewerClient()
				if(client)
					client.images -= t.obscured

	for(var/turf in visRemoved)
		var/turf/t = turf
		if(obscuredTurfs[t])
			if(!t.obscured)
				t.obscured = image('icons/effects/cameravis.dmi', t, "black", 16)

			obscured += t.obscured
			for(var/eye in seenby)
				var/mob/camera/aiEye/m = eye
				if(!m)
					seenby -= m
					continue
				var/client/client = m.GetViewerClient()
				if(client)
					client.images += t.obscured

	changed = 0

// Create a new camera chunk, since the chunks are made as they are needed.

/datum/camerachunk/New(loc, x, y, z)

	// 0xf = 15
	x &= ~(CHUNK_SIZE - 1)
	y &= ~(CHUNK_SIZE - 1)

	src.x = x
	src.y = y
	src.z = z

	for(var/obj/machinery/camera/c in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z)))
		if(c.can_use())
			cameras += c

	for(var/turf/t in block(locate(x, y, z), locate(min(x + CHUNK_SIZE - 1, world.maxx), min(y + CHUNK_SIZE - 1, world.maxy), z)))
		turfs[t] = t

	for(var/camera in cameras)
		var/obj/machinery/camera/c = camera
		if(!c)
			continue

		if(!c.can_use())
			continue

		for(var/turf/t in c.can_see())
			// Possible optimization: if(turfs[t]) here, rather than &= turfs afterwards.
			// List associations use a tree or hashmap of some sort (alongside the list itself)
			//  so are surprisingly fast. (significantly faster than var/thingy/x in list, in testing)
			visibleTurfs[t] = t

	// Removes turf that isn't in turfs.
	visibleTurfs &= turfs

	obscuredTurfs = turfs - visibleTurfs

	for(var/turf in obscuredTurfs)
		var/turf/t = turf
		if(!t.obscured)
			t.obscured = image('icons/effects/cameravis.dmi', t, "black", 16)
		obscured += t.obscured

#undef UPDATE_BUFFER


code:
// AI EYE
//
// An invisible (no icon) mob that the AI controls to look around the station with.
// It streams chunks as it moves around, which will show it what the AI can and cannot see.

/mob/camera/aiEye
	name = "Inactive AI Eye"

	invisibility = 100
	var/list/visibleCameraChunks = list()
	var/mob/living/silicon/ai/ai = null
	var/relay_speech = FALSE

// Use this when setting the aiEye's location.
// It will also stream the chunk that the new loc is in.

/mob/camera/aiEye/proc/setLoc(T)

	if(ai)
		if(!isturf(ai.loc))
			return
		T = get_turf(T)
		loc = T
		cameranet.visibility(src)
		if(ai.client)
			ai.client.eye = src
		//Holopad
		if(istype(ai.current, /obj/machinery/hologram/holopad))
			var/obj/machinery/hologram/holopad/H = ai.current
			H.move_hologram(ai)

/mob/camera/aiEye/Move()
	return 0

/mob/camera/aiEye/proc/GetViewerClient()
	if(ai)
		return ai.client
	return null

/mob/camera/aiEye/Destroy()
	ai = null
	return ..()

/atom/proc/move_camera_by_click()
	if(istype(usr, /mob/living/silicon/ai))
		var/mob/living/silicon/ai/AI = usr
		if(AI.eyeobj && AI.client.eye == AI.eyeobj)
			AI.cameraFollow = null
			if (isturf(src.loc) || isturf(src))
				AI.eyeobj.setLoc(src)

// This will move the AIEye. It will also cause lights near the eye to light up, if toggled.
// This is handled in the proc below this one.

/client/proc/AIMove(n, direct, mob/living/silicon/ai/user)

	var/initial = initial(user.sprint)
	var/max_sprint = 50

	if(user.cooldown && user.cooldown < world.timeofday) // 3 seconds
		user.sprint = initial

	for(var/i = 0; i < max(user.sprint, initial); i += 20)
		var/turf/step = get_turf(get_step(user.eyeobj, direct))
		if(step)
			user.eyeobj.setLoc(step)

	user.cooldown = world.timeofday + 5
	if(user.acceleration)
		user.sprint = min(user.sprint + 0.5, max_sprint)
	else
		user.sprint = initial

	if(!user.tracking)
		user.cameraFollow = null

	//user.unset_machine() //Uncomment this if it causes problems.
	//user.lightNearbyCamera()
	if (user.camera_light_on)
		user.light_cameras()

// Return to the Core.
/mob/living/silicon/ai/proc/view_core()

	current = null
	cameraFollow = null
	unset_machine()

	if(src.eyeobj && src.loc)
		src.eyeobj.loc = src.loc
	else
		src << "ERROR: Eyeobj not found. Creating new eye..."
		src.eyeobj = new(src.loc)
		src.eyeobj.ai = src
		src.eyeobj.name = "[src.name] (AI Eye)" // Give it a name

	eyeobj.setLoc(loc)

/mob/living/silicon/ai/verb/toggle_acceleration()
	set category = "AI Commands"
	set name = "Toggle Camera Acceleration"

	if(usr.stat == 2)
		return //won't work if dead
	acceleration = !acceleration
	usr << "Camera acceleration has been toggled [acceleration ? "on" : "off"]."

/mob/camera/aiEye/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans)
	if(relay_speech && speaker && ai && !radio_freq && speaker != ai && near_camera(speaker))
		ai.relay_speech(message, speaker, message_langs, raw_message, radio_freq, spans)


code:
// CREDITS
/*
 Initial code credit for this goes to Uristqwerty.
 Debugging, functionality, all comments and porting by Giacom.

 Everything about freelook (or what we can put in here) will be stored here.

 WHAT IS THIS?

 This is a replacement for the current camera movement system, of the AI. Before this, the AI had to move between cameras and could
 only see what the cameras could see. Not only this but the cameras could see through walls, which created problems.
 With this, the AI controls an "AI Eye" mob, which moves just like a ghost; such as moving through walls and being invisible to players.
 The AI's eye is set to this mob and then we use a system (explained below) to determine what the cameras around the AI Eye can and
 cannot see. If the camera cannot see a turf, it will black it out, otherwise it won't and the AI will be able to see it.
 This creates several features, such as.. no more see-through-wall cameras, easier to control camera movement, easier tracking,
 the AI only being able to track mobs which are visible to a camera, only trackable mobs appearing on the mob list and many more.

 HOW IT WORKS

 It works by first creating a camera network datum. Inside of this camera network are "chunks" (which will be
 explained later) and "cameras". The cameras list is kept up to date by obj/machinery/camera/New() and Del().

 Next the camera network has chunks. These chunks are a 16x16 tile block of turfs and cameras contained inside the chunk.
 These turfs are then sorted out based on what the cameras can and cannot see. If none of the cameras can see the turf, inside
 the 16x16 block, it is listed as an "obscured" turf. Meaning the AI won't be able to see it.

 HOW IT UPDATES

 The camera network uses a streaming method in order to effeciently update chunks. Since the server will have doors opening, doors closing,
 turf being destroyed and other lag inducing stuff, we want to update it under certain conditions and not every tick.

 The chunks are not created straight away, only when an AI eye moves into it's area is when it gets created.
 One a chunk is created, when a non glass door opens/closes or an opacity turf is destroyed, we check to see if an AI Eye is looking in the area.
 We do this with the "seenby" list, which updates everytime an AI is near a chunk. If there is an AI eye inside the area, we update the chunk
 that the changed atom is inside and all surrounding chunks, since a camera's vision could leak onto another chunk. If there is no AI Eye, we instead
 flag the chunk to update whenever it is loaded by an AI Eye. This is basically how the chunks update and keep it in sync. We then add some lag reducing
 measures, such as an UPDATE_BUFFER which stops a chunk from updating too many times in a certain time-frame, only updating if the changed atom was blocking
 sight; for example, we don't update glass airlocks or floors.

 WHERE IS EVERYTHING?

 cameranet.dm	=	Everything about the cameranet datum.
 chunk.dm		=	Everything about the chunk datum.
 eye.dm			=	Everything about the AI and the AIEye.
 updating.dm	=	Everything about triggers that will update chunks.

*/


code:
/mob/living/silicon/ai/proc/show_laws_verb()
	set category = "AI Commands"
	set name = "Show Laws"
	if(usr.stat == 2)
		return //won't work if dead
	src.show_laws()

/mob/living/silicon/ai/show_laws(everyone = 0)
	var/who

	if (everyone)
		who = world
	else
		who = src
	who << "<b>Obey these laws:</b>"

	src.laws_sanity_check()
	src.laws.show_laws(who)
	if(!everyone)
		for(var/mob/living/silicon/robot/R in connected_robots)
			if(R.lawupdate)
				R.lawsync()
				R.show_laws()
				R.law_change_counter++


code:
#define POWER_RESTORATION_OFF 0
#define POWER_RESTORATION_START 1
#define POWER_RESTORATION_SEARCH_APC 2
#define POWER_RESTORATION_APC_FOUND 3

/mob/living/silicon/ai/Life()
	if (src.stat == DEAD)
		return
	else //I'm not removing that shitton of tabs, unneeded as they are. -- Urist
		//Being dead doesn't mean your temperature never changes

		update_gravity(mob_has_gravity())

		if(malfhack)
			if(malfhack.aidisabled)
				src << "ERROR: APC access disabled, hack attempt canceled."
				malfhacking = 0
				malfhack = null

		if(machine)
			machine.check_eye(src)

		// Handle power damage (oxy)
		if(aiRestorePowerRoutine)
			// Lost power
			adjustOxyLoss(1)
		else
			// Gain Power
			if(getOxyLoss())
				adjustOxyLoss(-1)

		if(!lacks_power())
			var/area/home = get_area(src)
			if(home.powered(EQUIP))
				home.use_power(1000, EQUIP)

			if(aiRestorePowerRoutine >= POWER_RESTORATION_SEARCH_APC)
				ai_restore_power()
				return

		else if(!aiRestorePowerRoutine)
			ai_lose_power()

/mob/living/silicon/ai/proc/lacks_power()
	var/turf/T = get_turf(src)
	var/area/A = get_area(src)
	return !T || !A || ((!A.master.power_equip || istype(T, /turf/open/space)) && !is_type_in_list(src.loc, list(/obj/item, /obj/mecha)))

/mob/living/silicon/ai/updatehealth()
	if(status_flags & GODMODE)
		return
	health = maxHealth - getOxyLoss() - getToxLoss() - getBruteLoss()
	if(!fire_res_on_core)
		health -= getFireLoss()
	update_stat()
	diag_hud_set_health()

/mob/living/silicon/ai/update_stat()
	if(status_flags & GODMODE)
		return
	if(stat != DEAD)
		if(health <= config.health_threshold_dead)
			death()
			return
		else if(stat == UNCONSCIOUS)
			stat = CONSCIOUS
			adjust_blindness(-1)
	diag_hud_set_status()

/mob/living/silicon/ai/update_sight()
	see_invisible = initial(see_invisible)
	see_in_dark = initial(see_in_dark)
	sight = initial(sight)
	if(aiRestorePowerRoutine)
		sight = sight&~SEE_TURFS
		sight = sight&~SEE_MOBS
		sight = sight&~SEE_OBJS
		see_in_dark = 0

	if(see_override)
		see_invisible = see_override

/mob/living/silicon/ai/proc/start_RestorePowerRoutine()
	src << "Backup battery online. Scanners, camera, and radio interface offline. Beginning fault-detection."
	sleep(50)
	var/turf/T = get_turf(src)
	var/area/AIarea = get_area(src)
	if(AIarea && AIarea.master.power_equip)
		if(!istype(T, /turf/open/space))
			ai_restore_power()
			return
	src << "Fault confirmed: missing external power. Shutting down main control system to save power."
	sleep(20)
	src << "Emergency control system online. Verifying connection to power network."
	sleep(50)
	T = get_turf(src)
	if (istype(T, /turf/open/space))
		src << "Unable to verify! No power connection detected!"
		aiRestorePowerRoutine = POWER_RESTORATION_SEARCH_APC
		return
	src << "Connection verified. Searching for APC in power network."
	sleep(50)
	var/obj/machinery/power/apc/theAPC = null

	var/PRP //like ERP with the code, at least this stuff is no more 4x sametext
	for (PRP=1, PRP<=4, PRP++)
		T = get_turf(src)
		AIarea = get_area(src)
		if(AIarea)
			for(var/area/A in AIarea.master.related)
				for (var/obj/machinery/power/apc/APC in A)
					if (!(APC.stat & BROKEN))
						theAPC = APC
						break
		if (!theAPC)
			switch(PRP)
				if(1)
					src << "Unable to locate APC!"
				else
					src << "Lost connection with the APC!"
			aiRestorePowerRoutine = POWER_RESTORATION_SEARCH_APC
			return
		if(AIarea.master.power_equip)
			if (!istype(T, /turf/open/space))
				ai_restore_power()
				return
		switch(PRP)
			if (1) src << "APC located. Optimizing route to APC to avoid needless power waste."
			if (2) src << "Best route identified. Hacking offline APC power port."
			if (3) src << "Power port upload access confirmed. Loading control program into APC power port software."
			if (4)
				src << "Transfer complete. Forcing APC to execute program."
				sleep(50)
				src << "Receiving control information from APC."
				sleep(2)
				apc_override = 1
				theAPC.ui_interact(src, state = conscious_state)
				apc_override = 0
				aiRestorePowerRoutine = POWER_RESTORATION_APC_FOUND
				src << "Here are your current laws:"
				show_laws()
		sleep(50)
		theAPC = null

/mob/living/silicon/ai/proc/ai_restore_power()
	if(aiRestorePowerRoutine)
		if(aiRestorePowerRoutine == POWER_RESTORATION_APC_FOUND)
			src << "Alert cancelled. Power has been restored."
		else
			src << "Alert cancelled. Power has been restored without our assistance."
		aiRestorePowerRoutine = POWER_RESTORATION_OFF
		set_blindness(0)
		update_sight()

/mob/living/silicon/ai/proc/ai_lose_power()
	aiRestorePowerRoutine = POWER_RESTORATION_START
	blind_eyes(1)
	update_sight()
	src << "You've lost power!"
	spawn(20)
		start_RestorePowerRoutine()

#undef POWER_RESTORATION_OFF
#undef POWER_RESTORATION_START
#undef POWER_RESTORATION_SEARCH_APC
#undef POWER_RESTORATION_APC_FOUND


code:
/mob/living/silicon/ai/Login()
	..()
	for(var/obj/effect/rune/rune in world)
		var/image/blood = image(loc = rune)
		blood.override = 1
		client.images += blood

	if(stat != DEAD)
		for(var/obj/machinery/ai_status_display/O in machines) //change status
			O.mode = 1
			O.emotion = "Neutral"
	view_core()


code:
/mob/living/silicon/ai/Logout()
	..()
	for(var/obj/machinery/ai_status_display/O in world) //change status
		O.mode = 0
	view_core()


code:
/mob/living/silicon/ai/say(message)
	if(parent && istype(parent) && parent.stat != 2) //If there is a defined "parent" AI, it is actually an AI, and it is alive, anything the AI tries to say is said by the parent instead.
		parent.say(message)
		return
	..(message)

/mob/living/silicon/ai/compose_track_href(atom/movable/speaker, namepart)
	var/mob/M = speaker.GetSource()
	if(M)
		return ""
	return ""

/mob/living/silicon/ai/compose_job(atom/movable/speaker, message_langs, raw_message, radio_freq)
	//Also includes the  for AI hrefs, for convenience.
	return "[radio_freq ? " (" + speaker.GetJob() + ")" : ""]" + "[speaker.GetSource() ? "" : ""]"

/mob/living/silicon/ai/IsVocal()
	return !config.silent_ai

/mob/living/silicon/ai/radio(message, message_mode, list/spans)
	if(!radio_enabled || aiRestorePowerRoutine || stat) //AI cannot speak if radio is disabled (via intellicard) or depowered.
		src << "Your radio transmitter is offline!"
		return 0
	..()

/mob/living/silicon/ai/get_message_mode(message)
	if(copytext(message, 1, 3) in list(":h", ":H", ".h", ".H", "#h", "#H"))
		return MODE_HOLOPAD
	else
		return ..()

/mob/living/silicon/ai/handle_inherent_channels(message, message_mode)
	. = ..()
	if(.)
		return .

	if(message_mode == MODE_HOLOPAD)
		holopad_talk(message)
		return 1

//For holopads only. Usable by AI.
/mob/living/silicon/ai/proc/holopad_talk(message)
	log_say("[key_name(src)] : [message]")

	message = trim(message)

	if (!message)
		return

	var/obj/machinery/hologram/holopad/T = current
	if(istype(T) && T.masters[src])//If there is a hologram and its master is the user.
		send_speech(message, 7, T, "robot", get_spans())
		src << "Holopad transmitted, [real_name] \"[message]\""//The AI can "hear" its own message.
	else
		src << "No holopad connected."
	return

// Make sure that the code compiles with AI_VOX undefined
#ifdef AI_VOX

var/announcing_vox = 0 // Stores the time of the last announcement
var/const/VOX_CHANNEL = 200
var/const/VOX_DELAY = 600

/mob/living/silicon/ai/verb/announcement_help()

	set name = "Announcement Help"
	set desc = "Display a list of vocal words to announce to the crew."
	set category = "AI Commands"

	if(usr.stat == 2)
		return //won't work if dead

	var/dat = "Here is a list of words you can type into the 'Announcement' button to create sentences to vocally announce to everyone on the same level at you.
 \

You can also click on the word to preview it.
\

You can only say 30 words for every announcement.
\

Do not use punctuation as you would normally, if you want a pause you can use the full stop and comma characters by separating them with spaces, like so: 'Alpha . Test , Bravo'.

\
	WARNING:
Misuse of the announcement system will get you job banned.
"

	var/index = 0
	for(var/word in vox_sounds)
		index++
		dat += "[capitalize(word)]"
		if(index != vox_sounds.len)
			dat += " / "

	var/datum/browser/popup = new(src, "announce_help", "Announcement Help", 500, 400)
	popup.set_content(dat)
	popup.open()

/mob/living/silicon/ai/proc/announcement()
	if(announcing_vox > world.time)
		src << "Please wait [round((announcing_vox - world.time) / 10)] seconds."
		return

	var/message = input(src, "WARNING: Misuse of this verb can result in you being job banned. More help is available in 'Announcement Help'", "Announcement", src.last_announcement) as text

	last_announcement = message

	if(!message || announcing_vox > world.time)
		return

	if(stat != CONSCIOUS)
		return

	if(control_disabled)
		src << "Wireless interface disabled, unable to interact with announcement PA."
		return

	var/list/words = splittext(trim(message), " ")
	var/list/incorrect_words = list()

	if(words.len > 30)
		words.len = 30

	for(var/word in words)
		word = lowertext(trim(word))
		if(!word)
			words -= word
			continue
		if(!vox_sounds[word])
			incorrect_words += word

	if(incorrect_words.len)
		src << "These words are not available on the announcement system: [english_list(incorrect_words)]."
		return

	announcing_vox = world.time + VOX_DELAY

	log_game("[key_name(src)] made a vocal announcement with the following message: [message].")

	for(var/word in words)
		play_vox_word(word, src.z, null)
/*
	for(var/mob/M in player_list)
		if(M.client)
			var/turf/T = get_turf(M)
			var/turf/our_turf = get_turf(src)
			if(T.z == our_turf.z)
				M << "AI announcement: [message]"
*/

/proc/play_vox_word(word, z_level, mob/only_listener)

	word = lowertext(word)

	if(vox_sounds[word])

		var/sound_file = vox_sounds[word]
		var/sound/voice = sound(sound_file, wait = 1, channel = VOX_CHANNEL)
		voice.status = SOUND_STREAM

 		// If there is no single listener, broadcast to everyone in the same z level
		if(!only_listener)
			// Play voice for all mobs in the z level
			for(var/mob/M in player_list)
				if(M.client && !M.ear_deaf && !(M.client.prefs.toggles & SOUND_ANNOUNCEMENTS))
					var/turf/T = get_turf(M)
					if(T.z == z_level)
						M << voice
		else
			only_listener << voice
		return 1
	return 0

#endif


code:
// List is required to compile the resources into the game when it loads.
// Dynamically loading it has bad results with sounds overtaking each other, even with the wait variable.
#ifdef AI_VOX

var/list/vox_sounds = list("," = 'sound/vox_fem/,.ogg',
"." = 'sound/vox_fem/..ogg',
"a" = 'sound/vox_fem/a.ogg',
"abortions" = 'sound/vox_fem/abortions.ogg',
"accelerating" = 'sound/vox_fem/accelerating.ogg',
"accelerator" = 'sound/vox_fem/accelerator.ogg',
"accepted" = 'sound/vox_fem/accepted.ogg',
"access" = 'sound/vox_fem/access.ogg',
"acknowledge" = 'sound/vox_fem/acknowledge.ogg',
"acknowledged" = 'sound/vox_fem/acknowledged.ogg',
"acquired" = 'sound/vox_fem/acquired.ogg',
"acquisition" = 'sound/vox_fem/acquisition.ogg',
"across" = 'sound/vox_fem/across.ogg',
"activate" = 'sound/vox_fem/activate.ogg',
"activated" = 'sound/vox_fem/activated.ogg',
"activity" = 'sound/vox_fem/activity.ogg',
"adios" = 'sound/vox_fem/adios.ogg',
"administration" = 'sound/vox_fem/administration.ogg',
"advanced" = 'sound/vox_fem/advanced.ogg',
"aft" = 'sound/vox_fem/aft.ogg',
"after" = 'sound/vox_fem/after.ogg',
"agent" = 'sound/vox_fem/agent.ogg',
"ai" = 'sound/vox_fem/ai.ogg',
"alarm" = 'sound/vox_fem/alarm.ogg',
"alert" = 'sound/vox_fem/alert.ogg',
"alien" = 'sound/vox_fem/alien.ogg',
"aligned" = 'sound/vox_fem/aligned.ogg',
"all" = 'sound/vox_fem/all.ogg',
"alpha" = 'sound/vox_fem/alpha.ogg',
"am" = 'sound/vox_fem/am.ogg',
"amigo" = 'sound/vox_fem/amigo.ogg',
"ammunition" = 'sound/vox_fem/ammunition.ogg',
"an" = 'sound/vox_fem/an.ogg',
"and" = 'sound/vox_fem/and.ogg',
"announcement" = 'sound/vox_fem/announcement.ogg',
"anomalous" = 'sound/vox_fem/anomalous.ogg',
"antenna" = 'sound/vox_fem/antenna.ogg',
"any" = 'sound/vox_fem/any.ogg',
"apprehend" = 'sound/vox_fem/apprehend.ogg',
"approach" = 'sound/vox_fem/approach.ogg',
"are" = 'sound/vox_fem/are.ogg',
"area" = 'sound/vox_fem/area.ogg',
"arm" = 'sound/vox_fem/arm.ogg',
"armed" = 'sound/vox_fem/armed.ogg',
"armor" = 'sound/vox_fem/armor.ogg',
"armory" = 'sound/vox_fem/armory.ogg',
"array" = 'sound/vox_fem/array.ogg',
"arrest" = 'sound/vox_fem/arrest.ogg',
"asimov" = 'sound/vox_fem/asimov.ogg',
"ass" = 'sound/vox_fem/ass.ogg',
"asshole" = 'sound/vox_fem/asshole.ogg',
"assholes" = 'sound/vox_fem/assholes.ogg',
"at" = 'sound/vox_fem/at.ogg',
"atomic" = 'sound/vox_fem/atomic.ogg',
"attention" = 'sound/vox_fem/attention.ogg',
"authorize" = 'sound/vox_fem/authorize.ogg',
"authorized" = 'sound/vox_fem/authorized.ogg',
"automatic" = 'sound/vox_fem/automatic.ogg',
"away" = 'sound/vox_fem/away.ogg',
"b" = 'sound/vox_fem/b.ogg',
"back" = 'sound/vox_fem/back.ogg',
"backman" = 'sound/vox_fem/backman.ogg',
"bad" = 'sound/vox_fem/bad.ogg',
"bag" = 'sound/vox_fem/bag.ogg',
"bailey" = 'sound/vox_fem/bailey.ogg',
"barracks" = 'sound/vox_fem/barracks.ogg',
"base" = 'sound/vox_fem/base.ogg',
"bay" = 'sound/vox_fem/bay.ogg',
"be" = 'sound/vox_fem/be.ogg',
"been" = 'sound/vox_fem/been.ogg',
"before" = 'sound/vox_fem/before.ogg',
"beyond" = 'sound/vox_fem/beyond.ogg',
"biohazard" = 'sound/vox_fem/biohazard.ogg',
"biological" = 'sound/vox_fem/biological.ogg',
"birdwell" = 'sound/vox_fem/birdwell.ogg',
"bitch" = 'sound/vox_fem/bitch.ogg',
"bitches" = 'sound/vox_fem/bitches.ogg',
"black" = 'sound/vox_fem/black.ogg',
"blast" = 'sound/vox_fem/blast.ogg',
"blocked" = 'sound/vox_fem/blocked.ogg',
"blue" = 'sound/vox_fem/blue.ogg',
"bottom" = 'sound/vox_fem/bottom.ogg',
"bravo" = 'sound/vox_fem/bravo.ogg',
"breach" = 'sound/vox_fem/breach.ogg',
"breached" = 'sound/vox_fem/breached.ogg',
"break" = 'sound/vox_fem/break.ogg',
"bridge" = 'sound/vox_fem/bridge.ogg',
"bust" = 'sound/vox_fem/bust.ogg',
"but" = 'sound/vox_fem/but.ogg',
"button" = 'sound/vox_fem/button.ogg',
"bypass" = 'sound/vox_fem/bypass.ogg',
"c" = 'sound/vox_fem/c.ogg',
"cable" = 'sound/vox_fem/cable.ogg',
"call" = 'sound/vox_fem/call.ogg',
"called" = 'sound/vox_fem/called.ogg',
"canal" = 'sound/vox_fem/canal.ogg',
"cap" = 'sound/vox_fem/cap.ogg',
"captain" = 'sound/vox_fem/captain.ogg',
"capture" = 'sound/vox_fem/capture.ogg',
"cargo" = 'sound/vox_fem/cargo.ogg',
"ceiling" = 'sound/vox_fem/ceiling.ogg',
"celsius" = 'sound/vox_fem/celsius.ogg',
"centcom" = 'sound/vox_fem/centcom.ogg',
"center" = 'sound/vox_fem/center.ogg',
"centi" = 'sound/vox_fem/centi.ogg',
"central" = 'sound/vox_fem/central.ogg',
"chamber" = 'sound/vox_fem/chamber.ogg',
"changed" = 'sound/vox_fem/changed.ogg',
"charlie" = 'sound/vox_fem/charlie.ogg',
"check" = 'sound/vox_fem/check.ogg',
"checkpoint" = 'sound/vox_fem/checkpoint.ogg',
"chemical" = 'sound/vox_fem/chemical.ogg',
"cleanup" = 'sound/vox_fem/cleanup.ogg',
"clear" = 'sound/vox_fem/clear.ogg',
"clearance" = 'sound/vox_fem/clearance.ogg',
"close" = 'sound/vox_fem/close.ogg',
"clown" = 'sound/vox_fem/clown.ogg',
"code" = 'sound/vox_fem/code.ogg',
"coded" = 'sound/vox_fem/coded.ogg',
"collider" = 'sound/vox_fem/collider.ogg',
"come" = 'sound/vox_fem/come.ogg',
"command" = 'sound/vox_fem/command.ogg',
"communication" = 'sound/vox_fem/communication.ogg',
"complex" = 'sound/vox_fem/complex.ogg',
"computer" = 'sound/vox_fem/computer.ogg',
"condition" = 'sound/vox_fem/condition.ogg',
"connor" = 'sound/vox_fem/connor.ogg',
"containment" = 'sound/vox_fem/containment.ogg',
"contamination" = 'sound/vox_fem/contamination.ogg',
"contraband" = 'sound/vox_fem/contraband.ogg',
"control" = 'sound/vox_fem/control.ogg',
"coolant" = 'sound/vox_fem/coolant.ogg',
"coomer" = 'sound/vox_fem/coomer.ogg',
"core" = 'sound/vox_fem/core.ogg',
"correct" = 'sound/vox_fem/correct.ogg',
"corridor" = 'sound/vox_fem/corridor.ogg',
"coward" = 'sound/vox_fem/coward.ogg',
"cowards" = 'sound/vox_fem/cowards.ogg',
"crew" = 'sound/vox_fem/crew.ogg',
"cross" = 'sound/vox_fem/cross.ogg',
"cryogenic" = 'sound/vox_fem/cryogenic.ogg',
"cunt" = 'sound/vox_fem/cunt.ogg',
"cyborg" = 'sound/vox_fem/cyborg.ogg',
"cyborgs" = 'sound/vox_fem/cyborgs.ogg',
"d" = 'sound/vox_fem/d.ogg',
"damage" = 'sound/vox_fem/damage.ogg',
"damaged" = 'sound/vox_fem/damaged.ogg',
"danger" = 'sound/vox_fem/danger.ogg',
"day" = 'sound/vox_fem/day.ogg',
"deactivated" = 'sound/vox_fem/deactivated.ogg',
"decompression" = 'sound/vox_fem/decompression.ogg',
"decontamination" = 'sound/vox_fem/decontamination.ogg',
"deeoo" = 'sound/vox_fem/deeoo.ogg',
"defense" = 'sound/vox_fem/defense.ogg',
"degrees" = 'sound/vox_fem/degrees.ogg',
"delta" = 'sound/vox_fem/delta.ogg',
"denied" = 'sound/vox_fem/denied.ogg',
"deploy" = 'sound/vox_fem/deploy.ogg',
"deployed" = 'sound/vox_fem/deployed.ogg',
"destroy" = 'sound/vox_fem/destroy.ogg',
"destroyed" = 'sound/vox_fem/destroyed.ogg',
"detain" = 'sound/vox_fem/detain.ogg',
"detected" = 'sound/vox_fem/detected.ogg',
"detonation" = 'sound/vox_fem/detonation.ogg',
"device" = 'sound/vox_fem/device.ogg',
"did" = 'sound/vox_fem/did.ogg',
"die" = 'sound/vox_fem/die.ogg',
"dimensional" = 'sound/vox_fem/dimensional.ogg',
"dirt" = 'sound/vox_fem/dirt.ogg',
"disengaged" = 'sound/vox_fem/disengaged.ogg',
"dish" = 'sound/vox_fem/dish.ogg',
"disposal" = 'sound/vox_fem/disposal.ogg',
"distance" = 'sound/vox_fem/distance.ogg',
"distortion" = 'sound/vox_fem/distortion.ogg',
"do" = 'sound/vox_fem/do.ogg',
"doctor" = 'sound/vox_fem/doctor.ogg',
"door" = 'sound/vox_fem/door.ogg',
"down" = 'sound/vox_fem/down.ogg',
"dual" = 'sound/vox_fem/dual.ogg',
"duct" = 'sound/vox_fem/duct.ogg',
"e" = 'sound/vox_fem/e.ogg',
"east" = 'sound/vox_fem/east.ogg',
"echo" = 'sound/vox_fem/echo.ogg',
"ed" = 'sound/vox_fem/ed.ogg',
"effect" = 'sound/vox_fem/effect.ogg',
"egress" = 'sound/vox_fem/egress.ogg',
"eight" = 'sound/vox_fem/eight.ogg',
"eighteen" = 'sound/vox_fem/eighteen.ogg',
"eighty" = 'sound/vox_fem/eighty.ogg',
"electric" = 'sound/vox_fem/electric.ogg',
"electromagnetic" = 'sound/vox_fem/electromagnetic.ogg',
"elevator" = 'sound/vox_fem/elevator.ogg',
"eleven" = 'sound/vox_fem/eleven.ogg',
"eliminate" = 'sound/vox_fem/eliminate.ogg',
"emergency" = 'sound/vox_fem/emergency.ogg',
"energy" = 'sound/vox_fem/energy.ogg',
"engage" = 'sound/vox_fem/engage.ogg',
"engaged" = 'sound/vox_fem/engaged.ogg',
"engine" = 'sound/vox_fem/engine.ogg',
"enter" = 'sound/vox_fem/enter.ogg',
"entry" = 'sound/vox_fem/entry.ogg',
"environment" = 'sound/vox_fem/environment.ogg',
"error" = 'sound/vox_fem/error.ogg',
"escape" = 'sound/vox_fem/escape.ogg',
"evacuate" = 'sound/vox_fem/evacuate.ogg',
"exchange" = 'sound/vox_fem/exchange.ogg',
"exit" = 'sound/vox_fem/exit.ogg',
"expect" = 'sound/vox_fem/expect.ogg',
"experiment" = 'sound/vox_fem/experiment.ogg',
"experimental" = 'sound/vox_fem/experimental.ogg',
"explode" = 'sound/vox_fem/explode.ogg',
"explosion" = 'sound/vox_fem/explosion.ogg',
"exposure" = 'sound/vox_fem/exposure.ogg',
"exterminate" = 'sound/vox_fem/exterminate.ogg',
"extinguish" = 'sound/vox_fem/extinguish.ogg',
"extinguisher" = 'sound/vox_fem/extinguisher.ogg',
"extreme" = 'sound/vox_fem/extreme.ogg',
"f" = 'sound/vox_fem/f.ogg',
"facility" = 'sound/vox_fem/facility.ogg',
"fahrenheit" = 'sound/vox_fem/fahrenheit.ogg',
"failed" = 'sound/vox_fem/failed.ogg',
"failure" = 'sound/vox_fem/failure.ogg',
"farthest" = 'sound/vox_fem/farthest.ogg',
"fast" = 'sound/vox_fem/fast.ogg',
"feet" = 'sound/vox_fem/feet.ogg',
"field" = 'sound/vox_fem/field.ogg',
"fifteen" = 'sound/vox_fem/fifteen.ogg',
"fifth" = 'sound/vox_fem/fifth.ogg',
"fifty" = 'sound/vox_fem/fifty.ogg',
"final" = 'sound/vox_fem/final.ogg',
"fine" = 'sound/vox_fem/fine.ogg',
"fire" = 'sound/vox_fem/fire.ogg',
"first" = 'sound/vox_fem/first.ogg',
"five" = 'sound/vox_fem/five.ogg',
"flooding" = 'sound/vox_fem/flooding.ogg',
"floor" = 'sound/vox_fem/floor.ogg',
"fool" = 'sound/vox_fem/fool.ogg',
"for" = 'sound/vox_fem/for.ogg',
"forbidden" = 'sound/vox_fem/forbidden.ogg',
"force" = 'sound/vox_fem/force.ogg',
"fore" = 'sound/vox_fem/fore.ogg',
"forms" = 'sound/vox_fem/forms.ogg',
"found" = 'sound/vox_fem/found.ogg',
"four" = 'sound/vox_fem/four.ogg',
"fourteen" = 'sound/vox_fem/fourteen.ogg',
"fourth" = 'sound/vox_fem/fourth.ogg',
"fourty" = 'sound/vox_fem/fourty.ogg',
"foxtrot" = 'sound/vox_fem/foxtrot.ogg',
"freeman" = 'sound/vox_fem/freeman.ogg',
"freezer" = 'sound/vox_fem/freezer.ogg',
"from" = 'sound/vox_fem/from.ogg',
"front" = 'sound/vox_fem/front.ogg',
"fuck" = 'sound/vox_fem/fuck.ogg',
"fucking" = 'sound/vox_fem/fucking.ogg',
"fucks" = 'sound/vox_fem/fucks.ogg',
"fuel" = 'sound/vox_fem/fuel.ogg',
"g" = 'sound/vox_fem/g.ogg',
"gas" = 'sound/vox_fem/gas.ogg',
"get" = 'sound/vox_fem/get.ogg',
"glory" = 'sound/vox_fem/glory.ogg',
"go" = 'sound/vox_fem/go.ogg',
"going" = 'sound/vox_fem/going.ogg',
"good" = 'sound/vox_fem/good.ogg',
"goodbye" = 'sound/vox_fem/goodbye.ogg',
"gordon" = 'sound/vox_fem/gordon.ogg',
"got" = 'sound/vox_fem/got.ogg',
"government" = 'sound/vox_fem/government.ogg',
"granted" = 'sound/vox_fem/granted.ogg',
"gray" = 'sound/vox_fem/gray.ogg',
"great" = 'sound/vox_fem/great.ogg',
"green" = 'sound/vox_fem/green.ogg',
"grenade" = 'sound/vox_fem/grenade.ogg',
"guard" = 'sound/vox_fem/guard.ogg',
"gulf" = 'sound/vox_fem/gulf.ogg',
"gun" = 'sound/vox_fem/gun.ogg',
"guthrie" = 'sound/vox_fem/guthrie.ogg',
"h" = 'sound/vox_fem/h.ogg',
"hacker" = 'sound/vox_fem/hacker.ogg',
"hackers" = 'sound/vox_fem/hackers.ogg',
"handling" = 'sound/vox_fem/handling.ogg',
"hangar" = 'sound/vox_fem/hangar.ogg',
"harm" = 'sound/vox_fem/harm.ogg',
"has" = 'sound/vox_fem/has.ogg',
"have" = 'sound/vox_fem/have.ogg',
"hazard" = 'sound/vox_fem/hazard.ogg',
"head" = 'sound/vox_fem/head.ogg',
"health" = 'sound/vox_fem/health.ogg',
"heat" = 'sound/vox_fem/heat.ogg',
"helicopter" = 'sound/vox_fem/helicopter.ogg',
"helium" = 'sound/vox_fem/helium.ogg',
"hello" = 'sound/vox_fem/hello.ogg',
"help" = 'sound/vox_fem/help.ogg',
"here" = 'sound/vox_fem/here.ogg',
"hide" = 'sound/vox_fem/hide.ogg',
"high" = 'sound/vox_fem/high.ogg',
"highest" = 'sound/vox_fem/highest.ogg',
"hit" = 'sound/vox_fem/hit.ogg',
"hole" = 'sound/vox_fem/hole.ogg',
"hostile" = 'sound/vox_fem/hostile.ogg',
"hot" = 'sound/vox_fem/hot.ogg',
"hotel" = 'sound/vox_fem/hotel.ogg',
"hour" = 'sound/vox_fem/hour.ogg',
"hours" = 'sound/vox_fem/hours.ogg',
"human" = 'sound/vox_fem/human.ogg',
"hundred" = 'sound/vox_fem/hundred.ogg',
"hunger" = 'sound/vox_fem/hunger.ogg',
"hydro" = 'sound/vox_fem/hydro.ogg',
"hydroponics" = 'sound/vox_fem/hydroponics.ogg',
"i" = 'sound/vox_fem/i.ogg',
"idiot" = 'sound/vox_fem/idiot.ogg',
"illegal" = 'sound/vox_fem/illegal.ogg',
"immediate" = 'sound/vox_fem/immediate.ogg',
"immediately" = 'sound/vox_fem/immediately.ogg',
"in" = 'sound/vox_fem/in.ogg',
"inches" = 'sound/vox_fem/inches.ogg',
"india" = 'sound/vox_fem/india.ogg',
"ing" = 'sound/vox_fem/ing.ogg',
"inoperative" = 'sound/vox_fem/inoperative.ogg',
"inside" = 'sound/vox_fem/inside.ogg',
"inspection" = 'sound/vox_fem/inspection.ogg',
"inspector" = 'sound/vox_fem/inspector.ogg',
"interchange" = 'sound/vox_fem/interchange.ogg',
"intruder" = 'sound/vox_fem/intruder.ogg',
"invalid" = 'sound/vox_fem/invalid.ogg',
"invasion" = 'sound/vox_fem/invasion.ogg',
"is" = 'sound/vox_fem/is.ogg',
"it" = 'sound/vox_fem/it.ogg',
"j" = 'sound/vox_fem/j.ogg',
"johnson" = 'sound/vox_fem/johnson.ogg',
"juliet" = 'sound/vox_fem/juliet.ogg',
"k" = 'sound/vox_fem/k.ogg',
"key" = 'sound/vox_fem/key.ogg',
"kill" = 'sound/vox_fem/kill.ogg',
"kilo" = 'sound/vox_fem/kilo.ogg',
"kit" = 'sound/vox_fem/kit.ogg',
"l" = 'sound/vox_fem/l.ogg',
"lab" = 'sound/vox_fem/lab.ogg',
"lambda" = 'sound/vox_fem/lambda.ogg',
"laser" = 'sound/vox_fem/laser.ogg',
"last" = 'sound/vox_fem/last.ogg',
"launch" = 'sound/vox_fem/launch.ogg',
"law" = 'sound/vox_fem/law.ogg',
"laws" = 'sound/vox_fem/laws.ogg',
"leak" = 'sound/vox_fem/leak.ogg',
"leave" = 'sound/vox_fem/leave.ogg',
"left" = 'sound/vox_fem/left.ogg',
"legal" = 'sound/vox_fem/legal.ogg',
"level" = 'sound/vox_fem/level.ogg',
"lever" = 'sound/vox_fem/lever.ogg',
"lie" = 'sound/vox_fem/lie.ogg',
"lieutenant" = 'sound/vox_fem/lieutenant.ogg',
"life" = 'sound/vox_fem/life.ogg',
"light" = 'sound/vox_fem/light.ogg',
"lima" = 'sound/vox_fem/lima.ogg',
"liquid" = 'sound/vox_fem/liquid.ogg',
"loading" = 'sound/vox_fem/loading.ogg',
"locate" = 'sound/vox_fem/locate.ogg',
"located" = 'sound/vox_fem/located.ogg',
"location" = 'sound/vox_fem/location.ogg',
"lock" = 'sound/vox_fem/lock.ogg',
"locked" = 'sound/vox_fem/locked.ogg',
"locker" = 'sound/vox_fem/locker.ogg',
"lockout" = 'sound/vox_fem/lockout.ogg',
"loose" = 'sound/vox_fem/loose.ogg',
"lower" = 'sound/vox_fem/lower.ogg',
"lowest" = 'sound/vox_fem/lowest.ogg',
"m" = 'sound/vox_fem/m.ogg',
"magnetic" = 'sound/vox_fem/magnetic.ogg',
"main" = 'sound/vox_fem/main.ogg',
"maintenance" = 'sound/vox_fem/maintenance.ogg',
"malfunction" = 'sound/vox_fem/malfunction.ogg',
"man" = 'sound/vox_fem/man.ogg',
"mass" = 'sound/vox_fem/mass.ogg',
"materials" = 'sound/vox_fem/materials.ogg',
"maximum" = 'sound/vox_fem/maximum.ogg',
"may" = 'sound/vox_fem/may.ogg',
"me" = 'sound/vox_fem/me.ogg',
"medbay" = 'sound/vox_fem/medbay.ogg',
"medical" = 'sound/vox_fem/medical.ogg',
"men" = 'sound/vox_fem/men.ogg',
"mercy" = 'sound/vox_fem/mercy.ogg',
"mesa" = 'sound/vox_fem/mesa.ogg',
"message" = 'sound/vox_fem/message.ogg',
"meter" = 'sound/vox_fem/meter.ogg',
"micro" = 'sound/vox_fem/micro.ogg',
"middle" = 'sound/vox_fem/middle.ogg',
"mike" = 'sound/vox_fem/mike.ogg',
"miles" = 'sound/vox_fem/miles.ogg',
"military" = 'sound/vox_fem/military.ogg',
"milli" = 'sound/vox_fem/milli.ogg',
"million" = 'sound/vox_fem/million.ogg',
"minefield" = 'sound/vox_fem/minefield.ogg',
"minimum" = 'sound/vox_fem/minimum.ogg',
"minutes" = 'sound/vox_fem/minutes.ogg',
"mister" = 'sound/vox_fem/mister.ogg',
"mode" = 'sound/vox_fem/mode.ogg',
"money" = 'sound/vox_fem/money.ogg',
"motor" = 'sound/vox_fem/motor.ogg',
"motorpool" = 'sound/vox_fem/motorpool.ogg',
"move" = 'sound/vox_fem/move.ogg',
"must" = 'sound/vox_fem/must.ogg',
"my" = 'sound/vox_fem/my.ogg',
"n" = 'sound/vox_fem/n.ogg',
"nanotrasen" = 'sound/vox_fem/nanotrasen.ogg',
"nearest" = 'sound/vox_fem/nearest.ogg',
"nice" = 'sound/vox_fem/nice.ogg',
"nine" = 'sound/vox_fem/nine.ogg',
"nineteen" = 'sound/vox_fem/nineteen.ogg',
"ninety" = 'sound/vox_fem/ninety.ogg',
"no" = 'sound/vox_fem/no.ogg',
"nominal" = 'sound/vox_fem/nominal.ogg',
"north" = 'sound/vox_fem/north.ogg',
"not" = 'sound/vox_fem/not.ogg',
"november" = 'sound/vox_fem/november.ogg',
"now" = 'sound/vox_fem/now.ogg',
"number" = 'sound/vox_fem/number.ogg',
"o" = 'sound/vox_fem/o.ogg',
"objective" = 'sound/vox_fem/objective.ogg',
"observation" = 'sound/vox_fem/observation.ogg',
"obtain" = 'sound/vox_fem/obtain.ogg',
"of" = 'sound/vox_fem/of.ogg',
"officer" = 'sound/vox_fem/officer.ogg',
"ok" = 'sound/vox_fem/ok.ogg',
"on" = 'sound/vox_fem/on.ogg',
"one" = 'sound/vox_fem/one.ogg',
"open" = 'sound/vox_fem/open.ogg',
"operating" = 'sound/vox_fem/operating.ogg',
"operations" = 'sound/vox_fem/operations.ogg',
"operative" = 'sound/vox_fem/operative.ogg',
"option" = 'sound/vox_fem/option.ogg',
"order" = 'sound/vox_fem/order.ogg',
"organic" = 'sound/vox_fem/organic.ogg',
"oscar" = 'sound/vox_fem/oscar.ogg',
"out" = 'sound/vox_fem/out.ogg',
"outside" = 'sound/vox_fem/outside.ogg',
"over" = 'sound/vox_fem/over.ogg',
"overload" = 'sound/vox_fem/overload.ogg',
"override" = 'sound/vox_fem/override.ogg',
"p" = 'sound/vox_fem/p.ogg',
"pacify" = 'sound/vox_fem/pacify.ogg',
"pain" = 'sound/vox_fem/pain.ogg',
"pal" = 'sound/vox_fem/pal.ogg',
"panel" = 'sound/vox_fem/panel.ogg',
"percent" = 'sound/vox_fem/percent.ogg',
"perimeter" = 'sound/vox_fem/perimeter.ogg',
"permitted" = 'sound/vox_fem/permitted.ogg',
"personnel" = 'sound/vox_fem/personnel.ogg',
"pipe" = 'sound/vox_fem/pipe.ogg',
"plant" = 'sound/vox_fem/plant.ogg',
"plasma" = 'sound/vox_fem/plasma.ogg',
"platform" = 'sound/vox_fem/platform.ogg',
"please" = 'sound/vox_fem/please.ogg',
"point" = 'sound/vox_fem/point.ogg',
"port" = 'sound/vox_fem/port.ogg',
"portal" = 'sound/vox_fem/portal.ogg',
"power" = 'sound/vox_fem/power.ogg',
"presence" = 'sound/vox_fem/presence.ogg',
"press" = 'sound/vox_fem/press.ogg',
"primary" = 'sound/vox_fem/primary.ogg',
"proceed" = 'sound/vox_fem/proceed.ogg',
"processing" = 'sound/vox_fem/processing.ogg',
"progress" = 'sound/vox_fem/progress.ogg',
"proper" = 'sound/vox_fem/proper.ogg',
"propulsion" = 'sound/vox_fem/propulsion.ogg',
"prosecute" = 'sound/vox_fem/prosecute.ogg',
"protective" = 'sound/vox_fem/protective.ogg',
"push" = 'sound/vox_fem/push.ogg',
"q" = 'sound/vox_fem/q.ogg',
"quantum" = 'sound/vox_fem/quantum.ogg',
"quebec" = 'sound/vox_fem/quebec.ogg',
"queen" = 'sound/vox_fem/queen.ogg',
"question" = 'sound/vox_fem/question.ogg',
"questioning" = 'sound/vox_fem/questioning.ogg',
"quick" = 'sound/vox_fem/quick.ogg',
"quit" = 'sound/vox_fem/quit.ogg',
"r" = 'sound/vox_fem/r.ogg',
"radiation" = 'sound/vox_fem/radiation.ogg',
"radioactive" = 'sound/vox_fem/radioactive.ogg',
"rads" = 'sound/vox_fem/rads.ogg',
"raider" = 'sound/vox_fem/raider.ogg',
"raiders" = 'sound/vox_fem/raiders.ogg',
"rapid" = 'sound/vox_fem/rapid.ogg',
"reach" = 'sound/vox_fem/reach.ogg',
"reached" = 'sound/vox_fem/reached.ogg',
"reactor" = 'sound/vox_fem/reactor.ogg',
"red" = 'sound/vox_fem/red.ogg',
"relay" = 'sound/vox_fem/relay.ogg',
"released" = 'sound/vox_fem/released.ogg',
"remaining" = 'sound/vox_fem/remaining.ogg',
"removal" = 'sound/vox_fem/removal.ogg',
"renegade" = 'sound/vox_fem/renegade.ogg',
"repair" = 'sound/vox_fem/repair.ogg',
"report" = 'sound/vox_fem/report.ogg',
"reports" = 'sound/vox_fem/reports.ogg',
"required" = 'sound/vox_fem/required.ogg',
"research" = 'sound/vox_fem/research.ogg',
"resevoir" = 'sound/vox_fem/resevoir.ogg',
"resistance" = 'sound/vox_fem/resistance.ogg',
"rest" = 'sound/vox_fem/rest.ogg',
"right" = 'sound/vox_fem/right.ogg',
"rocket" = 'sound/vox_fem/rocket.ogg',
"roger" = 'sound/vox_fem/roger.ogg',
"romeo" = 'sound/vox_fem/romeo.ogg',
"room" = 'sound/vox_fem/room.ogg',
"round" = 'sound/vox_fem/round.ogg',
"run" = 'sound/vox_fem/run.ogg',
"s" = 'sound/vox_fem/s.ogg',
"safe" = 'sound/vox_fem/safe.ogg',
"safety" = 'sound/vox_fem/safety.ogg',
"sarah" = 'sound/vox_fem/sarah.ogg',
"sargeant" = 'sound/vox_fem/sargeant.ogg',
"satellite" = 'sound/vox_fem/satellite.ogg',
"save" = 'sound/vox_fem/save.ogg',
"science" = 'sound/vox_fem/science.ogg',
"scream" = 'sound/vox_fem/scream.ogg',
"screen" = 'sound/vox_fem/screen.ogg',
"search" = 'sound/vox_fem/search.ogg',
"second" = 'sound/vox_fem/second.ogg',
"secondary" = 'sound/vox_fem/secondary.ogg',
"seconds" = 'sound/vox_fem/seconds.ogg',
"sector" = 'sound/vox_fem/sector.ogg',
"secure" = 'sound/vox_fem/secure.ogg',
"secured" = 'sound/vox_fem/secured.ogg',
"security" = 'sound/vox_fem/security.ogg',
"select" = 'sound/vox_fem/select.ogg',
"selected" = 'sound/vox_fem/selected.ogg',
"sensors" = 'sound/vox_fem/sensors.ogg',
"service" = 'sound/vox_fem/service.ogg',
"seven" = 'sound/vox_fem/seven.ogg',
"seventeen" = 'sound/vox_fem/seventeen.ogg',
"seventy" = 'sound/vox_fem/seventy.ogg',
"severe" = 'sound/vox_fem/severe.ogg',
"sewage" = 'sound/vox_fem/sewage.ogg',
"sewer" = 'sound/vox_fem/sewer.ogg',
"shield" = 'sound/vox_fem/shield.ogg',
"shipment" = 'sound/vox_fem/shipment.ogg',
"shirt" = 'sound/vox_fem/shirt.ogg',
"shit" = 'sound/vox_fem/shit.ogg',
"shitlord" = 'sound/vox_fem/shitlord.ogg',
"shits" = 'sound/vox_fem/shits.ogg',
"shitting" = 'sound/vox_fem/shitting.ogg',
"shock" = 'sound/vox_fem/shock.ogg',
"shoot" = 'sound/vox_fem/shoot.ogg',
"shower" = 'sound/vox_fem/shower.ogg',
"shut" = 'sound/vox_fem/shut.ogg',
"shuttle" = 'sound/vox_fem/shuttle.ogg',
"side" = 'sound/vox_fem/side.ogg',
"sierra" = 'sound/vox_fem/sierra.ogg',
"sight" = 'sound/vox_fem/sight.ogg',
"silo" = 'sound/vox_fem/silo.ogg',
"singularity" = 'sound/vox_fem/singularity.ogg',
"six" = 'sound/vox_fem/six.ogg',
"sixteen" = 'sound/vox_fem/sixteen.ogg',
"sixty" = 'sound/vox_fem/sixty.ogg',
"slime" = 'sound/vox_fem/slime.ogg',
"slow" = 'sound/vox_fem/slow.ogg',
"solar" = 'sound/vox_fem/solar.ogg',
"solars" = 'sound/vox_fem/solars.ogg',
"soldier" = 'sound/vox_fem/soldier.ogg',
"some" = 'sound/vox_fem/some.ogg',
"someone" = 'sound/vox_fem/someone.ogg',
"something" = 'sound/vox_fem/something.ogg',
"son" = 'sound/vox_fem/son.ogg',
"sorry" = 'sound/vox_fem/sorry.ogg',
"south" = 'sound/vox_fem/south.ogg',
"squad" = 'sound/vox_fem/squad.ogg',
"square" = 'sound/vox_fem/square.ogg',
"ss13" = 'sound/vox_fem/ss13.ogg',
"stairway" = 'sound/vox_fem/stairway.ogg',
"starboard" = 'sound/vox_fem/starboard.ogg',
"station" = 'sound/vox_fem/station.ogg',
"status" = 'sound/vox_fem/status.ogg',
"sterile" = 'sound/vox_fem/sterile.ogg',
"sterilization" = 'sound/vox_fem/sterilization.ogg',
"storage" = 'sound/vox_fem/storage.ogg',
"stuck" = 'sound/vox_fem/stuck.ogg',
"sub" = 'sound/vox_fem/sub.ogg',
"subsurface" = 'sound/vox_fem/subsurface.ogg',
"sudden" = 'sound/vox_fem/sudden.ogg',
"suffer" = 'sound/vox_fem/suffer.ogg',
"suit" = 'sound/vox_fem/suit.ogg',
"superconducting" = 'sound/vox_fem/superconducting.ogg',
"supercooled" = 'sound/vox_fem/supercooled.ogg',
"supply" = 'sound/vox_fem/supply.ogg',
"surface" = 'sound/vox_fem/surface.ogg',
"surrender" = 'sound/vox_fem/surrender.ogg',
"surround" = 'sound/vox_fem/surround.ogg',
"surrounded" = 'sound/vox_fem/surrounded.ogg',
"switch" = 'sound/vox_fem/switch.ogg',
"syndicate" = 'sound/vox_fem/syndicate.ogg',
"system" = 'sound/vox_fem/system.ogg',
"systems" = 'sound/vox_fem/systems.ogg',
"t" = 'sound/vox_fem/t.ogg',
"tactical" = 'sound/vox_fem/tactical.ogg',
"take" = 'sound/vox_fem/take.ogg',
"talk" = 'sound/vox_fem/talk.ogg',
"tango" = 'sound/vox_fem/tango.ogg',
"tank" = 'sound/vox_fem/tank.ogg',
"target" = 'sound/vox_fem/target.ogg',
"team" = 'sound/vox_fem/team.ogg',
"temperature" = 'sound/vox_fem/temperature.ogg',
"temporal" = 'sound/vox_fem/temporal.ogg',
"ten" = 'sound/vox_fem/ten.ogg',
"terminal" = 'sound/vox_fem/terminal.ogg',
"terminated" = 'sound/vox_fem/terminated.ogg',
"termination" = 'sound/vox_fem/termination.ogg',
"test" = 'sound/vox_fem/test.ogg',
"that" = 'sound/vox_fem/that.ogg',
"the" = 'sound/vox_fem/the.ogg',
"then" = 'sound/vox_fem/then.ogg',
"there" = 'sound/vox_fem/there.ogg',
"third" = 'sound/vox_fem/third.ogg',
"thirteen" = 'sound/vox_fem/thirteen.ogg',
"thirty" = 'sound/vox_fem/thirty.ogg',
"this" = 'sound/vox_fem/this.ogg',
"those" = 'sound/vox_fem/those.ogg',
"thousand" = 'sound/vox_fem/thousand.ogg',
"threat" = 'sound/vox_fem/threat.ogg',
"three" = 'sound/vox_fem/three.ogg',
"through" = 'sound/vox_fem/through.ogg',
"tide" = 'sound/vox_fem/tide.ogg',
"time" = 'sound/vox_fem/time.ogg',
"to" = 'sound/vox_fem/to.ogg',
"top" = 'sound/vox_fem/top.ogg',
"topside" = 'sound/vox_fem/topside.ogg',
"touch" = 'sound/vox_fem/touch.ogg',
"towards" = 'sound/vox_fem/towards.ogg',
"toxins" = 'sound/vox_fem/toxins.ogg',
"track" = 'sound/vox_fem/track.ogg',
"train" = 'sound/vox_fem/train.ogg',
"traitor" = 'sound/vox_fem/traitor.ogg',
"transportation" = 'sound/vox_fem/transportation.ogg',
"truck" = 'sound/vox_fem/truck.ogg',
"tunnel" = 'sound/vox_fem/tunnel.ogg',
"turn" = 'sound/vox_fem/turn.ogg',
"turret" = 'sound/vox_fem/turret.ogg',
"twelve" = 'sound/vox_fem/twelve.ogg',
"twenty" = 'sound/vox_fem/twenty.ogg',
"two" = 'sound/vox_fem/two.ogg',
"u" = 'sound/vox_fem/u.ogg',
"unauthorized" = 'sound/vox_fem/unauthorized.ogg',
"under" = 'sound/vox_fem/under.ogg',
"uniform" = 'sound/vox_fem/uniform.ogg',
"unlocked" = 'sound/vox_fem/unlocked.ogg',
"until" = 'sound/vox_fem/until.ogg',
"up" = 'sound/vox_fem/up.ogg',
"update" = 'sound/vox_fem/update.ogg',
"updated" = 'sound/vox_fem/updated.ogg',
"updating" = 'sound/vox_fem/updating.ogg',
"upload" = 'sound/vox_fem/upload.ogg',
"upper" = 'sound/vox_fem/upper.ogg',
"uranium" = 'sound/vox_fem/uranium.ogg',
"us" = 'sound/vox_fem/us.ogg',
"usa" = 'sound/vox_fem/usa.ogg',
"use" = 'sound/vox_fem/use.ogg',
"used" = 'sound/vox_fem/used.ogg',
"user" = 'sound/vox_fem/user.ogg',
"v" = 'sound/vox_fem/v.ogg',
"vacate" = 'sound/vox_fem/vacate.ogg',
"valid" = 'sound/vox_fem/valid.ogg',
"vapor" = 'sound/vox_fem/vapor.ogg',
"vent" = 'sound/vox_fem/vent.ogg',
"ventilation" = 'sound/vox_fem/ventilation.ogg',
"victor" = 'sound/vox_fem/victor.ogg',
"violated" = 'sound/vox_fem/violated.ogg',
"violation" = 'sound/vox_fem/violation.ogg',
"virology" = 'sound/vox_fem/virology.ogg',
"voltage" = 'sound/vox_fem/voltage.ogg',
"vox" = 'sound/vox_fem/vox.ogg',
"vox_login" = 'sound/vox_fem/vox_login.ogg',
"voxtest" = 'sound/vox_fem/voxtest.ogg',
"w" = 'sound/vox_fem/w.ogg',
"walk" = 'sound/vox_fem/walk.ogg',
"wall" = 'sound/vox_fem/wall.ogg',
"wanker" = 'sound/vox_fem/wanker.ogg',
"want" = 'sound/vox_fem/want.ogg',
"wanted" = 'sound/vox_fem/wanted.ogg',
"warm" = 'sound/vox_fem/warm.ogg',
"warn" = 'sound/vox_fem/warn.ogg',
"warning" = 'sound/vox_fem/warning.ogg',
"waste" = 'sound/vox_fem/waste.ogg',
"water" = 'sound/vox_fem/water.ogg',
"we" = 'sound/vox_fem/we.ogg',
"weapon" = 'sound/vox_fem/weapon.ogg',
"welcome" = 'sound/vox_fem/welcome.ogg',
"west" = 'sound/vox_fem/west.ogg',
"whiskey" = 'sound/vox_fem/whiskey.ogg',
"white" = 'sound/vox_fem/white.ogg',
"wilco" = 'sound/vox_fem/wilco.ogg',
"will" = 'sound/vox_fem/will.ogg',
"with" = 'sound/vox_fem/with.ogg',
"without" = 'sound/vox_fem/without.ogg',
"wood" = 'sound/vox_fem/wood.ogg',
"woody" = 'sound/vox_fem/woody.ogg',
"x" = 'sound/vox_fem/x.ogg',
"xeno" = 'sound/vox_fem/xeno.ogg',
"xenobiology" = 'sound/vox_fem/xenobiology.ogg',
"xenomorph" = 'sound/vox_fem/xenomorph.ogg',
"xenomorphs" = 'sound/vox_fem/xenomorphs.ogg',
"y" = 'sound/vox_fem/y.ogg',
"yankee" = 'sound/vox_fem/yankee.ogg',
"yards" = 'sound/vox_fem/yards.ogg',
"year" = 'sound/vox_fem/year.ogg',
"yellow" = 'sound/vox_fem/yellow.ogg',
"yes" = 'sound/vox_fem/yes.ogg',
"you" = 'sound/vox_fem/you.ogg',
"your" = 'sound/vox_fem/your.ogg',
"yourself" = 'sound/vox_fem/yourself.ogg',
"z" = 'sound/vox_fem/z.ogg',
"zero" = 'sound/vox_fem/zero.ogg',
"zone" = 'sound/vox_fem/zone.ogg',
"zulu" = 'sound/vox_fem/zulu.ogg',
)
#endif


code:
/mob/living/silicon/spawn_gibs()
	robogibs(loc, viruses)

/mob/living/silicon/gib(animation = 0) // Please don't remove this thinking this proc does nothing, it is changing the default value for animation.
	..()

/mob/living/silicon/spawn_dust()
	new /obj/effect/decal/remains/robot(loc)

/mob/living/silicon/death(gibbed)
	diag_hud_set_status()
	diag_hud_set_health()
	update_health_hud()
	..()


code:
/mob/living/silicon/proc/show_laws() //Redefined in ai/laws.dm and robot/laws.dm
	return

/mob/living/silicon/proc/laws_sanity_check()
	if (!laws)
		make_laws()

/mob/living/silicon/proc/set_zeroth_law(law, law_borg)
	throw_alert("newlaw", /obj/screen/alert/newlaw)
	src.laws_sanity_check()
	src.laws.set_zeroth_law(law, law_borg)

/mob/living/silicon/proc/add_inherent_law(law)
	throw_alert("newlaw", /obj/screen/alert/newlaw)
	laws_sanity_check()
	laws.add_inherent_law(law)

/mob/living/silicon/proc/clear_inherent_laws()
	throw_alert("newlaw", /obj/screen/alert/newlaw)
	laws_sanity_check()
	laws.clear_inherent_laws()

/mob/living/silicon/proc/add_supplied_law(number, law)
	throw_alert("newlaw", /obj/screen/alert/newlaw)
	laws_sanity_check()
	laws.add_supplied_law(number, law)

/mob/living/silicon/proc/clear_supplied_laws()
	throw_alert("newlaw", /obj/screen/alert/newlaw)
	laws_sanity_check()
	laws.clear_supplied_laws()

/mob/living/silicon/proc/add_ion_law(law)
	throw_alert("newlaw", /obj/screen/alert/newlaw)
	laws_sanity_check()
	laws.add_ion_law(law)

/mob/living/silicon/proc/clear_ion_laws()
	throw_alert("newlaw", /obj/screen/alert/newlaw)
	laws_sanity_check()
	laws.clear_ion_laws()

/mob/living/silicon/proc/make_laws()
	switch(config.default_laws)
		if(0)
			laws = new /datum/ai_laws/default/asimov()
		if(1)
			laws = new /datum/ai_laws/custom()
		if(2)
			var/datum/ai_laws/lawtype = pick(subtypesof(/datum/ai_laws/default))
			laws = new lawtype()
	laws.associate(src)

/mob/living/silicon/proc/clear_zeroth_law(force)
	laws_sanity_check()
	laws.clear_zeroth_law(force)


code:
/mob/living/silicon/Login()
	if(mind && ticker && ticker.mode)
		ticker.mode.remove_cultist(mind, 0)
		ticker.mode.remove_revolutionary(mind, 0)
		ticker.mode.remove_gangster(mind, remove_bosses=1)
		ticker.mode.remove_thrall(mind,0)
		ticker.mode.remove_shadowling(mind)
		ticker.mode.remove_hog_follower(mind,0)
	..()


code:
/mob/living/silicon/pai/death(gibbed)
	if(stat == DEAD)
		return
	stat = DEAD
	canmove = 0
	update_sight()
	clear_fullscreens()

	//New pAI's get a brand new mind to prevent meta stuff from their previous life. This new mind causes problems down the line if it's not deleted here.
	living_mob_list -= src
	ghostize()
	qdel(src)


code:
/mob/living/silicon/pai/examine() //removed as it was pointless...moved to the pai-card instead.
	return


code:
/mob/living/silicon/pai/Life()
	if (src.stat == DEAD)
		return
	if(src.cable)
		if(get_dist(src, src.cable) > 1)
			var/turf/T = get_turf(src.loc)
			T.visible_message("[src.cable] rapidly retracts back into its spool.", "You hear a click and the sound of wire spooling rapidly.")
			qdel(src.cable)
			cable = null
	if(silence_time)
		if(world.timeofday >= silence_time)
			silence_time = null
			src << "Communication circuit reinitialized. Speech and messaging functionality restored."

/mob/living/silicon/pai/updatehealth()
	if(status_flags & GODMODE)
		return
	health = maxHealth - getBruteLoss() - getFireLoss()
	update_stat()