diff --git a/code/__DEFINES/dcs/signals/signals_admin.dm b/code/__DEFINES/dcs/signals/signals_admin.dm index 9b4b06ad5d4..dafd135ead7 100644 --- a/code/__DEFINES/dcs/signals/signals_admin.dm +++ b/code/__DEFINES/dcs/signals/signals_admin.dm @@ -3,5 +3,8 @@ /// Fired when an adminhelp is made inactive either due to closing or resolving. #define COMSIG_ADMIN_HELP_MADE_INACTIVE "admin_help_made_inactive" -/// Called when the player replies. From /client/proc/cmd_admin_pm(). +/// Called on the /datum/admin_help when the player replies. From /client/proc/cmd_admin_pm(). #define COMSIG_ADMIN_HELP_REPLIED "admin_help_replied" + +/// Called on a client when a player receives an adminhelp. From /client/proc/receive_ahelp(message) +#define COMSIG_ADMIN_HELP_RECEIVED "admin_help_received" diff --git a/code/__DEFINES/dcs/signals/signals_client.dm b/code/__DEFINES/dcs/signals/signals_client.dm index e20946d925a..07e5251cad3 100644 --- a/code/__DEFINES/dcs/signals/signals_client.dm +++ b/code/__DEFINES/dcs/signals/signals_client.dm @@ -1,7 +1,14 @@ -// Yes, they do support this - // from /client/proc/change_view() : (new_size) #define COMSIG_VIEW_SET "view_set" // from /client/proc/handle_popup_close() : (window_id) #define COMSIG_POPUP_CLEARED "popup_cleared" + +/// Called after one or more verbs are added: (list of verbs added) +#define COMSIG_CLIENT_VERB_ADDED "client_verb_added" + +/// Called after one or more verbs are added: (list of verbs added) +#define COMSIG_CLIENT_VERB_REMOVED "client_verb_removed" + +/// Called after a client logs into a mob: (mob) +#define COMSIG_CLIENT_MOB_LOGIN "client_mob_changed" diff --git a/code/__DEFINES/dcs/signals/signals_global.dm b/code/__DEFINES/dcs/signals/signals_global.dm index 79e35ec3fee..bafb53f1ffd 100644 --- a/code/__DEFINES/dcs/signals/signals_global.dm +++ b/code/__DEFINES/dcs/signals/signals_global.dm @@ -74,3 +74,7 @@ #define COMSIG_GLOB_LIGHT_MECHANISM_COMPLETED "!light_mechanism_completed" /// Global Signal sent when the crew wins the revolution (No arguments). #define COMSIG_GLOB_REVOLUTION_VICTORY "!revolution_victory" + +/// Global signal called after the station changes its name. +/// (new_name, old_name) +#define COMSIG_GLOB_STATION_NAME_CHANGED "!station_name_changed" diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index 9bf9988160f..d526b9c87b0 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -85,13 +85,22 @@ #define HUD_PLANE 40 #define ABOVE_HUD_PLANE 41 -///Plane of the "splash" icon used that shows on the lobby screen. only render plate planes should be above this +///Plane of the "splash" icon used that shows on the lobby screen #define SPLASHSCREEN_PLANE 50 +// The largest plane here must still be less than RENDER_PLANE_GAME + //-------------------- Rendering --------------------- #define RENDER_PLANE_GAME 100 #define RENDER_PLANE_NON_GAME 101 -#define RENDER_PLANE_MASTER 102 + +// Only VERY special planes should be here, as they are above not just the game, but the UI planes as well. + +/// Plane related to the menu when pressing Escape. +/// Needed so that we can apply a blur effect to EVERYTHING, and guarantee we are above all UI. +#define ESCAPE_MENU_PLANE 105 + +#define RENDER_PLANE_MASTER 110 // Lummox I swear to god I will find you // NOTE! You can only ever have planes greater then -10000, if you add too many with large offsets you will brick multiz @@ -252,6 +261,7 @@ ///Plane master controller keys #define PLANE_MASTERS_GAME "plane_masters_game" +#define PLANE_MASTERS_NON_MASTER "plane_masters_non_master" #define PLANE_MASTERS_COLORBLIND "plane_masters_colorblind" //Plane master critical flags diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm index 740223a3e5f..df891b4c5a8 100644 --- a/code/__DEFINES/text.dm +++ b/code/__DEFINES/text.dm @@ -4,6 +4,9 @@ /// Prepares a text to be used for maptext. Use this so it doesn't look hideous. #define MAPTEXT(text) {"[##text]"} +/// Prepares a text to be used for maptext, using a font that can handle larger text better. +#define MAPTEXT_VCR_OSD_MONO(text) {"[##text]"} + /// Macro from Lummox used to get height from a MeasureText proc. /// resolves the MeasureText() return value once, then resolves the height, then sets return_var to that. #define WXH_TO_HEIGHT(measurement, return_var) \ diff --git a/code/__DEFINES/time.dm b/code/__DEFINES/time.dm index 847eddcf25c..64955e4a569 100644 --- a/code/__DEFINES/time.dm +++ b/code/__DEFINES/time.dm @@ -2,7 +2,7 @@ #define MIDNIGHT_ROLLOVER 864000 ///displays the current time into the round, with a lot of extra code just there for ensuring it looks okay after an entire day passes -#define ROUND_TIME ( "[world.time - SSticker.round_start_time > MIDNIGHT_ROLLOVER ? "[round((world.time - SSticker.round_start_time)/MIDNIGHT_ROLLOVER)]:[worldtime2text()]" : worldtime2text()]" ) +#define ROUND_TIME(...) ( "[world.time - SSticker.round_start_time > MIDNIGHT_ROLLOVER ? "[round((world.time - SSticker.round_start_time)/MIDNIGHT_ROLLOVER)]:[worldtime2text()]" : worldtime2text()]" ) /// Define that just has the current in-universe year for use in whatever context you might want to display that in. (For example, 2022 -> 2562 given a 540 year offset) #define CURRENT_STATION_YEAR (GLOB.year_integer + STATION_YEAR_OFFSET) diff --git a/code/__HELPERS/names.dm b/code/__HELPERS/names.dm index d5356227613..82d9ea1ffab 100644 --- a/code/__HELPERS/names.dm +++ b/code/__HELPERS/names.dm @@ -45,8 +45,9 @@ GLOBAL_VAR(command_name) return GLOB.station_name -/proc/set_station_name(newname) - GLOB.station_name = newname +/proc/set_station_name(new_name) + var/old_name = GLOB.station_name + GLOB.station_name = new_name var/config_server_name = CONFIG_GET(string/servername) if(config_server_name) @@ -54,6 +55,7 @@ GLOBAL_VAR(command_name) else world.name = html_decode(GLOB.station_name) + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_STATION_NAME_CHANGED, new_name, old_name) /proc/new_station_name() var/random = rand(1,5) diff --git a/code/__HELPERS/verbs.dm b/code/__HELPERS/verbs.dm index d042929f118..badd3c6727a 100644 --- a/code/__HELPERS/verbs.dm +++ b/code/__HELPERS/verbs.dm @@ -46,6 +46,8 @@ target.stat_panel.send_message("add_verb_list", output_list) + SEND_SIGNAL(target, COMSIG_CLIENT_VERB_ADDED, verbs_list) + /** * handles removing verb and sending it to browser to update, use this for removing verbs * @@ -92,3 +94,5 @@ output_list[++output_list.len] = list(verb_to_remove.category, verb_to_remove.name) target.stat_panel.send_message("remove_verb_list", output_list) + + SEND_SIGNAL(target, COMSIG_CLIENT_VERB_REMOVED, verbs_list) diff --git a/code/_onclick/hud/generic_dextrous.dm b/code/_onclick/hud/generic_dextrous.dm index 9358ef18384..c0fa5e481c9 100644 --- a/code/_onclick/hud/generic_dextrous.dm +++ b/code/_onclick/hud/generic_dextrous.dm @@ -50,7 +50,7 @@ using.hud = src static_inventory += using - mymob.client.screen = list() + mymob.client.clear_screen() for(var/atom/movable/screen/inventory/inv in (static_inventory + toggleable_inventory)) if(inv.slot_id) diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index a8462025d5c..6d4e399732e 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -318,7 +318,7 @@ GLOBAL_LIST_INIT(available_erp_ui_styles, list( // This code is the absolute fucking worst, I want it to go die in a fire // Seriously, why - screenmob.client.screen = list() + screenmob.client.clear_screen() screenmob.client.apply_clickcatcher() var/display_hud_version = version diff --git a/code/_onclick/hud/rendering/plane_master.dm b/code/_onclick/hud/rendering/plane_master.dm index 5edf9ee99dc..10ab271c19c 100644 --- a/code/_onclick/hud/rendering/plane_master.dm +++ b/code/_onclick/hud/rendering/plane_master.dm @@ -615,8 +615,16 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/plane_master) /atom/movable/screen/plane_master/splashscreen name = "Splashscreen" - documentation = "Anything that's drawn above LITERALLY everything else. Think cinimatics and the well, spashscreen." + documentation = "Cinematics and the splash screen." plane = SPLASHSCREEN_PLANE appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR render_relay_planes = list(RENDER_PLANE_NON_GAME) allows_offsetting = FALSE + +/atom/movable/screen/plane_master/escape_menu + name = "Escape Menu" + documentation = "Anything relating to the escape menu." + plane = ESCAPE_MENU_PLANE + appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR + render_relay_planes = list(RENDER_PLANE_MASTER) + allows_offsetting = FALSE diff --git a/code/_onclick/hud/rendering/plane_master_controller.dm b/code/_onclick/hud/rendering/plane_master_controller.dm index 6e7676a5c71..f4b71611716 100644 --- a/code/_onclick/hud/rendering/plane_master_controller.dm +++ b/code/_onclick/hud/rendering/plane_master_controller.dm @@ -86,6 +86,15 @@ INITIALIZE_IMMEDIATE(/atom/movable/plane_master_controller) RENDER_PLANE_GAME ) +/// Exists for convienience when referencing all non-master render plates. +/// This is the whole game and the UI, but not the escape menu. +/atom/movable/plane_master_controller/non_master + name = PLANE_MASTERS_NON_MASTER + controlled_planes = list( + RENDER_PLANE_GAME, + RENDER_PLANE_NON_GAME, + ) + /// Exists for convienience when referencing all game render plates /atom/movable/plane_master_controller/colorblind name = PLANE_MASTERS_COLORBLIND diff --git a/code/_onclick/hud/screen_object_holder.dm b/code/_onclick/hud/screen_object_holder.dm new file mode 100644 index 00000000000..ba6a9b96172 --- /dev/null +++ b/code/_onclick/hud/screen_object_holder.dm @@ -0,0 +1,59 @@ +/// A helper instance that will handle adding objects from the client's screen +/// to easily remove from later. +/datum/screen_object_holder + VAR_PRIVATE + client/client + list/screen_objects = list() + list/protected_screen_objects = list() + +/datum/screen_object_holder/New(client/client) + ASSERT(istype(client)) + + src.client = client + + RegisterSignal(client, COMSIG_PARENT_QDELETING, PROC_REF(on_parent_qdel)) + +/datum/screen_object_holder/Destroy() + clear() + client = null + + return ..() + +/// Gives the screen object to the client, qdel'ing it when it's cleared +/datum/screen_object_holder/proc/give_screen_object(atom/screen_object) + ASSERT(istype(screen_object)) + + screen_objects += screen_object + client?.screen += screen_object + +/// Gives the screen object to the client, but does not qdel it when it's cleared +/datum/screen_object_holder/proc/give_protected_screen_object(atom/screen_object) + ASSERT(istype(screen_object)) + + protected_screen_objects += screen_object + client?.screen += screen_object + +/datum/screen_object_holder/proc/remove_screen_object(atom/screen_object) + ASSERT(istype(screen_object)) + ASSERT((screen_object in screen_objects) || (screen_object in protected_screen_objects)) + + screen_objects -= screen_object + protected_screen_objects -= screen_object + client?.screen -= screen_object + +/datum/screen_object_holder/proc/clear() + client?.screen -= screen_objects + client?.screen -= protected_screen_objects + + QDEL_LIST(screen_objects) + protected_screen_objects.Cut() + +// We don't qdel here, as clients leaving should not be a concern for consumers +// Consumers ought to be qdel'ing this on their own Destroy, but we shouldn't +// hard del because they aren't watching for the client, that's our job. +/datum/screen_object_holder/proc/on_parent_qdel() + PRIVATE_PROC(TRUE) + SIGNAL_HANDLER + + clear() + client = null diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 95175edd60a..326cb4db9a0 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -33,6 +33,9 @@ */ var/del_on_map_removal = TRUE + /// If FALSE, this will not be cleared when calling /client/clear_screen() + var/clear_with_screen = TRUE + /atom/movable/screen/Destroy() master = null hud = null diff --git a/code/controllers/subsystem/input.dm b/code/controllers/subsystem/input.dm index c39712771be..65dc1e31a1e 100644 --- a/code/controllers/subsystem/input.dm +++ b/code/controllers/subsystem/input.dm @@ -38,7 +38,7 @@ VERB_MANAGER_SUBSYSTEM_DEF(input) "Any+UP" = "\"KeyUp \[\[*\]\]\"", "Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"", "Tab" = "\".winset \\\"input.focus=true?map.focus=true:input.focus=true\\\"\"", - "Escape" = "Reset-Held-Keys", + "Escape" = "Open-Escape-Menu", ) // Badmins just wanna have fun ♪ diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm index 6417858d133..b0e64940f44 100644 --- a/code/controllers/subsystem/statpanel.dm +++ b/code/controllers/subsystem/statpanel.dm @@ -29,7 +29,7 @@ SUBSYSTEM_DEF(statpanels) cached ? "Next Map: [cached.map_name]" : null, "Round ID: [GLOB.round_id ? GLOB.round_id : "NULL"]", "Server Time: [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")]", - "Round Time: [ROUND_TIME]", + "Round Time: [ROUND_TIME()]", "Station Time: [station_time_timestamp()]", "Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)" ) diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index 02d2f3fe430..39bd15caec7 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -202,6 +202,10 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) var/webhook_sent = WEBHOOK_NONE /// List of player interactions var/list/player_interactions + /// List of admin ckeys that are involved, like through responding + var/list/admins_involved = list() + /// Has the player replied to this ticket yet? + var/player_replied = FALSE //SKYRAT EDIT START /// Have we requested this ticket to stop being part of the Ticket Ping subsystem? @@ -294,7 +298,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) "PLAYERS" = player_count, "ROUND STATE" = round_state, "ROUND ID" = GLOB.round_id, - "ROUND TIME" = ROUND_TIME, + "ROUND TIME" = ROUND_TIME(), "MESSAGE" = message, "ADMINS" = admin_text, ) @@ -366,9 +370,12 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) return ..() /datum/admin_help/proc/AddInteraction(formatted_message, player_message) - if(heard_by_no_admins && usr && usr.ckey != initiator_ckey) - heard_by_no_admins = FALSE - send2adminchat(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]") + if (!isnull(usr) && usr.ckey != initiator_ckey) + admins_involved |= usr.ckey + if(heard_by_no_admins) + heard_by_no_admins = FALSE + send2adminchat(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]") + ticket_interactions += "[time_stamp()]: [formatted_message]" if (!isnull(player_message)) player_interactions += "[time_stamp()]: [player_message]" @@ -434,12 +441,20 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) confidential = TRUE) //show it to the person adminhelping too - to_chat(initiator, - type = MESSAGE_TYPE_ADMINPM, - html = span_adminnotice("PM to-Admins: [span_linkify(msg)]"), - confidential = TRUE) + reply_to_admins_notification(msg) SSblackbox.LogAhelp(id, "Ticket Opened", msg, null, initiator.ckey, urgent = urgent) +/// Sends a message to the player that they are replying to admins. +/datum/admin_help/proc/reply_to_admins_notification(message) + to_chat( + initiator, + type = MESSAGE_TYPE_ADMINPM, + html = span_notice("PM to-Admins: [span_linkify(message)]"), + confidential = TRUE, + ) + + player_replied = TRUE + //Reopen a closed ticket /datum/admin_help/proc/Reopen() if(state == AHELP_ACTIVE) diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm index 78931c1ee7a..8293c9e3187 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -326,13 +326,12 @@ var/name_key_with_link = key_name(src, TRUE, TRUE) if(ambiguious_recipient == EXTERNAL_PM_USER) - to_chat(src, - type = MESSAGE_TYPE_ADMINPM, - html = span_notice("PM to-Admins: [span_linkify(raw_message)]"), - confidential = TRUE) var/datum/admin_help/new_admin_help = admin_ticket_log(src, "Reply PM from-[name_key_with_link] to External: [keyword_parsed_msg]", player_message = "Reply PM from-[name_key_with_link] to External: [send_message]") + + new_admin_help.reply_to_admins_notification(raw_message) + var/new_help_id = new_admin_help?.id externalreplyamount-- @@ -409,10 +408,12 @@ type = MESSAGE_TYPE_ADMINPM, html = "-- Administrator private message --", confidential = TRUE) - to_chat(recipient, - type = MESSAGE_TYPE_ADMINPM, - html = span_adminsay("Admin PM from-[link_to_us]: [span_linkify(send_message)]"), - confidential = TRUE) + + recipient.receive_ahelp( + link_to_us, + span_linkify(send_message), + ) + to_chat(recipient, type = MESSAGE_TYPE_ADMINPM, html = span_adminsay("Click on the administrator's name to reply."), @@ -480,10 +481,12 @@ // Admin on admin violence first if(our_holder) - to_chat(recipient, - type = MESSAGE_TYPE_ADMINPM, - html = span_danger("Admin PM from-[name_key_with_link]: [span_linkify(keyword_parsed_msg)]"), - confidential = TRUE) + recipient.receive_ahelp( + name_key_with_link, + span_linkify(keyword_parsed_msg), + "danger", + ) + to_chat(src, type = MESSAGE_TYPE_ADMINPM, html = span_notice("Admin PM to-[their_name_with_link]: [span_linkify(keyword_parsed_msg)]"), @@ -516,11 +519,10 @@ type = MESSAGE_TYPE_ADMINPM, html = span_danger("[replymsg]"), confidential = TRUE) - to_chat(src, - type = MESSAGE_TYPE_ADMINPM, - html = span_notice("PM to-Admins: [span_linkify(send_message)]"), - confidential = TRUE) + + ticket.reply_to_admins_notification(send_message) SSblackbox.LogAhelp(ticket_id, "Reply", send_message, recip_ckey, our_ckey) + return TRUE /// Notifies all admins about the existance of an admin pm, then logs the pm @@ -732,10 +734,12 @@ type = MESSAGE_TYPE_ADMINPM, html = "-- Administrator private message --", confidential = TRUE) - to_chat(recipient, - type = MESSAGE_TYPE_ADMINPM, - html = span_adminsay("Admin PM from-[adminname]: [message]"), - confidential = TRUE) + + recipient.receive_ahelp( + "[adminname]", + message, + ) + to_chat(recipient, type = MESSAGE_TYPE_ADMINPM, html = span_adminsay("Click on the administrator's name to reply."), @@ -781,6 +785,17 @@ return GLOB.directory[searching_ckey] +/client/proc/receive_ahelp(reply_to, message, span_class = "adminsay") + to_chat( + src, + type = MESSAGE_TYPE_ADMINPM, + html = "Admin PM from-[reply_to]: [message]", + confidential = TRUE, + ) + + current_ticket?.player_replied = FALSE + + SEND_SIGNAL(src, COMSIG_ADMIN_HELP_RECEIVED, message) #undef EXTERNAL_PM_USER #undef EXTERNALREPLYCOUNT diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 72cd26e43a3..3f68c238007 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -1333,3 +1333,13 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( winset(usr, "mapwindow.status_bar", "is-visible=true") else winset(usr, "mapwindow.status_bar", "is-visible=false") + +/// Clears the client's screen, aside from ones that opt out +/client/proc/clear_screen() + for (var/object in screen) + if (istype(object, /atom/movable/screen)) + var/atom/movable/screen/screen_object = object + if (!screen_object.clear_with_screen) + continue + + screen -= object diff --git a/code/modules/client/verbs/reset_held_keys.dm b/code/modules/client/verbs/reset_held_keys.dm deleted file mode 100644 index d5a25044090..00000000000 --- a/code/modules/client/verbs/reset_held_keys.dm +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Manually clears any held keys, in case due to lag or other undefined behavior a key gets stuck. - * - * Hardcoded to the ESC key. - */ -/client/verb/reset_held_keys() - set name = "Reset Held Keys" - set hidden = TRUE - - for(var/key in keys_held) - keyUp(key) - - //In case one got stuck and the previous loop didn't clean it, somehow. - for(var/key in key_combos_held) - keyUp(key_combos_held[key]) - - winset(src, "input", "text=[null]") //SKYRAT EDIT ADDITION - QOL diff --git a/code/modules/escape_menu/details.dm b/code/modules/escape_menu/details.dm new file mode 100644 index 00000000000..5a9a755a1cd --- /dev/null +++ b/code/modules/escape_menu/details.dm @@ -0,0 +1,43 @@ +// This doesn't instantiate right away, since we rely on other GLOBs +GLOBAL_DATUM(escape_menu_details, /atom/movable/screen/escape_menu/details) + +/// Provides a singleton for the escape menu details screen. +/proc/give_escape_menu_details() + if (isnull(GLOB.escape_menu_details)) + GLOB.escape_menu_details = new + + return GLOB.escape_menu_details + +/atom/movable/screen/escape_menu/details + screen_loc = "EAST:-180,NORTH:-25" + maptext_height = 100 + maptext_width = 200 + +/atom/movable/screen/escape_menu/details/Initialize(mapload) + . = ..() + + update_text() + START_PROCESSING(SSescape_menu, src) + +/atom/movable/screen/escape_menu/details/Destroy() + if (GLOB.escape_menu_details == src) + stack_trace("Something tried to delete the escape menu details screen") + return QDEL_HINT_LETMELIVE + + STOP_PROCESSING(SSescape_menu, src) + return ..() + +/atom/movable/screen/escape_menu/details/process(delta_time) + update_text() + +/atom/movable/screen/escape_menu/details/proc/update_text() + var/new_maptext = {" + + Round ID: [GLOB.round_id || "Unset"]
+ Round Time: [ROUND_TIME()]
+ Map: [SSmapping.config?.map_name || "Loading..."]
+ Time Dilation: [round(SStime_track.time_dilation_current,1)]%
+
+ "} + + maptext = MAPTEXT(new_maptext) diff --git a/code/modules/escape_menu/dimmer.dm b/code/modules/escape_menu/dimmer.dm new file mode 100644 index 00000000000..6bdf87846b6 --- /dev/null +++ b/code/modules/escape_menu/dimmer.dm @@ -0,0 +1,8 @@ +/atom/movable/screen/fullscreen/dimmer + icon = 'icons/hud/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "black" + alpha = 200 + plane = ESCAPE_MENU_PLANE + mouse_opacity = MOUSE_OPACITY_OPAQUE + clear_with_screen = FALSE diff --git a/code/modules/escape_menu/escape_menu.dm b/code/modules/escape_menu/escape_menu.dm new file mode 100644 index 00000000000..aebd636e5e9 --- /dev/null +++ b/code/modules/escape_menu/escape_menu.dm @@ -0,0 +1,127 @@ +GLOBAL_LIST_EMPTY(escape_menus) + +/// Opens the escape menu. +/// Verb, hardcoded to Escape, set in the client skin. +/client/verb/open_escape_menu() + set name = "Open Escape Menu" + set hidden = TRUE + + var/current_escape_menu = GLOB.escape_menus[ckey] + if (!isnull(current_escape_menu)) + qdel(current_escape_menu) + return + + reset_held_keys() + + new /datum/escape_menu(src) + +#define PAGE_HOME "PAGE_HOME" +#define PAGE_LEAVE_BODY "PAGE_LEAVE_BODY" + +/datum/escape_menu + /// The client that owns this escape menu + var/client/client + + VAR_PRIVATE + ckey + + datum/screen_object_holder/base_holder + datum/screen_object_holder/page_holder + + atom/movable/plane_master_controller/plane_master_controller + + menu_page = PAGE_HOME + +/datum/escape_menu/New(client/client) + ASSERT(!(client.ckey in GLOB.escape_menus)) + + ckey = client?.ckey + src.client = client + + base_holder = new(client) + populate_base_ui() + + page_holder = new(client) + show_page() + + RegisterSignal(client, COMSIG_PARENT_QDELETING, PROC_REF(on_client_qdel)) + RegisterSignal(client, COMSIG_CLIENT_MOB_LOGIN, PROC_REF(on_client_mob_login)) + + if (!isnull(ckey)) + GLOB.escape_menus[ckey] = src + +/datum/escape_menu/Destroy(force, ...) + QDEL_NULL(base_holder) + QDEL_NULL(page_holder) + + GLOB.escape_menus -= ckey + plane_master_controller.remove_filter("escape_menu_blur") + + return ..() + +/datum/escape_menu/proc/on_client_qdel() + SIGNAL_HANDLER + PRIVATE_PROC(TRUE) + + qdel(src) + +/datum/escape_menu/proc/on_client_mob_login() + SIGNAL_HANDLER + PRIVATE_PROC(TRUE) + + if (menu_page == PAGE_LEAVE_BODY) + qdel(src) + +/datum/escape_menu/proc/show_page() + PRIVATE_PROC(TRUE) + + page_holder.clear() + + switch (menu_page) + if (PAGE_HOME) + show_home_page() + if (PAGE_LEAVE_BODY) + show_leave_body_page() + else + CRASH("Unknown escape menu page: [menu_page]") + +/datum/escape_menu/proc/populate_base_ui() + PRIVATE_PROC(TRUE) + + base_holder.give_screen_object(new /atom/movable/screen/fullscreen/dimmer) + add_blur() + + base_holder.give_protected_screen_object(give_escape_menu_title()) + base_holder.give_protected_screen_object(give_escape_menu_details()) + +/datum/escape_menu/proc/open_home_page() + PRIVATE_PROC(TRUE) + + menu_page = PAGE_HOME + show_page() + +/datum/escape_menu/proc/open_leave_body() + PRIVATE_PROC(TRUE) + + menu_page = PAGE_LEAVE_BODY + show_page() + +/datum/escape_menu/proc/add_blur() + PRIVATE_PROC(TRUE) + + var/list/plane_master_controllers = client?.mob.hud_used.plane_master_controllers + if (isnull(plane_master_controllers)) + return + + plane_master_controller = plane_master_controllers[PLANE_MASTERS_NON_MASTER] + plane_master_controller.add_filter("escape_menu_blur", 1, list("type" = "blur", "size" = 2)) + +/atom/movable/screen/escape_menu + plane = ESCAPE_MENU_PLANE + clear_with_screen = FALSE + +// The escape menu can be opened before SSatoms +INITIALIZE_IMMEDIATE(/atom/movable/screen/escape_menu) + +#undef PAGE_HOME +#undef PAGE_LEAVE_BODY diff --git a/code/modules/escape_menu/home_page.dm b/code/modules/escape_menu/home_page.dm new file mode 100644 index 00000000000..6616de821fd --- /dev/null +++ b/code/modules/escape_menu/home_page.dm @@ -0,0 +1,285 @@ +/datum/escape_menu/proc/show_home_page() + page_holder.give_screen_object( + new /atom/movable/screen/escape_menu/home_button( + null, + src, + "Resume", + /* offset = */ 0, + CALLBACK(src, PROC_REF(home_resume)), + ) + ) + + page_holder.give_screen_object( + new /atom/movable/screen/escape_menu/home_button( + null, + src, + "Settings", + /* offset = */ 1, + CALLBACK(src, PROC_REF(home_open_settings)), + ) + ) + + page_holder.give_screen_object( + new /atom/movable/screen/escape_menu/home_button/admin_help( + null, + src, + "Admin Help", + /* offset = */ 2, + ) + ) + + page_holder.give_screen_object( + new /atom/movable/screen/escape_menu/home_button/leave_body( + null, + src, + "Leave Body", + /* offset = */ 3, + CALLBACK(src, PROC_REF(open_leave_body)), + ) + ) + +/datum/escape_menu/proc/home_resume() + qdel(src) + +/datum/escape_menu/proc/home_open_settings() + client?.prefs.ui_interact(client?.mob) + qdel(src) + +/atom/movable/screen/escape_menu/home_button + mouse_opacity = MOUSE_OPACITY_OPAQUE + + VAR_PRIVATE + atom/movable/screen/escape_menu/home_button_text/home_button_text + datum/escape_menu/escape_menu + datum/callback/on_click_callback + +/atom/movable/screen/escape_menu/home_button/Initialize( + mapload, + datum/escape_menu/escape_menu, + button_text, + offset, + on_click_callback, +) + . = ..() + + src.escape_menu = escape_menu + src.on_click_callback = on_click_callback + + home_button_text = new /atom/movable/screen/escape_menu/home_button_text( + src, + button_text, + ) + + vis_contents += home_button_text + + screen_loc = "NORTH:-[100 + (32 * offset)],WEST:110" + transform = transform.Scale(6, 1) + +/atom/movable/screen/escape_menu/home_button/Destroy() + escape_menu = null + QDEL_NULL(on_click_callback) + + return ..() + +/atom/movable/screen/escape_menu/home_button/Click(location, control, params) + if (!enabled()) + return + + on_click_callback.InvokeAsync() + +/atom/movable/screen/escape_menu/home_button/MouseEntered(location, control, params) + home_button_text.set_hovered(TRUE) + +/atom/movable/screen/escape_menu/home_button/MouseExited(location, control, params) + home_button_text.set_hovered(FALSE) + +/atom/movable/screen/escape_menu/home_button/proc/text_color() + return enabled() ? "white" : "gray" + +/atom/movable/screen/escape_menu/home_button/proc/enabled() + return TRUE + +// Needs to be separated so it doesn't scale +/atom/movable/screen/escape_menu/home_button_text + maptext_width = 200 + maptext_height = 50 + pixel_x = -80 + + VAR_PRIVATE + button_text + hovered = FALSE + +/atom/movable/screen/escape_menu/home_button_text/Initialize(mapload, button_text) + . = ..() + + src.button_text = button_text + update_text() + +/// Sets the hovered state of the button, and updates the text +/atom/movable/screen/escape_menu/home_button_text/proc/set_hovered(hovered) + if (src.hovered == hovered) + return + + src.hovered = hovered + update_text() + +/atom/movable/screen/escape_menu/home_button_text/proc/update_text() + var/atom/movable/screen/escape_menu/home_button/escape_menu_loc = loc + + maptext = MAPTEXT_VCR_OSD_MONO("[button_text]") + + if (hovered) + maptext = "[maptext]" + +/atom/movable/screen/escape_menu/home_button/admin_help + VAR_PRIVATE + current_blink = FALSE + is_blinking = FALSE + last_blink_time = 0 + + blink_interval = 0.4 SECONDS + +/atom/movable/screen/escape_menu/home_button/admin_help/Initialize( + mapload, + datum/escape_menu/escape_menu, + button_text, + offset, + on_click_callback, +) + . = ..() + + RegisterSignal(escape_menu.client, COMSIG_ADMIN_HELP_RECEIVED, PROC_REF(on_admin_help_received)) + RegisterSignals(escape_menu.client, list(COMSIG_CLIENT_VERB_ADDED, COMSIG_CLIENT_VERB_REMOVED), PROC_REF(on_client_verb_changed)) + + var/datum/admin_help/current_ticket = escape_menu.client?.current_ticket + if (!isnull(current_ticket)) + connect_ticket(current_ticket) + if (!current_ticket?.player_replied) + begin_processing() + +/atom/movable/screen/escape_menu/home_button/admin_help/Click(location, control, params) + if (!enabled()) + return + + QDEL_IN(escape_menu, 0) + + var/client/client = escape_menu.client + + if (has_open_adminhelp()) + client?.view_latest_ticket() + else + client?.adminhelp() + +/atom/movable/screen/escape_menu/home_button/admin_help/proc/has_open_adminhelp() + var/client/client = escape_menu.client + + var/datum/admin_help/current_ticket = client?.current_ticket + + // This is null with a closed ticket. + // This is okay since the View Latest Ticket panel already tells you if your ticket is closed, intentionally. + if (isnull(current_ticket)) + return FALSE + + // If we sent a ticket, but nobody has responded, send another one instead. + // Not worth opening a menu when there's nothing to read, you're only going to want to send. + if (length(current_ticket.admins_involved - client?.ckey) == 0) + return FALSE + + return TRUE + +/atom/movable/screen/escape_menu/home_button/admin_help/proc/on_admin_help_received() + SIGNAL_HANDLER + + begin_processing() + +/atom/movable/screen/escape_menu/home_button/admin_help/proc/on_client_verb_changed(client/source, list/verbs_changed) + SIGNAL_HANDLER + + if (/client/verb/adminhelp in verbs_changed) + home_button_text.update_text() + +/atom/movable/screen/escape_menu/home_button/admin_help/proc/begin_processing() + if (is_blinking) + return + + is_blinking = TRUE + current_blink = TRUE + START_PROCESSING(SSescape_menu, src) + home_button_text.update_text() + +/atom/movable/screen/escape_menu/home_button/admin_help/proc/end_processing() + if (!is_blinking) + return + + is_blinking = FALSE + current_blink = FALSE + STOP_PROCESSING(SSescape_menu, src) + home_button_text.update_text() + +/atom/movable/screen/escape_menu/home_button/admin_help/proc/connect_ticket(datum/admin_help/admin_help) + ASSERT(istype(admin_help)) + + RegisterSignal(admin_help, COMSIG_ADMIN_HELP_REPLIED, PROC_REF(on_admin_help_replied)) + +/atom/movable/screen/escape_menu/home_button/admin_help/proc/on_admin_help_replied() + SIGNAL_HANDLER + + end_processing() + +/atom/movable/screen/escape_menu/home_button/admin_help/enabled() + if (!..()) + return FALSE + + if (!has_open_adminhelp()) + return /client/verb/adminhelp in escape_menu.client?.verbs + + return TRUE + +/atom/movable/screen/escape_menu/home_button/admin_help/process(delta_time) + if (world.time - last_blink_time < blink_interval) + return + + current_blink = !current_blink + last_blink_time = world.time + home_button_text.update_text() + +/atom/movable/screen/escape_menu/home_button/admin_help/text_color() + if (!enabled()) + return ..() + + return current_blink ? "red" : ..() + +/atom/movable/screen/escape_menu/home_button/admin_help/MouseEntered(location, control, params) + . = ..() + + if (is_blinking) + openToolTip(usr, src, params, content = "An admin is trying to talk to you!") + +/atom/movable/screen/escape_menu/home_button/admin_help/MouseExited(location, control, params) + . = ..() + + closeToolTip(usr) + +/atom/movable/screen/escape_menu/home_button/leave_body + +/atom/movable/screen/escape_menu/home_button/leave_body/Initialize( + mapload, + datum/escape_menu/escape_menu, + button_text, + offset, + on_click_callback, +) + . = ..() + + RegisterSignal(escape_menu.client, COMSIG_CLIENT_MOB_LOGIN, PROC_REF(on_client_mob_login)) + +/atom/movable/screen/escape_menu/home_button/leave_body/enabled() + if (!..()) + return FALSE + + return isliving(escape_menu.client?.mob) + +/atom/movable/screen/escape_menu/home_button/leave_body/proc/on_client_mob_login() + SIGNAL_HANDLER + + home_button_text.update_text() diff --git a/code/modules/escape_menu/leave_body.dm b/code/modules/escape_menu/leave_body.dm new file mode 100644 index 00000000000..fdba3b6ad9d --- /dev/null +++ b/code/modules/escape_menu/leave_body.dm @@ -0,0 +1,134 @@ +/datum/escape_menu/proc/show_leave_body_page() + PRIVATE_PROC(TRUE) + + var/static/dead_clown + if (isnull(dead_clown)) + if (MC_RUNNING(SSatoms.init_stage)) // We're about to create a bunch of atoms for a human + dead_clown = create_dead_clown() + else + stack_trace("The leave body menu was opened before the atoms SS. This shouldn't be possible, as the leave body menu should only be accessible when you have a body.") + + //SKYRAT EDIT REMOVAL BEGIN - Suicide Disabled + /* + page_holder.give_screen_object(new /atom/movable/screen/escape_menu/leave_body_button( + src, + "Suicide", + "Perform a dramatic suicide in game", + /* pixel_offset = */ -105, + CALLBACK(src, PROC_REF(leave_suicide)), + /* button_overlay = */ dead_clown, + )) + */ + //SKYRAT EDIT REMOVAL END + + page_holder.give_screen_object( + new /atom/movable/screen/escape_menu/leave_body_button( + src, + "Ghost", + "Exit quietly, leaving your body", + /* pixel_offset = */ 0, + CALLBACK(src, PROC_REF(leave_ghost)), + /* button_overlay = */ "ghost", + ) + ) + + page_holder.give_screen_object( + new /atom/movable/screen/escape_menu/leave_body_button( + src, + "Back", + /* tooltip_text = */ null, + /* pixel_offset = */ 105, + CALLBACK(src, PROC_REF(open_home_page)), + /* button_overlay = */ "back", + ) + ) + +/datum/escape_menu/proc/create_dead_clown() + PRIVATE_PROC(TRUE) + + var/mob/living/carbon/human/consistent/human = new + human.equipOutfit(/datum/outfit/job/clown) + + var/mutable_appearance/appearance = new(human.appearance) + appearance.plane = ESCAPE_MENU_PLANE + + // SpacemanDMM bug prevents us from just chain applying these :( + appearance.transform = appearance.transform.Scale(2.5, 2.5) + appearance.transform = appearance.transform.Turn(90) + appearance.transform = appearance.transform.Translate(34, 24) + + qdel(human) + + return appearance + +/datum/escape_menu/proc/leave_ghost() + PRIVATE_PROC(TRUE) + + // Not guaranteed to be living. Everything defines verb/ghost separately. Fuck you. + var/mob/living/living_user = client?.mob + living_user?.ghost() + +//SKYRAT EDIT REMOVAL BEGIN - Suicide Disabled +/* +/datum/escape_menu/proc/leave_suicide() + PRIVATE_PROC(TRUE) + + // Not guaranteed to be human. Everything defines verb/suicide separately. Fuck you, still. + var/mob/living/carbon/human/human_user = client?.mob + human_user?.suicide() +*/ +//SKYRAT EDIT REMOVAL END + +/atom/movable/screen/escape_menu/leave_body_button + icon = 'icons/hud/escape_menu_leave_body.dmi' + icon_state = "template" + maptext_width = 96 + maptext_y = -32 + + VAR_PRIVATE + datum/callback/on_click_callback + hovered = FALSE + tooltip_text + +/atom/movable/screen/escape_menu/leave_body_button/Initialize( + mapload, + button_text, + tooltip_text, + pixel_offset, + on_click_callback, + button_overlay, +) + . = ..() + + src.on_click_callback = on_click_callback + src.tooltip_text = tooltip_text + + add_overlay(button_overlay) + + maptext = MAPTEXT_VCR_OSD_MONO("[button_text]") + screen_loc = "CENTER:[pixel_offset],CENTER-1" + +/atom/movable/screen/escape_menu/leave_body_button/Destroy() + QDEL_NULL(on_click_callback) + + return ..() + +/atom/movable/screen/escape_menu/leave_body_button/Click(location, control, params) + on_click_callback?.InvokeAsync() + +/atom/movable/screen/escape_menu/leave_body_button/MouseEntered(location, control, params) + if (hovered) + return + + hovered = TRUE + + // The UX on this is pretty shit, but it's okay enough for now. + // Regularly goes way too far from your cursor. Not designed for large icons. + openToolTip(usr, src, params, content = tooltip_text) + +/atom/movable/screen/escape_menu/leave_body_button/MouseExited(location, control, params) + if (!hovered) + return + + hovered = FALSE + closeToolTip(usr) diff --git a/code/modules/escape_menu/readme.md b/code/modules/escape_menu/readme.md new file mode 100644 index 00000000000..c10aab5535a --- /dev/null +++ b/code/modules/escape_menu/readme.md @@ -0,0 +1,2 @@ +Skyrat Edit: +leave_body.dm - removed suicide menu option diff --git a/code/modules/escape_menu/subsystem.dm b/code/modules/escape_menu/subsystem.dm new file mode 100644 index 00000000000..f75726514ec --- /dev/null +++ b/code/modules/escape_menu/subsystem.dm @@ -0,0 +1,6 @@ +/// Subsystem for controlling anything related to the escape menu +PROCESSING_SUBSYSTEM_DEF(escape_menu) + name = "Escape Menu" + flags = SS_NO_INIT + runlevels = ALL + wait = 2 SECONDS diff --git a/code/modules/escape_menu/title.dm b/code/modules/escape_menu/title.dm new file mode 100644 index 00000000000..1a82967a6eb --- /dev/null +++ b/code/modules/escape_menu/title.dm @@ -0,0 +1,43 @@ +// This doesn't instantiate right away, since we rely on other GLOBs +GLOBAL_DATUM(escape_menu_title, /atom/movable/screen/escape_menu/title) + +/// Provides a singleton for the escape menu details screen. +/proc/give_escape_menu_title() + if (isnull(GLOB.escape_menu_title)) + GLOB.escape_menu_title = new + + return GLOB.escape_menu_title + +/atom/movable/screen/escape_menu/title + screen_loc = "NORTH:-100,WEST:32" + maptext_height = 100 + maptext_width = 500 + +/atom/movable/screen/escape_menu/title/Initialize(mapload) + . = ..() + + update_text() + + RegisterSignal(SSdcs, COMSIG_GLOB_STATION_NAME_CHANGED, PROC_REF(on_station_name_changed)) + +/atom/movable/screen/escape_menu/title/Destroy() + if (GLOB.escape_menu_title == src) + stack_trace("Something tried to delete the escape menu details screen") + return QDEL_HINT_LETMELIVE + + return ..() + +/atom/movable/screen/escape_menu/title/proc/update_text() + var/subtitle_text = MAPTEXT("Another day on...") + var/title_text = {" + + [station_name()] + + "} + + maptext = "" + subtitle_text + MAPTEXT_VCR_OSD_MONO(title_text) + "" + +/atom/movable/screen/escape_menu/title/proc/on_station_name_changed() + SIGNAL_HANDLER + + update_text() diff --git a/code/modules/keybindings/setup.dm b/code/modules/keybindings/setup.dm index bb2757761e0..ef87e12d901 100644 --- a/code/modules/keybindings/setup.dm +++ b/code/modules/keybindings/setup.dm @@ -39,3 +39,12 @@ window.set_mouse_macro() update_special_keybinds() + +/// Manually clears any held keys, in case due to lag or other undefined behavior a key gets stuck. +/client/proc/reset_held_keys() + for(var/key in keys_held) + keyUp(key) + + //In case one got stuck and the previous loop didn't clean it, somehow. + for(var/key in key_combos_held) + keyUp(key_combos_held[key]) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 54108e0c2db..36c985963e4 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -883,7 +883,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp cleanup_observe() if(..()) if(hud_used) - client.screen = list() + client.clear_screen() hud_used.show_hud(hud_used.hud_version) @@ -939,7 +939,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp set_sight(null) //we dont want ghosts to see through walls in secret areas RegisterSignal(mob_eye, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_observing_z_changed)) if(mob_eye.hud_used) - client.screen = list() + client.clear_screen() LAZYOR(mob_eye.observers, src) mob_eye.hud_used.show_hud(mob_eye.hud_used.hud_version, src) observetarget = mob_eye diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index a40c29459bc..cda4e179fcc 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -35,7 +35,7 @@ computer_id = client.computer_id log_access("Mob Login: [key_name(src)] was assigned to a [type]") world.update_status() - client.screen = list() //remove hud items just in case + client.clear_screen() //remove hud items just in case client.images = list() client.set_right_click_menu_mode(shift_to_open_context_menu) @@ -112,6 +112,7 @@ log_message("Client [key_name(src)] has taken ownership of mob [src]([src.type])", LOG_OWNERSHIP) log_mob_tag("NEW OWNER: [key_name(src)]") SEND_SIGNAL(src, COMSIG_MOB_CLIENT_LOGIN, client) + SEND_SIGNAL(client, COMSIG_CLIENT_MOB_LOGIN, src) client.init_verbs() AddElement(/datum/element/weather_listener, /datum/weather/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds) diff --git a/code/modules/tooltip/tooltip.dm b/code/modules/tooltip/tooltip.dm index c551a130a7f..a2fbd42ec84 100644 --- a/code/modules/tooltip/tooltip.dm +++ b/code/modules/tooltip/tooltip.dm @@ -1,16 +1,4 @@ /* -Tooltips v1.1 - 22/10/15 -Developed by Wire (#goonstation on irc.synirc.net) -- Added support for screen_loc pixel offsets. Should work. Maybe. -- Added init function to more efficiently send base vars - -Configuration: -- Set control to the correct skin element (remember to actually place the skin element) -- Set file to the correct path for the .html file (remember to actually place the html file) -- Attach the datum to the user client on login, e.g. - /client/New() - src.tooltips = new /datum/tooltip(src) - Usage: - Define mouse event procs on your (probably HUD) object and simply call the show and hide procs respectively: /atom/movable/screen/hud @@ -26,8 +14,6 @@ Customization: Notes: - You may have noticed 90% of the work is done via javascript on the client. Gotta save those cycles man. -- This is entirely untested in any other codebase besides goonstation so I have no idea if it will port nicely. Good luck! - - After testing and discussion (Wire, Remie, MrPerson, AnturK) ToolTips are ok and work for /tg/station13 */ @@ -37,6 +23,7 @@ Notes: var/showing = 0 var/queueHide = 0 var/init = 0 + var/atom/last_target /datum/tooltip/New(client/C) @@ -52,6 +39,14 @@ Notes: /datum/tooltip/proc/show(atom/movable/thing, params = null, title = null, content = null, theme = "default", special = "none") if (!thing || !params || (!title && !content) || !owner || !isnum(world.icon_size)) return FALSE + + if (!isnull(last_target)) + UnregisterSignal(last_target, COMSIG_PARENT_QDELETING) + + RegisterSignal(thing, COMSIG_PARENT_QDELETING, PROC_REF(on_target_qdel)) + + last_target = thing + if (!init) //Initialize some vars init = 1 @@ -96,11 +91,18 @@ Notes: return TRUE +/datum/tooltip/proc/on_target_qdel() + SIGNAL_HANDLER + + INVOKE_ASYNC(src, PROC_REF(hide)) + last_target = null + /datum/tooltip/proc/do_hide() winshow(owner, control, FALSE) -/* TG SPECIFIC CODE */ - +/datum/tooltip/Destroy(force, ...) + last_target = null + return ..() //Open a tooltip for user, at a location based on params //Theme is a CSS class in tooltip.html, by default this wrapper chooses a CSS class based on the user's UI_style (Midnight, Plasmafire, Retro, etc) diff --git a/code/modules/tutorials/tutorial_instruction.dm b/code/modules/tutorials/tutorial_instruction.dm index 488b3558eef..7d8bda0fab7 100644 --- a/code/modules/tutorials/tutorial_instruction.dm +++ b/code/modules/tutorials/tutorial_instruction.dm @@ -46,8 +46,7 @@ change_message(message) /atom/movable/screen/tutorial_instruction_text/proc/change_message(message) - // We don't use MAPTEXT macro here because it doesn't handle big text - message = "[message]" + message = MAPTEXT_VCR_OSD_MONO("[message]") animate(src, alpha = 0, time = (maptext ? 0.5 SECONDS : 0), easing = SINE_EASING) animate(alpha = 255, time = 0.5 SECONDS, maptext = message) diff --git a/code/modules/unit_tests/create_and_destroy.dm b/code/modules/unit_tests/create_and_destroy.dm index ba21ece1315..76a489b1de6 100644 --- a/code/modules/unit_tests/create_and_destroy.dm +++ b/code/modules/unit_tests/create_and_destroy.dm @@ -111,6 +111,8 @@ GLOBAL_VAR_INIT(running_create_and_destroy, FALSE) ignore += typesof(/obj/structure/industrial_lift) // Runtimes if the associated machinery does not exist, but not the base type ignore += subtypesof(/obj/machinery/airlock_controller) + // Always ought to have an associated escape menu. Any references it could possibly hold would need one regardless. + ignore += subtypesof(/atom/movable/screen/escape_menu) var/list/cached_contents = spawn_at.contents.Copy() var/original_turf_type = spawn_at.type diff --git a/icons/effects/96x96.dmi b/icons/effects/96x96.dmi index e803dbcb259..7256ed50185 100644 Binary files a/icons/effects/96x96.dmi and b/icons/effects/96x96.dmi differ diff --git a/icons/hud/escape_menu_leave_body.dmi b/icons/hud/escape_menu_leave_body.dmi new file mode 100644 index 00000000000..2923e1376ac Binary files /dev/null and b/icons/hud/escape_menu_leave_body.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 26577a70d66..4ae248786a1 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -566,6 +566,7 @@ #include "code\_onclick\hud\radial_persistent.dm" #include "code\_onclick\hud\revenanthud.dm" #include "code\_onclick\hud\robot.dm" +#include "code\_onclick\hud\screen_object_holder.dm" #include "code\_onclick\hud\screen_objects.dm" #include "code\_onclick\hud\screentip.dm" #include "code\_onclick\hud\rendering\plane_master.dm" @@ -3031,7 +3032,6 @@ #include "code\modules\client\preferences\species_features\vampire.dm" #include "code\modules\client\verbs\ooc.dm" #include "code\modules\client\verbs\ping.dm" -#include "code\modules\client\verbs\reset_held_keys.dm" #include "code\modules\client\verbs\suicide.dm" #include "code\modules\client\verbs\who.dm" #include "code\modules\clothing\chameleon.dm" @@ -3187,6 +3187,13 @@ #include "code\modules\emoji\emoji_parse.dm" #include "code\modules\error_handler\error_handler.dm" #include "code\modules\error_handler\error_viewer.dm" +#include "code\modules\escape_menu\details.dm" +#include "code\modules\escape_menu\dimmer.dm" +#include "code\modules\escape_menu\escape_menu.dm" +#include "code\modules\escape_menu\home_page.dm" +#include "code\modules\escape_menu\leave_body.dm" +#include "code\modules\escape_menu\subsystem.dm" +#include "code\modules\escape_menu\title.dm" #include "code\modules\events\_event.dm" #include "code\modules\events\aurora_caelus.dm" #include "code\modules\events\brain_trauma.dm"