---
# System prepended metadata

title: '`say()` rework'

---

# `say()` rework
What if `say()` wasn't a complete unmaintainable mess?

## Basic Requirements
- Players press T or Y or type `say ":x thing"` in the command bar to speak either on the radio or not
- Speech modifiers should apply, ie accents, brain damage, oxy loss
- Modifiers should be removable/addable at runtime
- admin logging + filtering

## Not-basic requirements
- Flock speech
- skulls (fuck)

## General ideas
- messages are datums instead of just text so we can load up some metadata
- message starts at `say()` and gets passed through the chain of speech modules which mutate and do stuff with the message (like accents)
- hearers/recievers of the message get it as a datum and maybe pass it down a hearer chain? or just check some conditions or something? idk work this part out
- if we do it this way, makes it easy to add languages/encryption support
- radio channels could maybe be done on a COMSIG basis? ie listeners subscribe to COMSIG_RADIO_FREQ_123 and messages sent on that radio output get sent there?

## Listening
- Each listener gets a corresponding chain of hearing modules
- Hearing modules can reject the message
- Example - Ears (hear nearby message) -> language (garble if not understanding) -> output_maptext (reject if not in visual range), output_chat
- Could allow server side filtering/highlighting as a future expansion?
- Basically gonna need to implement a directed graph datastructure here. Probably. Unless we don't allow for input -> parralel processes -> output. Language could be just one module and list maybe?

## Here are some ideas what I had
```
/datum/speech_module
    var/priority = 0
    var/region = "accent"
    
    proc/proccess(var/datum/saymessage/message)
        //do stuff to/with the message
        return message
```

```
/atom
    var/list/speech_modules = list(
        "input" = list(),
        "accent" = list(),
        "output" = list())
        
/atom/say(var/text)
    var/datum/saymessage/message = new(text, some, status, vars)
    foreach key in ("input", "accent", "output")
        foreach var/datum/speech_module/module in speech_modules[key]
            message = module.process(message)
```

```
/datum/speech_module/output/out_loud
    region = "output"
    
    process(message)
        for atom in hearers(range)
            atom.recieveMessage(src)
```
```
var/list/listening_modules = list(
    "input" = list(),
    "languages" = list(),
    "output" = list())
    
/atom/recieveMessage(datum/saymessage/M)
    foreach key in ("input", "languages", "output")
        foreach var/datum/speech_module/module in listening_modules[key]
            message = module.process(message)
            if(!message) //if a module nulls the message, we don't recieve it
                return

/datum/listening_module/input/radio

    process(message)
        if(M.radio && listeningOnThisFrequency(M.radio))
            return message
        else
            return null
            
/datum/listening_module/language/flock

    process(message)
        if M.language == flock
            return message
        else
            message.text = flock_garble(message.text)
            return message

/datum/listening_module/output/maptext

    process(message)
        if(inVisualRange(message.source))
            src.owner.client.images += maptext(M.garbled, M.source)
        return FALSE

/datum/listening_module/output/chat
    process(message)
        src.owner << message.text
        return FALSE
```

## List of all the say calls and the weird shit they do

`/mob/zoldorfmob/say()`
- sanitize
- if zoldorf is free
    - check for emote prefix and do emote()
    - log (LOG_DIARY)
    - check client.ismuted
    - return say_dead(message, wraith = 1)
    - elif message, return

`/mob/dead/say()`
- sanitize
- log (GHOST)
- check client.ismuted
- calls /mob/say_dead()
- phrase_log("deadsay")
- for hearers(null,null) chance to hear stutter(message) (more for chaplain)

`/mob/dead/target_observer/hivemind_observer/say()`
- sanitize
- log (HIVEMIND)
- check client.ismuted
- calls /mob/say_hive()

`/mob/dead/target_observer/mentor_mouse_observer/say()`
- sanitize
- log (ADMINMOUSE)/(MENTORMOUSE)
- check client.ismuted
- game_stats.ScanText()
- show message to online admins
- boutput to observed and self

