[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 <jzo123@hotmail.com>
This commit is contained in:
SkyratBot
2021-09-01 16:38:53 +01:00
committed by GitHub
co-authored by Arkatos1 Gandalf
parent 9290399403
commit eedc63bbff
11 changed files with 210 additions and 176 deletions
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -333,4 +333,4 @@
if(.)
return
var/mob/living/silicon/robot/borgo = usr
borgo.robot_alerts()
borgo.alert_control.ui_interact(borgo)
+103
View File
@@ -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
+9 -25
View File
@@ -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"
+7 -48
View File
@@ -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 = "<HEAD><TITLE>Current Station Alerts</TITLE><META HTTP-EQUIV='Refresh' CONTENT='10'></HEAD><BODY>\n"
dat += "<A HREF='?src=[REF(src)];mach_close=aialerts'>Close</A><BR><BR>"
var/list/alarms = listener.alarms
for (var/alarm_type in alarms)
dat += "<B>[alarm_type]</B><BR>\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 += "<NOBR>"
if (C && istype(C, /list))
var/dat2 = ""
for (var/obj/machinery/camera/I in C)
dat2 += "[(dat2=="") ? "" : " | "]<A HREF=?src=[REF(src)];switchcamera=[REF(I)]>[I.c_tag]</A>"
dat += "-- [A.name] ([(dat2!="") ? dat2 : "No Camera"])"
else if (C && istype(C, /obj/machinery/camera))
var/obj/machinery/camera/Ctmp = C
dat += "-- [A.name] (<A HREF=?src=[REF(src)];switchcamera=[REF(C)]>[Ctmp.c_tag]</A>)"
else
dat += "-- [A.name] (No Camera)"
if (sources.len > 1)
dat += "- [sources.len] sources"
dat += "</NOBR><BR>\n"
else
dat += "-- All Systems Nominal<BR>\n"
dat += "<BR>\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
+8 -30
View File
@@ -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 += "<B>[alarm_type]</B><BR>\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 += "<NOBR>"
dat += "-- [A.name]"
dat += "</NOBR><BR>\n"
else
dat += "-- All Systems Nominal<BR>\n"
dat += "<BR>\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
@@ -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
@@ -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)
+1
View File
@@ -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"
@@ -4,8 +4,8 @@ import { StationAlertConsoleContent } from './StationAlertConsole';
export const NtosStationAlertConsole = () => {
return (
<NtosWindow
width={315}
height={500}>
width={335}
height={587}>
<NtosWindow.Content scrollable>
<StationAlertConsoleContent />
</NtosWindow.Content>
@@ -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 (
<Window
width={325}
height={500}>
width={cameraView ? 390 : 345}
height={587}>
<Window.Content scrollable>
<StationAlertConsoleContent />
</Window.Content>
@@ -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 (
<>
<Section title="Fire Alarms">
<ul>
{fire.length === 0 && (
<li className="color-good">
Systems Nominal
</li>
)}
{fire.map(alert => (
<li key={alert} className="color-average">
{alert}
</li>
))}
</ul>
</Section>
<Section title="Atmospherics Alarms">
<ul>
{atmos.length === 0 && (
<li className="color-good">
Systems Nominal
</li>
)}
{atmos.map(alert => (
<li key={alert} className="color-average">
{alert}
</li>
))}
</ul>
</Section>
<Section title="Power Alarms">
<ul>
{power.length === 0 && (
<li className="color-good">
Systems Nominal
</li>
)}
{power.map(alert => (
<li key={alert} className="color-average">
{alert}
</li>
))}
</ul>
</Section>
{sortedAlarms.map(category => (
<Section key={category.name} title={category.name + " Alarms"}>
<ul>
{category.alerts?.length === 0 && (
<li className="color-good">
Systems Nominal
</li>
)}
{category.alerts.map(alert => (
<Stack
key={alert.name}
height="30px"
align="baseline">
<Stack.Item grow>
<li className="color-average">
{alert.name} {!!cameraView && alert?.sources > 1
? " (" + alert.sources + " sources)" : ""}
</li>
</Stack.Item>
{!!cameraView && (
<Stack.Item>
<Button
textAlign="center"
width="100px"
icon={alert.cameras ? "video" : ""}
disabled={!alert.cameras}
content={alert.cameras === 1
? alert.cameras + " Camera" : alert.cameras > 1
? alert.cameras + " Cameras" : "No Camera"}
onClick={() => act('select_camera', {
alert: alert.ref,
})} />
</Stack.Item>
)}
</Stack>
))}
</ul>
</Section>
))}
</>
);
};