You now force_say() when becoming incapacitated (#95172)

## About The Pull Request

All `/datum/status_effect/incapacitating` as well as death trigger a
`force_say()` on application. Some have custom suffixes.

Supporting work:
- Added custom suffix support to `force_say()`.
- Automatically trigger text saving to prevent the need for a DM -> TGUI
-> DM round trip before a `force_say()` actually calls `say()`. This is
needed because if, say, `Unconscious()` is allowed to proceed before the
`say()` call, the mob will be unable to speak by the time the data gets
to the server.

## Why It's Good For The Game

You can become incapacitated with text in your tgui_say box. This allows
for part of that message to get out before you are completely robusted.

## Changelog

🆑 Dominion
add: Incapacitation status effects and dead now trigger you to
forcefully say whatever was in your input prompt.
/🆑



https://github.com/user-attachments/assets/d4a3ee9c-0d79-418b-98d3-e9c2a72c3541



https://github.com/user-attachments/assets/af8da7c3-434d-4097-b721-dbadd8db9dfb

---------

Co-authored-by: Jordan Dominion <Cyberboss@users.noreply.github.com>
This commit is contained in:
Jordan Dominion
2026-02-25 14:47:56 +13:00
committed by GitHub
co-authored by Jordan Dominion
parent 43fd419ae2
commit 0d7a760a37
7 changed files with 51 additions and 12 deletions
@@ -16,6 +16,8 @@
remove_on_fullheal = TRUE
heal_flag_necessary = HEAL_CC_STATUS
var/needs_update_stat = FALSE
/// Suffixes attached to the force_say when applied, uses the "hurt" suffixes by default
var/list/alter_phrases
/datum/status_effect/incapacitating/on_creation(mob/living/new_owner, set_duration)
if(isnum(set_duration))
@@ -31,6 +33,15 @@
owner.update_stat()
return ..()
/datum/status_effect/incapacitating/on_apply()
SHOULD_CALL_PARENT(TRUE)
. = ..()
if(!.)
return
var/mob/living/carbon/human/human = owner
if(istype(human))
human.force_say(alter_phrases, immediate = TRUE)
//STUN
/datum/status_effect/incapacitating/stun
@@ -79,6 +90,7 @@
//PARALYZED
/datum/status_effect/incapacitating/paralyzed
id = "paralyzed"
alter_phrases = list("") // "Why am I about to be froz-"
/datum/status_effect/incapacitating/paralyzed/on_apply()
. = ..()
@@ -134,6 +146,7 @@
alert_type = /atom/movable/screen/alert/status_effect/asleep
needs_update_stat = TRUE
tick_interval = 2 SECONDS
alter_phrases = list("Zzz...", "ZZz...", "ZZZ...", "zzZ...", "zZZ...", "ZzZ...", "zzz...", "zZz...", "mimimimimimi...")
/datum/status_effect/incapacitating/sleeping/on_apply()
. = ..()
-2
View File
@@ -314,8 +314,6 @@ structure_check() searches for nearby cultist structures required for the invoca
if(check_holidays(APRIL_FOOLS) && prob(10))
convertee.Paralyze(10 SECONDS)
if(istype(human_convertee))
human_convertee.force_say()
convertee.say("You son of a bitch! I'm in.", forced = "That son of a bitch! They're in. (April Fools)")
else
@@ -22,6 +22,8 @@ GLOBAL_LIST_EMPTY(dead_players_during_shift)
human_heart?.beat = BEAT_NONE
human_heart?.Stop()
force_say(immediate = TRUE)
. = ..()
if(client && !HAS_TRAIT(src, TRAIT_SUICIDED) && !(client in GLOB.dead_players_during_shift))
@@ -33,6 +33,10 @@
var/window_open
/// What text was present in the say box the last time save_text was called
var/saved_text = ""
/// What channel was in use in the say box the last time save_text was called
var/saved_channel
/// Speech suffuxes used for force_say after "-". Defaults to hurt_phrases
var/list/alter_phrases
/** Creates the new input window to exist in the background. */
/datum/tgui_say/New(client/client, id)
+29 -8
View File
@@ -9,9 +9,11 @@
*/
/datum/tgui_say/proc/alter_entry(payload)
var/entry = payload["entry"]
var/list/phrases = alter_phrases || hurt_phrases
/// No OOC leaks
if(!entry || payload["channel"] == OOC_CHANNEL || payload["channel"] == ME_CHANNEL)
return pick(hurt_phrases)
return pick(phrases)
/// Random trimming for larger sentences
if(length(entry) > 50)
entry = trim(entry, rand(40, 50))
@@ -19,7 +21,7 @@
/// Otherwise limit trim to just last letter
if(length(entry) > 1)
entry = trim(entry, length(entry))
return entry + "-" + pick(hurt_phrases)
return entry + "-" + pick(phrases)
/**
* Delegates the speech to the proper channel.
@@ -42,19 +44,34 @@
client.mob.me_verb(entry)
return TRUE
if(OOC_CHANNEL)
client.ooc(entry)
ASYNC
client.ooc(entry)
return TRUE
if(ADMIN_CHANNEL)
SSadmin_verbs.dynamic_invoke_verb(client, /datum/admin_verb/cmd_admin_say, entry)
INVOKE_ASYNC(SSadmin_verbs, TYPE_PROC_REF(/datum/controller/subsystem/admin_verbs, dynamic_invoke_verb), client, /datum/admin_verb/cmd_admin_say, entry)
return TRUE
return FALSE
/**
* Force say handler.
* Sends a message to the say modal to send its current value.
* Arguments:
* alter_phrases - Optional list of alternate suffixes to blurt out
* immediate - If [TRUE], the say must be invoked inline due to side effects that may cause the mob to be unable to speak
*/
/datum/tgui_say/proc/force_say()
window.send_message("force")
/datum/tgui_say/proc/force_say(list/alter_phrases = null, immediate = FALSE)
src.alter_phrases = alter_phrases
if(immediate)
if(!saved_text)
saved_text = ""
if(!saved_channel)
saved_channel = SAY_CHANNEL
handle_entry("force", list("type" = "force", "entry" = saved_text, "channel" = saved_channel))
window.send_message("close")
else
window.send_message("force")
stop_typing()
/**
@@ -66,11 +83,14 @@
/**
* Makes the player force say what's in their current input box.
* Arguments:
* alter_phrases - Optional list of alternate suffixes to blurt out
* immediate - If [TRUE], the say must be invoked inline due to side effects that may cause the mob to be unable to speak
*/
/mob/living/carbon/human/proc/force_say()
/mob/living/carbon/human/proc/force_say(list/alter_phrases = null, immediate = FALSE)
if(stat != CONSCIOUS || !client?.tgui_say?.window_open)
return FALSE
client.tgui_say.force_say()
client.tgui_say.force_say(alter_phrases, immediate)
if(client.typing_indicators)
log_speech_indicators("[key_name(client)] FORCED to stop typing, indicators enabled.")
else
@@ -118,5 +138,6 @@
var/target_channel = payload["channel"]
if(target_channel == SAY_CHANNEL || target_channel == RADIO_CHANNEL)
saved_text = payload["entry"] // only save IC text
saved_channel = target_channel
return TRUE
return FALSE
+2
View File
@@ -194,6 +194,7 @@ export function TguiSay() {
// Handles typing indicators
if (channelIterator.current.isVisible() && newPrefix !== ':b ') {
messages.current.typingMsg();
messages.current.saveText(newValue, iterator.current());
}
setValue(newValue);
@@ -262,6 +263,7 @@ export function TguiSay() {
Byond.subscribeTo('force', handleForceSay);
Byond.subscribeTo('open', handleOpen);
Byond.subscribeTo('save', handleSaveText);
Byond.subscribeTo('close', handleClose);
}, []);
/** Value has changed, we need to check if the size of the window is ok */
+1 -2
View File
@@ -17,11 +17,10 @@ export const byondMessages = {
1 * SECONDS,
true,
),
saveText: debounce(
saveText: throttle(
(entry: string, channel: Channel) =>
Byond.sendMessage('save', { entry, channel }),
1 * SECONDS,
true,
),
// Throttle: Prevents spamming the server
typingMsg: throttle(() => Byond.sendMessage('typing'), 4 * SECONDS),