`/mob/living/say()`
- sanitize
- forced_desussification (good lord)
- if reverse_mode reverse_text(message)
- log LOG_DIARY
- game_stats.ScanText()
- check client.ismuted
- canspeak check
- check if dead or in afterlife bar, and say_dead() if so
- src.stat check (I guess backup dead/unconcious check?)
- check for emote prefix and do emote()
- check bioholder.HasEffect("mute")
- check if mask is_muzzle
- if oxyloss > 10 or losebreath >=4 or hasreagent("capulettium_plus") or hasStatus("resting") then whisper
- check singing prefix and set singing to TRUE if so
- say_decorate()
- if brain_damage >= 60 && prob(50), set mode to headset
- if message starts with ; headset mode
- if message starts with 
    - :lh mode left hand
    - :rh mode right hand
    - :in mode intercom
    - else if(AI)
        - :1 mode internal 1
        - :2 mode internal 2 
        - :3 mode monitor
        - else mode monitor & if :*x secure_headset_mode = x
    - else if(human/mobcritter/robot/shell)
        - mode secure headset and :*x secure_headset_mode = x
- forced_language = get_special_language(secure_headset_mode) basically only used for silicon language
- if singing and bioholder.HasEffect("accent_scots") sing *Danny Boy* :no_good: 
- if src.capitalize_speech()
- select voicetype and speechbubble icon (radio/note/notebad/noterobot)
- playsound sounds_speak\[voicetype\]
- check for LOUD_SINGING (if singing and ends in !)
- log too phrase_log (sing/radio/say)
- set last_words
- brain damage accent
- show_speech_bubble()
- blobchat (jesus fuck)
    - basically just checks is player is blob, dead, or admin
- process_language() - gets messages for understanding/not understanding
- get_language_id - gets forced or mobs selected language
- if message_mode is headset, secure headset, right hand or left hand, use talk_into_equipment()
- if message_mode is internal 1, internal 2, or monitor 
    - make sure we're AI or silicon, and select the mainframe is we're a shell
    - ensure we're not dead,unconcious, stunned or weakened
    - grab the appropriate internal radio and call talk_into, as well as skipping any other mics in range
- if message_mode is intercom, grab the intercoms in range 1, and talk_into those
- decapitated skeleton code, holy fuck
- grab say_location from either skeleton head or talker
- compile a list of listeners (in range, in a loc with the talker, the mob if you're in their pocket)
- consider sound proofing if we're in a nested object
- compile list of understanders/not understanders from listeners
- maptext colour and position
- megaphone makes maptext bigger
- unique_maptext_style, maptext_animation_colors
- bump up previous maptexts
- saylist() for passing the message to listeners
- say_quote() for figuring out the verb to use, ie "x stutters 'blah'"
- italics
- get_heard_name() for display
- SPEECH_REVERSE flag for flipping the text in chat
- final block seems to just be for showing messages to ghosts, the dead, and special flags havers
    - MOB_HEARS_ALL
    - iswraith() && not dense???
    - is zoldorf
    - isintangible() and can hear the saylocation
- check ghosts have local_deadchat or are wraiths
- zoldorfs can only hear everything if they're ghosting it, otherwise just in range

`/mob/living/critter/exploder/say()`
- if dead or npc, return
- sanitize
- ..(message)
- if involuntary or empty message or unconscious/dead, return
- if emote prefix, return
- message set to message sans any radio prefix
- if not npc, give message gradientText

`/mob/living/silicon/say()`
- if client is muted, no talky
- if dead, sanitize input and call say_dead(message)
- if mob is unconscious or dead, return (please god replace this with an !isalive(mob) check)
- if message length >2
    - if silicon chat prefix
        - trim ASCII characters above 32, sanitize the message from char 3 onwards, then pass to robo_talk(message)
    - else, return ..(message)
- else, return ..(message)

`/mob/living/silicon/drone/say()`
- if client is muted, no talky
- if mob is unconscious or dead, return (please god replace this with an !isalive(mob) check)
- if dead, sanitize input and call say_dead(message)
- if emote prefix, return ..()
    - else, display visible message `""<b>[src]</b> beeps."` and play a beep boop sound.

`/mob/living/silicon/ghostdrone/say()`
- sanitize message
- if client is muted, no talky
- if message has radio prefix `;`, pass the rest of the message to say_dead(message)
- check for emote prefix and do emote()
- return drone_broadcast(message)