diff --git a/code/modules/mob/dead/observer/say.dm b/code/modules/mob/dead/observer/say.dm
index fb5b7735633..b4ae61f3e82 100644
--- a/code/modules/mob/dead/observer/say.dm
+++ b/code/modules/mob/dead/observer/say.dm
@@ -37,3 +37,12 @@
return
. = src.emote_dead(message)
+
+/mob/dead/observer/handle_track(var/message, var/verb = "says", var/datum/language/language, var/mob/speaker = null, var/speaker_name, var/atom/follow_target, var/hard_to_hear)
+ return "[speaker_name] ([ghost_follow_link(follow_target, ghost=src)])"
+
+/mob/dead/observer/handle_speaker_name(var/mob/speaker = null, var/vname, var/hard_to_hear)
+ var/speaker_name = ..()
+ if(speaker && (speaker_name != speaker.real_name) && !isAI(speaker)) //Announce computer and various stuff that broadcasts doesn't use it's real name but AI's can't pretend to be other mobs.
+ speaker_name = "[speaker.real_name] ([speaker_name])"
+ return speaker_name
\ No newline at end of file
diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm
index f158534475e..568033f3606 100644
--- a/code/modules/mob/hear_say.dm
+++ b/code/modules/mob/hear_say.dm
@@ -117,65 +117,8 @@
if(hard_to_hear)
message = stars(message)
- var/speaker_name = "unknown"
- if(speaker)
- speaker_name = speaker.name
-
- if(vname)
- speaker_name = vname
- if(hard_to_hear)
- speaker_name = "unknown"
-
- var/changed_voice
-
- if(isAI(src) && !hard_to_hear)
- var/jobname // the mob's "job"
- var/mob/living/carbon/human/impersonating //The crewmember being impersonated, if any.
-
- if(ishuman(speaker))
- var/mob/living/carbon/human/H = speaker
-
- var/obj/item/weapon/card/id/id = H.wear_id
- if((istype(id) && id.is_untrackable()) && H.HasVoiceChanger())
- changed_voice = 1
- var/mob/living/carbon/human/I = locate(speaker_name)
- if(I)
- impersonating = I
- jobname = impersonating.get_assignment()
- else
- jobname = "Unknown"
- else
- jobname = H.get_assignment()
-
- else if(iscarbon(speaker)) // Nonhuman carbon mob
- jobname = "No ID"
- else if(isAI(speaker))
- jobname = "AI"
- else if(isrobot(speaker))
- jobname = "Cyborg"
- else if(ispAI(speaker))
- jobname = "Personal AI"
- else if(isAutoAnnouncer(speaker))
- var/mob/living/automatedannouncer/AA = speaker
- jobname = AA.role
- else
- jobname = "Unknown"
-
- if(changed_voice)
- if(impersonating)
- track = "[speaker_name] ([jobname])"
- else
- track = "[speaker_name] ([jobname])"
- else
- if(istype(follow_target, /mob/living/simple_animal/bot))
- track = "[speaker_name] ([jobname])"
- else
- track = "[speaker_name] ([jobname])"
-
- if(isobserver(src))
- if(speaker && (speaker_name != speaker.real_name) && !isAI(speaker)) //Announce computer and various stuff that broadcasts doesn't use it's real name but AI's can't pretend to be other mobs.
- speaker_name = "[speaker.real_name] ([speaker_name])"
- track = "[speaker_name] ([ghost_follow_link(follow_target, ghost=src)])"
+ var/speaker_name = handle_speaker_name(speaker, vname, hard_to_hear)
+ track = handle_track(message, verb, language, speaker, speaker_name, follow_target, hard_to_hear)
var/formatted
if(language)
@@ -190,6 +133,22 @@
else
to_chat(src, "[part_a][speaker_name][part_b][formatted]")
+/mob/proc/handle_speaker_name(var/mob/speaker = null, var/vname, var/hard_to_hear)
+ var/speaker_name = "unknown"
+ if(speaker)
+ speaker_name = speaker.name
+
+ if(vname)
+ speaker_name = vname
+
+ if(hard_to_hear)
+ speaker_name = "unknown"
+
+ return speaker_name
+
+/mob/proc/handle_track(var/message, var/verb = "says", var/datum/language/language, var/mob/speaker = null, var/speaker_name, var/atom/follow_target, var/hard_to_hear)
+ return
+
/mob/proc/hear_signlang(var/message, var/verb = "gestures", var/datum/language/language, var/mob/speaker = null)
if(!client)
return
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 3235c6899c1..33e74cb6266 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -618,9 +618,10 @@ var/list/ai_verbs_default = list(
sleep(40)
continue
- return
-
- return
+ else if(href_list["open"])
+ var/mob/target = locate(href_list["open"]) in mob_list
+ if(target)
+ open_nearest_door(target)
/mob/living/silicon/ai/bullet_act(var/obj/item/projectile/Proj)
..(Proj)
@@ -1092,52 +1093,6 @@ var/list/ai_verbs_default = list(
aiRadio.interact(src)
-/mob/living/silicon/ai/proc/open_nearest_door(mob/living/target)
- if(!istype(target))
- return
- spawn(0)
- if(ishuman(target))
- var/mob/living/carbon/human/H = target
- var/obj/item/weapon/card/id/id = H.wear_id
- if(istype(id) && id.is_untrackable())
- to_chat(src, "Unable to locate an airlock")
- return
- if(H.digitalcamo)
- to_chat(src, "Unable to locate an airlock")
- return
- if(!near_camera(target))
- to_chat(src, "Target is not near any active cameras.")
- return
- var/obj/machinery/door/airlock/tobeopened
- var/dist = -1
- for(var/obj/machinery/door/airlock/D in range(3,target))
- if(!D.density)
- continue
- if(dist < 0)
- dist = get_dist(D, target)
-// to_chat(world, dist)
- tobeopened = D
- else
- if(dist > get_dist(D, target))
- dist = get_dist(D, target)
-// to_chat(world, dist)
- tobeopened = D
-// to_chat(world, "found [tobeopened.name] closer")
- else
-// to_chat(world, "[D.name] not close enough | [get_dist(D, target)] | [dist]")
- if(tobeopened)
- switch(alert(src, "Do you want to open \the [tobeopened] for [target]?","Doorknob_v2a.exe","Yes","No"))
- if("Yes")
- var/nhref = "src=[tobeopened.UID()];aiEnable=7"
- tobeopened.Topic(nhref, params2list(nhref), tobeopened, 1)
- to_chat(src, "\blue You've opened \the [tobeopened] for [target].")
- if("No")
- to_chat(src, "\red You deny the request.")
- else
- to_chat(src, "\red You've failed to open an airlock for [target]")
- return
-
-
/mob/living/silicon/ai/proc/check_unable(flags = 0)
if(stat == DEAD)
to_chat(usr, "You are dead!")
@@ -1234,3 +1189,37 @@ var/list/ai_verbs_default = list(
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/proc/open_nearest_door(mob/living/target)
+ if(!istype(target))
+ return
+
+ if(target && target.can_track())
+ var/obj/machinery/door/airlock/A = null
+
+ var/dist = -1
+ for(var/obj/machinery/door/airlock/D in range(3, target))
+ if(!D.density)
+ continue
+
+ var/curr_dist = get_dist(D, target)
+
+ if(dist < 0)
+ dist = curr_dist
+ A = D
+ else if(dist > curr_dist)
+ dist = curr_dist
+ A = D
+
+ if(istype(A))
+ switch(alert(src, "Do you want to open \the [A] for [target]?", "Doorknob_v2a.exe", "Yes", "No"))
+ if("Yes")
+ A.AIShiftClick()
+ to_chat(src, "You open \the [A] for [target].")
+ else
+ to_chat(src, "You deny the request.")
+ else
+ to_chat(src, "Unable to locate an airlock near [target].")
+
+ else
+ to_chat(src, "Target is not on or near any active cameras on the station.")
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/ai/say.dm b/code/modules/mob/living/silicon/ai/say.dm
index 2a3b39c1c9b..ccd0f652b68 100644
--- a/code/modules/mob/living/silicon/ai/say.dm
+++ b/code/modules/mob/living/silicon/ai/say.dm
@@ -1,3 +1,70 @@
+/*
+ * AI Saycode
+ */
+
+
+/mob/living/silicon/ai/handle_track(var/message, var/verb = "says", var/datum/language/language, var/mob/speaker = null, var/speaker_name, var/atom/follow_target, var/hard_to_hear)
+ if(hard_to_hear)
+ return
+
+ var/jobname // the mob's "job"
+ var/mob/living/carbon/human/impersonating //The crewmember being impersonated, if any.
+ var/changed_voice
+
+ if(ishuman(speaker))
+ var/mob/living/carbon/human/H = speaker
+
+ var/obj/item/weapon/card/id/id = H.wear_id
+ if((istype(id) && id.is_untrackable()) && H.HasVoiceChanger())
+ changed_voice = 1
+ var/mob/living/carbon/human/I = locate(speaker_name)
+ if(I)
+ impersonating = I
+ jobname = impersonating.get_assignment()
+ else
+ jobname = "Unknown"
+ else
+ jobname = H.get_assignment()
+
+ else if(iscarbon(speaker)) // Nonhuman carbon mob
+ jobname = "No ID"
+ else if(isAI(speaker))
+ jobname = "AI"
+ else if(isrobot(speaker))
+ jobname = "Cyborg"
+ else if(ispAI(speaker))
+ jobname = "Personal AI"
+ else if(isAutoAnnouncer(speaker))
+ var/mob/living/automatedannouncer/AA = speaker
+ jobname = AA.role
+ else
+ jobname = "Unknown"
+
+ var/track = ""
+ var/mob/mob_to_track = null
+ if(changed_voice)
+ if(impersonating)
+ mob_to_track = impersonating
+ else
+ track = "[speaker_name] ([jobname])"
+ else
+ if(istype(follow_target, /mob/living/simple_animal/bot))
+ track = "[speaker_name] ([jobname])"
+ else
+ mob_to_track = speaker
+
+ if(mob_to_track)
+ track = "[speaker_name] ([jobname])"
+ track += "\[OPEN\]"
+
+ return track
+
+
+
+/*
+ * AI VOX Announcements
+ */
+
var/announcing_vox = 0 // Stores the time of the last announcement
var/const/VOX_CHANNEL = 200
var/const/VOX_DELAY = 100