diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
index 1df2f39cc1b..378a86aceeb 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
@@ -105,7 +105,7 @@
/// Return to prevent the movable from talking into the radio.
#define COMPONENT_CANNOT_USE_RADIO (1<<0)
-/// Sent from /atom/movable/proc/say_quote() after say verb is chosen and before spans are applied.
+/// Sent from /atom/movable/proc/generate_messagepart() generating a quoted message, after say verb is chosen and before spans are applied.
#define COMSIG_MOVABLE_SAY_QUOTE "movable_say_quote"
// Used to access COMSIG_MOVABLE_SAY_QUOTE argslist
/// The index of args that corresponds to the actual message
diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm
index 61c773b0b7c..1a3f3834eb0 100644
--- a/code/__DEFINES/say.dm
+++ b/code/__DEFINES/say.dm
@@ -52,7 +52,10 @@
#define MODE_KEY_PUPPET "j"
#define MODE_ALIEN "alientalk"
+#define MODE_KEY_ALIEN "a"
+
#define MODE_HOLOPAD "holopad"
+#define MODE_KEY_HOLOPAD "h"
#define MODE_CHANGELING "changeling"
#define MODE_KEY_CHANGELING "g"
@@ -125,6 +128,9 @@
#define MSG_AUDIBLE (1<<1)
+// Say mode message handling return flags, exist for readability.
+/// Say mode has handled the message.
+#define SAYMODE_MESSAGE_HANDLED (1<<0)
// Used in visible_message_flags, audible_message_flags and runechat_flags
/// Automatically applies emote related spans/fonts/formatting to the message
diff --git a/code/__HELPERS/chat.dm b/code/__HELPERS/chat.dm
index 669a5f38fc4..5be492d9ef0 100644
--- a/code/__HELPERS/chat.dm
+++ b/code/__HELPERS/chat.dm
@@ -95,5 +95,10 @@ it will be sent to all connected chats.
/// Sends a message to everyone within the list, as well as all observers.
/proc/relay_to_list_and_observers(message, list/mob_list, source, message_type = null)
for(var/mob/creature as anything in mob_list)
- to_chat(creature, message, type = message_type)
+ to_chat(
+ creature,
+ message,
+ type = message_type,
+ avoid_highlighting = (creature == source),
+ )
send_to_observers(message, source)
diff --git a/code/__HELPERS/logging/talk.dm b/code/__HELPERS/logging/talk.dm
index 6d9c534287f..da5bebc17fc 100644
--- a/code/__HELPERS/logging/talk.dm
+++ b/code/__HELPERS/logging/talk.dm
@@ -1,3 +1,28 @@
+/**
+ * Helper for logging chat messages that may or may not have a custom say verb,
+ * or be a pure radio emote outright.
+ *
+ * This proc reads the `message_mods` to determine
+ * in what ways the given message should be logged,
+ * and forwards it to other logging procs as such.
+ * Arguments:
+ * * message - The message being logged
+ * * message_mods - A list of message modifiers, i.e. whispering/singing.
+ * * tag - tag that indicates the type of text(announcement, telepathy, etc)
+ * * log_globally - boolean checking whether or not we write this log to the log file
+ * * forced_by - source that forced the dialogue if any
+ */
+/atom/proc/log_sayverb_talk(message, list/message_mods = list(), tag = null, log_globally = TRUE, forced_by = null)
+ // If it's just the custom say verb, log it to emotes.
+ if(message_mods[MODE_CUSTOM_SAY_ERASE_INPUT])
+ log_talk(message_mods[MODE_CUSTOM_SAY_EMOTE], LOG_RADIO_EMOTE, tag, log_globally, forced_by)
+ return
+
+ if(message_mods[WHISPER_MODE])
+ log_talk(message, LOG_WHISPER, tag, log_globally, forced_by, message_mods[MODE_CUSTOM_SAY_EMOTE])
+ else
+ log_talk(message, LOG_SAY, tag, log_globally, forced_by, message_mods[MODE_CUSTOM_SAY_EMOTE])
+
/**
* Helper for logging chat messages or other logs with arbitrary inputs (e.g. announcements)
*
diff --git a/code/controllers/subsystem/networks/radio.dm b/code/controllers/subsystem/networks/radio.dm
index 578b69b0d0e..6d880cedeb3 100644
--- a/code/controllers/subsystem/networks/radio.dm
+++ b/code/controllers/subsystem/networks/radio.dm
@@ -11,6 +11,15 @@ SUBSYSTEM_DEF(radio)
saymodes[SM.key] = SM
return ..()
+/// Gets the say mode associated with the given key, if available to the given user.
+/datum/controller/subsystem/radio/proc/get_available_say_mode(mob/living/user, key)
+ var/datum/saymode/selected_saymode = SSradio.saymodes[key]
+ if(isnull(selected_saymode))
+ return
+ if(!selected_saymode.can_be_used_by(user))
+ return
+ return selected_saymode
+
/datum/controller/subsystem/radio/proc/add_object(obj/device, new_frequency as num, filter = null as text|null)
var/f_text = num2text(new_frequency)
var/datum/radio_frequency/frequency = frequencies[f_text]
diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm
index 449b0ec0657..617f590e5fb 100644
--- a/code/datums/brain_damage/imaginary_friend.dm
+++ b/code/datums/brain_damage/imaginary_friend.dm
@@ -223,7 +223,6 @@
/mob/eye/imaginary_friend/send_speech(message, range = IMAGINARY_FRIEND_SPEECH_RANGE, obj/source = src, bubble_type = bubble_icon, list/spans = list(), datum/language/message_language = null, list/message_mods = list(), forced = null)
message = get_message_mods(message, message_mods)
- message = capitalize(message)
if(message_mods[RADIO_EXTENSION] == MODE_ADMIN)
SSadmin_verbs.dynamic_invoke_verb(client, /datum/admin_verb/cmd_admin_say, message)
@@ -236,6 +235,9 @@
if(check_emote(message, forced))
return
+ message = check_for_custom_say_emote(message, message_mods)
+ message = capitalize(message)
+
if(message_mods[MODE_SING])
var/randomnote = pick("♩", "♪", "♫")
message = "[randomnote] [capitalize(message)] [randomnote]"
@@ -246,21 +248,17 @@
var/eavesdrop_range = 0
- if (message_mods[MODE_CUSTOM_SAY_ERASE_INPUT])
- message = message_mods[MODE_CUSTOM_SAY_EMOTE]
- log_message(message, LOG_RADIO_EMOTE)
- else
+ if(!(message_mods[MODE_CUSTOM_SAY_ERASE_INPUT]))
if(message_mods[WHISPER_MODE] == MODE_WHISPER)
- log_talk(message, LOG_WHISPER, tag="imaginary friend", forced_by = forced, custom_say_emote = message_mods[MODE_CUSTOM_SAY_EMOTE])
spans |= SPAN_ITALICS
eavesdrop_range = EAVESDROP_EXTRA_RANGE
range = WHISPER_RANGE
- else
- log_talk(message, LOG_SAY, tag="imaginary friend", forced_by = forced, custom_say_emote = message_mods[MODE_CUSTOM_SAY_EMOTE])
- var/quoted_message = say_quote(apply_message_emphasis(message), spans, message_mods)
- var/rendered = "[span_name("[name]")] [quoted_message]"
- var/dead_rendered = "[span_name("[name] (Imaginary friend of [owner])")] [quoted_message]"
+ log_sayverb_talk(message, message_mods, tag = "imaginary friend", forced_by = forced)
+
+ var/messagepart = generate_messagepart(message, spans, message_mods)
+ var/rendered = "[span_name("[name]")] [messagepart]"
+ var/dead_rendered = "[span_name("[name] (Imaginary friend of [owner])")] [messagepart]"
var/language = message_language || owner.get_selected_language()
Hear(rendered, src, language, message, null, null, null, spans, message_mods) // We always hear what we say
diff --git a/code/datums/components/blob_minion.dm b/code/datums/components/blob_minion.dm
index a17fd4b4a15..75eee8410d2 100644
--- a/code/datums/components/blob_minion.dm
+++ b/code/datums/components/blob_minion.dm
@@ -172,11 +172,17 @@
/// We only speak telepathically to blobs
/datum/component/blob_minion/proc/on_try_speech(mob/living/minion, message, ignore_spam, forced)
SIGNAL_HANDLER
- minion.log_talk(message, LOG_SAY, tag = "blob hivemind telepathy")
- var/spanned_message = minion.say_quote(message)
+ INVOKE_ASYNC(src, PROC_REF(send_blob_telepathy), minion, message)
+ return COMPONENT_CANNOT_SPEAK
+
+/datum/component/blob_minion/proc/send_blob_telepathy(mob/living/minion, message)
+ var/list/message_mods = list()
+ // Note: check_for_custom_say_emote can sleep.
+ var/adjusted_message = minion.check_for_custom_say_emote(message, message_mods)
+ minion.log_sayverb_talk(message, message_mods, tag = "blob hivemind telepathy")
+ var/spanned_message = minion.generate_messagepart(adjusted_message, message_mods = message_mods)
var/rendered = span_blob("\[Blob Telepathy\] [minion.real_name] [spanned_message]")
relay_to_list_and_observers(rendered, GLOB.blob_telepathy_mobs, minion, MESSAGE_TYPE_RADIO)
- return COMPONENT_CANNOT_SPEAK
/// Called when a blob minion is transformed into something else, hopefully a spore into a zombie
/datum/component/blob_minion/proc/on_transformed(mob/living/minion, mob/living/replacement)
diff --git a/code/datums/saymode.dm b/code/datums/saymode.dm
index 5e67d960b42..951c805f0af 100644
--- a/code/datums/saymode.dm
+++ b/code/datums/saymode.dm
@@ -1,20 +1,40 @@
/datum/saymode
+ /// The symbol key used to enable this say mode.
var/key
+ /// The corresponding say mode string.
var/mode
+ /// Whether this say mode works with custom say emotes.
+ var/allows_custom_say_emotes = FALSE
-//Return FALSE if you have handled the message. Otherwise, return TRUE and saycode will continue doing saycode things.
-//user = whoever said the message
-//message = the message
-//language = the language.
-/datum/saymode/proc/handle_message(mob/living/user, message, datum/language/language)
+/// Checks whether this saymode can be used by the given user. May send feedback.
+/datum/saymode/proc/can_be_used_by(mob/living/user)
return TRUE
+/**
+ * Handles actually modifying or forwarding our message.
+ * Returns `SAYMODE_[X]` flags.
+ *
+ * user - The living speaking using this say mode.
+ * message - The message to be said.
+ * spans - A list of spans to attach to the message.
+ * language - The language the message was said in.
+ * message_mods - A list of message modifiers, i.e. whispering/singing.
+ */
+/datum/saymode/proc/handle_message(
+ mob/living/user,
+ message,
+ list/spans = list(),
+ datum/language/language,
+ list/message_mods = list()
+)
+ return NONE
+
+
/datum/saymode/changeling
key = MODE_KEY_CHANGELING
mode = MODE_CHANGELING
-/datum/saymode/changeling/handle_message(mob/living/user, message, datum/language/language)
- //we can send the message
+/datum/saymode/changeling/can_be_used_by(mob/living/user)
if(!user.mind)
return FALSE
if(user.mind.has_antag_datum(/datum/antagonist/fallen_changeling))
@@ -26,11 +46,20 @@
if(HAS_TRAIT(user, TRAIT_CHANGELING_HIVEMIND_MUTE))
to_chat(user, span_warning("The poison in the air hinders our ability to interact with the hivemind."))
return FALSE
+ return TRUE
- user.log_talk(message, LOG_SAY, tag="changeling [ling_sender.changelingID]")
+/datum/saymode/changeling/handle_message/handle_message(
+ mob/living/user,
+ message,
+ list/spans = list(),
+ datum/language/language,
+ list/message_mods = list()
+)
+ var/datum/antagonist/changeling/ling_sender = IS_CHANGELING(user)
+ user.log_talk(message, LOG_SAY, tag = "changeling [ling_sender.changelingID]")
var/msg = span_changeling("[ling_sender.changelingID]: [message]")
- //the recipients can receive the message
+ // Send the message to our other changelings.
for(var/datum/antagonist/changeling/ling_receiver in GLOB.antagonists)
if(!ling_receiver.owner)
continue
@@ -45,54 +74,96 @@
for(var/mob/dead/ghost as anything in GLOB.dead_mob_list)
to_chat(ghost, "[FOLLOW_LINK(ghost, user)] [msg]", type = MESSAGE_TYPE_RADIO)
- return FALSE
+ return SAYMODE_MESSAGE_HANDLED
+
/datum/saymode/xeno
- key = "a"
+ key = MODE_KEY_ALIEN
mode = MODE_ALIEN
+ allows_custom_say_emotes = TRUE
-/datum/saymode/xeno/handle_message(mob/living/user, message, datum/language/language)
- if(user.hivecheck())
- user.alien_talk(message)
- return FALSE
+/datum/saymode/xeno/can_be_used_by(mob/living/user)
+ if(!user.hivecheck())
+ return FALSE
+ return TRUE
+
+/datum/saymode/xeno/handle_message/handle_message(
+ mob/living/user,
+ message,
+ list/spans = list(),
+ datum/language/language,
+ list/message_mods = list()
+)
+ user.alien_talk(message, spans, message_mods)
+ return SAYMODE_MESSAGE_HANDLED
/datum/saymode/vocalcords
key = MODE_KEY_VOCALCORDS
mode = MODE_VOCALCORDS
-/datum/saymode/vocalcords/handle_message(mob/living/user, message, datum/language/language)
- if(iscarbon(user))
- var/mob/living/carbon/C = user
- var/obj/item/organ/vocal_cords/V = C.get_organ_slot(ORGAN_SLOT_VOICE)
- if(V?.can_speak_with())
- V.handle_speech(message) //message
- V.speak_with(message) //action
- return FALSE
+/datum/saymode/vocalcords/can_be_used_by(mob/living/user)
+ if(!iscarbon(user))
+ return FALSE
+ return TRUE
+
+/datum/saymode/vocalcords/handle_message/handle_message(
+ mob/living/user,
+ message,
+ list/spans = list(),
+ datum/language/language,
+ list/message_mods = list()
+)
+ var/mob/living/carbon/carbon_user = user
+ var/obj/item/organ/vocal_cords/our_vocal_cords = carbon_user.get_organ_slot(ORGAN_SLOT_VOICE)
+ if(our_vocal_cords?.can_speak_with())
+ our_vocal_cords.handle_speech(message) //message
+ our_vocal_cords.speak_with(message) //action
+ return SAYMODE_MESSAGE_HANDLED
/datum/saymode/binary //everything that uses .b (silicons, drones)
key = MODE_KEY_BINARY
mode = MODE_BINARY
+ allows_custom_say_emotes = TRUE
-/datum/saymode/binary/handle_message(mob/living/user, message, datum/language/language)
+/datum/saymode/binary/can_be_used_by(mob/living/user)
+ if(!isdrone(user) && !user.binarycheck())
+ return FALSE
+ return TRUE
+
+/datum/saymode/binary/handle_message/handle_message(
+ mob/living/user,
+ message,
+ list/spans = list(),
+ datum/language/language,
+ list/message_mods = list()
+)
if(isdrone(user))
var/mob/living/basic/drone/drone_user = user
- drone_user.drone_chat(message)
- return FALSE
- if(user.binarycheck())
- user.robot_talk(message)
- return FALSE
- return FALSE
+ drone_user.drone_chat(message, spans, message_mods)
+ else if(user.binarycheck())
+ user.robot_talk(message, spans, message_mods)
+ return SAYMODE_MESSAGE_HANDLED
/datum/saymode/holopad
- key = "h"
+ key = MODE_KEY_HOLOPAD
mode = MODE_HOLOPAD
+ allows_custom_say_emotes = TRUE
-/datum/saymode/holopad/handle_message(mob/living/user, message, datum/language/language)
- if(isAI(user))
- var/mob/living/silicon/ai/AI = user
- AI.holopad_talk(message, language)
+/datum/saymode/holopad/can_be_used_by(mob/living/user)
+ if(!isAI(user))
return FALSE
return TRUE
+
+/datum/saymode/holopad/handle_message/handle_message(
+ mob/living/user,
+ message,
+ list/spans = list(),
+ datum/language/language,
+ list/message_mods = list()
+)
+ var/mob/living/silicon/ai/ai_user = user
+ ai_user.holopad_talk(message, spans, language, message_mods)
+ return SAYMODE_MESSAGE_HANDLED
diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm
index 99ad58a5f49..b7cc52ca076 100644
--- a/code/game/machinery/hologram.dm
+++ b/code/game/machinery/hologram.dm
@@ -592,7 +592,7 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
holocall_to_update.user.Hear(message, speaker, message_language, raw_message, radio_freq, radio_freq_name, radio_freq_color, spans, message_mods, message_range = INFINITY)
if(outgoing_call?.hologram && speaker == outgoing_call.user)
- outgoing_call.hologram.say(raw_message, sanitize = FALSE)
+ outgoing_call.hologram.say(raw_message, spans = spans, sanitize = FALSE, language = message_language, message_mods = message_mods)
if(record_mode && speaker == record_user)
record_message(speaker, raw_message, message_language)
diff --git a/code/game/say.dm b/code/game/say.dm
index a9554944715..d5b42d3905f 100644
--- a/code/game/say.dm
+++ b/code/game/say.dm
@@ -68,7 +68,8 @@ GLOBAL_LIST_INIT(freqtospan, list(
return
spans |= speech_span
language ||= get_selected_language()
- message_mods[SAY_MOD_VERB] = say_mod(message, message_mods)
+ if(!message_mods[SAY_MOD_VERB])
+ message_mods[SAY_MOD_VERB] = say_mod(message, message_mods)
send_speech(message, message_range, src, bubble_type, spans, language, message_mods, forced = forced)
/// Called when this movable hears a message from a source.
@@ -173,18 +174,15 @@ GLOBAL_LIST_INIT(freqtospan, list(
//End name span.
var/endspanpart = ""
- //Message
- var/messagepart
+ // Language icon.
var/languageicon = ""
- if(message_mods[MODE_CUSTOM_SAY_ERASE_INPUT])
- messagepart = message_mods[MODE_CUSTOM_SAY_EMOTE]
- else
- messagepart = speaker.say_quote(raw_message, spans, message_mods)
-
+ if(!message_mods[MODE_CUSTOM_SAY_ERASE_INPUT])
var/datum/language/dialect = GLOB.language_datum_instances[message_language]
if(istype(dialect) && dialect.display_icon(src))
languageicon = "[dialect.get_icon()] "
+ // The actual message part.
+ var/messagepart = speaker.generate_messagepart(raw_message, spans, message_mods)
messagepart = " [messagepart]"
return "[spanpart1][spanpart2][freqpart][languageicon][compose_track_href(speaker, namepart)][namepart][compose_job(speaker, message_language, raw_message, radio_freq)][endspanpart][messagepart]"
@@ -225,14 +223,20 @@ GLOBAL_LIST_INIT(freqtospan, list(
return verb_say
/**
- * This prock is used to generate a message for chat
- * Generates the `says, "meme"` part of the `Grey Tider says, "meme"`.
+ * This proc is used to generate the 'message' part of a chat message.
+ * Generates the `says, "meme"` part of the `Grey Tider says, "meme"`,
+ * or the `taps their microphone.` part of `Grey Tider taps their microphone.`.
*
* input - The message to be said
* spans - A list of spans to attach to the message. Includes the atom's speech span by default
* message_mods - A list of message modifiers, i.e. whispering/singing
*/
-/atom/movable/proc/say_quote(input, list/spans = list(speech_span), list/message_mods = list())
+/atom/movable/proc/generate_messagepart(input, list/spans = list(speech_span), list/message_mods = list())
+ // If we only care about the emote part, early return.
+ if(message_mods[MODE_CUSTOM_SAY_ERASE_INPUT])
+ return apply_message_emphasis(message_mods[MODE_CUSTOM_SAY_EMOTE])
+
+ // Otherwise, we format our full quoted message.
if(!input)
input = "..."
diff --git a/code/modules/antagonists/blob/overmind.dm b/code/modules/antagonists/blob/overmind.dm
index 2761077d86a..037c2169972 100644
--- a/code/modules/antagonists/blob/overmind.dm
+++ b/code/modules/antagonists/blob/overmind.dm
@@ -321,10 +321,11 @@ GLOBAL_LIST_EMPTY(blob_nodes)
if (!message)
return
- src.log_talk(message, LOG_SAY)
-
- var/message_a = say_quote(message)
- var/rendered = span_big(span_blob("\[Blob Telepathy\] [name]([blobstrain.name]) [message_a]"))
+ var/list/message_mods = list()
+ var/adjusted_message = check_for_custom_say_emote(message, message_mods)
+ log_sayverb_talk(message, message_mods, tag = "blob hivemind telepathy")
+ var/messagepart = generate_messagepart(adjusted_message, message_mods = message_mods)
+ var/rendered = span_big(span_blob("\[Blob Telepathy\] [name]([blobstrain.name]) [messagepart]"))
relay_to_list_and_observers(rendered, GLOB.blob_telepathy_mobs, src, MESSAGE_TYPE_RADIO)
/mob/eye/blob/blob_act(obj/structure/blob/B)
diff --git a/code/modules/mob/living/basic/drone/drone_say.dm b/code/modules/mob/living/basic/drone/drone_say.dm
index af0bef41bb1..3a006d8d1b2 100644
--- a/code/modules/mob/living/basic/drone/drone_say.dm
+++ b/code/modules/mob/living/basic/drone/drone_say.dm
@@ -9,18 +9,24 @@
* * exact_faction_match - Passed to [/mob/proc/faction_check_atom]
*/
/proc/_alert_drones(msg, dead_can_hear = FALSE, atom/source, mob/living/faction_checked_mob, exact_faction_match)
- if (dead_can_hear && source)
- for (var/mob/dead_mob in GLOB.dead_mob_list)
+ if(dead_can_hear && source)
+ for(var/mob/dead_mob in GLOB.dead_mob_list)
var/link = FOLLOW_LINK(dead_mob, source)
to_chat(dead_mob, "[link] [msg]")
for(var/global_drone in GLOB.drones_list)
var/mob/living/basic/drone/drone = global_drone
- if(istype(drone) && drone.stat != DEAD)
- if(faction_checked_mob)
- if(drone.faction_check_atom(faction_checked_mob, exact_faction_match))
- to_chat(drone, msg)
- else
- to_chat(drone, msg)
+ if(!istype(drone))
+ continue
+ if(drone.stat == DEAD)
+ continue
+ if(faction_checked_mob && !drone.faction_check_atom(faction_checked_mob, exact_faction_match))
+ continue
+ to_chat(
+ drone,
+ msg,
+ type = MESSAGE_TYPE_RADIO,
+ avoid_highlighting = (drone == source),
+ )
@@ -39,5 +45,7 @@
*
* Shares the same radio code with binary
*/
-/mob/living/basic/drone/proc/drone_chat(msg)
- alert_drones("Drone Chat: [span_name("[name]")] [say_quote(msg)]", TRUE)
+/mob/living/basic/drone/proc/drone_chat(message, list/spans = list(), list/message_mods = list())
+ log_sayverb_talk(message, message_mods, tag = "drone chat")
+ var/message_part = generate_messagepart(message, spans, message_mods)
+ alert_drones("Drone Chat: [span_name("[name]")] [message_part]", TRUE)
diff --git a/code/modules/mob/living/carbon/alien/alien_say.dm b/code/modules/mob/living/carbon/alien/alien_say.dm
index 43317ff59e7..21655bfa162 100644
--- a/code/modules/mob/living/carbon/alien/alien_say.dm
+++ b/code/modules/mob/living/carbon/alien/alien_say.dm
@@ -1,10 +1,10 @@
-/mob/living/proc/alien_talk(message, shown_name = real_name, big_voice = FALSE)
- src.log_talk(message, LOG_SAY)
+/mob/living/proc/alien_talk(message, list/spans = list(), list/message_mods = list(), shown_name = real_name, big_voice = FALSE)
+ log_sayverb_talk(message, message_mods, tag = "alien hivemind")
message = trim(message)
if(!message)
return
- var/message_a = say_quote(message)
+ var/message_a = generate_messagepart(message, spans, message_mods)
var/hivemind_spans = "alien"
if(big_voice)
hivemind_spans += " big"
@@ -16,8 +16,8 @@
var/link = FOLLOW_LINK(player, src)
to_chat(player, "[link] [rendered]", type = MESSAGE_TYPE_RADIO)
-/mob/living/carbon/alien/adult/royal/queen/alien_talk(message, shown_name = name)
- ..(message, shown_name, TRUE)
+/mob/living/carbon/alien/adult/royal/queen/alien_talk(message, list/spans = list(), list/message_mods = list(), shown_name = name, big_voice = TRUE)
+ ..(message, spans, message_mods, shown_name, TRUE)
/mob/living/carbon/hivecheck()
var/obj/item/organ/alien/hivenode/N = get_organ_by_type(/obj/item/organ/alien/hivenode)
diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm
index edb2081bf6b..12f97e9a4ce 100644
--- a/code/modules/mob/living/living_say.dm
+++ b/code/modules/mob/living/living_say.dm
@@ -121,8 +121,8 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
var/original_message = message
message = get_message_mods(message, message_mods)
- saymode = SSradio.saymodes[message_mods[RADIO_KEY]]
- if (!forced && !saymode)
+ saymode = SSradio.get_available_say_mode(src, message_mods[RADIO_KEY])
+ if(!forced && (isnull(saymode) || saymode.allows_custom_say_emotes))
message = check_for_custom_say_emote(message, message_mods)
if(!message)
@@ -176,15 +176,10 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
var/succumbed = FALSE
- // If there's a custom say emote it gets logged differently.
- if(message_mods[MODE_CUSTOM_SAY_EMOTE])
- log_message(message_mods[MODE_CUSTOM_SAY_EMOTE], LOG_RADIO_EMOTE)
-
// If it's not erasing the input portion, then something is being said and this isn't a pure custom say emote.
if(!message_mods[MODE_CUSTOM_SAY_ERASE_INPUT])
if(message_mods[WHISPER_MODE] == MODE_WHISPER)
message_range = 1
- log_talk(message, LOG_WHISPER, forced_by = forced, custom_say_emote = message_mods[MODE_CUSTOM_SAY_EMOTE])
if(stat == HARD_CRIT)
var/health_diff = round(-HEALTH_THRESHOLD_DEAD + health)
// If we cut our message short, abruptly end it with a-..
@@ -194,8 +189,8 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
last_words = message
message_mods[WHISPER_MODE] = MODE_WHISPER_CRIT
succumbed = TRUE
- else
- log_talk(message, LOG_SAY, forced_by = forced, custom_say_emote = message_mods[MODE_CUSTOM_SAY_EMOTE])
+
+ log_sayverb_talk(message, message_mods, forced_by = forced)
#ifdef UNIT_TESTS
// Saves a ref() to our arglist specifically.
@@ -241,7 +236,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
message = autopunct_bare(message)
// SKYRAT EDIT ADDITION END
//This is before anything that sends say a radio message, and after all important message type modifications, so you can scumb in alien chat or something
- if(saymode && !saymode.handle_message(src, message, language))
+ if(saymode && (saymode.handle_message(src, message, spans, language, message_mods) & SAYMODE_MESSAGE_HANDLED))
return
var/radio_return = radio(message, message_mods, spans, language)//roughly 27% of living/say()'s total cost
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 00cff3fe3ae..1cdfa46c511 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -798,7 +798,7 @@
/mob/living/silicon/ai/proc/relay_speech(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
var/raw_translation = translate_language(speaker, message_language, raw_message, spans, message_mods)
var/atom/movable/source = speaker.GetSource() || speaker // is the speaker virtual/radio
- var/treated_message = source.say_quote(raw_translation, spans, message_mods)
+ var/treated_message = source.generate_messagepart(raw_translation, spans, message_mods)
var/start = "Relayed Speech: "
var/namepart
diff --git a/code/modules/mob/living/silicon/ai/ai_say.dm b/code/modules/mob/living/silicon/ai/ai_say.dm
index 8e673bbe722..ca59835483b 100644
--- a/code/modules/mob/living/silicon/ai/ai_say.dm
+++ b/code/modules/mob/living/silicon/ai/ai_say.dm
@@ -42,25 +42,24 @@
return FALSE
//For holopads only. Usable by AI.
-/mob/living/silicon/ai/proc/holopad_talk(message, language)
+/mob/living/silicon/ai/proc/holopad_talk(message, list/spans = list(), language, list/message_mods = list())
message = trim(message)
if (!message)
return
var/obj/machinery/holopad/active_pad = current
- if(istype(active_pad) && active_pad.masters[src])//If there is a hologram and its master is the user.
- var/obj/effect/overlay/holo_pad_hologram/ai_holo = active_pad.masters[src]
- var/turf/padturf = get_turf(active_pad)
- var/padloc
- if(padturf)
- padloc = AREACOORD(padturf)
- else
- padloc = "(UNKNOWN)"
- src.log_talk(message, LOG_SAY, tag="HOLOPAD in [padloc]")
- ai_holo.say(message, sanitize = FALSE, language = language)
- else
+ // Only continue if there is a hologram and its master is the user.
+ if(!istype(active_pad) || !active_pad.masters[src])
to_chat(src, span_alert("No holopad connected."))
+ return
+
+ var/obj/effect/overlay/holo_pad_hologram/ai_holo = active_pad.masters[src]
+ var/turf/pad_turf = get_turf(active_pad)
+ var/pad_loc = pad_turf ? AREACOORD(pad_turf) : "(UNKNOWN)"
+
+ log_sayverb_talk(message, message_mods, tag = "HOLOPAD in [pad_loc]")
+ ai_holo.say(message, spans = spans, sanitize = FALSE, language = language, message_mods = message_mods)
/* SKYRAT EDIT REMOVAL - MOVED TO: MODULAR_SKYRAT/MODULES/ALT_VOX/CODE/VOX_PROCS.DM
// Make sure that the code compiles with AI_VOX undefined
diff --git a/code/modules/mob/living/silicon/silicon_say.dm b/code/modules/mob/living/silicon/silicon_say.dm
index d2b83890171..7e806bbdf8c 100644
--- a/code/modules/mob/living/silicon/silicon_say.dm
+++ b/code/modules/mob/living/silicon/silicon_say.dm
@@ -1,8 +1,8 @@
-/mob/living/proc/robot_talk(message)
- log_talk(message, LOG_SAY, tag="binary")
+/mob/living/proc/robot_talk(message, list/spans = list(), list/message_mods = list())
+ log_sayverb_talk(message, message_mods, tag="binary")
var/designation = "Default Cyborg"
- var/spans = list(SPAN_ROBOT)
+ spans |= SPAN_ROBOT
if(issilicon(src))
var/mob/living/silicon/player = src
@@ -15,9 +15,10 @@
// AIs are loud and ugly
spans |= SPAN_COMMAND
- var/quoted_message = say_quote(
+ var/messagepart = generate_messagepart(
message,
- spans
+ spans,
+ message_mods,
)
var/namepart = name
@@ -31,31 +32,31 @@
namepart = brain.mainframe.name
designation = brain.mainframe.job
- for(var/mob/M in GLOB.player_list)
- if(M.binarycheck())
- if(isAI(M))
+ for(var/mob/hearing_mob in GLOB.player_list)
+ if(hearing_mob.binarycheck())
+ if(isAI(hearing_mob))
to_chat(
- M,
+ hearing_mob,
span_binarysay("\
Robotic Talk, \
- [span_name("[namepart] ([designation])")] \
- [quoted_message]\
+ [span_name("[namepart] ([designation])")] \
+ [messagepart]\
"),
type = MESSAGE_TYPE_RADIO,
- avoid_highlighting = src == M
+ avoid_highlighting = (src == hearing_mob)
)
else
to_chat(
- M,
+ hearing_mob,
span_binarysay("\
Robotic Talk, \
- [span_name("[namepart]")] [quoted_message]\
+ [span_name("[namepart]")] [messagepart]\
"),
type = MESSAGE_TYPE_RADIO,
- avoid_highlighting = src == M
+ avoid_highlighting = (src == hearing_mob)
)
- if(isobserver(M))
+ if(isobserver(hearing_mob))
var/following = src
// If the AI talks on binary chat, we still want to follow
@@ -65,17 +66,17 @@
var/mob/living/silicon/ai/ai = src
following = ai.eyeobj
- var/follow_link = FOLLOW_LINK(M, following)
+ var/follow_link = FOLLOW_LINK(hearing_mob, following)
to_chat(
- M,
+ hearing_mob,
span_binarysay("\
[follow_link] \
Robotic Talk, \
- [span_name("[namepart]")] [quoted_message]\
+ [span_name("[namepart]")] [messagepart]\
"),
type = MESSAGE_TYPE_RADIO,
- avoid_highlighting = src == M
+ avoid_highlighting = (src == hearing_mob)
)
/mob/living/silicon/binarycheck()
diff --git a/code/modules/mob/mob_say.dm b/code/modules/mob/mob_say.dm
index 8dce7302089..90f84cfe7da 100644
--- a/code/modules/mob/mob_say.dm
+++ b/code/modules/mob/mob_say.dm
@@ -162,7 +162,7 @@
if(name != real_name)
alt_name = " (died as [real_name])"
- var/spanned = say_quote(apply_message_emphasis(message))
+ var/spanned = generate_messagepart(message)
var/source = "DEAD: [name][alt_name]"
var/rendered = " [emoji_parse(spanned)]"
log_talk(message, LOG_SAY, tag="DEAD")
diff --git a/code/modules/mob/say_readme.md b/code/modules/mob/say_readme.md
index 5284012de29..e39dfb9fbcf 100644
--- a/code/modules/mob/say_readme.md
+++ b/code/modules/mob/say_readme.md
@@ -53,7 +53,7 @@ global procs
languages live either in datum/languages_holder or in the mind.
verb_say/verb_ask/verb_exclaim/verb_yell/verb_sing
- These determine what the verb is for their respective action. Used in say_quote().
+ These determine what the verb is for their respective action. Used in generate_messagepart().
say(message, bubble_type, var/list/spans, sanitize, datum/language/language, ignore_spam, forced)
Say() is the "mother-proc". It calls all the other procs required for speaking, but does little itself.
@@ -82,8 +82,8 @@ global procs
Modifies the message by comparing the languages of the speaker with the languages of the hearer.
Called on the hearer.
- say_quote(input, spans, list/message_mods)
- Adds a verb and quotes to a message. Also attaches span classes to a message.
+ generate_messagepart(input, spans, list/message_mods)
+ Either adds a lone custom say verb, or a verb and quotes to a message. Also attaches span classes to a message.
Verbs are determined by verb_say/verb_ask/verb_yell/verb_sing variables. Called on the speaker.
/mob
diff --git a/code/modules/mod/mod_link.dm b/code/modules/mod/mod_link.dm
index 85ff92cf2e7..e44c923403e 100644
--- a/code/modules/mod/mod_link.dm
+++ b/code/modules/mod/mod_link.dm
@@ -122,7 +122,7 @@
. = ..()
if(speaker != wearer && speaker != ai_assistant)
return
- mod_link.visual.say(raw_message, sanitize = FALSE, message_range = 2)
+ mod_link.visual.say(raw_message, spans = spans, sanitize = FALSE, language = message_language, message_range = 2, message_mods = message_mods)
/obj/item/mod/control/proc/on_overlay_change(atom/source, cache_index, overlay)
SIGNAL_HANDLER
@@ -306,7 +306,7 @@
. = ..()
if(speaker != loc)
return
- mod_link.visual.say(raw_message, sanitize = FALSE, message_range = 3)
+ mod_link.visual.say(raw_message, spans = spans, sanitize = FALSE, language = message_language, message_range = 3, message_mods = message_mods)
/obj/item/clothing/neck/link_scryer/proc/on_overlay_change(atom/source, cache_index, overlay)
SIGNAL_HANDLER