diff --git a/code/datums/mutations/antenna.dm b/code/datums/mutations/antenna.dm index 7f564d6fa04..f5347fab41c 100644 --- a/code/datums/mutations/antenna.dm +++ b/code/datums/mutations/antenna.dm @@ -68,10 +68,12 @@ if (!owner) return ADD_TRAIT(grant_to, TRAIT_MIND_READER, GENETIC_MUTATION) + RegisterSignal(grant_to, COMSIG_MOB_EXAMINATE, PROC_REF(on_examining)) /datum/action/cooldown/spell/pointed/mindread/Remove(mob/remove_from) . = ..() REMOVE_TRAIT(remove_from, TRAIT_MIND_READER, GENETIC_MUTATION) + UnregisterSignal(remove_from, COMSIG_MOB_EXAMINATE) /datum/action/cooldown/spell/pointed/mindread/is_valid_target(atom/cast_on) if(!isliving(cast_on)) @@ -83,12 +85,15 @@ if(living_cast_on.stat == DEAD) to_chat(owner, span_warning("[cast_on] is dead!")) return FALSE + if(living_cast_on.mob_biotypes & MOB_ROBOTIC) + to_chat(owner, span_warning("[cast_on] is robotic, you can't read [cast_on.p_their()] mind!")) + return FALSE return TRUE /datum/action/cooldown/spell/pointed/mindread/cast(mob/living/cast_on) . = ..() - if(cast_on.can_block_magic(MAGIC_RESISTANCE_MIND, charge_cost = 0)) + if(cast_on.can_block_magic(antimagic_flags, charge_cost = 0)) to_chat(owner, span_warning("As you reach into [cast_on]'s mind, \ you are stopped by a mental blockage. It seems you've been foiled.")) return @@ -102,21 +107,66 @@ you feel the overwhelming emptiness within. A truly evil being. \ [HAS_TRAIT(owner, TRAIT_EVIL) ? "It's nice to find someone who is like-minded." : "What is wrong with this person?"]")) - to_chat(owner, span_boldnotice("You plunge into [cast_on]'s mind...")) + var/list/log_info = list() + var/list/discovered_info = list("You plunge into [cast_on]'s mind and discover...") if(prob(20)) // chance to alert the read-ee to_chat(cast_on, span_danger("You feel something foreign enter your mind.")) + log_info += "Target alerted!" var/list/recent_speech = cast_on.copy_recent_speech(copy_amount = 3, line_chance = 50) if(length(recent_speech)) - to_chat(owner, span_boldnotice("You catch some drifting memories of their past conversations...")) + discovered_info += "...Drifting memories of past conversations:" + var/list/speech_block = list() for(var/spoken_memory in recent_speech) - to_chat(owner, span_notice("[spoken_memory]")) + speech_block += " \"[spoken_memory]\"..." + log_info += "Recent speech: \"[spoken_memory]\"" + discovered_info += jointext(speech_block, "
") if(iscarbon(cast_on)) var/mob/living/carbon/carbon_cast_on = cast_on - to_chat(owner, span_boldnotice("You find that their intent is to [carbon_cast_on.combat_mode ? "harm" : "help"]...")) - to_chat(owner, span_boldnotice("You uncover that [carbon_cast_on.p_their()] true identity is [carbon_cast_on.mind.name].")) + discovered_info += "...Intent to [carbon_cast_on.combat_mode ? "harm" : "help"]." + discovered_info += "...True identity of [carbon_cast_on.mind.name]." + log_info += "Intent: \"[carbon_cast_on.combat_mode ? "harm" : "help"]\"" + log_info += "Identity: \"[carbon_cast_on.mind.name]\"" + + to_chat(owner, boxed_message(span_notice(jointext(discovered_info, "
")))) + log_combat(owner, cast_on, "mind read (cast intentionally)", null, "info: [english_list(log_info, and_text = ", ")]") + +/datum/action/cooldown/spell/pointed/mindread/proc/on_examining(mob/examiner, atom/examining) + SIGNAL_HANDLER + if(!isliving(examining) || examiner == examining) + return + + INVOKE_ASYNC(src, PROC_REF(read_mind), examiner, examining) + +/datum/action/cooldown/spell/pointed/mindread/proc/read_mind(mob/living/examiner, mob/living/examined) + if(examined.stat >= UNCONSCIOUS || isnull(examined.mind) || (examined.mob_biotypes & MOB_ROBOTIC)) + return + + var/antimagic = examined.can_block_magic(antimagic_flags, charge_cost = 0) + var/read_text = "" + if(!antimagic) + read_text = examined.get_typing_text() + if(!read_text) + return + + sleep(0.5 SECONDS) // small pause so it comes after all examine text and effects + if(QDELETED(examiner)) + return + if(antimagic) + to_chat(examiner, boxed_message(span_warning("You attempt to analyze [examined]'s current thoughts, but fail to penetrate [examined.p_their()] mind - It seems you've been foiled."))) + return + + var/list/log_info = list() + if(prob(10)) + to_chat(examined, span_danger("You feel something foreign enter your mind.")) + log_info += "Target alerted!" + + to_chat(examiner, boxed_message(span_notice("You analyze [examined]'s current thoughts...
 \"[read_text]\"..."))) + log_info += "Current thought: \"[read_text]\"" + + log_combat(examiner, examined, "mind read (triggered on examine)", null, "info: [english_list(log_info, and_text = ", ")]") /datum/mutation/mindreader/New(datum/mutation/copymut) ..() diff --git a/code/modules/tgui_input/say_modal/modal.dm b/code/modules/tgui_input/say_modal/modal.dm index 876c4476f67..551b8fc12e1 100644 --- a/code/modules/tgui_input/say_modal/modal.dm +++ b/code/modules/tgui_input/say_modal/modal.dm @@ -24,13 +24,15 @@ /// The user who opened the window var/client/client /// Injury phrases to blurt out - var/list/hurt_phrases = list("GACK!", "GLORF!", "OOF!", "AUGH!", "OW!", "URGH!", "HRNK!") + var/static/list/hurt_phrases = list("GACK!", "GLORF!", "OOF!", "AUGH!", "OW!", "URGH!", "HRNK!") /// Max message length var/max_length = MAX_MESSAGE_LEN /// The modal window var/datum/tgui_window/window /// Boolean for whether the tgui_say was opened by the user. var/window_open + /// What text was present in the say box the last time save_text was called + var/saved_text = "" /** Creates the new input window to exist in the background. */ /datum/tgui_say/New(client/client, id) @@ -128,7 +130,7 @@ if (type == "typing") start_typing() return TRUE - if (type == "entry" || type == "force") + if (type == "entry" || type == "force" || type == "save") handle_entry(type, payload) return TRUE return FALSE diff --git a/code/modules/tgui_input/say_modal/speech.dm b/code/modules/tgui_input/say_modal/speech.dm index 0d95b855a15..b5b7099868e 100644 --- a/code/modules/tgui_input/say_modal/speech.dm +++ b/code/modules/tgui_input/say_modal/speech.dm @@ -57,6 +57,13 @@ window.send_message("force") stop_typing() +/** + * Exports whatever text is currently in the input box to this datum + */ +/datum/tgui_say/proc/save_text() + saved_text = null + window.send_message("save") + /** * Makes the player force say what's in their current input box. */ @@ -70,6 +77,19 @@ log_speech_indicators("[key_name(client)] FORCED to stop typing, indicators DISABLED.") SEND_SIGNAL(src, COMSIG_HUMAN_FORCESAY) +/** + * Gets whatever text is currently in this mob's say box and returns it. + * + * Note: Sleeps, due to waiting for say to respond. + */ +/mob/proc/get_typing_text() + if(!client?.tgui_say?.window_open) + return + client.tgui_say.save_text() + var/safety = world.time + UNTIL(istext(client?.tgui_say?.saved_text) || world.time - safety > 2 SECONDS) + return client?.tgui_say?.saved_text + /** * Handles text entry and forced speech. * @@ -93,4 +113,10 @@ target_channel = SAY_CHANNEL // No ooc leaks delegate_speech(alter_entry(payload), target_channel) return TRUE + if(type == "save") + saved_text = "" // so we can differentiate null (nothing saved) and empty (nothing typed) + var/target_channel = payload["channel"] + if(target_channel == SAY_CHANNEL || target_channel == RADIO_CHANNEL) + saved_text = payload["entry"] // only save IC text + return TRUE return FALSE diff --git a/tgui/packages/tgui-say/TguiSay.tsx b/tgui/packages/tgui-say/TguiSay.tsx index fc3cdc2a34a..0fdcab89d7c 100644 --- a/tgui/packages/tgui-say/TguiSay.tsx +++ b/tgui/packages/tgui-say/TguiSay.tsx @@ -82,7 +82,11 @@ export function TguiSay() { setButtonContent(currentPrefix.current ?? iterator.current()); // Empty input, resets the channel - } else if (currentPrefix.current && iterator.isSay() && value?.length === 0) { + } else if ( + currentPrefix.current && + iterator.isSay() && + value?.length === 0 + ) { setCurrentPrefix(null); setButtonContent(iterator.current()); } @@ -152,6 +156,15 @@ export function TguiSay() { handleClose(); } + function handleSaveText(): void { + const iterator = channelIterator.current; + const currentValue = innerRef.current?.value; + + if (!currentValue || !iterator.isVisible()) return; + + messages.current.saveText(currentValue, iterator.current()); + } + function handleIncrementChannel(): void { const iterator = channelIterator.current; @@ -248,6 +261,7 @@ export function TguiSay() { Byond.subscribeTo('props', handleProps); Byond.subscribeTo('force', handleForceSay); Byond.subscribeTo('open', handleOpen); + Byond.subscribeTo('save', handleSaveText); }, []); /** Value has changed, we need to check if the size of the window is ok */ diff --git a/tgui/packages/tgui-say/timers.ts b/tgui/packages/tgui-say/timers.ts index b13e09ede75..6b4a9f46f08 100644 --- a/tgui/packages/tgui-say/timers.ts +++ b/tgui/packages/tgui-say/timers.ts @@ -17,6 +17,12 @@ export const byondMessages = { 1 * SECONDS, true, ), + saveText: debounce( + (entry: string, channel: Channel) => + Byond.sendMessage('save', { entry, channel }), + 1 * SECONDS, + true, + ), // Throttle: Prevents spamming the server typingMsg: throttle(() => Byond.sendMessage('typing'), 4 * SECONDS), } as const;