forked from Baystation12/Baystation12
-
-
Notifications
You must be signed in to change notification settings - Fork 108
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3408 from X0-11/chat-update-2
Runechat, too
- Loading branch information
Showing
14 changed files
with
325 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
#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 92 // pixels | ||
#define CHAT_MESSAGE_MAX_LENGTH 110 // characters | ||
#define WXH_TO_HEIGHT(x) text2num(copytext((x), findtextEx((x), "x") + 1)) // thanks lummox | ||
/atom | ||
var/chat_color = null | ||
var/chat_color_name = null | ||
var/chat_color_darkened = null | ||
|
||
/client | ||
var/list/seen_messages = list() | ||
|
||
|
||
/** | ||
* # 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 | ||
* * messageloc_override - Put the message above this item, instead. | ||
* * lifespan - The lifespan of the message in deciseconds | ||
*/ | ||
/datum/chatmessage/New(text, atom/target, mob/owner, list/extra_classes = null, messageloc_override = 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 | ||
if(messageloc_override) | ||
message_loc = messageloc_override | ||
INVOKE_ASYNC(src, .proc/generate_image, text, target, owner, extra_classes, lifespan) | ||
|
||
/datum/chatmessage/Destroy() | ||
if (owned_by) | ||
var/list/msgloclist = owned_by.seen_messages[message_loc] | ||
if (owned_by.seen_messages) | ||
msgloclist -= src | ||
if(msgloclist.len == 0) | ||
owned_by.seen_messages -= message_loc | ||
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 | ||
|
||
// Clip message | ||
var/maxlen = owned_by.prefs.max_chat_length | ||
var/textlen = length_char(text) | ||
if (textlen > maxlen) | ||
textlen = 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/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 = "<span class='center maptext [extra_classes != null ? extra_classes.Join(" ") : ""]' style='color: [tgt_color]'>[text]</span>" | ||
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 | ||
if(!message_loc) //For overriding line in-vehicles and whatnot. | ||
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 | ||
var/remaining_time = (sched_remaining) * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height) | ||
m.scheduled_destruction = world.time + remaining_time | ||
//See below as to why this is like this instead of the original method. | ||
|
||
// Build message image | ||
message = image(loc = message_loc, layer = CHAT_LAYER) | ||
message.plane = EFFECTS_BELOW_LIGHTING_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 | ||
if(!(message_loc in owned_by.seen_messages)) | ||
owned_by.seen_messages[message_loc] = list() | ||
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) | ||
GLOB.processing_objects += src | ||
//I wish we didn't have to do this, but we don't have a timer subsystem. More load for processing. | ||
|
||
/datum/chatmessage/proc/process() | ||
if(world.time >= scheduled_destruction) | ||
GLOB.processing_objects -= src | ||
end_of_life() | ||
return PROCESS_KILL | ||
|
||
/** | ||
* 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) | ||
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_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() | ||
|
||
var/atom/movable/originalSpeaker = speaker | ||
var/messageloc_override = null | ||
|
||
// Ignore virtual speaker (most often radio messages) from ourself | ||
if (originalSpeaker != src && speaker == src) | ||
return | ||
if(speaker.z != src.z) //We'll assume that speech from people we can't see is radio-speech. | ||
return //They don't want to see non-z (radio) messages. | ||
|
||
if(istype(originalSpeaker.loc,/obj/vehicles) || istype(originalSpeaker.loc,/obj/structure/closet)) | ||
messageloc_override = originalSpeaker.loc | ||
|
||
// Display visual above source | ||
new /datum/chatmessage(capitalize(raw_message), speaker, src, spans, messageloc_override) | ||
|
||
|
||
// 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 + game_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)]" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.