[MIRROR] Redoes how alarms are handled, moves their behavior to datums (#7547)

* Redoes how alarms are handled, moves their behavior to datums

* a

Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: Gandalf <jzo123@hotmail.com>
This commit is contained in:
SkyratBot
2021-08-16 00:38:01 +02:00
committed by GitHub
parent 61317efd99
commit d4e11d2080
19 changed files with 430 additions and 588 deletions

13
code/__DEFINES/alarm.dm Normal file
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"

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
@@ -1378,6 +1382,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
code/datums/alarm.dm Normal file
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

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()

View File

@@ -17,13 +17,13 @@
///Do we have an active fire alarm?
var/fire = FALSE
/* SKYRAT EDIT REMOVAL
///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.
@@ -135,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 ..()
/*
@@ -233,87 +234,8 @@ 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
/* SKYRAT EDIT REMOVAL
/**
* Try to close all the firedoors in the area
@@ -352,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)
/**
@@ -389,37 +299,13 @@ GLOBAL_LIST_EMPTY(teleportlocs)
if (should_reset_alarms) // if there's a source, make sure there's no fire alarms left
unset_fire_alarm_effects()
//ModifyFiredoors(TRUE) SKYRAT EDIT CHANGE
ModifyFiredoors(TRUE)
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)
*/ //SKYRAT EDIT END
///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)
/* SKYRAT EDIT REMOVAL
/**
* If 100 ticks has elapsed, toggle all the firedoors closed again
*/
@@ -428,7 +314,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
firereset() //If there are no breaches or fires, and this alert was caused by a breach or fire, die
if(firedoors_last_closed_on + 100 < world.time) //every 10 seconds
ModifyFiredoors(FALSE)
*/ //SKYRAT EDIT END
/**
* Close and lock a door passed into this proc
*
@@ -451,17 +337,11 @@ GLOBAL_LIST_EMPTY(teleportlocs)
if (area_flags & NO_ALERTS)
return
//Trigger alarm effect
//set_fire_alarm_effect() SKYRAT EDIT REMOVAL
set_fire_alarm_effect()
//Lockdown airlocks
for(var/obj/machinery/door/DOOR in src)
close_and_lock_door(DOOR)
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)
/* SKYRAT EDIT REMOVAL
/**
* Trigger the fire alarm visual affects in an area
*
@@ -494,7 +374,6 @@ GLOBAL_LIST_EMPTY(teleportlocs)
for(var/obj/machinery/light/L in src)
L.update()
*/
/**
* Update the icon state of the area
*

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)

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

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)
@@ -31,78 +32,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"

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)
@@ -96,8 +100,12 @@
/obj/structure/displaycase/proc/trigger_alarm()
if(!alert)
return
var/area/alarmed = get_area(src)
alarmed.burglaralert(src)
//var/area/alarmed = get_area(src) SKYRAT EDIT REMOVAL
//alarmed.burglaralert(src) SKYRAT EDIT REMOVAL
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))

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()

View File

@@ -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)

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
@@ -178,6 +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)
/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
@@ -211,6 +215,7 @@
QDEL_NULL(doomsday_device)
QDEL_NULL(robot_control)
QDEL_NULL(aiMulti)
QDEL_NULL(listener)
malfhack = null
current = null
Bot = null
@@ -270,12 +275,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"
@@ -286,19 +291,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]
@@ -306,15 +312,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"
@@ -416,7 +422,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"])
@@ -541,77 +547,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

View File

@@ -76,6 +76,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)
/mob/living/silicon/robot/model/syndicate/Initialize()
. = ..()
@@ -126,6 +131,7 @@
QDEL_NULL(inv2)
QDEL_NULL(inv3)
QDEL_NULL(spark_system)
QDEL_NULL(listener)
cell = null
return ..()
@@ -208,15 +214,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"
@@ -259,7 +266,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)
@@ -267,63 +274,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)

View File

@@ -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
/***************************************************************************************

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)

View File

@@ -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

View File

@@ -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 ..()

View File

@@ -169,6 +169,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
var/shock_proof = FALSE // SKYRAT EDIT ADD - APC Arcing. If TRUE, APCs will not arc.
@@ -269,7 +271,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)
@@ -296,6 +298,7 @@
/obj/machinery/power/apc/Initialize(mapload)
. = ..()
AddElement(/datum/element/atmos_sensitive, mapload)
alarm_manager = new(src)
if(!mapload)
return
@@ -1335,24 +1338,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)
@@ -1411,7 +1414,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

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"
@@ -433,6 +434,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"