diff --git a/code/game/machinery/camera/tracking.dm b/code/game/machinery/camera/tracking.dm
index 053ad9460bc..29843283846 100644
--- a/code/game/machinery/camera/tracking.dm
+++ b/code/game/machinery/camera/tracking.dm
@@ -118,6 +118,49 @@
src.track = null
ai_actual_track(target)
+/mob/living/silicon/ai/proc/open_nearest_door(mob/living/target as mob)
+ if(!istype(target)) return
+ spawn(0)
+ if(istype(target, /mob/living/carbon/human))
+ var/mob/living/carbon/human/H = target
+ if(H.wear_id && istype(H.wear_id.GetID(), /obj/item/weapon/card/id/syndicate))
+ src << "Unable to locate an airlock"
+ return
+ if(H.wear_id && istype(H.wear_id.GetID(), /obj/item/weapon/card/id/syndicate))
+ src << "Unable to locate an airlock"
+ return
+ if(istype(H.head, /obj/item/clothing/head/helmet/space/space_ninja) && !H.head.canremove)
+ src << "Unable to locate an airlock"
+ return
+ if(H.digitalcamo)
+ src << "Unable to locate an airlock"
+ return
+ if (!near_camera(target))
+ 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)
+ //world << dist
+ tobeopened = D
+ else
+ if(dist > get_dist(D, target))
+ dist = get_dist(D, target)
+ //world << dist
+ tobeopened = D
+ //world << "found [tobeopened.name] closer"
+ else
+ //world << "[D.name] not close enough | [get_dist(D, target)] | [dist]"
+ if(tobeopened)
+ var/nhref = "src=\ref[tobeopened];aiEnable=7"
+ tobeopened.Topic(nhref, params2list(nhref), tobeopened, 1)
+ src << "\blue You've opened the nearest airlock to [target]"
+ else
+ src << "\red You've failed to open an airlock for [target]"
+ return
/mob/living/silicon/ai/proc/ai_actual_track(mob/living/target as mob)
if(!istype(target)) return
var/mob/living/silicon/ai/U = usr
diff --git a/code/game/machinery/telecomms/broadcaster.dm b/code/game/machinery/telecomms/broadcaster.dm
index d90986a5dda..230991fad15 100644
--- a/code/game/machinery/telecomms/broadcaster.dm
+++ b/code/game/machinery/telecomms/broadcaster.dm
@@ -437,7 +437,7 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept
//End of research and feedback code.
var/aitrack = ""
-
+ var/aiopen = ""
/* ###### Send the message ###### */
@@ -449,11 +449,12 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept
var/rendered = "[part_a][N][part_b][quotedmsg][part_c]"
for (var/mob/R in heard_masked)
aitrack = ""
+ aiopen = "\[OPEN\] "
if(data == 4)
aitrack = ""
if(istype(R, /mob/living/silicon/ai))
- R.show_message("[part_a][aitrack][N] ([J]) [part_b][quotedmsg][part_c]", 2)
+ R.show_message("[part_a][aitrack][N] ([J]) [aiopen][part_b][quotedmsg][part_c]", 2)
else
R.show_message(rendered, 2)
@@ -464,11 +465,12 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept
for (var/mob/R in heard_normal)
aitrack = ""
+ aiopen = "\[OPEN\] "
if(data == 4)
aitrack = ""
if(istype(R, /mob/living/silicon/ai))
- R.show_message("[part_a][aitrack][realname] ([job]) [part_b][quotedmsg][part_c]", 2)
+ R.show_message("[part_a][aitrack][realname] ([job]) [aiopen][part_b][quotedmsg][part_c]", 2)
else
R.show_message(rendered, 2)
@@ -479,12 +481,13 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept
for (var/mob/R in heard_voice)
aitrack = ""
+ aiopen = "\[OPEN\] "
if(data == 4)
aitrack = ""
if(istype(R, /mob/living/silicon/ai))
- R.show_message("[part_a][aitrack][vname] ([job]) [part_b][vmessage]][part_c]", 2)
+ R.show_message("[part_a][aitrack][vname] ([job]) [aiopen][part_b][vmessage]][part_c]", 2)
else
R.show_message(rendered, 2)
@@ -501,12 +504,13 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept
for (var/mob/R in heard_garbled)
aitrack = ""
+ aiopen = "\[OPEN\] "
if(data == 4)
aitrack = ""
if(istype(R, /mob/living/silicon/ai))
- R.show_message("[part_a][aitrack][vname][part_b][quotedmsg][part_c]", 2)
+ R.show_message("[part_a][aitrack][vname][aiopen][part_b][quotedmsg][part_c]", 2)
else
R.show_message(rendered, 2)
@@ -523,12 +527,13 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept
for (var/mob/R in heard_gibberish)
aitrack = ""
+ aiopen = "\[OPEN\] "
if(data == 4)
aitrack = ""
if(istype(R, /mob/living/silicon/ai))
- R.show_message("[part_a][aitrack][Gibberish(realname, compression + 50)] ([Gibberish(job, compression + 50)]) [part_b][quotedmsg][part_c]", 2)
+ R.show_message("[part_a][aitrack][Gibberish(realname, compression + 50)] ([Gibberish(job, compression + 50)]) [aiopen][part_b][quotedmsg][part_c]", 2)
else
R.show_message(rendered, 2)
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index 45420280f68..0883ab4b080 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -130,6 +130,13 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
usr << browse(null, "window=radio")
return
usr.set_machine(src)
+ if (href_list["open"])
+ var/mob/target = locate(href_list["open"])
+ var/mob/living/silicon/ai/A = locate(href_list["open2"])
+ if(A && target)
+ A.open_nearest_door(target)
+ return
+
if (href_list["track"])
var/mob/target = locate(href_list["track"])
var/mob/living/silicon/ai/A = locate(href_list["track2"])
@@ -557,7 +564,7 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
var/rendered = "[part_a][N][part_b][quotedmsg][part_c]"
for (var/mob/R in heard_masked)
if(istype(R, /mob/living/silicon/ai))
- R.show_message("[part_a][N] ([J]) [part_b][quotedmsg][part_c]", 2)
+ R.show_message("[part_a][N] ([J]) \[OPEN\] [part_b][quotedmsg][part_c]", 2)
else
R.show_message(rendered, 2)
@@ -566,7 +573,7 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
for (var/mob/R in heard_normal)
if(istype(R, /mob/living/silicon/ai))
- R.show_message("[part_a][M.real_name] ([eqjobname]) [part_b][quotedmsg][part_c]", 2)
+ R.show_message("[part_a][M.real_name] ([eqjobname]) \[OPEN\] [part_b][quotedmsg][part_c]", 2)
else
R.show_message(rendered, 2)
@@ -575,7 +582,7 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
for (var/mob/R in heard_voice)
if(istype(R, /mob/living/silicon/ai))
- R.show_message("[part_a][M.voice_name] ([eqjobname]) [part_b][pick(M.speak_emote)][part_c]", 2)
+ R.show_message("[part_a][M.voice_name] ([eqjobname]) \[OPEN\] [part_b][pick(M.speak_emote)][part_c]", 2)
else
R.show_message(rendered, 2)
@@ -585,7 +592,7 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
for (var/mob/R in heard_voice)
if(istype(R, /mob/living/silicon/ai))
- R.show_message("[part_a][M.voice_name][part_b][quotedmsg][part_c]", 2)
+ R.show_message("[part_a][M.voice_name]\[OPEN\] [part_b][quotedmsg][part_c]", 2)
else
R.show_message(rendered, 2)