From 475fe94750732c8b4e4ad218b2b55c996e90bffa Mon Sep 17 00:00:00 2001
From: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Date: Sun, 26 Apr 2026 00:20:58 -0400
Subject: [PATCH] Say verb & skin shenanigans (Pray is a hotkey, Whisper is in
commandbar) (#95477)
## About The Pull Request
Removes Say/Me/OOC/Pray/Whisper as stat panel verbs
1. Adds a hotkey for Pray, P by default, and also adds it to the list of
say modes (so tabbing on tgui-say will show it)
2. Makes Whisper a button on the commandbar, which is literally just
``say "#``, letting players know it exists in a more direct manner.
3. You now need 2 characters to be considered "typing", so `#` for
whisper won't cause you to start to think anymore.
Also messes around with the skin a bit, moving the commandbar to be
separate from the chat window, therefore not resizing if the chat
window/stat panel splitter changes, since I was already messing with the
command bar entirely I might as well look into getting this fixed. This
will go even better with
https://github.com/tgstation/tgstation/pull/95383 as it wont stretch the
command bar when the stat panel is closed.
https://github.com/user-attachments/assets/26cd9df0-7582-445e-9f67-c03d2c9cf46d
Also adds a new Adminhelp submenu for the Pray button for additional
visibility
https://github.com/user-attachments/assets/64f31f86-92a2-4f04-b248-6bbe301919fe
My current goal is the deletion of the IC tab so I've been trying to
inch towards that.
## Why It's Good For The Game
Moves Praying & Whispering into something that's more easily seen by
newer players, and helps cut some of the more useless verbs out of the
stat panel.
This is also part of my project you can see more of here -
https://hackmd.io/443_dE5lRWeEAp9bjGcKYw?view
## Changelog
:cl:
qol: Pray verb is now a hotkey, Whisper is now a command bar button, and
all say verbs have been removed from the stat panel.
qol: Admin button on the escape menu is now its own section for praying,
as well as seeing your latest ticket & admin notes.
fix: Resizing the stat panel will no longer change the size of the
command bar.
/:cl:
---
code/__DEFINES/keybinding.dm | 1 +
code/__DEFINES/say.dm | 8 ++
code/__DEFINES/speech_channels.dm | 1 +
code/datums/keybinding/communication.dm | 30 +++++
code/modules/admin/verbs/pray.dm | 39 +++---
code/modules/client/verbs/ooc.dm | 3 +-
code/modules/client/verbs/typing.dm | 2 +-
code/modules/escape_menu/admin_page.dm | 83 ++++++++++++
code/modules/escape_menu/escape_menu.dm | 10 ++
code/modules/escape_menu/home_page.dm | 4 +-
.../escape_menu/screen_objects/admin_text.dm | 93 +++++--------
.../escape_menu/screen_objects/text.dm | 2 +-
code/modules/mob/mob_say.dm | 12 +-
code/modules/tgui_input/say_modal/modal.dm | 2 +-
code/modules/tgui_input/say_modal/speech.dm | 7 +-
interface/skin.dmf | 126 +++++++++++-------
tgstation.dme | 1 +
tgui/packages/tgui-panel/settings/scaling.ts | 5 +-
tgui/packages/tgui-panel/settings/themes.ts | 2 +
.../packages/tgui-say/ChannelIterator.test.ts | 1 +
tgui/packages/tgui-say/ChannelIterator.ts | 6 +-
tgui/packages/tgui-say/styles/colors.scss | 1 +
22 files changed, 293 insertions(+), 146 deletions(-)
create mode 100644 code/modules/escape_menu/admin_page.dm
diff --git a/code/__DEFINES/keybinding.dm b/code/__DEFINES/keybinding.dm
index 1e88e16d5d7..73da7d1924c 100644
--- a/code/__DEFINES/keybinding.dm
+++ b/code/__DEFINES/keybinding.dm
@@ -39,6 +39,7 @@
#define COMSIG_KB_CLIENT_RADIO_DOWN "keybinding_client_radio_down"
#define COMSIG_KB_CLIENT_ME_DOWN "keybinding_client_me_down"
#define COMSIG_KB_CLIENT_OOC_DOWN "keybinding_client_ooc_down"
+#define COMSIG_KB_CLIENT_PRAY_DOWN "keybinding_client_pray_down"
//Human
#define COMSIG_KB_HUMAN_QUICKEQUIP_DOWN "keybinding_human_quickequip_down"
diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm
index 9b8496f22fd..cf31b1e69f9 100644
--- a/code/__DEFINES/say.dm
+++ b/code/__DEFINES/say.dm
@@ -146,3 +146,11 @@
///Defines for priorities for the bubble_icon_override comp
#define BUBBLE_ICON_PRIORITY_ACCESSORY 2
#define BUBBLE_ICON_PRIORITY_ORGAN 1
+
+//These are the names of the commands, what you'd need to type to do the command (also used in winset)
+#define VERB_SAY "Say"
+#define VERB_WHISPER "Whisper"
+#define VERB_ME "Me"
+//Gave this shit a shorter name so you only have to time out "ooc" rather than "ooc message" to use it --NeoFite
+#define VERB_OOC "OOC"
+#define VERB_PRAY "Pray"
diff --git a/code/__DEFINES/speech_channels.dm b/code/__DEFINES/speech_channels.dm
index 9f1526ebd56..3cd5b40374e 100644
--- a/code/__DEFINES/speech_channels.dm
+++ b/code/__DEFINES/speech_channels.dm
@@ -4,3 +4,4 @@
#define ME_CHANNEL "Me"
#define OOC_CHANNEL "OOC"
#define ADMIN_CHANNEL "Admin"
+#define PRAY_CHANNEL "Pray"
diff --git a/code/datums/keybinding/communication.dm b/code/datums/keybinding/communication.dm
index bc77eee0f93..a5aa6981243 100644
--- a/code/datums/keybinding/communication.dm
+++ b/code/datums/keybinding/communication.dm
@@ -11,6 +11,9 @@
. = ..()
if(.)
return
+ if(!user.prefs.read_preference(/datum/preference/toggle/tgui_input))
+ winset(user, null, "command=[VERB_SAY]")
+ return TRUE
winset(user, null, "command=[user.tgui_say_create_open_command(SAY_CHANNEL)];")
winset(user, "tgui_say.browser", "focus=true")
return TRUE
@@ -25,6 +28,9 @@
. = ..()
if(.)
return
+ if(!user.prefs.read_preference(/datum/preference/toggle/tgui_input))
+ winset(user, null, "command=[VERB_SAY]")
+ return TRUE
winset(user, null, "command=[user.tgui_say_create_open_command(RADIO_CHANNEL)]")
winset(user, "tgui_say.browser", "focus=true")
return TRUE
@@ -39,6 +45,9 @@
. = ..()
if(.)
return
+ if(!user.prefs.read_preference(/datum/preference/toggle/tgui_input))
+ winset(user, null, "command=[VERB_OOC]")
+ return TRUE
winset(user, null, "command=[user.tgui_say_create_open_command(OOC_CHANNEL)]")
winset(user, "tgui_say.browser", "focus=true")
return TRUE
@@ -53,6 +62,27 @@
. = ..()
if(.)
return
+ if(!user.prefs.read_preference(/datum/preference/toggle/tgui_input))
+ winset(user, null, "command=[VERB_ME]")
+ return TRUE
winset(user, null, "command=[user.tgui_say_create_open_command(ME_CHANNEL)]")
winset(user, "tgui_say.browser", "focus=true")
return TRUE
+
+/datum/keybinding/client/communication/pray
+ hotkey_keys = list("P")
+ name = PRAY_CHANNEL
+ full_name = "Pray"
+ description = "Allows you to directly send a message to your deity (Admins) in an IC manner."
+ keybind_signal = COMSIG_KB_CLIENT_PRAY_DOWN
+
+/datum/keybinding/client/communication/pray/down(client/user, turf/target, mousepos_x, mousepos_y)
+ . = ..()
+ if(.)
+ return
+ if(!user.prefs.read_preference(/datum/preference/toggle/tgui_input))
+ winset(user, null, "command=[VERB_PRAY]")
+ return TRUE
+ winset(user, null, "command=[user.tgui_say_create_open_command(PRAY_CHANNEL)];")
+ winset(user, "tgui_say.browser", "focus=true")
+ return TRUE
diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm
index 4277cae7d81..aefd988aa74 100644
--- a/code/modules/admin/verbs/pray.dm
+++ b/code/modules/admin/verbs/pray.dm
@@ -1,51 +1,50 @@
-/mob/verb/pray(msg as text)
- set category = "IC"
- set name = "Pray"
+/mob/verb/pray(message as text)
+ set name = VERB_PRAY
if(GLOB.say_disabled) //This is here to try to identify lag problems
- to_chat(usr, span_danger("Speech is currently admin-disabled."), confidential = TRUE)
+ to_chat(src, span_danger("Speech is currently admin-disabled."), confidential = TRUE)
return
- msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN)
- if(!msg)
+ message = copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN)
+ if(!message)
return
- log_prayer("[src.key]/([src.name]): [msg]")
- if(usr.client)
- if(usr.client.prefs.muted & MUTE_PRAY)
- to_chat(usr, span_danger("You cannot pray (muted)."), confidential = TRUE)
+ log_prayer("[src.key]/([src.name]): [message]")
+ if(src.client)
+ if(src.client.prefs.muted & MUTE_PRAY)
+ to_chat(src, span_danger("You cannot pray (muted)."), confidential = TRUE)
return
- if(src.client.handle_spam_prevention(msg,MUTE_PRAY))
+ if(src.client.handle_spam_prevention(message, MUTE_PRAY))
return
var/mutable_appearance/cross = mutable_appearance('icons/obj/storage/book.dmi', "bible")
var/font_color = "purple"
var/prayer_type = "PRAYER"
var/deity
- if(usr.job == JOB_CHAPLAIN)
+ if(src.job == JOB_CHAPLAIN)
cross.icon_state = "kingyellow"
font_color = "blue"
prayer_type = "CHAPLAIN PRAYER"
if(GLOB.deity)
deity = GLOB.deity
- else if(IS_CULTIST(usr))
+ else if(IS_CULTIST(src))
cross.icon_state = "tome"
font_color = "red"
prayer_type = "CULTIST PRAYER"
deity = "Nar'Sie"
- else if(isliving(usr))
- var/mob/living/L = usr
+ else if(isliving(src))
+ var/mob/living/L = src
if(HAS_TRAIT(L, TRAIT_SPIRITUAL))
cross.icon_state = "holylight"
font_color = "blue"
prayer_type = "SPIRITUAL PRAYER"
- var/msg_tmp = msg
- GLOB.requests.pray(usr.client, msg, usr.job == JOB_CHAPLAIN)
- msg = span_adminnotice("[icon2html(cross, GLOB.admins)][prayer_type][deity ? " (to [deity])" : ""]: [ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]: [span_linkify(msg)]")
+ var/msg_tmp = message
+ GLOB.requests.pray(src.client, message, src.job == JOB_CHAPLAIN)
+ message = span_adminnotice("[icon2html(cross, GLOB.admins)][prayer_type][deity ? " (to [deity])" : ""]: [ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]: [span_linkify(message)]")
for(var/client/C in GLOB.admins)
if(get_chat_toggles(C) & CHAT_PRAYER)
- to_chat(C, msg, type = MESSAGE_TYPE_PRAYER, confidential = TRUE)
- to_chat(usr, span_info("You pray to the gods: \"[msg_tmp]\""), confidential = TRUE)
+ to_chat(C, message, type = MESSAGE_TYPE_PRAYER, confidential = TRUE)
+ to_chat(src, span_info("You pray to the gods: \"[msg_tmp]\""), confidential = TRUE)
BLACKBOX_LOG_ADMIN_VERB("Prayer")
diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm
index b1a95e81c2b..a25299089e2 100644
--- a/code/modules/client/verbs/ooc.dm
+++ b/code/modules/client/verbs/ooc.dm
@@ -3,8 +3,7 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
///talking in OOC uses this
/client/verb/ooc(msg as text)
- set name = "OOC" //Gave this shit a shorter name so you only have to time out "ooc" rather than "ooc message" to use it --NeoFite
- set category = "OOC"
+ set name = VERB_OOC
if(GLOB.say_disabled) //This is here to try to identify lag problems
to_chat(usr, span_danger("Speech is currently admin-disabled."))
diff --git a/code/modules/client/verbs/typing.dm b/code/modules/client/verbs/typing.dm
index b7a73622614..bef6d85a048 100644
--- a/code/modules/client/verbs/typing.dm
+++ b/code/modules/client/verbs/typing.dm
@@ -9,7 +9,7 @@
/client/proc/handle_commandbar_typing(href_list)
if (!typing_indicators) //check pref
return
- if (length(href_list["verb"]) < 1 || !(LOWER_TEXT(href_list["verb"]) in IC_VERBS) || text2num(href_list["argument_length"]) < 1)
+ if (length(href_list["verb"]) < 1 || !(LOWER_TEXT(href_list["verb"]) in IC_VERBS) || text2num(href_list["argument_length"]) < 2)
if (commandbar_typing)
commandbar_typing = FALSE
stop_typing()
diff --git a/code/modules/escape_menu/admin_page.dm b/code/modules/escape_menu/admin_page.dm
new file mode 100644
index 00000000000..3eeacf70e93
--- /dev/null
+++ b/code/modules/escape_menu/admin_page.dm
@@ -0,0 +1,83 @@
+/datum/escape_menu/proc/show_admin_page()
+ page_holder.give_screen_object(
+ new /atom/movable/screen/escape_menu/lobby_button/small(
+ null,
+ /* hud_owner = */ null,
+ "Back",
+ /* tooltip_text = */ null,
+ /* button_screen_loc = */ "TOP:-30,LEFT:30",
+ CALLBACK(src, PROC_REF(open_home_page)),
+ /* button_overlay = */ "back",
+ )
+ )
+
+ page_holder.give_screen_object(
+ new /atom/movable/screen/escape_menu/text/clickable/admin_help(
+ null,
+ /* hud_owner = */ null,
+ /* escape_menu = */ src,
+ /* button_text = */ "Create Admin Ticket",
+ /* offset = */ list(-136, 28),
+ /* font_size = */ 24,
+ /* on_click_callback = */ CALLBACK(src, PROC_REF(create_ticket)),
+ )
+ )
+
+ page_holder.give_screen_object(
+ new /atom/movable/screen/escape_menu/text/clickable/admin_ticket_notification(
+ null,
+ /* hud_owner = */ null,
+ /* escape_menu = */ src,
+ /* button_text = */ "View Latest Ticket",
+ /* offset = */ list(-171, 28),
+ /* font_size = */ 24,
+ /* on_click_callback = */ CALLBACK(src, PROC_REF(view_latest_ticket)),
+ )
+ )
+ page_holder.give_screen_object(
+ new /atom/movable/screen/escape_menu/text/clickable(
+ null,
+ /* hud_owner = */ null,
+ /* escape_menu = */ src,
+ /* button_text = */ "Pray",
+ /* offset = */ list(-206, 30),
+ /* font_size = */ 24,
+ /* on_click_callback = */ CALLBACK(src, PROC_REF(pray)),
+ )
+ )
+
+ if(CONFIG_GET(flag/see_own_notes))
+ page_holder.give_screen_object(
+ new /atom/movable/screen/escape_menu/text/clickable(
+ null,
+ /* hud_owner = */ null,
+ /* escape_menu = */ src,
+ /* button_text = */ "See Notes",
+ /* offset = */ list(-241, 30),
+ /* font_size = */ 24,
+ /* on_click_callback = */ CALLBACK(src, PROC_REF(see_notes)),
+ )
+ )
+
+/datum/escape_menu/proc/create_ticket()
+ if(!(/client/verb/adminhelp in client?.verbs))
+ return
+ client?.adminhelp()
+ qdel(src)
+
+///Opens your latest admin ticket.
+/datum/escape_menu/proc/view_latest_ticket()
+ client?.view_latest_ticket()
+
+///Manually calls the user's pray() hotkey (which is where prefs is taken into account).
+/datum/escape_menu/proc/pray()
+ var/datum/keybinding/client/communication/pray/pray_verb = GLOB.keybindings_by_name[/datum/keybinding/client/communication/pray::name]
+ pray_verb.down(client)
+ qdel(src)
+
+/datum/escape_menu/proc/see_notes()
+ if(!CONFIG_GET(flag/see_own_notes))
+ to_chat(client.mob, span_notice("Seeing notes has been disabled on this server."))
+ return
+ browse_messages(null, client.ckey, null, TRUE)
+ qdel(src)
diff --git a/code/modules/escape_menu/escape_menu.dm b/code/modules/escape_menu/escape_menu.dm
index 53940e0ae07..8e86d78d647 100644
--- a/code/modules/escape_menu/escape_menu.dm
+++ b/code/modules/escape_menu/escape_menu.dm
@@ -16,6 +16,7 @@ GLOBAL_LIST_EMPTY(escape_menus)
new /datum/escape_menu(src)
#define PAGE_HOME "PAGE_HOME"
+#define PAGE_ADMIN "ADMIN_PAGE"
#define PAGE_LEAVE_BODY "PAGE_LEAVE_BODY"
#define PAGE_QUIT_GAME "PAGE_QUIT_GAME"
@@ -126,6 +127,8 @@ GLOBAL_LIST_EMPTY(escape_menus)
switch (menu_page)
if (PAGE_HOME)
show_home_page()
+ if (PAGE_ADMIN)
+ show_admin_page()
if (PAGE_LEAVE_BODY)
show_leave_body_page()
if(PAGE_QUIT_GAME)
@@ -145,6 +148,12 @@ GLOBAL_LIST_EMPTY(escape_menus)
menu_page = PAGE_HOME
show_page()
+/datum/escape_menu/proc/open_admin_page()
+ PRIVATE_PROC(TRUE)
+
+ menu_page = PAGE_ADMIN
+ show_page()
+
/datum/escape_menu/proc/open_leave_body()
PRIVATE_PROC(TRUE)
@@ -162,5 +171,6 @@ GLOBAL_LIST_EMPTY(escape_menus)
clear_with_screen = FALSE
#undef PAGE_HOME
+#undef PAGE_ADMIN
#undef PAGE_LEAVE_BODY
#undef PAGE_QUIT_GAME
diff --git a/code/modules/escape_menu/home_page.dm b/code/modules/escape_menu/home_page.dm
index 641be0d3a34..aead9cc8d51 100644
--- a/code/modules/escape_menu/home_page.dm
+++ b/code/modules/escape_menu/home_page.dm
@@ -37,12 +37,14 @@
)
page_holder.give_screen_object(
- new /atom/movable/screen/escape_menu/text/clickable/admin_help(
+ new /atom/movable/screen/escape_menu/text/clickable/admin_ticket_notification(
null,
/* hud_owner = */ null,
/* escape_menu = */ src,
/* button_text = */ "Admin Help",
/* offset = */ list(-241, 30),
+ /* font_size = */ 24,
+ /* on_click_callback = */ CALLBACK(src, PROC_REF(open_admin_page)),
)
)
diff --git a/code/modules/escape_menu/screen_objects/admin_text.dm b/code/modules/escape_menu/screen_objects/admin_text.dm
index 2a7260785b3..abedf2cba3f 100644
--- a/code/modules/escape_menu/screen_objects/admin_text.dm
+++ b/code/modules/escape_menu/screen_objects/admin_text.dm
@@ -1,12 +1,12 @@
-/atom/movable/screen/escape_menu/text/clickable/admin_help
+///Text that gets highlighted when you have an admin ticket notification.
+/atom/movable/screen/escape_menu/text/clickable/admin_ticket_notification
VAR_PRIVATE
current_blink = FALSE
is_blinking = FALSE
last_blink_time = 0
-
blink_interval = 0.4 SECONDS
-/atom/movable/screen/escape_menu/text/clickable/admin_help/Initialize(
+/atom/movable/screen/escape_menu/text/clickable/admin_ticket_notification/Initialize(
mapload,
datum/hud/hud_owner,
datum/escape_menu/escape_menu,
@@ -18,7 +18,6 @@
. = ..()
RegisterSignal(escape_menu.client, COMSIG_ADMIN_HELP_RECEIVED, PROC_REF(on_admin_help_received))
- RegisterSignals(escape_menu.client, list(COMSIG_CLIENT_VERB_ADDED, COMSIG_CLIENT_VERB_REMOVED), PROC_REF(on_client_verb_changed))
var/datum/admin_help/current_ticket = escape_menu.client?.current_ticket
if (!isnull(current_ticket))
@@ -26,48 +25,12 @@
if (!current_ticket?.player_replied)
begin_processing()
-/atom/movable/screen/escape_menu/text/clickable/admin_help/Click(location, control, params)
- if (!enabled())
- return
-
- QDEL_IN(escape_menu, 0)
-
- var/client/client = escape_menu.client
-
- if (has_open_adminhelp())
- client?.view_latest_ticket()
- else
- client?.adminhelp()
-
-/atom/movable/screen/escape_menu/text/clickable/admin_help/proc/has_open_adminhelp()
- var/client/client = escape_menu.client
-
- var/datum/admin_help/current_ticket = client?.current_ticket
-
- // This is null with a closed ticket.
- // This is okay since the View Latest Ticket panel already tells you if your ticket is closed, intentionally.
- if (isnull(current_ticket))
- return FALSE
-
- // If we sent a ticket, but nobody has responded, send another one instead.
- // Not worth opening a menu when there's nothing to read, you're only going to want to send.
- if (length(current_ticket.admins_involved - client?.ckey) == 0)
- return FALSE
-
- return TRUE
-
-/atom/movable/screen/escape_menu/text/clickable/admin_help/proc/on_admin_help_received()
+/atom/movable/screen/escape_menu/text/clickable/admin_ticket_notification/proc/on_admin_help_received()
SIGNAL_HANDLER
begin_processing()
-/atom/movable/screen/escape_menu/text/clickable/admin_help/proc/on_client_verb_changed(client/source, list/verbs_changed)
- SIGNAL_HANDLER
-
- if (/client/verb/adminhelp in verbs_changed)
- update_text()
-
-/atom/movable/screen/escape_menu/text/clickable/admin_help/proc/begin_processing()
+/atom/movable/screen/escape_menu/text/clickable/admin_ticket_notification/proc/begin_processing()
if (is_blinking)
return
@@ -76,7 +39,7 @@
START_PROCESSING(SSescape_menu, src)
update_text()
-/atom/movable/screen/escape_menu/text/clickable/admin_help/proc/end_processing()
+/atom/movable/screen/escape_menu/text/clickable/admin_ticket_notification/proc/end_processing()
if (!is_blinking)
return
@@ -85,26 +48,17 @@
STOP_PROCESSING(SSescape_menu, src)
update_text()
-/atom/movable/screen/escape_menu/text/clickable/admin_help/proc/connect_ticket(datum/admin_help/admin_help)
+/atom/movable/screen/escape_menu/text/clickable/admin_ticket_notification/proc/connect_ticket(datum/admin_help/admin_help)
ASSERT(istype(admin_help))
RegisterSignal(admin_help, COMSIG_ADMIN_HELP_REPLIED, PROC_REF(on_admin_help_replied))
-/atom/movable/screen/escape_menu/text/clickable/admin_help/proc/on_admin_help_replied()
+/atom/movable/screen/escape_menu/text/clickable/admin_ticket_notification/proc/on_admin_help_replied()
SIGNAL_HANDLER
end_processing()
-/atom/movable/screen/escape_menu/text/clickable/admin_help/enabled()
- if (!..())
- return FALSE
-
- if (!has_open_adminhelp())
- return /client/verb/adminhelp in escape_menu.client?.verbs
-
- return TRUE
-
-/atom/movable/screen/escape_menu/text/clickable/admin_help/process(seconds_per_tick)
+/atom/movable/screen/escape_menu/text/clickable/admin_ticket_notification/process(seconds_per_tick)
if (world.time - last_blink_time < blink_interval)
return
@@ -112,19 +66,42 @@
last_blink_time = world.time
update_text()
-/atom/movable/screen/escape_menu/text/clickable/admin_help/text_color()
+/atom/movable/screen/escape_menu/text/clickable/admin_ticket_notification/text_color()
if (!enabled())
return ..()
return current_blink ? "red" : ..()
-/atom/movable/screen/escape_menu/text/clickable/admin_help/MouseEntered(location, control, params)
+/atom/movable/screen/escape_menu/text/clickable/admin_ticket_notification/MouseEntered(location, control, params)
. = ..()
if (is_blinking)
openToolTip(usr, src, params, content = "An admin is trying to talk to you!")
-/atom/movable/screen/escape_menu/text/clickable/admin_help/MouseExited(location, control, params)
+/atom/movable/screen/escape_menu/text/clickable/admin_ticket_notification/MouseExited(location, control, params)
. = ..()
closeToolTip(usr)
+
+///The button used for adminhelping, this is used to grey it out when you're on cooldown.
+/atom/movable/screen/escape_menu/text/clickable/admin_help/Initialize(
+ mapload,
+ datum/hud/hud_owner,
+ datum/escape_menu/escape_menu,
+ button_text,
+ list/offset,
+ font_size = 24,
+ on_click_callback,
+)
+ . = ..()
+
+ RegisterSignals(escape_menu.client, list(COMSIG_CLIENT_VERB_ADDED, COMSIG_CLIENT_VERB_REMOVED), PROC_REF(on_client_verb_changed))
+
+/atom/movable/screen/escape_menu/text/clickable/admin_help/proc/on_client_verb_changed(client/source, list/verbs_changed)
+ SIGNAL_HANDLER
+
+ if (/client/verb/adminhelp in verbs_changed)
+ update_text()
+
+/atom/movable/screen/escape_menu/text/clickable/admin_help/enabled()
+ return (/client/verb/adminhelp in escape_menu.client?.verbs)
diff --git a/code/modules/escape_menu/screen_objects/text.dm b/code/modules/escape_menu/screen_objects/text.dm
index fe08bd021dc..529bd034c86 100644
--- a/code/modules/escape_menu/screen_objects/text.dm
+++ b/code/modules/escape_menu/screen_objects/text.dm
@@ -23,7 +23,7 @@
//this decides how far out you can 'click' on this, so it's important to keep it short.
//yes even here, maptext can still embed links without using clickable subtype.
- src.maptext_width = round((max(length(button_text), 20) * (font_size / 2.25)), 1)
+ src.maptext_width = max(round(length(button_text) * font_size * 0.75), font_size * 4)
src.maptext_height = (maptext_height * (font_size / 5))
src.maptext_height *= length(splittext(button_text, "\n"))
diff --git a/code/modules/mob/mob_say.dm b/code/modules/mob/mob_say.dm
index 568df514fdf..920439d1091 100644
--- a/code/modules/mob/mob_say.dm
+++ b/code/modules/mob/mob_say.dm
@@ -2,9 +2,7 @@
///what clients use to speak. when you type a message into the chat bar in say mode, this is the first thing that goes off serverside.
/mob/verb/say_verb(message as text)
- set name = "Say"
- set category = "IC"
- set instant = TRUE
+ set name = VERB_SAY
if(GLOB.say_disabled) //This is here to try to identify lag problems
to_chat(usr, span_danger("Speech is currently admin-disabled."))
@@ -17,9 +15,7 @@
///Whisper verb
/mob/verb/whisper_verb(message as text)
- set name = "Whisper"
- set category = "IC"
- set instant = TRUE
+ set name = VERB_WHISPER
if(GLOB.say_disabled) //This is here to try to identify lag problems
to_chat(usr, span_danger("Speech is currently admin-disabled."))
@@ -40,9 +36,7 @@
///The me emote verb
/mob/verb/me_verb(message as text)
- set name = "Me"
- set category = "IC"
- set desc = "Perform a custom emote. Leave blank to pick between an audible or a visible emote (Defaults to visible)."
+ set name = VERB_ME
if(GLOB.say_disabled) //This is here to try to identify lag problems
to_chat(usr, span_danger("Speech is currently admin-disabled."))
diff --git a/code/modules/tgui_input/say_modal/modal.dm b/code/modules/tgui_input/say_modal/modal.dm
index 054c656d2e9..5dc6d7d9a97 100644
--- a/code/modules/tgui_input/say_modal/modal.dm
+++ b/code/modules/tgui_input/say_modal/modal.dm
@@ -92,7 +92,7 @@
if(!payload?["channel"])
CRASH("No channel provided to an open TGUI-Say")
window_open = TRUE
- if(payload["channel"] != OOC_CHANNEL && payload["channel"] != ADMIN_CHANNEL)
+ if(payload["channel"] != OOC_CHANNEL && payload["channel"] != ADMIN_CHANNEL && payload["channel"] != PRAY_CHANNEL)
start_thinking()
if(!client.typing_indicators)
log_speech_indicators("[key_name(client)] started typing at [loc_name(client.mob)], indicators DISABLED.")
diff --git a/code/modules/tgui_input/say_modal/speech.dm b/code/modules/tgui_input/say_modal/speech.dm
index d27e7cfc0fd..79f604af6d5 100644
--- a/code/modules/tgui_input/say_modal/speech.dm
+++ b/code/modules/tgui_input/say_modal/speech.dm
@@ -12,7 +12,7 @@
var/list/phrases = alter_phrases || hurt_phrases
/// No OOC leaks
- if(!entry || payload["channel"] == OOC_CHANNEL || payload["channel"] == ME_CHANNEL)
+ if(!entry || payload["channel"] == OOC_CHANNEL || payload["channel"] == ME_CHANNEL || payload["channel"] == PRAY_CHANNEL)
return pick(phrases)
/// Random trimming for larger sentences
if(length(entry) > 50)
@@ -50,6 +50,9 @@
if(ADMIN_CHANNEL)
INVOKE_ASYNC(SSadmin_verbs, TYPE_PROC_REF(/datum/controller/subsystem/admin_verbs, dynamic_invoke_verb), client, /datum/admin_verb/cmd_admin_say, entry)
return TRUE
+ if(PRAY_CHANNEL)
+ client.mob.pray(entry)
+ return TRUE
return FALSE
/**
@@ -129,7 +132,7 @@
return TRUE
if(type == "force")
var/target_channel = payload["channel"]
- if(target_channel == ME_CHANNEL || target_channel == OOC_CHANNEL)
+ if(target_channel == ME_CHANNEL || target_channel == OOC_CHANNEL || target_channel == PRAY_CHANNEL)
target_channel = SAY_CHANNEL // No ooc leaks
delegate_speech(alter_entry(payload), target_channel)
return TRUE
diff --git a/interface/skin.dmf b/interface/skin.dmf
index 366919f4b1c..2b9460cd8ca 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -138,7 +138,6 @@ window "info_and_buttons"
size = 640x477
anchor1 = 0,0
anchor2 = 100,100
- background-color = #ffc41f
saved-params = "splitter"
left = "infobuttons"
right = "infowindow"
@@ -149,23 +148,71 @@ window "info_and_buttons"
window "infowindow"
elem "infowindow"
type = MAIN
- pos = 281,0
+ pos = 281,5
size = 640x475
anchor1 = -1,-1
anchor2 = -1,-1
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
- elem "info"
+ elem "info_split"
type = CHILD
pos = 0,5
+ anchor1 = 0,0
+ anchor2 = 100,100
+ size = 640x475
+ is-vert = false
+ splitter = 97.5
+ background-color = #ffc41f
+ show-splitter = false
+ saved-params = "splitter"
+ left = "info"
+ right = "input_buttons"
+
+window "info"
+ elem "info_main"
+ type = MAIN
+ pos = 0,0
+ size = 640x475
+ anchor1 = 0,0
+ anchor2 = 100,100
+ saved-params = "pos;size;is-minimized;is-maximized"
+ is-pane = true
+ elem "info_child"
+ type = CHILD
+ pos = 0,0
size = 640x475
anchor1 = 0,0
anchor2 = 100,100
saved-params = "splitter"
+ splitter = 40
+ background-color = #202020
left = "statwindow"
right = "outputwindow"
is-vert = false
+window "input_buttons"
+ elem "input_buttons_main"
+ type = MAIN
+ pos = 0,0
+ size = 640x475
+ anchor1 = 0,0
+ anchor2 = 100,100
+ saved-params = "pos;size;is-minimized;is-maximized"
+ is-pane = true
+ elem "input_buttons_child"
+ type = CHILD
+ pos = 0,0
+ size = 640x475
+ anchor1 = 0,0
+ anchor2 = 100,100
+ background-color = #ffc41f
+ saved-params = "splitter"
+ left = "inputwindow"
+ right = "inputbuttons"
+ is-vert = true
+ splitter = 70
+ show-splitter = false
+
window "outputwindow"
elem "outputwindow"
type = MAIN
@@ -175,19 +222,14 @@ window "outputwindow"
anchor2 = -1,-1
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
- elem "output_input_child"
+ elem "output_selector_screen"
type = CHILD
- pos = 0,0
size = 640x475
anchor1 = 0,0
anchor2 = 100,100
- background-color = #ffc41f
saved-params = "splitter"
+ background-color = #ffc41f
left = "output_selector"
- right = "input_and_buttons"
- is-vert = false
- splitter = 96
- show-splitter = false
window "output_selector"
elem "output_selector"
@@ -207,27 +249,6 @@ window "output_selector"
saved-params = "splitter"
left = "output_legacy"
is-vert = false
-
-window "input_and_buttons"
- elem "input_and_buttons"
- type = MAIN
- pos = 291,0
- size = 640x20
- anchor1 = -1,-1
- anchor2 = -1,-1
- saved-params = "pos;size;is-minimized;is-maximized"
- is-pane = true
- elem "input_buttons_child"
- type = CHILD
- pos = 0,0
- size = 640x20
- anchor1 = 0,0
- anchor2 = 100,100
- background-color = #ffc41f
- saved-params = "splitter"
- left = "inputwindow"
- right = "inputbuttons"
- is-vert = true
splitter = 80
show-splitter = false
@@ -235,16 +256,16 @@ window "inputwindow"
elem "inputwindow"
type = MAIN
pos = 575,0
- size = 520x25
- anchor1 = -1,-1
- anchor2 = -1,-1
+ size = 480x25
+ anchor1 = 0,0
+ anchor2 = 70,100
background-color = none
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
elem "input"
type = INPUT
pos = 0,0
- size = 520x20
+ size = 480x20
anchor1 = 0,0
anchor2 = 100,100
is-default = true
@@ -256,8 +277,8 @@ window "inputbuttons"
elem "inputbuttons"
type = MAIN
pos = 291,0
- size = 120x25
- anchor1 = 0,0
+ size = 160x25
+ anchor1 = 70,0
anchor2 = 100,100
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
@@ -266,38 +287,51 @@ window "inputbuttons"
pos = 0,0
size = 40x20
anchor1 = 0,0
- anchor2 = 33,100
+ anchor2 = 25,100
font-size = 4
border = line
saved-params = "is-checked"
text = "Say"
- command = ".winset \"saybutton.is-checked=true ? input.command=\"!say \\\"\" : input.command=\"\"saybutton.is-checked=true ? mebutton.is-checked=false\"\"saybutton.is-checked=true ? oocbutton.is-checked=false\""
+ command = ".winset \"saybutton.is-checked=true ? input.command=\"!say \\\"\" : input.command=\"\"saybutton.is-checked=true ? mebutton.is-checked=false\"\"saybutton.is-checked=true ? oocbutton.is-checked=false\"\"saybutton.is-checked=true ? whisperbutton.is-checked=false\""
+ is-flat = true
+ button-type = pushbox
+ elem "whisperbutton"
+ type = BUTTON
+ pos = 40,0
+ size = 40x20
+ anchor1 = 25,0
+ anchor2 = 50,100
+ font-size = 4
+ border = line
+ saved-params = "is-checked"
+ text = "Whisper"
+ command = ".winset \"whisperbutton.is-checked=true ? input.command=\"!say \\\"#\" : input.command=\"\"whisperbutton.is-checked=false ? saybutton.is-checked=false\"\"whisperbutton.is-checked=true ? mebutton.is-checked=false\"\"whisperbutton.is-checked=true ? oocbutton.is-checked=false\""
is-flat = true
button-type = pushbox
elem "mebutton"
type = BUTTON
- pos = 40,0
+ pos = 80,0
size = 40x20
- anchor1 = 33,0
- anchor2 = 66,100
+ anchor1 = 50,0
+ anchor2 = 75,100
font-size = 4
border = line
saved-params = "is-checked"
text = "Me"
- command = ".winset \"mebutton.is-checked=true ? input.command=\"!me \\\"\" : input.command=\"\"mebutton.is-checked=true ? saybutton.is-checked=false\"\"mebutton.is-checked=true ? oocbutton.is-checked=false\""
+ command = ".winset \"mebutton.is-checked=true ? input.command=\"!me \\\"\" : input.command=\"\"mebutton.is-checked=true ? saybutton.is-checked=false\"\"mebutton.is-checked=true ? oocbutton.is-checked=false\"\"mebutton.is-checked=true ? whisperbutton.is-checked=false\""
is-flat = true
button-type = pushbox
elem "oocbutton"
type = BUTTON
- pos = 80,0
+ pos = 120,0
size = 40x20
- anchor1 = 66,0
+ anchor1 = 75,0
anchor2 = 100,100
font-size = 4
border = line
saved-params = "is-checked"
text = "OOC"
- command = ".winset \"oocbutton.is-checked=true ? input.command=\"!ooc \\\"\" : input.command=\"\"oocbutton.is-checked=true ? mebutton.is-checked=false\"\"oocbutton.is-checked=true ? saybutton.is-checked=false\""
+ command = ".winset \"oocbutton.is-checked=true ? input.command=\"!ooc \\\"\" : input.command=\"\"oocbutton.is-checked=true ? mebutton.is-checked=false\"\"oocbutton.is-checked=true ? saybutton.is-checked=false\"\"oocbutton.is-checked=true ? whisperbutton.is-checked=false\""
is-flat = true
button-type = pushbox
diff --git a/tgstation.dme b/tgstation.dme
index 7b10ea3d1e3..06086dbe517 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -4348,6 +4348,7 @@
#include "code\modules\emote_panel\emote_panel.dm"
#include "code\modules\error_handler\error_handler.dm"
#include "code\modules\error_handler\error_viewer.dm"
+#include "code\modules\escape_menu\admin_page.dm"
#include "code\modules\escape_menu\details.dm"
#include "code\modules\escape_menu\dimmer.dm"
#include "code\modules\escape_menu\escape_menu.dm"
diff --git a/tgui/packages/tgui-panel/settings/scaling.ts b/tgui/packages/tgui-panel/settings/scaling.ts
index bd1b5250012..77cc5f831ba 100644
--- a/tgui/packages/tgui-panel/settings/scaling.ts
+++ b/tgui/packages/tgui-panel/settings/scaling.ts
@@ -11,6 +11,7 @@ const ELEMENTS_TO_ADJUST = [
'inputbuttons.saybutton',
'inputbuttons.mebutton',
'inputbuttons.oocbutton',
+ 'inputbuttons.whisperbutton',
'mapwindow.status_bar',
];
@@ -33,8 +34,8 @@ export async function setDisplayScaling() {
const PANE_SPLITTERS = {
info_button_child: 2,
- input_buttons_child: 80,
- output_input_child: 96,
+ input_buttons_child: 70,
+ info_split: 97.5,
};
export function setEditPaneSplitters(editing: boolean) {
diff --git a/tgui/packages/tgui-panel/settings/themes.ts b/tgui/packages/tgui-panel/settings/themes.ts
index e94aaf7be3a..1bfd05800cb 100644
--- a/tgui/packages/tgui-panel/settings/themes.ts
+++ b/tgui/packages/tgui-panel/settings/themes.ts
@@ -68,6 +68,8 @@ export function setClientTheme(name): void | Promise {
'saybutton.text-color': themeColor.TEXT,
'oocbutton.background-color': themeColor.BG_BASE,
'oocbutton.text-color': themeColor.TEXT,
+ 'whisperbutton.background-color': themeColor.BG_BASE,
+ 'whisperbutton.text-color': themeColor.TEXT,
'mebutton.background-color': themeColor.BG_BASE,
'mebutton.text-color': themeColor.TEXT,
'asset_cache_browser.background-color': themeColor.BG_BASE,
diff --git a/tgui/packages/tgui-say/ChannelIterator.test.ts b/tgui/packages/tgui-say/ChannelIterator.test.ts
index b57f0d0df74..11d949b7fc9 100644
--- a/tgui/packages/tgui-say/ChannelIterator.test.ts
+++ b/tgui/packages/tgui-say/ChannelIterator.test.ts
@@ -14,6 +14,7 @@ describe('ChannelIterator', () => {
expect(channelIterator.next()).toBe('Radio');
expect(channelIterator.next()).toBe('Me');
expect(channelIterator.next()).toBe('OOC');
+ expect(channelIterator.next()).toBe('Pray');
expect(channelIterator.next()).toBe('Say'); // Admin is blacklisted so it should be skipped
});
diff --git a/tgui/packages/tgui-say/ChannelIterator.ts b/tgui/packages/tgui-say/ChannelIterator.ts
index 5d73730e51a..d4b6380be7d 100644
--- a/tgui/packages/tgui-say/ChannelIterator.ts
+++ b/tgui/packages/tgui-say/ChannelIterator.ts
@@ -1,4 +1,4 @@
-export type Channel = 'Say' | 'Radio' | 'Me' | 'OOC' | 'Admin';
+export type Channel = 'Say' | 'Radio' | 'Me' | 'OOC' | 'Admin' | 'Pray';
/**
* ### ChannelIterator
@@ -8,9 +8,9 @@ export type Channel = 'Say' | 'Radio' | 'Me' | 'OOC' | 'Admin';
*/
export class ChannelIterator {
private index: number = 0;
- private readonly channels: Channel[] = ['Say', 'Radio', 'Me', 'OOC', 'Admin'];
+ private readonly channels: Channel[] = ['Say', 'Radio', 'Me', 'OOC', 'Admin', 'Pray'];
private readonly blacklist: Channel[] = ['Admin'];
- private readonly quiet: Channel[] = ['OOC', 'Admin'];
+ private readonly quiet: Channel[] = ['OOC', 'Admin', 'Pray'];
public next(): Channel {
if (this.blacklist.includes(this.channels[this.index])) {
diff --git a/tgui/packages/tgui-say/styles/colors.scss b/tgui/packages/tgui-say/styles/colors.scss
index 0ba363660bb..42a7144b9e2 100644
--- a/tgui/packages/tgui-say/styles/colors.scss
+++ b/tgui/packages/tgui-say/styles/colors.scss
@@ -20,6 +20,7 @@ $_channel_map: (
'Med': hsl(202, 83.6%, 64.1%),
'OOC': hsl(47.9, 100%, 40%),
'Ent': hsl(176.1, 20%, 45.1%),
+ 'Pray': hsl(214, 86%, 30%),
'Radio': hsl(132.8, 74.4%, 45.9%),
'Say': hsl(213.6, 37.9%, 74.1%),
'Sci': hsl(271.6, 91.7%, 76.5%),