diff --git a/baystation12.dme b/baystation12.dme
index 45767dacd6..e41c414a46 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -93,8 +93,10 @@
#include "code\controllers\lighting_controller.dm"
#include "code\controllers\master_controller.dm"
#include "code\controllers\shuttle_controller.dm"
+#include "code\controllers\subsystems.dm"
#include "code\controllers\verbs.dm"
#include "code\controllers\voting.dm"
+#include "code\controllers\subsystem\alarms.dm"
#include "code\datums\ai_laws.dm"
#include "code\datums\browser.dm"
#include "code\datums\computerfiles.dm"
@@ -811,6 +813,13 @@
#include "code\modules\admin\verbs\ticklag.dm"
#include "code\modules\admin\verbs\tripAI.dm"
#include "code\modules\admin\verbs\vox_raiders.dm"
+#include "code\modules\alarm\alarm.dm"
+#include "code\modules\alarm\alarm_handler.dm"
+#include "code\modules\alarm\atmosphere_alarm.dm"
+#include "code\modules\alarm\camera_alarm.dm"
+#include "code\modules\alarm\fire_alarm.dm"
+#include "code\modules\alarm\motion_alarm.dm"
+#include "code\modules\alarm\power_alarm.dm"
#include "code\modules\assembly\assembly.dm"
#include "code\modules\assembly\bomb.dm"
#include "code\modules\assembly\helpers.dm"
@@ -1130,7 +1139,6 @@
#include "code\modules\mob\living\carbon\monkey\login.dm"
#include "code\modules\mob\living\carbon\monkey\monkey.dm"
#include "code\modules\mob\living\carbon\monkey\update_icons.dm"
-#include "code\modules\mob\living\silicon\alarm.dm"
#include "code\modules\mob\living\silicon\death.dm"
#include "code\modules\mob\living\silicon\laws.dm"
#include "code\modules\mob\living\silicon\login.dm"
@@ -1144,8 +1152,8 @@
#include "code\modules\mob\living\silicon\ai\life.dm"
#include "code\modules\mob\living\silicon\ai\login.dm"
#include "code\modules\mob\living\silicon\ai\logout.dm"
-#include "code\modules\mob\living\silicon\ai\nano.dm"
#include "code\modules\mob\living\silicon\ai\say.dm"
+#include "code\modules\mob\living\silicon\ai\subsystems.dm"
#include "code\modules\mob\living\silicon\ai\freelook\cameranet.dm"
#include "code\modules\mob\living\silicon\ai\freelook\chunk.dm"
#include "code\modules\mob\living\silicon\ai\freelook\eye.dm"
@@ -1178,6 +1186,7 @@
#include "code\modules\mob\living\silicon\robot\robot_items.dm"
#include "code\modules\mob\living\silicon\robot\robot_modules.dm"
#include "code\modules\mob\living\silicon\robot\robot_movement.dm"
+#include "code\modules\mob\living\silicon\robot\subsystems.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_abilities.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_console.dm"
@@ -1236,6 +1245,7 @@
#include "code\modules\nano\nanomanager.dm"
#include "code\modules\nano\nanomapgen.dm"
#include "code\modules\nano\nanoui.dm"
+#include "code\modules\nano\modules\alarm_monitor.dm"
#include "code\modules\nano\modules\crew_monitor.dm"
#include "code\modules\nano\modules\power_monitor.dm"
#include "code\modules\nano\modules\rcon.dm"
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index 0dae7ecc0c..45618f2ffc 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -491,5 +491,8 @@ datum/projectile_data
temps[direction] = rstats
return temps
-/proc/MinutesToTicks(var/minutes as num)
- return minutes * 60 * 10
+/proc/MinutesToTicks(var/minutes)
+ return SecondsToTicks(60 * minutes)
+
+/proc/SecondsToTicks(var/seconds)
+ return seconds * 10
diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm
index 1e8ccb6a4d..9fd6a8c0e9 100644
--- a/code/__HELPERS/lists.dm
+++ b/code/__HELPERS/lists.dm
@@ -596,3 +596,7 @@ datum/proc/dd_SortValue()
/obj/machinery/camera/dd_SortValue()
return "[c_tag]"
+
+/datum/alarm/dd_SortValue()
+ return "[sanitize(last_name)]"
+
diff --git a/code/controllers/master_controller.dm b/code/controllers/master_controller.dm
index 97e4c8074b..bb5a3e48b0 100644
--- a/code/controllers/master_controller.dm
+++ b/code/controllers/master_controller.dm
@@ -26,6 +26,7 @@ datum/controller/game_controller
var/powernets_cost = 0
var/nano_cost = 0
var/events_cost = 0
+ var/alarms_cost = 0
var/ticker_cost = 0
var/total_cost = 0
@@ -231,6 +232,11 @@ datum/controller/game_controller/proc/process()
process_events()
events_cost = (world.timeofday - timer) / 10
+ //ALARMS
+ timer = world.timeofday
+ process_alarms()
+ alarms_cost = (world.timeofday - timer) / 10
+
//TICKER
timer = world.timeofday
last_thing_processed = ticker.type
@@ -238,7 +244,7 @@ datum/controller/game_controller/proc/process()
ticker_cost = (world.timeofday - timer) / 10
//TIMING
- total_cost = air_cost + sun_cost + mobs_cost + diseases_cost + machines_cost + objects_cost + networks_cost + powernets_cost + nano_cost + events_cost + ticker_cost
+ total_cost = air_cost + sun_cost + mobs_cost + diseases_cost + machines_cost + objects_cost + networks_cost + powernets_cost + nano_cost + events_cost + alarms_cost + ticker_cost
var/end_time = world.timeofday
if(end_time < start_time) //why not just use world.time instead?
@@ -334,9 +340,13 @@ datum/controller/game_controller/proc/process_nano()
nanomanager.processing_uis.Cut(i,i+1)
datum/controller/game_controller/proc/process_events()
- last_thing_processed = /datum/event
+ last_thing_processed = /datum/event_manager
event_manager.process()
+datum/controller/game_controller/proc/process_alarms()
+ last_thing_processed = /datum/subsystem/alarm
+ alarm_manager.fire()
+
datum/controller/game_controller/proc/Recover() //Mostly a placeholder for now.
var/msg = "## DEBUG: [time2text(world.timeofday)] MC restarted. Reports:\n"
for(var/varname in master_controller.vars)
diff --git a/code/controllers/subsystem/alarms.dm b/code/controllers/subsystem/alarms.dm
new file mode 100644
index 0000000000..a13e60782d
--- /dev/null
+++ b/code/controllers/subsystem/alarms.dm
@@ -0,0 +1,33 @@
+// We manually initialize the alarm handlers instead of looping over all existing types
+// to make it possible to write: camera.triggerAlarm() rather than alarm_manager.managers[datum/alarm_handler/camera].triggerAlarm() or a variant thereof.
+/var/global/datum/alarm_handler/atmosphere/atmosphere_alarm = new()
+/var/global/datum/alarm_handler/camera/camera_alarm = new()
+/var/global/datum/alarm_handler/fire/fire_alarm = new()
+/var/global/datum/alarm_handler/motion/motion_alarm = new()
+/var/global/datum/alarm_handler/power/power_alarm = new()
+
+/datum/subsystem/alarm
+ name = "Alarm"
+ var/list/datum/alarm/all_handlers
+
+/datum/subsystem/alarm/New()
+ all_handlers = list(atmosphere_alarm, camera_alarm, fire_alarm, motion_alarm, power_alarm)
+
+/datum/subsystem/alarm/stat_entry()
+ stat(null,"Alarm-[master_controller.alarms_cost]\t#[number_of_active_alarms()]")
+
+/datum/subsystem/alarm/fire()
+ for(var/datum/alarm_handler/AH in all_handlers)
+ AH.process()
+
+/datum/subsystem/alarm/proc/active_alarms()
+ var/list/all_alarms = new
+ for(var/datum/alarm_handler/AH in all_handlers)
+ var/list/alarms = AH.alarms
+ all_alarms += alarms
+
+ return all_alarms
+
+/datum/subsystem/alarm/proc/number_of_active_alarms()
+ var/list/alarms = active_alarms()
+ return alarms.len
diff --git a/code/controllers/subsystems.dm b/code/controllers/subsystems.dm
new file mode 100644
index 0000000000..11025d8d53
--- /dev/null
+++ b/code/controllers/subsystems.dm
@@ -0,0 +1,46 @@
+#define NEW_SS_GLOBAL(varname) if(varname != src){if(istype(varname)){Recover();qdel(varname);}varname = src;}
+
+/datum/subsystem
+ //things you will want to define
+ var/name //name of the subsystem
+ var/priority = 0 //priority affects order of initialization. Higher priorities are initialized first, lower priorities later. Can be decimal and negative values.
+ var/wait = 20 //time to wait (in deciseconds) between each call to fire(). Must be a positive integer.
+
+ //things you will probably want to leave alone
+ var/can_fire = 0 //prevent fire() calls
+ var/last_fire = 0 //last world.time we called fire()
+ var/next_fire = 0 //scheduled world.time for next fire()
+ var/cpu = 0 //cpu-usage stats (somewhat vague)
+ var/cost = 0 //average time to execute
+ var/times_fired = 0 //number of times we have called fire()
+
+//used to initialize the subsystem BEFORE the map has loaded
+/datum/subsystem/New()
+
+//previously, this would have been named 'process()' but that name is used everywhere for different things!
+//fire() seems more suitable. This is the procedure that gets called every 'wait' deciseconds.
+//fire(), and the procs it calls, SHOULD NOT HAVE ANY SLEEP OPERATIONS in them!
+//YE BE WARNED!
+/datum/subsystem/proc/fire()
+ can_fire = 0
+
+//used to initialize the subsystem AFTER the map has loaded
+/datum/subsystem/proc/Initialize(start_timeofday)
+ var/time = (world.timeofday - start_timeofday) / 10
+ var/msg = "Initialized [name] SubSystem within [time] seconds"
+ world << "[msg]"
+ world.log << msg
+
+//hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc.
+/datum/subsystem/proc/stat_entry()
+ stat(name, "[round(cost,0.001)]ds\t(CPU:[round(cpu,1)]%)")
+
+//could be used to postpone a costly subsystem for one cycle
+//for instance, during cpu intensive operations like explosions
+/datum/subsystem/proc/postpone()
+ if(next_fire - world.time < wait)
+ next_fire += wait
+
+//usually called via datum/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash)
+//should attempt to salvage what it can from the old instance of subsystem
+/datum/subsystem/proc/Recover()
diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm
index 3dcba37bc0..325e2e6472 100644
--- a/code/controllers/verbs.dm
+++ b/code/controllers/verbs.dm
@@ -56,7 +56,7 @@
message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.")
return
-/client/proc/debug_controller(controller in list("Master","Failsafe","Ticker","Lighting","Air","Jobs","Sun","Radio","Supply","Shuttles","Emergency Shuttle","Configuration","pAI", "Cameras", "Transfer Controller", "Gas Data","Event"))
+/client/proc/debug_controller(controller in list("Master","Failsafe","Ticker","Lighting","Air","Jobs","Sun","Radio","Supply","Shuttles","Emergency Shuttle","Configuration","pAI", "Cameras", "Transfer Controller", "Gas Data","Event","Alarm"))
set category = "Debug"
set name = "Debug Controller"
set desc = "Debug the various periodic loop controllers for the game (be careful!)"
@@ -114,5 +114,8 @@
if("Event")
debug_variables(event_manager)
feedback_add_details("admin_verb", "DEvent")
+ if("Alarm")
+ debug_variables(alarm_manager)
+ feedback_add_details("admin_verb", "DAlarm")
message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.")
return
diff --git a/code/datums/wires/alarm.dm b/code/datums/wires/alarm.dm
index 6ebc937265..c34b0291fb 100644
--- a/code/datums/wires/alarm.dm
+++ b/code/datums/wires/alarm.dm
@@ -46,7 +46,7 @@ var/const/AALARM_WIRE_AALARM = 16
//world << "Syphon Wire Cut"
if(AALARM_WIRE_AALARM)
- if (A.alarm_area.atmosalert(2))
+ if (A.alarm_area.atmosalert(2, A))
A.post_alert(2)
A.update_icon()
@@ -88,6 +88,6 @@ var/const/AALARM_WIRE_AALARM = 16
if(AALARM_WIRE_AALARM)
// world << "Aalarm wire pulsed"
- if (A.alarm_area.atmosalert(0))
+ if (A.alarm_area.atmosalert(0, A))
A.post_alert(0)
A.update_icon()
diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm
index 0f19430b31..55cfeaccfb 100755
--- a/code/game/area/Space Station 13 areas.dm
+++ b/code/game/area/Space Station 13 areas.dm
@@ -110,7 +110,13 @@ var/list/ghostteleportlocs = list()
power_environ = 0
ambience = list('sound/ambience/ambispace.ogg','sound/music/title2.ogg','sound/music/space.ogg','sound/music/main.ogg','sound/music/traitor.ogg')
-/area/space/firealert()
+area/space/atmosalert()
+ return
+
+/area/space/fire_alert()
+ return
+
+/area/space/fire_reset()
return
/area/space/readyalert()
@@ -633,7 +639,7 @@ var/list/ghostteleportlocs = list()
/area/prison/cell_block/C
name = "Prison Cell Block C"
icon_state = "brig"
-
+
////////////////////
//SPACE STATION 13//
////////////////////
@@ -1070,7 +1076,7 @@ var/list/ghostteleportlocs = list()
name = "\improper Engineering"
icon_state = "engineering"
ambience = list('sound/ambience/ambisin1.ogg','sound/ambience/ambisin2.ogg','sound/ambience/ambisin3.ogg','sound/ambience/ambisin4.ogg')
-
+
/area/engineering/atmos
name = "\improper Atmospherics"
icon_state = "atmos"
@@ -1082,7 +1088,7 @@ var/list/ghostteleportlocs = list()
/area/engineering/atmos/storage
name = "\improper Atmospherics Storage"
icon_state = "atmos_storage"
-
+
/area/engineering/drone_fabrication
name = "\improper Drone Fabrication"
icon_state = "drone_fab"
@@ -1130,7 +1136,7 @@ var/list/ghostteleportlocs = list()
/area/engineering/locker_room
name = "\improper Engineering Locker Room"
icon_state = "engineering_locker"
-
+
/area/engineering/workshop
name = "\improper Engineering Workshop"
icon_state = "engineering_workshop"
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index 609d1a2a6b..ae11b5aa48 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -30,37 +30,14 @@
power_change() // all machines set to current power level, also updates lighting icon
InitializeLighting()
+/area/proc/get_cameras()
+ var/list/cameras = list()
+ for (var/area/RA in related)
+ for (var/obj/machinery/camera/C in RA)
+ cameras += C
+ return cameras
-/area/proc/poweralert(var/state, var/obj/source as obj)
- if (state != poweralm)
- poweralm = state
- if(istype(source)) //Only report power alarms on the z-level where the source is located.
- var/list/cameras = list()
- for (var/area/RA in related)
- for (var/obj/machinery/camera/C in RA)
- cameras += C
- if(state == 1)
- C.network.Remove("Power Alarms")
- else
- C.network.Add("Power Alarms")
- for (var/mob/living/silicon/aiPlayer in player_list)
- if(aiPlayer.z == source.z)
- if (state == 1)
- aiPlayer.cancelAlarm("Power", src, source)
- else
- aiPlayer.triggerAlarm("Power", src, cameras, source)
- for(var/obj/machinery/computer/station_alert/a in machines)
- if(a.z == source.z)
- if(state == 1)
- a.cancelAlarm("Power", src, source)
- else
- a.triggerAlarm("Power", src, cameras, source)
- return
-
-/area/proc/atmosalert(danger_level, var/set_firelocks=1)
-// if(type==/area) //No atmos alarms in space
-// return 0 //redudant
-
+/area/proc/atmosalert(danger_level, var/alarm_source)
//Check all the alarms before lowering atmosalm. Raising is perfectly fine.
for (var/area/RA in related)
for (var/obj/machinery/alarm/AA in RA)
@@ -68,32 +45,16 @@
danger_level = max(danger_level, AA.danger_level)
if(danger_level != atmosalm)
- if (set_firelocks && danger_level < 1 && atmosalm >= 1)
+ if (danger_level < 1 && atmosalm >= 1)
//closing the doors on red and opening on green provides a bit of hysteresis that will hopefully prevent fire doors from opening and closing repeatedly due to noise
air_doors_open()
+ else if (danger_level >= 2 && atmosalm < 2)
+ air_doors_close()
- if (danger_level < 2 && atmosalm >= 2)
- for(var/area/RA in related)
- for(var/obj/machinery/camera/C in RA)
- C.network.Remove("Atmosphere Alarms")
- for(var/mob/living/silicon/aiPlayer in player_list)
- aiPlayer.cancelAlarm("Atmosphere", src, src)
- for(var/obj/machinery/computer/station_alert/a in machines)
- a.cancelAlarm("Atmosphere", src, src)
-
- if (danger_level >= 2 && atmosalm < 2)
- var/list/cameras = list()
- for(var/area/RA in related)
- //updateicon()
- for(var/obj/machinery/camera/C in RA)
- cameras += C
- C.network.Add("Atmosphere Alarms")
- for(var/mob/living/silicon/aiPlayer in player_list)
- aiPlayer.triggerAlarm("Atmosphere", src, cameras, src)
- for(var/obj/machinery/computer/station_alert/a in machines)
- a.triggerAlarm("Atmosphere", src, cameras, src)
- if (set_firelocks)
- air_doors_close()
+ if (danger_level == 0)
+ atmosphere_alarm.clearAlarm(master, alarm_source)
+ else
+ atmosphere_alarm.triggerAlarm(master, alarm_source, severity = danger_level)
atmosalm = danger_level
for(var/area/RA in related)
@@ -107,9 +68,9 @@
if(!src.master.air_doors_activated)
src.master.air_doors_activated = 1
for(var/obj/machinery/door/firedoor/E in src.master.all_doors)
- if(!E:blocked)
+ if(!E.blocked)
if(E.operating)
- E:nextstate = CLOSED
+ E.nextstate = CLOSED
else if(!E.density)
spawn(0)
E.close()
@@ -118,21 +79,21 @@
if(src.master.air_doors_activated)
src.master.air_doors_activated = 0
for(var/obj/machinery/door/firedoor/E in src.master.all_doors)
- if(!E:blocked)
+ if(!E.blocked)
if(E.operating)
- E:nextstate = OPEN
+ E.nextstate = OPEN
else if(E.density)
spawn(0)
E.open()
-/area/proc/firealert()
- if(name == "Space") //no fire alarms in space
- return
- if( !fire )
- fire = 1
- master.fire = 1 //used for firedoor checks
- updateicon()
+/area/proc/fire_alert()
+ if(!fire)
+ master.fire = 1 //used for firedoor checks
+ master.updateicon()
+ for(var/area/A in related)
+ A.fire = 1
+ A.updateicon()
mouse_opacity = 0
for(var/obj/machinery/door/firedoor/D in all_doors)
if(!D.blocked)
@@ -141,22 +102,15 @@
else if(!D.density)
spawn()
D.close()
- var/list/cameras = list()
- for(var/area/RA in related)
- for (var/obj/machinery/camera/C in RA)
- cameras.Add(C)
- C.network.Add("Fire Alarms")
- for (var/mob/living/silicon/ai/aiPlayer in player_list)
- aiPlayer.triggerAlarm("Fire", src, cameras, src)
- for (var/obj/machinery/computer/station_alert/a in machines)
- a.triggerAlarm("Fire", src, cameras, src)
-/area/proc/firereset()
+/area/proc/fire_reset()
if (fire)
- fire = 0
- master.fire = 0 //used for firedoor checks
+ master.fire = 0 //used for firedoor checks
+ master.updateicon()
+ for(var/area/A in related)
+ A.fire = 0
+ A.updateicon()
mouse_opacity = 0
- updateicon()
for(var/obj/machinery/door/firedoor/D in all_doors)
if(!D.blocked)
if(D.operating)
@@ -164,13 +118,6 @@
else if(D.density)
spawn(0)
D.open()
- for(var/area/RA in related)
- for (var/obj/machinery/camera/C in RA)
- C.network.Remove("Fire Alarms")
- for (var/mob/living/silicon/ai/aiPlayer in player_list)
- aiPlayer.cancelAlarm("Fire", src, src)
- for (var/obj/machinery/computer/station_alert/a in machines)
- a.cancelAlarm("Fire", src, src)
/area/proc/readyalert()
if(!eject)
diff --git a/code/game/machinery/alarm.dm b/code/game/machinery/alarm.dm
index a2e78a10f9..0f2af7485f 100644
--- a/code/game/machinery/alarm.dm
+++ b/code/game/machinery/alarm.dm
@@ -117,19 +117,6 @@
first_run()
-/obj/machinery/alarm/Del()
- //If there's an active alarm, clear it after minute so that alarms don't keep going forver
- delayed_reset()
- ..()
-
-//needed to cancel the alarm after it is deleted
-/obj/machinery/alarm/proc/delayed_reset()
- var/area/A = alarm_area
- src = null
- spawn(600)
- //It makes sense not to touch firelocks here. The alarm itself is gone, we have no idea what the atmos is like.
- A.atmosalert(0, set_firelocks=0)
-
/obj/machinery/alarm/proc/first_run()
alarm_area = get_area(src)
if (alarm_area.master)
@@ -441,7 +428,7 @@
send_signal(device_id, list("power"= 0) )
/obj/machinery/alarm/proc/apply_danger_level(var/new_danger_level)
- if (report_danger_level && alarm_area.atmosalert(new_danger_level))
+ if (report_danger_level && alarm_area.atmosalert(new_danger_level, src))
post_alert(new_danger_level)
update_icon()
@@ -769,13 +756,13 @@
return 1
if(href_list["atmos_alarm"])
- if (alarm_area.atmosalert(2))
+ if (alarm_area.atmosalert(2, src))
apply_danger_level(2)
update_icon()
return 1
if(href_list["atmos_reset"])
- if (alarm_area.atmosalert(0))
+ if (alarm_area.atmosalert(0, src))
apply_danger_level(0)
update_icon()
return 1
@@ -947,7 +934,6 @@ FIRE ALARM
var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone
/obj/machinery/firealarm/update_icon()
-
if(wiresexposed)
switch(buildstage)
if(2)
@@ -981,7 +967,8 @@ FIRE ALARM
return src.alarm()
/obj/machinery/firealarm/emp_act(severity)
- if(prob(50/severity)) alarm()
+ if(prob(50/severity))
+ alarm(rand(30/severity, 60/severity))
..()
/obj/machinery/firealarm/attackby(obj/item/W as obj, mob/user as mob)
@@ -1080,6 +1067,7 @@ FIRE ALARM
var/d2
if (istype(user, /mob/living/carbon/human) || istype(user, /mob/living/silicon))
A = A.loc
+ A = A.master
if (A.fire)
d1 = text("Reset - Lockdown", src)
@@ -1145,26 +1133,26 @@ FIRE ALARM
/obj/machinery/firealarm/proc/reset()
if (!( src.working ))
return
- var/area/A = src.loc
- A = A.loc
- if (!( istype(A, /area) ))
- return
- A.firereset()
+ var/area/area = get_area(src)
+ for(var/area/A in area.related)
+ for(var/obj/machinery/firealarm/FA in A)
+ fire_alarm.clearAlarm(loc, FA)
update_icon()
return
-/obj/machinery/firealarm/proc/alarm()
- if (!( src.working ))
+/obj/machinery/firealarm/proc/alarm(var/duration = 0)
+ if (!( src.working))
return
- var/area/A = src.loc
- A = A.loc
- if (!( istype(A, /area) ))
- return
- A.firealert()
+ var/area/area = get_area(src)
+ for(var/area/A in area.related)
+ for(var/obj/machinery/firealarm/FA in A)
+ fire_alarm.triggerAlarm(loc, FA, duration)
update_icon()
//playsound(src.loc, 'sound/ambience/signal.ogg', 75, 0)
return
+
+
/obj/machinery/firealarm/New(loc, dir, building)
..()
@@ -1180,20 +1168,6 @@ FIRE ALARM
pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24)
pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0
-/obj/machinery/firealarm/Del()
- //so fire alarms don't keep going forever
- delayed_reset()
- ..()
-
-//needed to cancel the alarm after it is deleted
-/obj/machinery/firealarm/proc/delayed_reset()
- var/area/A = get_area(src)
- if (!A) return
-
- src = null
- spawn(600)
- A.firereset()
-
/obj/machinery/firealarm/initialize()
if(z in config.contact_levels)
if(security_level)
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 3b3104201f..77f41cb2eb 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -53,13 +53,6 @@
ASSERT(src.network.len > 0)
..()
-/obj/machinery/camera/Del()
- if(!alarm_on)
- triggerCameraAlarm()
-
- cancelCameraAlarm()
- ..()
-
/obj/machinery/camera/emp_act(severity)
if(!isEmpProof())
if(prob(100/severity))
@@ -67,7 +60,7 @@
stat |= EMPED
SetLuminosity(0)
kick_viewers()
- triggerCameraAlarm()
+ triggerCameraAlarm(10 * severity)
update_icon()
spawn(900)
@@ -261,22 +254,16 @@
else
icon_state = initial(icon_state)
-/obj/machinery/camera/proc/triggerCameraAlarm()
+/obj/machinery/camera/proc/triggerCameraAlarm(var/duration = 0)
alarm_on = 1
- if(!get_area(src))
- return
-
- for(var/mob/living/silicon/S in mob_list)
- S.triggerAlarm("Camera", get_area(src), list(src), src)
-
+ camera_alarm.triggerAlarm(loc, src, duration)
/obj/machinery/camera/proc/cancelCameraAlarm()
- alarm_on = 0
- if(!get_area(src))
+ if(wires.IsIndexCut(CAMERA_WIRE_ALARM))
return
- for(var/mob/living/silicon/S in mob_list)
- S.cancelAlarm("Camera", get_area(src), src)
+ alarm_on = 0
+ camera_alarm.clearAlarm(loc, src)
//if false, then the camera is listed as DEACTIVATED and cannot be used
/obj/machinery/camera/proc/can_use()
@@ -361,3 +348,13 @@
user.set_machine(src)
wires.Interact(user)
+
+/obj/machinery/camera/proc/nano_structure()
+ var/cam[0]
+ cam["name"] = sanitize(c_tag)
+ cam["deact"] = !can_use()
+ cam["camera"] = "\ref[src]"
+ cam["x"] = x
+ cam["y"] = y
+ cam["z"] = z
+ return cam
diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm
index 0c6f7d95a7..636f114de7 100644
--- a/code/game/machinery/camera/motion.dm
+++ b/code/game/machinery/camera/motion.dm
@@ -45,8 +45,7 @@
if (!status || (stat & NOPOWER))
return 0
if (detectTime == -1)
- for (var/mob/living/silicon/aiPlayer in player_list)
- aiPlayer.cancelAlarm("Motion", get_area(src), src)
+ motion_alarm.clearAlarm(loc, src)
detectTime = 0
return 1
@@ -54,8 +53,7 @@
if (!status || (stat & NOPOWER))
return 0
if (!detectTime) return 0
- for (var/mob/living/silicon/aiPlayer in player_list)
- aiPlayer.triggerAlarm("Motion", get_area(src), list(src), src)
+ motion_alarm.triggerAlarm(loc, src)
detectTime = -1
return 1
diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm
index 8e978853c4..8306b3a87e 100644
--- a/code/game/machinery/computer/atmos_alert.dm
+++ b/code/game/machinery/computer/atmos_alert.dm
@@ -1,116 +1,85 @@
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31
-// Converting these to global lists may be a bit laggy when removal procs are called. Consider
-// rewriting this properly to fix the update bug, rather than unifying all monitors. ~Z
-
var/global/list/priority_air_alarms = list()
var/global/list/minor_air_alarms = list()
+
/obj/machinery/computer/atmos_alert
name = "atmospheric alert computer"
desc = "Used to access the station's atmospheric sensors."
circuit = "/obj/item/weapon/circuitboard/atmos_alert"
icon_state = "alert:0"
- var/receive_frequency = 1437
- var/datum/radio_frequency/radio_connection
-
-/obj/machinery/computer/atmos_alert/initialize()
+/obj/machinery/computer/atmos_alert/New()
..()
- set_frequency(receive_frequency)
-
-/obj/machinery/computer/atmos_alert/receive_signal(datum/signal/signal)
- if(!signal || signal.encryption) return
-
- var/zone = signal.data["zone"]
- var/severity = signal.data["alert"]
-
- if(!zone || !severity) return
-
- minor_air_alarms -= zone
- priority_air_alarms -= zone
- if(severity=="severe")
- priority_air_alarms |= zone
- else if (severity=="minor")
- minor_air_alarms |= zone
- update_icon()
- return
-
-
-/obj/machinery/computer/atmos_alert/proc/set_frequency(new_frequency)
- radio_controller.remove_object(src, receive_frequency)
- receive_frequency = new_frequency
- radio_connection = radio_controller.add_object(src, receive_frequency, RADIO_ATMOSIA)
-
+ atmosphere_alarm.register(src, /obj/machinery/computer/station_alert/update_icon)
+
+/obj/machinery/computer/atmos_alert/Del()
+ atmosphere_alarm.unregister(src)
+ ..()
/obj/machinery/computer/atmos_alert/attack_hand(mob/user)
- if(..(user))
- return
- user << browse(return_text(),"window=computer")
- user.set_machine(src)
- onclose(user, "computer")
+ ui_interact(user)
-/obj/machinery/computer/atmos_alert/process()
- if(..())
- src.updateDialog()
+/obj/machinery/computer/atmos_alert/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
+ var/data[0]
+ var/major_alarms[0]
+ var/minor_alarms[0]
+
+ for(var/datum/alarm/alarm in atmosphere_alarm.major_alarms())
+ major_alarms[++major_alarms.len] = list("name" = sanitize(alarm.alarm_name()), "ref" = "\ref[alarm]")
+
+ for(var/datum/alarm/alarm in atmosphere_alarm.minor_alarms())
+ minor_alarms[++minor_alarms.len] = list("name" = sanitize(alarm.alarm_name()), "ref" = "\ref[alarm]")
+
+ data["priority_alarms"] = major_alarms
+ data["minor_alarms"] = minor_alarms
+
+ ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "atmos_alert.tmpl", src.name, 500, 500)
+ ui.set_initial_data(data)
+ ui.open()
+ ui.set_auto_update(1)
/obj/machinery/computer/atmos_alert/update_icon()
..()
if(stat & (NOPOWER|BROKEN))
return
- if(priority_air_alarms.len)
+ var/list/alarms = atmosphere_alarm.major_alarms()
+ if(alarms.len)
icon_state = "alert:2"
-
- else if(minor_air_alarms.len)
- icon_state = "alert:1"
-
else
- icon_state = "alert:0"
+ alarms = atmosphere_alarm.minor_alarms()
+ if(alarms.len)
+ icon_state = "alert:1"
+ else
+ icon_state = initial(icon_state)
return
-
-/obj/machinery/computer/atmos_alert/proc/return_text()
- var/priority_text
- var/minor_text
-
- if(priority_air_alarms.len)
- for(var/zone in priority_air_alarms)
- priority_text += "[zone] X
"
- else
- priority_text = "No priority alerts detected.
"
-
- if(minor_air_alarms.len)
- for(var/zone in minor_air_alarms)
- minor_text += "[zone] X
"
- else
- minor_text = "No minor alerts detected.
"
-
- var/output = {"[name]
-Priority Alerts:
-[priority_text]
-
-
-Minor Alerts:
-[minor_text]
-
"}
-
- return output
-
-
/obj/machinery/computer/atmos_alert/Topic(href, href_list)
if(..())
- return
+ return 1
- if(href_list["priority_clear"])
- var/removing_zone = href_list["priority_clear"]
- for(var/zone in priority_air_alarms)
- if(ckey(zone) == removing_zone)
- priority_air_alarms -= zone
+ if(href_list["clear_alarm"])
+ var/datum/alarm/alarm = locate(href_list["clear_alarm"]) in atmosphere_alarm.alarms
+ if(alarm)
+ for(var/datum/alarm_source/alarm_source in alarm.sources)
+ var/obj/machinery/alarm/air_alarm = alarm_source.source
+ if(istype(air_alarm))
+ var/list/new_ref = list("atmos_reset" = 1)
+ air_alarm.Topic(href, new_ref, custom_state = atmos_alert_topic)
+ return 1
- if(href_list["minor_clear"])
- var/removing_zone = href_list["minor_clear"]
- for(var/zone in minor_air_alarms)
- if(ckey(zone) == removing_zone)
- minor_air_alarms -= zone
- update_icon()
- return
+
+var/datum/topic_state/atmos_alert/atmos_alert_topic = new()
+
+/datum/topic_state/atmos_alert
+ flags = NANO_IGNORE_DISTANCE
+
+/datum/topic_state/air_alarm/href_list(var/mob/user)
+ var/list/extra_href = list()
+ extra_href["remote_connection"] = 1
+ extra_href["remote_access"] = 1
+
+ return extra_href
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index 2067bd43a0..8cb58a14cb 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -3,6 +3,8 @@
/proc/invalidateCameraCache()
for(var/obj/machinery/computer/security/s in world)
s.camera_cache = null
+ for(var/datum/alarm/A in world)
+ A.cameras = null
/obj/machinery/computer/security
name = "security camera monitor"
@@ -43,33 +45,17 @@
if(!can_access_camera(C))
continue
- var/cam[0]
- cam["name"] = sanitize(C.c_tag)
- cam["deact"] = !C.can_use()
- cam["camera"] = "\ref[C]"
- cam["x"] = C.x
- cam["y"] = C.y
- cam["z"] = C.z
-
+ var/cam = C.nano_structure()
cameras[++cameras.len] = cam
if(C == current)
data["current"] = cam
- var/list/camera_list = list("cameras" = cameras)
- camera_cache=list2json(camera_list)
-
+ var/list/camera_list = list("cameras" = cameras)
+ camera_cache=list2json(camera_list)
else
if(current)
- var/cam[0]
- cam["name"] = current.c_tag
- cam["deact"] = !current.can_use()
- cam["camera"] = "\ref[current]"
- cam["x"] = current.x
- cam["y"] = current.y
- cam["z"] = current.z
-
- data["current"] = cam
+ data["current"] = current.nano_structure()
if(ui)
diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm
index 028be6e3a3..84ce5cc0d1 100644
--- a/code/game/machinery/computer/station_alert.dm
+++ b/code/game/machinery/computer/station_alert.dm
@@ -5,106 +5,42 @@
icon_state = "alert:0"
circuit = "/obj/item/weapon/circuitboard/stationalert"
var/alarms = list("Fire"=list(), "Atmosphere"=list(), "Power"=list())
+ var/obj/nano_module/alarm_monitor/engineering/alarm_monitor
+/obj/machinery/computer/station_alert/New()
+ ..()
+ alarm_monitor = new(src)
+ alarm_monitor.register(src, /obj/machinery/computer/station_alert/update_icon)
- attack_ai(mob/user)
- add_fingerprint(user)
- if(stat & (BROKEN|NOPOWER))
- return
- interact(user)
+/obj/machinery/computer/station_alert/Del()
+ alarm_monitor.unregister(src)
+ ..()
+
+/obj/machinery/computer/station_alert/attack_ai(mob/user)
+ add_fingerprint(user)
+ if(stat & (BROKEN|NOPOWER))
+ return
+ interact(user)
+ return
+
+/obj/machinery/computer/station_alert/attack_hand(mob/user)
+ add_fingerprint(user)
+ if(stat & (BROKEN|NOPOWER))
+ return
+ interact(user)
+ return
+
+/obj/machinery/computer/station_alert/interact(mob/user)
+ alarm_monitor.ui_interact(user)
+
+/obj/machinery/computer/station_alert/update_icon()
+ ..()
+ if(stat & (BROKEN|NOPOWER))
return
-
- attack_hand(mob/user)
- add_fingerprint(user)
- if(stat & (BROKEN|NOPOWER))
- return
- interact(user)
- return
-
-
- interact(mob/user)
- usr.set_machine(src)
- var/dat = "Current Station Alerts\n"
- dat += "Close
"
- for (var/cat in src.alarms)
- dat += text("[]
\n", cat)
- var/list/L = src.alarms[cat]
- if (L.len)
- for (var/alarm in L)
- var/list/alm = L[alarm]
- var/area/A = alm[1]
- var/list/sources = alm[3]
- dat += ""
- dat += "• "
- dat += "[A.name]"
- if (sources.len > 1)
- dat += text(" - [] sources", sources.len)
- dat += "
\n"
- else
- dat += "-- All Systems Nominal
\n"
- dat += "
\n"
- user << browse(dat, "window=alerts")
- onclose(user, "alerts")
-
-
- Topic(href, href_list)
- if(..())
- return
- return
-
-
- proc/triggerAlarm(var/class, area/A, var/O, var/alarmsource)
- if(stat & (BROKEN))
- return
- var/list/L = src.alarms[class]
- for (var/I in L)
- if (I == A.name)
- var/list/alarm = L[I]
- var/list/sources = alarm[3]
- if (!(alarmsource in sources))
- sources += alarmsource
- return 1
- var/obj/machinery/camera/C = null
- var/list/CL = null
- if (O && istype(O, /list))
- CL = O
- if (CL.len == 1)
- C = CL[1]
- else if (O && istype(O, /obj/machinery/camera))
- C = O
- L[A.name] = list(A, (C) ? C : O, list(alarmsource))
- return 1
-
-
- proc/cancelAlarm(var/class, area/A as area, obj/origin)
- if(stat & (BROKEN))
- return
- var/list/L = src.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
-
-
- process()
- if(stat & (BROKEN|NOPOWER))
- icon_state = "atmos0"
- return
- var/active_alarms = 0
- for (var/cat in src.alarms)
- var/list/L = src.alarms[cat]
- if(L.len) active_alarms = 1
- if(active_alarms)
- icon_state = "alert:2"
- else
- icon_state = "alert:0"
- ..()
- return
+ var/list/alarms = alarm_monitor.active_alarms()
+ if(alarms.len)
+ icon_state = "alert:2"
+ else
+ icon_state = initial(icon_state)
+ return
diff --git a/code/global.dm b/code/global.dm
index c5fd821f79..13a62b38ce 100644
--- a/code/global.dm
+++ b/code/global.dm
@@ -174,8 +174,9 @@ var/gravity_is_on = 1
var/join_motd = null
var/forceblob = 0
-var/datum/nanomanager/nanomanager = new() // NanoManager, the manager for Nano UIs.
-var/datum/event_manager/event_manager = new() // Event Manager, the manager for events.
+var/datum/nanomanager/nanomanager = new() // NanoManager, the manager for Nano UIs.
+var/datum/event_manager/event_manager = new() // Event Manager, the manager for events.
+var/datum/subsystem/alarm/alarm_manager = new() // Alarm Manager, the manager for alarms.
var/list/awaydestinations = list() // Away missions. A list of landmarks that the warpgate can take you to.
diff --git a/code/modules/alarm/alarm.dm b/code/modules/alarm/alarm.dm
new file mode 100644
index 0000000000..525a5b3d21
--- /dev/null
+++ b/code/modules/alarm/alarm.dm
@@ -0,0 +1,138 @@
+#define ALARM_RESET_DELAY 100 // How long will the alarm/trigger remain active once origin/source has been found to be gone?
+
+/datum/alarm_source
+ var/source = null // The source trigger
+ var/source_name = "" // The name of the source should it be lost (for example a destroyed camera)
+ var/duration = 0 // How long this source will be alarming, 0 for indefinetely.
+ var/severity = 1 // How severe the alarm from this source is.
+ var/start_time = 0 // When this source began alarming.
+ var/end_time = 0 // Use to set when this trigger should clear, in case the source is lost.
+
+/datum/alarm_source/New(var/atom/source)
+ src.source = source
+ start_time = world.time
+ source_name = source.get_source_name()
+
+/datum/alarm
+ var/atom/origin //Used to identify the alarm area.
+ var/list/sources = new() //List of sources triggering the alarm. Used to determine when the alarm should be cleared.
+ var/list/sources_assoc = new() //Associative list of source triggers. Used to efficiently acquire the alarm source.
+ var/list/cameras //List of cameras that can be switched to, if the player has that capability.
+ var/area/last_area //The last acquired area, used should origin be lost (for example a destroyed borg containing an alarming camera).
+ var/area/last_name //The last acquired name, used should origin be lost
+ var/area/last_camera_area //The last area in which cameras where fetched, used to see if the camera list should be updated.
+ var/end_time //Used to set when this alarm should clear, in case the origin is lost.
+
+/datum/alarm/New(var/atom/origin, var/atom/source, var/duration, var/severity)
+ src.origin = origin
+
+ cameras() // Sets up both cameras and last alarm area.
+ set_source_data(source, duration, severity)
+
+/datum/alarm/proc/process()
+ // Has origin gone missing?
+ if(!origin && !end_time)
+ end_time = world.time + ALARM_RESET_DELAY
+ for(var/datum/alarm_source/AS in sources)
+ // Has the alarm passed its best before date?
+ if((AS.end_time && world.time > AS.end_time) || (AS.duration && world.time > (AS.start_time + AS.duration)))
+ sources -= AS
+ // Has the source gone missing? Then reset the normal duration and set end_time
+ if(!AS.source && !AS.end_time) // end_time is used instead of duration to ensure the reset doesn't remain in the future indefinetely.
+ AS.duration = 0
+ AS.end_time = world.time + ALARM_RESET_DELAY
+
+/datum/alarm/proc/set_source_data(var/atom/source, var/duration, var/severity)
+ var/datum/alarm_source/AS = sources_assoc[source]
+ if(!AS)
+ AS = new/datum/alarm_source(source)
+ sources += AS
+ sources_assoc[source] = AS
+ // Currently only non-0 durations can be altered (normal alarms VS EMP blasts)
+ if(AS.duration)
+ duration = SecondsToTicks(duration)
+ AS.duration = duration
+ AS.severity = severity
+
+/datum/alarm/proc/clear(var/source)
+ var/datum/alarm_source/AS = sources_assoc[source]
+ sources -= AS
+ sources_assoc -= source
+
+/datum/alarm/proc/alarm_area()
+ if(!origin)
+ return last_area
+
+ last_area = origin.get_alarm_area()
+ return last_area
+
+/datum/alarm/proc/alarm_name()
+ if(!origin)
+ return last_name
+
+ last_name = origin.get_alarm_name()
+ return last_name
+
+/datum/alarm/proc/cameras()
+ // If the alarm origin has changed area, for example a borg containing an alarming camera, reset the list of cameras
+ if(cameras && (last_camera_area != alarm_area()))
+ cameras = null
+
+ // The list of cameras is also reset by /proc/invalidateCameraCache()
+ if(!cameras)
+ cameras = origin ? origin.get_alarm_cameras() : last_area.get_alarm_cameras()
+
+ last_camera_area = last_area
+ return cameras
+
+/datum/alarm/proc/max_severity()
+ var/max_severity = 0
+ for(var/datum/alarm_source/AS in sources)
+ max_severity = max(AS.severity, max_severity)
+
+ return max_severity
+
+/******************
+* Assisting procs *
+******************/
+/atom/proc/get_alarm_area()
+ var/area/A = get_area(src)
+ return A.master
+
+/area/get_alarm_area()
+ return src.master
+
+/atom/proc/get_alarm_name()
+ var/area/A = get_area(src)
+ return A.master.name
+
+/area/get_alarm_name()
+ return master.name
+
+/mob/get_alarm_name()
+ return name
+
+/atom/proc/get_source_name()
+ return name
+
+/obj/machinery/camera/get_source_name()
+ return c_tag
+
+/atom/proc/get_alarm_cameras()
+ var/area/A = get_area(src)
+ return A.get_cameras()
+
+/area/get_alarm_cameras()
+ return get_cameras()
+
+/mob/living/silicon/robot/get_alarm_cameras()
+ var/list/cameras = ..()
+ if(camera)
+ cameras += camera
+
+ return cameras
+
+/mob/living/silicon/robot/syndicate/get_alarm_cameras()
+ return list()
+
+#undef ALARM_LOSS_DELAY
diff --git a/code/modules/alarm/alarm_handler.dm b/code/modules/alarm/alarm_handler.dm
new file mode 100644
index 0000000000..73bd2348aa
--- /dev/null
+++ b/code/modules/alarm/alarm_handler.dm
@@ -0,0 +1,99 @@
+#define ALARM_RAISED 1
+#define ALARM_CLEARED 0
+
+/datum/alarm_handler
+ var/category = ""
+ var/list/datum/alarm/alarms = new // All alarms, to handle cases when an origin has been deleted with one or more active alarms
+ var/list/datum/alarm/alarms_assoc = new // Associative list of alarms, to efficiently acquire them based on origin.
+ var/list/listeners = new // A list of all objects interested in alarm changes.
+
+/datum/alarm_handler/proc/process()
+ for(var/datum/alarm/A in alarms)
+ A.process()
+ check_alarm_cleared(A)
+
+/datum/alarm_handler/proc/triggerAlarm(var/atom/origin, var/atom/source, var/duration = 0, var/severity = 1)
+ var/new_alarm
+ //Proper origin and source mandatory
+ if(!(origin && source))
+ return
+ origin = origin.get_alarm_origin()
+
+ new_alarm = 0
+ //see if there is already an alarm of this origin
+ var/datum/alarm/existing = alarms_assoc[origin]
+ if(existing)
+ existing.set_source_data(source, duration, severity)
+ else
+ existing = new/datum/alarm(origin, source, duration, severity)
+ new_alarm = 1
+
+ alarms |= existing
+ alarms_assoc[origin] = existing
+ if(new_alarm)
+ alarms = dd_sortedObjectList(alarms)
+ on_alarm_change(existing, ALARM_RAISED)
+
+ return new_alarm
+
+/datum/alarm_handler/proc/clearAlarm(var/atom/origin, var/source)
+ //Proper origin and source mandatory
+ if(!(origin && source))
+ return
+ origin = origin.get_alarm_origin()
+
+ var/datum/alarm/existing = alarms_assoc[origin]
+ if(existing)
+ existing.clear(source)
+ return check_alarm_cleared(existing)
+
+/datum/alarm_handler/proc/major_alarms()
+ return alarms
+
+/datum/alarm_handler/proc/minor_alarms()
+ return alarms
+
+/datum/alarm_handler/proc/check_alarm_cleared(var/datum/alarm/alarm)
+ if ((alarm.end_time && world.time > alarm.end_time) || !alarm.sources.len)
+ alarms -= alarm
+ alarms_assoc -= alarm.origin
+ on_alarm_change(alarm, ALARM_CLEARED)
+ return 1
+ return 0
+
+/datum/alarm_handler/proc/on_alarm_change(var/datum/alarm/alarm, var/was_raised)
+ for(var/obj/machinery/camera/C in alarm.cameras())
+ if(was_raised)
+ C.network.Add(category)
+ invalidateCameraCache()
+ else
+ C.network.Remove(category)
+ notify_listeners(alarm, was_raised)
+
+/datum/alarm_handler/proc/get_alarm_severity_for_origin(var/atom/origin)
+ if(!origin)
+ return
+
+ origin = origin.get_alarm_origin()
+ var/datum/alarm/existing = alarms_assoc[origin]
+ if(!existing)
+ return
+
+ return existing.max_severity()
+
+/atom/proc/get_alarm_origin()
+ return src
+
+/turf/get_alarm_origin()
+ var/area/area = get_area(src)
+ return area.master // Very important to get area.master, as dynamic lightning can and will split areas.
+
+/datum/alarm_handler/proc/register(var/object, var/procName)
+ listeners[object] = procName
+
+/datum/alarm_handler/proc/unregister(var/object)
+ listeners -= object
+
+/datum/alarm_handler/proc/notify_listeners(var/alarm, var/was_raised)
+ for(var/listener in listeners)
+ call(listener, listeners[listener])(src, alarm, was_raised)
diff --git a/code/modules/alarm/atmosphere_alarm.dm b/code/modules/alarm/atmosphere_alarm.dm
new file mode 100644
index 0000000000..9751319111
--- /dev/null
+++ b/code/modules/alarm/atmosphere_alarm.dm
@@ -0,0 +1,19 @@
+/datum/alarm_handler/atmosphere
+ category = "Atmosphere Alarms"
+
+/datum/alarm_handler/atmosphere/triggerAlarm(var/atom/origin, var/atom/source, var/duration = 0, var/severity = 1)
+ ..()
+
+/datum/alarm_handler/atmosphere/major_alarms()
+ var/list/major_alarms = new()
+ for(var/datum/alarm/A in alarms)
+ if(A.max_severity() > 1)
+ major_alarms.Add(A)
+ return major_alarms
+
+/datum/alarm_handler/atmosphere/minor_alarms()
+ var/list/minor_alarms = new()
+ for(var/datum/alarm/A in alarms)
+ if(A.max_severity() == 1)
+ minor_alarms.Add(A)
+ return minor_alarms
diff --git a/code/modules/alarm/camera_alarm.dm b/code/modules/alarm/camera_alarm.dm
new file mode 100644
index 0000000000..9594a1c8a0
--- /dev/null
+++ b/code/modules/alarm/camera_alarm.dm
@@ -0,0 +1,2 @@
+/datum/alarm_handler/camera
+ category = "Camera Alarms"
diff --git a/code/modules/alarm/fire_alarm.dm b/code/modules/alarm/fire_alarm.dm
new file mode 100644
index 0000000000..9c7a9874c7
--- /dev/null
+++ b/code/modules/alarm/fire_alarm.dm
@@ -0,0 +1,11 @@
+/datum/alarm_handler/fire
+ category = "Fire Alarms"
+
+/datum/alarm_handler/fire/on_alarm_change(var/datum/alarm/alarm, var/was_raised)
+ var/area/A = alarm.origin
+ if(istype(A))
+ if(was_raised)
+ A.fire_alert()
+ else
+ A.fire_reset()
+ ..()
diff --git a/code/modules/alarm/motion_alarm.dm b/code/modules/alarm/motion_alarm.dm
new file mode 100644
index 0000000000..cafc7c128d
--- /dev/null
+++ b/code/modules/alarm/motion_alarm.dm
@@ -0,0 +1,2 @@
+/datum/alarm_handler/motion
+ category = "Motion Alarms"
diff --git a/code/modules/alarm/power_alarm.dm b/code/modules/alarm/power_alarm.dm
new file mode 100644
index 0000000000..2df6d1eab3
--- /dev/null
+++ b/code/modules/alarm/power_alarm.dm
@@ -0,0 +1,10 @@
+/datum/alarm_handler/power
+ category = "Power Alarms"
+
+/datum/alarm_handler/power/on_alarm_change(var/datum/alarm/alarm, var/was_raised)
+ var/area/A = alarm.origin
+ if(istype(A))
+ A.power_alert(was_raised)
+ ..()
+
+/area/proc/power_alert(var/alarming)
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index d5eaec70a1..a0781d7073 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -3,7 +3,6 @@
var/list/ai_list = list()
var/list/ai_verbs_default = list(
- /mob/living/silicon/ai/proc/ai_alerts,
/mob/living/silicon/ai/proc/ai_announcement,
/mob/living/silicon/ai/proc/ai_call_shuttle,
// /mob/living/silicon/ai/proc/ai_recall_shuttle,
@@ -23,10 +22,7 @@ var/list/ai_verbs_default = list(
/mob/living/silicon/ai/proc/sensor_mode,
/mob/living/silicon/ai/proc/show_laws_verb,
/mob/living/silicon/ai/proc/toggle_acceleration,
- /mob/living/silicon/ai/proc/toggle_camera_light,
- /mob/living/silicon/ai/proc/nano_rcon,
- /mob/living/silicon/ai/proc/nano_crew_monitor,
- /mob/living/silicon/ai/proc/nano_power_monitor
+ /mob/living/silicon/ai/proc/toggle_camera_light
)
//Not sure why this is necessary...
@@ -83,9 +79,11 @@ var/list/ai_verbs_default = list(
/mob/living/silicon/ai/proc/add_ai_verbs()
src.verbs |= ai_verbs_default
+ src.verbs |= ai_verbs_subsystems
/mob/living/silicon/ai/proc/remove_ai_verbs()
src.verbs -= ai_verbs_default
+ src.verbs -= ai_verbs_subsystems
/mob/living/silicon/ai/New(loc, var/datum/ai_laws/L, var/obj/item/device/mmi/B, var/safety = 0)
announcement = new()
@@ -166,8 +164,6 @@ var/list/ai_verbs_default = list(
hud_list[IMPTRACK_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
hud_list[SPECIALROLE_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
- init_subsystems()
-
ai_list += src
..()
return
@@ -325,36 +321,6 @@ var/list/ai_verbs_default = list(
if(malf && malf.apcs >= 3)
stat(null, "Time until station control secured: [max(malf.AI_win_timeleft/(malf.apcs/3), 0)] seconds")
-/mob/living/silicon/ai/proc/ai_alerts()
- set category = "AI Commands"
- set name = "Show Alerts"
-
- var/dat = "Current Station Alerts\n"
- dat += "Close
"
- for (var/cat in alarms)
- dat += text("[]
\n", cat)
- var/list/alarmlist = alarms[cat]
- if (alarmlist.len)
- for (var/area_name in alarmlist)
- var/datum/alarm/alarm = alarmlist[area_name]
- dat += ""
-
- var/cameratext = ""
- if (alarm.cameras)
- for (var/obj/machinery/camera/I in alarm.cameras)
- cameratext += text("[][]", (cameratext=="") ? "" : " | ", src, I, I.c_tag)
- dat += text("-- [] ([])", alarm.area.name, (cameratext)? cameratext : "No Camera")
-
- if (alarm.sources.len > 1)
- dat += text(" - [] sources", alarm.sources.len)
- dat += "
\n"
- else
- dat += "-- All Systems Nominal
\n"
- dat += "
\n"
-
- viewalerts = 1
- src << browse(dat, "window=aialerts&can_close=0")
-
// this verb lets the ai see the stations manifest
/mob/living/silicon/ai/proc/ai_roster()
set category = "AI Commands"
@@ -451,7 +417,7 @@ var/list/ai_verbs_default = list(
if (href_list["switchcamera"])
switchCamera(locate(href_list["switchcamera"])) in cameranet.cameras
if (href_list["showalerts"])
- ai_alerts()
+ subsystem_alarm_monitor()
//Carn: holopad requests
if (href_list["jumptoholopad"])
var/obj/machinery/hologram/holopad/H = locate(href_list["jumptoholopad"])
@@ -526,29 +492,6 @@ var/list/ai_verbs_default = list(
return 1
-/mob/living/silicon/ai/triggerAlarm(var/class, area/A, list/cameralist, var/source)
- if (stat == 2)
- return 1
-
- ..()
-
- var/cameratext = ""
- for (var/obj/machinery/camera/C in cameralist)
- cameratext += "[(cameratext == "")? "" : "|"][C.c_tag]"
-
- queueAlarm("--- [class] alarm detected in [A.name]! ([(cameratext)? cameratext : "No Camera"])", class)
-
- if (viewalerts) ai_alerts()
-
-/mob/living/silicon/ai/cancelAlarm(var/class, area/A as area, var/source)
- var/has_alarm = ..()
-
- if (!has_alarm)
- queueAlarm(text("--- [] alarm in [] has been cleared.", class, A.name), class, 0)
- if (viewalerts) ai_alerts()
-
- return has_alarm
-
/mob/living/silicon/ai/cancel_camera()
set category = "AI Commands"
set name = "Cancel Camera View"
diff --git a/code/modules/mob/living/silicon/ai/life.dm b/code/modules/mob/living/silicon/ai/life.dm
index a1b9f20605..602e427f95 100644
--- a/code/modules/mob/living/silicon/ai/life.dm
+++ b/code/modules/mob/living/silicon/ai/life.dm
@@ -171,6 +171,7 @@
sleep(50)
theAPC = null
+ process_queued_alarms()
regular_hud_updates()
switch(src.sensor_mode)
if (SEC_HUD)
diff --git a/code/modules/mob/living/silicon/ai/nano.dm b/code/modules/mob/living/silicon/ai/nano.dm
deleted file mode 100644
index 8572c8ec59..0000000000
--- a/code/modules/mob/living/silicon/ai/nano.dm
+++ /dev/null
@@ -1,27 +0,0 @@
-var/obj/nano_module/crew_monitor/crew_monitor
-var/obj/nano_module/rcon/rcon
-var/obj/nano_module/power_monitor/power_monitor
-
-/mob/living/silicon/ai/proc/init_subsystems()
- crew_monitor = new(src)
- rcon = new(src)
- power_monitor = new(src)
-
-/mob/living/silicon/ai/proc/nano_crew_monitor()
- set category = "AI Subystems"
- set name = "Crew Monitor"
-
- crew_monitor.ui_interact(usr)
-
-/mob/living/silicon/ai/proc/nano_power_monitor()
- set category = "AI Subystems"
- set name = "Power Monitor"
-
- power_monitor.ui_interact(usr)
-
-
-/mob/living/silicon/ai/proc/nano_rcon()
- set category = "AI Subystems"
- set name = "RCON"
-
- rcon.ui_interact(usr)
diff --git a/code/modules/mob/living/silicon/ai/subsystems.dm b/code/modules/mob/living/silicon/ai/subsystems.dm
new file mode 100644
index 0000000000..3ef229712e
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai/subsystems.dm
@@ -0,0 +1,44 @@
+var/list/ai_verbs_subsystems = list(
+ /mob/living/silicon/ai/proc/subsystem_alarm_monitor,
+ /mob/living/silicon/ai/proc/subsystem_crew_monitor,
+ /mob/living/silicon/ai/proc/subsystem_power_monitor,
+ /mob/living/silicon/ai/proc/subsystem_rcon
+)
+
+/mob/living/silicon/ai
+ var/
+ var/obj/nano_module/crew_monitor/crew_monitor
+ var/obj/nano_module/rcon/rcon
+ var/obj/nano_module/power_monitor/power_monitor
+
+/mob/living/silicon/ai/init_subsystems()
+ ..()
+ del(alarm_monitor)
+ alarm_monitor = new/obj/nano_module/alarm_monitor/ai(src)
+ crew_monitor = new(src)
+ rcon = new(src)
+ power_monitor = new(src)
+
+/mob/living/silicon/ai/proc/subsystem_alarm_monitor()
+ set name = "Alarm Monitor"
+ set category = "AI Subystems"
+
+ alarm_monitor.ui_interact(usr)
+
+/mob/living/silicon/ai/proc/subsystem_crew_monitor()
+ set category = "AI Subystems"
+ set name = "Crew Monitor"
+
+ crew_monitor.ui_interact(usr)
+
+/mob/living/silicon/ai/proc/subsystem_power_monitor()
+ set category = "AI Subystems"
+ set name = "Power Monitor"
+
+ power_monitor.ui_interact(usr)
+
+/mob/living/silicon/ai/proc/subsystem_rcon()
+ set category = "AI Subystems"
+ set name = "RCON"
+
+ rcon.ui_interact(usr)
diff --git a/code/modules/mob/living/silicon/alarm.dm b/code/modules/mob/living/silicon/alarm.dm
deleted file mode 100644
index ee505fae5c..0000000000
--- a/code/modules/mob/living/silicon/alarm.dm
+++ /dev/null
@@ -1,111 +0,0 @@
-/datum/alarm
- var/area/area //the area associated with the alarm. Used to identify the alarm
- var/list/sources //list of things triggering the alarm. Used to determine when the alarm should be cleared.
- var/list/cameras //list of cameras that can be switched to, if the player has that capability.
-
-/datum/alarm/New(area/A, list/sourcelist=list(), list/cameralist=list())
- area = A
- sources = sourcelist
- cameras = cameralist
-
-/mob/living/silicon
- var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list()) //each sublist stores alarms keyed by the area name
- var/list/alarms_to_show = list()
- var/list/alarms_to_clear = list()
- 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)
-
-/mob/living/silicon/proc/triggerAlarm(var/class, area/A, list/cameralist, var/source)
- var/list/alarmlist = alarms[class]
-
- //see if there is already an alarm of this class for this area
- if (A.name in alarmlist)
- var/datum/alarm/existing = alarmlist[A.name]
- existing.sources += source
- existing.cameras |= cameralist
- else
- alarmlist[A.name] = new /datum/alarm(A, list(source), cameralist)
-
-/mob/living/silicon/proc/cancelAlarm(var/class, area/A as area, var/source)
- var/cleared = 0
- var/list/alarmlist = alarms[class]
-
- if (A.name in alarmlist)
- var/datum/alarm/alarm = alarmlist[A.name]
- alarm.sources -= source
-
- if (!(alarm.sources.len))
- cleared = 1
- alarmlist -= A.name
-
- return !cleared
-
-/mob/living/silicon/proc/queueAlarm(var/message, var/type, var/incoming = 1)
- var/in_cooldown = (alarms_to_show.len > 0 || alarms_to_clear.len > 0)
- if(incoming)
- alarms_to_show += message
- alarm_types_show[type] += 1
- else
- alarms_to_clear += message
- alarm_types_clear[type] += 1
-
- if(!in_cooldown)
- spawn(10 * 10) // 10 seconds
-
- if(alarms_to_show.len < 5)
- for(var/msg in alarms_to_show)
- src << msg
- else if(alarms_to_show.len)
-
- var/msg = "--- "
-
- 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["Power"]] alarms detected. - "
-
- msg += "\[Show Alerts\]"
- src << msg
-
- if(alarms_to_clear.len < 3)
- for(var/msg in alarms_to_clear)
- src << msg
-
- 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_show["Power"]] alarms detected. - "
-
- msg += "\[Show Alerts\]"
- src << msg
-
-
- alarms_to_show = list()
- alarms_to_clear = list()
- for(var/i = 1; i < alarm_types_show.len; i++)
- alarm_types_show[i] = 0
- for(var/i = 1; i < alarm_types_clear.len; i++)
- alarm_types_clear[i] = 0
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm
index 8295687de9..c5d74c54dd 100644
--- a/code/modules/mob/living/silicon/robot/life.dm
+++ b/code/modules/mob/living/silicon/robot/life.dm
@@ -18,6 +18,7 @@
use_power()
process_killswitch()
process_locks()
+ process_queued_alarms()
update_canmove()
/mob/living/silicon/robot/proc/clamp_values()
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index e30ba5f038..667f57f953 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -437,39 +437,12 @@ var/list/robot_verbs_default = list(
updatename()
updateicon()
-/mob/living/silicon/robot/verb/cmd_robot_alerts()
- set category = "Robot Commands"
- set name = "Show Alerts"
- robot_alerts()
-
// this verb lets cyborgs see the stations manifest
/mob/living/silicon/robot/verb/cmd_station_manifest()
set category = "Robot Commands"
set name = "Show Crew Manifest"
show_station_manifest()
-
-/mob/living/silicon/robot/proc/robot_alerts()
- var/dat = "Current Station Alerts\n"
- dat += "Close
"
- for (var/cat in alarms)
- dat += text("[cat]
\n")
- var/list/alarmlist = alarms[cat]
- if (alarmlist.len)
- for (var/area_name in alarmlist)
- var/datum/alarm/alarm = alarmlist[area_name]
- dat += ""
- dat += text("-- [area_name]")
- if (alarm.sources.len > 1)
- dat += text("- [alarm.sources.len] sources")
- dat += "
\n"
- else
- dat += "-- All Systems Nominal
\n"
- dat += "
\n"
-
- viewalerts = 1
- src << browse(dat, "window=robotalerts&can_close=0")
-
/mob/living/silicon/robot/proc/self_diagnosis()
if(!is_component_functioning("diagnosis unit"))
return null
@@ -639,25 +612,6 @@ var/list/robot_verbs_default = list(
return
return
-
-/mob/living/silicon/robot/triggerAlarm(var/class, area/A, list/cameralist, var/source)
- if (stat == 2)
- return 1
-
- ..()
-
- queueAlarm(text("--- [class] alarm detected in [A.name]!"), class)
-
-
-/mob/living/silicon/robot/cancelAlarm(var/class, area/A as area, obj/origin)
- var/has_alarm = ..()
-
- if (!has_alarm)
- queueAlarm(text("--- [class] alarm in [A.name] has been cleared."), class, 0)
-// if (viewalerts) robot_alerts()
- return has_alarm
-
-
/mob/living/silicon/robot/attackby(obj/item/weapon/W as obj, mob/user as mob)
if (istype(W, /obj/item/weapon/handcuffs)) // fuck i don't even know why isrobot() in handcuff code isn't working so this will have to do
return
@@ -1043,30 +997,31 @@ var/list/robot_verbs_default = list(
/mob/living/silicon/robot/Topic(href, href_list)
if(..())
- return
+ return 1
if(usr != src)
- return
+ return 1
if (href_list["showalerts"])
- robot_alerts()
- return
+ subsystem_alarm_monitor()
+ return 1
if (href_list["mod"])
var/obj/item/O = locate(href_list["mod"])
if (istype(O) && (O.loc == src))
O.attack_self(src)
+ return 1
if (href_list["act"])
var/obj/item/O = locate(href_list["act"])
if (!istype(O))
- return
+ return 1
if(!((O in src.module.modules) || (O == src.module.emag)))
- return
+ return 1
if(activated(O))
src << "Already activated"
- return
+ return 1
if(!module_state_1)
module_state_1 = O
O.layer = 20
@@ -1088,6 +1043,7 @@ var/list/robot_verbs_default = list(
else
src << "You need to disable a module first!"
installed_modules()
+ return 1
if (href_list["deact"])
var/obj/item/O = locate(href_list["deact"])
@@ -1106,6 +1062,7 @@ var/list/robot_verbs_default = list(
else
src << "Module isn't activated"
installed_modules()
+ return 1
if (href_list["lawc"]) // Toggling whether or not a law gets stated by the State Laws verb --NeoFite
var/L = text2num(href_list["lawc"])
@@ -1114,6 +1071,7 @@ var/list/robot_verbs_default = list(
if ("No") lawcheck[L+1] = "Yes"
// src << text ("Switching Law [L]'s report status to []", lawcheck[L+1])
checklaws()
+ return 1
if (href_list["lawi"]) // Toggling whether or not a law gets stated by the State Laws verb --NeoFite
var/L = text2num(href_list["lawi"])
@@ -1122,9 +1080,11 @@ var/list/robot_verbs_default = list(
if ("No") ioncheck[L] = "Yes"
// src << text ("Switching Law [L]'s report status to []", lawcheck[L+1])
checklaws()
+ return 1
if (href_list["laws"]) // With how my law selection code works, I changed statelaws from a verb to a proc, and call it through my law selection panel. --NeoFite
statelaws()
+ return 1
return
/mob/living/silicon/robot/proc/radio_menu()
@@ -1257,9 +1217,11 @@ var/list/robot_verbs_default = list(
/mob/living/silicon/robot/proc/add_robot_verbs()
src.verbs |= robot_verbs_default
+ src.verbs |= robot_verbs_subsystems
/mob/living/silicon/robot/proc/remove_robot_verbs()
src.verbs -= robot_verbs_default
+ src.verbs -= robot_verbs_subsystems
// Uses power from cyborg's cell. Returns 1 on success or 0 on failure.
// Properly converts using CELLRATE now! Amount is in Joules.
diff --git a/code/modules/mob/living/silicon/robot/subsystems.dm b/code/modules/mob/living/silicon/robot/subsystems.dm
new file mode 100644
index 0000000000..4750828544
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/subsystems.dm
@@ -0,0 +1,9 @@
+var/list/robot_verbs_subsystems = list(
+ /mob/living/silicon/robot/proc/subsystem_alarm_monitor
+)
+
+/mob/living/silicon/robot/proc/subsystem_alarm_monitor()
+ set name = "Alarm Monitor"
+ set category = "Robot Subystems"
+
+ alarm_monitor.ui_interact(usr)
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index bda7d984e2..fc647dcbeb 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -22,13 +22,26 @@
var/obj/item/device/camera/siliconcam/aiCamera = null //photography
var/local_transmit //If set, can only speak to others of the same type within a short range.
+ // Subsystems
+ var/obj/nano_module/alarm_monitor = null
+
var/sensor_mode = 0 //Determines the current HUD.
+
+ var/next_alarm_notice
+ var/list/datum/alarm/queued_alarms = new()
+
#define SEC_HUD 1 //Security HUD mode
#define MED_HUD 2 //Medical HUD mode
/mob/living/silicon/New()
..()
add_language("Galactic Common")
+ init_subsystems()
+
+/mob/living/silicon/Del()
+ for(var/datum/alarm_handler/AH in alarm_manager.all_handlers)
+ AH.unregister(src)
+ ..()
/mob/living/silicon/proc/SetName(pickedName as text)
real_name = pickedName
@@ -243,7 +256,8 @@
return 1
/mob/living/silicon/Topic(href, href_list)
- ..()
+ if(..())
+ return 1
if (href_list["lawr"]) // Selects on which channel to state laws
var/list/channels = list(MAIN_CHANNEL)
@@ -282,3 +296,62 @@
adjustBruteLoss(30)
updatehealth()
+
+/mob/living/silicon/proc/init_subsystems()
+ alarm_monitor = new/obj/nano_module/alarm_monitor/borg(src)
+ for(var/datum/alarm_handler/AH in alarm_manager.all_handlers)
+ AH.register(src, /mob/living/silicon/proc/receive_alarm)
+ queued_alarms[AH] = list() // Makes sure alarms remain listed in consistent order
+
+/mob/living/silicon/proc/receive_alarm(var/datum/alarm_handler/alarm_handler, var/datum/alarm/alarm, was_raised)
+ if(!next_alarm_notice)
+ next_alarm_notice = world.time + SecondsToTicks(10)
+
+ var/list/alarms = queued_alarms[alarm_handler]
+ if(was_raised)
+ // Raised alarms are always set
+ alarms[alarm] = 1
+ else
+ // Alarms that were raised but then cleared before the next notice are instead removed
+ if(alarm in alarms)
+ alarms -= alarm
+ // And alarms that have only been cleared thus far are set as such
+ else
+ alarms[alarm] = -1
+
+/mob/living/silicon/proc/process_queued_alarms()
+ if(next_alarm_notice && (world.time > next_alarm_notice))
+ next_alarm_notice = 0
+
+ for(var/datum/alarm_handler/AH in queued_alarms)
+ var/list/alarms = queued_alarms[AH]
+ var/reported = 0
+ for(var/datum/alarm/A in alarms)
+ if(alarms[A] == 1)
+ if(!reported)
+ reported = 1
+ src << "--- [AH.category] Detected ---"
+ raised_alarm(A)
+
+ for(var/datum/alarm_handler/AH in queued_alarms)
+ var/list/alarms = queued_alarms[AH]
+ var/reported = 0
+ for(var/datum/alarm/A in alarms)
+ if(alarms[A] == -1)
+ if(!reported)
+ reported = 1
+ src << "--- [AH.category] Cleared ---"
+ src << "\The [A.alarm_name()]."
+
+ for(var/datum/alarm_handler/AH in queued_alarms)
+ var/list/alarms = queued_alarms[AH]
+ alarms.Cut()
+
+/mob/living/silicon/proc/raised_alarm(var/datum/alarm/A)
+ src << "[A.alarm_name()]!"
+
+/mob/living/silicon/ai/raised_alarm(var/datum/alarm/A)
+ var/cameratext = ""
+ for(var/obj/machinery/camera/C in A.cameras())
+ cameratext += "[(cameratext == "")? "" : "|"][C.c_tag]"
+ src << "[A.alarm_name()]! ([(cameratext)? cameratext : "No Camera"])"
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 7104292d64..850a4147dd 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -792,26 +792,25 @@ note dizziness decrements automatically in the mob's Life() proc.
/mob/Stat()
..()
- if(statpanel("MC")) //not looking at that panel
-
- if(client && client.holder)
+ if(client && client.holder)
+ if(statpanel("Status"))
stat(null,"Location:\t([x], [y], [z])")
stat(null,"CPU:\t[world.cpu]")
stat(null,"Instances:\t[world.contents.len]")
-
- if(master_controller)
- stat(null,"MasterController-[last_tick_duration] ([master_controller.processing?"On":"Off"]-[controller_iteration])")
- stat(null,"Air-[master_controller.air_cost]\tSun-[master_controller.sun_cost]")
- stat(null,"Mob-[master_controller.mobs_cost]\t#[mob_list.len]")
- stat(null,"Dis-[master_controller.diseases_cost]\t#[active_diseases.len]")
- stat(null,"Mch-[master_controller.machines_cost]\t#[machines.len]")
- stat(null,"Obj-[master_controller.objects_cost]\t#[processing_objects.len]")
- stat(null,"Net-[master_controller.networks_cost]\tPnet-[master_controller.powernets_cost]")
- stat(null,"NanoUI-[master_controller.nano_cost]\t#[nanomanager.processing_uis.len]")
- stat(null,"Events-[master_controller.events_cost]\t#[event_manager.active_events.len]")
- stat(null,"Tick-[master_controller.ticker_cost]\tALL-[master_controller.total_cost]")
- else
- stat(null,"MasterController-ERROR")
+ if(statpanel("MC") && master_controller)
+ stat(null,"MasterController-[last_tick_duration] ([master_controller.processing?"On":"Off"]-[controller_iteration])")
+ stat(null,"Air-[master_controller.air_cost]\tSun-[master_controller.sun_cost]")
+ stat(null,"Mob-[master_controller.mobs_cost]\t#[mob_list.len]")
+ stat(null,"Dis-[master_controller.diseases_cost]\t#[active_diseases.len]")
+ stat(null,"Mch-[master_controller.machines_cost]\t#[machines.len]")
+ stat(null,"Obj-[master_controller.objects_cost]\t#[processing_objects.len]")
+ stat(null,"Net-[master_controller.networks_cost]\tPnet-[master_controller.powernets_cost]")
+ stat(null,"NanoUI-[master_controller.nano_cost]\t#[nanomanager.processing_uis.len]")
+ stat(null,"Event-[master_controller.events_cost]\t#[event_manager.active_events.len]")
+ alarm_manager.stat_entry()
+ stat(null,"Tick-[master_controller.ticker_cost]\tALL-[master_controller.total_cost]")
+ else
+ stat(null,"MasterController-ERROR")
if(listed_turf && client)
if(!TurfAdjacent(listed_turf))
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index bc9ba4a33d..e8339965c9 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -559,3 +559,16 @@ proc/is_blind(A)
say_dead_direct("The ghost of [name] now [pick("skulks","lurks","prowls","creeps","stalks")] among the dead. [message]")
else
say_dead_direct("[name] no longer [pick("skulks","lurks","prowls","creeps","stalks")] in the realm of the dead. [message]")
+
+/mob/proc/switch_to_camera(var/obj/machinery/camera/C)
+ if (!C.can_use() || stat || (get_dist(C, src) > 1 || machine != src || blinded || !canmove))
+ return 0
+ check_eye(src)
+ return 1
+
+/mob/living/silicon/ai/switch_to_camera(var/obj/machinery/camera/C)
+ if(!C.can_use() || !is_in_chassis())
+ return 0
+
+ eyeobj.setLoc(C)
+ return 1
diff --git a/code/modules/nano/modules/alarm_monitor.dm b/code/modules/nano/modules/alarm_monitor.dm
new file mode 100644
index 0000000000..e4de543498
--- /dev/null
+++ b/code/modules/nano/modules/alarm_monitor.dm
@@ -0,0 +1,83 @@
+/obj/nano_module/alarm_monitor
+ name = "Alarm monitor"
+ var/list_cameras = 0 // Whether or not to list camera references. A future goal would be to merge this with the enginering/security camera console. Currently really only for AI-use.
+ var/list/datum/alarm_handler/alarm_handlers // The particular list of alarm handlers this alarm monitor should present to the user.
+
+/obj/nano_module/alarm_monitor/ai
+ list_cameras = 1
+
+/obj/nano_module/alarm_monitor/ai/New()
+ ..()
+ alarm_handlers = alarm_manager.all_handlers
+
+/obj/nano_module/alarm_monitor/borg/New()
+ ..()
+ alarm_handlers = alarm_manager.all_handlers
+
+/obj/nano_module/alarm_monitor/engineering/New()
+ ..()
+ alarm_handlers = list(atmosphere_alarm, fire_alarm, power_alarm)
+
+/obj/nano_module/alarm_monitor/security/New()
+ ..()
+ alarm_handlers = list(camera_alarm, motion_alarm)
+
+/obj/nano_module/alarm_monitor/proc/register(var/object, var/procName)
+ for(var/datum/alarm_handler/AH in alarm_handlers)
+ AH.register(object, procName)
+
+/obj/nano_module/alarm_monitor/proc/unregister(var/object)
+ for(var/datum/alarm_handler/AH in alarm_handlers)
+ AH.unregister(object)
+
+/obj/nano_module/alarm_monitor/proc/active_alarms()
+ var/list/all_alarms = new()
+ for(var/datum/alarm_handler/AH in alarm_handlers)
+ var/list/alarms = AH.alarms
+ all_alarms += alarms
+
+ return all_alarms
+
+/obj/nano_module/alarm_monitor/ai/Topic(ref, href_list)
+ if(..())
+ return 1
+ if(href_list["switchTo"])
+ var/obj/machinery/camera/C = locate(href_list["switchTo"]) in cameranet.cameras
+ if(!C)
+
+ return
+
+ usr.switch_to_camera(C)
+ return 1
+
+/obj/nano_module/alarm_monitor/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
+ var/data[0]
+
+ var/categories[0]
+ for(var/datum/alarm_handler/AH in alarm_handlers)
+ categories[++categories.len] = list("category" = AH.category, "alarms" = list())
+ for(var/datum/alarm/A in AH.major_alarms())
+ var/cameras[0]
+ var/lost_sources[0]
+
+ if(list_cameras)
+ for(var/obj/machinery/camera/C in A.cameras())
+ cameras[++cameras.len] = C.nano_structure()
+ for(var/datum/alarm_source/AS in A.sources)
+ if(!AS.source)
+ lost_sources[++lost_sources.len] = AS.source_name
+
+ categories[categories.len]["alarms"] += list(list(
+ "name" = sanitize(A.alarm_name()),
+ "origin_lost" = A.origin == null,
+ "has_cameras" = cameras.len,
+ "cameras" = cameras,
+ "lost_sources" = sanitize(english_list(lost_sources, nothing_text = "", and_text = ", "))))
+ data["categories"] = categories
+
+ ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
+ if (!ui)
+ ui = new(user, src, ui_key, "alarm_monitor.tmpl", "Alarm Monitoring Console", 800, 800)
+ ui.set_initial_data(data)
+ ui.open()
+ ui.set_auto_update(1)
diff --git a/code/modules/nano/modules/rcon.dm b/code/modules/nano/modules/rcon.dm
index 6224c56de8..abdae311d0 100644
--- a/code/modules/nano/modules/rcon.dm
+++ b/code/modules/nano/modules/rcon.dm
@@ -1,5 +1,5 @@
/obj/nano_module/rcon
- name = "RCON interface"
+ name = "Power RCON"
var/list/known_SMESs = null
var/list/known_breakers = null
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index ea16adbe96..90b5500ac7 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -183,9 +183,6 @@
if(terminal)
disconnect_terminal()
- //If there's no more APC then there shouldn't be a cause for alarm I guess
- area.poweralert(1, src) //so that alarms don't go on forever
-
..()
/obj/machinery/power/apc/proc/make_terminal()
@@ -725,7 +722,7 @@
src.interact(user)
/obj/machinery/power/apc/attack_ghost(user as mob)
- if(stat & (BROKEN|MAINT))
+ if(stat & (BROKEN|MAINT))
return
return ui_interact(user)
@@ -1164,29 +1161,27 @@
lighting = autoset(lighting, 1)
environ = autoset(environ, 1)
autoflag = 3
- area.poweralert(1, src)
- if(cell.charge >= 4000)
- area.poweralert(1, src)
+ power_alarm.clearAlarm(loc, src)
else if((cell.percent() <= 30) && (cell.percent() > 15) && longtermpower < 0) // <30%, turn off equipment
if(autoflag != 2)
equipment = autoset(equipment, 2)
lighting = autoset(lighting, 1)
environ = autoset(environ, 1)
- area.poweralert(0, src)
+ power_alarm.triggerAlarm(loc, src)
autoflag = 2
else if(cell.percent() <= 15) // <15%, turn off lighting & equipment
if((autoflag > 1 && longtermpower < 0) || (autoflag > 1 && longtermpower >= 0))
equipment = autoset(equipment, 2)
lighting = autoset(lighting, 2)
environ = autoset(environ, 1)
- area.poweralert(0, src)
+ power_alarm.triggerAlarm(loc, src)
autoflag = 1
else // zero charge, turn all off
if(autoflag != 0)
equipment = autoset(equipment, 0)
lighting = autoset(lighting, 0)
environ = autoset(environ, 0)
- area.poweralert(0, src)
+ power_alarm.triggerAlarm(loc, src)
autoflag = 0
// now trickle-charge the cell
@@ -1233,7 +1228,7 @@
equipment = autoset(equipment, 0)
lighting = autoset(lighting, 0)
environ = autoset(environ, 0)
- area.poweralert(0, src)
+ power_alarm.triggerAlarm(loc, src)
autoflag = 0
// update icon & area power if anything changed
diff --git a/code/modules/power/singularity/investigate.dm b/code/modules/power/singularity/investigate.dm
index cc2b7016b5..63c54c0e54 100644
--- a/code/modules/power/singularity/investigate.dm
+++ b/code/modules/power/singularity/investigate.dm
@@ -1,4 +1,4 @@
-/area/engineering/poweralert(var/state, var/source)
- if (state != poweralm)
+/area/engineering/power_alert(var/alarming)
+ if (alarming)
investigate_log("has a power alarm!","singulo")
..()
\ No newline at end of file
diff --git a/nano/css/shared.css b/nano/css/shared.css
index ee4725a4bb..c4f9d1acd9 100644
--- a/nano/css/shared.css
+++ b/nano/css/shared.css
@@ -271,6 +271,12 @@ div.notice {
color: #e9c183;
}
+.itemLabelWidest {
+ float: left;
+ width: 100%;
+ color: #e9c183;
+}
+
.itemContentWide {
float: left;
width: 79%;
diff --git a/nano/templates/alarm_monitor.tmpl b/nano/templates/alarm_monitor.tmpl
new file mode 100644
index 0000000000..0089467e43
--- /dev/null
+++ b/nano/templates/alarm_monitor.tmpl
@@ -0,0 +1,37 @@
+
+
+{{for data.categories}}
+ {{:value.category}}
+ {{for value.alarms :alarmValue:alarmIndex}}
+ {{if alarmValue.origin_lost}}
+ {{:alarmValue.name}} Alarm Origin Lost
+ {{else}}
+ {{:alarmValue.name}}
+ {{/if}}
+ {{if alarmValue.has_cameras || alarmValue.lost_sources != ""}}
+
+ {{if alarmValue.has_cameras}}
+
+ {{for alarmValue.cameras :cameraValue:cameraIndex}}
+ {{if cameraValue.deact}}
+ {{:helper.link(cameraValue.name + " (deactivated)", '', {}, 'inactive')}}
+ {{else}}
+ {{:helper.link(cameraValue.name, '', {'switchTo' : cameraValue.camera})}}
+ {{/if}}
+ {{/for}}
+
+ {{/if}}
+ {{if alarmValue.lost_sources != ""}}
+
+
Lost Alarm Sources: {{:alarmValue.lost_sources}}
+
+ {{/if}}
+
+ {{/if}}
+ {{empty}}
+ --All Systems Nominal
+ {{/for}}
+{{/for}}
\ No newline at end of file
diff --git a/nano/templates/atmos_alert.tmpl b/nano/templates/atmos_alert.tmpl
new file mode 100644
index 0000000000..dc6f3b9da8
--- /dev/null
+++ b/nano/templates/atmos_alert.tmpl
@@ -0,0 +1,17 @@
+Priority Alerts
+{{for data.priority_alarms}}
+
+ {{:value.name}} {{:helper.link('Reset', null, {'clear_alarm' : value.ref})}}
+
+{{empty}}
+ No priority alerts detected.
+{{/for}}
+
+Minor Alerts
+
+ {{for data.minor_alarms}}
+ {{:value.name}} {{:helper.link('Reset', null, {'clear_alarm' : value.ref})}}
+ {{empty}}
+ No minor alerts detected.
+ {{/for}}
+