From eedc63bbff9c32497c811ffab5447eb20215c420 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Wed, 1 Sep 2021 17:38:53 +0200 Subject: [PATCH] [MIRROR] tgui: Silicon Station Alerts (#7847) * tgui: Silicon Station Alerts * 0 Co-authored-by: Arkatos1 <43862960+Arkatos1@users.noreply.github.com> Co-authored-by: Gandalf --- 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 | 38 ++---- .../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(+), 176 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 6e6200f0af4..31c3f3f742c 100644 --- a/code/_onclick/hud/robot.dm +++ b/code/_onclick/hud/robot.dm @@ -333,4 +333,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 8036fa4dd29..b3c784fc81e 100644 --- a/code/game/machinery/computer/station_alert.dm +++ b/code/game/machinery/computer/station_alert.dm @@ -4,51 +4,35 @@ 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) +/obj/machinery/computer/station_alert/ui_interact(mob/user) //SKYRAT EDIT ADDITON BEGIN - AESTHETICS if(clicksound && world.time > next_clicksound && isliving(user)) next_clicksound = world.time + rand(50, 100) playsound(src, get_sfx_skyrat(clicksound), clickvol) //SKYRAT EDIT END - 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 + 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(listener && length(listener.alarms)) //skyrat edit: fix createanddestroy + 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 bf150c0ff11..0128bd0c5ed 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 interaction_range = null @@ -180,9 +179,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 @@ -216,7 +215,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 @@ -296,40 +295,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!")) @@ -421,15 +386,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, vox_type) //SKYRAT EDIT CHANGE @@ -566,15 +529,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 8540eee1cdf..e0b967632c1 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -68,7 +68,6 @@ toner = tonermax diag_hud_set_borgcell() logevent("System brought online.") - // SKYRAT EDIT ADDITION BEGIN - Cyborg PDA if(!shell) aiPDA = new/obj/item/pda/ai(src) @@ -76,11 +75,11 @@ aiPDA.ownjob = "Cyborg" aiPDA.name = real_name + " (" + aiPDA.ownjob + ")" //SKYRAT EDIT ADDITION END - 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() . = ..() @@ -131,15 +130,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 @@ -212,27 +211,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 362a765aaa0..cdb3829f34c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -471,6 +471,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 && ( + +
+
+ ))} ); };