Redoes how alarms are handled, moves their behavior to datums (#60060)

* Adds in a set of datums to support sending, listening and storing alerts
In contrast to the old system, we now store a list of send alerts on the listener, rather then the area itself.

This makes clearing "our" alerts on destroy not a massive headache.

In addition, we now use a direct ref to the area's cameras list and signals to prevent camera hard deletes. This, combined with the aformentioned ability to clear, virtually eliminates hard deletes
sourced from alerts caused by strange senarios like the alert source moving its tile.

* Converts areas to the system, of note is the fact that areas no longer store a bool that determins if an alert
for power or atmos has been sent, that's instead handled by the alert sender datum. This means the sources list
on alert listeners actually means something

additionally, in order to prevent dumbassery with fire alarms since they're area based, fire alerts are sent by
an alert handler on the area itself
This commit is contained in:
LemonInTheDark
2021-08-13 11:54:44 -07:00
committed by GitHub
parent 3352d10c97
commit 79dc58fe2a
19 changed files with 425 additions and 579 deletions
+13
View File
@@ -0,0 +1,13 @@
//A set of defines to be used by the alarm datums
///Sent by air alarms, indecates something wrong with thier attached atmosphere
#define ALARM_ATMOS "Atmosphere"
///Sent by fire alarms when they are toggled
#define ALARM_FIRE "Fire"
///Sent by apcs when their power starts to fail
#define ALARM_POWER "Power"
///Sent by cameras when they're disabled in some manner
#define ALARM_CAMERA "Camera"
///Sent by display cases when they're broken into
#define ALARM_BURGLAR "Burglar"
///Sent by motion detecting cameras when they well, detect motion
#define ALARM_MOTION "Motion"
+9
View File
@@ -49,6 +49,10 @@
#define COMSIG_WEATHER_START(event_type) "!weather_start [event_type]"
#define COMSIG_WEATHER_WINDDOWN(event_type) "!weather_winddown [event_type]"
#define COMSIG_WEATHER_END(event_type) "!weather_end [event_type]"
/// An alarm of some form was sent (datum/alarm_handler/source, alarm_type, area/source_area)
#define COMSIG_ALARM_FIRE(alarm_type) "!alarm_fire [alarm_type]"
/// An alarm of some form was cleared (datum/alarm_handler/source, alarm_type, area/source_area)
#define COMSIG_ALARM_CLEAR(alarm_type) "!alarm_clear [alarm_type]"
/// signals from globally accessible objects
@@ -1381,6 +1385,11 @@
/// Called on the merger after finishing a refresh: (list/leaving_members, list/joining_members)
#define COMSIG_MERGER_REFRESH_COMPLETE "comsig_merger_refresh_complete"
// Alarm listener datum signals
///Sent when an alarm is fired (alarm, area/source_area)
#define COMSIG_ALARM_TRIGGERED "comsig_alarm_triggered"
///Send when an alarm source is cleared (alarm_type, area/source_area)
#define COMSIG_ALARM_CLEARED "comsig_alarm_clear"
// Vacuum signals
/// Called on a bag being attached to a vacuum parent
#define COMSIG_VACUUM_BAG_ATTACH "comsig_vacuum_bag_attach"
+199
View File
@@ -0,0 +1,199 @@
//This files deals with the generic sending and receiving of "alarms"
//This is a somewhat blanket term, it covers things like fire/power/atmos alarms, along with some oddballs
//Side effect of how the system is used, these are mostly things that are of interest to ais and borgs
//Though it could easily be expanded to cover other senders/revievers
//The system as a whole differs from reading off a global list in a few ways.
//In that A, it allows us to send cameras for ais/borgs/potentially others to jump to
//And B, it's not like we're giving you all the alarms that have been sent, because of the seperate listing for each reviever
//You only recieve alarms sent after you start to listen
//Also of note, due to an optimzation done on areas, one alarm handler will only ever send one "on" or "off" alarm
//So the whole only receving stuff sent post creation thing actually matters
//Honestly I'm not sure how much of this is a feature, and how much is just old code
//But I'm leaving it how I found it
///Represents a single source of alarms, one alarm handler will only ever count for one alarm per listener
/datum/alarm_handler
///A list of alarm type -> list of areas we currently have alarms in
var/list/sent_alarms = list()
///Our source atom
var/atom/source_atom
/datum/alarm_handler/New(atom/source_atom)
if(istype(source_atom))
src.source_atom = source_atom
else
var/source_type = (istype(source_atom, /datum)) ? source_atom.type : ""
stack_trace("a non atom was passed into alarm_handler! [source_atom] [source_type]")
return ..()
/datum/alarm_handler/Destroy()
for(var/alarm_type in sent_alarms)
for(var/area/area_to_clear as anything in sent_alarms[alarm_type])
//Yeet all connected alarms
clear_alarm_from_area(alarm_type, area_to_clear)
source_atom = null
return ..()
///Sends an alarm to any interested things, does some checks to prevent unneeded work
///Important to note is that source_atom is not held as a ref, we're used as a proxy to prevent hard deletes
///optional_camera should only be used when you have one camera you want to pass along to alarm listeners, most of the time you should have no use for it
/datum/alarm_handler/proc/send_alarm(alarm_type, atom/use_as_source_atom, optional_camera)
if(!use_as_source_atom)
use_as_source_atom = source_atom
if(!use_as_source_atom)
return
var/area/our_area = get_area(use_as_source_atom)
var/our_z_level = use_as_source_atom.z
if (our_area.area_flags & NO_ALERTS)
return FALSE
var/list/existing_alarms = sent_alarms[alarm_type]
if(existing_alarms)
if(our_area in existing_alarms)
return FALSE
else
sent_alarms[alarm_type] = list()
existing_alarms = sent_alarms[alarm_type]
existing_alarms += our_area
our_area.active_alarms[alarm_type] += 1
SEND_GLOBAL_SIGNAL(COMSIG_ALARM_FIRE(alarm_type), src, alarm_type, our_area, our_z_level, optional_camera)
return TRUE
///Clears an alarm from any interested listeners
/datum/alarm_handler/proc/clear_alarm(alarm_type, use_as_source_atom)
SIGNAL_HANDLER
if(!use_as_source_atom)
use_as_source_atom = source_atom
if(!use_as_source_atom)
return
return clear_alarm_from_area(alarm_type, get_area(use_as_source_atom))
///Exists so we can request that the alarms from an area are cleared, even if our source atom is no longer in that area
/datum/alarm_handler/proc/clear_alarm_from_area(alarm_type, area/our_area)
if (our_area.area_flags & NO_ALERTS)
return FALSE
var/list/existing_alarms = sent_alarms[alarm_type]
if(!existing_alarms)
return FALSE
if(!(our_area in existing_alarms))
return FALSE
existing_alarms -= our_area
if(!length(existing_alarms))
sent_alarms -= alarm_type
our_area.active_alarms[alarm_type] -= 1
if(!length(our_area.active_alarms))
our_area.active_alarms -= alarm_type
SEND_GLOBAL_SIGNAL(COMSIG_ALARM_CLEAR(alarm_type), src, alarm_type, our_area)
return TRUE
/datum/alarm_listener
///List of valid source z levels, ignored if null
var/list/allowed_z_levels
///List of allowed areas. if this is null it's ignored
var/list/allowed_areas
///List of alarm type -> list of area name -> list(area, ref to area's cameras, list(sources))
var/list/alarms = list()
///Should we allow alarm changes to go through or not
var/accepting_alarm_changes = TRUE
///Accepts a list of alarm types to pay attention to, a list of valid z levels, and a list of valid areas. areas and zlevels are ignored if null
/datum/alarm_listener/New(alarms_to_listen_for, allowed_z_levels, allowed_areas)
src.allowed_z_levels = allowed_z_levels
src.allowed_areas = allowed_areas
for(var/alarm_type in alarms_to_listen_for)
RegisterSignal(SSdcs, COMSIG_ALARM_FIRE(alarm_type), .proc/add_alarm)
RegisterSignal(SSdcs, COMSIG_ALARM_CLEAR(alarm_type), .proc/clear_alarm)
return ..()
///Adds an alarm to our alarms list, you shouldn't be calling this manually
///It should all be handled by the signal listening we do, unless you want to only send an alarm to one listener
/datum/alarm_listener/proc/add_alarm(datum/source, datum/alarm_handler/handler, alarm_type, area/source_area, source_z, optional_camera)
if (!accepting_alarm_changes)
return
if(allowed_z_levels && !(source_z in allowed_z_levels))
return
if(allowed_areas && !(source_area.type in allowed_areas))
return
var/list/alarms_of_our_type = alarms[alarm_type]
if(!alarms_of_our_type)
alarms[alarm_type] = list()
alarms_of_our_type = alarms[alarm_type]
if(alarms_of_our_type[source_area.name])
var/list/alarm = alarms_of_our_type[source_area.name]
var/list/sources = alarm[3]
sources |= handler
//Return if a source already exists, we don't want to send a signal or add a new entry
return
//We normally directly pass in a ref to the area's camera's list to prevent hanging refs
var/list/cameras = source_area.cameras
if(optional_camera)
cameras = list(optional_camera) // This will cause harddels, so we need to clear manually
RegisterSignal(optional_camera, COMSIG_PARENT_QDELETING, .proc/clear_camera_ref, override = TRUE) //It's just fine to override, cause we clear all refs in the proc
//This does mean that only the first alarm of that camera type in the area will send a ping, but jesus what else can ya do
alarms_of_our_type[source_area.name] = list(source_area, cameras, list(handler))
SEND_SIGNAL(src, COMSIG_ALARM_TRIGGERED, alarm_type, source_area)
///Removes an alarm to our alarms list, you probably shouldn't be calling this manually
///It should all be handled by the signal listening we do, unless you want to only remove an alarm to one listener
/datum/alarm_listener/proc/clear_alarm(datum/source, datum/alarm_handler/handler, alarm_type, area/source_area)
if(!accepting_alarm_changes)
return
var/list/alarms_of_our_type = alarms[alarm_type]
if(!alarms_of_our_type)
return
if(!alarms_of_our_type[source_area.name])
return
var/list/alarm = alarms_of_our_type[source_area.name]
var/list/sources = alarm[3]
sources -= handler
if (length(sources))
return //Return if there's still sources left, no sense clearing the list or bothering anyone about it
alarms_of_our_type -= source_area.name
SEND_SIGNAL(src, COMSIG_ALARM_CLEARED, alarm_type, source_area)
///Does what it says on the tin, exists for signal hooking
/datum/alarm_listener/proc/prevent_alarm_changes()
SIGNAL_HANDLER
accepting_alarm_changes = FALSE
///Does what it says on the tin, exists for signal hooking
/datum/alarm_listener/proc/allow_alarm_changes()
SIGNAL_HANDLER
accepting_alarm_changes = TRUE
///Used to manually clear camera refs if one is ref'd directly
/datum/alarm_listener/proc/clear_camera_ref(obj/machinery/camera/source)
SIGNAL_HANDLER
var/list/alarms_cache = alarms //Cache for sonic speec
for(var/alarm_type in alarms_cache)
var/list/alarms_of_type = alarms_cache[alarm_type] //Sonic cache speed forads
for(var/area_name as anything in alarms_of_type)
var/list/alarm_packet = alarms_of_type[area_name]
var/list/cameras = alarm_packet[2]
cameras -= source // REF FOUND AND CLEARED BOYSSSS
+2 -4
View File
@@ -48,8 +48,7 @@
A.mode = 1 // AALARM_MODE_SCRUB
A.apply_mode(usr)
if(WIRE_ALARM) // Clear alarms.
var/area/AA = get_area(A)
if(AA.atmosalert(FALSE, holder))
if(A.alarm_manager.clear_alarm(ALARM_ATMOS))
A.post_alert(0)
A.update_appearance()
@@ -70,7 +69,6 @@
A.mode = 3 // AALARM_MODE_PANIC
A.apply_mode(usr)
if(WIRE_ALARM) // Post alarm.
var/area/AA = get_area(A)
if(AA.atmosalert(TRUE, holder))
if(A.alarm_manager.send_alarm(ALARM_ATMOS))
A.post_alert(2)
A.update_appearance()
+11 -129
View File
@@ -19,9 +19,11 @@
var/fire = FALSE
///How many fire alarm sources do we have?
var/triggered_firealarms = 0
///Whether there is an atmos alarm in this area
var/atmosalm = FALSE
var/poweralm = FALSE
///Alarm type to count of sources. Not usable for ^ because we handle fires differently
var/list/active_alarms = list()
///We use this just for fire alarms, because they're area based right now so one alarm going poof shouldn't prevent you from clearing your alarms listing
var/datum/alarm_handler/alarm_manager
var/lightswitch = TRUE
/// All beauty in this area combined, only includes indoor area.
@@ -133,6 +135,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
if (area_flags & UNIQUE_AREA)
GLOB.areas_by_type[type] = src
power_usage = new /list(AREA_USAGE_LEN) // Some atoms would like to use power in Initialize()
alarm_manager = new(src) // just in case
return ..()
/*
@@ -231,88 +234,9 @@ GLOBAL_LIST_EMPTY(teleportlocs)
GLOB.areas_by_type[type] = null
GLOB.sortedAreas -= src
STOP_PROCESSING(SSobj, src)
QDEL_NULL(alarm_manager)
return ..()
/**
* Generate a power alert for this area
*
* Sends to all ai players, alert consoles, drones and alarm monitor programs in the world
*/
/area/proc/poweralert(state, obj/source)
if (area_flags & NO_ALERTS)
return
if (state != poweralm)
poweralm = state
if(istype(source)) //Only report power alarms on the z-level where the source is located.
for (var/item in GLOB.silicon_mobs)
var/mob/living/silicon/aiPlayer = item
if (!state)
aiPlayer.cancelAlarm("Power", src, source)
else
aiPlayer.triggerAlarm("Power", src, cameras, source)
for (var/item in GLOB.alert_consoles)
var/obj/machinery/computer/station_alert/a = item
if(!state)
a.cancelAlarm("Power", src, source)
else
a.triggerAlarm("Power", src, cameras, source)
for (var/item in GLOB.drones_list)
var/mob/living/simple_animal/drone/D = item
if(!state)
D.cancelAlarm("Power", src, source)
else
D.triggerAlarm("Power", src, cameras, source)
for(var/item in GLOB.alarmdisplay)
var/datum/computer_file/program/alarm_monitor/p = item
if(!state)
p.cancelAlarm("Power", src, source)
else
p.triggerAlarm("Power", src, cameras, source)
/**
* Generate an atmospheric alert for this area
*
* Sends to all ai players, alert consoles, drones and alarm monitor programs in the world
*/
/area/proc/atmosalert(isdangerous, obj/source)
if (area_flags & NO_ALERTS)
return
if(isdangerous != atmosalm)
if(isdangerous)
for (var/item in GLOB.silicon_mobs)
var/mob/living/silicon/aiPlayer = item
aiPlayer.triggerAlarm("Atmosphere", src, cameras, source)
for (var/item in GLOB.alert_consoles)
var/obj/machinery/computer/station_alert/a = item
a.triggerAlarm("Atmosphere", src, cameras, source)
for (var/item in GLOB.drones_list)
var/mob/living/simple_animal/drone/D = item
D.triggerAlarm("Atmosphere", src, cameras, source)
for(var/item in GLOB.alarmdisplay)
var/datum/computer_file/program/alarm_monitor/p = item
p.triggerAlarm("Atmosphere", src, cameras, source)
else
for (var/item in GLOB.silicon_mobs)
var/mob/living/silicon/aiPlayer = item
aiPlayer.cancelAlarm("Atmosphere", src, source)
for (var/item in GLOB.alert_consoles)
var/obj/machinery/computer/station_alert/a = item
a.cancelAlarm("Atmosphere", src, source)
for (var/item in GLOB.drones_list)
var/mob/living/simple_animal/drone/D = item
D.cancelAlarm("Atmosphere", src, source)
for(var/item in GLOB.alarmdisplay)
var/datum/computer_file/program/alarm_monitor/p = item
p.cancelAlarm("Atmosphere", src, source)
atmosalm = isdangerous
return TRUE
return FALSE
/**
* Try to close all the firedoors in the area
*/
@@ -350,19 +274,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
for(var/item in firealarms)
var/obj/machinery/firealarm/F = item
F.update_appearance()
if (!(area_flags & NO_ALERTS)) //Check here instead at the start of the proc so that fire alarms can still work locally even in areas that don't send alerts
for (var/item in GLOB.alert_consoles)
var/obj/machinery/computer/station_alert/a = item
a.triggerAlarm("Fire", src, cameras, source)
for (var/item in GLOB.silicon_mobs)
var/mob/living/silicon/aiPlayer = item
aiPlayer.triggerAlarm("Fire", src, cameras, source)
for (var/item in GLOB.drones_list)
var/mob/living/simple_animal/drone/D = item
D.triggerAlarm("Fire", src, cameras, source)
for(var/item in GLOB.alarmdisplay)
var/datum/computer_file/program/alarm_monitor/p = item
p.triggerAlarm("Fire", src, cameras, source)
alarm_manager.send_alarm(ALARM_FIRE, source)
START_PROCESSING(SSobj, src)
/**
@@ -391,33 +303,9 @@ GLOBAL_LIST_EMPTY(teleportlocs)
for(var/item in firealarms)
var/obj/machinery/firealarm/F = item
F.update_appearance()
if (!(area_flags & NO_ALERTS)) //Check here instead at the start of the proc so that fire alarms can still work locally even in areas that don't send alerts
for (var/item in GLOB.silicon_mobs)
var/mob/living/silicon/aiPlayer = item
aiPlayer.cancelAlarm("Fire", src, source)
for (var/item in GLOB.alert_consoles)
var/obj/machinery/computer/station_alert/a = item
a.cancelAlarm("Fire", src, source)
for (var/item in GLOB.drones_list)
var/mob/living/simple_animal/drone/D = item
D.cancelAlarm("Fire", src, source)
for(var/item in GLOB.alarmdisplay)
var/datum/computer_file/program/alarm_monitor/p = item
p.cancelAlarm("Fire", src, source)
alarm_manager.clear_alarm(ALARM_FIRE, source)
STOP_PROCESSING(SSobj, src)
///Get rid of any dangling camera refs
/area/proc/clear_camera(obj/machinery/camera/cam)
LAZYREMOVE(cameras, cam)
for (var/mob/living/silicon/aiPlayer as anything in GLOB.silicon_mobs)
aiPlayer.freeCamera(src, cam)
for (var/obj/machinery/computer/station_alert/comp as anything in GLOB.alert_consoles)
comp.freeCamera(src, cam)
for (var/mob/living/simple_animal/drone/drone_on as anything in GLOB.drones_list)
drone_on.freeCamera(src, cam)
for(var/datum/computer_file/program/alarm_monitor/monitor as anything in GLOB.alarmdisplay)
monitor.freeCamera(src, cam)
/**
* If 100 ticks has elapsed, toggle all the firedoors closed again
*/
@@ -451,14 +339,8 @@ GLOBAL_LIST_EMPTY(teleportlocs)
//Trigger alarm effect
set_fire_alarm_effect()
//Lockdown airlocks
for(var/obj/machinery/door/DOOR in src)
close_and_lock_door(DOOR)
for (var/i in GLOB.silicon_mobs)
var/mob/living/silicon/SILICON = i
if(SILICON.triggerAlarm("Burglar", src, cameras, trigger))
//Cancel silicon alert after 1 minute
addtimer(CALLBACK(SILICON, /mob/living/silicon.proc/cancelAlarm,"Burglar",src,trigger), 600)
for(var/obj/machinery/door/door in src)
close_and_lock_door(door)
/**
* Trigger the fire alarm visual affects in an area
+8 -5
View File
@@ -40,6 +40,8 @@
var/upgrades = 0
var/internal_light = TRUE //Whether it can light up when an AI views it
///Represents a signel source of camera alarms about movement or camera tampering
var/datum/alarm_handler/alarm_manager
/obj/machinery/camera/preset/toxins //Bomb test site in space
name = "Hardened Bomb-Test Camera"
@@ -86,6 +88,8 @@
else //this is handled by toggle_camera, so no need to update it twice.
update_appearance()
alarm_manager = new(src)
/obj/machinery/camera/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
for(var/i in network)
network -= i
@@ -106,7 +110,8 @@
GLOB.cameranet.cameras -= src
cancelCameraAlarm()
if(isarea(myarea))
myarea.clear_camera(src)
LAZYREMOVE(myarea.cameras, src)
QDEL_NULL(alarm_manager)
QDEL_NULL(assembly_ref)
if(bug)
bug.bugged_cameras -= c_tag
@@ -443,13 +448,11 @@
/obj/machinery/camera/proc/triggerCameraAlarm()
alarm_on = TRUE
for(var/mob/living/silicon/S in GLOB.silicon_mobs)
S.triggerAlarm("Camera", get_area(src), list(src), src)
alarm_manager.send_alarm(ALARM_CAMERA, src, src)
/obj/machinery/camera/proc/cancelCameraAlarm()
alarm_on = FALSE
for(var/mob/living/silicon/S in GLOB.silicon_mobs)
S.cancelAlarm("Camera", get_area(src), src)
alarm_manager.clear_alarm(ALARM_CAMERA)
/obj/machinery/camera/proc/can_use()
if(!status)
+4 -8
View File
@@ -51,20 +51,16 @@
cancelAlarm()
/obj/machinery/camera/proc/cancelAlarm()
if (detectTime == -1)
for (var/i in GLOB.silicon_mobs)
var/mob/living/silicon/aiPlayer = i
if (status)
aiPlayer.cancelAlarm("Motion", get_area(src), src)
if (detectTime == -1 && status)
alarm_manager.clear_alarm(ALARM_MOTION)
detectTime = 0
return TRUE
/obj/machinery/camera/proc/triggerAlarm()
if (!detectTime)
return FALSE
for (var/mob/living/silicon/aiPlayer in GLOB.player_list)
if (status)
aiPlayer.triggerAlarm("Motion", get_area(src), list(src), src)
if(status)
if(alarm_manager.send_alarm(ALARM_MOTION, src, src))
visible_message(span_warning("A red light flashes on the [src]!"))
detectTime = -1
return TRUE
+16 -70
View File
@@ -4,16 +4,17 @@
icon_screen = "alert:0"
icon_keyboard = "atmos_key"
circuit = /obj/item/circuitboard/computer/stationalert
var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list())
///Listens for alarms, provides the alarms list for our ui
var/datum/alarm_listener/listener
light_color = LIGHT_COLOR_CYAN
/obj/machinery/computer/station_alert/Initialize()
. = ..()
GLOB.alert_consoles += src
listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), list(z))
return ..()
/obj/machinery/computer/station_alert/Destroy()
GLOB.alert_consoles -= src
QDEL_NULL(listener)
return ..()
/obj/machinery/computer/station_alert/ui_interact(mob/user, datum/tgui/ui)
@@ -26,78 +27,23 @@
var/list/data = list()
data["alarms"] = list()
for(var/class in alarms)
data["alarms"][class] = list()
for(var/area in alarms[class])
data["alarms"][class] += area
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/proc/triggerAlarm(class, area/home, cameras, obj/source)
if(source.z != z)
return
if(machine_stat & (BROKEN))
return
var/list/our_sort = alarms[class]
for(var/areaname in our_sort)
if (areaname == home.name)
var/list/alarm = our_sort[areaname]
var/list/sources = alarm[3]
if (!(source in sources))
sources += source
return TRUE
var/obj/machinery/camera/cam = null
var/list/our_cams = null
if(cameras && islist(cameras))
our_cams = cameras
if (our_cams.len == 1)
cam = our_cams[1]
else if(cameras && istype(cameras, /obj/machinery/camera))
cam = cameras
our_sort[home.name] = list(home, (cam ? cam : cameras), list(source))
return TRUE
/obj/machinery/computer/station_alert/proc/freeCamera(area/home, obj/machinery/camera/cam)
for(var/class in alarms)
var/our_area = alarms[class][home.name]
if(!our_area)
continue
var/cams = our_area[2] //Get the cameras
if(!cams)
continue
if(islist(cams))
cams -= cam
if(length(cams) == 1)
our_area[2] = cams[1]
else
our_area[2] = null
/obj/machinery/computer/station_alert/proc/cancelAlarm(class, area/A, obj/origin)
if(machine_stat & (BROKEN))
return
var/list/L = alarms[class]
var/cleared = 0
for (var/I in L)
if (I == A.name)
var/list/alarm = L[I]
var/list/srcs = alarm[3]
if (origin in srcs)
srcs -= origin
if (srcs.len == 0)
cleared = 1
L -= I
return !cleared
/obj/machinery/computer/station_alert/on_set_machine_stat(old_value)
if(machine_stat & BROKEN)
listener.prevent_alarm_changes()
else
listener.allow_alarm_changes()
/obj/machinery/computer/station_alert/update_overlays()
. = ..()
if(machine_stat & (NOPOWER|BROKEN))
return
var/active_alarms = FALSE
for(var/cat in alarms)
var/list/L = alarms[cat]
if(L.len)
active_alarms = TRUE
if(active_alarms)
if(length(listener.alarms))
. += "alert:2"
+9 -1
View File
@@ -20,6 +20,8 @@
var/list/start_showpieces = list() //Takes sublists in the form of list("type" = /obj/item/bikehorn, "trophy_message" = "henk")
var/trophy_message = ""
var/glass_fix = TRUE
///Represents a signel source of screaming when broken
var/datum/alarm_handler/alarm_manager
/obj/structure/displaycase/Initialize()
. = ..()
@@ -32,6 +34,7 @@
if(start_showpiece_type)
showpiece = new start_showpiece_type (src)
update_appearance()
alarm_manager = new(src)
/obj/structure/displaycase/vv_edit_var(vname, vval)
. = ..()
@@ -49,6 +52,7 @@
/obj/structure/displaycase/Destroy()
QDEL_NULL(electronics)
QDEL_NULL(showpiece)
QDEL_NULL(alarm_manager)
return ..()
/obj/structure/displaycase/examine(mob/user)
@@ -98,6 +102,10 @@
return
var/area/alarmed = get_area(src)
alarmed.burglaralert(src)
alarm_manager.send_alarm(ALARM_BURGLAR)
addtimer(CALLBACK(alarm_manager, /datum/alarm_handler/proc/clear_alarm, ALARM_BURGLAR), 1 MINUTES)
playsound(src, 'sound/effects/alert.ogg', 50, TRUE)
/obj/structure/displaycase/update_overlays()
@@ -122,7 +130,7 @@
to_chat(user, span_notice("You [open ? "close":"open"] [src]."))
toggle_lock(user)
else
to_chat(user, span_alert("Access denied."))
to_chat(user, span_alert("Access denied."))
else if(W.tool_behaviour == TOOL_WELDER && !user.combat_mode && !broken)
if(obj_integrity < max_integrity)
if(!W.tool_start_check(user, amount=5))
+18 -13
View File
@@ -82,6 +82,8 @@
var/frequency = FREQ_ATMOS_CONTROL
var/alarm_frequency = FREQ_ATMOS_ALARMS
var/datum/radio_frequency/radio_connection
///Represents a signel source of atmos alarms, complains to all the listeners if one of our thresholds is violated
var/datum/alarm_handler/alarm_manager
var/list/TLV = list( // Breathable air.
"pressure" = new/datum/tlv(HAZARD_LOW_PRESSURE, WARNING_LOW_PRESSURE, WARNING_HIGH_PRESSURE, HAZARD_HIGH_PRESSURE), // kPa. Values are min2, min1, max1, max2
@@ -227,14 +229,13 @@
if(name == initial(name))
name = "[get_area_name(src)] Air Alarm"
alarm_manager = new(src)
update_appearance()
/obj/machinery/airalarm/Destroy()
SSradio.remove_object(src, frequency)
qdel(wires)
wires = null
var/area/ourarea = get_area(src)
ourarea.atmosalert(FALSE, src)
QDEL_NULL(wires)
QDEL_NULL(alarm_manager)
return ..()
/obj/machinery/airalarm/Initialize(mapload)
@@ -277,7 +278,7 @@
)
var/area/A = get_area(src)
data["atmos_alarm"] = A.atmosalm
data["atmos_alarm"] = !!A.active_alarms[ALARM_ATMOS]
data["fire_alarm"] = A.fire
var/turf/T = get_turf(src)
@@ -447,13 +448,11 @@
apply_mode(usr)
. = TRUE
if("alarm")
var/area/A = get_area(src)
if(A.atmosalert(TRUE, src))
if(alarm_manager.send_alarm(ALARM_ATMOS))
post_alert(2)
. = TRUE
if("reset")
var/area/A = get_area(src)
if(A.atmosalert(FALSE, src))
if(alarm_manager.clear_alarm(ALARM_ATMOS))
post_alert(0)
. = TRUE
update_appearance()
@@ -659,8 +658,8 @@
icon_state = "alarmp"
return ..()
var/area/A = get_area(src)
switch(max(danger_level, A.atmosalm))
var/area/our_area = get_area(src)
switch(max(danger_level, !!our_area.active_alarms[ALARM_ATMOS]))
if(0)
icon_state = "alarm0"
if(1)
@@ -734,8 +733,14 @@
var/new_area_danger_level = 0
for(var/obj/machinery/airalarm/AA in A)
if (!(AA.machine_stat & (NOPOWER|BROKEN)) && !AA.shorted)
new_area_danger_level = clamp(max(new_area_danger_level, AA.danger_level), 0,1)
if(A.atmosalert(new_area_danger_level,src)) //if area was in normal state or if area was in alert state
new_area_danger_level = clamp(max(new_area_danger_level, AA.danger_level), 0, 1)
var/did_anything_happen
if(new_area_danger_level)
did_anything_happen = alarm_manager.send_alarm(ALARM_ATMOS)
else
did_anything_happen = alarm_manager.clear_alarm(ALARM_ATMOS)
if(did_anything_happen) //if something actually changed
post_alert(new_area_danger_level)
update_appearance()
@@ -1,4 +1,4 @@
// Look up levels[z].traits[trait]
/// Look up levels[z].traits[trait]
/datum/controller/subsystem/mapping/proc/level_trait(z, trait)
if (!isnum(z) || z < 1)
return null
@@ -15,21 +15,21 @@
return list()
return default[z][DL_TRAITS][trait]
// Check if levels[z] has any of the specified traits
/// Check if levels[z] has any of the specified traits
/datum/controller/subsystem/mapping/proc/level_has_any_trait(z, list/traits)
for (var/I in traits)
if (level_trait(z, I))
return TRUE
return FALSE
// Check if levels[z] has all of the specified traits
/// Check if levels[z] has all of the specified traits
/datum/controller/subsystem/mapping/proc/level_has_all_traits(z, list/traits)
for (var/I in traits)
if (!level_trait(z, I))
return FALSE
return TRUE
// Get a list of all z which have the specified trait
/// Get a list of all z which have the specified trait
/datum/controller/subsystem/mapping/proc/levels_by_trait(trait)
. = list()
var/list/_z_list = z_list
@@ -38,7 +38,7 @@
if (S.traits[trait])
. += S.z_value
// Get a list of all z which have any of the specified traits
/// Get a list of all z which have any of the specified traits
/datum/controller/subsystem/mapping/proc/levels_by_any_trait(list/traits)
. = list()
var/list/_z_list = z_list
@@ -49,7 +49,7 @@
. += S.z_value
break
// Attempt to get the turf below the provided one according to Z traits
/// Attempt to get the turf below the provided one according to Z traits
/datum/controller/subsystem/mapping/proc/get_turf_below(turf/T)
if (!T)
return
@@ -58,7 +58,7 @@
return
return locate(T.x, T.y, T.z + offset)
// Attempt to get the turf above the provided one according to Z traits
/// Attempt to get the turf above the provided one according to Z traits
/datum/controller/subsystem/mapping/proc/get_turf_above(turf/T)
if (!T)
return
@@ -67,7 +67,7 @@
return
return locate(T.x, T.y, T.z + offset)
// Prefer not to use this one too often
/// Prefer not to use this one too often
/datum/controller/subsystem/mapping/proc/get_station_center()
var/station_z = levels_by_trait(ZTRAIT_STATION)[1]
return locate(round(world.maxx * 0.5, 1), round(world.maxy * 0.5, 1), station_z)
+46 -84
View File
@@ -37,7 +37,6 @@
var/aiRestorePowerRoutine = POWER_RESTORATION_OFF
var/requires_power = POWER_REQ_ALL
var/can_be_carded = TRUE
var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list(), "Burglar"=list())
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.
@@ -101,6 +100,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
///remember AI's last location
var/atom/lastloc
@@ -176,6 +177,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)
/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
@@ -209,6 +213,7 @@
QDEL_NULL(doomsday_device)
QDEL_NULL(robot_control)
QDEL_NULL(aiMulti)
QDEL_NULL(listener)
malfhack = null
current = null
Bot = null
@@ -268,12 +273,12 @@
/mob/living/silicon/ai/get_status_tab_items()
. = ..()
if(stat != CONSCIOUS)
. += text("Systems nonfunctional")
. += "Systems nonfunctional"
return
. += text("System integrity: [(health + 100) * 0.5]%")
. += "System integrity: [(health + 100) * 0.5]%"
if(isturf(loc)) //only show if we're "in" a core
. += text("Backup Power: [battery * 0.5]%")
. += text("Connected cyborgs: [length(connected_robots)]")
. += "Backup Power: [battery * 0.5]%"
. += "Connected cyborgs: [length(connected_robots)]"
for(var/r in connected_robots)
var/mob/living/silicon/robot/connected_robot = r
var/robot_status = "Nominal"
@@ -284,19 +289,20 @@
else if(!connected_robot.cell || connected_robot.cell.charge <= 0)
robot_status = "DEPOWERED"
//Name, Health, Battery, Model, Area, and Status! Everything an AI wants to know about its borgies!
. += text("[connected_robot.name] | S.Integrity: [connected_robot.health]% | Cell: [connected_robot.cell ? "[connected_robot.cell.charge]/[connected_robot.cell.maxcharge]" : "Empty"] | \
Model: [connected_robot.designation] | Loc: [get_area_name(connected_robot, TRUE)] | Status: [robot_status]")
. += text("AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]") //Count of total AI shells
. += "[connected_robot.name] | S.Integrity: [connected_robot.health]% | Cell: [connected_robot.cell ? "[connected_robot.cell.charge]/[connected_robot.cell.maxcharge]" : "Empty"] | \
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>"
for (var/cat in alarms)
dat += text("<B>[]</B><BR>\n", cat)
var/list/L = alarms[cat]
if (L.len)
for (var/alarm in L)
var/list/alm = L[alarm]
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]
@@ -304,15 +310,15 @@
if (C && istype(C, /list))
var/dat2 = ""
for (var/obj/machinery/camera/I in C)
dat2 += text("[]<A HREF=?src=[REF(src)];switchcamera=[REF(I)]>[]</A>", (dat2=="") ? "" : " | ", I.c_tag)
dat += text("-- [] ([])", A.name, (dat2!="") ? dat2 : "No Camera")
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 += text("-- [] (<A HREF=?src=[REF(src)];switchcamera=[REF(C)]>[]</A>)", A.name, Ctmp.c_tag)
dat += "-- [A.name] (<A HREF=?src=[REF(src)];switchcamera=[REF(C)]>[Ctmp.c_tag]</A>)"
else
dat += text("-- [] (No Camera)", A.name)
dat += "-- [A.name] (No Camera)"
if (sources.len > 1)
dat += text("- [] sources", sources.len)
dat += "- [sources.len] sources"
dat += "</NOBR><BR>\n"
else
dat += "-- All Systems Nominal<BR>\n"
@@ -414,7 +420,7 @@
if (href_list["mach_close"])
if (href_list["mach_close"] == "aialerts")
viewalerts = 0
var/t1 = text("window=[]", href_list["mach_close"])
var/t1 = "window=[href_list["mach_close"]]"
unset_machine()
src << browse(null, t1)
if (href_list["switchcamera"])
@@ -538,77 +544,33 @@
Bot.call_bot(src, waypoint)
call_bot_cooldown = 0
/mob/living/silicon/ai/triggerAlarm(class, area/home, cameras, obj/source)
if(source.z != z)
return
var/list/our_sort = alarms[class]
for(var/areaname in our_sort)
if (areaname == home.name)
var/list/alarm = our_sort[areaname]
var/list/sources = alarm[3]
if (!(source in sources))
sources += source
return TRUE
/mob/living/silicon/ai/proc/alarm_triggered(datum/source, alarm_type, area/source_area)
SIGNAL_HANDLER
var/list/cameras = source_area.cameras
var/home_name = source_area.name
var/obj/machinery/camera/cam = null
var/list/our_cams = null
if(cameras && islist(cameras))
our_cams = cameras
if (our_cams.len == 1)
cam = our_cams[1]
else if(cameras && istype(cameras, /obj/machinery/camera))
cam = cameras
our_sort[home.name] = list(home, (cam ? cam : cameras), list(source))
if (cameras)
if (cam?.can_use())
queueAlarm("--- [class] alarm detected in [home.name]! (<A HREF=?src=[REF(src)];switchcamera=[REF(cam)]>[cam.c_tag]</A>)", class)
else if (our_cams?.len)
var/foo = 0
var/dat2 = ""
for (var/obj/machinery/camera/I in our_cams)
dat2 += text("[]<A HREF=?src=[REF(src)];switchcamera=[REF(I)]>[]</A>", (!foo) ? "" : " | ", I.c_tag) //I'm not fixing this shit...
foo = 1
queueAlarm(text ("--- [] alarm detected in []! ([])", class, home.name, dat2), class)
if (length(cameras))
var/obj/machinery/camera/cam = cameras[1]
if (cam.can_use())
queueAlarm("--- [alarm_type] alarm detected in [home_name]! (<A HREF=?src=[REF(src)];switchcamera=[REF(cam)]>[cam.c_tag]</A>)", alarm_type)
else
queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, home.name), class)
var/first_run = FALSE
var/dat2 = ""
for (var/obj/machinery/camera/camera as anything in cameras)
dat2 += "[(!first_run) ? "" : " | "]<A HREF=?src=[REF(src)];switchcamera=[REF(camera)]>[camera.c_tag]</A>"
first_run = TRUE
queueAlarm("--- [alarm_type] alarm detected in [home_name]! ([dat2])", alarm_type)
else
queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, home.name), class)
queueAlarm("--- [alarm_type] alarm detected in [home_name]! (No Camera)", alarm_type)
if (viewalerts)
ai_alerts()
return 1
/mob/living/silicon/ai/freeCamera(area/home, obj/machinery/camera/cam)
for(var/class in alarms)
var/our_area = alarms[class][home.name]
if(!our_area)
continue
var/cams = our_area[2] //Get the cameras
if(!cams)
continue
if(islist(cams))
cams -= cam
if(length(cams) == 1)
our_area[2] = cams[1]
else
our_area[2] = null
/mob/living/silicon/ai/cancelAlarm(class, area/A, obj/origin)
var/list/L = alarms[class]
var/cleared = 0
for (var/I in L)
if (I == A.name)
var/list/alarm = L[I]
var/list/srcs = alarm[3]
if (origin in srcs)
srcs -= origin
if (srcs.len == 0)
cleared = 1
L -= I
if (cleared)
queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0)
if (viewalerts) ai_alerts()
return !cleared
/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
+22 -64
View File
@@ -69,6 +69,12 @@
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)
/mob/living/silicon/robot/model/syndicate/Initialize()
. = ..()
laws = new /datum/ai_laws/syndicate_override()
@@ -118,6 +124,7 @@
QDEL_NULL(inv2)
QDEL_NULL(inv3)
QDEL_NULL(spark_system)
QDEL_NULL(listener)
cell = null
return ..()
@@ -200,15 +207,16 @@
/mob/living/silicon/robot/proc/robot_alerts()
var/dat = ""
for (var/cat in alarms)
dat += text("<B>[cat]</B><BR>\n")
var/list/L = alarms[cat]
if (L.len)
for (var/alarm in L)
var/list/alm = L[alarm]
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 += text("-- [A.name]")
dat += "-- [A.name]"
dat += "</NOBR><BR>\n"
else
dat += "-- All Systems Nominal<BR>\n"
@@ -251,7 +259,7 @@
if(cell)
. += "Charge Left: [cell.charge]/[cell.maxcharge]"
else
. += text("No Cell Inserted!")
. += "No Cell Inserted!"
if(model)
for(var/datum/robot_energy_storage/st in model.storages)
@@ -259,63 +267,13 @@
if(connected_ai)
. += "Master AI: [connected_ai.name]"
/mob/living/silicon/robot/proc/alarm_triggered(datum/source, alarm_type, area/source_area)
SIGNAL_HANDLER
queueAlarm("--- [alarm_type] alarm detected in [source_area.name]!", alarm_type)
/mob/living/silicon/robot/triggerAlarm(class, area/home, cameras, obj/source)
if(source.z != z)
return
if(stat == DEAD)
return TRUE
var/list/our_sort = alarms[class]
for(var/areaname in our_sort)
if (areaname == home.name)
var/list/alarm = our_sort[areaname]
var/list/sources = alarm[3]
if (!(source in sources))
sources += source
return TRUE
var/obj/machinery/camera/cam = null
var/list/our_cams = null
if(cameras && islist(cameras))
our_cams = cameras
if (our_cams.len == 1)
cam = our_cams[1]
else if(cameras && istype(cameras, /obj/machinery/camera))
cam = cameras
our_sort[home.name] = list(home, (cam ? cam : cameras), list(source))
queueAlarm(text("--- [class] alarm detected in [home.name]!"), class)
return TRUE
/mob/living/silicon/robot/freeCamera(area/home, obj/machinery/camera/cam)
for(var/class in alarms)
var/our_area = alarms[class][home.name]
if(!our_area)
continue
var/cams = our_area[2] //Get the cameras
if(!cams)
continue
if(islist(cams))
cams -= cam
if(length(cams) == 1)
our_area[2] = cams[1]
else
our_area[2] = null
/mob/living/silicon/robot/cancelAlarm(class, area/A, obj/origin)
var/list/L = alarms[class]
var/cleared = FALSE
for (var/I in L)
if (I == A.name)
var/list/alarm = L[I]
var/list/srcs = alarm[3]
if (origin in srcs)
srcs -= origin
if (srcs.len == 0)
cleared = TRUE
L -= I
if (cleared)
queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0)
return !cleared
/mob/living/silicon/robot/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, FALSE)
/mob/living/silicon/robot/can_interact_with(atom/A)
if (A == modularInterface)
@@ -114,14 +114,6 @@
///Ionpulse effect.
var/datum/effect_system/trail_follow/ion/ion_trail
var/alarms = list(
"Motion" = list(),
"Fire" = list(),
"Atmosphere" = list(),
"Power" = list(),
"Camera" = list(),
"Burglar" = list())
// ------------------------------------------ Misc
var/toner = 0
var/tonermax = 40
@@ -139,6 +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
/***************************************************************************************
+7 -44
View File
@@ -25,8 +25,8 @@
var/obj/item/radio/borg/radio = null ///If this is a path, this gets created as an object in Initialize.
var/list/alarm_types_show = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0)
var/list/alarm_types_clear = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0)
var/list/alarm_types_show = list(ALARM_ATMOS = 0, ALARM_FIRE = 0, ALARM_POWER = 0, ALARM_CAMERA = 0, ALARM_MOTION = 0)
var/list/alarm_types_clear = list(ALARM_ATMOS = 0, ALARM_FIRE = 0, ALARM_POWER = 0, ALARM_CAMERA = 0, ALARM_MOTION = 0)
var/lawcheck[1]
var/ioncheck[1]
@@ -79,16 +79,7 @@
/mob/living/silicon/contents_explosion(severity, target)
return
/mob/living/silicon/proc/cancelAlarm()
return
/mob/living/silicon/proc/freeCamera()
return
/mob/living/silicon/proc/triggerAlarm()
return
/mob/living/silicon/proc/queueAlarm(message, type, incoming = 1)
/mob/living/silicon/proc/queueAlarm(message, type, incoming = FALSE)
var/in_cooldown = (alarms_to_show.len > 0 || alarms_to_clear.len > 0)
if(incoming)
alarms_to_show += message
@@ -109,24 +100,8 @@
else if(alarms_to_show.len)
var/msg = "--- "
if(alarm_types_show["Burglar"])
msg += "BURGLAR: [alarm_types_show["Burglar"]] alarms detected. - "
if(alarm_types_show["Motion"])
msg += "MOTION: [alarm_types_show["Motion"]] alarms detected. - "
if(alarm_types_show["Fire"])
msg += "FIRE: [alarm_types_show["Fire"]] alarms detected. - "
if(alarm_types_show["Atmosphere"])
msg += "ATMOSPHERE: [alarm_types_show["Atmosphere"]] alarms detected. - "
if(alarm_types_show["Power"])
msg += "POWER: [alarm_types_show["Power"]] alarms detected. - "
if(alarm_types_show["Camera"])
msg += "CAMERA: [alarm_types_show["Camera"]] alarms detected. - "
for(var/alarm_type in alarm_types_show)
msg += "[uppertext(alarm_type)]: [alarm_types_show[alarm_type]] alarms detected. - "
msg += "<A href=?src=[REF(src)];showalerts=1'>\[Show Alerts\]</a>"
to_chat(src, msg)
@@ -138,20 +113,8 @@
else if(alarms_to_clear.len)
var/msg = "--- "
if(alarm_types_clear["Motion"])
msg += "MOTION: [alarm_types_clear["Motion"]] alarms cleared. - "
if(alarm_types_clear["Fire"])
msg += "FIRE: [alarm_types_clear["Fire"]] alarms cleared. - "
if(alarm_types_clear["Atmosphere"])
msg += "ATMOSPHERE: [alarm_types_clear["Atmosphere"]] alarms cleared. - "
if(alarm_types_clear["Power"])
msg += "POWER: [alarm_types_clear["Power"]] alarms cleared. - "
if(alarm_types_show["Camera"])
msg += "CAMERA: [alarm_types_clear["Camera"]] alarms cleared. - "
for(var/alarm_type in alarm_types_clear)
msg += "[uppertext(alarm_type)]: [alarm_types_clear[alarm_type]] alarms cleared. - "
msg += "<A href=?src=[REF(src)];showalerts=1'>\[Show Alerts\]</a>"
to_chat(src, msg)
@@ -63,8 +63,8 @@
"3. Your goals are to actively build, maintain, repair, improve, and provide power to the best of your abilities within the facility that housed your activation." //for derelict drones so they don't go to station.
/// Amount of damage sustained if hit by a heavy EMP pulse
var/heavy_emp_damage = 25
/// List of active alarms. See [/mob/living/simple_animal/drone/proc/triggerAlarm] and [/mob/living/simple_animal/drone/proc/cancelAlarm]
var/alarms = list("Atmosphere" = list(), "Fire" = list(), "Power" = list())
///Alarm listener datum, handes caring about alarm events and such
var/datum/alarm_listener/listener
/// Internal storage slot. Fits any item
var/obj/item/internal_storage
/// Headwear slot
@@ -181,6 +181,12 @@
ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), 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)
/mob/living/simple_animal/drone/med_hud_set_health()
var/image/holder = hud_list[DIAG_HUD]
var/icon/I = icon(icon, icon_state, dir)
@@ -200,7 +206,8 @@
/mob/living/simple_animal/drone/Destroy()
GLOB.drones_list -= src
qdel(access_card) //Otherwise it ends up on the floor!
QDEL_NULL(access_card) //Otherwise it ends up on the floor!
QDEL_NULL(listener)
return ..()
/mob/living/simple_animal/drone/Login()
@@ -289,60 +296,13 @@
adjustBruteLoss(heavy_emp_damage)
to_chat(src, span_userdanger("HeAV% DA%^MMA+G TO I/O CIR!%UUT!"))
/mob/living/simple_animal/drone/proc/alarm_triggered(datum/source, alarm_type, area/source_area)
SIGNAL_HANDLER
to_chat(src, "--- [alarm_type] alarm detected in [source_area.name]!")
/**
* Alerts drones about different priorities of alarms
*
* Arguments:
* * class - One of the keys listed in [/mob/living/simple_animal/drone/var/alarms]
* * A - [/area] the alarm occurs
* * O - unused argument, see [/mob/living/silicon/robot/triggerAlarm]
* * alarmsource - [/atom] source of the alarm
*/
/mob/living/simple_animal/drone/proc/triggerAlarm(class, area/home, cameras, obj/source)
if(source.z != z)
return
if(stat == DEAD)
return
var/list/our_sort = alarms[class]
for(var/areaname in our_sort)
if (areaname == home.name)
var/list/alarm = our_sort[areaname]
var/list/sources = alarm[2]
if (!(source in sources))
sources += source
return TRUE
our_sort[home.name] = list(home, list(source))
to_chat(src, "--- [class] alarm detected in [home.name]!")
///This isn't currently needed since drones do jack shit with cameras. I hate this code so much
/mob/living/simple_animal/drone/proc/freeCamera(area/home, obj/machinery/camera/cam)
return
/**
* Clears alarm and alerts drones
*
* Arguments:
* * class - One of the keys listed in [/mob/living/simple_animal/drone/var/alarms]
* * A - [/area] the alarm occurs
* * alarmsource - [/atom] source of the alarm
*/
/mob/living/simple_animal/drone/proc/cancelAlarm(class, area/A, obj/origin)
if(stat != DEAD)
var/list/L = alarms[class]
var/cleared = 0
for (var/I in L)
if (I == A.name)
var/list/alarm = L[I]
var/list/srcs = alarm[2]
if (origin in srcs)
srcs -= origin
if (srcs.len == 0)
cleared = 1
L -= I
if(cleared)
to_chat(src, "--- [class] alarm in [A.name] has been cleared.")
/mob/living/simple_animal/drone/proc/alarm_cleared(datum/source, alarm_type, area/source_area)
SIGNAL_HANDLER
to_chat(src, "--- [alarm_type] alarm in [source_area.name] has been cleared.")
/mob/living/simple_animal/drone/proc/blacklist_on_try_use_machine(datum/source, obj/machinery/machine)
SIGNAL_HANDLER
@@ -10,7 +10,20 @@
tgui_id = "NtosStationAlertConsole"
program_icon = "bell"
var/has_alert = 0
var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list())
///Listens for alarms, manages our listing of alarms
var/datum/alarm_listener/listener
/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)
return ..()
/datum/computer_file/program/alarm_monitor/Destroy()
QDEL_NULL(listener)
return ..()
/datum/computer_file/program/alarm_monitor/process_tick()
..()
@@ -30,87 +43,19 @@
var/list/data = get_header_data()
data["alarms"] = list()
for(var/class in alarms)
data["alarms"][class] = list()
for(var/area in alarms[class])
data["alarms"][class] += area
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
return data
/datum/computer_file/program/alarm_monitor/proc/triggerAlarm(class, area/home, cameras, obj/source)
if(is_station_level(source.z))
if(!(home.type in GLOB.the_station_areas))
return
else if(!is_mining_level(source.z) || istype(home, /area/ruin))
return
var/list/our_sort = alarms[class]
for(var/areaname in our_sort)
if (areaname == home.name)
var/list/alarm = our_sort[areaname]
var/list/sources = alarm[3]
if (!(source in sources))
sources += source
return TRUE
var/obj/machinery/camera/cam = null
var/list/our_cams = null
if(cameras && islist(cameras))
our_cams = cameras
if (our_cams.len == 1)
cam = our_cams[1]
else if(cameras && istype(cameras, /obj/machinery/camera))
cam = cameras
our_sort[home.name] = list(home, (cam ? cam : cameras), list(source))
update_alarm_display()
return TRUE
/datum/computer_file/program/alarm_monitor/proc/freeCamera(area/home, obj/machinery/camera/cam)
for(var/class in alarms)
var/our_area = alarms[class][home.name]
if(!our_area)
continue
var/cams = our_area[2] //Get the cameras
if(!cams)
continue
if(islist(cams))
cams -= cam
if(length(cams) == 1)
our_area[2] = cams[1]
else
our_area[2] = null
/datum/computer_file/program/alarm_monitor/proc/cancelAlarm(class, area/A, obj/origin)
var/list/L = alarms[class]
var/cleared = 0
var/arealevelalarm = FALSE // set to TRUE for alarms that set/clear whole areas
if (class=="Fire")
arealevelalarm = TRUE
for (var/I in L)
if (I == A.name)
if (!arealevelalarm) // the traditional behaviour
var/list/alarm = L[I]
var/list/srcs = alarm[3]
if (origin in srcs)
srcs -= origin
if (srcs.len == 0)
cleared = 1
L -= I
else
L -= I // wipe the instances entirely
cleared = 1
update_alarm_display()
return !cleared
/datum/computer_file/program/alarm_monitor/proc/update_alarm_display()
SIGNAL_HANDLER
has_alert = FALSE
for(var/cat in alarms)
var/list/L = alarms[cat]
if(L.len)
has_alert = TRUE
if(length(listener.alarms))
has_alert = TRUE
/datum/computer_file/program/alarm_monitor/run_program(mob/user)
. = ..(user)
@@ -118,4 +63,4 @@
/datum/computer_file/program/alarm_monitor/kill_program(forced = FALSE)
GLOB.alarmdisplay -= src
..()
return ..()
+10 -7
View File
@@ -160,6 +160,8 @@
var/update_overlay = -1
var/icon_update_needed = FALSE
var/obj/machinery/computer/apc_control/remote_control = null
///Represents a signel source of power alarms for this apc
var/datum/alarm_handler/alarm_manager
/obj/machinery/power/apc/unlocked
locked = FALSE
@@ -258,7 +260,7 @@
area.power_equip = FALSE
area.power_environ = FALSE
area.power_change()
area.poweralert(FALSE, src)
QDEL_NULL(alarm_manager)
if(occupier)
malfvacate(1)
qdel(wires)
@@ -285,6 +287,7 @@
/obj/machinery/power/apc/Initialize(mapload)
. = ..()
AddElement(/datum/element/atmos_sensitive, mapload)
alarm_manager = new(src)
if(!mapload)
return
@@ -1309,24 +1312,24 @@
equipment = autoset(equipment, AUTOSET_FORCE_OFF)
lighting = autoset(lighting, AUTOSET_FORCE_OFF)
environ = autoset(environ, AUTOSET_FORCE_OFF)
area.poweralert(TRUE, src)
alarm_manager.send_alarm(ALARM_POWER)
else if(cell.percent() < 15 && longtermpower < 0) // <15%, turn off lighting & equipment
equipment = autoset(equipment, AUTOSET_OFF)
lighting = autoset(lighting, AUTOSET_OFF)
environ = autoset(environ, AUTOSET_ON)
area.poweralert(TRUE, src)
alarm_manager.send_alarm(ALARM_POWER)
else if(cell.percent() < 30 && longtermpower < 0) // <30%, turn off equipment
equipment = autoset(equipment, AUTOSET_OFF)
lighting = autoset(lighting, AUTOSET_ON)
environ = autoset(environ, AUTOSET_ON)
area.poweralert(TRUE, src)
alarm_manager.send_alarm(ALARM_POWER)
else // otherwise all can be on
equipment = autoset(equipment, AUTOSET_ON)
lighting = autoset(lighting, AUTOSET_ON)
environ = autoset(environ, AUTOSET_ON)
area.poweralert(FALSE, src)
if(cell.percent() > 75)
area.poweralert(FALSE, src)
alarm_manager.clear_alarm(ALARM_POWER)
// now trickle-charge the cell
if(chargemode && charging == APC_CHARGING && operating)
@@ -1368,7 +1371,7 @@
equipment = autoset(equipment, AUTOSET_FORCE_OFF)
lighting = autoset(lighting, AUTOSET_FORCE_OFF)
environ = autoset(environ, AUTOSET_FORCE_OFF)
area.poweralert(TRUE, src)
alarm_manager.send_alarm(ALARM_POWER)
// update icon & area power if anything changed
+2
View File
@@ -29,6 +29,7 @@
#include "code\__DEFINES\admin.dm"
#include "code\__DEFINES\adventure.dm"
#include "code\__DEFINES\ai.dm"
#include "code\__DEFINES\alarm.dm"
#include "code\__DEFINES\antagonists.dm"
#include "code\__DEFINES\aquarium.dm"
#include "code\__DEFINES\art.dm"
@@ -397,6 +398,7 @@
#include "code\controllers\subsystem\processing\wet_floors.dm"
#include "code\datums\action.dm"
#include "code\datums\ai_laws.dm"
#include "code\datums\alarm.dm"
#include "code\datums\armor.dm"
#include "code\datums\beam.dm"
#include "code\datums\browser.dm"