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 = ""
+ 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"