diff --git a/citadel.dme b/citadel.dme index 8c82ff04d50..5fc26828f86 100644 --- a/citadel.dme +++ b/citadel.dme @@ -207,6 +207,7 @@ #include "code\__HELPERS\_global_objects.dm" #include "code\__HELPERS\_lists_tg.dm" #include "code\__HELPERS\_logging.dm" +#include "code\__HELPERS\animations.dm" #include "code\__HELPERS\areas.dm" #include "code\__HELPERS\atom_movables.dm" #include "code\__HELPERS\chat.dm" @@ -2667,6 +2668,7 @@ #include "code\modules\mob\animations.dm" #include "code\modules\mob\death.dm" #include "code\modules\mob\emote.dm" +#include "code\modules\mob\floating_message.dm" #include "code\modules\mob\gender.dm" #include "code\modules\mob\health.dm" #include "code\modules\mob\hear_say.dm" diff --git a/code/__HELPERS/animations.dm b/code/__HELPERS/animations.dm new file mode 100644 index 00000000000..6a8cc78538d --- /dev/null +++ b/code/__HELPERS/animations.dm @@ -0,0 +1,49 @@ +/proc/remove_images_from_clients(image/I, list/show_to) + for(var/client/C in show_to) + C.images -= I + qdel(I) + +/proc/fade_out(image/I, list/show_to) + animate(I, alpha = 0, time = 0.5 SECONDS, easing = EASE_IN) + addtimer(CALLBACK(GLOBAL_PROC, .proc/remove_images_from_clients, I, show_to), 0.5 SECONDS) + +/proc/animate_speech_bubble(image/I, list/show_to, duration) + var/matrix/M = matrix() + M.Scale(0,0) + I.transform = M + I.alpha = 0 + for(var/client/C in show_to) + C.images += I + animate(I, transform = 0, alpha = 255, time = 0.2 SECONDS, easing = EASE_IN) + addtimer(CALLBACK(GLOBAL_PROC, .proc/fade_out, I, show_to), (duration - 0.5 SECONDS)) + +/proc/animate_receive_damage(atom/A) + var/pixel_x_diff = rand(-2,2) + var/pixel_y_diff = rand(-2,2) + animate(A, pixel_x = A.pixel_x + pixel_x_diff, pixel_y = A.pixel_y + pixel_y_diff, time = 2) + animate(pixel_x = initial(A.pixel_x), pixel_y = initial(A.pixel_y), time = 2) + +/proc/animate_throw(atom/A) + var/ipx = A.pixel_x + var/ipy = A.pixel_y + var/mpx = 0 + var/mpy = 0 + + if(A.dir & NORTH) + mpy += 3 + else if(A.dir & SOUTH) + mpy -= 3 + if(A.dir & EAST) + mpx += 3 + else if(A.dir & WEST) + mpx -= 3 + + var/x = mpx + ipx + var/y = mpy + ipy + + animate(A, pixel_x = x, pixel_y = y, time = 0.6, easing = EASE_OUT) + + var/matrix/M = matrix(A.transform) + animate(transform = turn(A.transform, (mpx - mpy) * 4), time = 0.6, easing = EASE_OUT) + animate(pixel_x = ipx, pixel_y = ipy, time = 0.6, easing = EASE_IN) + animate(transform = M, time = 0.6, easing = EASE_IN) diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 7832340f21b..3c1d61cfb13 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -634,3 +634,54 @@ datum/projectile_data min(list_y), max(list_x), max(list_y)) + +/proc/recursive_mob_check(var/atom/O, var/list/L = list(), var/recursion_limit = 3, var/client_check = 1, var/sight_check = 1, var/include_radio = 1) + + //GLOB.debug_mob += O.contents.len + if(!recursion_limit) + return L + for(var/atom/A in O.contents) + + if(ismob(A)) + var/mob/M = A + if(client_check && !M.client) + L |= recursive_mob_check(A, L, recursion_limit - 1, client_check, sight_check, include_radio) + continue + if(sight_check && !isInSight(A, O)) + continue + L |= M + //log_world("[recursion_limit] = [M] - [get_turf(M)] - ([M.x], [M.y], [M.z])") + + else if(include_radio && istype(A, /obj/item/radio)) + if(sight_check && !isInSight(A, O)) + continue + L |= A + + if(isobj(A) || ismob(A)) + L |= recursive_mob_check(A, L, recursion_limit - 1, client_check, sight_check, include_radio) + return L + +/proc/get_mobs_in_view(var/R, var/atom/source, var/include_clientless = FALSE) + // Returns a list of mobs in range of R from source. Used in radio and say code. + + var/turf/T = get_turf(source) + var/list/hear = list() + + if(!T) + return hear + + var/list/range = hear(R, T) + + for(var/atom/A in range) + if(ismob(A)) + var/mob/M = A + if(M.client || include_clientless) + hear += M + //log_world("Start = [M] - [get_turf(M)] - ([M.x], [M.y], [M.z])") + else if(istype(A, /obj/item/radio)) + hear += A + + if(isobj(A) || ismob(A)) + hear |= recursive_mob_check(A, hear, 3, 1, 0, 1) + + return hear diff --git a/code/_macros.dm b/code/_macros.dm index fa458fc3e33..e8c450742ab 100644 --- a/code/_macros.dm +++ b/code/_macros.dm @@ -24,3 +24,6 @@ #define random_id(key,min_id,max_id) uniqueness_repository.Generate(/datum/uniqueness_generator/id_random, key, min_id, max_id) #define ARGS_DEBUG log_debug("[__FILE__] - [__LINE__]") ; for(var/arg in args) { log_debug("\t[log_info_line(arg)]") } + +#define JOINTEXT(X) jointext(X, null) +//thank you Kevin for not running checks again, now I have to update one file with a comment - Papalus diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 96bb90f1b54..f5112de091d 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -769,7 +769,7 @@ for(var/mob in seeing_mobs) var/mob/M = mob if(self_message && (M == src)) - M.show_message( self_message, 1, blind_message, 2) + M.show_message(self_message, 1, blind_message, 2) else if((M.see_invisible >= invisibility) && MOB_CAN_SEE_PLANE(M, plane)) M.show_message(message, 1, blind_message, 2) else if(blind_message) @@ -787,7 +787,7 @@ var/list/hearing_mobs = hear["mobs"] var/list/hearing_objs = hear["objs"] - + var/list/heard_to_floating_message for(var/obj in hearing_objs) var/obj/O = obj O.show_message(message, 2, deaf_message, 1) @@ -796,6 +796,9 @@ var/mob/M = mob var/msg = message M.show_message(msg, 2, deaf_message, 1) + M += heard_to_floating_message + INVOKE_ASYNC(src, /atom/movable/proc/animate_chat, (message ? message : deaf_message), null, FALSE, heard_to_floating_message, 30) + /atom/movable/proc/dropInto(var/atom/destination) while(istype(destination)) @@ -836,7 +839,7 @@ if(!message) return var/list/speech_bubble_hearers = list() - for(var/mob/M in get_hearers_in_view(7, src)) + for(var/mob/M in get_hearers_in_view(MESSAGE_RANGE_COMBAT_LOUD, src)) M.show_message("[src] [atom_say_verb], \"[message]\"", 2, null, 1) if(M.client) speech_bubble_hearers += M.client @@ -845,6 +848,18 @@ var/image/I = generate_speech_bubble(src, "[bubble_icon][say_test(message)]", FLY_LAYER) I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA INVOKE_ASYNC(GLOBAL_PROC, /.proc/flick_overlay, I, speech_bubble_hearers, 30) + INVOKE_ASYNC(src, /atom/movable/proc/animate_chat, message, null, FALSE, speech_bubble_hearers, 30) + +/atom/proc/say_overhead(var/message, whispering, message_range = 7, var/datum/language/speaking = null, var/list/passed_hearing_list) + var/list/speech_bubble_hearers = list() + var/italics + if(whispering) + italics = TRUE + for(var/mob/M in get_mobs_in_view(message_range, src)) + if(M.client) + speech_bubble_hearers += M.client + if(length(speech_bubble_hearers)) + INVOKE_ASYNC(src, /atom/movable/proc/animate_chat, message, speaking, italics, speech_bubble_hearers, 30) /proc/generate_speech_bubble(var/bubble_loc, var/speech_state, var/set_layer = FLOAT_LAYER) var/image/I = image('icons/mob/talk_vr.dmi', bubble_loc, speech_state, set_layer) @@ -854,7 +869,6 @@ /atom/proc/speech_bubble(bubble_state = "", bubble_loc = src, list/bubble_recipients = list()) return - //! ## Atom Colour Priority System /** * A System that gives finer control over which atom colour to colour the atom with. diff --git a/code/game/objects/items/devices/text_to_speech.dm b/code/game/objects/items/devices/text_to_speech.dm index 179a52b26f2..e75f40116b8 100644 --- a/code/game/objects/items/devices/text_to_speech.dm +++ b/code/game/objects/items/devices/text_to_speech.dm @@ -26,6 +26,8 @@ if(message) var/obj/item/text_to_speech/O = src audible_message("[icon2html(thing = O, target = world)] \The [O.name] states, \"[message]\"") + user.say_overhead(message, FALSE, MESSAGE_RANGE_COMBAT_LOUD) // I don't like this, I wish I could just invoke what this calls directly! + /obj/item/text_to_speech/AltClick(mob/user) // QOL Change attack_self(user) diff --git a/code/modules/client/preference_setup/global/setting_datums.dm b/code/modules/client/preference_setup/global/setting_datums.dm index 1bffd17da3d..241d127cc7b 100644 --- a/code/modules/client/preference_setup/global/setting_datums.dm +++ b/code/modules/client/preference_setup/global/setting_datums.dm @@ -286,6 +286,12 @@ var/list/_client_preferences_by_type /datum/client_preference/parallax/toggled(mob/preference_mob, enabled) . = ..() preference_mob?.client?.parallax_holder?.Reset() +/datum/client_preference/overhead_chat + description = "Overhead Chat" + key = "OVERHEAD_CHAT" + enabled_description = "Show" + disabled_description = "Hide" + enabled_by_default = TRUE /******************** * Staff Preferences * diff --git a/code/modules/client/preferences_toggle_procs.dm b/code/modules/client/preferences_toggle_procs.dm index 10dbf9c5d66..838e6edbf84 100644 --- a/code/modules/client/preferences_toggle_procs.dm +++ b/code/modules/client/preferences_toggle_procs.dm @@ -292,6 +292,20 @@ to_chat(src, "You will now [(is_preference_enabled(/datum/client_preference/status_indicators)) ? "see" : "not see"] status indicators.") feedback_add_details("admin_verb","TStatusIndicators") + +/client/verb/toggle_overhead_chat() + set name = "Toggle Overhead Chat" + set category = "Preferences" + set desc = "Enable/Disable seeing overhead chat messages." + + var/pref_path = /datum/client_preference/overhead_chat + toggle_preference(pref_path) + SScharacter_setup.queue_preferences_save(prefs) + + to_chat(src, "You will now [(is_preference_enabled(/datum/client_preference/overhead_chat)) ? "see" : "not see"] overhead chat messages..") + + feedback_add_details("admin_verb","TOHChat") + //Toggles for Staff //Developers diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm index 8ad3c99d306..059a8a71a60 100644 --- a/code/modules/mob/emote.dm +++ b/code/modules/mob/emote.dm @@ -23,6 +23,8 @@ if (message) message = say_emphasis(message) + var/overhead_message = ("** [message] **") + say_overhead(overhead_message, FALSE, range) SEND_SIGNAL(src, COMSIG_MOB_CUSTOM_EMOTE, src, message) // Hearing gasp and such every five seconds is not good emotes were not global for a reason. diff --git a/code/modules/mob/floating_message.dm b/code/modules/mob/floating_message.dm new file mode 100644 index 00000000000..f92a2670e80 --- /dev/null +++ b/code/modules/mob/floating_message.dm @@ -0,0 +1,74 @@ +var/list/floating_chat_colors = list() + +/atom/movable + var/list/stored_chat_text + +/atom/movable/proc/animate_chat(message, var/datum/language/speaking = null, small, list/show_to, duration = 30) + set waitfor = FALSE + if(!speaking) + var/datum/language/noise/noise + speaking = noise + // 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") + message = replacetext(message, url_scheme, "") + + var/static/regex/html_metachars = new(@"&[A-Za-z]{1,7};", "g") + message = replacetext(message, html_metachars, "") + + var/style //additional style params for the message + var/fontsize = 4 + if(small) + fontsize = 2 + var/limit = 160 + if(copytext_char(message, length_char(message) - 1) == "!!") + fontsize = 8 + limit = 160 + style += "font-weight: bold;" + + if(length_char(message) > limit) + message = "[copytext_char(message, 1, limit)]..." + + if(!floating_chat_colors[src]) + floating_chat_colors[src] = get_random_colour(0,160,230) + style += "color: [floating_chat_colors[src]];" + + // create 2 messages, one that appears if you know the language, and one that appears when you don't know the language + var/image/understood = generate_floating_text(src, capitalize(message), style, fontsize, duration, show_to) + var/image/gibberish = speaking ? generate_floating_text(src, speaking.scramble(message), style, fontsize, duration, show_to) : understood + + for(var/client/C in show_to) + if(!C.mob.is_deaf() && C.is_preference_enabled(/datum/client_preference/overhead_chat)) + if(C.mob.say_understands(null, speaking)) + C.images += understood + else + C.images += gibberish + +#define MAPTEXT(text) {"[##text]"} + +/proc/generate_floating_text(atom/movable/holder, message, style, size, duration, show_to) + var/image/I = image(null, holder) + var/mob/living/X + if(isliving(holder)) + X = holder + I.plane = PLANE_PLAYER_HUD + I.layer = PLANE_PLAYER_HUD_ITEMS + I.alpha = 15 + I.maptext_width = 160 + I.maptext_height = 64 + I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA + I.pixel_x = -round(I.maptext_width/2) + 16 + + I.maptext = MAPTEXT("
[message]
") // whoa calm down!! + animate(I, 1, alpha = 255, pixel_y = 24 * (X?.size_multiplier || 1)) + for(var/image/old in holder.stored_chat_text) + animate(old, 2, pixel_y = old.pixel_y + 8) + LAZYADD(holder.stored_chat_text, I) + + addtimer(CALLBACK(GLOBAL_PROC, .proc/remove_floating_text, holder, I), duration + 16) + addtimer(CALLBACK(GLOBAL_PROC, .proc/remove_images_from_clients, I, show_to), duration + 18) + + return I + +/proc/remove_floating_text(atom/movable/holder, image/I) + animate(I, 2, pixel_y = I.pixel_y + 10, alpha = 0) + LAZYREMOVE(holder.stored_chat_text, I) diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm index d18ce8a7b96..5bc0353b5df 100644 --- a/code/modules/mob/hear_say.dm +++ b/code/modules/mob/hear_say.dm @@ -71,6 +71,7 @@ if(check_mentioned(message) && is_preference_enabled(/datum/client_preference/check_mention)) message_to_send = "[message_to_send]" + on_hear_say(message_to_send) if (speech_sound && (get_dist(speaker, src) <= world.view && src.z == speaker.z)) @@ -126,6 +127,15 @@ input = replacetext_char(input, strikethrough, "$1") return input +/mob/proc/say_emphasis_strip(input) + var/static/regex/italics = regex("\\|(?=\\S)(.*?)(?=\\S)\\|", "g") + input = replacetext_char(input, italics, "$1") + var/static/regex/bold = regex("\\+(?=\\S)(.*?)(?=\\S)\\+", "g") + input = replacetext_char(input, bold, "$1") + var/static/regex/underline = regex("_(?=\\S)(.*?)(?=\\S)_", "g") + input = replacetext_char(input, underline, "$1") + return input + /mob/proc/hear_radio(var/message, var/verb="says", var/datum/language/language=null, var/part_a, var/part_b, var/part_c, var/mob/speaker = null, var/hard_to_hear = 0, var/vname ="") if(!client) diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 626c7aa4ac0..e8805194778 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -287,7 +287,7 @@ proc/get_radio_key_from_channel(var/channel) var/msg if(!speaking || !(speaking.flags & NO_TALK_MSG)) msg = "\The [src] talks into \the [used_radios[1]]" - for(var/mob/living/M in hearers(5, src)) + for(var/mob/living/M in hearers(7, src)) if((M != src) && msg) M.show_message(msg) if (speech_sound) @@ -380,6 +380,10 @@ proc/get_radio_key_from_channel(var/channel) listening[item] = z_speech_bubble listening_obj |= results["objs"] above = above.shadow + var/atom/emitter = src + if(!isobserver(emitter) || !IsAdminGhost(emitter)) + emitter.say_overhead(say_emphasis_strip(message), whispering, message_range, speaking) + //Main 'say' and 'whisper' message delivery for(var/mob/M in listening) @@ -395,12 +399,14 @@ proc/get_radio_key_from_channel(var/channel) SEND_IMAGE(M, I1) M.hear_say(message, verb, speaking, alt_name, italics, src, speech_sound, sound_vol) if(whispering) //Don't even bother with these unless whispering + if(dst > message_range && dst <= w_scramble_range) //Inside whisper scramble range if(M.client) var/image/I2 = listening[M] || speech_bubble images_to_clients[I2] |= M.client SEND_IMAGE(M, I2) M.hear_say(stars(message), verb, speaking, alt_name, italics, src, speech_sound, sound_vol*0.2) + if(dst > w_scramble_range && dst <= world.view) //Inside whisper 'visible' range M.show_message("[src.name] [w_not_heard].", 2) diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index e75fd28d01e..a087786e517 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -611,3 +611,17 @@ var/list/global/organ_rel_size = list( /mob/proc/can_see_reagents() return stat == DEAD || issilicon(src) //Dead guys and silicons can always see reagents + +//Ingnores the possibility of breaking tags. +/proc/stars_no_html(text, pr, re_encode) + text = html_decode(text) //We don't want to screw up escaped characters + . = list() + for(var/i = 1, i <= length_char(text), i++) + var/char = copytext_char(text, i, i+1) + if(char == " " || prob(pr)) + . += char + else + . += "*" + . = JOINTEXT(.) + if(re_encode) + . = html_encode(.) diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index c5dac842346..c8231189244 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -1,6 +1,7 @@ /mob/proc/say(var/message, var/datum/language/speaking = null, var/verb="says", var/alt_name="", var/whispering = 0) return + /mob/proc/whisper_wrapper() var/message = input("","whisper (text)") as text|null if(message) diff --git a/interface/skin.dmf b/interface/skin.dmf index ae5eb98282e..d6ee15f5fdf 100644 --- a/interface/skin.dmf +++ b/interface/skin.dmf @@ -2,21 +2,21 @@ macro "default" menu "menu" - elem + elem name = "&File" command = "" saved-params = "is-checked" - elem + elem name = "&Quick screenshot\tF2" command = ".screenshot auto" category = "&File" saved-params = "is-checked" - elem + elem name = "&Save screenshot as...\tShift+F2" command = ".screenshot" category = "&File" saved-params = "is-checked" - elem + elem name = "" command = "" category = "&File" @@ -26,12 +26,12 @@ menu "menu" command = ".reconnect" category = "&File" saved-params = "is-checked" - elem + elem name = "&Quit\tAlt-F4" command = ".quit" category = "&File" saved-params = "is-checked" - elem + elem name = "&Icons" command = "" saved-params = "is-checked" @@ -78,7 +78,7 @@ menu "menu" can-check = true group = "size" saved-params = "is-checked" - elem + elem name = "" command = "" category = "&Icons" @@ -89,16 +89,16 @@ menu "menu" category = "&Icons" can-check = true saved-params = "is-checked" - elem + elem name = "&Help" command = "" saved-params = "is-checked" - elem + elem name = "&Admin Help\tF1" command = "adminhelp" category = "&Help" saved-params = "is-checked" - elem + elem name = "&Hotkeys" command = "hotkeys-help" category = "&Help" diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm index aae68a3abac..63a8a7bfdb1 100644 --- a/interface/stylesheet.dm +++ b/interface/stylesheet.dm @@ -254,5 +254,6 @@ h1.alert, h2.alert {color: #000000;} .debug_info {} .debug_debug {color:#0000FF;} .debug_trace {color:#888888;} +.maptext { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; text-align: center; } "}