diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm
index 7b6626411a1..cb97b5cdda5 100644
--- a/code/__DEFINES/admin.dm
+++ b/code/__DEFINES/admin.dm
@@ -142,10 +142,10 @@ GLOBAL_VAR_INIT(ghost_role_flags, (~0))
#define LIGHTNING_BOLT_DAMAGE 75
#define LIGHTNING_BOLT_ELECTROCUTION_ANIMATION_LENGTH 40
-/// for asay pings, this is the index in the return list for [/proc/check_admin_pings] that contains the message modified with underlines for the spotted names
-#define ADMINSAY_PING_UNDERLINE_NAME_INDEX "!underlined_names"
-/// for asay datum refs, this is the index in the return list for [/proc/check_memory_refs] that contains the message modified with underlines for the linked datums
-#define ADMINSAY_LINK_DATUM_REF "!datum_ref"
+/// for [/proc/check_asay_links], if there are any actionable refs in the asay message, this index in the return list contains the new message text to be printed
+#define ASAY_LINK_NEW_MESSAGE_INDEX "!asay_new_message"
+/// for [/proc/check_asay_links], if there are any admin pings in the asay message, this index in the return list contains a list of admins to ping
+#define ASAY_LINK_PINGED_ADMINS_INDEX "!pinged_admins"
/// When passed in as the duration for ban_panel, will make the ban default to permanent
#define BAN_PANEL_PERMANENT "permanent"
diff --git a/code/_globalvars/_regexes.dm b/code/_globalvars/_regexes.dm
index 1902fed0d20..7e571576213 100644
--- a/code/_globalvars/_regexes.dm
+++ b/code/_globalvars/_regexes.dm
@@ -6,7 +6,6 @@ GLOBAL_DATUM_INIT(is_email, /regex, regex("\[a-z0-9_-]+@\[a-z0-9_-]+.\[a-z0-9_-]
GLOBAL_DATUM_INIT(is_alphanumeric, /regex, regex("\[a-z0-9]+", "i"))
GLOBAL_DATUM_INIT(is_punctuation, /regex, regex("\[.!?]+", "i"))
GLOBAL_DATUM_INIT(is_color, /regex, regex("^#\[0-9a-fA-F]{6}$"))
-GLOBAL_DATUM_INIT(is_memref, /regex, regex("0x\[0-9a-fA-F\]{7,8}"))
//finds text strings recognized as links on discord. Mainly used to stop embedding.
GLOBAL_DATUM_INIT(has_discord_embeddable_links, /regex, regex("(https?://\[^\\s|<\]{2,})"))
diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm
index 11be561b9d8..30b8de8dcaa 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -977,68 +977,77 @@ GLOBAL_DATUM_INIT(admin_help_ui_handler, /datum/admin_help_ui_handler, new)
return potential_hits
/**
- * Checks a given message to see if any of the words contain an active admin's ckey with an @ before it
+ * Checks a given message to see if any of the words are something we want to treat specially, as detailed below.
*
- * Returns nothing if no pings are found, otherwise returns an associative list with ckey -> client
- * Also modifies msg to underline the pings, then stores them in the key [ADMINSAY_PING_UNDERLINE_NAME_INDEX] for returning
+ * There are 3 cases where a word is something we want to act on
+ * 1. Admin pings, like @adminckey. Pings the admin in question, text is not clickable
+ * 2. Datum refs, like @0x2001169 or @mob_23. Clicking on the link opens up the VV for that datum
+ * 3. Ticket refs, like #3. Displays the status and ahelper in the link, clicking on it brings up the ticket panel for it.
+ * Returns a list being used as a tuple. Index ASAY_LINK_NEW_MESSAGE_INDEX contains the new message text (with clickable links and such)
+ * while index ASAY_LINK_PINGED_ADMINS_INDEX contains a list of pinged admin clients, if there are any.
*
* Arguments:
* * msg - the message being scanned
*/
-/proc/check_admin_pings(msg)
- //explode the input msg into a list
- var/list/msglist = splittext(msg, " ")
- var/list/admins_to_ping = list()
+/proc/check_asay_links(msg)
+ var/list/msglist = splittext(msg, " ") //explode the input msg into a list
+ var/list/pinged_admins = list() // if we ping any admins, store them here so we can ping them after
+ var/modified = FALSE // did we find anything?
var/i = 0
for(var/word in msglist)
i++
if(!length(word))
continue
- if(word[1] != "@")
- continue
- var/ckey_check = lowertext(copytext(word, 2))
- var/client/client_check = GLOB.directory[ckey_check]
- if(client_check?.holder)
- msglist[i] = "[word]"
- admins_to_ping[ckey_check] = client_check
- if(length(admins_to_ping))
- admins_to_ping[ADMINSAY_PING_UNDERLINE_NAME_INDEX] = jointext(msglist, " ") // without tuples, we must make do!
- return admins_to_ping
+ switch(word[1])
+ if("@")
+ var/stripped_word = ckey(copytext(word, 2))
-/**
- * Checks a given message to see if any of the words contain a memory ref for a datum. Said ref should not have brackets around it
- *
- * Returns nothing if no refs are found, otherwise returns an associative list with ckey -> client
- * Also modifies msg to underline and linkify the [ref] so other admins can click on the address to open the VV entry for said datum
- *
- * Arguments:
- * * msg - the message being scanned
- */
-/proc/check_memory_refs(msg)
- if(!findtext(msg, GLOB.is_memref))
- return
+ // first we check if it's a ckey of an admin
+ var/client/client_check = GLOB.directory[stripped_word]
+ if(client_check?.holder)
+ msglist[i] = "[word]"
+ pinged_admins[stripped_word] = client_check
+ modified = TRUE
+ continue
- //explode the input msg into a list
- var/list/msglist = splittext(msg, " ")
- var/list/datums_to_ref = list()
+ // then if not, we check if it's a datum ref
- var/i = 0
- for(var/word in msglist)
- i++
- if(!length(word))
- continue
- var/word_with_brackets = "\[[word]\]" // the actual memory address lookups need the bracket wraps
- var/datum/check_datum = locate(word_with_brackets)
- if(!istype(check_datum))
- continue
- msglist[i] = "[word_with_brackets]"
- datums_to_ref[word] = word
+ var/word_with_brackets = "\[[stripped_word]\]" // the actual memory address lookups need the bracket wraps
+ var/datum/datum_check = locate(word_with_brackets)
+ if(!istype(datum_check))
+ continue
+ msglist[i] = "[word]"
+ modified = TRUE
+
+ if("#") // check if we're linking a ticket
+ var/possible_ticket_id = text2num(copytext(word, 2))
+ if(!possible_ticket_id)
+ continue
+
+ var/datum/admin_help/ahelp_check = GLOB.ahelp_tickets?.TicketByID(possible_ticket_id)
+ if(!ahelp_check)
+ continue
+
+ var/state_word
+ switch(ahelp_check.state)
+ if(AHELP_ACTIVE)
+ state_word = "Active"
+ if(AHELP_CLOSED)
+ state_word = "Closed"
+ if(AHELP_RESOLVED)
+ state_word = "Resolved"
+
+ msglist[i]= "[word] ([state_word] | [ahelp_check.initiator_key_name])"
+ modified = TRUE
+
+ if(modified)
+ var/list/return_list = list()
+ return_list[ASAY_LINK_NEW_MESSAGE_INDEX] = jointext(msglist, " ") // without tuples, we must make do!
+ return_list[ASAY_LINK_PINGED_ADMINS_INDEX] = pinged_admins
+ return return_list
- if(length(datums_to_ref))
- datums_to_ref[ADMINSAY_LINK_DATUM_REF] = jointext(msglist, " ") // without tuples, we must make do!
- return datums_to_ref
#undef WEBHOOK_URGENT
#undef WEBHOOK_NONE
diff --git a/code/modules/admin/verbs/adminsay.dm b/code/modules/admin/verbs/adminsay.dm
index f39358b2f34..3ff5c6802e6 100644
--- a/code/modules/admin/verbs/adminsay.dm
+++ b/code/modules/admin/verbs/adminsay.dm
@@ -9,23 +9,18 @@
if(!msg)
return
- var/list/pinged_admin_clients = check_admin_pings(msg)
- if(length(pinged_admin_clients) && pinged_admin_clients[ADMINSAY_PING_UNDERLINE_NAME_INDEX])
- msg = pinged_admin_clients[ADMINSAY_PING_UNDERLINE_NAME_INDEX]
- pinged_admin_clients -= ADMINSAY_PING_UNDERLINE_NAME_INDEX
-
- for(var/iter_ckey in pinged_admin_clients)
- var/client/iter_admin_client = pinged_admin_clients[iter_ckey]
- if(!iter_admin_client?.holder)
- continue
- window_flash(iter_admin_client)
- SEND_SOUND(iter_admin_client.mob, sound('sound/misc/asay_ping.ogg'))
-
-
- var/list/linked_datums = check_memory_refs(msg)
- if(length(linked_datums) && linked_datums[ADMINSAY_LINK_DATUM_REF])
- msg = linked_datums[ADMINSAY_LINK_DATUM_REF]
- linked_datums -= ADMINSAY_LINK_DATUM_REF
+ if(findtext(msg, "@") || findtext(msg, "#"))
+ var/list/link_results = check_asay_links(msg)
+ if(length(link_results))
+ msg = link_results[ASAY_LINK_NEW_MESSAGE_INDEX]
+ link_results[ASAY_LINK_NEW_MESSAGE_INDEX] = null
+ var/list/pinged_admin_clients = link_results[ASAY_LINK_PINGED_ADMINS_INDEX]
+ for(var/iter_ckey in pinged_admin_clients)
+ var/client/iter_admin_client = pinged_admin_clients[iter_ckey]
+ if(!iter_admin_client?.holder)
+ continue
+ window_flash(iter_admin_client)
+ SEND_SOUND(iter_admin_client.mob, sound('sound/misc/asay_ping.ogg'))
mob.log_talk(msg, LOG_ASAY)
msg = keywords_lookup(msg)
diff --git a/code/modules/admin/view_variables/view_variables.dm b/code/modules/admin/view_variables/view_variables.dm
index 459316f18aa..830979e2f54 100644
--- a/code/modules/admin/view_variables/view_variables.dm
+++ b/code/modules/admin/view_variables/view_variables.dm
@@ -42,7 +42,7 @@
sprite_text = no_icon? "\[NO ICON\]" : "