diff --git a/code/__DEFINES/layers_planes.dm b/code/__DEFINES/layers_planes.dm index f4a30cd482..eccae15bff 100644 --- a/code/__DEFINES/layers_planes.dm +++ b/code/__DEFINES/layers_planes.dm @@ -89,6 +89,8 @@ #define MASSIVE_OBJ_LAYER 11 #define POINT_LAYER 12 +#define CHAT_LAYER 12.1 + #define EMISSIVE_BLOCKER_PLANE 12 #define EMISSIVE_BLOCKER_LAYER 12 #define EMISSIVE_BLOCKER_RENDER_TARGET "*EMISSIVE_BLOCKER_PLANE" diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index 428f355c31..3f71d192e3 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -150,6 +150,8 @@ friend_talk(message) /mob/camera/imaginary_friend/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) + if (client?.prefs.chat_on_map && (client.prefs.see_chat_non_mob || ismob(speaker))) + create_chat_message(speaker, message_language, raw_message, spans, message_mode) to_chat(src, compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source)) /mob/camera/imaginary_friend/proc/friend_talk(message) diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm new file mode 100644 index 0000000000..cbcb34ac5e --- /dev/null +++ b/code/datums/chatmessage.dm @@ -0,0 +1,236 @@ +#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.7 // Messages decay at pow(factor, idx in stack) +#define CHAT_MESSAGE_HEIGHT_DECAY 0.9 // 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_MAX_LENGTH 110 // characters +#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 + */ +/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(QDELETED(owner) || !istype(owner) || !owner.client) + stack_trace("/datum/chatmessage created with [isnull(owner) ? "null" : "invalid"] mob owner") + qdel(src) + return + INVOKE_ASYNC(src, .proc/generate_image, text, target, owner, extra_classes, lifespan) + +/datum/chatmessage/Destroy() + if (owned_by) + if (owned_by.seen_messages) + LAZYREMOVEASSOC(owned_by.seen_messages, message_loc, src) + owned_by.images.Remove(message) + 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) + // Register client who owns this message + owned_by = owner.client + RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, .proc/qdel, src) + + // Clip message + var/maxlen = owned_by.prefs.max_chat_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" + + // Append radio icon if from a virtual speaker + if (extra_classes.Find("virtual-speaker")) + var/image/r_icon = image('icons/UI_Icons/chat/chat_icons.dmi', icon_state = "radio") + text = "\icon[r_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[message_loc]) + 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 + addtimer(CALLBACK(m, .proc/end_of_life), remaining_time, TIMER_UNIQUE|TIMER_OVERRIDE) + + // Build message image + message = image(loc = message_loc, layer = CHAT_LAYER) + message.plane = GAME_PLANE + 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 + + // View the message + LAZYADDASSOC(owned_by.seen_messages, message_loc, 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) + addtimer(CALLBACK(src, .proc/end_of_life), lifespan - CHAT_MESSAGE_EOL_FADE, TIMER_UNIQUE|TIMER_OVERRIDE) + +/** + * 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) + animate(message, alpha = 0, time = fadetime, flags = ANIMATION_PARALLEL) + QDEL_IN(src, fadetime) + +/** + * Creates a message overlay at a defined location for a given speaker + * + * Arguments: + * * speaker - The atom who is saying this message + * * message_language - The language that the message is said in + * * raw_message - The text content of the message + * * spans - Additional classes to be added to the message + * * message_mode - Bitflags relating to the mode of the message + */ +/mob/proc/create_chat_message(atom/movable/speaker, datum/language/message_language, raw_message, list/spans, message_mode) + // Ensure the list we are using, if present, is a copy so we don't modify the list provided to us + spans = spans?.Copy() + + // Check for virtual speakers (aka hearing a message through a radio) + var/atom/movable/originalSpeaker = speaker + if (istype(speaker, /atom/movable/virtualspeaker)) + var/atom/movable/virtualspeaker/v = speaker + speaker = v.source + spans |= "virtual-speaker" + + // Ignore virtual speaker (most often radio messages) from ourself + if (originalSpeaker != src && speaker == src) + return + + // Display visual above source + new /datum/chatmessage(lang_treat(speaker, message_language, raw_message, spans, null, TRUE), speaker, src, spans) + + +// Tweak these defines to change the available color ranges +#define CM_COLOR_SAT_MIN 0.6 +#define CM_COLOR_SAT_MAX 0.7 +#define CM_COLOR_LUM_MIN 0.65 +#define CM_COLOR_LUM_MAX 0.75 + +/** + * 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 + GLOB.round_id), 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 rgb + 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 "#[num2hex(c, 2)][num2hex(x, 2)][num2hex(m, 2)]" + if(1) + return "#[num2hex(x, 2)][num2hex(c, 2)][num2hex(m, 2)]" + if(2) + return "#[num2hex(m, 2)][num2hex(c, 2)][num2hex(x, 2)]" + if(3) + return "#[num2hex(m, 2)][num2hex(x, 2)][num2hex(c, 2)]" + if(4) + return "#[num2hex(x, 2)][num2hex(m, 2)][num2hex(c, 2)]" + if(5) + return "#[num2hex(c, 2)][num2hex(m, 2)][num2hex(x, 2)]" diff --git a/code/game/atoms.dm b/code/game/atoms.dm index afa8cfed3b..dbb6864028 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -50,6 +50,13 @@ var/list/blood_DNA var/list/suit_fibers + /// 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 + /atom/New(loc, ...) //atom creation method that preloads variables at creation if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New() diff --git a/code/game/say.dm b/code/game/say.dm index 85ae9f0681..5fa5a2b35d 100644 --- a/code/game/say.dm +++ b/code/game/say.dm @@ -96,21 +96,26 @@ GLOBAL_LIST_INIT(freqtospan, list( return "[say_mod(input, message_mode)][spanned ? ", \"[spanned]\"" : ""]" // Citadel edit [spanned ? ", \"[spanned]\"" : ""]" -/atom/movable/proc/lang_treat(atom/movable/speaker, datum/language/language, raw_message, list/spans, message_mode) +/// Quirky citadel proc for our custom sayverbs to strip the verb out. Snowflakey as hell, say rewrite 3.0 when? +/atom/movable/proc/quoteless_say_quote(input, list/spans = list(speech_span), message_mode) + var/pos = findtext(input, "*") + return pos? copytext(input, pos + 1) : input + +/atom/movable/proc/lang_treat(atom/movable/speaker, datum/language/language, raw_message, list/spans, message_mode, no_quote = FALSE) if(has_language(language)) var/atom/movable/AM = speaker.GetSource() if(AM) //Basically means "if the speaker is virtual" - return AM.say_quote(raw_message, spans, message_mode) + return no_quote ? AM.quoteless_say_quote(raw_message, spans, message_mode) : AM.say_quote(raw_message, spans, message_mode) else - return speaker.say_quote(raw_message, spans, message_mode) + return no_quote ? speaker.quoteless_say_quote(raw_message, spans, message_mode) : speaker.say_quote(raw_message, spans, message_mode) else if(language) var/atom/movable/AM = speaker.GetSource() var/datum/language/D = GLOB.language_datum_instances[language] raw_message = D.scramble(raw_message) if(AM) - return AM.say_quote(raw_message, spans, message_mode) + return no_quote ? AM.quoteless_say_quote(raw_message, spans, message_mode) : AM.say_quote(raw_message, spans, message_mode) else - return speaker.say_quote(raw_message, spans, message_mode) + return no_quote ? speaker.quoteless_say_quote(raw_message, spans, message_mode) : speaker.say_quote(raw_message, spans, message_mode) else return "makes a strange sound." diff --git a/code/modules/antagonists/disease/disease_mob.dm b/code/modules/antagonists/disease/disease_mob.dm index a20a9cef85..e876beb5dc 100644 --- a/code/modules/antagonists/disease/disease_mob.dm +++ b/code/modules/antagonists/disease/disease_mob.dm @@ -128,6 +128,9 @@ the new instance inside the host to be updated to the template's stats. link = FOLLOW_LINK(src, to_follow) else link = "" + // Create map text prior to modifying message for goonchat + if (client?.prefs.chat_on_map && (client.prefs.see_chat_non_mob || ismob(speaker))) + create_chat_message(speaker, message_language, raw_message, spans, message_mode) // Recompose the message, because it's scrambled by default message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source) to_chat(src, "[link] [message]") diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index 1deb702de4..f865cfddbb 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -84,3 +84,6 @@ var/next_keysend_reset = 0 var/next_keysend_trip_reset = 0 var/keysend_tripped = FALSE + + /// Messages currently seen by this client + var/list/seen_messages diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 37ae8b3b09..abe43e38ae 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -48,6 +48,9 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/UI_style = null var/buttons_locked = FALSE var/hotkeys = FALSE + var/chat_on_map = TRUE + var/max_chat_length = CHAT_MESSAGE_MAX_LENGTH + var/see_chat_non_mob = TRUE var/tgui_fancy = TRUE var/tgui_lock = TRUE var/windowflashing = TRUE @@ -824,6 +827,9 @@ GLOBAL_LIST_EMPTY(preferences_datums) dat += "UI Style: [UI_style]
" dat += "tgui Monitors: [(tgui_lock) ? "Primary" : "All"]
" dat += "tgui Style: [(tgui_fancy) ? "Fancy" : "No Frills"]
" + dat += "Show Runechat Chat Bubbles: [chat_on_map ? "Enabled" : "Disabled"]
" + dat += "Runechat message char limit: [max_chat_length]
" + dat += "See Runechat for non-mobs: [see_chat_non_mob ? "Enabled" : "Disabled"]
" dat += "
" dat += "Action Buttons: [(buttons_locked) ? "Locked In Place" : "Unlocked"]
" dat += "Keybindings: [(hotkeys) ? "Hotkeys" : "Default"]
" @@ -2134,6 +2140,10 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/pickedPDASkin = input(user, "Choose your PDA reskin.", "Character Preference", pda_skin) as null|anything in GLOB.pda_reskins if(pickedPDASkin) pda_skin = pickedPDASkin + if ("max_chat_length") + var/desiredlength = input(user, "Choose the max character length of shown Runechat messages. Valid range is 1 to [CHAT_MESSAGE_MAX_LENGTH] (default: [initial(max_chat_length)]))", "Character Preference", max_chat_length) as null|num + if (!isnull(desiredlength)) + max_chat_length = clamp(desiredlength, 1, CHAT_MESSAGE_MAX_LENGTH) if("hud_toggle_color") var/new_toggle_color = input(user, "Choose your HUD toggle flash color:", "Game Preference",hud_toggle_color) as color|null @@ -2236,6 +2246,10 @@ GLOBAL_LIST_EMPTY(preferences_datums) winset(user, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED] mainwindow.macro=default") else winset(user, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED] mainwindow.macro=old_default") + if("chat_on_map") + chat_on_map = !chat_on_map + if("see_chat_non_mob") + see_chat_non_mob = !see_chat_non_mob if("action_buttons") buttons_locked = !buttons_locked if("tgui_fancy") diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 715829b56e..29733acb8c 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -162,7 +162,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car if(malformed_hockeys[hockey]) features["cock_shape"] = malformed_hockeys[hockey] features["cock_taur"] = TRUE - + if(current_version < 29) var/digestable var/devourable @@ -211,6 +211,9 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car S["lastchangelog"] >> lastchangelog S["UI_style"] >> UI_style S["hotkeys"] >> hotkeys + S["chat_on_map"] >> chat_on_map + S["max_chat_length"] >> max_chat_length + S["see_chat_non_mob"] >> see_chat_non_mob S["tgui_fancy"] >> tgui_fancy S["tgui_lock"] >> tgui_lock S["buttons_locked"] >> buttons_locked @@ -266,6 +269,9 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car lastchangelog = sanitize_text(lastchangelog, initial(lastchangelog)) UI_style = sanitize_inlist(UI_style, GLOB.available_ui_styles, GLOB.available_ui_styles[1]) hotkeys = sanitize_integer(hotkeys, 0, 1, initial(hotkeys)) + chat_on_map = sanitize_integer(chat_on_map, 0, 1, initial(chat_on_map)) + max_chat_length = sanitize_integer(max_chat_length, 1, CHAT_MESSAGE_MAX_LENGTH, initial(max_chat_length)) + see_chat_non_mob = sanitize_integer(see_chat_non_mob, 0, 1, initial(see_chat_non_mob)) tgui_fancy = sanitize_integer(tgui_fancy, 0, 1, initial(tgui_fancy)) tgui_lock = sanitize_integer(tgui_lock, 0, 1, initial(tgui_lock)) buttons_locked = sanitize_integer(buttons_locked, 0, 1, initial(buttons_locked)) @@ -319,6 +325,9 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car WRITE_FILE(S["lastchangelog"], lastchangelog) WRITE_FILE(S["UI_style"], UI_style) WRITE_FILE(S["hotkeys"], hotkeys) + WRITE_FILE(S["chat_on_map"], chat_on_map) + WRITE_FILE(S["max_chat_length"], max_chat_length) + WRITE_FILE(S["see_chat_non_mob"], see_chat_non_mob) WRITE_FILE(S["tgui_fancy"], tgui_fancy) WRITE_FILE(S["tgui_lock"], tgui_lock) WRITE_FILE(S["buttons_locked"], buttons_locked) diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm index ea475b28d6..0c2cbc0c67 100644 --- a/code/modules/flufftext/Hallucination.dm +++ b/code/modules/flufftext/Hallucination.dm @@ -701,6 +701,9 @@ GLOBAL_LIST_INIT(hallucination_list, list( target.client.images |= speech_overlay sleep(30) target.client.images.Remove(speech_overlay) + var/spans = list(person.speech_span) + if (target.client?.prefs.chat_on_map) + target.create_chat_message(person, understood_language, chosen, spans, 0) else // Radio talk var/chosen = specific_message if(!chosen) diff --git a/code/modules/mob/dead/observer/say.dm b/code/modules/mob/dead/observer/say.dm index 8712f39531..c5c7350b47 100644 --- a/code/modules/mob/dead/observer/say.dm +++ b/code/modules/mob/dead/observer/say.dm @@ -33,6 +33,9 @@ else to_follow = V.source var/link = FOLLOW_LINK(src, to_follow) + // Create map text prior to modifying message for goonchat + if (client?.prefs.chat_on_map && (client.prefs.see_chat_non_mob || ismob(speaker))) + create_chat_message(speaker, message_language, raw_message, spans, message_mode) // Recompose the message, because it's scrambled by default message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source) to_chat(src, "[link] [message]") diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index f90b285d2d..a772f4a278 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -237,6 +237,10 @@ GLOBAL_LIST_INIT(department_radio_keys, list( deaf_message = "You can't hear yourself!" deaf_type = 2 // Since you should be able to hear yourself without looking + // Create map text prior to modifying message for goonchat + if (client?.prefs.chat_on_map && stat != UNCONSCIOUS && (client.prefs.see_chat_non_mob || ismob(speaker)) && can_hear()) + create_chat_message(speaker, message_language, raw_message, spans, message_mode) + // Recompose message for AI hrefs, language incomprehension. message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source) @@ -289,7 +293,7 @@ GLOBAL_LIST_INIT(department_radio_keys, list( //speech bubble var/list/speech_bubble_recipients = list() for(var/mob/M in listening) - if(M.client) + if(M.client && !M.client.prefs.chat_on_map) speech_bubble_recipients.Add(M.client) var/image/I = image('icons/mob/talk.dmi', src, "[bubble_type][say_test(message)]", FLY_LAYER) I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index e959144ed5..dff5584617 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -822,21 +822,21 @@ return get_dist(src, A) <= max(viewscale[1]*0.5,viewscale[2]*0.5) /mob/living/silicon/ai/proc/relay_speech(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) - raw_message = lang_treat(speaker, message_language, raw_message, spans, message_mode) + var/treated_message = lang_treat(speaker, message_language, raw_message, spans, message_mode) var/start = "Relayed Speech: " var/namepart = "[speaker.GetVoice()][speaker.get_alt_name()]" var/hrefpart = "" - var/jobpart + var/jobpart = "Unknown" if (iscarbon(speaker)) var/mob/living/carbon/S = speaker if(S.job) jobpart = "[S.job]" - else - jobpart = "Unknown" - var/rendered = "[start][hrefpart][namepart] ([jobpart]) [raw_message]" + var/rendered = "[start][hrefpart][namepart] ([jobpart]) [treated_message]" + if (client?.prefs.chat_on_map && (client.prefs.see_chat_non_mob || ismob(speaker))) + create_chat_message(speaker, message_language, raw_message, spans, message_mode) show_message(rendered, MSG_AUDIBLE) /mob/living/silicon/ai/fully_replace_character_name(oldname,newname) diff --git a/icons/UI_Icons/chat/chat_icons.dmi b/icons/UI_Icons/chat/chat_icons.dmi new file mode 100644 index 0000000000..7040b3966f Binary files /dev/null and b/icons/UI_Icons/chat/chat_icons.dmi differ diff --git a/interface/skin.dmf b/interface/skin.dmf index 5f8e1ad125..04a78af061 100644 --- a/interface/skin.dmf +++ b/interface/skin.dmf @@ -1,5 +1,6 @@ macro "default" + menu "menu" elem name = "&File" @@ -128,24 +129,28 @@ window "mainwindow" window "mapwindow" elem "mapwindow" type = MAIN - pos = 0,0 - size = 640x480 + pos = 418,0 + size = 1024x1024 anchor1 = none anchor2 = none - saved-params = "pos;size;is-minimized;is-maximized" + background-color = none + saved-params = "" + statusbar = false is-pane = true + outer-size = 1090x1192 + inner-size = 1068x1136 elem "map" type = MAP pos = 0,0 - size = 640x480 + size = 1024x1024 anchor1 = 0,0 anchor2 = 100,100 font-family = "Arial" font-size = 7 - text-color = none is-default = true saved-params = "icon-size" zoom-mode = distort + style = ".center { text-align: center; }\n.maptext { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; }\n.command_headset { font-weight: bold;\tfont-size: 8px; } .small { font-size: 6px; }\n.big { font-size: 8px; }\n.reallybig { font-size: 8px; }\n.extremelybig { font-size: 8px; }\n.greentext { color: #00ff00; font-size: 7px; }\n.redtext { color: #ff0000; font-size: 7px; }\n.clown { color: #ff69bf; font-size: 7px; font-weight: bold; }\n.his_grace { color: #15d512; }\n.hypnophrase { color: #0d0d0d; font-weight: bold; }\n.yell { font-weight: bold; }\n.italics { font-size: 6px; }" window "infowindow" elem "infowindow" @@ -268,14 +273,13 @@ window "statwindow" is-default = true saved-params = "" - window "preferences_window" +window "preferences_window" elem "preferences_window" type = MAIN pos = 372,0 size = 1280x1000 anchor1 = none anchor2 = none - background-color = none is-visible = false saved-params = "pos;size;is-minimized;is-maximized" statusbar = false @@ -285,7 +289,6 @@ window "statwindow" size = 960x1000 anchor1 = 0,0 anchor2 = 75,100 - background-color = none saved-params = "" elem "character_preview_map" type = MAP @@ -295,3 +298,4 @@ window "statwindow" anchor2 = 100,100 right-click = true saved-params = "zoom;letterbox;zoom-mode" + diff --git a/modular_citadel/interface/skin.dmf b/modular_citadel/interface/skin.dmf index 025a53bdca..ec9a010128 100644 --- a/modular_citadel/interface/skin.dmf +++ b/modular_citadel/interface/skin.dmf @@ -94,16 +94,20 @@ window "mainwindow" window "mapwindow" elem "mapwindow" type = MAIN - pos = 0,0 - size = 640x480 + pos = 418,0 + size = 1024x1024 anchor1 = none anchor2 = none + background-color = none saved-params = "pos;size;is-minimized;is-maximized" + statusbar = false is-pane = true + outer-size = 684x617 + inner-size = 662x561 elem "map" type = MAP pos = 0,0 - size = 640x480 + size = 1024x1024 anchor1 = 0,0 anchor2 = 100,100 font-family = "Arial" @@ -111,6 +115,7 @@ window "mapwindow" is-default = true saved-params = "icon-size" zoom-mode = distort + style = ".center { text-align: center; }\n.maptext { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; }\n.command_headset { font-weight: bold;\tfont-size: 8px; } .small { font-size: 6px; }\n.big { font-size: 8px; }\n.reallybig { font-size: 8px; }\n.extremelybig { font-size: 8px; }\n.greentext { color: #00ff00; font-size: 7px; }\n.redtext { color: #ff0000; font-size: 7px; }\n.clown { color: #ff69bf; font-size: 7px; font-weight: bold; }\n.his_grace { color: #15d512; }\n.hypnophrase { color: #0d0d0d; font-weight: bold; }\n.yell { font-weight: bold; }\n.italics { font-size: 6px; }" window "infowindow" elem "infowindow" @@ -196,7 +201,7 @@ window "infowindow" anchor2 = 97,0 font-size = 8 text-color = #e0e0e0 - background-color = #A92c2c + background-color = #a92c2c saved-params = "is-checked" text = "Report Issue" command = "report-issue" diff --git a/tgstation.dme b/tgstation.dme index 049857c685..58840f56e2 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -333,6 +333,7 @@ #include "code\datums\beam.dm" #include "code\datums\browser.dm" #include "code\datums\callback.dm" +#include "code\datums\chatmessage.dm" #include "code\datums\cinematic.dm" #include "code\datums\dash_weapon.dm" #include "code\datums\datacore.dm"