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