diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index c3c99eaf4ec..c0b68f29d34 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -57,6 +57,13 @@
# stylemistake
+/code/__DEFINES/chat.dm @stylemistake
+/code/__DEFINES/tgui.dm @stylemistake
+/code/controllers/subsystem/chat.dm @stylemistake
+/code/controllers/subsystem/tgui.dm @stylemistake
+/code/modules/tgchat @stylemistake
+/code/modules/tgui @stylemistake
+/code/modules/tgui_panel @stylemistake
/tgui @stylemistake
# Multiple Owners
diff --git a/code/__DEFINES/chat.dm b/code/__DEFINES/chat.dm
new file mode 100644
index 00000000000..fcf8d2b8b7c
--- /dev/null
+++ b/code/__DEFINES/chat.dm
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+#define MESSAGE_TYPE_SYSTEM "system"
+#define MESSAGE_TYPE_LOCALCHAT "localchat"
+#define MESSAGE_TYPE_RADIO "radio"
+#define MESSAGE_TYPE_INFO "info"
+#define MESSAGE_TYPE_WARNING "warning"
+#define MESSAGE_TYPE_DEADCHAT "deadchat"
+#define MESSAGE_TYPE_OOC "ooc"
+#define MESSAGE_TYPE_ADMINPM "adminpm"
+#define MESSAGE_TYPE_COMBAT "combat"
+#define MESSAGE_TYPE_ADMINCHAT "adminchat"
+#define MESSAGE_TYPE_MODCHAT "modchat"
+#define MESSAGE_TYPE_EVENTCHAT "eventchat"
+#define MESSAGE_TYPE_ADMINLOG "adminlog"
+#define MESSAGE_TYPE_ATTACKLOG "attacklog"
+#define MESSAGE_TYPE_DEBUG "debug"
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 63319aee335..c6651cda17c 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -292,9 +292,15 @@ GLOBAL_LIST_INIT(pda_styles, sortList(list(MONO, VT, ORBITRON, SHARE)))
#define SHELTER_DEPLOY_ANCHORED_OBJECTS "anchored objects"
//debug printing macros
-#define debug_world(msg) if (GLOB.Debug2) to_chat(world, "DEBUG: [msg]")
-#define debug_usr(msg) if (GLOB.Debug2&&usr) to_chat(usr, "DEBUG: [msg]")
-#define debug_admins(msg) if (GLOB.Debug2) to_chat(GLOB.admins, "DEBUG: [msg]")
+#define debug_world(msg) if (GLOB.Debug2) to_chat(world, \
+ type = MESSAGE_TYPE_DEBUG, \
+ text = "DEBUG: [msg]")
+#define debug_usr(msg) if (GLOB.Debug2&&usr) to_chat(usr, \
+ type = MESSAGE_TYPE_DEBUG, \
+ text = "DEBUG: [msg]")
+#define debug_admins(msg) if (GLOB.Debug2) to_chat(GLOB.admins, \
+ type = MESSAGE_TYPE_DEBUG, \
+ text = "DEBUG: [msg]")
#define debug_world_log(msg) if (GLOB.Debug2) log_world("DEBUG: [msg]")
#define INCREMENT_TALLY(L, stat) if(L[stat]){L[stat]++}else{L[stat] = 1}
diff --git a/code/controllers/subsystem/chat.dm b/code/controllers/subsystem/chat.dm
index a4f8dfdc5d3..f2e9da704f4 100644
--- a/code/controllers/subsystem/chat.dm
+++ b/code/controllers/subsystem/chat.dm
@@ -1,3 +1,8 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
SUBSYSTEM_DEF(chat)
name = "Chat"
flags = SS_TICKER
@@ -16,24 +21,18 @@ SUBSYSTEM_DEF(chat)
// Send to tgchat
client.tgui_panel?.window.send_message("chat/message", payload)
// Send to old chat
- for(var/msg in payload)
- SEND_TEXT(client, msg["text"])
+ for(var/message in payload)
+ SEND_TEXT(client, message_to_html(message))
if(MC_TICK_CHECK)
return
-/datum/controller/subsystem/chat/proc/queue(target, text, flags)
+/datum/controller/subsystem/chat/proc/queue(target, message)
if(islist(target))
for(var/_target in target)
var/client/client = CLIENT_FROM_VAR(_target)
if(client)
- LAZYADD(payload_by_client[client], list(list(
- "text" = text,
- "flags" = flags,
- )))
+ LAZYADD(payload_by_client[client], list(message))
return
var/client/client = CLIENT_FROM_VAR(target)
if(client)
- LAZYADD(payload_by_client[client], list(list(
- "text" = text,
- "flags" = flags,
- )))
+ LAZYADD(payload_by_client[client], list(message))
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index e834d9a609c..f7aa85693b7 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -1,12 +1,18 @@
////////////////////////////////
/proc/message_admins(msg)
- msg = "ADMIN LOG: "
- to_chat(GLOB.admins, msg, confidential = TRUE)
+ msg = "ADMIN LOG: "
+ to_chat(GLOB.admins,
+ type = MESSAGE_TYPE_ADMINLOG,
+ html = msg,
+ confidential = TRUE)
/proc/relay_msg_admins(msg)
- msg = "RELAY: "
- to_chat(GLOB.admins, msg, confidential = TRUE)
+ msg = "RELAY: "
+ to_chat(GLOB.admins,
+ type = MESSAGE_TYPE_ADMINLOG,
+ html = msg,
+ confidential = TRUE)
///////////////////////////////////////////////////////////////////////////////////////////////Panels
diff --git a/code/modules/admin/ipintel.dm b/code/modules/admin/ipintel.dm
index 2a88c909d41..228b7997105 100644
--- a/code/modules/admin/ipintel.dm
+++ b/code/modules/admin/ipintel.dm
@@ -134,8 +134,3 @@
/proc/log_ipintel(text)
log_game("IPINTEL: [text]")
debug_admins("IPINTEL: [text]")
-
-
-
-
-
diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm
index 1e93f32d881..f0f9d1728d3 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -267,10 +267,16 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
if(X.prefs.toggles & SOUND_ADMINHELP)
SEND_SOUND(X, sound('sound/effects/adminhelp.ogg'))
window_flash(X, ignorepref = TRUE)
- to_chat(X, admin_msg, confidential = TRUE)
+ to_chat(X,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = admin_msg,
+ confidential = TRUE)
//show it to the person adminhelping too
- to_chat(initiator, "PM to-Admins: [msg]", confidential = TRUE)
+ to_chat(initiator,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "PM to-Admins: [msg]",
+ confidential = TRUE)
SSblackbox.LogAhelp(id, "Ticket Opened", msg, null, initiator.ckey)
//Reopen a closed ticket
@@ -608,7 +614,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
if (!server_url)
CRASH("Invalid cross comms config: [server_name]")
world.Export("[server_url]?[list2params(message)]")
-
+
/proc/tgsadminwho()
var/list/message = list("Admins: ")
diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm
index 58af87b0b0d..200ccff0da5 100644
--- a/code/modules/admin/verbs/adminpm.dm
+++ b/code/modules/admin/verbs/adminpm.dm
@@ -5,9 +5,12 @@
set category = null
set name = "Admin PM Mob"
if(!holder)
- to_chat(src, "Error: Admin-PM-Context: Only administrators may use this command.", confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Error: Admin-PM-Context: Only administrators may use this command.",
+ confidential = TRUE)
return
- if( !ismob(M) || !M.client )
+ if(!ismob(M) || !M.client)
return
cmd_admin_pm(M.client,null)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -17,7 +20,10 @@
set category = "Admin"
set name = "Admin PM"
if(!holder)
- to_chat(src, "Error: Admin-PM-Panel: Only administrators may use this command.", confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Error: Admin-PM-Panel: Only administrators may use this command.",
+ confidential = TRUE)
return
var/list/client/targets[0]
for(var/client/T)
@@ -36,7 +42,10 @@
/client/proc/cmd_ahelp_reply(whom)
if(prefs.muted & MUTE_ADMINHELP)
- to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).", confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Error: Admin-PM: You are unable to use admin PM-s (muted).",
+ confidential = TRUE)
return
var/client/C
if(istext(whom))
@@ -47,7 +56,10 @@
C = whom
if(!C)
if(holder)
- to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Error: Admin-PM: Client not found.",
+ confidential = TRUE)
return
var/datum/admin_help/AH = C.current_ticket
@@ -62,8 +74,14 @@
if(GLOB.directory[AH.initiator_ckey]) // Client has reconnected, lets try to recover
whom = GLOB.directory[AH.initiator_ckey]
else
- to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE)
- to_chat(src, "Message not sent: "+e+"
[msg]", confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Error: Admin-PM: Client not found.",
+ confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Message not sent:
[msg]",
+ confidential = TRUE)
AH.AddInteraction("No client found, message not sent:
[msg]")
return
cmd_admin_pm(whom, msg)
@@ -72,12 +90,21 @@
//Fetching a message if needed. src is the sender and C is the target client
/client/proc/cmd_admin_pm(whom, msg)
if(prefs.muted & MUTE_ADMINHELP)
- to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).", confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Error: Admin-PM: You are unable to use admin PM-s (muted).",
+ confidential = TRUE)
return
if(!holder && !current_ticket) //no ticket? https://www.youtube.com/watch?v=iHSPf6x1Fdo
- to_chat(src, "You can no longer reply to this ticket, please open another one by using the Adminhelp verb if need be.", confidential = TRUE)
- to_chat(src, "Message: [msg]", confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "You can no longer reply to this ticket, please open another one by using the Adminhelp verb if need be.",
+ confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Message: [msg]",
+ confidential = TRUE)
return
var/client/recipient
@@ -95,7 +122,10 @@
recipient = whom
if(!recipient)
- to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Error: Admin-PM: Client not found.",
+ confidential = TRUE)
return
recipient_ckey = recipient.ckey
@@ -110,7 +140,10 @@
if(!msg)
return
if(holder)
- to_chat(src, "Error: Use the admin IRC/Discord channel, nerd.", confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Error: Use the admin IRC/Discord channel, nerd.",
+ confidential = TRUE)
return
@@ -127,8 +160,14 @@
recipient = GLOB.directory[recipient_ckey]
else
if(holder)
- to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE)
- to_chat(src, "Message not sent:
[msg]", confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Error: Admin-PM: Client not found.",
+ confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Message not sent:
[msg]",
+ confidential = TRUE)
if(recipient_ticket)
recipient_ticket.AddInteraction("No client found, message not sent:
[msg]")
return
@@ -138,7 +177,10 @@
if(prefs.muted & MUTE_ADMINHELP)
- to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).", confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Error: Admin-PM: You are unable to use admin PM-s (muted).",
+ confidential = TRUE)
return
if (src.handle_spam_prevention(msg,MUTE_ADMINHELP))
@@ -158,7 +200,10 @@
var/keywordparsedmsg = keywords_lookup(msg)
if(external)
- to_chat(src, "PM to-Admins: [rawmsg]", confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "PM to-Admins: [rawmsg]",
+ confidential = TRUE)
var/datum/admin_help/AH = admin_ticket_log(src, "Reply PM from-[key_name(src, TRUE, TRUE)] to External: [keywordparsedmsg]")
externalreplyamount--
send2adminchat("[AH ? "#[AH.id] " : ""]Reply: [ckey]", rawmsg)
@@ -168,9 +213,14 @@
badmin = TRUE
if(recipient.holder && !badmin)
if(holder)
- to_chat(recipient, "Admin PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]", confidential = TRUE)
- to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [keywordparsedmsg]", confidential = TRUE)
-
+ to_chat(recipient,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Admin PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]",
+ confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Admin PM to-[key_name(recipient, src, 1)]: [keywordparsedmsg]",
+ confidential = TRUE)
//omg this is dumb, just fill in both their tickets
var/interaction_message = "PM from-[key_name(src, recipient, 1)] to-[key_name(recipient, src, 1)]: [keywordparsedmsg]"
admin_ticket_log(src, interaction_message)
@@ -180,8 +230,14 @@
else //recipient is an admin but sender is not
var/replymsg = "Reply PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]"
admin_ticket_log(src, "[replymsg]")
- to_chat(recipient, "[replymsg]", confidential = TRUE)
- to_chat(src, "PM to-Admins: [msg]", confidential = TRUE)
+ to_chat(recipient,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "[replymsg]",
+ confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "PM to-Admins: [msg]",
+ confidential = TRUE)
SSblackbox.LogAhelp(current_ticket.id, "Reply", msg, recipient.ckey, src.ckey)
//play the receiving admin the adminhelp sound (if they have them enabled)
@@ -196,10 +252,22 @@
already_logged = TRUE
SSblackbox.LogAhelp(recipient.current_ticket.id, "Ticket Opened", msg, recipient.ckey, src.ckey)
- to_chat(recipient, "-- Administrator private message --", confidential = TRUE)
- to_chat(recipient, "Admin PM from-[key_name(src, recipient, 0)]: [msg]", confidential = TRUE)
- to_chat(recipient, "Click on the administrator's name to reply.", confidential = TRUE)
- to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [msg]", confidential = TRUE)
+ to_chat(recipient,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "-- Administrator private message --",
+ confidential = TRUE)
+ to_chat(recipient,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Admin PM from-[key_name(src, recipient, 0)]: [msg]",
+ confidential = TRUE)
+ to_chat(recipient,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Click on the administrator's name to reply.",
+ confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Admin PM to-[key_name(recipient, src, 1)]: [msg]",
+ confidential = TRUE)
admin_ticket_log(recipient, "PM From [key_name_admin(src)]: [keywordparsedmsg]")
@@ -215,20 +283,29 @@
INVOKE_ASYNC(src, .proc/popup_admin_pm, recipient, msg)
else //neither are admins
- to_chat(src, "Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.", confidential = TRUE)
+ to_chat(src,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.",
+ confidential = TRUE)
return
if(external)
log_admin_private("PM: [key_name(src)]->External: [rawmsg]")
for(var/client/X in GLOB.admins)
- to_chat(X, "PM: [key_name(src, X, 0)]->External: [keywordparsedmsg]", confidential = TRUE)
+ to_chat(X,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "PM: [key_name(src, X, 0)]->External: [keywordparsedmsg]",
+ confidential = TRUE)
else
window_flash(recipient, ignorepref = TRUE)
log_admin_private("PM: [key_name(src)]->[key_name(recipient)]: [rawmsg]")
//we don't use message_admins here because the sender/receiver might get it too
for(var/client/X in GLOB.admins)
if(X.key!=key && X.key!=recipient.key) //check client/X is an admin and isn't the sender or recipient
- to_chat(X, "PM: [key_name(src, X, 0)]->[key_name(recipient, X, 0)]: [keywordparsedmsg]" , confidential = TRUE)
+ to_chat(X,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "PM: [key_name(src, X, 0)]->[key_name(recipient, X, 0)]: [keywordparsedmsg]" ,
+ confidential = TRUE)
/client/proc/popup_admin_pm(client/recipient, msg)
var/sender = src
@@ -318,9 +395,18 @@
log_admin_private("External PM: [sender] -> [key_name(C)] : [msg]")
msg = emoji_parse(msg)
- to_chat(C, "-- Administrator private message --", confidential = TRUE)
- to_chat(C, "Admin PM from-[adminname]: [msg]", confidential = TRUE)
- to_chat(C, "Click on the administrator's name to reply.", confidential = TRUE)
+ to_chat(C,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "-- Administrator private message --",
+ confidential = TRUE)
+ to_chat(C,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Admin PM from-[adminname]: [msg]",
+ confidential = TRUE)
+ to_chat(C,
+ type = MESSAGE_TYPE_ADMINPM,
+ html = "Click on the administrator's name to reply.",
+ confidential = TRUE)
admin_ticket_log(C, "PM From [tgs_tagged]: [msg]")
diff --git a/code/modules/admin/verbs/adminsay.dm b/code/modules/admin/verbs/adminsay.dm
index 5793ff6d3c2..62ba62f01c3 100644
--- a/code/modules/admin/verbs/adminsay.dm
+++ b/code/modules/admin/verbs/adminsay.dm
@@ -13,7 +13,11 @@
msg = keywords_lookup(msg)
var/custom_asay_color = (CONFIG_GET(flag/allow_admin_asaycolor) && prefs.asaycolor) ? "" : ""
msg = "ADMIN: [key_name(usr, 1)] [ADMIN_FLW(mob)]: [custom_asay_color][custom_asay_color ? "":null]"
- to_chat(GLOB.admins, msg, confidential = TRUE)
+ to_chat(GLOB.admins, list(
+ "type" = MESSAGE_TYPE_ADMINCHAT,
+ "html" = msg,
+ "confidential" = TRUE,
+ ))
SSblackbox.record_feedback("tally", "admin_verb", 1, "Asay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/code/modules/tgchat/message.dm b/code/modules/tgchat/message.dm
new file mode 100644
index 00000000000..2e9bfe784ec
--- /dev/null
+++ b/code/modules/tgchat/message.dm
@@ -0,0 +1,17 @@
+/**
+ * Message-related procs
+ *
+ * Message format (/list):
+ * - type - Message type, must be one of defines in `code/__DEFINES/chat.dm`
+ * - text - Plain message text
+ * - html - HTML message text
+ * - Optional metadata, can be any key/value pair.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/proc/message_to_html(message)
+ // Here it is possible to add a switch statement
+ // to custom-handle various message types.
+ return message["html"] || message["text"]
diff --git a/code/modules/tgchat/to_chat.dm b/code/modules/tgchat/to_chat.dm
new file mode 100644
index 00000000000..6e0691cfea3
--- /dev/null
+++ b/code/modules/tgchat/to_chat.dm
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/**
+ * Circumvents the message queue and sends the message
+ * to the recipient (target) as soon as possible.
+ */
+/proc/to_chat_immediate(target, html,
+ type = null,
+ text = null,
+ // FIXME: These flags are now pointless and have no effect
+ handle_whitespace = TRUE,
+ trailing_newline = TRUE,
+ confidential = FALSE)
+ if(!target || (!html && !text))
+ return
+ if(target == world)
+ target = GLOB.clients
+ // Build a message
+ var/message = list()
+ if(type) message["type"] = type
+ if(text) message["text"] = text
+ if(html) message["html"] = html
+ var/message_blob = TGUI_CREATE_MESSAGE("chat/message", message)
+ var/message_html = message_to_html(message)
+ if(islist(target))
+ for(var/_target in target)
+ var/client/client = CLIENT_FROM_VAR(_target)
+ if(client)
+ // Send to tgchat
+ client.tgui_panel?.window.send_raw_message(message_blob)
+ // Send to old chat
+ SEND_TEXT(client, message_html)
+ return
+ var/client/client = CLIENT_FROM_VAR(target)
+ if(client)
+ // Send to tgchat
+ client.tgui_panel?.window.send_raw_message(message_blob)
+ // Send to old chat
+ SEND_TEXT(client, message_html)
+
+/**
+ * Sends the message to the recipient (target).
+ *
+ * Recommended way to write to_chat calls:
+ * to_chat(client,
+ * type = MESSAGE_TYPE_INFO,
+ * html = "You have found [object]")
+ */
+/proc/to_chat(target, html,
+ type = null,
+ text = null,
+ // FIXME: These flags are now pointless and have no effect
+ handle_whitespace = TRUE,
+ trailing_newline = TRUE,
+ confidential = FALSE)
+ if(Master.current_runlevel == RUNLEVEL_INIT || !SSchat?.initialized)
+ to_chat_immediate(target, html, type, text)
+ return
+ if(!target || (!html && !text))
+ return
+ if(target == world)
+ target = GLOB.clients
+ // Build a message
+ var/message = list()
+ if(type) message["type"] = type
+ if(text) message["text"] = text
+ if(html) message["html"] = html
+ SSchat.queue(target, message)
diff --git a/code/modules/tgui_panel/external.dm b/code/modules/tgui_panel/external.dm
index 57c89dc194d..3257407e9cb 100644
--- a/code/modules/tgui_panel/external.dm
+++ b/code/modules/tgui_panel/external.dm
@@ -12,7 +12,7 @@
set name = "Fix chat"
set category = "OOC"
var/action
- log_tgui(src, "tgui_panel: Started fix_chat.")
+ log_tgui(src, "tgui_panel: Started fixing.")
// Not initialized
if(!tgui_panel || !istype(tgui_panel))
log_tgui(src, "tgui_panel: datum is missing")
diff --git a/code/modules/tgui_panel/to_chat.dm b/code/modules/tgui_panel/to_chat.dm
deleted file mode 100644
index aad27d48727..00000000000
--- a/code/modules/tgui_panel/to_chat.dm
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * Copyright (c) 2020 Aleksej Komarov
- * SPDX-License-Identifier: MIT
- */
-
-/**
- * global
- *
- * Circumvents the message queue and sends the message
- * to the recipient (target) as soon as possible.
- */
-/proc/to_chat_immediate(
- target,
- text,
- handle_whitespace = TRUE,
- trailing_newline = TRUE,
- confidential = FALSE)
- if(!target || !text)
- return
- if(target == world)
- target = GLOB.clients
- var/flags = handle_whitespace \
- | trailing_newline << 1 \
- | confidential << 2
- var/message = TGUI_CREATE_MESSAGE("chat/message", list(
- "text" = text,
- "flags" = flags,
- ))
- if(islist(target))
- for(var/_target in target)
- var/client/client = CLIENT_FROM_VAR(_target)
- if(client)
- // Send to tgchat
- client.tgui_panel?.window.send_raw_message(message)
- // Send to old chat
- SEND_TEXT(client, text)
- return
- var/client/client = CLIENT_FROM_VAR(target)
- if(client)
- // Send to tgchat
- client.tgui_panel?.window.send_raw_message(message)
- // Send to old chat
- SEND_TEXT(client, text)
-
-/**
- * global
- *
- * Sends the message to the recipient (target).
- */
-/proc/to_chat(
- target,
- text,
- handle_whitespace = TRUE,
- trailing_newline = TRUE,
- confidential = FALSE)
- if(Master.current_runlevel == RUNLEVEL_INIT || !SSchat?.initialized)
- to_chat_immediate(
- target,
- text,
- handle_whitespace,
- trailing_newline,
- confidential)
- return
- if(!target || !text)
- return
- if(target == world)
- target = GLOB.clients
- var/flags = handle_whitespace \
- | trailing_newline << 1 \
- | confidential << 2
- SSchat.queue(target, text, flags)
diff --git a/tgstation.dme b/tgstation.dme
index 32c399d1f04..e53d4b401f8 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -31,6 +31,7 @@
#include "code\__DEFINES\botany.dm"
#include "code\__DEFINES\callbacks.dm"
#include "code\__DEFINES\cargo.dm"
+#include "code\__DEFINES\chat.dm"
#include "code\__DEFINES\cinematics.dm"
#include "code\__DEFINES\cleaning.dm"
#include "code\__DEFINES\colors.dm"
@@ -3110,6 +3111,8 @@
#include "code\modules\swarmers\swarmer.dm"
#include "code\modules\swarmers\swarmer_act.dm"
#include "code\modules\swarmers\swarmer_objs.dm"
+#include "code\modules\tgchat\message.dm"
+#include "code\modules\tgchat\to_chat.dm"
#include "code\modules\tgs\includes.dm"
#include "code\modules\tgui\external.dm"
#include "code\modules\tgui\states.dm"
@@ -3136,7 +3139,6 @@
#include "code\modules\tgui_panel\external.dm"
#include "code\modules\tgui_panel\telemetry.dm"
#include "code\modules\tgui_panel\tgui_panel.dm"
-#include "code\modules\tgui_panel\to_chat.dm"
#include "code\modules\tooltip\tooltip.dm"
#include "code\modules\unit_tests\_unit_tests.dm"
#include "code\modules\uplink\uplink_devices.dm"
diff --git a/tgui/package.json b/tgui/package.json
index d0ef805c470..380cbda4efa 100644
--- a/tgui/package.json
+++ b/tgui/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "tgui",
- "version": "4.1.0",
+ "version": "4.2.0",
"workspaces": [
"packages/*"
],
diff --git a/tgui/packages/common/package.json b/tgui/packages/common/package.json
index 22560d92f93..5e04c1d5963 100644
--- a/tgui/packages/common/package.json
+++ b/tgui/packages/common/package.json
@@ -1,6 +1,6 @@
{
"private": true,
"name": "common",
- "version": "4.1.0",
+ "version": "4.2.0",
"type": "module"
}
diff --git a/tgui/packages/tgui-dev-server/package.json b/tgui/packages/tgui-dev-server/package.json
index 3e50a97f0eb..d3063870210 100644
--- a/tgui/packages/tgui-dev-server/package.json
+++ b/tgui/packages/tgui-dev-server/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "tgui-dev-server",
- "version": "4.1.0",
+ "version": "4.2.0",
"type": "module",
"dependencies": {
"axios": "^0.19.2",
diff --git a/tgui/packages/tgui-panel/Panel.js b/tgui/packages/tgui-panel/Panel.js
index d86a1fc8e86..fdd7977f4e3 100644
--- a/tgui/packages/tgui-panel/Panel.js
+++ b/tgui/packages/tgui-panel/Panel.js
@@ -14,9 +14,10 @@ import { PingIndicator } from './ping';
import { SettingsPanel, useSettings } from './settings';
export const Panel = (props, context) => {
- if (Byond.IS_LTE_IE8) {
+ // IE8-10: Needs special treatment due to missing Flex support
+ if (Byond.IS_LTE_IE10) {
return (
-
/gi,"\n").replace(/<\/?[a-z0-9-_]+[^>]*>/gi,"").replace(/&(nbsp|amp|quot|lt|gt|apos);/g,(function(e,n){return t[n]})).replace(/?([0-9]+);/gi,(function(e,t){var n=parseInt(t,10);return String.fromCharCode(n)})).replace(/?([0-9a-f]+);/gi,(function(e,t){var n=parseInt(t,16);return String.fromCharCode(n)}))};t.buildQueryString=function(e){return Object.keys(e).map((function(t){return encodeURIComponent(t)+"="+encodeURIComponent(e[t])})).join("&")}},function(e,t,n){"use strict";var r={}.hasOwnProperty;e.exports=function(e,t){return r.call(e,t)}},function(e,t,n){"use strict";var r=n(53),o=n(66),i=n(18),a=n(12),c=n(72),u=[].push,s=function(e){var t=1==e,n=2==e,s=3==e,l=4==e,f=6==e,d=5==e||f;return function(p,h,g,v){for(var m,y,b=i(p),w=o(b),x=r(h,g,3),_=a(w.length),E=0,k=v||c,S=t?k(p,_):n?k(p,0):undefined;_>E;E++)if((d||E in w)&&(y=x(m=w[E],E,b),e))if(t)S[E]=y;else if(y)switch(e){case 3:return!0;case 5:return m;case 6:return E;case 2:u.call(S,m)}else if(l)return!1;return f?-1:s||l?l:S}};e.exports={forEach:s(0),map:s(1),filter:s(2),some:s(3),every:s(4),find:s(5),findIndex:s(6)}},function(e,t,n){"use strict";t.__esModule=!0,t.useSelector=t.useDispatch=t.createAction=t.combineReducers=t.applyMiddleware=t.createStore=void 0;n(0);var r=n(30);function o(e,t){var n;if("undefined"==typeof Symbol||null==e[Symbol.iterator]){if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return i(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return i(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}return(n=e[Symbol.iterator]()).next.bind(n)}function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);nu||n!=n?l*Infinity:l*n}},function(e,t,n){"use strict";var r=n(7).isFinite;e.exports=Number.isFinite||function(e){return"number"==typeof e&&r(e)}},function(e,t,n){"use strict";var r=n(7),o=n(62).trim,i=n(91),a=r.parseFloat,c=1/a(i+"-0")!=-Infinity;e.exports=c?function(e){var t=o(String(e)),n=a(t);return 0===n&&"-"==t.charAt(0)?-0:n}:a},function(e,t,n){"use strict";var r=n(36);e.exports=function(e){if("number"!=typeof e&&"Number"!=r(e))throw TypeError("Incorrect invocation");return+e}},function(e,t,n){"use strict";var r=n(9),o=n(5),i=n(71),a=n(115),c=n(81),u=n(18),s=n(66),l=Object.assign,f=Object.defineProperty;e.exports=!l||o((function(){if(r&&1!==l({b:1},l(f({},"a",{enumerable:!0,get:function(){f(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var e={},t={},n=Symbol();return e[n]=7,"abcdefghijklmnopqrst".split("").forEach((function(e){t[e]=e})),7!=l({},e)[n]||"abcdefghijklmnopqrst"!=i(l({},t)).join("")}))?function(e,t){for(var n=u(e),o=arguments.length,l=1,f=a.f,d=c.f;o>l;)for(var p,h=s(arguments[l++]),g=f?i(h).concat(f(h)):i(h),v=g.length,m=0;v>m;)p=g[m++],r&&!d.call(h,p)||(n[p]=h[p]);return n}:l},function(e,t,n){"use strict";var r=n(121),o=n(84);e.exports=r?{}.toString:function(){return"[object "+o(this)+"]"}},function(e,t,n){"use strict";var r=n(7);e.exports=function(e,t){var n=r.console;n&&n.error&&(1===arguments.length?n.error(e):n.error(e,t))}},function(e,t,n){"use strict";var r=n(37);e.exports=function(e){var t=r(e);if(t<0)throw RangeError("The argument can't be less than 0");return t}},function(e,t,n){"use strict";t.__esModule=!0,t._CI=Ae,t._HI=B,t._M=Te,t._MCCC=Le,t._ME=Ie,t._MFCC=Re,t._MP=Ce,t._MR=ye,t.__render=je,t.createComponentVNode=function(e,t,n,r,o){var a=new T(1,null,null,e=function(e,t){if(12&e)return e;if(t.prototype&&t.prototype.render)return 4;if(t.render)return 32776;return 8}(e,t),r,function(e,t,n){var r=(32768&e?t.render:t).defaultProps;if(i(r))return n;if(i(n))return l(r,null);return N(n,r)}(e,t,n),function(e,t,n){if(4&e)return n;var r=(32768&e?t.render:t).defaultHooks;if(i(r))return n;if(i(n))return r;return N(n,r)}(e,t,o),t);k.createVNode&&k.createVNode(a);return a},t.createFragment=M,t.createPortal=function(e,t){var n=B(e);return O(1024,1024,null,n,0,null,n.key,t)},t.createRef=function(){return{current:null}},t.createRenderer=function(e){return function(t,n,r,o){e||(e=t),Fe(n,e,r,o)}},t.createTextVNode=I,t.createVNode=O,t.directClone=L,t.findDOMfromVNode=b,t.forwardRef=function(e){return{render:e}},t.getFlagsForElementVnode=function(e){switch(e){case"svg":return 32;case"input":return 64;case"select":return 256;case"textarea":return 128;case"$F":return 8192;default:return 1}},t.linkEvent=function(e,t){if(c(t))return{data:e,event:t};return null},t.normalizeProps=function(e){var t=e.props;if(t){var n=e.flags;481&n&&(void 0!==t.children&&i(e.children)&&P(e,t.children),void 0!==t.className&&(e.className=t.className||null,t.className=undefined)),void 0!==t.key&&(e.key=t.key,t.key=undefined),void 0!==t.ref&&(e.ref=8&n?l(e.ref,t.ref):t.ref,t.ref=undefined)}return e},t.render=Fe,t.rerender=He,t.version=t.options=t.Fragment=t.EMPTY_OBJ=t.Component=void 0;var r=Array.isArray;function o(e){var t=typeof e;return"string"===t||"number"===t}function i(e){return null==e}function a(e){return null===e||!1===e||!0===e||void 0===e}function c(e){return"function"==typeof e}function u(e){return"string"==typeof e}function s(e){return null===e}function l(e,t){var n={};if(e)for(var r in e)n[r]=e[r];if(t)for(var o in t)n[o]=t[o];return n}function f(e){return!s(e)&&"object"==typeof e}var d={};t.EMPTY_OBJ=d;function p(e){return e.substr(2).toLowerCase()}function h(e,t){e.appendChild(t)}function g(e,t,n){s(n)?h(e,t):e.insertBefore(t,n)}function v(e,t){e.removeChild(t)}function m(e){for(var t=0;t
\n":"'+(n?e:Y(e,!0))+"
\n"},t.blockquote=function(e){return""+(n?e:Y(e,!0))+"\n"+e+"
\n"},t.html=function(e){return e},t.heading=function(e,t,n,r){return this.options.headerIds?"
\n":"
\n"},t.list=function(e,t,n){var r=t?"ol":"ul";return"<"+r+(t&&1!==n?' start="'+n+'"':"")+">\n"+e+""+r+">\n"},t.listitem=function(e){return"\n\n"+e+"\n"+t+"
\n"},t.tablerow=function(e){return"\n"+e+" \n"},t.tablecell=function(e,t){var n=t.header?"th":"td";return(t.align?"<"+n+' align="'+t.align+'">':"<"+n+">")+e+""+n+">\n"},t.strong=function(e){return""+e+""},t.em=function(e){return""+e+""},t.codespan=function(e){return""+e+""},t.br=function(){return this.options.xhtml?"
":"
"},t.del=function(e){return""+e+""},t.link=function(e,t,n){if(null===(e=z(this.options.sanitize,this.options.baseUrl,e)))return n;var r='"+n+""},t.image=function(e,t,n){if(null===(e=z(this.options.sanitize,this.options.baseUrl,e)))return n;var r='":">"},t.text=function(e){return e},e}(),$=function(){function e(){}var t=e.prototype;return t.strong=function(e){return e},t.em=function(e){return e},t.codespan=function(e){return e},t.del=function(e){return e},t.html=function(e){return e},t.text=function(e){return e},t.link=function(e,t,n){return""+n},t.image=function(e,t,n){return""+n},t.br=function(){return""},e}(),H=function(){function e(){this.seen={}}return e.prototype.slug=function(e){var t=e.toLowerCase().trim().replace(/<[!\/a-z].*?>/gi,"").replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g,"").replace(/\s/g,"-");if(this.seen.hasOwnProperty(t)){var n=t;do{this.seen[n]++,t=n+"-"+this.seen[n]}while(this.seen.hasOwnProperty(t))}return this.seen[t]=0,t},e}(),W=r.defaults,q=x.unescape,G=function(){function e(e){this.options=e||W,this.options.renderer=this.options.renderer||new U,this.renderer=this.options.renderer,this.renderer.options=this.options,this.textRenderer=new $,this.slugger=new H}e.parse=function(t,n){return new e(n).parse(t)};var t=e.prototype;return t.parse=function(e,t){void 0===t&&(t=!0);var n,r,o,i,a,c,u,s,l,f,d,p,h,g,v,m,y,b,w="",x=e.length;for(n=0;n
"+Q(u.message+"",!0)+"";throw u}}return ne.options=ne.setOptions=function(e){return X(ne.defaults,e),ee(ne.defaults),ne},ne.getDefaults=J,ne.defaults=te,ne.use=function(e){var t=X({},e);if(e.renderer&&function(){var n=ne.defaults.renderer||new U,r=function(t){var r=n[t];n[t]=function(){for(var o=arguments.length,i=new Array(o),a=0;a
'+(n?e:Y(e,!0))+"\n":""+(n?e:Y(e,!0))+"\n"},t.blockquote=function(e){return"\n"+e+"\n"},t.html=function(e){return e},t.heading=function(e,t,n,r){return this.options.headerIds?"
"+e+"
\n"},t.table=function(e,t){return t&&(t=""+t+""),""+e+""},t.br=function(){return this.options.xhtml?""+Q(u.message+"",!0)+"";throw u}}return ne.options=ne.setOptions=function(e){return X(ne.defaults,e),ee(ne.defaults),ne},ne.getDefaults=J,ne.defaults=te,ne.use=function(e){var t=X({},e);if(e.renderer&&function(){var n=ne.defaults.renderer||new U,r=function(t){var r=n[t];n[t]=function(){for(var o=arguments.length,i=new Array(o),a=0;a