/// A complete audible message made of a list of /datum/say_segment. /datum/say_message /// The mob saying the message. var/mob/speaker /// Name shown instead of the speaker's real name, if any. var/alt_name = "" /// The raw string that was typed. Used in the admin log. var/raw_message /// The list of segments that make up the message body. var/list/datum/say_segment/segments = list() /// The sole language of this message, or null. var/datum/language/single_language /// Which verb to use. Determined by punctuation and language of the first piece. var/verb = "says" /// Is the message being whispered? var/whisper = FALSE /// Is the message being sung? var/singing = FALSE /// The musical note bracketing a sung message. var/sing_note /// Should the message render as italic? var/italics = FALSE /// Optional font size override for the rendered message. var/font_size /// Optional range limit for hearing the message. var/message_range /// The requested mode for this message. var/message_mode /// Sound played to listeners alongside the message, if any. var/sound/speech_sound /// Volume of speech_sound. var/sound_vol /// How the message is reaching the listener. var/say_mode = SAYMODE_SPOKEN /// Initial clarity. Most of the time this is clear. Sometimes the radio will degrade it. var/base_clarity = CLARITY_CLEAR /// Radio envelope parts. This must live here for performance reasons pending a telecomms rewrite. var/list/radio_parts /// Renders each segment as the listener perceives it. Returns list(text, is_emote) per segment. /datum/say_message/proc/render_pieces(mob/listener, clarity = CLARITY_CLEAR) var/list/pieces = list() var/cap_next = TRUE for(var/datum/say_segment/segment as anything in segments) var/is_emote = (segment.language?.flags & INNATE) ? TRUE : FALSE var/rendered = segment.plain_text_for(listener, speaker) if(clarity == CLARITY_FAINT && !is_emote) rendered = length(rendered) ? stars(rendered) : "" rendered = trim(rendered) // Strip whitespace for quoting. We'll re-add it later. if(!length(rendered)) continue if(is_emote) cap_next = TRUE // emote re-arms caps for the next spoken run else if(cap_next) rendered = capitalize(rendered) cap_next = FALSE if(segment.language) rendered = segment.language.colourize(rendered) pieces += list(list(rendered, is_emote)) return pieces /// Quotes the given text if it's not an emote. /proc/quote_run(text, emote) text = trim(text) if(!length(text)) return "" return emote ? text : "\"[text]\"" /// Renders the body. Speech is quoted. Returns list(body, leads_with_emote). /datum/say_message/proc/render_body(mob/listener, clarity = CLARITY_CLEAR) var/list/pieces = render_pieces(listener, clarity) if(!length(pieces)) return list("", FALSE) var/leads_with_emote = pieces[1][2] var/list/runs = list() var/list/cur = list() var/cur_emote = pieces[1][2] for(var/list/piece in pieces) if(length(cur) && piece[2] != cur_emote) var/q = quote_run(jointext(cur, " "), cur_emote) if(length(q)) runs += q cur = list() cur_emote = piece[2] cur += piece[1] var/last = quote_run(jointext(cur, " "), cur_emote) if(length(last)) runs += last var/body = listener.hallucinate_heard(jointext(runs, " "), speaker) if(singing && length(body)) body = "[sing_note] [body] [sing_note]" return list(body, leads_with_emote) /// Returns the complete message as the given listener perceives it. /datum/say_message/proc/text_for(mob/listener, clarity = CLARITY_CLEAR) if(clarity == CLARITY_DROWSY) return drowsy_text_for(listener) var/list/out = list() for(var/list/piece in render_pieces(listener, clarity)) out += piece[1] if(!length(out)) return "" var/body = listener.hallucinate_heard(jointext(out, ""), speaker) if(singing && length(body)) body = "[sing_note] [body] [sing_note]" return body /// Returns the message as a bare perceived string. /datum/say_message/proc/plain_text_for(mob/listener) var/list/plain = list() for(var/datum/say_segment/segment as anything in segments) plain += segment.plain_text_for(listener, speaker) return jointext(plain, "") /// Returns the message as decorated text for drowsy listeners. /datum/say_message/proc/drowsy_text_for(mob/listener) if(isdeaf(listener)) return "" if(!prob(15)) return "...You almost hear someone talking..." var/list/messages = text2list(plain_text_for(listener), " ") if(!length(messages)) return "...You almost hear someone talking..." var/list/punctuation = list(",", "!", ".", ";", "?") var/heardword = messages[rand(1, messages.len)] if(copytext(heardword, 1, 1) in punctuation) heardword = copytext(heardword, 2) if(copytext(heardword, -1) in punctuation) heardword = copytext(heardword, 1, length(heardword)) return "...You hear something about...[heardword]" /// Returns a flat string with languages stripped out. /datum/say_message/proc/to_string() var/list/out = list() for(var/datum/say_segment/segment as anything in segments) out += segment.text return jointext(out, "") /// Collapses the whole message into a single segment in the given language. /datum/say_message/proc/collapse_to(datum/language/language, text) single_language = language segments = list(new /datum/say_segment(text, language)) /// Returns a shallow copy. /datum/say_message/proc/copy() var/datum/say_message/copy = new copy.speaker = speaker copy.alt_name = alt_name copy.raw_message = raw_message copy.segments = segments copy.single_language = single_language copy.verb = verb copy.whisper = whisper copy.singing = singing copy.sing_note = sing_note copy.italics = italics copy.font_size = font_size copy.message_range = message_range copy.message_mode = message_mode copy.speech_sound = speech_sound copy.sound_vol = sound_vol copy.say_mode = say_mode copy.base_clarity = base_clarity copy.radio_parts = radio_parts return copy