# `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)