From 2ac44cb9d732dcc600153bd639d5e6c2e3884ade Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Thu, 19 Nov 2020 07:15:31 +0100 Subject: [PATCH] [MIRROR] [READY] Adds notification support to modular computer apps, updates CIMS to use it (#1715) * [READY] Adds notification support to modular computer apps, updates CIMS to use it * Update tgui.bundle.js Co-authored-by: zxaber <37497534+zxaber@users.noreply.github.com> Co-authored-by: Azarak --- code/__DEFINES/dcs/signals.dm | 6 ++ .../computers/item/computer.dm | 22 +++++++ .../computers/item/computer_ui.dm | 4 +- .../computers/item/processor.dm | 6 ++ .../computers/item/tablet.dm | 7 +++ .../modular_computers/file_system/program.dm | 6 ++ .../file_system/programs/file_browser.dm | 17 ++++- .../file_system/programs/sm_monitor.dm | 62 ++++++++++++++++++- code/modules/power/supermatter/supermatter.dm | 4 ++ .../tgui/interfaces/NtosFileManager.js | 11 +++- tgui/packages/tgui/interfaces/NtosMain.js | 2 +- tgui/public/tgui.bundle.js | 2 +- 12 files changed, 143 insertions(+), 6 deletions(-) diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index f19e93bdf7b..42f8a09dede 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -484,6 +484,12 @@ ///from /obj/machinery/set_occupant(atom/movable/O): (new_occupant) #define COMSIG_MACHINERY_SET_OCCUPANT "machinery_set_occupant" +// /obj/machinery/power/supermatter_crystal signals +/// from /obj/machinery/power/supermatter_crystal/process_atmos(); when the SM delam reaches the point of sounding alarms +#define COMSIG_SUPERMATTER_DELAM_START_ALARM "sm_delam_start_alarm" +/// from /obj/machinery/power/supermatter_crystal/process_atmos(); when the SM sounds an audible alarm +#define COMSIG_SUPERMATTER_DELAM_ALARM "sm_delam_alarm" + // /obj/machinery/atmospherics/components/unary/cryo_cell signals /// from /obj/machinery/atmospherics/components/unary/cryo_cell/set_on(bool): (on) diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm index 223777e7fca..59549f4eb65 100644 --- a/code/modules/modular_computers/computers/item/computer.dm +++ b/code/modules/modular_computers/computers/item/computer.dm @@ -263,6 +263,28 @@ handle_power(delta_time) // Handles all computer power interaction //check_update_ui_need() +/** + * Displays notification text alongside a soundbeep when requested to by a program. + * + * After checking tha the requesting program is allowed to send an alert, creates + * a visible message of the requested text alongside a soundbeep. This proc adds + * text to indicate that the message is coming from this device and the program + * on it, so the supplied text should be the exact message and ending punctuation. + * + * Arguments: + * The program calling this proc. + * The message that the program wishes to display. + */ + +/obj/item/modular_computer/proc/alert_call(datum/computer_file/program/caller, alerttext, sound = 'sound/machines/twobeep_high.ogg') + if(!caller || !caller.alert_able || caller.alert_silenced || !alerttext) //Yeah, we're checking alert_able. No, you don't get to make alerts that the user can't silence. + return + playsound(src, sound, 50, TRUE) + visible_message("The [src] displays a [caller.filedesc] notification: [alerttext]") + var/mob/living/holder = loc + if(istype(holder)) + to_chat(holder, "[icon2html(src)] The [src] displays a [caller.filedesc] notification: [alerttext]") + // Function used by NanoUI's to obtain data for header. All relevant entries begin with "PC_" /obj/item/modular_computer/proc/get_header_data() var/list/data = list() diff --git a/code/modules/modular_computers/computers/item/computer_ui.dm b/code/modules/modular_computers/computers/item/computer_ui.dm index 5e831b75f77..a9f353bca42 100644 --- a/code/modules/modular_computers/computers/item/computer_ui.dm +++ b/code/modules/modular_computers/computers/item/computer_ui.dm @@ -80,7 +80,7 @@ if(P in idle_threads) running = TRUE - data["programs"] += list(list("name" = P.filename, "desc" = P.filedesc, "running" = running, "icon" = P.program_icon)) + data["programs"] += list(list("name" = P.filename, "desc" = P.filedesc, "running" = running, "icon" = P.program_icon, "alert" = P.alert_pending)) data["has_light"] = has_light data["light_on"] = light_on @@ -148,6 +148,7 @@ if(P in idle_threads) P.program_state = PROGRAM_STATE_ACTIVE active_program = P + P.alert_pending = FALSE idle_threads.Remove(P) update_icon() return @@ -163,6 +164,7 @@ return if(P.run_program(user)) active_program = P + P.alert_pending = FALSE update_icon() return 1 diff --git a/code/modules/modular_computers/computers/item/processor.dm b/code/modules/modular_computers/computers/item/processor.dm index a8e4a8a6545..970dc8bd1df 100644 --- a/code/modules/modular_computers/computers/item/processor.dm +++ b/code/modules/modular_computers/computers/item/processor.dm @@ -52,3 +52,9 @@ /obj/item/modular_computer/processor/attack_ghost(mob/user) ui_interact(user) + +/obj/item/modular_computer/processor/alert_call(datum/computer_file/program/caller, alerttext) + if(!caller || !caller.alert_able || caller.alert_silenced || !alerttext) + return + playsound(src, 'sound/machines/twobeep_high.ogg', 50, TRUE) + machinery_computer.visible_message("The [src] displays a [caller.filedesc] notification: [alerttext]") diff --git a/code/modules/modular_computers/computers/item/tablet.dm b/code/modules/modular_computers/computers/item/tablet.dm index 0886b5686b2..d48b62d8817 100644 --- a/code/modules/modular_computers/computers/item/tablet.dm +++ b/code/modules/modular_computers/computers/item/tablet.dm @@ -130,6 +130,13 @@ borgo.toggle_headlamp(FALSE, TRUE) return TRUE +/obj/item/modular_computer/tablet/integrated/alert_call(datum/computer_file/program/caller, alerttext, sound = 'sound/machines/twobeep_high.ogg') + if(!caller || !caller.alert_able || caller.alert_silenced || !alerttext) //Yeah, we're checking alert_able. No, you don't get to make alerts that the user can't silence. + return + borgo.playsound_local(src, sound, 50, TRUE) + to_chat(borgo, "The [src] displays a [caller.filedesc] notification: [alerttext]") + + /obj/item/modular_computer/tablet/integrated/syndicate icon_state = "tablet-silicon-syndicate" device_theme = "syndicate" diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm index 3b48a08ef43..4c3f252de13 100644 --- a/code/modules/modular_computers/file_system/program.dm +++ b/code/modules/modular_computers/file_system/program.dm @@ -35,6 +35,12 @@ var/ui_header = null /// Font Awesome icon to use as this program's icon in the modular computer main menu. Defaults to a basic program maximize window icon if not overridden. var/program_icon = "window-maximize-o" + /// Whether this program can send alerts while minimized or closed. Used to show a mute button per program in the file manager + var/alert_able = FALSE + /// Whether the user has muted this program's ability to send alerts. + var/alert_silenced = FALSE + /// Whether to highlight our program in the main screen. Intended for alerts, but loosely available for any need to notify of changed conditions. Think Windows task bar highlighting. Available even if alerts are muted. + var/alert_pending = FALSE /datum/computer_file/program/New(obj/item/modular_computer/comp = null) ..() diff --git a/code/modules/modular_computers/file_system/programs/file_browser.dm b/code/modules/modular_computers/file_system/programs/file_browser.dm index 4c4e24120af..97a71496eab 100644 --- a/code/modules/modular_computers/file_system/programs/file_browser.dm +++ b/code/modules/modular_computers/file_system/programs/file_browser.dm @@ -67,6 +67,13 @@ var/datum/computer_file/C = F.clone(FALSE) HDD.store_file(C) return TRUE + if("PRG_togglesilence") + if(!HDD) + return + var/datum/computer_file/program/binary = HDD.find_file_by_name(params["name"]) + if(!binary || !istype(binary)) + return + binary.alert_silenced = !binary.alert_silenced /datum/computer_file/program/filemanager/ui_data(mob/user) var/list/data = get_header_data() @@ -80,11 +87,19 @@ else var/list/files = list() for(var/datum/computer_file/F in HDD.stored_files) + var/noisy = FALSE + var/silenced = FALSE + var/datum/computer_file/program/binary = F + if(istype(binary)) + noisy = binary.alert_able + silenced = binary.alert_silenced files += list(list( "name" = F.filename, "type" = F.filetype, "size" = F.size, - "undeletable" = F.undeletable + "undeletable" = F.undeletable, + "alert_able" = noisy, + "alert_silenced" = silenced )) data["files"] = files if(RHDD) diff --git a/code/modules/modular_computers/file_system/programs/sm_monitor.dm b/code/modules/modular_computers/file_system/programs/sm_monitor.dm index 4c0ad012573..5653e4621d9 100644 --- a/code/modules/modular_computers/file_system/programs/sm_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/sm_monitor.dm @@ -9,10 +9,15 @@ size = 5 tgui_id = "NtosSupermatterMonitor" program_icon = "radiation" + alert_able = TRUE var/last_status = SUPERMATTER_INACTIVE var/list/supermatters var/obj/machinery/power/supermatter_crystal/active // Currently selected supermatter crystal. +/datum/computer_file/program/supermatter_monitor/Destroy() + clear_signals() + active = null + return ..() /datum/computer_file/program/supermatter_monitor/process_tick() ..() @@ -26,10 +31,11 @@ /datum/computer_file/program/supermatter_monitor/run_program(mob/living/user) . = ..(user) + if(!(active in GLOB.machines)) + active = null refresh() /datum/computer_file/program/supermatter_monitor/kill_program(forced = FALSE) - active = null supermatters = null ..() @@ -53,6 +59,58 @@ for(var/obj/machinery/power/supermatter_crystal/S in supermatters) . = max(., S.get_status()) +/** + * Sets up the signal listener for Supermatter delaminations. + * + * Unregisters any old listners for SM delams, and then registers one for the SM refered + * to in the `active` variable. This proc is also used with no active SM to simply clear + * the signal and exit. + */ +/datum/computer_file/program/supermatter_monitor/proc/set_signals() + if(active) + RegisterSignal(active, COMSIG_SUPERMATTER_DELAM_ALARM, .proc/send_alert, override = TRUE) + RegisterSignal(active, COMSIG_SUPERMATTER_DELAM_START_ALARM, .proc/send_start_alert, override = TRUE) + +/** + * Removes the signal listener for Supermatter delaminations from the selected supermatter. + * + * Pretty much does what it says. + */ +/datum/computer_file/program/supermatter_monitor/proc/clear_signals() + if(active) + UnregisterSignal(active, COMSIG_SUPERMATTER_DELAM_ALARM) + UnregisterSignal(active, COMSIG_SUPERMATTER_DELAM_START_ALARM) + +/** + * Sends an SM delam alert to the computer. + * + * Triggered by a signal from the selected supermatter, this proc sends a notification + * to the computer if the program is either closed or minimized. We do not send these + * notifications to the comptuer if we're the active program, because engineers fixing + * the supermatter probably don't need constant beeping to distract them. + */ +/datum/computer_file/program/supermatter_monitor/proc/send_alert() + if(!computer.get_ntnet_status()) + return + if(computer.active_program != src) + computer.alert_call(src, "Crystal delamination in progress!") + alert_pending = TRUE + +/** + * Sends an SM delam start alert to the computer. + * + * Triggered by a signal from the selected supermatter at the start of a delamination, + * this proc sends a notification to the computer if this program is the active one. + * We do this so that people carrying a tablet with NT CIMS open but with the NTOS window + * closed will still get one audio alert. This is not sent to computers with the program + * minimized or closed to avoid double-notifications. + */ +/datum/computer_file/program/supermatter_monitor/proc/send_start_alert() + if(!computer.get_ntnet_status()) + return + if(computer.active_program == src) + computer.alert_call(src, "Crystal delamination in progress!") + /datum/computer_file/program/supermatter_monitor/ui_data() var/list/data = get_header_data() @@ -112,6 +170,7 @@ switch(action) if("PRG_clear") + clear_signals() active = null return TRUE if("PRG_refresh") @@ -122,4 +181,5 @@ for(var/obj/machinery/power/supermatter_crystal/S in supermatters) if(S.uid == newuid) active = S + set_signals() return TRUE diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index 4ebae271150..cc426d9bca3 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -776,12 +776,15 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) //Tells the engi team to get their butt in gear if(damage > warning_point) // while the core is still damaged and it's still worth noting its status + if(damage_archived < warning_point) //If damage_archive is under the warning point, this is the very first cycle that we've reached said point. + SEND_SIGNAL(src, COMSIG_SUPERMATTER_DELAM_START_ALARM) if((REALTIMEOFDAY - lastwarning) / 10 >= WARNING_DELAY) alarm() //Oh shit it's bad, time to freak out if(damage > emergency_point) radio.talk_into(src, "[emergency_alert] Integrity: [get_integrity()]%", common_channel) + SEND_SIGNAL(src, COMSIG_SUPERMATTER_DELAM_ALARM) lastwarning = REALTIMEOFDAY if(!has_reached_emergency) investigate_log("has reached the emergency point for the first time.", INVESTIGATE_SUPERMATTER) @@ -789,6 +792,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) has_reached_emergency = TRUE else if(damage >= damage_archived) // The damage is still going up radio.talk_into(src, "[warning_alert] Integrity: [get_integrity()]%", engineering_channel) + SEND_SIGNAL(src, COMSIG_SUPERMATTER_DELAM_ALARM) lastwarning = REALTIMEOFDAY - (WARNING_DELAY * 5) else // Phew, we're safe diff --git a/tgui/packages/tgui/interfaces/NtosFileManager.js b/tgui/packages/tgui/interfaces/NtosFileManager.js index da762e459f4..f093b3147a8 100644 --- a/tgui/packages/tgui/interfaces/NtosFileManager.js +++ b/tgui/packages/tgui/interfaces/NtosFileManager.js @@ -24,7 +24,8 @@ export const NtosFileManager = (props, context) => { name: file, new_name: newName, })} - onDuplicate={file => act('PRG_clone', { file: file })} /> + onDuplicate={file => act('PRG_clone', { file: file })} + onToggleSilence={file => act('PRG_togglesilence', { name: file })} /> {usbconnected && (
@@ -54,6 +55,7 @@ const FileTable = props => { onUpload, onDelete, onRename, + onToggleSilence, } = props; return ( @@ -89,6 +91,13 @@ const FileTable = props => { {file.size} + {!!file.alert_able && ( +