diff --git a/code/__DEFINES/~doppler_defines/fonts.dm b/code/__DEFINES/~doppler_defines/fonts.dm
new file mode 100644
index 00000000000000..e6f097424dff0f
--- /dev/null
+++ b/code/__DEFINES/~doppler_defines/fonts.dm
@@ -0,0 +1 @@
+#define MODULAR_EMOJI_SET 'modular_doppler/modular_emoji/emoji.dmi'
diff --git a/code/__DEFINES/~doppler_defines/keybindings.dm b/code/__DEFINES/~doppler_defines/keybindings.dm
index eecdd302fb8686..1b29e84336294d 100644
--- a/code/__DEFINES/~doppler_defines/keybindings.dm
+++ b/code/__DEFINES/~doppler_defines/keybindings.dm
@@ -2,6 +2,7 @@
#define COMSIG_KB_MOB_PIXEL_SHIFT_DOWN "keybinding_mob_pixel_shift_down"
#define COMSIG_KB_MOB_PIXEL_SHIFT_UP "keybinding_mob_pixel_shift_up"
#define COMSIG_KB_CLIENT_LOOC_DOWN "keybinding_client_looc_down"
+#define COMSIG_KB_CLIENT_BACKSTAGE_DOWN "keybinding_client_backstage_down"
#define COMSIG_KB_CLIENT_WHISPER_DOWN "keybinding_client_whisper_down"
#define COMSIG_KB_CLIENT_DO_DOWN "keybinding_client_do_down"
#define COMSIG_KB_CLIENT_DO_LONGER_DOWN "keybinding_client_do_longer_down"
diff --git a/code/__DEFINES/~doppler_defines/ooc_channels.dm b/code/__DEFINES/~doppler_defines/ooc_channels.dm
new file mode 100644
index 00000000000000..129c6e496c965d
--- /dev/null
+++ b/code/__DEFINES/~doppler_defines/ooc_channels.dm
@@ -0,0 +1,3 @@
+/// Flags for the associated list that gets used for to determine an OOC channel listener status
+#define LISTEN_PLAYER 1
+#define LISTEN_ADMIN 2
diff --git a/code/__DEFINES/~doppler_defines/speech_channels.dm b/code/__DEFINES/~doppler_defines/speech_channels.dm
index c4e4b7c4b9f0b1..e2b1cb33069802 100644
--- a/code/__DEFINES/~doppler_defines/speech_channels.dm
+++ b/code/__DEFINES/~doppler_defines/speech_channels.dm
@@ -1,4 +1,5 @@
#define LOOC_CHANNEL "LOOC" // LOOC
#define WHIS_CHANNEL "Whis" // Whisper
+#define BACKSTAGE_CHANNEL "Backstage" // ASOOC
#define DO_CHANNEL "Do" // Do
#define IRC_CHANNEL "IRC" // IRC
diff --git a/code/_globalvars/~doppler_globalvars/ooc_channels.dm b/code/_globalvars/~doppler_globalvars/ooc_channels.dm
new file mode 100644
index 00000000000000..249c9213ca328e
--- /dev/null
+++ b/code/_globalvars/~doppler_globalvars/ooc_channels.dm
@@ -0,0 +1,24 @@
+/// SOOC
+#define SOOC_COLOR "#6551FF"
+GLOBAL_VAR_INIT(sooc_allowed, TRUE) // used with admin verbs to disable sooc - not a config option
+GLOBAL_LIST_INIT(sooc_job_lookup, list(
+ JOB_CAPTAIN = TRUE,
+ JOB_HEAD_OF_PERSONNEL = TRUE,
+ JOB_RESEARCH_DIRECTOR = TRUE,
+ JOB_CHIEF_ENGINEER = TRUE,
+ JOB_CHIEF_MEDICAL_OFFICER = TRUE,
+ JOB_QUARTERMASTER = TRUE,
+ JOB_AI = TRUE,
+ JOB_HEAD_OF_SECURITY = TRUE,
+ JOB_WARDEN = TRUE,
+ JOB_DETECTIVE = TRUE,
+ JOB_SECURITY_OFFICER = TRUE,
+ JOB_COMMAND_BODYGUARD = TRUE,
+ ))
+
+/// AOOC
+#define AOOC_COLOR "#ff5967"
+GLOBAL_VAR_INIT(aooc_allowed, TRUE)
+
+/// Backstage
+GLOBAL_VAR_INIT(backstage_allowed, TRUE)
diff --git a/code/modules/tgui_input/say_modal/speech.dm b/code/modules/tgui_input/say_modal/speech.dm
index 5138c019ee8b00..cbfa443cbca635 100644
--- a/code/modules/tgui_input/say_modal/speech.dm
+++ b/code/modules/tgui_input/say_modal/speech.dm
@@ -54,6 +54,9 @@
if(WHIS_CHANNEL)
client.mob.whisper_verb(entry)
return TRUE
+ if(BACKSTAGE_CHANNEL)
+ client.backstage(entry)
+ return TRUE
if(DO_CHANNEL)
client.mob.do_verb(entry)
if(IRC_CHANNEL)
diff --git a/modular_doppler/administration/code/aooc.dm b/modular_doppler/administration/code/aooc.dm
index b3bfb8be52ee14..0ce316743f4cd2 100644
--- a/modular_doppler/administration/code/aooc.dm
+++ b/modular_doppler/administration/code/aooc.dm
@@ -1,12 +1,5 @@
-GLOBAL_VAR_INIT(AOOC_COLOR, "#de3c8c")
-GLOBAL_VAR_INIT(aooc_allowed, TRUE) // used with admin verbs to disable aooc - not a config option
-GLOBAL_LIST_EMPTY(ckey_to_aooc_name)
-
-#define AOOC_LISTEN_PLAYER 1
-#define AOOC_LISTEN_ADMIN 2
-
/client/verb/aooc(msg as text)
- set name = "AOOC"
+ set name = "OOC: Antag"
set category = "OOC"
if(GLOB.say_disabled) //This is here to try to identify lag problems
@@ -45,40 +38,30 @@ GLOBAL_LIST_EMPTY(ckey_to_aooc_name)
return
mob.log_talk(raw_msg, LOG_OOC, tag = "AOOC")
-
- var/keyname = key
- var/anon = FALSE
-
- //Anonimity for players and deadminned admins
- if(!holder || holder.deadmined)
- if(!GLOB.ckey_to_aooc_name[key])
- GLOB.ckey_to_aooc_name[key] = "Operator [pick(GLOB.phonetic_alphabet)] [rand(1, 99)]"
- keyname = GLOB.ckey_to_aooc_name[key]
- anon = TRUE
-
var/list/listeners = list()
for(var/mind as anything in get_antag_minds(/datum/antagonist))
var/datum/mind/antag_mind = mind
if(!antag_mind.current || !antag_mind.current.client || isnewplayer(antag_mind.current))
continue
- listeners[antag_mind.current.client] = AOOC_LISTEN_PLAYER
+ listeners[antag_mind.current.client] = LISTEN_PLAYER
for(var/iterated_player as anything in GLOB.player_list)
var/mob/iterated_mob = iterated_player
//Admins with muted OOC do not get to listen to AOOC, but normal players do, as it could be admins talking important stuff to them
if(iterated_mob.client?.holder && !iterated_mob.client?.holder.deadmined && iterated_mob.client?.prefs?.chat_toggles & CHAT_OOC)
- listeners[iterated_mob.client] = AOOC_LISTEN_ADMIN
+ listeners[iterated_mob.client] = LISTEN_ADMIN
+ continue
+ if(isobserver(iterated_mob) && iterated_mob.client?.prefs?.chat_toggles & CHAT_OOC)
+ listeners[iterated_mob.client] = LISTEN_PLAYER
+ continue
for(var/iterated_listener as anything in listeners)
var/client/iterated_client = iterated_listener
var/mode = listeners[iterated_listener]
- var/color = (!anon && CONFIG_GET(flag/allow_admin_ooccolor) && iterated_client?.prefs?.read_preference(/datum/preference/color/ooc_color)) ? iterated_client?.prefs?.read_preference(/datum/preference/color/ooc_color) : GLOB.AOOC_COLOR
- var/name = (mode == AOOC_LISTEN_ADMIN && anon) ? "([key])[keyname]" : keyname
- to_chat(iterated_client, span_oocplain("AOOC: [name]: [msg]"))
-
-#undef AOOC_LISTEN_PLAYER
-#undef AOOC_LISTEN_ADMIN
+ var/name = (mode == LISTEN_ADMIN) ? "([key]) [mob.real_name]" : mob?.real_name
+ to_chat(iterated_client, span_oocplain("Private (A): [ooc_channel_emoji(mob)] [name] says, [msg]"))
+ SEND_SOUND(iterated_client, 'modular_doppler/modular_sounds/sound/machines/typewriter_click.ogg')
/proc/toggle_aooc(toggle = null)
if(toggle != null) //if we're specifically en/disabling aooc
@@ -102,7 +85,7 @@ GLOBAL_LIST_EMPTY(ckey_to_aooc_name)
var/client/iterated_client = iterated_listener
to_chat(iterated_client, span_oocplain("The AOOC channel has been globally [GLOB.aooc_allowed ? "enabled" : "disabled"]."))
-ADMIN_VERB(toggleaooc, R_ADMIN, "Toggle Antag OOC", "Toggles Antag OOC.", ADMIN_CATEGORY_SERVER)
+ADMIN_VERB(toggle_aooc, R_ADMIN, "Toggle Antag OOC", "Toggles Antag OOC.", ADMIN_CATEGORY_SERVER)
toggle_aooc()
log_admin("[key_name(usr)] toggled Antagonist OOC.")
message_admins("[key_name_admin(usr)] toggled Antagonist OOC.")
diff --git a/modular_doppler/administration/code/asooc.dm b/modular_doppler/administration/code/asooc.dm
new file mode 100644
index 00000000000000..f7f3e6a06d093e
--- /dev/null
+++ b/modular_doppler/administration/code/asooc.dm
@@ -0,0 +1,94 @@
+/client/verb/backstage(msg as text)
+ set name = "OOC: Backstage (Antag/Sec)"
+ set category = "OOC"
+
+ if(GLOB.say_disabled) //This is here to try to identify lag problems
+ to_chat(usr, span_danger("Speech is currently admin-disabled."))
+ return
+
+ if(!mob)
+ return
+ var/backstage_access = FALSE
+ if(GLOB.sooc_job_lookup[mob.mind?.assigned_role?.title])
+ backstage_access = TRUE
+ if(length(mob.mind?.antag_datums))
+ backstage_access = TRUE
+
+ if(!holder)
+ if(!backstage_access)
+ to_chat(src, span_danger("You don't have backstage access!"))
+ return
+ if(!GLOB.backstage_allowed)
+ to_chat(src, span_danger("The backstage is staff-only."))
+ return
+ if(prefs.muted & MUTE_OOC)
+ to_chat(src, span_danger("You cannot use OOC channels (muted)."))
+ return
+ if(is_banned_from(ckey, "OOC"))
+ to_chat(src, span_danger("You have been banned from OOC channels."))
+ return
+ if(QDELETED(src))
+ return
+
+ msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN)
+ var/raw_msg = msg
+
+ if(!msg)
+ return
+
+ msg = emoji_parse(msg)
+
+ if(!(prefs.chat_toggles & CHAT_OOC))
+ to_chat(src, span_danger("You have OOC communication muted."))
+ return
+
+ mob.log_talk(raw_msg, LOG_OOC, tag = "Backstage")
+ var/list/listeners = list()
+
+ for(var/iterated_player as anything in GLOB.player_list)
+ var/mob/iterated_mob = iterated_player
+ //Admins with muted OOC do not get to listen to backstage chatter, but normal players do, as it could be admins talking important stuff to them
+ if(iterated_mob.client?.holder && !iterated_mob.client?.holder?.deadmined && iterated_mob.client?.prefs?.chat_toggles & CHAT_OOC)
+ listeners[iterated_mob.client] = LISTEN_ADMIN
+ continue
+ if(isobserver(iterated_mob) && iterated_mob.client?.prefs?.chat_toggles & CHAT_OOC)
+ listeners[iterated_mob.client] = LISTEN_PLAYER
+ continue
+ if((length(iterated_mob.mind?.antag_datums) || GLOB.sooc_job_lookup[iterated_mob.mind?.assigned_role?.title]))
+ listeners[iterated_mob.client] = LISTEN_PLAYER
+ continue
+
+ for(var/iterated_listener as anything in listeners)
+ var/client/iterated_client = iterated_listener
+ var/mode = listeners[iterated_listener]
+ var/name = (mode == LISTEN_ADMIN) ? "([key]) [mob?.real_name]" : mob?.real_name
+ to_chat(iterated_client, span_oocplain(" [ooc_channel_emoji(mob)] [name] says, [msg]"))
+ SEND_SOUND(iterated_client, 'modular_doppler/modular_sounds/sound/machines/typewriter_click.ogg')
+
+/proc/toggle_backstage(toggle = null)
+ if(toggle != null) //if we're specifically en/disabling aooc
+ if(toggle == GLOB.backstage_allowed)
+ return
+ GLOB.backstage_allowed = toggle
+ else //otherwise just toggle it
+ GLOB.backstage_allowed = !GLOB.backstage_allowed
+ var/list/listeners = list()
+ for(var/mind as anything in get_antag_minds(/datum/antagonist))
+ var/datum/mind/antag_mind = mind
+ if(!antag_mind.current || !antag_mind.current.client || isnewplayer(antag_mind.current))
+ continue
+ listeners[antag_mind.current.client] = TRUE
+
+ for(var/iterated_player in GLOB.player_list)
+ var/mob/iterated_mob = iterated_player
+ if(!iterated_mob.client?.holder?.deadmined)
+ listeners[iterated_mob.client] = TRUE
+ for(var/iterated_listener in listeners)
+ var/client/iterated_client = iterated_listener
+ to_chat(iterated_client, span_oocplain("Backstage access has been [GLOB.backstage_allowed ? "granted" : "revoked"]."))
+
+ADMIN_VERB(toggle_backstage, R_ADMIN, "Toggle Backstage Access", "Toggles Backstage Access.", ADMIN_CATEGORY_SERVER)
+ toggle_backstage()
+ log_admin("[key_name(usr)] toggled Backstage Access.")
+ message_admins("[key_name_admin(usr)] toggled Backstage Access.")
+ SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Backstage Access", "[GLOB.backstage_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/modular_doppler/administration/code/flavor.dm b/modular_doppler/administration/code/flavor.dm
new file mode 100644
index 00000000000000..fbe8a24d1b20ee
--- /dev/null
+++ b/modular_doppler/administration/code/flavor.dm
@@ -0,0 +1,39 @@
+/*
+ returns the appropriate message color for the OOC channels.
+ SOOC_COLOR for secoffs, AOOC_COLOR for antags, and the ooc pref for admins
+*/
+/proc/ooc_channel_color(mob/chatter)
+ var/color = "#c43b23"
+
+ // if you're sec
+ if(GLOB.sooc_job_lookup[chatter.mind?.assigned_role?.title])
+ color = SOOC_COLOR
+ // if you're tot
+ if(length(chatter.mind?.antag_datums))
+ color = AOOC_COLOR
+ // if you're admin
+ if(is_admin(chatter) && !GLOB.deadmins[chatter.client?.ckey])
+ color = chatter.client?.prefs?.read_preference(/datum/preference/color/ooc_color)
+
+ return color
+
+/*
+ returns the appropriate emoji for the OOC channels.
+ picks from a pref regarding if youre secoff or antag. always doppie for admins
+*/
+/proc/ooc_channel_emoji(mob/chatter)
+ var/emoji_icon_state = "fpalm"
+ var/emoji_icon_file = EMOJI_SET
+
+ // if you're sec
+ if(GLOB.sooc_job_lookup[chatter.mind?.assigned_role?.title])
+ emoji_icon_state = chatter.client?.prefs?.read_preference(/datum/preference/choiced/ooc_channel_emoji_sec)
+ // if you're tot
+ if(length(chatter.mind?.antag_datums))
+ emoji_icon_state = chatter.client?.prefs?.read_preference(/datum/preference/choiced/ooc_channel_emoji_tot)
+ // if you're admin
+ if(is_admin(chatter) && !GLOB.deadmins[chatter.client?.ckey])
+ emoji_icon_state = "dolphin"
+ emoji_icon_file = MODULAR_EMOJI_SET
+
+ return icon2html(emoji_icon_file, world, emoji_icon_state)
diff --git a/modular_doppler/administration/code/preferences.dm b/modular_doppler/administration/code/preferences.dm
index 3720993e1c5e9e..f86b4c24b49092 100644
--- a/modular_doppler/administration/code/preferences.dm
+++ b/modular_doppler/administration/code/preferences.dm
@@ -6,3 +6,66 @@
return FALSE
return is_admin(preferences.parent)
+
+
+/datum/preference/choiced/ooc_channel_emoji_sec
+ category = PREFERENCE_CATEGORY_GAME_PREFERENCES
+ savefile_key = "ooc_channel_emoji_sec"
+ savefile_identifier = PREFERENCE_PLAYER
+
+/datum/preference/choiced/ooc_channel_emoji_sec/create_default_value()
+ return "shades"
+
+/datum/preference/choiced/ooc_channel_emoji_sec/init_possible_values()
+ return list(
+ "shades",
+ "flash",
+ "flashbang",
+ "whiskey",
+ "riot",
+ "up",
+ "help",
+ "cam",
+ "disarm",
+ "thinking",
+ "drone",
+ "taser",
+ "charged",
+ "on",
+ "thelaw",
+ "salt",
+ "popcorn",
+ "donut",
+ "donut2",
+ )
+
+/datum/preference/choiced/ooc_channel_emoji_tot
+ category = PREFERENCE_CATEGORY_GAME_PREFERENCES
+ savefile_key = "ooc_channel_emoji_tot"
+ savefile_identifier = PREFERENCE_PLAYER
+
+/datum/preference/choiced/ooc_channel_emoji_tot/create_default_value()
+ return "toysword"
+
+/datum/preference/choiced/ooc_channel_emoji_tot/init_possible_values()
+ return list(
+ "toysword",
+ "c4",
+ "revolver",
+ "disk",
+ "pepper",
+ "fedora",
+ "rollie",
+ "down",
+ "flush",
+ "swarmer",
+ "call",
+ "cultie",
+ "haxxor",
+ "sbomb",
+ "conbaton",
+ "trap",
+ "sink",
+ "grab",
+ "harm",
+ )
diff --git a/modular_doppler/administration/code/sooc.dm b/modular_doppler/administration/code/sooc.dm
index 23119c8f256599..2447024b0c0125 100644
--- a/modular_doppler/administration/code/sooc.dm
+++ b/modular_doppler/administration/code/sooc.dm
@@ -1,12 +1,5 @@
-GLOBAL_VAR_INIT(SOOC_COLOR, "#ff5454")
-GLOBAL_VAR_INIT(sooc_allowed, TRUE) // used with admin verbs to disable sooc - not a config option
-GLOBAL_LIST_EMPTY(ckey_to_sooc_name)
-
-#define SOOC_LISTEN_PLAYER 1
-#define SOOC_LISTEN_ADMIN 2
-
/client/verb/sooc(msg as text)
- set name = "SOOC"
+ set name = "OOC: Security"
set category = "OOC"
if(GLOB.say_disabled) //This is here to try to identify lag problems
@@ -16,10 +9,8 @@ GLOBAL_LIST_EMPTY(ckey_to_sooc_name)
if(!mob)
return
- var/static/list/job_lookup = list(JOB_CAPTAIN=TRUE, JOB_HEAD_OF_SECURITY=TRUE, JOB_WARDEN=TRUE, JOB_DETECTIVE=TRUE, JOB_SECURITY_OFFICER=TRUE, JOB_COMMAND_BODYGUARD=TRUE)
if(!holder)
- var/job = mob?.mind.assigned_role.title
- if(!job || !job_lookup[job])
+ if(!GLOB.sooc_job_lookup[mob.mind?.assigned_role?.title])
to_chat(src, span_danger("You're not a security role!"))
return
if(!GLOB.sooc_allowed)
@@ -47,39 +38,27 @@ GLOBAL_LIST_EMPTY(ckey_to_sooc_name)
return
mob.log_talk(raw_msg, LOG_OOC, tag="SOOC")
-
- var/keyname = key
- var/anon = FALSE
-
- //Anonimity for players and deadminned admins
- if(!holder || holder.deadmined)
- if(!GLOB.ckey_to_sooc_name[key])
- GLOB.ckey_to_sooc_name[key] = "Deputy [pick(GLOB.phonetic_alphabet)] [rand(1, 99)]"
- keyname = GLOB.ckey_to_sooc_name[key]
- anon = TRUE
-
var/list/listeners = list()
for(var/iterated_player as anything in GLOB.player_list)
var/mob/iterated_mob = iterated_player
//Admins with muted OOC do not get to listen to SOOC, but normal players do, as it could be admins talking important stuff to them
if(iterated_mob.client?.holder && !iterated_mob.client?.holder?.deadmined && iterated_mob.client?.prefs?.chat_toggles & CHAT_OOC)
- listeners[iterated_mob.client] = SOOC_LISTEN_ADMIN
- else
- if(iterated_mob.mind)
- var/datum/mind/mob_mind = iterated_mob.mind
- if(job_lookup[mob_mind.assigned_role?.title])
- listeners[iterated_mob.client] = SOOC_LISTEN_PLAYER
+ listeners[iterated_mob.client] = LISTEN_ADMIN
+ continue
+ if(GLOB.sooc_job_lookup[iterated_mob.mind?.assigned_role?.title])
+ listeners[iterated_mob.client] = LISTEN_PLAYER
+ continue
+ if(isobserver(iterated_mob) && iterated_mob.client?.prefs?.chat_toggles & CHAT_OOC)
+ listeners[iterated_mob.client] = LISTEN_PLAYER
+ continue
for(var/iterated_listener as anything in listeners)
var/client/iterated_client = iterated_listener
var/mode = listeners[iterated_listener]
- var/color = (!anon && CONFIG_GET(flag/allow_admin_ooccolor) && iterated_client?.prefs?.read_preference(/datum/preference/color/ooc_color)) ? iterated_client?.prefs?.read_preference(/datum/preference/color/ooc_color) : GLOB.SOOC_COLOR
- var/name = (mode == SOOC_LISTEN_ADMIN && anon) ? "([key])[keyname]" : keyname
- to_chat(iterated_client, span_oocplain("SOOC: [name]: [msg]"))
-
-#undef SOOC_LISTEN_PLAYER
-#undef SOOC_LISTEN_ADMIN
+ var/name = (mode == LISTEN_ADMIN) ? "([key]) [mob?.real_name]" : mob?.real_name
+ to_chat(iterated_client, span_oocplain("Private (S): [ooc_channel_emoji(mob)] [name] says, [msg]"))
+ SEND_SOUND(iterated_client, 'modular_doppler/modular_sounds/sound/machines/typewriter_click.ogg')
/proc/toggle_sooc(toggle = null)
if(toggle != null) //if we're specifically en/disabling sooc
@@ -90,7 +69,6 @@ GLOBAL_LIST_EMPTY(ckey_to_sooc_name)
else //otherwise just toggle it
GLOB.sooc_allowed = !GLOB.sooc_allowed
var/list/listeners = list()
- var/static/list/job_lookup = list(JOB_SECURITY_OFFICER = TRUE, JOB_WARDEN = TRUE, JOB_DETECTIVE = TRUE, JOB_HEAD_OF_SECURITY = TRUE, JOB_CAPTAIN = TRUE, JOB_COMMAND_BODYGUARD = TRUE)
for(var/iterated_player as anything in GLOB.player_list)
var/mob/iterated_mob = iterated_player
if(!iterated_mob.client?.holder?.deadmined)
@@ -98,13 +76,13 @@ GLOBAL_LIST_EMPTY(ckey_to_sooc_name)
else
if(iterated_mob.mind)
var/datum/mind/mob_mind = iterated_mob.mind
- if(job_lookup[mob_mind.assigned_role])
+ if(GLOB.sooc_job_lookup[mob_mind.assigned_role])
listeners[iterated_mob.client] = TRUE
for(var/iterated_listener as anything in listeners)
var/client/iterated_client = iterated_listener
to_chat(iterated_client, span_oocplain("The SOOC channel has been globally [GLOB.sooc_allowed ? "enabled" : "disabled"]."))
-ADMIN_VERB(togglesooc, R_ADMIN, "Toggle Security OOC", "Toggles Security OOC.", ADMIN_CATEGORY_SERVER)
+ADMIN_VERB(toggle_sooc, R_ADMIN, "Toggle Security OOC", "Toggles Security OOC.", ADMIN_CATEGORY_SERVER)
toggle_sooc()
log_admin("[key_name(usr)] toggled Security OOC.")
message_admins("[key_name_admin(usr)] toggled Security OOC.")
diff --git a/modular_doppler/modular_emoji/emoji.dm b/modular_doppler/modular_emoji/emoji.dm
index 49c15b7042e2df..2328385881b084 100644
--- a/modular_doppler/modular_emoji/emoji.dm
+++ b/modular_doppler/modular_emoji/emoji.dm
@@ -1,5 +1,3 @@
-#define MODULAR_EMOJI_SET 'modular_doppler/modular_emoji/emoji.dmi'
-
/datum/asset/spritesheet_batched/chat/create_spritesheets()
. = ..()
insert_all_icons("emoji", MODULAR_EMOJI_SET)
diff --git a/modular_doppler/modular_emoji/emoji.dmi b/modular_doppler/modular_emoji/emoji.dmi
index 28d1ac9b5bd074..588616d4079c71 100644
Binary files a/modular_doppler/modular_emoji/emoji.dmi and b/modular_doppler/modular_emoji/emoji.dmi differ
diff --git a/modular_doppler/modular_sounds/attributions.txt b/modular_doppler/modular_sounds/attributions.txt
index e5909f90ec474a..f580184fa39ed1 100644
--- a/modular_doppler/modular_sounds/attributions.txt
+++ b/modular_doppler/modular_sounds/attributions.txt
@@ -1,2 +1,3 @@
bolt_thrower.ogg - lentikula, "Sci-Fi Weapon Shots SFX", CC0 license
particle_cannon.ogg - lentikula, "Sci-Fi Weapon Shots SFX", CC0 license
+typewriter_click.ogg - https://freesound.org/people/BMacZero/sounds/160678/, CC0 license
\ No newline at end of file
diff --git a/modular_doppler/modular_sounds/sound/machines/typewriter_click.ogg b/modular_doppler/modular_sounds/sound/machines/typewriter_click.ogg
new file mode 100644
index 00000000000000..77d74abd934e0d
Binary files /dev/null and b/modular_doppler/modular_sounds/sound/machines/typewriter_click.ogg differ
diff --git a/modular_doppler/verbs/code/communication.dm b/modular_doppler/verbs/code/communication.dm
index 458d603af862d6..658560c8e16a14 100644
--- a/modular_doppler/verbs/code/communication.dm
+++ b/modular_doppler/verbs/code/communication.dm
@@ -11,6 +11,19 @@
winset(user, null, "command=[user.tgui_say_create_open_command(LOOC_CHANNEL)]")
return TRUE
+/datum/keybinding/client/communication/backstage
+ hotkey_keys = list("ShiftO")
+ name = BACKSTAGE_CHANNEL
+ full_name = "Backstage OOC (SAOOC)"
+ keybind_signal = COMSIG_KB_CLIENT_BACKSTAGE_DOWN
+
+/datum/keybinding/client/communication/backstage/down(client/user)
+ . = ..()
+ if(.)
+ return
+ winset(user, null, "command=[user.tgui_say_create_open_command(BACKSTAGE_CHANNEL)]")
+ return TRUE
+
/datum/keybinding/client/communication/whisper
hotkey_keys = list("CtrlT")
name = WHIS_CHANNEL
diff --git a/tgstation.dme b/tgstation.dme
index 52c59bf6391a14..10b97b244ca57e 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -431,6 +431,7 @@
#include "code\__DEFINES\~doppler_defines\enterprise_resource_planning.dm"
#include "code\__DEFINES\~doppler_defines\examinemore.dm"
#include "code\__DEFINES\~doppler_defines\flavor_defines.dm"
+#include "code\__DEFINES\~doppler_defines\fonts.dm"
#include "code\__DEFINES\~doppler_defines\footsteps.dm"
#include "code\__DEFINES\~doppler_defines\inventory.dm"
#include "code\__DEFINES\~doppler_defines\is_helpers.dm"
@@ -449,6 +450,7 @@
#include "code\__DEFINES\~doppler_defines\mutant_variations.dm"
#include "code\__DEFINES\~doppler_defines\ntnrc.dm"
#include "code\__DEFINES\~doppler_defines\obj_flags_doppler.dm"
+#include "code\__DEFINES\~doppler_defines\ooc_channels.dm"
#include "code\__DEFINES\~doppler_defines\organ_slots.dm"
#include "code\__DEFINES\~doppler_defines\powers.dm"
#include "code\__DEFINES\~doppler_defines\preferences.dm"
@@ -661,6 +663,7 @@
#include "code\_globalvars\~doppler_globalvars\bitfields.dm"
#include "code\_globalvars\~doppler_globalvars\configuration.dm"
#include "code\_globalvars\~doppler_globalvars\objective.dm"
+#include "code\_globalvars\~doppler_globalvars\ooc_channels.dm"
#include "code\_globalvars\~doppler_globalvars\regexes.dm"
#include "code\_globalvars\~doppler_globalvars\religion.dm"
#include "code\_globalvars\~doppler_globalvars\text.dm"
@@ -6841,7 +6844,9 @@
#include "modular_doppler\accessable_storage\item.dm"
#include "modular_doppler\accessable_storage\strippable.dm"
#include "modular_doppler\administration\code\aooc.dm"
+#include "modular_doppler\administration\code\asooc.dm"
#include "modular_doppler\administration\code\extra_vv.dm"
+#include "modular_doppler\administration\code\flavor.dm"
#include "modular_doppler\administration\code\preferences.dm"
#include "modular_doppler\administration\code\sooc.dm"
#include "modular_doppler\administration\code\whitelisting.dm"
diff --git a/tgui/packages/tgui-say/ChannelIterator.test.ts b/tgui/packages/tgui-say/ChannelIterator.test.ts
index cda2fc8bd8264d..1e989943aa75ab 100644
--- a/tgui/packages/tgui-say/ChannelIterator.test.ts
+++ b/tgui/packages/tgui-say/ChannelIterator.test.ts
@@ -16,6 +16,7 @@ describe('ChannelIterator', () => {
// DOPPLER EDIT ADDITION START
expect(channelIterator.next()).toBe('Whis');
expect(channelIterator.next()).toBe('LOOC');
+ expect(channelIterator.next()).toBe('Backstage');
expect(channelIterator.next()).toBe('Do');
expect(channelIterator.next()).toBe('IRC');
// DOPPLER EDIT ADDITION END
diff --git a/tgui/packages/tgui-say/ChannelIterator.ts b/tgui/packages/tgui-say/ChannelIterator.ts
index 1d4d4b93e41cce..b75f7f2e8e079d 100644
--- a/tgui/packages/tgui-say/ChannelIterator.ts
+++ b/tgui/packages/tgui-say/ChannelIterator.ts
@@ -5,6 +5,7 @@ export type Channel =
// DOPPLER EDIT ADDITION START
| 'Whis'
| 'LOOC'
+ | 'Backstage'
| 'Do'
| 'IRC'
// DOPPLER EDIT ADDITION END
@@ -26,6 +27,7 @@ export class ChannelIterator {
// DOPPLER EDIT ADDITION
'Whis',
'LOOC',
+ 'Backstage',
'Do',
'IRC',
// DOPPLER EDIT ADDITION
diff --git a/tgui/packages/tgui-say/styles/colors.scss b/tgui/packages/tgui-say/styles/colors.scss
index 23a49cc48cf519..b696b61c342ff2 100644
--- a/tgui/packages/tgui-say/styles/colors.scss
+++ b/tgui/packages/tgui-say/styles/colors.scss
@@ -30,6 +30,7 @@ $_channel_map: (
// DOPPLER EDIT ADDITION START
'LOOC': hsl(20, 100%, 86%),
'Whis': hsl(238, 55%, 67%),
+ 'Backstage': hsl(182, 85%, 31%),
'Do': hsl(137, 64%, 60%),
'IRC': hsl(312.8, 74.4%, 45.9%), // DOPPLER EDIT ADDITION END
);
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dopplershift_preferences/ooc_channel_emoji.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dopplershift_preferences/ooc_channel_emoji.tsx
new file mode 100644
index 00000000000000..91b1fd197c6381
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dopplershift_preferences/ooc_channel_emoji.tsx
@@ -0,0 +1,20 @@
+import type { FeatureChoiced } from '../base';
+import { FeatureDropdownInput } from '../dropdowns';
+
+export const ooc_channel_emoji_sec: FeatureChoiced = {
+ name: 'OOC: security emoji',
+ category: 'CHAT',
+ description: `
+ Which icon appears besides your name when you are security in OOC channels.
+`,
+ component: FeatureDropdownInput,
+};
+
+export const ooc_channel_emoji_tot: FeatureChoiced = {
+ name: 'OOC: antag emoji',
+ category: 'CHAT',
+ description: `
+ Which icon appears besides your name when you are antag in OOC channels.
+`,
+ component: FeatureDropdownInput,
+};