From ccfa0fba7d7763d3bd3d3b3baac50f2569df51d9 Mon Sep 17 00:00:00 2001 From: Arkatos1 <43862960+Arkatos1@users.noreply.github.com> Date: Sun, 29 Aug 2021 12:11:24 +0200 Subject: [PATCH] tgui: Silicon Station Alerts (#61070) This PR unifies all Station Alert menus into one datum system. This also means that the old html menus will be converted to tgui, which is a case for cyborg and AI menus. All menus above differ very little, practically only by amount of type of alerts they show except for the AI menu, which also has access to cameras in the alert areas and knows number of the alert sources. --- code/_onclick/hud/ai.dm | 2 +- code/_onclick/hud/robot.dm | 2 +- code/datums/station_alert.dm | 103 +++++++++++++++ code/game/machinery/computer/station_alert.dm | 34 ++--- code/modules/mob/living/silicon/ai/ai.dm | 55 ++------ .../modules/mob/living/silicon/robot/robot.dm | 37 ++---- .../mob/living/silicon/robot/robot_defines.dm | 5 +- .../file_system/programs/alarm.dm | 24 ++-- tgstation.dme | 1 + .../interfaces/NtosStationAlertConsole.js | 4 +- .../tgui/interfaces/StationAlertConsole.js | 118 ++++++++++-------- 11 files changed, 210 insertions(+), 175 deletions(-) create mode 100644 code/datums/station_alert.dm diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm index 0f1820eb42c..99368490fe6 100644 --- a/code/_onclick/hud/ai.dm +++ b/code/_onclick/hud/ai.dm @@ -74,7 +74,7 @@ if(..()) return var/mob/living/silicon/ai/AI = usr - AI.ai_alerts() + AI.alert_control.ui_interact(AI) /atom/movable/screen/ai/announcement name = "Make Vox Announcement" diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm index e3b981f3ced..01d4758f4e3 100644 --- a/code/_onclick/hud/robot.dm +++ b/code/_onclick/hud/robot.dm @@ -319,4 +319,4 @@ if(.) return var/mob/living/silicon/robot/borgo = usr - borgo.robot_alerts() + borgo.alert_control.ui_interact(borgo) diff --git a/code/datums/station_alert.dm b/code/datums/station_alert.dm new file mode 100644 index 00000000000..edec9f5cb50 --- /dev/null +++ b/code/datums/station_alert.dm @@ -0,0 +1,103 @@ +/datum/station_alert + /// Holder of the datum + var/holder + /// List of all alarm types we are listening to + var/list/alarm_types + /// Listens for alarms, provides the alarms list for our UI + var/datum/alarm_listener/listener + /// Title of our UI + var/title + /// If UI will also show and allow jumping to cameras connected to each alert area + var/camera_view + +/datum/station_alert/ui_host(mob/user) + return holder + +/datum/station_alert/New(holder, list/alarm_types, list/listener_z_level, list/listener_areas, title = "Station Alerts", camera_view = FALSE) + src.holder = holder + src.alarm_types = alarm_types + src.title = title + src.camera_view = camera_view + listener = new(alarm_types, listener_z_level, listener_areas) + +/datum/station_alert/Destroy() + QDEL_NULL(listener) + return ..() + +/datum/station_alert/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "StationAlertConsole", title) + ui.open() + +/datum/station_alert/ui_data(mob/user) + var/list/data = list() + data["cameraView"] = camera_view + data["alarms"] = list() + var/list/nominal_types = alarm_types.Copy() + var/list/alarms = listener.alarms + for(var/alarm_type in alarms) + var/list/alarm_category = list( + "name" = alarm_type, + "alerts" = list(), + ) + var/list/alerts = alarms[alarm_type] + for(var/alert in alerts) + var/list/alert_details = alerts[alert] + alarm_category["alerts"] += list(list( + "name" = get_area_name(alert_details[1], TRUE), + "cameras" = camera_view ? length(alert_details[2]) : null, + "sources" = camera_view ? length(alert_details[3]) : null, + "ref" = camera_view ? REF(alert) : null, + )) + data["alarms"] += list(alarm_category) + nominal_types -= alarm_type + if(length(nominal_types)) + for(var/nominal_type in nominal_types) + var/list/nominal_category = list( + "name" = nominal_type, + "alerts" = list(), + ) + data["alarms"] += list(nominal_category) + return data + +/datum/station_alert/ui_act(action, params) + . = ..() + if(.) + return + + switch(action) + if("select_camera") + var/mob/living/silicon/ai/ai = usr + if(!istype(ai)) + return + + var/list/alarms = listener.alarms + var/list/alerts = list() + for(var/alarm_type in alarms) + alerts += alarms[alarm_type] + + var/list/our_alert = locate(params["alert"]) in alerts + if(!length(our_alert)) + return + var/chosen_alert = alerts[our_alert] + var/list/cameras = chosen_alert[2] + if(!length(cameras)) + return + var/list/named_cameras = list() + for(var/obj/machinery/camera/camera in cameras) + named_cameras[camera.c_tag] = camera + + var/chosen_camera + if(length(named_cameras) == 1) + chosen_camera = named_cameras[1] + else + chosen_camera = tgui_input_list(ai, "Choose a camera to jump to", "Camera Selection", named_cameras) + if(!chosen_camera) + return + var/obj/machinery/camera/selected_camera = named_cameras[chosen_camera] + if(!selected_camera.can_use()) + to_chat(ai, span_warning("Camera is unavailable!")) + return + ai.switchCamera(selected_camera) + return TRUE diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm index 897cc85dc79..058bff2f8e8 100644 --- a/code/game/machinery/computer/station_alert.dm +++ b/code/game/machinery/computer/station_alert.dm @@ -4,46 +4,30 @@ icon_screen = "alert:0" icon_keyboard = "atmos_key" circuit = /obj/item/circuitboard/computer/stationalert - ///Listens for alarms, provides the alarms list for our ui - var/datum/alarm_listener/listener - light_color = LIGHT_COLOR_CYAN + /// Station alert datum for showing alerts UI + var/datum/station_alert/alert_control /obj/machinery/computer/station_alert/Initialize() - listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), list(z)) + alert_control = new(src, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), list(z), title = name) return ..() /obj/machinery/computer/station_alert/Destroy() - QDEL_NULL(listener) + QDEL_NULL(alert_control) return ..() -/obj/machinery/computer/station_alert/ui_interact(mob/user, datum/tgui/ui) - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "StationAlertConsole", name) - ui.open() - -/obj/machinery/computer/station_alert/ui_data(mob/user) - var/list/data = list() - - data["alarms"] = list() - var/list/alarms = listener.alarms - for(var/alarm_type in alarms) - data["alarms"][alarm_type] = list() - for(var/area_name in alarms[alarm_type]) - data["alarms"][alarm_type] += area_name - - return data +/obj/machinery/computer/station_alert/ui_interact(mob/user) + alert_control.ui_interact(user) /obj/machinery/computer/station_alert/on_set_machine_stat(old_value) if(machine_stat & BROKEN) - listener.prevent_alarm_changes() + alert_control.listener.prevent_alarm_changes() else - listener.allow_alarm_changes() + alert_control.listener.allow_alarm_changes() /obj/machinery/computer/station_alert/update_overlays() . = ..() if(machine_stat & (NOPOWER|BROKEN)) return - if(length(listener.alarms)) + if(length(alert_control.listener.alarms)) . += "alert:2" diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index a28b5bdbb70..a19693e5636 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -37,7 +37,6 @@ var/aiRestorePowerRoutine = POWER_RESTORATION_OFF var/requires_power = POWER_REQ_ALL var/can_be_carded = TRUE - var/viewalerts = 0 var/icon/holo_icon //Default is assigned when AI is created. var/obj/vehicle/sealed/mecha/controlled_mech //For controlled_mech a mech, to determine whether to relaymove or use the AI eye. var/radio_enabled = TRUE //Determins if a carded AI can speak with its built in radio or not. @@ -100,8 +99,8 @@ var/atom/cam_prev var/datum/robot_control/robot_control - ///Alarm listener datum, handes caring about alarm events and such - var/datum/alarm_listener/listener + /// Station alert datum for showing alerts UI + var/datum/station_alert/alert_control ///remember AI's last location var/atom/lastloc @@ -177,9 +176,9 @@ ADD_TRAIT(src, TRAIT_PULL_BLOCKED, ROUNDSTART_TRAIT) ADD_TRAIT(src, TRAIT_HANDS_BLOCKED, ROUNDSTART_TRAIT) - listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z)) - RegisterSignal(listener, COMSIG_ALARM_TRIGGERED, .proc/alarm_triggered) - RegisterSignal(listener, COMSIG_ALARM_CLEARED, .proc/alarm_cleared) + alert_control = new(src, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z), camera_view = TRUE) + RegisterSignal(alert_control.listener, COMSIG_ALARM_TRIGGERED, .proc/alarm_triggered) + RegisterSignal(alert_control.listener, COMSIG_ALARM_CLEARED, .proc/alarm_cleared) /mob/living/silicon/ai/key_down(_key, client/user) if(findtext(_key, "numpad")) //if it's a numpad number, we can convert it to just the number @@ -213,7 +212,7 @@ QDEL_NULL(doomsday_device) QDEL_NULL(robot_control) QDEL_NULL(aiMulti) - QDEL_NULL(listener) + QDEL_NULL(alert_control) malfhack = null current = null Bot = null @@ -293,40 +292,6 @@ Model: [connected_robot.designation] | Loc: [get_area_name(connected_robot, TRUE)] | Status: [robot_status]" . += "AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]" //Count of total AI shells -/mob/living/silicon/ai/proc/ai_alerts() - var/dat = "Current Station Alerts\n" - dat += "Close

" - var/list/alarms = listener.alarms - for (var/alarm_type in alarms) - dat += "[alarm_type]
\n" - var/list/alerts = alarms[alarm_type] - if (length(alerts)) - for (var/alarm in alerts) - var/list/alm = alerts[alarm] - var/area/A = alm[1] - var/C = alm[2] - var/list/sources = alm[3] - dat += "" - if (C && istype(C, /list)) - var/dat2 = "" - for (var/obj/machinery/camera/I in C) - dat2 += "[(dat2=="") ? "" : " | "][I.c_tag]" - dat += "-- [A.name] ([(dat2!="") ? dat2 : "No Camera"])" - else if (C && istype(C, /obj/machinery/camera)) - var/obj/machinery/camera/Ctmp = C - dat += "-- [A.name] ([Ctmp.c_tag])" - else - dat += "-- [A.name] (No Camera)" - if (sources.len > 1) - dat += "- [sources.len] sources" - dat += "
\n" - else - dat += "-- All Systems Nominal
\n" - dat += "
\n" - - viewalerts = 1 - src << browse(dat, "window=aialerts&can_close=0") - /mob/living/silicon/ai/proc/ai_call_shuttle() if(control_disabled) to_chat(usr, span_warning("Wireless control is disabled!")) @@ -418,15 +383,13 @@ return if (href_list["mach_close"]) - if (href_list["mach_close"] == "aialerts") - viewalerts = 0 var/t1 = "window=[href_list["mach_close"]]" unset_machine() src << browse(null, t1) if (href_list["switchcamera"]) switchCamera(locate(href_list["switchcamera"]) in GLOB.cameranet.cameras) if (href_list["showalerts"]) - ai_alerts() + alert_control.ui_interact(src) #ifdef AI_VOX if(href_list["say_word"]) play_vox_word(href_list["say_word"], null, src) @@ -562,15 +525,11 @@ queueAlarm("--- [alarm_type] alarm detected in [home_name]! ([dat2])", alarm_type) else queueAlarm("--- [alarm_type] alarm detected in [home_name]! (No Camera)", alarm_type) - if (viewalerts) - ai_alerts() return 1 /mob/living/silicon/ai/proc/alarm_cleared(datum/source, alarm_type, area/source_area) SIGNAL_HANDLER queueAlarm("--- [alarm_type] alarm in [source_area.name] has been cleared.", alarm_type, 0) - if(viewalerts) - ai_alerts() //Replaces /mob/living/silicon/ai/verb/change_network() in ai.dm & camera.dm //Adds in /mob/living/silicon/ai/proc/ai_network_change() instead diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index a5eee8671af..272c008b027 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -69,11 +69,11 @@ diag_hud_set_borgcell() logevent("System brought online.") - listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z)) - RegisterSignal(listener, COMSIG_ALARM_TRIGGERED, .proc/alarm_triggered) - RegisterSignal(listener, COMSIG_ALARM_CLEARED, .proc/alarm_cleared) - listener.RegisterSignal(src, COMSIG_LIVING_DEATH, /datum/alarm_listener/proc/prevent_alarm_changes) - listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, /datum/alarm_listener/proc/allow_alarm_changes) + alert_control = new(src, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z)) + RegisterSignal(alert_control.listener, COMSIG_ALARM_TRIGGERED, .proc/alarm_triggered) + RegisterSignal(alert_control.listener, COMSIG_ALARM_CLEARED, .proc/alarm_cleared) + alert_control.listener.RegisterSignal(src, COMSIG_LIVING_DEATH, /datum/alarm_listener/proc/prevent_alarm_changes) + alert_control.listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, /datum/alarm_listener/proc/allow_alarm_changes) /mob/living/silicon/robot/model/syndicate/Initialize() . = ..() @@ -124,15 +124,15 @@ QDEL_NULL(inv2) QDEL_NULL(inv3) QDEL_NULL(spark_system) - QDEL_NULL(listener) + QDEL_NULL(alert_control) cell = null return ..() /mob/living/silicon/robot/Topic(href, href_list) . = ..() //Show alerts window if user clicked on "Show alerts" in chat - if (href_list["showalerts"]) - robot_alerts() + if(href_list["showalerts"]) + alert_control.ui_interact(src) /mob/living/silicon/robot/get_cell() return cell @@ -205,27 +205,6 @@ /mob/living/silicon/robot/proc/get_standard_name() return "[(designation ? "[designation] " : "")][mmi.braintype]-[ident]" -/mob/living/silicon/robot/proc/robot_alerts() - var/dat = "" - var/list/alarms = listener.alarms - for (var/alarm_type in alarms) - dat += "[alarm_type]
\n" - var/list/alerts = alarms[alarm_type] - if (length(alerts)) - for (var/alarm in alerts) - var/list/alm = alerts[alarm] - var/area/A = alm[1] - dat += "" - dat += "-- [A.name]" - dat += "
\n" - else - dat += "-- All Systems Nominal
\n" - dat += "
\n" - - var/datum/browser/alerts = new(usr, "robotalerts", "Current Station Alerts", 400, 410) - alerts.set_content(dat) - alerts.open() - /mob/living/silicon/robot/proc/ionpulse() if(!ionpulse_on) return diff --git a/code/modules/mob/living/silicon/robot/robot_defines.dm b/code/modules/mob/living/silicon/robot/robot_defines.dm index 23216222951..cc4a9b429d9 100644 --- a/code/modules/mob/living/silicon/robot/robot_defines.dm +++ b/code/modules/mob/living/silicon/robot/robot_defines.dm @@ -131,9 +131,8 @@ /// the last health before updating - to check net change in health var/previous_health - ///Alarm listener datum, handes caring about alarm events and such - var/datum/alarm_listener/listener - + /// Station alert datum for showing alerts UI + var/datum/station_alert/alert_control /*************************************************************************************** * Defining specific kinds of robots diff --git a/code/modules/modular_computers/file_system/programs/alarm.dm b/code/modules/modular_computers/file_system/programs/alarm.dm index 340041d754f..134472c27e2 100644 --- a/code/modules/modular_computers/file_system/programs/alarm.dm +++ b/code/modules/modular_computers/file_system/programs/alarm.dm @@ -9,20 +9,21 @@ size = 5 tgui_id = "NtosStationAlertConsole" program_icon = "bell" - var/has_alert = 0 - ///Listens for alarms, manages our listing of alarms - var/datum/alarm_listener/listener + /// If there is any station alert + var/has_alert = FALSE + /// Station alert datum for showing alerts UI + var/datum/station_alert/alert_control /datum/computer_file/program/alarm_monitor/New() //We want to send an alarm if we're in one of the mining home areas //Or if we're on station. Otherwise, die. var/list/allowed_areas = GLOB.the_station_areas + typesof(/area/mine) - listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), null, allowed_areas) - RegisterSignal(listener, list(COMSIG_ALARM_TRIGGERED, COMSIG_ALARM_CLEARED), .proc/update_alarm_display) + alert_control = new(computer, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), listener_areas = allowed_areas) + RegisterSignal(alert_control.listener, list(COMSIG_ALARM_TRIGGERED, COMSIG_ALARM_CLEARED), .proc/update_alarm_display) return ..() /datum/computer_file/program/alarm_monitor/Destroy() - QDEL_NULL(listener) + QDEL_NULL(alert_control) return ..() /datum/computer_file/program/alarm_monitor/process_tick() @@ -41,20 +42,13 @@ /datum/computer_file/program/alarm_monitor/ui_data(mob/user) var/list/data = get_header_data() - - data["alarms"] = list() - var/list/alarms = listener.alarms - for(var/alarm_type in alarms) - data["alarms"][alarm_type] = list() - for(var/area in alarms[alarm_type]) - data["alarms"][alarm_type] += area - + data += alert_control.ui_data(user) return data /datum/computer_file/program/alarm_monitor/proc/update_alarm_display() SIGNAL_HANDLER has_alert = FALSE - if(length(listener.alarms)) + if(length(alert_control.listener.alarms)) has_alert = TRUE /datum/computer_file/program/alarm_monitor/run_program(mob/user) diff --git a/tgstation.dme b/tgstation.dme index 21cb1156922..ecfa1325e07 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -440,6 +440,7 @@ #include "code\datums\saymode.dm" #include "code\datums\shuttles.dm" #include "code\datums\spawners_menu.dm" +#include "code\datums\station_alert.dm" #include "code\datums\station_integrity.dm" #include "code\datums\tgs_event_handler.dm" #include "code\datums\verbs.dm" diff --git a/tgui/packages/tgui/interfaces/NtosStationAlertConsole.js b/tgui/packages/tgui/interfaces/NtosStationAlertConsole.js index 67666f2cdff..e39a38dd737 100644 --- a/tgui/packages/tgui/interfaces/NtosStationAlertConsole.js +++ b/tgui/packages/tgui/interfaces/NtosStationAlertConsole.js @@ -4,8 +4,8 @@ import { StationAlertConsoleContent } from './StationAlertConsole'; export const NtosStationAlertConsole = () => { return ( + width={335} + height={587}> diff --git a/tgui/packages/tgui/interfaces/StationAlertConsole.js b/tgui/packages/tgui/interfaces/StationAlertConsole.js index 8773a1b6553..e4e933917ac 100644 --- a/tgui/packages/tgui/interfaces/StationAlertConsole.js +++ b/tgui/packages/tgui/interfaces/StationAlertConsole.js @@ -1,12 +1,18 @@ +import { sortBy } from 'common/collections'; +import { flow } from 'common/fp'; import { useBackend } from '../backend'; -import { Section } from '../components'; +import { Button, Section, Stack } from '../components'; import { Window } from '../layouts'; -export const StationAlertConsole = () => { +export const StationAlertConsole = (props, context) => { + const { data } = useBackend(context); + const { + cameraView, + } = data; return ( + width={cameraView ? 390 : 345} + height={587}> @@ -15,55 +21,65 @@ export const StationAlertConsole = () => { }; export const StationAlertConsoleContent = (props, context) => { - const { data } = useBackend(context); - const categories = data.alarms || []; - const fire = categories['Fire'] || []; - const atmos = categories['Atmosphere'] || []; - const power = categories['Power'] || []; + const { act, data } = useBackend(context); + const { + cameraView, + } = data; + + const sortingKey = { + "Fire": 0, + "Atmosphere": 1, + "Power": 2, + "Burglar": 3, + "Motion": 4, + "Camera": 5, + }; + + const sortedAlarms = flow([ + sortBy((alarm) => sortingKey[alarm.name]), + ])(data.alarms || []); + return ( <> -
-
    - {fire.length === 0 && ( -
  • - Systems Nominal -
  • - )} - {fire.map(alert => ( -
  • - {alert} -
  • - ))} -
-
-
-
    - {atmos.length === 0 && ( -
  • - Systems Nominal -
  • - )} - {atmos.map(alert => ( -
  • - {alert} -
  • - ))} -
-
-
-
    - {power.length === 0 && ( -
  • - Systems Nominal -
  • - )} - {power.map(alert => ( -
  • - {alert} -
  • - ))} -
-
+ {sortedAlarms.map(category => ( +
+
    + {category.alerts?.length === 0 && ( +
  • + Systems Nominal +
  • + )} + {category.alerts.map(alert => ( + + +
  • + {alert.name} {!!cameraView && alert?.sources > 1 + ? " (" + alert.sources + " sources)" : ""} +
  • +
    + {!!cameraView && ( + +
+
+ ))} ); };