diff --git a/aurorastation.dme b/aurorastation.dme index b55f8a3351e..c975178ce55 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -168,6 +168,7 @@ #include "code\__DEFINES\dcs\helpers.dm" #include "code\__DEFINES\dcs\signals.dm" #include "code\__DEFINES\dcs\signals\signals_area.dm" +#include "code\__DEFINES\dcs\signals\signals_client.dm" #include "code\__DEFINES\dcs\signals\signals_datum.dm" #include "code\__DEFINES\dcs\signals\signals_global.dm" #include "code\__DEFINES\dcs\signals\signals_lore_radio.dm" @@ -493,6 +494,7 @@ #include "code\datums\helper_datums\stack_end_detector.dm" #include "code\datums\helper_datums\synthsprites.dm" #include "code\datums\helper_datums\teleport.dm" +#include "code\datums\langchat\langchat.dm" #include "code\datums\looping_sounds\_looping_sound.dm" #include "code\datums\looping_sounds\machinery_sounds.dm" #include "code\datums\looping_sounds\revenant_rift.dm" @@ -2505,6 +2507,7 @@ #include "code\modules\mapping\space_management\space_transition.dm" #include "code\modules\mapping\space_management\traits.dm" #include "code\modules\mapping\space_management\zlevel_manager.dm" +#include "code\modules\maptext_alerts\screen_alerts.dm" #include "code\modules\martial_arts\gunkata.dm" #include "code\modules\martial_arts\martial.dm" #include "code\modules\martial_arts\plasma_fist.dm" @@ -2552,7 +2555,6 @@ #include "code\modules\mob\death.dm" #include "code\modules\mob\emote.dm" #include "code\modules\mob\examinations.dm" -#include "code\modules\mob\floating_messages.dm" #include "code\modules\mob\gender.dm" #include "code\modules\mob\hear_say.dm" #include "code\modules\mob\holder.dm" diff --git a/code/__DEFINES/dcs/signals/signals_client.dm b/code/__DEFINES/dcs/signals/signals_client.dm new file mode 100644 index 00000000000..b8d2f6762ad --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_client.dm @@ -0,0 +1,5 @@ +/// Called when something is added to a client's screen : /client/proc/add_to_screen(screen_add) +#define COMSIG_CLIENT_SCREEN_ADD "client_screen_add" + +/// Called when something is removed from a client's screen : /client/proc/remove_from_screen(screen_remove) +#define COMSIG_CLIENT_SCREEN_REMOVE "client_screen_remove" diff --git a/code/__DEFINES/fonts.dm b/code/__DEFINES/fonts.dm index 7298ff5dbde..096bf0a0c76 100644 --- a/code/__DEFINES/fonts.dm +++ b/code/__DEFINES/fonts.dm @@ -1,3 +1,6 @@ // Font metrics bitfield /// Include leading A width and trailing C width in GetWidth() or in DrawText() #define INCLUDE_AC (1<<0) + +/// Maptext styles +#define MAP_STYLESHEET ".maptext { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; } .center { text-align: center; } .langchat { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; } .langchat_small { font-size: 6px; } .langchat_yell { font-weight: bold; font-size: 10px; } .langchat_bolded { font-weight: bold; font-size: 8px; } .langchat_announce { font-weight: bold; font-size: 12px; } .langchat_bolditalicbig {font-weight: bold; font-size: 24px; font-style: italic; } .langchat_italic {font-style: italic; }" diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index c47c4570b30..b789a52a419 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -185,6 +185,8 @@ /// The layer you should use when you -really- don't want an emissive overlay to be blocked. #define EMISSIVE_LAYER_UNBLOCKABLE 9999 +#define RUNECHAT_PLANE 500 + //-------------------- Rendering --------------------- /// Semantics - The final compositor or a filter effect renderer diff --git a/code/__DEFINES/qdel.dm b/code/__DEFINES/qdel.dm index a1ffd5c3699..3af7532401e 100644 --- a/code/__DEFINES/qdel.dm +++ b/code/__DEFINES/qdel.dm @@ -81,6 +81,7 @@ ///Delete and null the reference, if the object isn't null #define QDEL_NULL(item) if(item) { qdel(item) ; item = null } +#define QDEL_NULL_LIST(x) if(x) { for(var/y in x) { qdel(y) }}; if(x) {x.Cut(); x = null } // Second x check to handle items that LAZYREMOVE on qdel. #define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); } #define QDEL_LIST_IN(L, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(______qdel_list_wrapper), L), time, TIMER_STOPPABLE) diff --git a/code/__DEFINES/span.dm b/code/__DEFINES/span.dm index 27f7581e517..bb8fd3dad4d 100644 --- a/code/__DEFINES/span.dm +++ b/code/__DEFINES/span.dm @@ -33,6 +33,8 @@ #define SPAN_HIGHDANGER(str) (FONT_LARGE(SPAN_DANGER(str))) +#define SPAN_LANGCHAT(X) "[X]" + /* ##################### Font sizes diff --git a/code/controllers/subsystems/processing/tgui.dm b/code/controllers/subsystems/processing/tgui.dm index 144691ac791..70b19c9a8cb 100644 --- a/code/controllers/subsystems/processing/tgui.dm +++ b/code/controllers/subsystems/processing/tgui.dm @@ -34,6 +34,8 @@ PROCESSING_SUBSYSTEM_DEF(tgui) basehtml = replacetextEx(basehtml, "", polyfill) basehtml = replacetextEx(basehtml, "", "NanoTrasen © 2457-[text2num(time2text(world.realtime, "YYYY")) + 442]") + basehtml = replacetext(basehtml, "tgui:stylesheet", MAP_STYLESHEET) + /datum/controller/subsystem/processing/tgui/OnConfigLoad() var/storage_iframe = GLOB.config.storage_cdn_iframe if(storage_iframe && storage_iframe != /datum/configuration::storage_cdn_iframe) diff --git a/code/datums/langchat/langchat.dm b/code/datums/langchat/langchat.dm new file mode 100644 index 00000000000..7615c24a9a5 --- /dev/null +++ b/code/datums/langchat/langchat.dm @@ -0,0 +1,189 @@ +/atom/var/langchat_height = 32 // abovetile usually +/atom/var/langchat_color = "#FFFFFF" +/atom/var/langchat_styles = "" + +#define LANGCHAT_LONGEST_TEXT 64 +#define LANGCHAT_WIDTH 96 +#define LANGCHAT_MAX_ALPHA 196 + +//pop defines +#define LANGCHAT_DEFAULT_POP 0 //normal message +#define LANGCHAT_PANIC_POP 1 //this causes shaking +#define LANGCHAT_FAST_POP 2 //this just makes it go away faster + +// params for default pop +#define LANGCHAT_MESSAGE_POP_TIME 3 +#define LANGCHAT_MESSAGE_POP_Y_SINK 8 + +// params for panic pop +#define LANGCHAT_MESSAGE_PANIC_POP_TIME 1 +#define LANGCHAT_MESSAGE_PANIC_POP_Y_SINK 8 +#define LANGCHAT_MESSAGE_PANIC_SHAKE_SIZE 6 +#define LANGCHAT_MESSAGE_PANIC_SHAKE_TIMES 6 +#define LANGCHAT_MESSAGE_PANIC_SHAKE_TIME_TAKEN 1 +// params for fast pop +#define LANGCHAT_MESSAGE_FAST_POP_TIME 1 +#define LANGCHAT_MESSAGE_FAST_POP_Y_SINK 8 + +#define langchat_client_enabled(M) (M && M.client && M.client.prefs && (M.client.prefs.toggles_secondary & FLOATING_MESSAGES)) + +/atom/var/image/langchat_image +/atom/var/list/mob/langchat_listeners + +///Hides the image, if one exists. Do not null the langchat image; it is rotated when the mob is buckled or proned to maintain text orientation. +/atom/proc/langchat_drop_image() + if(langchat_listeners) + for(var/mob/M in langchat_listeners) + if(M.client) + M.client.images -= langchat_image + langchat_listeners = null + +/atom/proc/get_maxptext_x_offset(image/maptext_image) + return (world.icon_size / 2) - (maptext_image.maptext_width / 2) + +/atom/movable/get_maxptext_x_offset(image/maptext_image) + return (bound_width / 2) - (maptext_image.maptext_width / 2) + +/mob/get_maxptext_x_offset(image/maptext_image) + return (icon_size / 2) - (maptext_image.maptext_width / 2) + +///Creates the image if one does not exist, resets settings that are modified by speech procs. +/atom/proc/langchat_make_image(override_color = null) + if(!langchat_image) + langchat_image = image(null, src) + langchat_image.layer = 20 + langchat_image.plane = EFFECTS_ABOVE_LIGHTING_PLANE + langchat_image.appearance_flags = NO_CLIENT_COLOR|KEEP_APART|RESET_COLOR|RESET_TRANSFORM + langchat_image.maptext_y = langchat_height + langchat_image.maptext_height = 64 + langchat_image.maptext_y -= LANGCHAT_MESSAGE_POP_Y_SINK + langchat_image.maptext_x = get_maxptext_x_offset(langchat_image) + langchat_image.filters = filter(type="drop_shadow", size = 1, color = COLOR_BLACK) + + langchat_image.pixel_y = 0 + langchat_image.alpha = 0 + langchat_image.color = override_color ? override_color : langchat_color + if(appearance_flags & PIXEL_SCALE) + langchat_image.appearance_flags |= PIXEL_SCALE + +/mob/langchat_make_image(override_color = null) + var/new_image = FALSE + if(!langchat_image) + new_image = TRUE + . = ..() + // Recenter for icons more than 32 wide + if(new_image) + langchat_image.maptext_x += (icon_size - 32) / 2 + +/mob/abstract/ghost/langchat_make_image(override_color = null) + if(!override_color) + override_color = "#c51fb7" + . = ..() + langchat_image.appearance_flags |= RESET_ALPHA + +/atom/proc/langchat_speech(message, list/listeners, language, override_color, skip_language_check = FALSE, animation_style = LANGCHAT_DEFAULT_POP, list/additional_styles = list("langchat")) + langchat_drop_image() + langchat_make_image(override_color) + var/image/r_icon + var/use_mob_style = TRUE + var/text_to_display = message + if(length(text_to_display) > LANGCHAT_LONGEST_TEXT) + text_to_display = copytext_char(text_to_display, 1, LANGCHAT_LONGEST_TEXT + 1) + "..." + var/timer = (length(text_to_display) / LANGCHAT_LONGEST_TEXT) * 4 SECONDS + 2 SECONDS + if(additional_styles.Find("emote")) + additional_styles.Remove("emote") + use_mob_style = FALSE + r_icon = image('icons/mob/chat_icons.dmi', icon_state = "emote") + else if(additional_styles.Find("virtual-speaker")) + additional_styles.Remove("virtual-speaker") + r_icon = image('icons/mob/chat_icons.dmi', icon_state = "radio") + if(r_icon) + text_to_display = "\icon[r_icon]&zwsp;[text_to_display]" + text_to_display = "[text_to_display]" + + langchat_image.maptext = text_to_display + langchat_image.maptext_width = LANGCHAT_WIDTH + langchat_image.maptext_x = get_maxptext_x_offset(langchat_image) + + langchat_listeners = listeners + for(var/mob/M in langchat_listeners) + if(langchat_client_enabled(M) && !M.ear_deaf && (skip_language_check || M.say_understands(src, language))) + M.client.images += langchat_image + + if(isturf(loc)) + langchat_image.loc = src + else + langchat_image.loc = recursive_holder_check(src) + + switch(animation_style) + if(LANGCHAT_DEFAULT_POP) + langchat_image.alpha = 0 + animate(langchat_image, pixel_y = langchat_image.pixel_y + LANGCHAT_MESSAGE_POP_Y_SINK, alpha = LANGCHAT_MAX_ALPHA, time = LANGCHAT_MESSAGE_POP_TIME) + if(LANGCHAT_PANIC_POP) + langchat_image.alpha = LANGCHAT_MAX_ALPHA + animate(langchat_image, pixel_y = langchat_image.pixel_y + LANGCHAT_MESSAGE_PANIC_POP_Y_SINK, time = LANGCHAT_MESSAGE_PANIC_POP_TIME) + animate(pixel_x = langchat_image.pixel_x - LANGCHAT_MESSAGE_PANIC_SHAKE_SIZE, time = LANGCHAT_MESSAGE_PANIC_SHAKE_TIME_TAKEN, easing = CUBIC_EASING) + for(var/i = 1 to LANGCHAT_MESSAGE_PANIC_SHAKE_TIMES) + animate(pixel_x = langchat_image.pixel_x + 2*LANGCHAT_MESSAGE_PANIC_SHAKE_SIZE, time = 2*LANGCHAT_MESSAGE_PANIC_SHAKE_TIME_TAKEN, easing = CUBIC_EASING) + animate(pixel_x = langchat_image.pixel_x - 2*LANGCHAT_MESSAGE_PANIC_SHAKE_SIZE, time = LANGCHAT_MESSAGE_PANIC_SHAKE_TIME_TAKEN, easing = CUBIC_EASING) + animate(pixel_x = langchat_image.pixel_x + LANGCHAT_MESSAGE_PANIC_SHAKE_SIZE, time = LANGCHAT_MESSAGE_PANIC_SHAKE_TIME_TAKEN, easing = CUBIC_EASING) + if(LANGCHAT_FAST_POP) + langchat_image.alpha = 0 + animate(langchat_image, pixel_y = langchat_image.pixel_y + LANGCHAT_MESSAGE_FAST_POP_Y_SINK, alpha = LANGCHAT_MAX_ALPHA, time = LANGCHAT_MESSAGE_FAST_POP_TIME) + + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, langchat_drop_image), language), timer, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_NO_HASH_WAIT) + +/atom/proc/langchat_long_speech(message, list/listeners, language) + langchat_drop_image() + langchat_make_image() + + var/text_left = null + var/text_to_display = message + + if(length(message) > LANGCHAT_LONGEST_TEXT) + text_to_display = copytext_char(message, 1, LANGCHAT_LONGEST_TEXT - 5) + "..." + text_left = "..." + copytext_char(message, LANGCHAT_LONGEST_TEXT - 5) + var/timer = 6 SECONDS + if(text_left) + timer = 4 SECONDS + text_to_display = "[text_to_display]" + + langchat_image.maptext = text_to_display + langchat_image.maptext_width = LANGCHAT_WIDTH * 2 + langchat_image.maptext_x = get_maxptext_x_offset(langchat_image) + + langchat_listeners = listeners + for(var/mob/M in langchat_listeners) + if(langchat_client_enabled(M) && !M.ear_deaf && M.say_understands(src, language)) + M.client.images += langchat_image + + if(isturf(loc)) + langchat_image.loc = src + else + langchat_image.loc = recursive_holder_check(src) + + animate(langchat_image, pixel_y = langchat_image.pixel_y + LANGCHAT_MESSAGE_POP_Y_SINK, alpha = LANGCHAT_MAX_ALPHA, time = LANGCHAT_MESSAGE_POP_TIME) + if(text_left) + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, langchat_long_speech), text_left, listeners, language), timer, TIMER_OVERRIDE|TIMER_UNIQUE|TIMER_NO_HASH_WAIT) + else + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, langchat_drop_image), language), timer, TIMER_OVERRIDE|TIMER_UNIQUE|TIMER_NO_HASH_WAIT) + +/** Displays image to a single listener after it was built above eg. for chaining different game logic than speech code +This does just that, doesn't check deafness or language! Do what you will in that regard **/ +/atom/proc/langchat_display_image(mob/M) + if(langchat_image) + if(!langchat_client_enabled(M)) + return + if(!langchat_listeners) // shouldn't happen + langchat_listeners = list() + langchat_listeners |= M + M.client.images += langchat_image + +/// Will attempt to find what's holding this item if it's being contained by something, ie if it's in a satchel held by a human, this'll return the human +/proc/recursive_holder_check(obj/item/held_item, recursion_limit = 3) + if(recursion_limit <= 0) + return held_item + if(!held_item.loc || isturf(held_item.loc)) + return held_item + recursion_limit-- + return recursive_holder_check(held_item.loc, recursion_limit) diff --git a/code/defines/procs/hud.dm b/code/defines/procs/hud.dm index a51d2aabd22..ca6e7422b97 100644 --- a/code/defines/procs/hud.dm +++ b/code/defines/procs/hud.dm @@ -123,3 +123,13 @@ the HUD updates properly! */ state = "hudunknown" return state + +/// Wrapper for adding anything to a client's screen +/client/proc/add_to_screen(screen_add) + screen += screen_add + SEND_SIGNAL(src, COMSIG_CLIENT_SCREEN_ADD, screen_add) + +/// Wrapper for removing anything from a client's screen +/client/proc/remove_from_screen(screen_remove) + screen -= screen_remove + SEND_SIGNAL(src, COMSIG_CLIENT_SCREEN_REMOVE, screen_remove) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 5abce4fe001..4f6c919a204 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -587,9 +587,6 @@ ASSOC_UNSETEMPTY(recursive_contents, channel) UNSETEMPTY(location.important_recursive_contents) - if(LAZYLEN(gone.stored_chat_text)) - return_floating_text(gone) - GLOB.dir_set_event.unregister(src, gone, TYPE_PROC_REF(/atom, recursive_dir_set)) /atom/movable/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) @@ -608,9 +605,6 @@ SSspatial_grid.add_grid_awareness(location, channel) recursive_contents[channel] |= arrived.important_recursive_contents[channel] - if (LAZYLEN(arrived.stored_chat_text)) - give_floating_text(arrived) - if(GLOB.dir_set_event.has_listeners(arrived)) GLOB.dir_set_event.register(src, arrived, TYPE_PROC_REF(/atom, recursive_dir_set)) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 8af3e54e398..72425829b55 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -909,9 +909,11 @@ GLOBAL_LIST_INIT(slot_flags_enumeration, list( SSicon_cache.bloody_cache[type] = blood_overlay /obj/item/proc/showoff(mob/user) - for (var/mob/M in view(user)) + var/list/viewers = get_hearers_in_view(world.view, src) + user.langchat_speech("holds up [src].", viewers, GLOB.all_languages, skip_language_check = TRUE, animation_style = LANGCHAT_FAST_POP, additional_styles = list("langchat_small", "emote")) + for (var/mob/M in viewers) if(!user.is_invisible_to(M)) - M.show_message("[user] holds up [icon2html(src, viewers(get_turf(src)))] [src]. Take a closer look.",1) /mob/living/carbon/verb/showoff() set name = "Show Held Item" diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index b40e1a8dcad..f91bde4579b 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -319,4 +319,4 @@ if(mob.client) clients_in_hearers += mob.client if(length(clients_in_hearers)) - INVOKE_ASYNC(src, TYPE_PROC_REF(/atom/movable, animate_chat), message, null, FALSE, clients_in_hearers, overhead_time) + langchat_speech(message, hearers, GLOB.all_languages, skip_language_check = TRUE) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 6af1a9a78a9..53ba0095bfa 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -47,6 +47,8 @@ GLOBAL_LIST_INIT(admin_verbs_admin, list( /client/proc/admin_cancel_shuttle, //allows us to cancel the emergency shuttle, sending it back to centcomm, /client/proc/cmd_admin_direct_narrate, //send text directly to a player with no padding. Useful for narratives and fluff-text, /client/proc/cmd_admin_local_narrate, //sends text to all mobs within 7 tiles of src.mob + /client/proc/cmd_admin_local_screen_text, + /client/proc/cmd_admin_global_screen_text, /client/proc/cmd_admin_world_narrate, //sends text to all players with no padding, /client/proc/cmd_admin_create_centcom_report, /client/proc/check_ai_laws, //shows AI and borg laws, @@ -305,6 +307,8 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list( /client/proc/manage_silicon_laws, /client/proc/cmd_admin_direct_narrate, /client/proc/cmd_admin_local_narrate, + /client/proc/cmd_admin_local_screen_text, + /client/proc/cmd_admin_global_screen_text, /client/proc/cmd_admin_world_narrate, /client/proc/cmd_admin_grab_observers, /client/proc/cmd_admin_create_centcom_report, diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index fbc69d878d5..96620f6ffb5 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -119,6 +119,65 @@ message_admins(SPAN_NOTICE("\bold LocalNarrate: [key_name_admin(usr)] : [msg]
"), 1) feedback_add_details("admin_verb", "LCLN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_local_screen_text() + set category = "Special Verbs" + set name = "Local Screen Text" + + if(!check_rights(R_ADMIN, TRUE)) + return + + var/list/mob/message_mobs = list() + var/choice = tgui_alert(usr, "Local Screen Text will send a screen text message to mobs. Do you want the mobs messaged to be only ones that you can see, or ignore blocked vision and message everyone within seven tiles of you?", "Narrate Selection", list("View", "Range", "Cancel")) + if(choice != "Cancel") + if(choice == "View") + message_mobs = mobs_in_view(view, src.mob) + else + for(var/mob/M in range(view, src.mob)) + message_mobs += M + else + return + + var/msg = tgui_input_text(usr, "Insert the screen message you want to send.", "Local Screen Text") + if(!msg) + return + + var/big_text = tgui_alert(src, "Do you want big or normal text?", "Local Screen Text", list("Big", "Normal")) + var/text_type = /atom/movable/screen/text/screen_text + if(big_text == "Big") + text_type = /atom/movable/screen/text/screen_text/command_order + + for(var/mob/M in message_mobs) + if(M.client) + M.play_screen_text(msg,text_type, COLOR_RED) + log_admin("LocalScreenText: [key_name(usr)] : [msg]") + message_admins(SPAN_NOTICE("Local Screen Text: [key_name_admin(usr)] : [msg]"), 1) + feedback_add_details("admin_verb", "LSTX") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_global_screen_text() + set category = "Special Verbs" + set name = "Global Screen Text" + + if(!check_rights(R_ADMIN, TRUE)) + return + + var/msg = tgui_input_text(usr, "Insert the screen message you want to send.", "Global Screen Text") + if(!msg) + return + + var/big_text = tgui_alert(src, "Do you want big or normal text?", "Global Screen Text", list("Big", "Normal")) + var/text_type = /atom/movable/screen/text/screen_text + if(big_text == "Big") + text_type = /atom/movable/screen/text/screen_text/command_order + + for(var/mob/M in GLOB.mob_list) + if(M.client) + M.play_screen_text(msg, text_type, COLOR_RED) + + log_admin("GlobalScreenText: [key_name(usr)] : [msg]") + message_admins(SPAN_NOTICE("Global Screen Text: [key_name_admin(usr)] : [msg]"), 1) + feedback_add_details("admin_verb", "GSTX") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + /client/proc/cmd_admin_direct_narrate(var/mob/M) // Targetted narrate -- TLE set category = "Special Verbs" set name = "Direct Narrate" diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index 08a24cd3a29..953484e726a 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -100,3 +100,7 @@ /// The DPI scale of the client. 1 is equivalent to 100% window scaling, 2 will be 200% window scaling var/window_scaling + + //screen_text vars + ///lazylist of screen_texts for this client, first in this list is the one playing + var/list/atom/movable/screen/text/screen_text/screen_texts diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 24189220379..4188f859321 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -386,6 +386,8 @@ GLOBAL_LIST_INIT(localhost_addresses, list( // Forcibly enable hardware-accelerated graphics, as we need them for the lighting overlays. winset(src, null, "command=\".configure graphics-hwmode on\"") + winset(src, "map", "style=\"[MAP_STYLESHEET]\"") + if(IsGuestKey(key) && GLOB.config.external_auth) src.authed = FALSE var/mob/abstract/unauthed/m = new() diff --git a/code/modules/client/preference_setup/general/01_basic.dm b/code/modules/client/preference_setup/general/01_basic.dm index ee027322fde..5e7d5f7f50f 100644 --- a/code/modules/client/preference_setup/general/01_basic.dm +++ b/code/modules/client/preference_setup/general/01_basic.dm @@ -262,7 +262,7 @@ pref.floating_chat_color = new_fc_color var/mob/living/carbon/human/H = preference_mob() if(ishuman(H)) - H.set_floating_chat_color(new_fc_color) + H.langchat_color = pref.floating_chat_color return TOPIC_REFRESH else if(href_list["speech_bubble_type"]) diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 94bd9094529..d4ccb7c91cf 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -412,7 +412,7 @@ GLOBAL_LIST_EMPTY_TYPED(preferences_datums, /datum/preferences) character.set_species(species) if(character.dna) character.dna.real_name = character.real_name - character.set_floating_chat_color(floating_chat_color) + character.langchat_color = floating_chat_color character.flavor_texts["general"] = flavor_texts["general"] character.flavor_texts[BP_HEAD] = flavor_texts[BP_HEAD] diff --git a/code/modules/emotes/emote_mob.dm b/code/modules/emotes/emote_mob.dm index 7a5554e7700..ab161cdabd0 100644 --- a/code/modules/emotes/emote_mob.dm +++ b/code/modules/emotes/emote_mob.dm @@ -149,7 +149,6 @@ return pretext + nametext + subtext /mob/proc/custom_emote(var/m_type = VISIBLE_MESSAGE, var/message = null, var/do_show_observers = TRUE) - if((usr && stat) || (!use_me && usr == src)) to_chat(src, "You are unable to emote.") return @@ -157,27 +156,21 @@ if(!message) return - var/animated_chat_message = message - - message = format_emote(src, message) - if (message) log_emote("[name]/[key] : [message]") message = process_chat_markup(message, list("~", "_")) + var/langchat_message = message + message = format_emote(src, message) + var/list/emote_viewers = list() if(m_type == VISIBLE_MESSAGE) visible_message(message, show_observers = do_show_observers) + emote_viewers = viewers(world.view, src) else audible_message(message, ghost_hearing = do_show_observers) + emote_viewers = get_hearers_in_LOS(world.view, src) - var/list/hearers = get_hearers_in_view(7, src) - var/list/hear_clients = list() - for(var/mob/M in hearers) - if(M.client) - hear_clients |= M.client - - animated_chat_message = SPAN_COLOR("#f3ef09", "*") + animated_chat_message - animate_chat(animated_chat_message, null, TRUE, hear_clients, 30) + langchat_speech(langchat_message, emote_viewers, GLOB.all_languages, skip_language_check = TRUE, additional_styles = list("emote", "langchat_small")) // Specific mob type exceptions below. /mob/living/silicon/ai/emote(var/act, var/type, var/message) diff --git a/code/modules/maptext_alerts/screen_alerts.dm b/code/modules/maptext_alerts/screen_alerts.dm new file mode 100644 index 00000000000..9c3a4b3d958 --- /dev/null +++ b/code/modules/maptext_alerts/screen_alerts.dm @@ -0,0 +1,118 @@ +/* +* These are ported from TGMC and are hopefully more flexible than text blurbs +*/ + +/** + * proc for playing a screen_text on a mob. + * enqueues it if a screen text is running and plays i otherwise + * Arguments: + * * text: text we want to be displayed + * * alert_type: typepath for screen text type we want to play here + * * override_color: the color of the text to use + */ +/mob/proc/play_screen_text(text, alert_type = /atom/movable/screen/text/screen_text, override_color = "#FFFFFF") + var/atom/movable/screen/text/screen_text/text_box = new alert_type() + text_box.text_to_play = text + text_box.player = client + if(override_color) + text_box.color = override_color + + LAZYADD(client.screen_texts, text_box) + if(LAZYLEN(client.screen_texts) == 1) //lets only play one at a time, for thematic effect and prevent overlap + INVOKE_ASYNC(text_box, TYPE_PROC_REF(/atom/movable/screen/text/screen_text, play_to_client)) + +/atom/movable/screen/text/screen_text + icon = null + icon_state = null + alpha = 255 + + maptext_height = 64 + maptext_width = 480 + maptext_x = 0 + maptext_y = 0 + screen_loc = "LEFT,TOP-3" + + ///Time taken to fade in as we start printing text + var/fade_in_time = 0 + ///Time before fade out after printing is finished + var/fade_out_delay = 2 SECONDS + ///Time taken when fading out after fade_out_delay + var/fade_out_time = 0.5 SECONDS + ///delay between playing each letter. in general use 1 for fluff and 0.5 for time sensitive messsages + var/play_delay = 0.5 + ///letters to update by per text to per play_delay + var/letters_per_update = 1 + + ///opening styling for the message + var/style_open = "" + ///closing styling for the message + var/style_close = "" + ///var for the text we are going to play + var/text_to_play + ///The client that this text is for + var/client/player + +/atom/movable/screen/text/screen_text/command_order + maptext_height = 64 + maptext_width = 480 + maptext_x = 0 + maptext_y = 0 + screen_loc = "LEFT,TOP-3" + + letters_per_update = 2 + fade_out_delay = 4.5 SECONDS + style_open = "" + style_close = "" + +///proc for actually playing this screen_text on a mob. +/atom/movable/screen/text/screen_text/proc/play_to_client() + player?.add_to_screen(src) + if(fade_in_time) + animate(src, alpha = 255) + var/list/lines_to_skip = list() + var/static/html_locate_regex = regex("<.*>") + var/tag_position = findtext(text_to_play, html_locate_regex) + var/reading_tag = TRUE + while(tag_position) + if(reading_tag) + if(text_to_play[tag_position] == ">") + reading_tag = FALSE + lines_to_skip += tag_position + tag_position++ + else + tag_position = findtext(text_to_play, html_locate_regex, tag_position) + reading_tag = TRUE + + for(var/letter = 2 to length(text_to_play) + letters_per_update step letters_per_update) + if(letter in lines_to_skip) + continue + maptext = "[style_open][copytext_char(text_to_play, 1, letter)][style_close]" + sleep(play_delay) + + addtimer(CALLBACK(src, PROC_REF(after_play)), fade_out_delay) + +///handles post-play effects like fade out after the fade out delay +/atom/movable/screen/text/screen_text/proc/after_play() + if(!fade_out_time) + end_play() + return + + animate(src, alpha = 0, time = fade_out_time) + addtimer(CALLBACK(src, PROC_REF(end_play)), fade_out_time) + +///ends the play then deletes this screen object and plays the next one in queue if it exists +/atom/movable/screen/text/screen_text/proc/end_play() + if(!player) + qdel(src) + return + + player.remove_from_screen(src) + LAZYREMOVE(player.screen_texts, src) + qdel(src) + + if(QDELETED(player)) + QDEL_NULL_LIST(player.screen_texts) + return + + if(LAZYLEN(player.screen_texts)) + player.screen_texts[1].play_to_client() // Theres more? diff --git a/code/modules/mob/abstract/ghost/storyteller/storyteller_verbs.dm b/code/modules/mob/abstract/ghost/storyteller/storyteller_verbs.dm index 7653e9fa24f..866a307cc78 100644 --- a/code/modules/mob/abstract/ghost/storyteller/storyteller_verbs.dm +++ b/code/modules/mob/abstract/ghost/storyteller/storyteller_verbs.dm @@ -142,6 +142,56 @@ var/datum/tgui_module/narrate_panel/NP = new /datum/tgui_module/narrate_panel(usr) NP.ui_interact(usr) +/mob/abstract/ghost/storyteller/verb/local_screen_text() + set name = "Local Screen Text" + set category = "Storyteller" + + var/list/mob/message_mobs = list() + var/choice = html_decode(sanitize(tgui_alert(src, "Local Screen Text will send a screen text message to mobs. Do you want the mobs messaged to be only ones that you can see, or ignore blocked vision and message everyone within seven tiles of you?", "Narrate Selection", list("View", "Range", "Cancel")))) + if(choice != "Cancel") + if(choice == "View") + message_mobs = mobs_in_view(world.view, src) + else + for(var/mob/M in range(world.view, src)) + message_mobs += M + else + return + + var/msg = tgui_input_text(src, "Insert the screen message you want to send.", "Local Screen Text") + if(!msg) + return + + var/big_text = tgui_alert(src, "Do you want big or normal text?", "Local Screen Text", list("Big", "Normal")) + var/text_type = /atom/movable/screen/text/screen_text + if(big_text == "Big") + text_type = /atom/movable/screen/text/screen_text/command_order + + for(var/mob/M in message_mobs) + if(M.client) + M.play_screen_text(msg, text_type, COLOR_PURPLE) + log_admin("LocalScreenText: [key_name(usr)] : [msg]") + message_admins(SPAN_NOTICE("Local Screen Text: [key_name_admin(usr)] : [msg]"), 1) + +/mob/abstract/ghost/storyteller/verb/global_screen_text() + set name = "Global Screen Text" + set category = "Storyteller" + + var/msg = html_decode(sanitize(tgui_input_text(src, "Insert the screen message you want to send.", "Global Screen Text"))) + if(!msg) + return + + var/big_text = tgui_alert(src, "Do you want big or normal text?", "Global Screen Text", list("Big", "Normal")) + var/text_type = /atom/movable/screen/text/screen_text + if(big_text == "Big") + text_type = /atom/movable/screen/text/screen_text/command_order + + for(var/mob/M in GLOB.mob_list) + if(M.client) + M.play_screen_text(msg, text_type, COLOR_PURPLE) + + log_admin("GlobalScreenText: [key_name(usr)] : [msg]") + message_admins(SPAN_NOTICE("Global Screen Text: [key_name_admin(usr)] : [msg]"), 1) + /mob/abstract/ghost/storyteller/verb/storyteller_direct_narrate(var/mob/M) set name = "Direct Narrate" set category = "Storyteller" diff --git a/code/modules/mob/floating_messages.dm b/code/modules/mob/floating_messages.dm deleted file mode 100644 index 8fcc8633bbe..00000000000 --- a/code/modules/mob/floating_messages.dm +++ /dev/null @@ -1,219 +0,0 @@ -// Thanks to Burger from Burgerstation for the foundation for this -GLOBAL_LIST_INIT(floating_chat_colors, list()) - -///Compute an unique key that is used to associate an image to the client that received said image -#define STORED_CHAT_TEXT_HASH(client) "[client.ckey]" -/atom/movable - /** - * A lazy list with the following format: - * - * * K -> The hash function result of STORED_CHAT_TEXT_HASH, a string - * * V -> A `/list` of `/image`, the images are the runetext sent to be shown to the various clients (as per the hash function) - */ - VAR_PRIVATE/list/stored_chat_text - -/atom/movable/proc/get_floating_chat_color() - return get_random_colour(0, 160, 230) - -/atom/movable/proc/set_floating_chat_color(color) - GLOB.floating_chat_colors[name] = color - -/atom/movable/proc/animate_chat(message, datum/language/language, small, list/show_to, duration, override_color) - SHOULD_NOT_SLEEP(TRUE) - - var/style //additional style params for the message - var/fontsize = GENERATE_FLOATING_TEXT_MEDIUM - if(small) - fontsize = GENERATE_FLOATING_TEXT_SMALL - var/limit = 50 - if(copytext(message, length(message) - 1) == "!!") - fontsize = GENERATE_FLOATING_TEXT_LARGE - limit = 30 - style += "font-weight: bold;" - - if(length(message) > limit) - message = "[copytext_char(message, 1, limit)]..." - - if(istype(language, /datum/language/noise)) - message = "* " + uncapitalize(message) - - if(!GLOB.floating_chat_colors[name]) - GLOB.floating_chat_colors[name] = get_floating_chat_color() - style += "color: [GLOB.floating_chat_colors[name]];" - - send_chat_floating_text_to_clients(show_to, message, fontsize, style, duration, language) - - -/** - * Generates a floating message image and sent it to the clients - * - * - * * show_to - A `/list` of clients to show the image to - * * message - A string, the message to show - * * style - String, a CSS-DM that will be injected in the style of the maptext, MUST be semicolon (;) terminated - * * duration - Duration in deciseconds to show the message for - * * language - A `/datum/language`, to appropriately change the image based on the understanding of the mob that receives it - */ -/atom/movable/proc/send_chat_floating_text_to_clients(list/client/show_to, message, fontsize, style, duration, datum/language/language) - SHOULD_NOT_SLEEP(TRUE) - - // create 2 messages, one that appears if you know the language, and one that appears when you don't know the language - for(var/client/C in show_to) - if(!(C.prefs.toggles_secondary & FLOATING_MESSAGES)) - continue - - var/message_to_put_in_image - - //See if it needs an understood message, or a gibberish one, to be shown, and generate it - if(isnull(language) || C.mob.say_understands(null, language)) - message_to_put_in_image = capitalize(message) - else if(!isnull(language)) - message_to_put_in_image = language.scramble(message) - - //Generate the image, sent it to the client - generate_floating_text(message_to_put_in_image, style, fontsize, duration, C) - - -/** - * Generates a floating text message and takes care of showing it to the client, and remove it - * - * - * * message - String, the text to put into the floating text - * * style - String, a CSS-DM that will be injected in the style of the maptext, MUST be semicolon (;) terminated - * * fontsize - One of the GENERATE_FLOATING_TEXT_* macros defined in `code\__DEFINES\text.dm`, determines the size - * * duration - Duration in deciseconds to show the message for - * - * This process can sleep as it uses the client to measure the size of text, it should only be called inside a non-blocking call chain - * - */ -/atom/movable/proc/generate_floating_text(message, style, fontsize = GENERATE_FLOATING_TEXT_MEDIUM, duration, client/show_to) - set waitfor = FALSE - PRIVATE_PROC(TRUE) - - if(!istype(show_to)) - crash_with("Wrong argument supplied, show_to is not a client!") - - //No point if the source or the destination of the floating text are deleted - if(QDELETED(src) || QDELETED(show_to)) - return FALSE - - var/atom/movable/attached_holder = get_last_atom_before_turf(src) - var/image/I = image(null, attached_holder, layer = FLY_LAYER) - I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART | PIXEL_SCALE - - I.plane = HUD_PLANE - I.layer = UNDER_HUD_LAYER - I.appearance_flags = RESET_COLOR|RESET_ALPHA|RESET_TRANSFORM - - I.alpha = 0 - - I.maptext_width = CHAT_MESSAGE_WIDTH - - I.pixel_y = attached_holder.get_floating_chat_y_offset() - I.pixel_x = (-round(I.maptext_width/2) + 16) + attached_holder.get_floating_chat_x_offset() - - //Select the various parameters for the maptext, to ensure pixel-perfect scaling - var/font_family - var/font_size - var/other - switch(fontsize) - if(GENERATE_FLOATING_TEXT_SMALL) - font_family = "Spess Font" - font_size = 6 - other = "-dm-text-outline: 1px black sharp; line-height: 1.4;" - if(GENERATE_FLOATING_TEXT_MEDIUM) - font_family = "Grand9K Pixel" - font_size = 6 - other = "-dm-text-outline: 1px black sharp;" - if(GENERATE_FLOATING_TEXT_LARGE) - font_family = "Grand9K Pixel" - font_size = 12 - other = "-dm-text-outline: 1.2px black sharp;" - else - crash_with("Wrong size specified, use one of the defines!") - - var/complete_text = "[message]" - - I.maptext = complete_text - - //At this point we enter the wait, we patiently wait for the client to tell us how large - //the text will be on its screen, as we need that info for the image generation afterwards - var/mheight = 0 - WXH_TO_HEIGHT(show_to.MeasureText(complete_text, null, CHAT_MESSAGE_WIDTH), mheight) - - //Then we register a callback to complete the image generation, this is done to avoid - //the proc waking up at the end of a tick and cause overtime - SSrunechat.message_queue += CALLBACK(src, PROC_REF(finish_generate_floating_text), I, mheight, show_to, duration) - - -/** - * Runs after the calculation of the client for the runechat size measurement, - * takes care of the last things needed to be set on the image, to display it to the clients, - * and to register it for removal when it expires - * - * Should only, ever be called by the runechat subsystem as a consequence of the `generate_floating_text()` callback insertion - */ -/atom/movable/proc/finish_generate_floating_text(image/runetext_image, mheight, client/show_to, lifespan) - SHOULD_NOT_SLEEP(TRUE) - PRIVATE_PROC(TRUE) - - //The client can go offline, which would cause a runtime below - //this prevents the runtime, checking for both null and being deleted - if(QDELETED(show_to)) - return - - runetext_image.maptext_height = mheight * 1.25 - - var/stored_chat_text_hash_cache = STORED_CHAT_TEXT_HASH(show_to) - - //Older runetext messages are shifted up so they clear the visual for the new ones - for(var/image/old in LAZYACCESS(src.stored_chat_text, stored_chat_text_hash_cache)) - animate(old, 2, pixel_y = old.pixel_y + min(runetext_image.maptext_height, 45) + 8) - - //We register ourself as a stored chat text image - LAZYADDASSOCLIST(src.stored_chat_text, stored_chat_text_hash_cache, runetext_image) - - //Send the image to the client, takes care to remove it afterwards - flick_overlay(runetext_image, list(show_to), (lifespan + 2)) - - //Animation to show the text "appearing up" - animate(runetext_image, 1, alpha = 255, pixel_y = runetext_image.pixel_y + 23) - - //Register a timer for our removal from the list - addtimer(CALLBACK(src, PROC_REF(remove_floating_text), runetext_image, stored_chat_text_hash_cache), lifespan) - - -/// Gives floating text to src upon holder entering -/atom/movable/proc/give_floating_text(atom/movable/holder) - PRIVATE_PROC(TRUE) - SHOULD_NOT_SLEEP(TRUE) - - if(!holder) - return - for(var/key in holder.stored_chat_text) - for(var/image/I in holder.stored_chat_text[key]) - I.loc = src - -/// Returns floating text to holder upon leaving src -/atom/movable/proc/return_floating_text(atom/movable/holder) - PRIVATE_PROC(TRUE) - SHOULD_NOT_SLEEP(TRUE) - - if(!holder) - return - for(var/key in holder.stored_chat_text) - for(var/image/I in holder.stored_chat_text[key]) - I.loc = holder - -/atom/movable/proc/remove_floating_text(image/image_to_remove, stored_chat_text_hash) - PRIVATE_PROC(TRUE) - SHOULD_NOT_SLEEP(TRUE) - - var/list/image/client_associated_images = LAZYACCESS(src.stored_chat_text, stored_chat_text_hash) - if(!(client_associated_images?.Find(image_to_remove))) - crash_with("Trying to remove a floating text image that is not there!") - - animate(image_to_remove, 2, pixel_y = image_to_remove.pixel_y + 10, alpha = 0) - LAZYREMOVEASSOC(src.stored_chat_text, stored_chat_text_hash, image_to_remove) - -#undef STORED_CHAT_TEXT_HASH diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 26c258a47f2..484942428e9 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -352,7 +352,11 @@ var/list/channel_to_radio_key = new speech_bubble.appearance_flags = RESET_COLOR|RESET_ALPHA INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(animate_speechbubble), speech_bubble, hear_clients, 30) - animate_chat(message, speaking, italics, hear_clients, 30) + var/list/langchat_styles = list() + if(istype(speaking, /datum/language/noise)) + langchat_styles = list("emote", "langchat_small") + + langchat_speech(message, get_hearers_in_view(message_range, src), speaking, additional_styles = langchat_styles) var/bypass_listen_obj = (speaking && (speaking.flags & PASSLISTENOBJ)) if(!bypass_listen_obj) diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 098cb6b5c36..e2ae2d0df7f 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -252,6 +252,8 @@ var/list/active_genes=list() var/mob_size = MOB_MEDIUM + /// The icon size width of the mob. Used for langchat resizing. + var/icon_size = 32 var/list/progressbars diff --git a/html/changelogs/mattatlas-langchat.yml b/html/changelogs/mattatlas-langchat.yml new file mode 100644 index 00000000000..cf079aad0fc --- /dev/null +++ b/html/changelogs/mattatlas-langchat.yml @@ -0,0 +1,59 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# - (fixes bugs) +# wip +# - (work in progress) +# qol +# - (quality of life) +# soundadd +# - (adds a sound) +# sounddel +# - (removes a sound) +# rscadd +# - (adds a feature) +# rscdel +# - (removes a feature) +# imageadd +# - (adds an image or sprite) +# imagedel +# - (removes an image or sprite) +# spellcheck +# - (fixes spelling or grammar) +# experiment +# - (experimental change) +# balance +# - (balance changes) +# code_imp +# - (misc internal code change) +# refactor +# - (refactors code) +# config +# - (makes a change to the config files) +# admin +# - (makes changes to administrator tools) +# server +# - (miscellaneous changes to server) +################################# + +# Your name. +author: MattAtlas (Porting), CM Developers (original code) + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit. +# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Replaced Runechat with Langchat from CM for better functionality and clearer text." + - rscadd: "Ported screen text from CM." diff --git a/icons/mob/chat_icons.dmi b/icons/mob/chat_icons.dmi new file mode 100644 index 00000000000..2796a2248e4 Binary files /dev/null and b/icons/mob/chat_icons.dmi differ