"
diff --git a/code/modules/admin/verbs/admin.dm b/code/modules/admin/verbs/admin.dm
index 88ee5148f7a3c..37c50f53225ce 100644
--- a/code/modules/admin/verbs/admin.dm
+++ b/code/modules/admin/verbs/admin.dm
@@ -181,6 +181,11 @@ ADMIN_VERB(drop_everything, R_ADMIN, "Drop Everything", ADMIN_VERB_NO_DESCRIPTIO
if(MUTE_DEADCHAT)
mute_string = "deadchat and DSAY"
feedback_string = "Deadchat"
+ // DOPPLER EDIT ADDITION START - LOOC muting.
+ if(MUTE_LOOC)
+ mute_string = "LOOC"
+ feedback_string = "LOOC"
+ // DOPPLER EDIT ADDITION END - LOOC muting.
if(MUTE_INTERNET_REQUEST)
mute_string = "internet sound requests"
feedback_string = "Internet Sound Requests"
diff --git a/code/modules/admin/verbs/admingame.dm b/code/modules/admin/verbs/admingame.dm
index 57311446961ed..5982f62e5a904 100644
--- a/code/modules/admin/verbs/admingame.dm
+++ b/code/modules/admin/verbs/admingame.dm
@@ -88,7 +88,10 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_ADMIN, "Show Player Panel", mo
body += "
PRAY | "
body += "
ADMINHELP | "
body += "
WEBREQ | "
- body += "
DEADCHAT\]"
+ //DOPPLER EDIT ADDITION BEGIN - LOOC muting.
+ body += "
DEADCHAT | "
+ body += "
LOOC\]"
+ //DOPPLER EDIT ADDITION END - LOOC muting.
body += "(
toggle all)"
body += "
"
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
index 2d16cc169202a..831589ceaf632 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
@@ -175,7 +175,7 @@
return FALSE
return ..()
-/mob/living/simple_animal/hostile/megafauna/dragon/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs, visible_message_flags = NONE)
+/mob/living/simple_animal/hostile/megafauna/dragon/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs, visible_message_flags = NONE, separation = " ") // DOPPLER EDIT ADDITION - separation
if(swooping & SWOOP_INVULNERABLE) //to suppress attack messages without overriding every single proc that could send a message saying we got hit
return
return ..()
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 8cffe1b766d60..7e0ba37d2bb13 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -266,7 +266,7 @@
* * ignored_mobs (optional) doesn't show any message to any mob in this list.
* * visible_message_flags (optional) is the type of message being sent.
*/
-/atom/proc/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs, visible_message_flags = NONE)
+/atom/proc/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs, visible_message_flags = NONE, separation = " ") // DOPPLER EDIT ADDITION - separation
var/turf/T = get_turf(src)
if(!T)
return
@@ -276,12 +276,23 @@
var/list/hearers = get_hearers_in_view(vision_distance, src) //caches the hearers and then removes ignored mobs.
hearers -= ignored_mobs
+ //DOPPLER EDIT ADDITION BEGIN - AI QoL
+ for(var/mob/camera/ai_eye/ai_eye in hearers)
+ if(ai_eye.ai?.client && !(ai_eye.ai.stat == DEAD))
+ hearers -= ai_eye
+ hearers |= ai_eye.ai
+
+ for(var/obj/effect/overlay/holo_pad_hologram/holo in hearers)
+ if(holo.Impersonation?.client)
+ hearers |= holo.Impersonation
+ //DOPPLER EDIT ADDITION END - AI QoL
+
if(self_message)
hearers -= src
var/raw_msg = message
if(visible_message_flags & EMOTE_MESSAGE)
- message = "
[src] [message]"
+ message = "
[src][separation][message]" // DOPPLER EDIT ADDITION - Better emotes
for(var/mob/M in hearers)
if(!M.client)
@@ -311,7 +322,7 @@
///Adds the functionality to self_message.
-/mob/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs, visible_message_flags = NONE)
+/mob/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs, visible_message_flags = NONE, separation = " ") // DOPPLER EDIT ADDITION - separation
. = ..()
if(!self_message)
return
@@ -342,13 +353,25 @@
* * self_message (optional) is what the src mob hears.
* * audible_message_flags (optional) is the type of message being sent.
*/
-/atom/proc/audible_message(message, deaf_message, hearing_distance = DEFAULT_MESSAGE_RANGE, self_message, audible_message_flags = NONE)
+/atom/proc/audible_message(message, deaf_message, hearing_distance = DEFAULT_MESSAGE_RANGE, self_message, audible_message_flags = NONE, separation = " ") // DOPPLER EDIT ADDITION - separation
var/list/hearers = get_hearers_in_view(hearing_distance, src)
+
+ //DOPPLER EDIT ADDITION BEGIN - AI QoL
+ for(var/mob/camera/ai_eye/ai_eye in hearers)
+ if(ai_eye.ai?.client && !(ai_eye.ai.stat == DEAD))
+ hearers -= ai_eye
+ hearers |= ai_eye.ai
+
+ for(var/obj/effect/overlay/holo_pad_hologram/holo in hearers)
+ if(holo.Impersonation?.client)
+ hearers |= holo.Impersonation
+ //DOPPLER EDIT ADDITION END - AI QoL
+
if(self_message)
hearers -= src
var/raw_msg = message
if(audible_message_flags & EMOTE_MESSAGE)
- message = "
[src] [message]"
+ message = "
[src][separation][message]" //DOPPLER EDIT CHANGE
for(var/mob/M in hearers)
if(audible_message_flags & EMOTE_MESSAGE && runechat_prefs_check(M, audible_message_flags) && M.can_hear())
M.create_chat_message(src, raw_message = raw_msg, runechat_flags = audible_message_flags)
@@ -365,7 +388,7 @@
* * deaf_message (optional) is what deaf people will see.
* * hearing_distance (optional) is the range, how many tiles away the message can be heard.
*/
-/mob/audible_message(message, deaf_message, hearing_distance = DEFAULT_MESSAGE_RANGE, self_message, audible_message_flags = NONE)
+/mob/audible_message(message, deaf_message, hearing_distance = DEFAULT_MESSAGE_RANGE, self_message, audible_message_flags = NONE, separation = " ") // DOPPLER EDIT ADDITION - separation
. = ..()
if(!self_message)
return
diff --git a/code/modules/tgui_input/say_modal/modal.dm b/code/modules/tgui_input/say_modal/modal.dm
index bc3f8f314e021..b8087609022e2 100644
--- a/code/modules/tgui_input/say_modal/modal.dm
+++ b/code/modules/tgui_input/say_modal/modal.dm
@@ -86,7 +86,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"] != LOOC_CHANNEL) // DOPPLER EDIT CHANGE (Add LOOC_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 0d95b855a15ff..32d524eb0146a 100644
--- a/code/modules/tgui_input/say_modal/speech.dm
+++ b/code/modules/tgui_input/say_modal/speech.dm
@@ -10,7 +10,7 @@
/datum/tgui_say/proc/alter_entry(payload)
var/entry = payload["entry"]
/// 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"] == LOOC_CHANNEL) // DOPPLER EDIT CHANGE - VERBS
return pick(hurt_phrases)
/// Random trimming for larger sentences
if(length(entry) > 50)
@@ -47,6 +47,16 @@
if(ADMIN_CHANNEL)
SSadmin_verbs.dynamic_invoke_verb(client, /datum/admin_verb/cmd_admin_say, entry)
return TRUE
+ // DOPPLER EDIT ADDITION START - VERBS
+ if(LOOC_CHANNEL)
+ client.looc(entry)
+ return TRUE
+ if(WHIS_CHANNEL)
+ client.mob.whisper_verb(entry)
+ return TRUE
+ if(DO_CHANNEL)
+ client.mob.do_verb(entry)
+ // DOPPLER EDIT ADDITION END
return FALSE
/**
diff --git a/modular_doppler/administration/code/preferences.dm b/modular_doppler/administration/code/preferences.dm
new file mode 100644
index 0000000000000..3720993e1c5e9
--- /dev/null
+++ b/modular_doppler/administration/code/preferences.dm
@@ -0,0 +1,8 @@
+/datum/preference/toggle/admin
+ abstract_type = /datum/preference/toggle/admin
+
+/datum/preference/toggle/admin/is_accessible(datum/preferences/preferences)
+ if (!..(preferences))
+ return FALSE
+
+ return is_admin(preferences.parent)
diff --git a/modular_doppler/emotes/code/hologram.dm b/modular_doppler/emotes/code/hologram.dm
new file mode 100644
index 0000000000000..08ce03c90c6ec
--- /dev/null
+++ b/modular_doppler/emotes/code/hologram.dm
@@ -0,0 +1,14 @@
+GLOBAL_LIST_EMPTY(hologram_impersonators)
+
+/obj/machinery/holopad/set_holo(mob/living/user, obj/effect/overlay/holo_pad_hologram/holo)
+ if(holo.Impersonation)
+ GLOB.hologram_impersonators[user] = holo
+ holo.become_hearing_sensitive() // Well, we need to show up on "get_hearers_in_view()"
+ . = ..()
+
+/obj/machinery/holopad/clear_holo(mob/living/user)
+ var/obj/effect/overlay/holo_pad_hologram/hologram = GLOB.hologram_impersonators[user]
+ if(hologram)
+ hologram.lose_hearing_sensitivity()
+ GLOB.hologram_impersonators -= user
+ . = ..()
diff --git a/modular_doppler/modular_hydroponics/readme.md b/modular_doppler/modular_hydroponics/readme.md
index e69de29bb2d1d..7f3cff653c556 100644
--- a/modular_doppler/modular_hydroponics/readme.md
+++ b/modular_doppler/modular_hydroponics/readme.md
@@ -0,0 +1,23 @@
+## Title: Modular Hydrophonics
+
+MODULE ID: MODULAR_HYDROPONICS
+
+### Description:
+
+Module for Doppler's hydroponics custom content, like plants and related.
+
+### TG Proc Changes:
+
+### Defines:
+
+N/A
+
+### Master file additions
+
+N/A
+
+### Included files that are not contained in this module:
+
+N/A
+
+### Credits:
diff --git a/modular_doppler/modular_mapping/readme.md b/modular_doppler/modular_mapping/readme.md
index e69de29bb2d1d..38f7f00ddfcc9 100644
--- a/modular_doppler/modular_mapping/readme.md
+++ b/modular_doppler/modular_mapping/readme.md
@@ -0,0 +1,23 @@
+## Title: Modular Mapping
+
+MODULE ID: MODULAR_MAPPING
+
+### Description:
+
+Module for Doppler's custom ruins and everything related, like unique ruin items and structures, etc.
+
+### TG Proc Changes:
+
+### Defines:
+
+N/A
+
+### Master file additions
+
+N/A
+
+### Included files that are not contained in this module:
+
+N/A
+
+### Credits:
diff --git a/modular_doppler/objects_and_structures/readme.md b/modular_doppler/objects_and_structures/readme.md
index e69de29bb2d1d..97a20749a4741 100644
--- a/modular_doppler/objects_and_structures/readme.md
+++ b/modular_doppler/objects_and_structures/readme.md
@@ -0,0 +1,23 @@
+## Title: Objects and Structures
+
+MODULE ID: OBJECTS_AND_STRUCTURES
+
+### Description:
+
+Module for all objects and structures used for mapping that don't really have a place to go.
+
+### TG Proc Changes:
+
+### Defines:
+
+N/A
+
+### Master file additions
+
+N/A
+
+### Included files that are not contained in this module:
+
+N/A
+
+### Credits:
diff --git a/modular_doppler/verbs/code/communication.dm b/modular_doppler/verbs/code/communication.dm
new file mode 100644
index 0000000000000..458d603af862d
--- /dev/null
+++ b/modular_doppler/verbs/code/communication.dm
@@ -0,0 +1,55 @@
+/datum/keybinding/client/communication/looc
+ hotkey_keys = list("CtrlO")
+ name = LOOC_CHANNEL
+ full_name = "Local OOC (LOOC)"
+ keybind_signal = COMSIG_KB_CLIENT_LOOC_DOWN
+
+/datum/keybinding/client/communication/looc/down(client/user)
+ . = ..()
+ if(.)
+ return
+ winset(user, null, "command=[user.tgui_say_create_open_command(LOOC_CHANNEL)]")
+ return TRUE
+
+/datum/keybinding/client/communication/whisper
+ hotkey_keys = list("CtrlT")
+ name = WHIS_CHANNEL
+ full_name = "IC Whisper"
+ keybind_signal = COMSIG_KB_CLIENT_WHISPER_DOWN
+
+/datum/keybinding/client/communication/whisper/down(client/user)
+ . = ..()
+ if(.)
+ return
+ winset(user, null, "command=[user.tgui_say_create_open_command(WHIS_CHANNEL)]")
+ return TRUE
+
+/datum/keybinding/client/communication/Do
+ hotkey_keys = list("K")
+ name = DO_CHANNEL
+ full_name = "Do"
+ keybind_signal = COMSIG_KB_CLIENT_DO_DOWN
+
+/datum/keybinding/client/communication/Do/down(client/user)
+ . = ..()
+ if(.)
+ return
+ winset(user, null, "command=[user.tgui_say_create_open_command(DO_CHANNEL)]")
+ return TRUE
+
+/datum/keybinding/client/communication/Do_longer
+ hotkey_keys = list("CtrlK")
+ name = "do_longer"
+ full_name = "Do (Longer)"
+ keybind_signal = COMSIG_KB_CLIENT_DO_LONGER_DOWN
+
+/datum/keybinding/client/communication/Do_longer/down(client/user)
+ . = ..()
+ if(.)
+ return
+ var/message_text = tgui_input_text(user, "Write out your Do action:", "Do (Longer)", null, MAX_MESSAGE_LEN, TRUE)
+ if (!message_text)
+ return
+
+ user.mob.do_verb(message_text)
+ return TRUE
diff --git a/modular_doppler/verbs/code/do_checks.dm b/modular_doppler/verbs/code/do_checks.dm
new file mode 100644
index 0000000000000..cab5717df4c6e
--- /dev/null
+++ b/modular_doppler/verbs/code/do_checks.dm
@@ -0,0 +1,20 @@
+/mob/living/proc/doverb_checks(message)
+ if(!length(message))
+ return FALSE
+
+ if(GLOB.say_disabled) //This is here to try to identify lag problems
+ to_chat(usr, span_danger("Speech is currently admin-disabled."))
+ return FALSE
+
+ //quickly calc our name stub again: duplicate this in say.dm override
+ var/name_stub = " (
[usr])"
+ if(length(message) > (MAX_MESSAGE_LEN - length(name_stub)))
+ to_chat(usr, message)
+ to_chat(usr, span_warning("^^^----- The preceding message has been DISCARDED for being over the maximum length of [MAX_MESSAGE_LEN]. It has NOT been sent! -----^^^"))
+ return FALSE
+
+ if(usr.stat != CONSCIOUS)
+ to_chat(usr, span_notice("You cannot send a Do in your current condition."))
+ return FALSE
+
+ return TRUE
diff --git a/modular_doppler/verbs/code/do_verbs.dm b/modular_doppler/verbs/code/do_verbs.dm
new file mode 100644
index 0000000000000..eae4adf25620d
--- /dev/null
+++ b/modular_doppler/verbs/code/do_verbs.dm
@@ -0,0 +1,48 @@
+/mob/verb/do_verb(message as message)
+ set name = "Do"
+ set category = "IC"
+ set instant = TRUE
+
+ if(GLOB.say_disabled)
+ to_chat(usr, span_danger("Speech is currently admin-disabled."))
+ return
+
+ if(message)
+ QUEUE_OR_CALL_VERB_FOR(VERB_CALLBACK(src, TYPE_VERB_REF(/mob/living, do_actual_verb), message), SSspeech_controller)
+
+/mob/living/verb/do_actual_verb(message as message)
+ if (!message || !doverb_checks(message))
+ return
+
+ if (!try_speak(message)) // ensure we pass the vibe check (filters, etc)
+ return
+
+ var/name_stub = " (
[usr])"
+ message = usr.say_emphasis(message)
+ message = trim(copytext_char(message, 1, (MAX_MESSAGE_LEN - length(name_stub))))
+ var/message_with_name = message + name_stub
+
+ usr.log_message(message, LOG_EMOTE)
+
+ var/list/viewers = get_hearers_in_view(DEFAULT_MESSAGE_RANGE, usr)
+
+ if(istype(usr, /mob/living/silicon/ai))
+ var/mob/living/silicon/ai/ai = usr
+ viewers = get_hearers_in_view(DEFAULT_MESSAGE_RANGE, ai.eyeobj)
+
+ var/obj/effect/overlay/holo_pad_hologram/hologram = GLOB.hologram_impersonators[usr]
+ if(hologram)
+ viewers |= get_hearers_in_view(1, hologram)
+
+ for(var/mob/living/silicon/ai/ai as anything in GLOB.ai_list)
+ if(ai.client && !(ai in viewers) && (ai.eyeobj in viewers))
+ viewers += ai
+
+ for(var/mob/ghost as anything in GLOB.dead_mob_list)
+ if((ghost.client?.prefs.chat_toggles & CHAT_GHOSTSIGHT) && !(ghost in viewers))
+ ghost.show_message(span_emote(message_with_name))
+
+ for(var/mob/receiver in viewers)
+ receiver.show_message(span_emote(message_with_name), alt_msg = span_emote(message_with_name))
+ if (receiver.client?.prefs.read_preference(/datum/preference/toggle/enable_runechat))
+ create_chat_message(usr, null, message, null, EMOTE_MESSAGE)
diff --git a/modular_doppler/verbs/code/log_category_game.dm b/modular_doppler/verbs/code/log_category_game.dm
new file mode 100644
index 0000000000000..cc00d85f63175
--- /dev/null
+++ b/modular_doppler/verbs/code/log_category_game.dm
@@ -0,0 +1,3 @@
+/datum/log_category/game_subtle
+ category = LOG_CATEGORY_GAME_SUBTLE
+ master_category = /datum/log_category/game
diff --git a/modular_doppler/verbs/code/looc.dm b/modular_doppler/verbs/code/looc.dm
new file mode 100644
index 0000000000000..b88810c1c9146
--- /dev/null
+++ b/modular_doppler/verbs/code/looc.dm
@@ -0,0 +1,100 @@
+/client/verb/looc(msg as text)
+ set name = "LOOC"
+ set desc = "Local OOC, seen only by those in view."
+ set category = "OOC"
+
+ looc_message(msg)
+
+/client/verb/looc_wallpierce(msg as text)
+ set name = "LOOC (Wallpierce)"
+ set desc = "Local OOC, seen by anyone within 7 tiles of you."
+ set category = "OOC"
+
+ looc_message(msg, TRUE)
+
+/client/proc/looc_message(msg, wall_pierce)
+ if(GLOB.say_disabled)
+ to_chat(usr, span_danger("Speech is currently admin-disabled."))
+ return
+
+ if(!mob)
+ return
+
+ msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN)
+ if(!msg)
+ return
+
+ if(!holder)
+ if(!GLOB.looc_allowed)
+ to_chat(src, span_danger("LOOC is globally muted."))
+ return
+ if(handle_spam_prevention(msg, MUTE_OOC))
+ return
+ if(findtext(msg, "byond://"))
+ to_chat(src, span_boldannounce("
Advertising other servers is not allowed."))
+ log_admin("[key_name(src)] has attempted to advertise in LOOC: [msg]")
+ return
+ if(prefs.muted & MUTE_LOOC)
+ to_chat(src, span_danger("You cannot use LOOC (muted)."))
+ return
+ if(is_banned_from(ckey, BAN_LOOC))
+ to_chat(src, span_warning("You are LOOC banned!"))
+ return
+ if(mob.stat == DEAD)
+ to_chat(src, span_danger("You cannot use LOOC while dead."))
+ return
+ if(istype(mob, /mob/dead))
+ to_chat(src, span_danger("You cannot use LOOC while ghosting."))
+ return
+
+ msg = emoji_parse(msg)
+
+ mob.log_talk(msg,LOG_OOC, tag="LOOC")
+ var/list/heard
+ if(wall_pierce)
+ heard = get_hearers_in_looc_range(mob.get_top_level_mob())
+ else
+ heard = get_hearers_in_view(LOOC_RANGE, mob.get_top_level_mob())
+
+ //so the ai can post looc text
+ if(istype(mob, /mob/living/silicon/ai))
+ var/mob/living/silicon/ai/ai = mob
+ if(wall_pierce)
+ heard = get_hearers_in_looc_range(ai.eyeobj)
+ else
+ heard = get_hearers_in_view(LOOC_RANGE, ai.eyeobj)
+ //so the ai can see looc text
+ for(var/mob/living/silicon/ai/ai as anything in GLOB.ai_list)
+ if(ai.client && !(ai in heard) && (ai.eyeobj in heard))
+ heard += ai
+
+ var/list/admin_seen = list()
+ for(var/mob/hearing in heard)
+ if(!hearing.client)
+ continue
+ var/client/hearing_client = hearing.client
+
+ var/is_holder = hearing_client.holder
+ if (is_holder)
+ admin_seen[hearing_client] = TRUE
+ // dont continue here, still need to show runechat
+
+ if (isobserver(hearing) && !is_holder)
+ continue //ghosts dont hear looc, apparantly
+
+ // do the runetext here so admins can still get the runetext
+ if(mob.runechat_prefs_check(hearing) && hearing.client?.prefs.read_preference(/datum/preference/toggle/enable_looc_runechat))
+ // EMOTE is close enough. We don't want it to treat the raw message with languages.
+ // I wish it didn't include the asterisk but it's modular this way.
+ hearing.create_chat_message(mob, raw_message = "(LOOC: [msg])", runechat_flags = EMOTE_MESSAGE)
+
+ if (is_holder)
+ continue //admins are handled afterwards
+
+ to_chat(hearing_client, span_looc(span_prefix("LOOC[wall_pierce ? " (WALL PIERCE)" : ""]:
[src.mob.name]: [msg]")))
+
+ for(var/client/cli_client as anything in GLOB.admins)
+ if (admin_seen[cli_client])
+ to_chat(cli_client, span_looc("[ADMIN_FLW(usr)] LOOC[wall_pierce ? " (WALL PIERCE)" : ""]: [src.key]/[src.mob.name]: [msg]"))
+ else if (cli_client.prefs.read_preference(/datum/preference/toggle/admin/see_looc))
+ to_chat(cli_client, span_rlooc("[ADMIN_FLW(usr)] (R)LOOC[wall_pierce ? " (WALL PIERCE)" : ""]: [src.key]/[src.mob.name]: [msg]"))
diff --git a/modular_doppler/verbs/code/preferences.dm b/modular_doppler/verbs/code/preferences.dm
new file mode 100644
index 0000000000000..be6618bd8e655
--- /dev/null
+++ b/modular_doppler/verbs/code/preferences.dm
@@ -0,0 +1,11 @@
+/datum/preference/toggle/admin/see_looc
+ category = PREFERENCE_CATEGORY_GAME_PREFERENCES
+ default_value = TRUE
+ savefile_key = "looc_admin_pref"
+ savefile_identifier = PREFERENCE_PLAYER
+
+/datum/preference/toggle/enable_looc_runechat
+ category = PREFERENCE_CATEGORY_GAME_PREFERENCES
+ default_value = FALSE
+ savefile_key = "enable_looc_runechat"
+ savefile_identifier = PREFERENCE_PLAYER
diff --git a/modular_doppler/verbs/code/say.dm b/modular_doppler/verbs/code/say.dm
new file mode 100644
index 0000000000000..935d8d1331ef7
--- /dev/null
+++ b/modular_doppler/verbs/code/say.dm
@@ -0,0 +1,5 @@
+/mob/proc/get_top_level_mob()
+ if(ismob(loc) && (loc != src))
+ var/mob/M = loc
+ return M.get_top_level_mob()
+ return src
diff --git a/modular_doppler/verbs/code/subtle.dm b/modular_doppler/verbs/code/subtle.dm
new file mode 100644
index 0000000000000..5667d37fab759
--- /dev/null
+++ b/modular_doppler/verbs/code/subtle.dm
@@ -0,0 +1,116 @@
+#define SUBTLE_DEFAULT_DISTANCE 1
+#define SUBTLE_SAME_TILE_DISTANCE 0
+
+#define SUBTLE_ONE_TILE_TEXT "1-Tile Range"
+#define SUBTLE_SAME_TILE_TEXT "Same Tile"
+
+/datum/emote/living/subtle
+ key = "subtle"
+ key_third_person = "subtle"
+ message = null
+ mob_type_blacklist_typecache = list(/mob/living/brain)
+
+/datum/emote/living/subtle/run_emote(mob/user, params, type_override = null)
+ if(!can_run_emote(user))
+ to_chat(user, span_warning("You can't emote at this time."))
+ return FALSE
+ var/subtle_message
+ var/subtle_emote = params
+ var/target
+ var/subtle_range = SUBTLE_DEFAULT_DISTANCE
+
+ if(SSdbcore.IsConnected() && is_banned_from(user, "emote"))
+ to_chat(user, span_warning("You cannot send subtle emotes (banned)."))
+ return FALSE
+ else if(user.client?.prefs.muted & MUTE_IC)
+ to_chat(user, span_warning("You cannot send IC messages (muted)."))
+ return FALSE
+ else if(!subtle_emote)
+ subtle_emote = tgui_input_text(user, "Choose an emote to display.", "Subtle" , null, MAX_MESSAGE_LEN, TRUE)
+ if(!subtle_emote)
+ return FALSE
+
+ var/list/in_view = get_hearers_in_view(subtle_range, user)
+
+ var/obj/effect/overlay/holo_pad_hologram/hologram = GLOB.hologram_impersonators[user]
+ if(hologram)
+ in_view |= get_hearers_in_view(1, hologram)
+
+ in_view -= GLOB.dead_mob_list
+ in_view.Remove(user)
+
+ for(var/mob/camera/ai_eye/ai_eye in in_view)
+ in_view.Remove(ai_eye)
+
+ var/list/targets = list(SUBTLE_ONE_TILE_TEXT, SUBTLE_SAME_TILE_TEXT) + in_view
+ target = tgui_input_list(user, "Pick a target", "Target Selection", targets)
+ if(!target)
+ return FALSE
+
+ switch(target)
+ if(SUBTLE_ONE_TILE_TEXT)
+ target = SUBTLE_DEFAULT_DISTANCE
+ if(SUBTLE_SAME_TILE_TEXT)
+ target = SUBTLE_SAME_TILE_DISTANCE
+ subtle_message = subtle_emote
+ else
+ target = SUBTLE_DEFAULT_DISTANCE
+ subtle_message = subtle_emote
+ if(type_override)
+ emote_type = type_override
+
+ if(!can_run_emote(user))
+ to_chat(user, span_warning("You can't emote at this time."))
+ return FALSE
+
+ user.log_message(subtle_message, LOG_SUBTLE)
+
+ var/space = should_have_space_before_emote(html_decode(subtle_emote)[1]) ? " " : ""
+
+ subtle_message = span_emote("[user][space][user.say_emphasis(subtle_message)]")
+
+ if(istype(target, /mob))
+ var/mob/target_mob = target
+ user.show_message(subtle_message, alt_msg = subtle_message)
+ var/obj/effect/overlay/holo_pad_hologram/hologram = GLOB.hologram_impersonators[user]
+ if((get_dist(user.loc, target_mob.loc) <= subtle_range) || (hologram && get_dist(hologram.loc, target_mob.loc) <= subtle_range))
+ target_mob.show_message(subtle_message, alt_msg = subtle_message)
+ else
+ to_chat(user, span_warning("Your emote was unable to be sent to your target: Too far away."))
+ else if(istype(target, /obj/effect/overlay/holo_pad_hologram))
+ var/obj/effect/overlay/holo_pad_hologram/hologram = target
+ if(hologram.Impersonation?.client)
+ hologram.Impersonation.show_message(subtle_message, alt_msg = subtle_message)
+ else
+ var/ghostless = get_hearers_in_view(target, user) - GLOB.dead_mob_list
+
+ var/obj/effect/overlay/holo_pad_hologram/hologram = GLOB.hologram_impersonators[user]
+ if(hologram)
+ ghostless |= get_hearers_in_view(target, hologram)
+
+ for(var/obj/effect/overlay/holo_pad_hologram/holo in ghostless)
+ if(holo?.Impersonation?.client)
+ ghostless |= holo.Impersonation
+
+ for(var/mob/receiver in ghostless)
+ receiver.show_message(subtle_message, alt_msg = subtle_message)
+
+ return TRUE
+
+/*
+* VERB CODE
+*/
+
+/mob/living/verb/subtle()
+ set name = "Subtle (Anti-Ghost)"
+ set category = "IC"
+ if(GLOB.say_disabled) // This is here to try to identify lag problems
+ to_chat(usr, span_danger("Speech is currently admin-disabled."))
+ return
+ usr.emote("subtle")
+
+#undef SUBTLE_DEFAULT_DISTANCE
+#undef SUBTLE_SAME_TILE_DISTANCE
+
+#undef SUBTLE_ONE_TILE_TEXT
+#undef SUBTLE_SAME_TILE_TEXT
diff --git a/modular_doppler/verbs/readme.md b/modular_doppler/verbs/readme.md
new file mode 100644
index 0000000000000..5d4750fcd11a4
--- /dev/null
+++ b/modular_doppler/verbs/readme.md
@@ -0,0 +1,58 @@
+## Title: More verbs and subtler.
+
+MODULE ID: VERBS
+
+### Description:
+
+Adds the following new verbs:
+- LOOC: OOC with a default range of 7 tiles.
+- LOOC (Wallpierce): Like LOOC but a wall piercing version.
+- Subtle (Anti-Ghost): Like Me but invisible to ghosts. Range can be 1 tile, choosing a character in this range or same tile.
+- Do: Like Me but not centered on character. Perfect for narrations. Default range of 7 tiles.
+- Do (Longer): Like Do but in a TGUI format.
+
+Adds keybinds for LOOC, Do, Do (Longer) and Whisper.
+
+### TG Proc Changes:
+
+| proc | file |
+| ----------------------------------------------------- | --------------------------------------------- |
+| `/proc/cmd_admin_mute(whom, mute_type, automute = 0)` | `code\modules\admin\verbs\admin.dm` |
+| `ADMIN_VERB_ONLY_CONTEXT_MENU(..., ..., ..., ...)` | `code\modules\admin\verbs\admingame.dm` |
+| `/datum/tgui_say/proc/open(payload)` | `code\modules\tgui_input\say_modal\modal.dm` |
+| `/datum/tgui_say/proc/alter_entry(payload)` | `code\modules\tgui_input\say_modal\speech.dm` |
+
+### Defines:
+
+- `code\__DEFINES\~doppler_defines\admin.dm` looc mute
+- `code\__DEFINES\~doppler_defines\banning.dm` looc ban define
+- `code\__DEFINES\~doppler_defines\keybindings.dm` looc, whisper and do keybinds
+- `code\__DEFINES\~doppler_defines\logging.dm` subtle log define
+- `code\__DEFINES\~doppler_defines\say.dm` looc range define
+- `code\__DEFINES\~doppler_defines\span.dm` looc(wallpierce), do, subtle and span defines
+- `code\__DEFINES\~doppler_defines\speech_channels.dm` looc, whis and do channel defines
+
+
+### Included files that are not contained in this module:
+
+- `code\__HELPERS\logging\_logging.dm` subtle logging
+- `code\__HELPERS\logging\mob.dm` subtle messages readibility for admins
+- `code\__HELPERS\~doppler_helpers\chat.dm` subtle formatting helper
+- `code\__HELPERS\~doppler_helpers\logging.dm` subtle helper for logging
+- `code\__HELPERS\~doppler_helpers\verbs.dm` looc range hearing helper
+- `code\_globalvars\~doppler_globalvars\configuration.dm` looc configuration global var init
+- `code\modules\admin\sql_ban_system.dm` looc ban option in admin banning panel
+- `code\modules\admin\verbs\admingame.dm` looc mute message
+- `modular_doppler\administration\code\preferences.dm` toggle admin preference datum
+- `tgui\packages\tgui\interfaces\PreferencesMenu\preferences\features\dopplershift_preferences\looc.tsx` looc preferences visual components
+- `tgui\packages\tgui-panel\chat\constants.ts`
+- `tgui\packages\tgui-panel\styles\tgchat\chat-dark.scss` multiple font colors for dark theme
+- `tgui\packages\tgui-panel\styles\tgchat\chat-light.scss` multiple font colors for light theme
+- `tgui\packages\tgui-say\ChannelIterator.test.ts` whis, looc and do channel cycler test
+- `tgui\packages\tgui-say\ChannelIterator.ts` whis, looc and do channel types and channel iterators
+- `tgui\packages\tgui-say\styles\colors.scss` whis, looc and do font colors
+
+### Credits:
+Gandalf2k15 - porting and refactoring
+yooriss - do verb
+Kaostico - edition
diff --git a/tgstation.dme b/tgstation.dme
index 9b8c6f4ecb405..648e2846d33a4 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -397,9 +397,11 @@
#include "code\__DEFINES\traits\macros.dm"
#include "code\__DEFINES\traits\sources.dm"
#include "code\__DEFINES\~doppler_defines\access.dm"
+#include "code\__DEFINES\~doppler_defines\admin.dm"
#include "code\__DEFINES\~doppler_defines\armor_defines.dm"
#include "code\__DEFINES\~doppler_defines\atom_hud.dm"
#include "code\__DEFINES\~doppler_defines\automapper.dm"
+#include "code\__DEFINES\~doppler_defines\banning.dm"
#include "code\__DEFINES\~doppler_defines\cells.dm"
#include "code\__DEFINES\~doppler_defines\colony_fabricator_misc.dm"
#include "code\__DEFINES\~doppler_defines\declarations.dm"
@@ -411,6 +413,7 @@
#include "code\__DEFINES\~doppler_defines\keybindings.dm"
#include "code\__DEFINES\~doppler_defines\living.dm"
#include "code\__DEFINES\~doppler_defines\loadout.dm"
+#include "code\__DEFINES\~doppler_defines\logging.dm"
#include "code\__DEFINES\~doppler_defines\manufacturer_strings.dm"
#include "code\__DEFINES\~doppler_defines\mobfactions.dm"
#include "code\__DEFINES\~doppler_defines\mutant_variations.dm"
@@ -419,10 +422,12 @@
#include "code\__DEFINES\~doppler_defines\reagent_forging_tools.dm"
#include "code\__DEFINES\~doppler_defines\reskin_defines.dm"
#include "code\__DEFINES\~doppler_defines\role_preferences.dm"
+#include "code\__DEFINES\~doppler_defines\say.dm"
#include "code\__DEFINES\~doppler_defines\signals.dm"
#include "code\__DEFINES\~doppler_defines\sound.dm"
#include "code\__DEFINES\~doppler_defines\span.dm"
#include "code\__DEFINES\~doppler_defines\species.dm"
+#include "code\__DEFINES\~doppler_defines\speech_channels.dm"
#include "code\__DEFINES\~doppler_defines\strippable.dm"
#include "code\__DEFINES\~doppler_defines\techweb_nodes.dm"
#include "code\__DEFINES\~doppler_defines\traits.dm"
@@ -541,7 +546,10 @@
#include "code\__HELPERS\paths\sssp.dm"
#include "code\__HELPERS\sorts\helpers.dm"
#include "code\__HELPERS\sorts\sort_instance.dm"
+#include "code\__HELPERS\~doppler_helpers\chat.dm"
#include "code\__HELPERS\~doppler_helpers\global_lists.dm"
+#include "code\__HELPERS\~doppler_helpers\logging.dm"
+#include "code\__HELPERS\~doppler_helpers\verbs.dm"
#include "code\_globalvars\_regexes.dm"
#include "code\_globalvars\admin.dm"
#include "code\_globalvars\arcade.dm"
@@ -588,6 +596,7 @@
#include "code\_globalvars\traits\_traits.dm"
#include "code\_globalvars\traits\admin_tooling.dm"
#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\religion.dm"
#include "code\_js\byjax.dm"
@@ -6430,6 +6439,7 @@
#include "modular_doppler\accessable_storage\accessable_storage.dm"
#include "modular_doppler\accessable_storage\item.dm"
#include "modular_doppler\accessable_storage\strippable.dm"
+#include "modular_doppler\administration\code\preferences.dm"
#include "modular_doppler\administration\code\whitelisting.dm"
#include "modular_doppler\advanced_reskin\code\advanced_reskin.dm"
#include "modular_doppler\automapper\code\area_spawn_entries.dm"
@@ -6504,6 +6514,7 @@
#include "modular_doppler\deforest_medical_items\code\medstation_designs\blood.dm"
#include "modular_doppler\deforest_medical_items\code\medstation_designs\medical.dm"
#include "modular_doppler\emotes\code\emotes.dm"
+#include "modular_doppler\emotes\code\hologram.dm"
#include "modular_doppler\emotes\code\added_emotes\animal_sounds.dm"
#include "modular_doppler\emotes\code\added_emotes\human_things.dm"
#include "modular_doppler\emotes\code\added_emotes\robot_sounds.dm"
@@ -6800,6 +6811,14 @@
#include "modular_doppler\vending_machines\code\vendor_food.dm"
#include "modular_doppler\vending_machines\code\vendor_snacks.dm"
#include "modular_doppler\vending_machines\code\vendors.dm"
+#include "modular_doppler\verbs\code\communication.dm"
+#include "modular_doppler\verbs\code\do_checks.dm"
+#include "modular_doppler\verbs\code\do_verbs.dm"
+#include "modular_doppler\verbs\code\log_category_game.dm"
+#include "modular_doppler\verbs\code\looc.dm"
+#include "modular_doppler\verbs\code\preferences.dm"
+#include "modular_doppler\verbs\code\say.dm"
+#include "modular_doppler\verbs\code\subtle.dm"
#include "modular_doppler\wargaming\code\game_kit.dm"
#include "modular_doppler\wargaming\code\holograms.dm"
#include "modular_doppler\wargaming\code\projectors.dm"
diff --git a/tgui/packages/tgui-panel/chat/constants.ts b/tgui/packages/tgui-panel/chat/constants.ts
index 57ad525a9a9aa..56378b8bc54b5 100644
--- a/tgui/packages/tgui-panel/chat/constants.ts
+++ b/tgui/packages/tgui-panel/chat/constants.ts
@@ -53,7 +53,7 @@ export const MESSAGE_TYPES = [
type: MESSAGE_TYPE_LOCALCHAT,
name: 'Local',
description: 'In-character local messages (say, emote, etc)',
- selector: '.say, .emote',
+ selector: '.say, .emote, .looc', // DOPPLER EDIT ADDITION - LOOC
},
{
type: MESSAGE_TYPE_RADIO,
@@ -86,7 +86,7 @@ export const MESSAGE_TYPES = [
type: MESSAGE_TYPE_OOC,
name: 'OOC',
description: 'The bluewall of global OOC messages',
- selector: '.ooc, .adminooc, .adminobserverooc, .oocplain',
+ selector: '.ooc, .adminooc, .adminobserverooc, .oocplain, .looc, .rlooc', // DOPPLER EDIT ADDITION - LOOC AND RLOOC
},
{
type: MESSAGE_TYPE_ADMINPM,
diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss
index 25f4eabd45d86..d6e2ef8fc692b 100644
--- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss
+++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss
@@ -1188,3 +1188,40 @@ $border-width-px: $border-width * 1px;
background-color: darken(map.get($alert-stripe-colors, $color-name), 5);
}
}
+
+/* DOPPLER EDIT ADDITION START - DARK MODE CLASSES */
+
+.looc {
+ color: #d8b555;
+}
+
+.rlooc {
+ color: #b09448;
+}
+
+.pink {
+ color: #ff00ff;
+ font-weight: bold;
+}
+
+.brown {
+ color: #3d2009;
+ font-weight: bold;
+}
+
+.cyan {
+ color: #0ea1e6;
+ font-weight: bold;
+}
+
+.orange {
+ color: #b8761a;
+ font-weight: bold;
+}
+
+.yellow {
+ color: #c7b72c;
+ font-weight: bold;
+}
+
+// DOPPLER EDIT ADDITION END
diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss
index b9caf24ddd81f..d0ae460667606 100644
--- a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss
+++ b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss
@@ -1212,3 +1212,41 @@ $border-width-px: $border-width * 1px;
);
}
}
+
+/* DOPPLER EDIT ADDITION START - LIGHT MODE CLASSES */
+
+.looc {
+ color: #6699cc;
+ font-weight: bold;
+}
+
+.rlooc {
+ color: #507294;
+ font-weight: bold;
+}
+
+.pink {
+ color: #ff00ff;
+ font-weight: bold;
+}
+
+.brown {
+ color: #3d2009;
+ font-weight: bold;
+}
+
+.cyan {
+ color: #0ea1e6;
+ font-weight: bold;
+}
+
+.orange {
+ color: #b8761a;
+ font-weight: bold;
+}
+
+.yellow {
+ color: #c7b72c;
+ font-weight: bold;
+}
+// DOPPLER EDIT ADDITION END
diff --git a/tgui/packages/tgui-say/ChannelIterator.test.ts b/tgui/packages/tgui-say/ChannelIterator.test.ts
index 15e9812e702ec..93891ce5fc57a 100644
--- a/tgui/packages/tgui-say/ChannelIterator.test.ts
+++ b/tgui/packages/tgui-say/ChannelIterator.test.ts
@@ -11,6 +11,11 @@ describe('ChannelIterator', () => {
expect(channelIterator.current()).toBe('Say');
expect(channelIterator.next()).toBe('Radio');
expect(channelIterator.next()).toBe('Me');
+ // DOPPLER EDIT ADDITION START
+ expect(channelIterator.next()).toBe('Whis');
+ expect(channelIterator.next()).toBe('LOOC');
+ expect(channelIterator.next()).toBe('Do');
+ // DOPPLER EDIT ADDITION END
expect(channelIterator.next()).toBe('OOC');
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 136806927e95e..b30c36bacd8d9 100644
--- a/tgui/packages/tgui-say/ChannelIterator.ts
+++ b/tgui/packages/tgui-say/ChannelIterator.ts
@@ -1,4 +1,14 @@
-export type Channel = 'Say' | 'Radio' | 'Me' | 'OOC' | 'Admin';
+export type Channel =
+ | 'Say'
+ | 'Radio'
+ | 'Me'
+ // DOPPLER EDIT ADDITION START
+ | 'Whis'
+ | 'LOOC'
+ | 'Do'
+ // DOPPLER EDIT ADDITION END
+ | 'OOC'
+ | 'Admin';
/**
* ### ChannelIterator
@@ -8,7 +18,18 @@ 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',
+ // DOPPLER EDIT ADDITION
+ 'Whis',
+ 'LOOC',
+ 'Do',
+ // DOPPLER EDIT ADDITION
+ 'OOC',
+ 'Admin',
+ ];
private readonly blacklist: Channel[] = ['Admin'];
private readonly quiet: Channel[] = ['OOC', 'Admin'];
diff --git a/tgui/packages/tgui-say/styles/colors.scss b/tgui/packages/tgui-say/styles/colors.scss
index a6b492247187b..aa53224b732ec 100644
--- a/tgui/packages/tgui-say/styles/colors.scss
+++ b/tgui/packages/tgui-say/styles/colors.scss
@@ -26,6 +26,11 @@ $_channel_map: (
'Supp': #b88646,
'Svc': #6ca729,
'Synd': #8f4a4b,
+ // NOVA EDIT ADDITION START
+ 'Whis': #7c7fd9,
+ 'LOOC': #ffceb6,
+ 'Do': #59da7e,
+ // NOVA EDIT ADDITION END
);
$channel_keys: map.keys($_channel_map) !default;
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dopplershift_preferences/looc.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dopplershift_preferences/looc.tsx
new file mode 100644
index 0000000000000..1c9c5eb3f436e
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dopplershift_preferences/looc.tsx
@@ -0,0 +1,17 @@
+import { CheckboxInput, FeatureToggle } from '../base';
+
+export const looc_admin_pref: FeatureToggle = {
+ name: 'See admin LOOC',
+ category: 'ADMIN',
+ description:
+ 'Toggles whether you want to see LOOC anywhere as an admin or not.',
+ component: CheckboxInput,
+};
+
+export const enable_looc_runechat: FeatureToggle = {
+ name: 'Enable LOOC runechat',
+ category: 'RUNECHAT',
+ description:
+ "If TRUE, LOOC will appear above the speaker's head as well as in the chat.",
+ component: CheckboxInput,
+};