mirror of
https://github.com/vgstation-coders/vgstation13.git
synced 2025-12-09 07:57:50 +00:00
* Pref code refactor * Empty database reference * Unit testing SQLite * Everything else * Disable unit testing. * Equivalent * more robust unit tests
819 lines
29 KiB
Plaintext
819 lines
29 KiB
Plaintext
//bitflag #defines for radio returns.
|
|
#define ITALICS 1
|
|
#define REDUCE_RANGE 2
|
|
#define NOPASS 4
|
|
|
|
|
|
#define SAY_MINIMUM_PRESSURE 10
|
|
|
|
/proc/message_mode_to_name(mode)
|
|
switch(mode)
|
|
if(MODE_WHISPER)
|
|
return "whisper"
|
|
if(MODE_SECURE_HEADSET)
|
|
return "secure_headset"
|
|
if(MODE_DEPARTMENT)
|
|
return "department"
|
|
if(MODE_ALIEN)
|
|
return "alientalk"
|
|
if(MODE_HOLOPAD)
|
|
return "holopad"
|
|
if(MODE_CHANGELING)
|
|
return "changeling"
|
|
if(MODE_CULTCHAT)
|
|
return "cultchat"
|
|
if(MODE_ANCIENT)
|
|
return "ancientchat"
|
|
if(MODE_MUSHROOM)
|
|
return "sporechat"
|
|
if(MODE_BORER)
|
|
return "borerchat"
|
|
else
|
|
return "Unknown"
|
|
var/list/department_radio_keys = list(
|
|
":0" = "Deathsquad", "#0" = "Deathsquad", ".0" = "Deathsquad",
|
|
//1 Used by LANGUAGE_GALACTIC_COMMON
|
|
//2 Used by LANGUAGE_TRADEBAND
|
|
//3 Used by LANGUAGE_GUTTER
|
|
//4 Used by LANGUAGE_XENO
|
|
//5 Used by LANGUAGE_CULT
|
|
//6 Used by LANGUAGE_MONKEY
|
|
//7 Used by LANGUAGE_HUMAN
|
|
//8 Used by LANGUAGE_GOLEM
|
|
//9 Used by LANGUAGE_MOUSE
|
|
":-" = "Response Team","#-" = "Response Team",".-" = "Response Team",
|
|
":a" = "alientalk", "#a" = "alientalk", ".a" = "alientalk",
|
|
":b" = "binary", "#b" = "binary", ".b" = "binary",
|
|
":c" = "Command", "#c" = "Command", ".c" = "Command",
|
|
":d" = "Service", "#d" = "Service", ".d" = "Service",
|
|
":e" = "Engineering", "#e" = "Engineering", ".e" = "Engineering",
|
|
//f Used by LANGUAGE_SLIME
|
|
":g" = "changeling", "#g" = "changeling", ".g" = "changeling",
|
|
":h" = "department", "#h" = "department", ".h" = "department",
|
|
":i" = "intercom", "#i" = "intercom", ".i" = "intercom",
|
|
//j Used by LANGUAGE_TAJARAN
|
|
//k Used by LANGUAGE_SKRELLIAN and LANGUAGE_GREY
|
|
":l" = "left hand", "#l" = "left hand", ".l" = "left hand", "!l" = "fake left hand",
|
|
":m" = "Medical", "#m" = "Medical", ".m" = "Medical",
|
|
":n" = "Science", "#n" = "Science", ".n" = "Science",
|
|
":o" = "Common", "#o" = "Common", ".o" = "Common",
|
|
":p" = "AI Private", "#p" = "AI Private", ".p" = "AI Private",
|
|
//q Used by LANGUAGE_ROOTSPEAK
|
|
":r" = "right hand", "#r" = "right hand", ".r" = "right hand", "!r" = "fake right hand",
|
|
":s" = "Security", "#s" = "Security", ".s" = "Security",
|
|
":t" = "Syndicate", "#t" = "Syndicate", ".t" = "Syndicate",
|
|
":u" = "Supply", "#u" = "Supply", ".u" = "Supply",
|
|
//v Used by LANGUAGE_VOX
|
|
":w" = "whisper", "#w" = "whisper", ".w" = "whisper",
|
|
":x" = "cultchat", "#x" = "cultchat", ".x" = "cultchat",
|
|
":y" = "ancientchat", "#y" = "ancientchat", ".y" = "ancientchat",
|
|
//z Used by LANGUAGE_CLATTER
|
|
//@ Used by LANGUAGE_MARTIAN
|
|
":~" = "sporechat", "#~" = "sporechat", ".~" = "sporechat",
|
|
//borers
|
|
":&" = "borerchat", "#&" = "borerchat", ".&" = "borerchat",
|
|
)
|
|
|
|
var/list/headset_modes = list(
|
|
"Response Team",
|
|
"Command",
|
|
"Service",
|
|
"Engineering",
|
|
"Security",
|
|
"Syndicate",
|
|
"Supply",
|
|
"Medical",
|
|
"Science",
|
|
"department",
|
|
"Common",
|
|
)
|
|
|
|
/mob/living/proc/get_default_language()
|
|
if(!default_language)
|
|
if(languages && languages.len)
|
|
default_language = languages[1]
|
|
return default_language
|
|
|
|
/mob/living/hivecheck()
|
|
if (isalien(src))
|
|
return 1
|
|
if (!ishuman(src))
|
|
return 0
|
|
var/mob/living/carbon/human/H = src
|
|
if (H.ears)
|
|
var/obj/item/device/radio/headset/dongle
|
|
if(istype(H.ears,/obj/item/device/radio/headset))
|
|
dongle = H.ears
|
|
if(!istype(dongle))
|
|
return
|
|
if(dongle.translate_hive)
|
|
return 1
|
|
|
|
|
|
// /vg/edit: Added forced_by for handling braindamage messages and meme stuff
|
|
/mob/living/say(var/message, bubble_type)
|
|
say_testing(src, "/mob/living/say(\"[message]\", [bubble_type]")
|
|
if(timestopped)
|
|
return //under the effects of time magick
|
|
message = sanitize_speech(message)
|
|
message = capitalize(message)
|
|
|
|
say_testing(src, "Say start, message=[message]")
|
|
if(!message)
|
|
return
|
|
|
|
//Muting
|
|
var/turf/T = get_turf(src)
|
|
if(T && T.mute_time > world.time)
|
|
return
|
|
|
|
var/message_mode = get_message_mode(message)
|
|
if(silent)
|
|
to_chat(src, "<span class='warning'>You can't speak while silenced.</span>")
|
|
return
|
|
if((status_flags & FAKEDEATH) && !stat && message_mode != MODE_CHANGELING)
|
|
to_chat(src, "<span class='danger'>Talking right now would give us away!</span>")
|
|
return
|
|
|
|
//var/message_mode_name = message_mode_to_name(message_mode)
|
|
if (stat == DEAD) // Dead.
|
|
say_testing(src, "ur ded kid")
|
|
say_dead(message)
|
|
return
|
|
if(check_emote(message))
|
|
say_testing(src, "Emoted")
|
|
return
|
|
if (stat) // Unconcious.
|
|
if(message_mode == MODE_WHISPER) //Lets us say our last words.
|
|
say_testing(src, "message mode was whisper.")
|
|
whisper(copytext(message, 3))
|
|
return
|
|
if(!can_speak_basic(message))
|
|
say_testing(src, "we aren't able to talk")
|
|
return
|
|
|
|
if(message_mode == MODE_HEADSET || message_mode == MODE_ROBOT)
|
|
say_testing(src, "Message mode was [message_mode == MODE_HEADSET ? "headset" : "robot"]")
|
|
message = copytext(message, 2)
|
|
else if(message_mode)
|
|
say_testing(src, "Message mode is [message_mode]")
|
|
if(message_mode != MODE_HOLOPAD)
|
|
message = copytext(message, 3)
|
|
|
|
// SAYCODE 90.0!
|
|
// We construct our speech object here.
|
|
var/datum/speech/speech = create_speech(message)
|
|
|
|
if(!speech.language)
|
|
speech.language = parse_language(speech.message)
|
|
say_testing(src, "Getting speaking language, got [istype(speech.language) ? speech.language.name : "null"]")
|
|
if(istype(speech.language))
|
|
|
|
#ifdef SAY_DEBUG
|
|
var/oldmsg = message
|
|
#endif
|
|
speech.message = copytext(speech.message,2+length(speech.language.key))
|
|
say_testing(src, "Have a language, oldmsg = [oldmsg], newmsg = [message]")
|
|
else
|
|
if(!isnull(speech.language))
|
|
#ifdef SAY_DEBUG
|
|
var/oldmsg = message
|
|
#endif
|
|
var/n = speech.language
|
|
message = copytext(message,1+length(n))
|
|
say_testing(src, "We tried to speak a language we don't have; length = [length(n)], oldmsg = [oldmsg] parsed message = [message]")
|
|
speech.language = null
|
|
speech.language = get_default_language()
|
|
say_testing(src, "Didnt have a language, get_default_language() gave us [speech.language ? speech.language.name : "null"]")
|
|
speech.message = trim_left(speech.message)
|
|
|
|
//Handle speech muffling by muzzles.
|
|
if(!(speech?.language?.flags & NONORAL))
|
|
var/mob/living/carbon/C = src
|
|
switch(C.is_muzzled())
|
|
if(MUZZLE_SOFT)
|
|
speech.message = muffle(speech.message)
|
|
if(MUZZLE_HARD)
|
|
qdel(speech)
|
|
return
|
|
|
|
if(handle_inherent_channels(speech, message_mode))
|
|
say_testing(src, "Handled by inherent channel")
|
|
qdel(speech)
|
|
return
|
|
if(!can_speak_vocal(speech.message))
|
|
qdel(speech)
|
|
return
|
|
|
|
//parse the language code and consume it
|
|
|
|
//but first, scoreboard for syndiphrases stuff
|
|
if(src.mind && (src.mind.GetRole(TRAITOR) || src.mind.GetRole(NUKE_OP) || src.mind.GetRole(CHALLENGER)))
|
|
for(var/syn in syndicate_code_phrase)
|
|
if(findtext(speech.message, syn))
|
|
score.syndiphrases += 1
|
|
for(var/syn in syndicate_code_response)
|
|
if(findtext(speech.message, syn))
|
|
score.syndisponses += 1
|
|
|
|
var/message_range = SPEECH_RANGE
|
|
if(!isDead()) //Dead players can still talk from dead bodies and it would be inconvenient to have these.
|
|
treat_speech(speech)
|
|
if(!speech.message)
|
|
qdel(speech)
|
|
return
|
|
var/radio_return = get_speech_flags(message_mode)
|
|
if (speech_was_spoken_into_radio(message_mode))
|
|
speech.wrapper_classes.Add("spoken_into_radio")
|
|
if(radio_return & NOPASS) //There's a whisper() message_mode, no need to continue the proc if that is called
|
|
whisper(speech.message, speech.language)
|
|
qdel(speech)
|
|
return
|
|
|
|
if(radio_return & REDUCE_RANGE)
|
|
message_range = 1
|
|
if(copytext(text, length(text)) == "!")
|
|
message_range++
|
|
if(M_WHISPER in mutations)
|
|
message_range -= 2
|
|
|
|
if(M_LOUD in mutations)
|
|
message_range += 3
|
|
|
|
if(radio_return & ITALICS)
|
|
speech.message_classes.Add("italics")
|
|
send_speech(speech, message_range, bubble_type)
|
|
speech.message_classes.Remove("italics") //Wow, this is really hacky, but not as bad as creating a separate speech object with one differing var.
|
|
else
|
|
send_speech(speech, message_range, bubble_type)
|
|
radio(speech, message_mode) //Sends the radio signal
|
|
log_say_message(speech, message_mode, message)
|
|
qdel(speech)
|
|
return 1
|
|
|
|
/mob/living/proc/log_say_message(var/datum/speech/speech, var/message_mode, var/message)
|
|
var/turf/T = get_turf(src)
|
|
log_say("[name]/[key] [T?"(@[T.x],[T.y],[T.z])":"(@[x],[y],[z])"] [speech.language ? "As [speech.language.name] ":""]: [message_mode ? "([message_mode]):":""] [message]")
|
|
|
|
/mob/living/proc/resist_memes(var/datum/speech/speech)
|
|
if(stat || ear_deaf || speech.frequency || speech.speaker == src || !isliving(speech.speaker))
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/mob/living/Hear(var/datum/speech/speech, var/rendered_message = null)
|
|
if(!rendered_message)
|
|
rendered_message = speech.message
|
|
|
|
//Meme disease code. Needs to come before client so that NPCs/catatonic can be infected.
|
|
//We don't concern ourselves with: radio chatter, our own speech, or if we're deaf.
|
|
if(!resist_memes(speech))
|
|
var/mob/living/L = speech.speaker
|
|
var/list/diseases = L.virus2
|
|
if(istype(diseases) && diseases.len)
|
|
for(var/ID in diseases)
|
|
var/datum/disease2/disease/V = diseases[ID]
|
|
if(V.spread & SPREAD_MEMETIC)
|
|
infect_disease2(V, notes="(Memed, from [L])")
|
|
|
|
INVOKE_EVENT(src, /event/hear, "speech" = speech)
|
|
if(!client)
|
|
return
|
|
say_testing(src, "[src] ([src.type]) has heard a message (lang=[speech.language ? speech.language.name : "null"])")
|
|
var/deaf_message
|
|
var/deaf_type
|
|
var/type = 2
|
|
if(speech.speaker != src)
|
|
if(!speech.frequency) //These checks have to be seperate, else people talking on the radio will make "You can't hear yourself!" appear when hearing people over the radio while deaf.
|
|
deaf_message = "<span class='name'>[speech.speaker]</span> talks but you cannot hear them."
|
|
deaf_type = 1
|
|
else
|
|
if(hear_radio_only())
|
|
type = null //This kills the deaf check for radio only.
|
|
else
|
|
deaf_message = "<span class='notice'>You can't hear yourself!</span>"
|
|
deaf_type = 2 // Since you should be able to hear yourself without looking
|
|
var/atom/movable/AM = speech.speaker.GetSource()
|
|
if(!say_understands((istype(AM) ? AM : speech.speaker),speech.language)|| force_compose) //force_compose is so AIs don't end up without their hrefs.
|
|
rendered_message = render_speech(speech)
|
|
|
|
//checking for syndie codephrases if person is a tator
|
|
if(src.mind.GetRole(TRAITOR) || src.mind.GetRole(NUKE_OP) || src.mind.GetRole(CHALLENGER))
|
|
for(var/T in syndicate_code_phrase)
|
|
rendered_message = replacetext(rendered_message, html_encode(T), "<b style='color: red;'>[html_encode(T)]</b>")
|
|
|
|
for(var/T in syndicate_code_response)
|
|
rendered_message = replacetext(rendered_message, html_encode(T), "<i style='color: red;'>[html_encode(T)]</i>")
|
|
|
|
//AI mentions
|
|
if(isAI(src) && speech.frequency && !findtextEx(speech.job,"AI") && (speech.name != name))
|
|
var/mob/living/silicon/ai/ai = src
|
|
if(ai.mentions_on)
|
|
/* Find "AI", "AI...", or "... AI ...", case-insensitive. Global flag set so regex.Replace() hits all matches. */
|
|
/* We use a raw string (@"...") to avoid escaping all of the backslashes used in the pattern, as well as for readability. */
|
|
/* The first pattern is meant to help find "AI" all on its own, WITHOUT including "AI" when surrounded by letters, i.e. (rain) */
|
|
/* It's also necessary to ensure "AI" is found when surrounded by, say, quotation marks in rendered_message, which is HTML. */
|
|
var/static/regex/pattern = regex(@"(?<!\l)AI(?!\l)|\Aai\Z|\Aai(?=\s)|(?<=\s)ai(?=\s)", "gi")
|
|
if(pattern.Find(speech.message) || findtext(speech.message, ai.real_name))
|
|
ai << 'sound/machines/twobeep.ogg'
|
|
rendered_message = pattern.Replace(rendered_message, "<i style='color: blue;'>AI</i>")
|
|
rendered_message = replacetext(rendered_message, ai.real_name, "<i style='color: blue;'>[ai.real_name]</i>")
|
|
|
|
// Runechat messages
|
|
if (ismob(speech.speaker) && client?.prefs.get_pref(/datum/preference_setting/toggle/mob_chat_on_map) && stat != UNCONSCIOUS && !is_deaf())
|
|
create_chat_message(speech.speaker, speech.language, speech.message, speech.mode, speech.wrapper_classes)
|
|
else if (client?.prefs.get_pref(/datum/preference_setting/toggle/obj_chat_on_map) && stat != UNCONSCIOUS && !is_deaf())
|
|
create_chat_message(speech.speaker, speech.language, speech.message, speech.mode, speech.wrapper_classes)
|
|
if (ismob(speech.speaker))
|
|
show_message(rendered_message, type, deaf_message, deaf_type, src)
|
|
else if (!client.prefs.get_pref(/datum/preference_setting/toggle/no_goonchat_for_obj) || length_char(speech.message) > client?.prefs.get_pref(/datum/preference_setting/numerical/max_chat_length)) // Objects : only display if no goonchat on map or if the runemessage is too small.
|
|
show_message(rendered_message, type, deaf_message, deaf_type, src)
|
|
else if (istype(speech.speaker, /obj/item/device/assembly/speaker) || istype(speech.speaker, /obj/item/device/assembly_frame)) //Speakers will still work if no_goonchat_for_obj is set to TRUE
|
|
show_message(rendered_message, type, deaf_message, deaf_type, src)
|
|
return rendered_message
|
|
|
|
/mob/living/proc/hear_radio_only()
|
|
return 0
|
|
|
|
/mob/living/send_speech(var/datum/speech/speech, var/message_range=7, var/bubble_type) // what is bubble type?
|
|
var/visual_range = message_range //the range of hearers who can see that something was said, but not hear the message
|
|
say_testing(src, "/mob/living/send_speech() start, msg = [speech.message]; message_range = [message_range]; language = [speech.language ? speech.language.name : "None"]; speaker = [speech.speaker];")
|
|
if(isnull(message_range))
|
|
message_range = 7
|
|
|
|
message_range = atmospheric_speech(speech,message_range)
|
|
|
|
var/list/total_listeners = get_hearers_in_view(visual_range, speech.speaker)
|
|
var/list/actual_listeners = observers.Copy()
|
|
var/outside_range_message = "<span class='name'>[speech.speaker]</span> appears to say something, but you can't make it out from here."
|
|
for(var/atom/A in total_listeners)
|
|
if(!(A in actual_listeners))
|
|
if(get_dist(src, A) <= message_range)
|
|
actual_listeners.Add(A)
|
|
else if(ismob(A))
|
|
var/mob/M = A
|
|
if(!M.is_blind())
|
|
M.show_message(outside_range_message,MESSAGE_SEE,speaker = src)
|
|
else
|
|
to_chat(A, outside_range_message)
|
|
|
|
var/rendered = render_speech(speech)
|
|
|
|
var/list/listening_nonmobs = actual_listeners.Copy()
|
|
for(var/mob/M in actual_listeners)
|
|
listening_nonmobs -= M
|
|
M.Hear(speech, rendered)
|
|
|
|
send_speech_bubble(speech.message, bubble_type, total_listeners)
|
|
|
|
for (var/atom/movable/listener in listening_nonmobs)
|
|
listener.Hear(speech, rendered)
|
|
|
|
/mob/living/carbon/human/send_speech(var/datum/speech/speech, var/message_range=7, var/bubble_type)
|
|
talkcount++
|
|
. = ..()
|
|
|
|
/proc/say_test(var/text)
|
|
var/ending = copytext(text, length(text))
|
|
if (ending == "?")
|
|
return "1"
|
|
else if (ending == "!")
|
|
return "2"
|
|
return "0"
|
|
|
|
/mob/living/can_speak(message) //For use outside of Say()
|
|
if(can_speak_basic(message) && can_speak_vocal(message))
|
|
return 1
|
|
|
|
/mob/living/proc/can_speak_basic(message) //Check BEFORE handling of xeno and ling channels
|
|
if(!message || message == "")
|
|
return
|
|
|
|
if(client)
|
|
if(client.prefs.muted & MUTE_IC)
|
|
to_chat(src, "<span class='danger'>You cannot speak in IC (muted).</span>")
|
|
return
|
|
if(client.handle_spam_prevention(message,MUTE_IC))
|
|
return
|
|
|
|
return 1
|
|
|
|
|
|
/mob/living/proc/can_speak_vocal(message) //Check AFTER handling of xeno and ling channels
|
|
if(!message)
|
|
return
|
|
|
|
if(is_mute())
|
|
return
|
|
|
|
if(!IsVocal())
|
|
return
|
|
|
|
return 1
|
|
|
|
/mob/living/proc/check_emote(message)
|
|
if(copytext(message, 1, 2) == "*" && is_letter(text2ascii(message, 2)))
|
|
emote(copytext(message, 2))
|
|
return 1
|
|
|
|
|
|
/mob/living/proc/get_message_mode(message)
|
|
if(copytext(message, 1, 2) == ";")
|
|
return MODE_HEADSET
|
|
else if(length(message) > 2)
|
|
return department_radio_keys[lowertext(copytext(message, 1, 3))]
|
|
|
|
#define SPEAK_OVER_GENERAL_CULT_CHAT 0
|
|
#define SPEAK_OVER_CHANNEL_INTO_CULT_CHAT 1
|
|
#define HEAR_CULT_CHAT 2
|
|
|
|
/mob/living/proc/handle_inherent_channels(var/datum/speech/speech, var/message_mode)
|
|
switch(message_mode)
|
|
if(MODE_CHANGELING)
|
|
if(lingcheck())
|
|
var/turf/T = get_turf(src)
|
|
var/datum/role/changeling/C = mind.GetRole(CHANGELING)
|
|
if(!C)
|
|
return 0
|
|
log_say("[C.changelingID]/[key_name(src)] (@[T.x],[T.y],[T.z]) Changeling Hivemind: [html_encode(speech.message)]")
|
|
var/themessage = text("<i><font color=#800080><b>[]:</b> []</font></i>",C.changelingID,html_encode(speech.message))
|
|
for(var/mob/M in player_list)
|
|
if(M.lingcheck() || ((M in dead_mob_list) && !istype(M, /mob/new_player)))
|
|
handle_render(M,themessage,src)
|
|
return 1
|
|
if(MODE_CULTCHAT)
|
|
if(cult_chat_check(SPEAK_OVER_CHANNEL_INTO_CULT_CHAT))
|
|
var/turf/T = get_turf(src)
|
|
log_say("[key_name(src)] (@[T.x],[T.y],[T.z]) Cult channel: [html_encode(speech.message)]")
|
|
var/mob/living/L = speech.speaker
|
|
var/themessage
|
|
var/datum/role/cultist/C = iscultist(L)
|
|
if (C)
|
|
switch (C.cultist_role)
|
|
if (CULTIST_ROLE_MENTOR)
|
|
themessage = text("<span class='sinistermentor'><b>[]:</b> []</span>",src.name,html_encode(speech.message))
|
|
if (CULTIST_ROLE_ACOLYTE)
|
|
themessage = text("<span class='sinisteracolyte'><b>[]:</b> []</span>",src.name,html_encode(speech.message))
|
|
else
|
|
themessage = text("<span class='sinister'><b>[]:</b> []</span>",src.name,html_encode(speech.message))
|
|
else
|
|
themessage = text("<span class='sinister'><b>[]:</b> []</span>",src.name,html_encode(speech.message))
|
|
for(var/mob/M in player_list)
|
|
if(M.cult_chat_check(HEAR_CULT_CHAT) || ((M in dead_mob_list) && !istype(M, /mob/new_player)))
|
|
handle_render(M,themessage,src)
|
|
return 1
|
|
if(MODE_ANCIENT)
|
|
if(isMoMMI(src))
|
|
return 0 //Noice try, I really do appreciate the effort
|
|
var/list/stone = search_contents_for(/obj/item/commstone)
|
|
if(stone.len)
|
|
var/obj/item/commstone/commstone = stone[1]
|
|
if(commstone.commdevice)
|
|
var/list/stones = commstone.commdevice.get_active_stones()
|
|
var/themessage = text("<span class='ancient'>Ancient communication, <b>[]:</b> []</span>",src.name,html_encode(speech.message))
|
|
var/turf/T = get_turf(src)
|
|
log_say("[key_name(src)] (@[T.x],[T.y],[T.z]) Ancient chat: [html_encode(speech.message)]")
|
|
for(var/thestone in stones)
|
|
var/mob/M = get_holder_of_type(thestone,/mob)
|
|
if(M)
|
|
handle_render(M,themessage,src)
|
|
for(var/M in dead_mob_list)
|
|
if(!istype(M,/mob/new_player))
|
|
handle_render(M,themessage,src)
|
|
return 1
|
|
if(MODE_MUSHROOM)
|
|
var/message = text("<span class='mushroom'>Sporemind, <b>[]:</b> []</span>", src.real_name, html_encode(speech.message))
|
|
var/turf/T = get_turf(src)
|
|
log_say("[key_name(src)] (@[T.x],[T.y],[T.z]) Spore chat: [html_encode(speech.message)]")
|
|
for(var/mob/M in player_list)
|
|
if(iscarbon(M))
|
|
var/mob/living/carbon/human/H = M
|
|
if(ismushroom(H))
|
|
handle_render(M, message,src)
|
|
if((M in dead_mob_list) && !istype(M, /mob/new_player))
|
|
handle_render(M, message,src)
|
|
if(MODE_BORER)
|
|
//this is sent to and usable by borers and mobs controlled by borers
|
|
var/mob/living/simple_animal/borer/head = src.has_brain_worms(LIMB_HEAD)
|
|
if(isborer(src) || head && head.controlling)
|
|
var/mob/living/simple_animal/borer/B = head && head.controlling ? head : src
|
|
var/message = text("<span class='cortical'>Cortical link, <b>[]</b>: []</span>",B.truename, html_encode(speech.message))
|
|
var/turf/T = get_turf(src)
|
|
log_say("[key_name(src)] (@[T.x],[T.y],[T.z]) Borer chat: [html_encode(speech.message)]")
|
|
|
|
for(var/mob/M in mob_list)
|
|
if(isborer(M) || ((M in dead_mob_list) && !istype(M, /mob/new_player)))
|
|
if(isborer (M)) //for borers that are IN CONTROL
|
|
B = M //why it no typecast
|
|
if(B.controlling)
|
|
M = B.host
|
|
handle_render(M, message, src)
|
|
return 1
|
|
return 0
|
|
|
|
#undef SPEAK_OVER_GENERAL_CULT_CHAT
|
|
#undef SPEAK_OVER_CHANNEL_INTO_CULT_CHAT
|
|
#undef HEAR_CULT_CHAT
|
|
|
|
/mob/living/proc/treat_speech(var/datum/speech/speech, genesay = 0)
|
|
if(!(copytext(speech.message, 1, 2) == "*"))
|
|
for(var/obj/item/I in get_all_slots() + held_items)
|
|
I.affect_speech(speech, src)
|
|
|
|
if(getBrainLoss() >= 60)
|
|
if(braindamagespeechcooldown)
|
|
speech.message = null
|
|
emote("gibber")
|
|
else
|
|
braindamagespeechcooldown = TRUE
|
|
speech.message = derpspeech(speech.message, stuttering)
|
|
spawn(1 SECONDS)
|
|
braindamagespeechcooldown = FALSE
|
|
|
|
if(stuttering || (undergoing_hypothermia() == MODERATE_HYPOTHERMIA && prob(25)) )
|
|
speech.message = stutter(speech.message)
|
|
|
|
if (reagents)
|
|
var/datum/reagent/hyperzine/H = reagents.get_reagent_by_type(/datum/reagent/hyperzine)//also checks for hyperzine subtypes like cocaine etc
|
|
if (H && (H.data != "no motor mouth"))
|
|
speech.message = replacetext(speech.message," ","") // motor mouth
|
|
speech.message = replacetext(speech.message,",","") // motor mouth
|
|
speech.message = replacetext(speech.message,";","") // motor mouth
|
|
speech.message = replacetext(speech.message,"-","") // motor mouth
|
|
|
|
for(var/obj/item/weapon/implant/vocal/VI in src)
|
|
if(VI.imp_in == src)
|
|
var/original_message = speech.message
|
|
speech.message = VI.filter.FilterSpeech(speech.message)
|
|
var/datum/signal/signal = new /datum/signal
|
|
signal.data["message"] = speech.message
|
|
signal.data["reject"] = 0
|
|
signal.data["mob"] = src
|
|
signal.data["implant"] = VI
|
|
VI.Compiler.Run(signal)
|
|
speech.message = signal.data["reject"] ? null : signal.data["message"]
|
|
if(speech.message != original_message)
|
|
message_admins("The [VI] in [src] made \him say \"[speech.message]\" instead of \"[original_message]\" [formatJumpTo(src)]")
|
|
|
|
/mob/living/proc/get_speech_flags(var/message_mode)
|
|
switch(message_mode)
|
|
if(MODE_WHISPER, SPEECH_MODE_FINAL)
|
|
return NOPASS
|
|
if(MODE_HEADSET, MODE_SECURE_HEADSET, MODE_R_HAND, MODE_L_HAND, MODE_INTERCOM, MODE_BINARY)
|
|
return ITALICS | REDUCE_RANGE //most cases
|
|
if("robot")
|
|
return REDUCE_RANGE
|
|
if(message_mode in radiochannels)
|
|
return ITALICS | REDUCE_RANGE //for borgs and polly
|
|
|
|
return 0
|
|
|
|
/mob/living/proc/speech_was_spoken_into_radio(var/message_mode)
|
|
if (message_mode in headset_modes)
|
|
return TRUE
|
|
switch (message_mode)
|
|
if(MODE_HEADSET, MODE_SECURE_HEADSET, MODE_R_HAND, MODE_L_HAND, MODE_INTERCOM, MODE_BINARY)
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/mob/living/proc/radio(var/datum/speech/speech, var/message_mode)
|
|
switch(message_mode)
|
|
if(MODE_R_HAND)
|
|
say_testing(src, "/mob/living/radio() - MODE_R_HAND")
|
|
var/obj/item/I = get_held_item_by_index(GRASP_RIGHT_HAND)
|
|
if(I)
|
|
I.talk_into(speech)
|
|
return ITALICS | REDUCE_RANGE
|
|
if(MODE_L_HAND)
|
|
say_testing(src, "/mob/living/radio() - MODE_L_HAND")
|
|
var/obj/item/I = get_held_item_by_index(GRASP_LEFT_HAND)
|
|
if(I)
|
|
I.talk_into(speech)
|
|
return ITALICS | REDUCE_RANGE
|
|
if(MODE_INTERCOM)
|
|
say_testing(src, "/mob/living/radio() - MODE_INTERCOM")
|
|
for (var/obj/item/device/radio/intercom/I in view(1, null))
|
|
I.talk_into(speech)
|
|
return ITALICS | REDUCE_RANGE
|
|
if(MODE_BINARY)
|
|
say_testing(src, "/mob/living/radio() - MODE_BINARY")
|
|
if(binarycheck())
|
|
robot_talk(speech.message)
|
|
return ITALICS | REDUCE_RANGE //Does not return 0 since this is only reached by humans, not borgs or AIs.
|
|
if(MODE_WHISPER)
|
|
say_testing(src, "/mob/living/radio() - MODE_WHISPER")
|
|
whisper(speech.message, speech.language)
|
|
return NOPASS
|
|
return 0
|
|
|
|
/mob/living/lingcheck()
|
|
if(ischangeling(src) && !issilicon(src))
|
|
return 1
|
|
return 0
|
|
|
|
#define SPEAK_OVER_GENERAL_CULT_CHAT 0
|
|
#define SPEAK_OVER_CHANNEL_INTO_CULT_CHAT 1
|
|
#define HEAR_CULT_CHAT 2
|
|
|
|
/mob/living/cult_chat_check(var/setting = SPEAK_OVER_GENERAL_CULT_CHAT)
|
|
if (!mind)
|
|
return
|
|
if (occult_muted())
|
|
return
|
|
if (setting == SPEAK_OVER_GENERAL_CULT_CHAT) //overridden for constructs
|
|
return
|
|
|
|
var/datum/role/cultist/culto = iscultist(src)
|
|
if (culto)
|
|
if (setting == SPEAK_OVER_CHANNEL_INTO_CULT_CHAT)
|
|
var/turf/T = get_turf(src)
|
|
for (var/obj/structure/cult/spire/S in cult_spires)
|
|
if (isturf(S.loc) && S.z == T.z) // Spires need to not be concealed and on the same Z Level.
|
|
return 1
|
|
if (setting == HEAR_CULT_CHAT)
|
|
return 1
|
|
|
|
var/datum/faction/cult = find_active_faction_by_member(mind.GetRole(LEGACY_CULT))
|
|
if (cult)
|
|
if (setting == SPEAK_OVER_CHANNEL_INTO_CULT_CHAT)
|
|
if(universal_cult_chat == 1)
|
|
return 1
|
|
if (setting == HEAR_CULT_CHAT)
|
|
return 1
|
|
|
|
#undef SPEAK_OVER_GENERAL_CULT_CHAT
|
|
#undef SPEAK_OVER_CHANNEL_INTO_CULT_CHAT
|
|
#undef HEAR_CULT_CHAT
|
|
|
|
// Obsolete for any mob which uses a language.
|
|
/mob/living/say_quote()
|
|
if (stuttering)
|
|
return "stammers, [text]"
|
|
if (getBrainLoss() >= 60)
|
|
return "gibbers, [text]"
|
|
return ..()
|
|
|
|
// Use this when the mob speaks a given language.
|
|
/mob/proc/get_spoken_verb(var/msg)
|
|
return ""
|
|
|
|
/mob/living/get_spoken_verb(var/msg)
|
|
if (stuttering)
|
|
return "stammers"
|
|
if (getBrainLoss() >= 60)
|
|
return "gibbers"
|
|
return ..()
|
|
|
|
/atom/proc/send_speech_bubble(var/message,var/bubble_type, var/list/hearers)
|
|
//speech bubble
|
|
var/list/tracking_speech_bubble_recipients = list()
|
|
var/list/static_speech_bubble_recipients = list()
|
|
for(var/mob/M in hearers)
|
|
M.heard(src)
|
|
if(M.client)
|
|
if(src.invisibility > M.see_invisible) //You cannot see who's talking, so you only get a vague sense of where the sound originated from. This is mostly for Jaunt invocation.
|
|
static_speech_bubble_recipients.Add(M.client)
|
|
else
|
|
tracking_speech_bubble_recipients.Add(M.client)
|
|
spawn(0)
|
|
if(static_speech_bubble_recipients.len)
|
|
display_bubble_to_clientlist(image('icons/mob/talk.dmi', get_turf(src), "h[bubble_type][say_test(message)]",MOB_LAYER+1), static_speech_bubble_recipients)
|
|
if(tracking_speech_bubble_recipients.len)
|
|
display_bubble_to_clientlist(image('icons/mob/talk.dmi', get_holder_at_turf_level(src), "h[bubble_type][say_test(message)]",MOB_LAYER+1), tracking_speech_bubble_recipients)
|
|
|
|
/proc/display_bubble_to_clientlist(var/image/speech_bubble, var/clientlist, var/mob/living/source)
|
|
if (source)
|
|
speech_bubble.pixel_x = source.pixel_x
|
|
speech_bubble.pixel_y = source.pixel_y
|
|
speech_bubble.plane = ABOVE_LIGHTING_PLANE
|
|
speech_bubble.appearance_flags = RESET_COLOR
|
|
flick_overlay(speech_bubble, clientlist, 30)
|
|
|
|
/mob/proc/addSpeechBubble(image/speech_bubble)
|
|
if(client)
|
|
client.images += speech_bubble
|
|
spawn(30)
|
|
if(client)
|
|
client.images -= speech_bubble
|
|
|
|
/mob/living/whisper(message as text)
|
|
if(!IsVocal())
|
|
to_chat(src, "<span class='warning'>You can't speak while silenced.</span>")
|
|
return
|
|
|
|
#ifdef SAY_DEBUG
|
|
var/oldmsg = message
|
|
#endif
|
|
|
|
if (isDead() || (stat == UNCONSCIOUS && health > 0))
|
|
return
|
|
|
|
if(say_disabled) //This is here to try to identify lag problems
|
|
to_chat(usr, "<span class='danger'>Speech is currently admin-disabled.</span>")
|
|
return
|
|
|
|
var/datum/speech/speech = create_speech(message)
|
|
speech.language = parse_language(speech.message)
|
|
speech.mode = SPEECH_MODE_WHISPER
|
|
speech.message_classes.Add("whisper")
|
|
|
|
if(istype(speech.language))
|
|
speech.message = copytext(speech.message,2+length(speech.language.key))
|
|
else
|
|
if(!isnull(speech.language))
|
|
var/n = speech.language
|
|
speech.message = copytext(speech.message,1+length(n))
|
|
say_testing(src, "We tried to speak a language we don't have length = [length(n)], oldmsg = [oldmsg] parsed message = [speech.message]")
|
|
speech.language = null
|
|
speech.language = get_default_language()
|
|
|
|
speech.message = trim(speech.message)
|
|
|
|
if(!can_speak(message))
|
|
return
|
|
|
|
speech.message = "[message]"
|
|
|
|
if (client && client.prefs.muted & MUTE_IC)
|
|
to_chat(src, "<span class='danger'>You cannot whisper (muted).</span>")
|
|
return
|
|
|
|
|
|
var/whispers = "whispers"
|
|
var/critical = InCritical()
|
|
|
|
log_whisper("[key_name(src)] ([formatLocation(src)]): [message]")
|
|
if(!isDead())
|
|
treat_speech(speech)
|
|
if(!speech.message)
|
|
qdel(speech)
|
|
return
|
|
|
|
// If whispering your last words, limit the whisper based on how close you are to death.
|
|
if(critical && !said_last_words)
|
|
var/health_diff = round(-config.health_threshold_dead + health)
|
|
// If we cut our message short, abruptly end it with a-..
|
|
var/message_len = length(speech.message)
|
|
speech.message = copytext(speech.message, 1, health_diff) + "[message_len > health_diff ? "-.." : "..."]"
|
|
speech.message = Ellipsis(speech.message, 10, 1)
|
|
speech.mode= SPEECH_MODE_FINAL
|
|
whispers = "whispers with their final breath"
|
|
said_last_words = src.stat
|
|
if(!isDead())
|
|
treat_speech(speech)
|
|
if(!speech.message)
|
|
qdel(speech)
|
|
return
|
|
|
|
var/listeners = get_hearers_in_view(1, src) | observers
|
|
var/eavesdroppers = get_hearers_in_view(2, src) - listeners
|
|
var/watchers = hearers(5, src) - listeners - eavesdroppers
|
|
var/rendered = render_speech(speech)
|
|
for (var/atom/movable/listener in listeners)
|
|
listener.Hear(speech, rendered)
|
|
|
|
speech.message = stars(speech.message)
|
|
rendered = render_speech(speech)
|
|
|
|
for (var/atom/movable/eavesdropper in eavesdroppers)
|
|
eavesdropper.Hear(speech, rendered)
|
|
|
|
rendered = "<span class='game say'><span class='name'>[src.name]</span> [whispers] something.</span>"
|
|
|
|
for (var/mob/watcher in watchers)
|
|
watcher.show_message(rendered, 2)
|
|
|
|
if (said_last_words) // dying words
|
|
succumb_proc(0)
|
|
|
|
qdel(speech)
|
|
|
|
/obj/effect/speech_bubble
|
|
var/mob/parent
|
|
|
|
//Muffles a message for when muzzled.
|
|
/proc/muffle(var/message)
|
|
var/muffle_syllables = list("mh","mph","mm","mgh","mg")
|
|
var/unmuffled = list(" ", "-", ",", ".", "!", "?")
|
|
var/output = ""
|
|
var/i = 1
|
|
var/current_char
|
|
while(i <= length(message))
|
|
current_char = message[i]
|
|
if(current_char in unmuffled)
|
|
output += current_char
|
|
i += 1
|
|
else
|
|
var/length_to_add = 1
|
|
var/allcaps = uppertext(message[i]) == message[i]
|
|
while((i + length_to_add <= length(message)) && (length_to_add < 3))
|
|
if(message[i + length_to_add] in unmuffled)
|
|
break
|
|
allcaps &= uppertext(message[i + length_to_add]) == message[i + length_to_add]
|
|
length_to_add += 1
|
|
i += length_to_add
|
|
if(allcaps)
|
|
output += uppertext(pick(muffle_syllables))
|
|
else
|
|
output += pick(muffle_syllables)
|
|
return output
|