From ea42ee23034ef841302aa49742ebebfb0198242a Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sat, 29 May 2021 01:38:36 -0400 Subject: [PATCH] Port Runechat --- code/__defines/_planes+layers.dm | 2 + code/_helpers/time.dm | 2 + code/_helpers/unsorted.dm | 16 + code/datums/chat_message.dm | 293 ++++++++++++++++++ code/game/atoms.dm | 17 +- code/game/world.dm | 1 + code/modules/admin/admin_verbs.dm | 3 +- code/modules/client/client defines.dm | 3 + .../preference_setup/global/setting_datums.dm | 26 ++ code/modules/emotes/emote_define.dm | 17 +- code/modules/emotes/emote_mob.dm | 8 +- code/modules/mob/hear_say.dm | 1 + code/modules/mob/living/say.dm | 29 +- code/modules/mob/mob.dm | 13 +- .../ventcrawl/ventcrawl_atmospherics.dm | 11 +- interface/skin.dmf | 2 +- vorestation.dme | 1 + 17 files changed, 414 insertions(+), 31 deletions(-) create mode 100644 code/datums/chat_message.dm diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm index 96d65c6f495..111349d887c 100644 --- a/code/__defines/_planes+layers.dm +++ b/code/__defines/_planes+layers.dm @@ -93,6 +93,8 @@ What is the naming convention for planes or layers? #define BELOW_MOB_LAYER 3.9 // Should be converted to plane swaps #define ABOVE_MOB_LAYER 4.1 // Should be converted to plane swaps +#define ABOVE_MOB_PLANE -24 + // Invisible things plane #define CLOAKED_PLANE -15 diff --git a/code/_helpers/time.dm b/code/_helpers/time.dm index 6e63dabb45a..b75aeb77e38 100644 --- a/code/_helpers/time.dm +++ b/code/_helpers/time.dm @@ -20,6 +20,8 @@ #define TICKS2DS(T) ((T) TICKS) // Convert ticks to deciseconds #define DS2NEARESTTICK(DS) TICKS2DS(-round(-(DS2TICKS(DS)))) +var/world_startup_time + /proc/get_game_time() var/global/time_offset = 0 var/global/last_time = 0 diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm index 5701521ec52..fe2b51195ab 100644 --- a/code/_helpers/unsorted.dm +++ b/code/_helpers/unsorted.dm @@ -1691,3 +1691,19 @@ GLOBAL_REAL_VAR(list/stack_trace_storage) return "CLIENT: [D]" else return "Unknown data type: [D]" + +/* + is_holder_of(): Returns 1 if A is a holder of B, meaning, A is B.loc or B.loc.loc or B.loc.loc.loc etc. + This is essentially the same as calling (locate(B) in A), but a little clearer as to what you're doing, and locate() has been known to bug out or be extremely slow in the past. +*/ +/proc/is_holder_of(const/atom/movable/A, const/atom/movable/B) + if(istype(A, /turf) || istype(B, /turf)) //Clicking on turfs is a common thing and turfs are also not /atom/movable, so it was causing the assertion to fail. + return 0 + ASSERT(istype(A) && istype(B)) + var/atom/O = B + while(O && !isturf(O)) + if(O == A) + return 1 + O = O.loc + return 0 + \ No newline at end of file diff --git a/code/datums/chat_message.dm b/code/datums/chat_message.dm new file mode 100644 index 00000000000..37fdca02f72 --- /dev/null +++ b/code/datums/chat_message.dm @@ -0,0 +1,293 @@ +#define CHAT_MESSAGE_SPAWN_TIME 0.2 SECONDS +#define CHAT_MESSAGE_LIFESPAN 5 SECONDS +#define CHAT_MESSAGE_EOL_FADE 0.7 SECONDS +#define CHAT_MESSAGE_EXP_DECAY 0.8 // Messages decay at pow(factor, idx in stack) +#define CHAT_MESSAGE_HEIGHT_DECAY 0.7 // Increase message decay based on the height of the message +#define CHAT_MESSAGE_APPROX_LHEIGHT 11 // Approximate height in pixels of an 'average' line, used for height decay +#define CHAT_MESSAGE_WIDTH 96 // pixels +#define CHAT_MESSAGE_NORM_LENGTH 68 // characters +#define CHAT_MESSAGE_EXT_LENGTH 150 // characters +#define CHAT_MESSAGE_MOB 1 +#define CHAT_MESSAGE_OBJ 2 +#define WXH_TO_HEIGHT(x) text2num(copytext((x), findtextEx((x), "x") + 1)) // thanks lummox + +/** + * # Chat Message Overlay + * + * Datum for generating a message overlay on the map + * Ported from TGStation; https://github.com/tgstation/tgstation/pull/50608/, author: bobbahbrown + */ + +// Cached runechat icon +var/runechat_icon = null + +/datum/chatmessage + /// The visual element of the chat messsage + var/image/message + /// The location in which the message is appearing + var/atom/message_loc + /// The client who heard this message + var/client/owned_by + /// Contains the scheduled destruction time + var/scheduled_destruction + /// Contains the approximate amount of lines for height decay + var/approx_lines + +/** + * Constructs a chat message overlay + * + * Arguments: + * * text - The text content of the overlay + * * target - The target atom to display the overlay at + * * owner - The mob that owns this overlay, only this mob will be able to view it + * * extra_classes - Extra classes to apply to the span that holds the text + * * lifespan - The lifespan of the message in deciseconds + */ +/datum/chatmessage/New(text, atom/target, mob/owner, list/extra_classes = null, lifespan = CHAT_MESSAGE_LIFESPAN) + . = ..() + if (!istype(target)) + CRASH("Invalid target given for chatmessage") + if(!istype(owner) || QDELETED(owner) || !owner.client) + stack_trace("/datum/chatmessage created with [isnull(owner) ? "null" : "invalid"] mob owner") + qdel(src) + return + generate_image(text, target, owner, extra_classes, lifespan) + +/datum/chatmessage/Destroy() + if (owned_by) + owned_by.seen_messages.Remove(src) + owned_by.images.Remove(message) + UnregisterSignal(owned_by, COMSIG_PARENT_QDELETING) + owned_by = null + message_loc = null + message = null + return ..() + +/** + * Generates a chat message image representation + * + * Arguments: + * * text - The text content of the overlay + * * target - The target atom to display the overlay at + * * owner - The mob that owns this overlay, only this mob will be able to view it + * * extra_classes - Extra classes to apply to the span that holds the text + * * lifespan - The lifespan of the message in deciseconds + */ +/datum/chatmessage/proc/generate_image(text, atom/target, mob/owner, list/extra_classes, lifespan) + set waitfor = FALSE + // Register client who owns this message + owned_by = owner.client + RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, .proc/qdel_self) + + // Clip message + var/maxlen = owned_by.is_preference_enabled(/datum/client_preference/runechat_long_messages) ? CHAT_MESSAGE_EXT_LENGTH : CHAT_MESSAGE_NORM_LENGTH + if (length_char(text) > maxlen) + text = copytext_char(text, 1, maxlen + 1) + "..." // BYOND index moment + + // Calculate target color if not already present + if (!target.chat_color || target.chat_color_name != target.name) + target.chat_color = colorize_string(target.name) + target.chat_color_darkened = colorize_string(target.name, 0.85, 0.85) + target.chat_color_name = target.name + + // Get rid of any URL schemes that might cause BYOND to automatically wrap something in an anchor tag + var/static/regex/url_scheme = new(@"[A-Za-z][A-Za-z0-9+-\.]*:\/\/", "g") + text = replacetext(text, url_scheme, "") + + // Reject whitespace + var/static/regex/whitespace = new(@"^\s*$") + if (whitespace.Find(text)) + qdel(src) + return + + // Non mobs speakers can be small + if (!ismob(target)) + extra_classes |= "small" + + // If we heard our name, it's important + var/list/names = splittext(owner.name, " ") + for (var/word in names) + text = replacetext(text, word, "[word]") + + // Append radio icon if comes from a radio + if (extra_classes.Find("spoken_into_radio")) + if (!runechat_icon) + var/image/r_icon = image('icons/UI_Icons/chat/chat_icons.dmi', icon_state = "radio") + runechat_icon = "\icon[r_icon] " + text = runechat_icon + text + + // We dim italicized text to make it more distinguishable from regular text + var/tgt_color = extra_classes.Find("italics") ? target.chat_color_darkened : target.chat_color + // Approximate text height + // Note we have to replace HTML encoded metacharacters otherwise MeasureText will return a zero height + // BYOND Bug #2563917 + // Construct text + var/static/regex/html_metachars = new(@"&[A-Za-z]{1,7};", "g") + var/complete_text = "[text]" + var/mheight = WXH_TO_HEIGHT(owned_by.MeasureText(replacetext(complete_text, html_metachars, "m"), null, CHAT_MESSAGE_WIDTH)) + approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT) + + // Translate any existing messages upwards, apply exponential decay factors to timers + message_loc = target + if (owned_by.seen_messages) + var/idx = 1 + var/combined_height = approx_lines + for(var/msg in owned_by.seen_messages) + var/datum/chatmessage/m = msg + animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME) + combined_height += m.approx_lines + var/sched_remaining = m.scheduled_destruction - world.time + if (sched_remaining > CHAT_MESSAGE_SPAWN_TIME) + var/remaining_time = (sched_remaining) * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height) + m.scheduled_destruction = world.time + remaining_time + spawn(remaining_time) + m.end_of_life() + + // Build message image + message = image(loc = message_loc, layer = ABOVE_MOB_LAYER) + message.plane = PLANE_LIGHTING_ABOVE + message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART + message.alpha = 0 + message.pixel_y = owner.bound_height * 0.95 + message.maptext_width = CHAT_MESSAGE_WIDTH + message.maptext_height = mheight + message.maptext_x = (CHAT_MESSAGE_WIDTH - owner.bound_width) * -0.5 + message.maptext = complete_text + + if (is_holder_of(owner, target)) // Special case, holding an atom speaking (pAI, recorder...) + message.plane = PLANE_PLAYER_HUD_ABOVE + + // View the message + owned_by.seen_messages.Add(src) + owned_by.images += message + animate(message, alpha = 255, time = CHAT_MESSAGE_SPAWN_TIME) + + // Prepare for destruction + scheduled_destruction = world.time + (lifespan - CHAT_MESSAGE_EOL_FADE) + spawn(lifespan - CHAT_MESSAGE_EOL_FADE) + end_of_life() + +/** + * Applies final animations to overlay CHAT_MESSAGE_EOL_FADE deciseconds prior to message deletion + */ +/datum/chatmessage/proc/end_of_life(fadetime = CHAT_MESSAGE_EOL_FADE) + if (gc_destroyed) + return + animate(message, alpha = 0, time = fadetime, flags = ANIMATION_PARALLEL) + spawn(fadetime) + qdel(src) + +/** + * Creates a message overlay at a defined location for a given speaker + * + * Arguments: + * * speaker - The atom who is saying this message + * * message - The text content of the message + * * italics - Decides if this should be small or not, as generally italics text are for whisper/radio overhear + * * existing_extra_classes - Additional classes to add to the message + */ +/mob/proc/create_chat_message(atom/movable/speaker, message, italics, list/existing_extra_classes, audible = TRUE) + if(!client) + return + + // Doesn't want to hear + if(ismob(speaker) && !client.is_preference_enabled(/datum/client_preference/runechat_mob)) + return + else if(isobj(speaker) && !client.is_preference_enabled(/datum/client_preference/runechat_obj)) + return + + // Incapable of receiving + if((audible && is_deaf()) || (!audible && is_blind())) + return + + // Check for virtual speakers (aka hearing a message through a radio) + if (existing_extra_classes.Find("radio")) + return + + /* Not currently necessary + message = strip_html_properly(message) + if(!message) + return + */ + + var/list/extra_classes = list() + extra_classes += existing_extra_classes + + if (italics) + extra_classes |= "italics" + + if (client.is_preference_enabled(/datum/client_preference/runechat_border)) + extra_classes |= "black_outline" + + var/dist = get_dist(src, speaker) + switch (dist) + if (4 to 5) + extra_classes |= "small" + if (5 to 16) + extra_classes |= "very_small" + + // Display visual above source + new /datum/chatmessage(message, speaker, src, extra_classes) + +// Tweak these defines to change the available color ranges +#define CM_COLOR_SAT_MIN 0.6 +#define CM_COLOR_SAT_MAX 0.95 +#define CM_COLOR_LUM_MIN 0.70 +#define CM_COLOR_LUM_MAX 0.90 + +/** + * Gets a color for a name, will return the same color for a given string consistently within a round.atom + * + * Note that this proc aims to produce pastel-ish colors using the HSL colorspace. These seem to be favorable for displaying on the map. + * + * Arguments: + * * name - The name to generate a color for + * * sat_shift - A value between 0 and 1 that will be multiplied against the saturation + * * lum_shift - A value between 0 and 1 that will be multiplied against the luminescence + */ +/datum/chatmessage/proc/colorize_string(name, sat_shift = 1, lum_shift = 1) + // seed to help randomness + var/static/rseed = rand(1,26) + + // get hsl using the selected 6 characters of the md5 hash + var/hash = copytext(md5(name + "[world_startup_time]"), rseed, rseed + 6) + var/h = hex2num(copytext(hash, 1, 3)) * (360 / 255) + var/s = (hex2num(copytext(hash, 3, 5)) >> 2) * ((CM_COLOR_SAT_MAX - CM_COLOR_SAT_MIN) / 63) + CM_COLOR_SAT_MIN + var/l = (hex2num(copytext(hash, 5, 7)) >> 2) * ((CM_COLOR_LUM_MAX - CM_COLOR_LUM_MIN) / 63) + CM_COLOR_LUM_MIN + + // adjust for shifts + s *= clamp(sat_shift, 0, 1) + l *= clamp(lum_shift, 0, 1) + + // convert to rgba + var/h_int = round(h/60) // mapping each section of H to 60 degree sections + var/c = (1 - abs(2 * l - 1)) * s + var/x = c * (1 - abs((h / 60) % 2 - 1)) + var/m = l - c * 0.5 + x = (x + m) * 255 + c = (c + m) * 255 + m *= 255 + switch(h_int) + if(0) + return rgb(c,x,m) + if(1) + return rgb(x,c,m) + if(2) + return rgb(m,c,x) + if(3) + return rgb(m,x,c) + if(4) + return rgb(x,m,c) + if(5) + return rgb(c,m,x) + +/atom/proc/runechat_message(message, range = world.view, italics, list/classes = list(), audible = TRUE) + var/list/hear = get_mobs_and_objs_in_view_fast(get_turf(src), range, remote_ghosts = FALSE) + + var/list/hearing_mobs = hear["mobs"] + + for(var/mob in hearing_mobs) + var/mob/M = mob + if(!M.client) + continue + M.create_chat_message(src, message, italics, classes, audible) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 46f001c8e1b..f4117d37e84 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -34,6 +34,15 @@ // Track if we are already had initialize() called to prevent double-initialization. var/initialized = FALSE + /// Last name used to calculate a color for the chatmessage overlays + var/chat_color_name + /// Last color calculated for the the chatmessage overlays + var/chat_color + /// A luminescence-shifted value of the last color calculated for chatmessage overlays + var/chat_color_darkened + /// The chat color var, without alpha. + var/chat_color_hover + /atom/New(loc, ...) // Don't call ..() unless /datum/New() ever exists @@ -490,7 +499,7 @@ // Use for objects performing visible actions // message is output to anyone who can see, e.g. "The [src] does something!" // blind_message (optional) is what blind people will hear e.g. "You hear something!" -/atom/proc/visible_message(var/message, var/blind_message, var/list/exclude_mobs, var/range = world.view) +/atom/proc/visible_message(var/message, var/blind_message, var/list/exclude_mobs, var/range = world.view, var/runemessage = "ðŸ‘") //VOREStation Edit var/list/see @@ -513,6 +522,8 @@ var/mob/M = mob if(M.see_invisible >= invisibility && MOB_CAN_SEE_PLANE(M, plane)) M.show_message(message, VISIBLE_MESSAGE, blind_message, AUDIBLE_MESSAGE) + if(runemessage != -1) + M.create_chat_message(src, "* [runemessage || message] *", FALSE, list("emote"), audible = FALSE) else if(blind_message) M.show_message(blind_message, AUDIBLE_MESSAGE) @@ -521,7 +532,7 @@ // message is the message output to anyone who can hear. // deaf_message (optional) is what deaf people will see. // hearing_distance (optional) is the range, how many tiles away the message can be heard. -/atom/proc/audible_message(var/message, var/deaf_message, var/hearing_distance, var/radio_message) +/atom/proc/audible_message(var/message, var/deaf_message, var/hearing_distance, var/radio_message, var/runemessage) var/range = hearing_distance || world.view var/list/hear = get_mobs_and_objs_in_view_fast(get_turf(src),range,remote_ghosts = FALSE) @@ -542,6 +553,8 @@ var/mob/M = mob var/msg = message M.show_message(msg, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) + if(runemessage != -1) + M.create_chat_message(src, "* [runemessage || message] *", FALSE, list("emote")) /atom/movable/proc/dropInto(var/atom/destination) while(istype(destination)) diff --git a/code/game/world.dm b/code/game/world.dm index 9f84772b9fc..2895a191dba 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -1,5 +1,6 @@ #define RECOMMENDED_VERSION 501 /world/New() + world_startup_time = world.timeofday to_world_log("Map Loading Complete") //logs //VOREStation Edit Start diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index d8deb0dce60..b0f553a4871 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -324,8 +324,7 @@ var/message = sanitize(input("What do you want the message to be?", "Make Sound") as text|null) if(!message) return - for (var/mob/V in hearers(O)) - V.show_message(message, 2) + O.audible_message(message) log_admin("[key_name(usr)] made [O] at [O.x], [O.y], [O.z]. make a sound") message_admins("[key_name_admin(usr)] made [O] at [O.x], [O.y], [O.z]. make a sound.", 1) feedback_add_details("admin_verb","MS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm index 9fb7295266c..abd55097a8c 100644 --- a/code/modules/client/client defines.dm +++ b/code/modules/client/client defines.dm @@ -76,3 +76,6 @@ var/connection_realtime ///world.timeofday they connected var/connection_timeofday + + // Runechat messages + var/list/seen_messages = list() diff --git a/code/modules/client/preference_setup/global/setting_datums.dm b/code/modules/client/preference_setup/global/setting_datums.dm index 24cca9ca18b..25eae07ff1b 100644 --- a/code/modules/client/preference_setup/global/setting_datums.dm +++ b/code/modules/client/preference_setup/global/setting_datums.dm @@ -290,6 +290,32 @@ var/list/_client_preferences_by_type enabled_description = "Show" disabled_description = "Hide" +/datum/client_preference/runechat_mob + description = "Runechat (Mobs)" + key = "RUNECHAT_MOB" + enabled_description = "Show" + disabled_description = "Hide" + +/datum/client_preference/runechat_obj + description = "Runechat (Objs)" + key = "RUNECHAT_OBJ" + enabled_description = "Show" + disabled_description = "Hide" + +/datum/client_preference/runechat_border + description = "Runechat Message Border" + key = "RUNECHAT_BORDER" + enabled_description = "Show" + disabled_description = "Hide" + enabled_by_default = FALSE + +/datum/client_preference/runechat_long_messages + description = "Runechat Message Length" + key = "RUNECHAT_LONG" + enabled_description = "ERP KING" + disabled_description = "Normie" + enabled_by_default = FALSE + /datum/client_preference/status_indicators/toggled(mob/preference_mob, enabled) . = ..() if(preference_mob && preference_mob.plane_holder) diff --git a/code/modules/emotes/emote_define.dm b/code/modules/emotes/emote_define.dm index 6b50b09dde1..d8184b9bae2 100644 --- a/code/modules/emotes/emote_define.dm +++ b/code/modules/emotes/emote_define.dm @@ -103,11 +103,14 @@ var/global/list/emotes_by_key if(target) use_1p = replace_target_tokens(use_1p, target) use_1p = "[capitalize(replace_user_tokens(use_1p, user))]" - var/use_3p = get_emote_message_3p(user, target, extra_params) - if(use_3p) + var/prefinal_3p + var/use_3p + var/raw_3p = get_emote_message_3p(user, target, extra_params) + if(raw_3p) if(target) - use_3p = replace_target_tokens(use_3p, target) - use_3p = "\The [user] [replace_user_tokens(use_3p, user)]" + raw_3p = replace_target_tokens(raw_3p, target) + prefinal_3p = replace_user_tokens(raw_3p, user) + use_3p = "\The [user] [prefinal_3p]" var/use_radio = get_radio_message(user) if(use_radio) if(target) @@ -124,12 +127,12 @@ var/global/list/emotes_by_key if(isliving(user)) var/mob/living/L = user if(L.silent) - M.visible_message(message = "[user] opens their mouth silently!", self_message = "You cannot say anything!", blind_message = emote_message_impaired) + M.visible_message(message = "[user] opens their mouth silently!", self_message = "You cannot say anything!", blind_message = emote_message_impaired, runemessage = "opens their mouth silently!") return else - M.audible_message(message = use_3p, self_message = use_1p, deaf_message = emote_message_impaired, hearing_distance = use_range, radio_message = use_radio) + M.audible_message(message = use_3p, self_message = use_1p, deaf_message = emote_message_impaired, hearing_distance = use_range, radio_message = use_radio, runemessage = prefinal_3p) else - M.visible_message(message = use_3p, self_message = use_1p, blind_message = emote_message_impaired, range = use_range) + M.visible_message(message = use_3p, self_message = use_1p, blind_message = emote_message_impaired, range = use_range, runemessage = prefinal_3p) do_extra(user, target) do_sound(user) diff --git a/code/modules/emotes/emote_mob.dm b/code/modules/emotes/emote_mob.dm index 61544715803..ef4129ae464 100644 --- a/code/modules/emotes/emote_mob.dm +++ b/code/modules/emotes/emote_mob.dm @@ -86,7 +86,8 @@ return if(use_emote.message_type == AUDIBLE_MESSAGE && is_muzzled()) - audible_message("\The [src] [use_emote.emote_message_muffled || "makes a muffled sound."]") + var/muffle_message = use_emote.emote_message_muffled || "makes a muffled sound." + audible_message("\The [src] [muffle_message]", runemessage = "* [muffle_message] *") return next_emote = world.time + use_emote.emote_delay @@ -164,9 +165,13 @@ input = message var/list/formatted + var/runemessage if(input) formatted = format_emote(src, message) message = formatted["pretext"] + formatted["nametext"] + formatted["subtext"] + runemessage = formatted["subtext"] + // This is just personal preference (but I'm objectively right) that custom emotes shouldn't have periods at the end in runechat + runemessage = replacetext(runemessage,".","",length(runemessage),length(runemessage)+1) else return @@ -194,6 +199,7 @@ if(isobserver(M)) message = "[src] ([ghost_follow_link(src, M)]) [input]" M.show_message(message, m_type) + M.create_chat_message(src, "* [runemessage] *", FALSE, list("emote"), (m_type == AUDIBLE_MESSAGE)) for(var/obj in o_viewers) var/obj/O = obj diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm index df959f07a24..8e4fd3970ea 100644 --- a/code/modules/mob/hear_say.dm +++ b/code/modules/mob/hear_say.dm @@ -116,6 +116,7 @@ message_to_send = "[message_to_send]" on_hear_say(message_to_send) + create_chat_message(speaker, combined["raw"], italics, list()) if(speech_sound && (get_dist(speaker, src) <= world.view && z == speaker.z)) var/turf/source = speaker ? get_turf(speaker) : get_turf(src) diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index ae1b09ae714..07ae8377608 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -40,18 +40,18 @@ var/list/department_radio_keys = list( //kinda localization -- rastaf0 //same keys as above, but on russian keyboard layout. This file uses cp1251 as encoding. - ":ê" = "right ear", ".ê" = "right ear", - ":ä" = "left ear", ".ä" = "left ear", - ":ø" = "intercom", ".ø" = "intercom", - ":ð" = "department", ".ð" = "department", - ":ñ" = "Command", ".ñ" = "Command", - ":ò" = "Science", ".ò" = "Science", - ":ü" = "Medical", ".ü" = "Medical", - ":ó" = "Engineering", ".ó" = "Engineering", - ":û" = "Security", ".û" = "Security", - ":ö" = "whisper", ".ö" = "whisper", - ":å" = "Mercenary", ".å" = "Mercenary", - ":é" = "Supply", ".é" = "Supply", + ":�" = "right ear", ".�" = "right ear", + ":�" = "left ear", ".�" = "left ear", + ":�" = "intercom", ".�" = "intercom", + ":�" = "department", ".�" = "department", + ":�" = "Command", ".�" = "Command", + ":�" = "Science", ".�" = "Science", + ":�" = "Medical", ".�" = "Medical", + ":�" = "Engineering", ".�" = "Engineering", + ":�" = "Security", ".�" = "Security", + ":�" = "whisper", ".�" = "whisper", + ":�" = "Mercenary", ".�" = "Mercenary", + ":�" = "Supply", ".�" = "Supply", ) @@ -362,16 +362,17 @@ proc/get_radio_key_from_channel(var/channel) //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.client) + if(M.client && !runechat_enabled) var/image/I1 = listening[M] || speech_bubble images_to_clients[I1] |= M.client M << I1 M.hear_say(message_pieces, verb, italics, src, speech_sound, sound_vol) 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.client) + if(M.client && !runechat_enabled) var/image/I2 = listening[M] || speech_bubble images_to_clients[I2] |= M.client M << I2 diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 00d705f8e1a..6626b689726 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -77,7 +77,7 @@ // message is the message output to anyone who can see e.g. "[src] does something!" // self_message (optional) is what the src mob sees e.g. "You do something!" // blind_message (optional) is what blind people will hear e.g. "You hear something!" -/mob/visible_message(var/message, var/self_message, var/blind_message, var/list/exclude_mobs = null, var/range = world.view) +/mob/visible_message(var/message, var/self_message, var/blind_message, var/list/exclude_mobs = null, var/range = world.view, var/runemessage) if(self_message) if(LAZYLEN(exclude_mobs)) exclude_mobs |= src @@ -87,7 +87,9 @@ // Transfer messages about what we are doing to upstairs if(shadow) shadow.visible_message(message, self_message, blind_message, exclude_mobs, range) - . = ..(message, blind_message, exclude_mobs, range) // Really not ideal that atom/visible_message has different arg numbering :( + if(isnull(runemessage)) + runemessage = -1 + . = ..(message, blind_message, exclude_mobs, range, runemessage) // Really not ideal that atom/visible_message has different arg numbering :( // Returns an amount of power drawn from the object (-1 if it's not viable). // If drain_check is set it will not actually drain power, just return a value. @@ -102,7 +104,7 @@ // self_message (optional) is what the src mob hears. // deaf_message (optional) is what deaf people will see. // hearing_distance (optional) is the range, how many tiles away the message can be heard. -/mob/audible_message(var/message, var/deaf_message, var/hearing_distance, var/self_message, var/radio_message) +/mob/audible_message(var/message, var/deaf_message, var/hearing_distance, var/self_message, var/radio_message, var/runemessage) var/range = hearing_distance || world.view var/list/hear = get_mobs_and_objs_in_view_fast(get_turf(src),range,remote_ghosts = FALSE) @@ -110,6 +112,9 @@ var/list/hearing_mobs = hear["mobs"] var/list/hearing_objs = hear["objs"] + if(isnull(runemessage)) + runemessage = -1 // Symmetry with mob/audible_message, despite the fact this one doesn't call parent. Maybe it should! + if(radio_message) for(var/obj in hearing_objs) var/obj/O = obj @@ -125,6 +130,8 @@ if(self_message && M==src) msg = self_message M.show_message(msg, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) + if(runemessage != -1) + M.create_chat_message(src, "* [runemessage || message] *", FALSE, list("emote"), audible = FALSE) /mob/proc/findname(msg) for(var/mob/M in mob_list) diff --git a/code/modules/ventcrawl/ventcrawl_atmospherics.dm b/code/modules/ventcrawl/ventcrawl_atmospherics.dm index 1fd9dd077ca..80348c76994 100644 --- a/code/modules/ventcrawl/ventcrawl_atmospherics.dm +++ b/code/modules/ventcrawl/ventcrawl_atmospherics.dm @@ -42,7 +42,16 @@ user.client.eye = target_move //if we don't do this, Byond only updates the eye every tick - required for smooth movement if(world.time > user.next_play_vent) user.next_play_vent = world.time+30 - playsound(src, 'sound/machines/ventcrawl.ogg', 50, 1, -3) + var/turf/T = get_turf(src) + playsound(T, 'sound/machines/ventcrawl.ogg', 50, 1, -3) + var/message = pick( + prob(90);"* clunk *", + prob(90);"* thud *", + prob(90);"* clatter *", + prob(1);"* ඞ *" + ) + T.runechat_message(message) + else if((direction & initialize_directions) || is_type_in_list(src, ventcrawl_machinery) && src.can_crawl_through()) //if we move in a way the pipe can connect, but doesn't - or we're in a vent user.remove_ventcrawl() diff --git a/interface/skin.dmf b/interface/skin.dmf index fc14eb8a69d..b1d9a6267e2 100644 --- a/interface/skin.dmf +++ b/interface/skin.dmf @@ -1282,7 +1282,7 @@ window "mapwindow" saved-params = "icon-size" on-show = ".winset\"mainwindow.mainvsplit.left=mapwindow\"" on-hide = ".winset\"mainwindow.mainvsplit.left=\"" - style=".center { text-align: center; } .maptext { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; } .small { font-size: 6px; } .big { font-size: 8px; } .reallybig { font-size: 8px; } .extremelybig { font-size: 8px; } .clown { color: #FF69Bf;} .tajaran {color: #803B56;} .skrell {color: #00CED1;} .solcom {color: #22228B;} .com_srus {color: #7c4848;} .zombie {color: #ff0000;} .soghun {color: #228B22;} .vox {color: #AA00AA;} .diona {color: #804000; font-weight: bold;} .trinary {color: #727272;} .kidan {color: #664205;} .slime {color: #0077AA;} .drask {color: #a3d4eb;} .vulpkanin {color: #B97A57;} .abductor {color: #800080; font-style: italic;} .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; }" + style=".center { text-align: center; } .runechatdiv {background-color: #20202070} .black_outline { -dm-text-outline: 1px black } .boldtext { font-weight: bold; } .maptext { font-family: 'Small Fonts'; font-size: 7px; color: white; line-height: 1.1; } .command_headset { font-weight: bold; font-size: 8px; } .small { font-size: 6px; } .very_small { font-size: 5px;} .big { font-size: 8px; } .reallybig { font-size: 8px; } .extremelybig { font-size: 8px; } .greentext { color: #00FF00; font-size: 7px; } .redtext { color: #FF0000; font-size: 7px; } .clown { color: #FF69Bf; font-size: 7px; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-size: 7px; font-style: italic; }" window "outputwindow" elem "outputwindow" diff --git a/vorestation.dme b/vorestation.dme index f3e9b290287..f5938321638 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -308,6 +308,7 @@ #include "code\datums\browser.dm" #include "code\datums\callback.dm" #include "code\datums\category.dm" +#include "code\datums\chat_message.dm" #include "code\datums\computerfiles.dm" #include "code\datums\datacore.dm" #include "code\datums\datum.dm"