var/list/department_radio_keys = list(
":r" = "right ear", ".r" = "right ear",
":l" = "left ear", ".l" = "left ear",
":i" = "intercom", ".i" = "intercom",
":h" = "department", ".h" = "department",
":+" = "special", ".+" = "special", //activate radio-specific special functions
":c" = "Command", ".c" = "Command",
":n" = "Science", ".n" = "Science",
":m" = "Medical", ".m" = "Medical",
":e" = "Engineering", ".e" = "Engineering",
":k" = "Response Team", ".k" = "Response Team",
":s" = "Security", ".s" = "Security",
":w" = "whisper", ".w" = "whisper",
":t" = "Mercenary", ".t" = "Mercenary",
":x" = "Raider", ".x" = "Raider",
":u" = "Supply", ".u" = "Supply",
":v" = "Service", ".v" = "Service",
":p" = "AI Private", ".p" = "AI Private",
":y" = "Explorer", ".y" = "Explorer", //VOREStation Edit //CHOMP keep explo
":a" = "Talon", ".a" = "Talon", //VOREStation Add, //CHOMP keep explo
":g" = "Casino", ".g" = "Casino",
":R" = "right ear", ".R" = "right ear",
":L" = "left ear", ".L" = "left ear",
":I" = "intercom", ".I" = "intercom",
":H" = "department", ".H" = "department",
":C" = "Command", ".C" = "Command",
":N" = "Science", ".N" = "Science",
":M" = "Medical", ".M" = "Medical",
":E" = "Engineering", ".E" = "Engineering",
":k" = "Response Team", ".k" = "Response Team",
":S" = "Security", ".S" = "Security",
":W" = "whisper", ".W" = "whisper",
":T" = "Mercenary", ".T" = "Mercenary",
":X" = "Raider", ".X" = "Raider",
":U" = "Supply", ".U" = "Supply",
":V" = "Service", ".V" = "Service",
":P" = "AI Private", ".P" = "AI Private",
":Y" = "Explorer", ".Y" = "Explorer", //CHOMP keep explo
":A" = "Talon", ".A" = "Talon", //VOREStation Add, //CHOMP keep explo
":G" = "Casino", ".G" = "Casino",
// Cyrillic characters on the same keys on the Russian QWERTY (phonetic) layout
":к" = "right ear", ".к" = "right ear",
":д" = "left ear", ".д" = "left ear",
":ш" = "intercom", ".ш" = "intercom",
":р" = "department", ".р" = "department",
":+" = "special", ".+" = "special", //activate radio-specific special functions
":с" = "Command", ".с" = "Command",
":т" = "Science", ".т" = "Science",
":ь" = "Medical", ".ь" = "Medical",
":у" = "Engineering", ".у" = "Engineering",
":л" = "Response Team", ".л" = "Response Team",
":ы" = "Security", ".ы" = "Security",
":ц" = "whisper", ".ц" = "whisper",
":е" = "Mercenary", ".е" = "Mercenary",
":ч" = "Raider", ".ч" = "Raider",
":г" = "Supply", ".г" = "Supply",
":м" = "Service", ".м" = "Service",
":з" = "AI Private", ".з" = "AI Private",
":н" = "Explorer", ".н" = "Explorer", //CHOMP keep explo
":ф" = "Talon", ".ф" = "Talon", //VOREStation Add //CHOMP keep explo
":п" = "Casino", ".п" = "Casino",
)
var/list/channel_to_radio_key = new
/proc/get_radio_key_from_channel(var/channel)
var/key = channel_to_radio_key[channel]
if(!key)
for(var/radio_key in department_radio_keys)
if(department_radio_keys[radio_key] == channel)
key = radio_key
break
if(!key)
key = ""
channel_to_radio_key[channel] = key
return key
/mob/living/proc/binarycheck()
return FALSE
/mob/proc/get_default_language()
return null
/mob/living/get_default_language()
return default_language
//Takes a list of the form list(message, verb, whispering) and modifies it as needed
//Returns 1 if a speech problem was applied, 0 otherwise
/mob/living/proc/handle_speech_problems(var/list/message_data)
var/list/message_pieces = message_data[1]
var/verb = message_data[2]
var/whispering = message_data[3]
. = 0
// Technically this rerolls the verb for as many say pieces as there are. _shrug_
for(var/datum/multilingual_say_piece/S in message_pieces)
if(S.speaking && (S.speaking.flags & NO_STUTTER || S.speaking.flags & SIGNLANG))
continue
if((HULK in mutations) && health >= 25 && length(S.message))
S.message = "[uppertext(S.message)]!!!"
verb = pick("yells","roars","hollers")
whispering = 0
. = 1
if(slurring)
S.message = slur(S.message)
verb = pick("slobbers","slurs")
. = 1
if(stuttering)
S.message = stutter(S.message)
verb = pick("stammers","stutters")
. = 1
//VOREStation Edit Start
if(muffled)
verb = pick("muffles")
whispering = 1
. = 1
//VOREStation Edit End
//YW Edit start
if(wingdings)
S.message = span("wingdings",(S.message))
. = 1
//YW Edit End
message_data[1] = message_pieces
message_data[2] = verb
message_data[3] = whispering
/mob/living/proc/handle_message_mode(message_mode, list/message_pieces, verb, used_radios)
if(message_mode == "intercom")
for(var/obj/item/device/radio/intercom/I in view(1, null))
I.talk_into(src, message_pieces, verb)
used_radios += I
return 0
/mob/living/proc/handle_speech_sound()
var/list/returns[2]
returns[1] = null
returns[2] = null
return returns
/mob/living/proc/get_speech_ending(verb, var/ending)
if(ending == "!")
return pick("exclaims","shouts","yells")
if(ending == "?")
return "asks"
return verb
/mob/living/say(var/message, var/datum/language/speaking = null, var/whispering = 0)
//If you're muted for IC chat
if(client)
if(message)
client.handle_spam_prevention(MUTE_IC)
if((client.prefs.muted & MUTE_IC) || say_disabled)
to_chat(src, "You cannot speak in IC (Muted).")
return
//Redirect to say_dead if talker is dead
if(stat)
if(stat == DEAD && !forbid_seeing_deadchat)
return say_dead(message)
return
//VOREStation Addition Start
if(forced_psay)
psay(message)
return
if(autowhisper)
whispering = 1
//VOREStation Addition End
//Parse the mode
var/message_mode = parse_message_mode(message, "headset")
//Maybe they are using say/whisper to do a quick emote, so do those
switch(copytext(message, 1, 2))
if("*") return emote(copytext(message, 2))
if("^") return custom_emote(1, copytext(message, 2))
//Parse the radio code and consume it
if(message_mode)
if(message_mode == "headset")
message = copytext(message, 2) //it would be really nice if the parse procs could do this for us.
else if(message_mode == "whisper")
whispering = 1
message_mode = null
message = copytext(message, 3)
else
message = copytext(message, 3)
//Clean up any remaining space on the left
message = trim_left(message)
// VOREStation Edit - Reflect messages as needed, no sanitizing because parse_languages will handle it for us
if(reflect_if_needed(message, src))
return
// VOREStation Edit End
//Parse the language code and consume it
var/list/message_pieces = parse_languages(message)
if(istype(message_pieces, /datum/multilingual_say_piece)) // Little quark for dealing with hivemind/signlang languages.
var/datum/multilingual_say_piece/S = message_pieces // Yay for BYOND's hilariously broken typecasting for allowing us to do this.
S.speaking.broadcast(src, S.message)
return 1
if(!LAZYLEN(message_pieces))
log_runtime(EXCEPTION("Message failed to generate pieces. [message] - [json_encode(message_pieces)]"))
return 0
// If you're muzzled, you can only speak sign language
// However, sign language is handled above.
if(is_muzzled())
to_chat(src, "You're muzzled and cannot speak!")
return
//Whisper vars
var/w_scramble_range = 5 //The range at which you get ***as*th**wi****
var/w_adverb //An adverb prepended to the verb in whispers
var/w_not_heard //The message for people in watching range
var/datum/multilingual_say_piece/first_piece = message_pieces[1]
var/verb = ""
//Handle language-specific verbs and adverb setup if necessary
if(!whispering) //Just doing normal 'say' (for now, may change below)
verb = say_quote(message, first_piece.speaking)
else if(whispering && first_piece.speaking.whisper_verb) //Language has defined whisper verb
verb = first_piece.speaking.whisper_verb
w_not_heard = "[verb] something"
else //Whispering but language has no whisper verb, use say verb
w_adverb = pick("quietly", "softly")
verb = first_piece.speaking.speech_verb
w_not_heard = "[first_piece.speaking.speech_verb] something [w_adverb]"
//For speech disorders (hulk, slurring, stuttering)
var/list/message_data = list(message_pieces, verb, whispering)
if(handle_speech_problems(message_data))
message_pieces = message_data[1]
whispering = message_data[3]
if(verb != message_data[2]) //They changed our verb
if(whispering)
w_adverb = pick("quietly", "softly")
verb = message_data[2]
//Whisper may have adverbs, add those if one was set
if(w_adverb)
verb = "[verb] [w_adverb]"
//If something nulled or emptied the message, forget it
if(!LAZYLEN(message_pieces))
return 0
//Radio message handling
var/list/used_radios = list()
if(handle_message_mode(message_mode, message_pieces, verb, used_radios, whispering))
return 1
//For languages with actual speech sounds
var/list/handle_v = handle_speech_sound()
var/sound/speech_sound = handle_v[1]
var/sound_vol = handle_v[2]
//Default range and italics, may be overridden past here
var/message_range = world.view
var/italics = 0
var/do_sound = TRUE
if(!voice_sounds_list || !voice_sounds_list.len)
do_sound = FALSE
//Speaking into radios
if(used_radios.len)
italics = 1
message_range = 1
if(first_piece.speaking)
message_range = first_piece.speaking.get_talkinto_msg_range(message)
var/msg
if(!first_piece.speaking || !(first_piece.speaking.flags & NO_TALK_MSG))
msg = "[src] talks into [used_radios[1]]"
if(msg)
for(var/mob/living/M in hearers(5, src) - src)
M.show_message(msg)
if(speech_sound)
sound_vol *= 0.5
//Set vars if we're still whispering by this point
if(whispering)
italics = 1
message_range = 1
sound_vol *= 0.5
//VOREStation edit - allows for custom say verbs, overriding all other say-verb types- e.g. "says loudly" instead of "shouts"
//You'll still stammer if injured or slur if drunk, but it won't have those specific words
var/ending = copytext(message, length(message))
if(custom_whisper && whispering)
verb = "[custom_whisper]"
else if(custom_exclaim && ending=="!")
verb = "[custom_exclaim]"
else if(custom_ask && ending=="?")
verb = "[custom_ask]"
else if(custom_say)
verb = "[custom_say]"
//VOREStation edit ends
//Handle nonverbal languages here
for(var/datum/multilingual_say_piece/S in message_pieces)
if((S.speaking.flags & NONVERBAL) || (S.speaking.flags & INAUDIBLE))
custom_emote(1, "[pick(S.speaking.signlang_verb)].")
do_sound = FALSE
//These will contain the main receivers of the message
var/list/listening = list()
var/list/listening_obj = list()
//Atmosphere calculations (speaker's side only, for now)
var/turf/T = get_turf(src)
if(T)
//Air is too thin to carry sound at all, contact speech only
var/datum/gas_mixture/environment = T.return_air()
var/pressure = environment ? environment.return_pressure() : 0
if(pressure < SOUND_MINIMUM_PRESSURE)
message_range = 1
//Air is nearing minimum levels, make text italics as a hint, and muffle sound
if(pressure < ONE_ATMOSPHERE * 0.4)
italics = 1
sound_vol *= 0.5
//Obtain the mobs and objects in the message range
var/list/results = get_mobs_and_objs_in_view_fast(T, world.view, remote_ghosts = client ? TRUE : FALSE)
listening = results["mobs"]
listening_obj = results["objs"]
else
return 1 //If we're in nullspace, then forget it.
//Remember the speech images so we can remove them later and they can get GC'd
var/list/images_to_clients = list()
//The 'post-say' static speech bubble
var/speech_bubble_test = say_test(message)
//var/image/speech_bubble = image('icons/mob/talk_vr.dmi',src,"h[speech_bubble_test]") //VOREStation Edit. Commented this out in case we need to reenable.
var/speech_type = custom_speech_bubble
if(!speech_type || speech_type == "default")
speech_type = speech_bubble_appearance()
var/image/speech_bubble = generate_speech_bubble(src, "[speech_type][speech_bubble_test]")
var/sb_alpha = 255
var/atom/loc_before_turf = src
//VOREStation Add
if(isbelly(loc))
speech_bubble.pixel_y = -13 //teehee
//VOREStation Add End
while(loc_before_turf && !isturf(loc_before_turf.loc))
loc_before_turf = loc_before_turf.loc
sb_alpha -= 50
if(sb_alpha < 0)
break
speech_bubble.loc = loc_before_turf
speech_bubble.alpha = CLAMP(sb_alpha, 0, 255)
images_to_clients[speech_bubble] = list()
//Main 'say' and 'whisper' message delivery
for(var/mob/M in listening)
spawn(0) //Using spawns to queue all the messages for AFTER this proc is done, and stop runtimes
if(M && src) //If we still exist, when the spawn processes
//VOREStation Add - Ghosts don't hear whispers
if(whispering && isobserver(M) && (!M.is_preference_enabled(/datum/client_preference/ghost_see_whisubtle) || \
(!(is_preference_enabled(/datum/client_preference/whisubtle_vis) || (isbelly(M.loc) && src == M.loc:owner)) && !M.client?.holder))) // CHOMPedit
M.show_message("[src.name] [w_not_heard].", 2)
return
//VOREStation Add End
var/dst = get_dist(get_turf(M),get_turf(src))
var/runechat_enabled = M.client?.is_preference_enabled(/datum/client_preference/runechat_mob)
if(dst <= message_range || (M.stat == DEAD && !forbid_seeing_deadchat)) //Inside normal message range, or dead with ears (handled in the view proc)
if(M.hear_say(message_pieces, verb, italics, src, speech_sound, sound_vol))
if(M.client && !runechat_enabled)
var/image/I1 = listening[M] || speech_bubble
images_to_clients[I1] |= M.client
M << I1
if(whispering && !isobserver(M)) //Don't even bother with these unless whispering
if(dst > message_range && dst <= w_scramble_range) //Inside whisper scramble range
if(M.hear_say(stars_all(message_pieces), verb, italics, src, speech_sound, sound_vol*0.2))
if(M.client && !runechat_enabled)
var/image/I2 = listening[M] || speech_bubble
images_to_clients[I2] |= M.client
M << I2
if(dst > w_scramble_range && dst <= world.view) //Inside whisper 'visible' range
M.show_message("[name] [w_not_heard].", 2)
//Object message delivery
for(var/obj/O in listening_obj)
spawn(0)
if(O && src) //If we still exist, when the spawn processes
var/dst = get_dist(get_turf(O),get_turf(src))
if(dst <= message_range)
O.hear_talk(src, message_pieces, verb)
//Remove all those images. At least it's just ONE spawn this time.
spawn(30)
for(var/image/I as anything in images_to_clients)
var/list/clients_from_image = images_to_clients[I]
for(var/client/C as anything in clients_from_image)
if(C) //Could have disconnected after message sent, before removing bubble.
C.images -= I
qdel(I)
/* CHOMPEdit - Not needed if you set your defaults right
var/ourfreq = null
if(voice_freq > 0 )
ourfreq = voice_freq
*/
//Log the message to file
if(message_mode)
message = "([message_mode == "headset" ? "Common" : capitalize(message_mode)]) [message]" //Adds radio keys used if available
if(whispering)
if(do_sound && message)
playsound(T, pick(voice_sounds_list), 25, TRUE, extrarange = -6, falloff = 1 , is_global = TRUE, frequency = voice_freq, ignore_walls = FALSE, preference = /datum/client_preference/say_sounds) //CHOMPEdit - Use say sound prefs
log_whisper(message, src)
else
if(do_sound && message)
playsound(T, pick(voice_sounds_list), 75, TRUE, falloff = 1 , is_global = TRUE, frequency = voice_freq, ignore_walls = FALSE, preference = /datum/client_preference/say_sounds) //CHOMPEdit - tiny fix
log_say(message, src)
return 1
/mob/living/proc/say_signlang(var/message, var/verb="gestures", var/verb_understood="gestures", var/datum/language/language, var/type = 1)
var/turf/T = get_turf(src)
//We're in something, gesture to people inside the same thing
if(loc != T && !istype(loc, /obj/item/weapon/holder)) //CHOMPEdit - Partially fixes sign language while being held.
for(var/mob/M in loc)
M.hear_signlang(message, verb, verb_understood, language, src, type)
//We're on a turf, gesture to visible as if we were a normal language
else
var/low_range = FALSE
if(T && type == 2) // type 2 is audible signlang. yes. sue me.
//Air is too thin to carry sound at all, contact speech only
var/datum/gas_mixture/environment = T.return_air()
var/pressure = environment ? environment.return_pressure() : 0
if(pressure < SOUND_MINIMUM_PRESSURE)
low_range = TRUE
var/list/potentials = get_mobs_and_objs_in_view_fast(T, world.view)
var/list/mobs = potentials["mobs"]
for(var/mob/M as anything in mobs)
if(low_range && !(M in range(1, src)))
continue
M.hear_signlang(message, verb, verb_understood, language, src, type)
var/list/objs = potentials["objs"]
for(var/obj/O as anything in objs)
if(low_range && !(O in range(1, src)))
continue
O.hear_signlang(message, verb, language, src)
return 1
/obj/effect/speech_bubble
var/mob/parent
/mob/proc/GetVoice()
return name
/mob/proc/speech_bubble_appearance()
return "normal"