diff --git a/_maps/map_files/Delta/delta.dmm b/_maps/map_files/Delta/delta.dmm index 02f36f5ea40..28907e9e05e 100644 --- a/_maps/map_files/Delta/delta.dmm +++ b/_maps/map_files/Delta/delta.dmm @@ -47144,7 +47144,7 @@ pixel_y = 3 }, /obj/item/circuitboard/powermonitor, -/obj/item/circuitboard/stationalert_all{ +/obj/item/circuitboard/stationalert{ pixel_x = 3; pixel_y = -3 }, diff --git a/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm b/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm index bcbbe8b2351..4152ad16382 100644 --- a/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm +++ b/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm @@ -31415,7 +31415,7 @@ pixel_x = -2; pixel_y = 2 }, -/obj/item/circuitboard/stationalert_all{ +/obj/item/circuitboard/stationalert{ pixel_x = 1; pixel_y = -1 }, diff --git a/_maps/map_files/cyberiad/cyberiad.dmm b/_maps/map_files/cyberiad/cyberiad.dmm index 2c12ac11e44..8f6f8d77f7f 100644 --- a/_maps/map_files/cyberiad/cyberiad.dmm +++ b/_maps/map_files/cyberiad/cyberiad.dmm @@ -35244,7 +35244,7 @@ }, /area/bridge) "bnW" = ( -/obj/machinery/computer/station_alert/all, +/obj/machinery/computer/station_alert, /turf/simulated/floor/plasteel{ dir = 0; icon_state = "yellow" @@ -72578,7 +72578,7 @@ pixel_x = -2; pixel_y = 2 }, -/obj/item/circuitboard/stationalert_all{ +/obj/item/circuitboard/stationalert{ pixel_x = 1; pixel_y = -1 }, @@ -75846,7 +75846,7 @@ pixel_x = 32; pixel_y = 0 }, -/obj/machinery/computer/station_alert/all, +/obj/machinery/computer/station_alert, /obj/structure/cable/yellow{ d1 = 4; d2 = 8; @@ -77210,6 +77210,10 @@ pixel_x = -22 }, /obj/item/stack/tape_roll, +/obj/machinery/camera/motion{ + c_tag = "EVA Motion Sensor"; + dir = 4 + }, /turf/simulated/floor/plasteel{ icon_state = "dark" }, diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 10aa1718dda..26b1d2d44aa 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -726,3 +726,7 @@ #define COMSIG_XENO_TURF_CLICK_CTRL "xeno_turf_click_alt" ///from monkey CtrlClickOn(): (/mob) #define COMSIG_XENO_MONKEY_CLICK_CTRL "xeno_monkey_click_ctrl" + +///SSalarm signals +#define COMSIG_TRIGGERED_ALARM "ssalarm_triggered" +#define COMSIG_CANCELLED_ALARM "ssalarm_cancelled" diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 181d2a5140c..829c2c4fade 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -5,11 +5,11 @@ var/turf/T = get_turf(A) return T ? T.loc : null -/proc/get_area_name(N) //get area by its name - for(var/area/A in world) - if(A.name == N) - return A - return 0 +/proc/get_area_name(atom/X, format_text = FALSE) + var/area/A = isarea(X) ? X : get_area(X) + if(!A) + return null + return format_text ? format_text(A.name) : A.name /proc/get_location_name(atom/X, format_text = FALSE) var/area/A = isarea(X) ? X : get_area(X) @@ -31,6 +31,24 @@ areas |= T.loc return areas +/proc/get_open_turf_in_dir(atom/center, dir) + var/turf/T = get_ranged_target_turf(center, dir, 1) + if(T && !T.density) + return T + +/proc/get_adjacent_open_turfs(atom/center) + . = list(get_open_turf_in_dir(center, NORTH), + get_open_turf_in_dir(center, SOUTH), + get_open_turf_in_dir(center, EAST), + get_open_turf_in_dir(center, WEST)) + listclearnulls(.) + +/proc/get_adjacent_open_areas(atom/center) + . = list() + var/list/adjacent_turfs = get_adjacent_open_turfs(center) + for(var/I in adjacent_turfs) + . |= get_area(I) + // Like view but bypasses luminosity check /proc/hear(var/range, var/atom/source) diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index c1cf5d53520..075f3370965 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -672,9 +672,6 @@ proc/dd_sortedObjectList(list/incoming) /obj/machinery/camera/dd_SortValue() return "[c_tag]" -/datum/alarm/dd_SortValue() - return "[sanitize(last_name)]" - //Picks from the list, with some safeties, and returns the "default" arg if it fails #define DEFAULTPICK(L, default) ((istype(L, /list) && L:len) ? pick(L) : default) diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm index 21af95cab48..7999b36f467 100644 --- a/code/_globalvars/lists/objects.dm +++ b/code/_globalvars/lists/objects.dm @@ -47,7 +47,7 @@ GLOBAL_LIST_EMPTY(ladders) GLOBAL_LIST_INIT(active_diseases, list()) //List of Active disease in all mobs; purely for quick referencing. GLOBAL_LIST_EMPTY(mob_spawners) // All mob_spawn objects - +GLOBAL_LIST_EMPTY(alert_consoles) // Station alert consoles, /obj/machinery/computer/station_alert GLOBAL_LIST_EMPTY(explosive_walls) GLOBAL_LIST_EMPTY(engine_beacon_list) diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm index ada0ce8cd30..856f724f925 100644 --- a/code/_onclick/hud/ai.dm +++ b/code/_onclick/hud/ai.dm @@ -64,7 +64,7 @@ /obj/screen/ai/alerts/Click() if(isAI(usr)) var/mob/living/silicon/ai/AI = usr - AI.subsystem_alarm_monitor() + AI.ai_alerts() /obj/screen/ai/announcement name = "Make Announcement" diff --git a/code/controllers/subsystem/alarm.dm b/code/controllers/subsystem/alarm.dm index 289adf6de40..3d91d763be3 100644 --- a/code/controllers/subsystem/alarm.dm +++ b/code/controllers/subsystem/alarm.dm @@ -1,31 +1,31 @@ -SUBSYSTEM_DEF(alarms) - name = "Alarms" - init_order = INIT_ORDER_ALARMS // 2 - offline_implications = "Alarms (Power, camera, fire, etc) will no longer be checked. No immediate action is needed." - var/datum/alarm_handler/atmosphere/atmosphere_alarm = new() - var/datum/alarm_handler/burglar/burglar_alarm = new() - var/datum/alarm_handler/camera/camera_alarm = new() - var/datum/alarm_handler/fire/fire_alarm = new() - var/datum/alarm_handler/motion/motion_alarm = new() - var/datum/alarm_handler/power/power_alarm = new() - var/list/datum/alarm/all_handlers +SUBSYSTEM_DEF(alarm) + name = "Alarm" + flags = SS_NO_INIT | SS_NO_FIRE + var/list/alarms = list("Motion" = list(), "Fire" = list(), "Atmosphere" = list(), "Power" = list(), "Camera" = list(), "Burglar" = list()) -/datum/controller/subsystem/alarms/Initialize(start_timeofday) - all_handlers = list(SSalarms.atmosphere_alarm, SSalarms.burglar_alarm, SSalarms.camera_alarm, SSalarms.fire_alarm, SSalarms.motion_alarm, SSalarms.power_alarm) - return ..() +/datum/controller/subsystem/alarm/proc/triggerAlarm(class, area/A, list/O, obj/alarmsource) + var/list/L = alarms[class] + for(var/I in L) + if(I == A.name) + var/list/alarm = L[I] + var/list/sources = alarm[3] + if(!(alarmsource.UID() in sources)) + sources += alarmsource.UID() + return TRUE + L[A.name] = list(get_area_name(A, TRUE), O, list(alarmsource.UID())) + SEND_SIGNAL(SSalarm, COMSIG_TRIGGERED_ALARM, class, A, O, alarmsource) + return TRUE -/datum/controller/subsystem/alarms/fire() - for(var/datum/alarm_handler/AH in all_handlers) - AH.process() +/datum/controller/subsystem/alarm/proc/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] + srcs -= origin.UID() + if(!length(srcs)) + cleared = TRUE + L -= I -/datum/controller/subsystem/alarms/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/controller/subsystem/alarms/proc/number_of_active_alarms() - var/list/alarms = active_alarms() - return alarms.len + SEND_SIGNAL(SSalarm, COMSIG_CANCELLED_ALARM, class, A, origin, cleared) diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm index abb0b604069..8d02529ae6b 100644 --- a/code/controllers/verbs.dm +++ b/code/controllers/verbs.dm @@ -20,7 +20,7 @@ message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.") /client/proc/debug_controller(controller in list("failsafe", "Master", "Ticker", "Air", "Jobs", "Sun", "Radio", "Configuration", "pAI", - "Cameras", "Garbage", "Event", "Alarm", "Nano", "Vote", "Fires", + "Cameras", "Garbage", "Event", "Nano", "Vote", "Fires", "Mob", "NPC Pool", "Shuttle", "Timer", "Weather", "Space", "Mob Hunt Server","Input")) set category = "Debug" set name = "Debug Controller" @@ -65,9 +65,6 @@ if("Event") debug_variables(SSevents) feedback_add_details("admin_verb","DEvent") - if("Alarm") - debug_variables(SSalarms) - feedback_add_details("admin_verb", "DAlarm") if("Nano") debug_variables(SSnanoui) feedback_add_details("admin_verb","DNano") diff --git a/code/datums/wires/alarm.dm b/code/datums/wires/alarm.dm index f6b49b0dd99..4f8d086a659 100644 --- a/code/datums/wires/alarm.dm +++ b/code/datums/wires/alarm.dm @@ -13,16 +13,16 @@ switch(index) if(AALARM_WIRE_IDSCAN) return "ID Scan" - + if(AALARM_WIRE_POWER) return "Power" - + if(AALARM_WIRE_SYPHON) return "Syphon" - + if(AALARM_WIRE_AI_CONTROL) return "AI Control" - + if(AALARM_WIRE_AALARM) return "Atmospherics Alarm" @@ -64,8 +64,8 @@ // to_chat(world, "Syphon Wire Cut") if(AALARM_WIRE_AALARM) - if(A.alarm_area.atmosalert(2, A)) - A.post_alert(2) + if(A.alarm_area.atmosalert(ATMOS_ALARM_DANGER, A)) + A.post_alert(ATMOS_ALARM_DANGER) A.update_icon() ..() @@ -107,7 +107,7 @@ if(AALARM_WIRE_AALARM) // to_chat(world, "Aalarm wire pulsed") - if(A.alarm_area.atmosalert(0, A)) - A.post_alert(0) + if(A.alarm_area.atmosalert(ATMOS_ALARM_NONE, A)) + A.post_alert(ATMOS_ALARM_NONE) A.update_icon() ..() diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm index c33e7609685..9a333d268f3 100644 --- a/code/game/area/Space Station 13 areas.dm +++ b/code/game/area/Space Station 13 areas.dm @@ -50,10 +50,10 @@ NOTE: there are two lists of areas in the end of this file: centcom and station /area/space/atmosalert() return -/area/space/fire_alert() +/area/space/firealert(obj/source) return -/area/space/fire_reset() +/area/space/firereset(obj/source) return /area/space/readyalert() diff --git a/code/game/area/ai_monitored.dm b/code/game/area/ai_monitored.dm index 8f65ac634f4..723574ee949 100644 --- a/code/game/area/ai_monitored.dm +++ b/code/game/area/ai_monitored.dm @@ -1,24 +1,30 @@ /area/ai_monitored name = "AI Monitored Area" - var/obj/machinery/camera/motioncamera = null + var/list/motioncameras = list() + var/list/motionTargets = list() - -/area/ai_monitored/LateInitialize() +/area/ai_monitored/Initialize(mapload) . = ..() - // locate and store the motioncamera - for(var/obj/machinery/camera/M in src) - if(M.isMotion()) - motioncamera = M - M.area_motion = src - break + if(mapload) + for(var/obj/machinery/camera/M in src) + if(M.isMotion()) + motioncameras.Add(M) + M.set_area_motion(src) /area/ai_monitored/Entered(atom/movable/O) ..() - if(ismob(O) && motioncamera) - motioncamera.newTarget(O) + if(ismob(O) && length(motioncameras)) + for(var/X in motioncameras) + var/obj/machinery/camera/cam = X + cam.newTarget(O) + return /area/ai_monitored/Exited(atom/movable/O) - if(ismob(O) && motioncamera) - motioncamera.lostTarget(O) + ..() + if(ismob(O) && length(motioncameras)) + for(var/X in motioncameras) + var/obj/machinery/camera/cam = X + cam.lostTargetRef(O.UID()) + return diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index da80a6e775c..de913ac9f91 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -57,6 +57,11 @@ var/list/ambientsounds = GENERIC_SOUNDS + var/list/firedoors + var/list/cameras + var/list/firealarms + var/firedoors_last_closed_on = 0 + var/fast_despawn = FALSE var/can_get_auto_cryod = TRUE var/hide_attacklogs = FALSE // For areas such as thunderdome, lavaland syndiebase, etc which generate a lot of spammy attacklogs. Reduces log priority. @@ -125,35 +130,6 @@ cameras += C return cameras - -/area/proc/atmosalert(danger_level, var/alarm_source, var/force = FALSE) - if(report_alerts) - if(danger_level == ATMOS_ALARM_NONE) - SSalarms.atmosphere_alarm.clearAlarm(src, alarm_source) - else - SSalarms.atmosphere_alarm.triggerAlarm(src, alarm_source, severity = danger_level) - - //Check all the alarms before lowering atmosalm. Raising is perfectly fine. If force = 1 we don't care. - for(var/obj/machinery/alarm/AA in src) - if(!(AA.stat & (NOPOWER|BROKEN)) && !AA.shorted && AA.report_danger_level && !force) - danger_level = max(danger_level, AA.danger_level) - - if(danger_level != atmosalm) - if(danger_level < ATMOS_ALARM_WARNING && atmosalm >= ATMOS_ALARM_WARNING) - //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 >= ATMOS_ALARM_DANGER && atmosalm < ATMOS_ALARM_DANGER) - air_doors_close() - - atmosalm = danger_level - for(var/obj/machinery/alarm/AA in src) - AA.update_icon() - - GLOB.air_alarm_repository.update_cache(src) - return 1 - GLOB.air_alarm_repository.update_cache(src) - return 0 - /area/proc/air_doors_close() if(!air_doors_activated) air_doors_activated = TRUE @@ -179,44 +155,151 @@ D.open() -/area/proc/fire_alert() - if(!fire) - fire = 1 //used for firedoor checks - updateicon() - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - air_doors_close() +/area/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() -/area/proc/fire_reset() - if(fire) - fire = 0 //used for firedoor checks - updateicon() - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - air_doors_open() +/** + * 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(state != poweralm) + poweralm = state + if(istype(source)) //Only report power alarms on the z-level where the source is located. + for(var/thing in cameras) + var/obj/machinery/camera/C = locateUID(thing) + if(!QDELETED(C) && is_station_level(C.z)) + if(state) + C.network -= "Power Alarms" + else + C.network |= "Power Alarms" - return + if(state) + SSalarm.cancelAlarm("Power", src, source) + else + SSalarm.triggerAlarm("Power", src, cameras, source) -/area/proc/burglaralert(var/obj/trigger) - if(always_unpowered == 1) //no burglar alarms in space/asteroid +/** + * 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(danger_level, obj/source) + if(danger_level != atmosalm) + if(danger_level == ATMOS_ALARM_DANGER) + + for(var/thing in cameras) + var/obj/machinery/camera/C = locateUID(thing) + if(!QDELETED(C) && is_station_level(C.z)) + C.network |= "Atmosphere Alarms" + + + SSalarm.triggerAlarm("Atmosphere", src, cameras, source) + + else if(atmosalm == ATMOS_ALARM_DANGER) + for(var/thing in cameras) + var/obj/machinery/camera/C = locateUID(thing) + if(!QDELETED(C) && is_station_level(C.z)) + C.network -= "Atmosphere Alarms" + + SSalarm.cancelAlarm("Atmosphere", src, source) + + atmosalm = danger_level + return TRUE + return FALSE + +/** + * Try to close all the firedoors in the area + */ +/area/proc/ModifyFiredoors(opening) + if(firedoors) + firedoors_last_closed_on = world.time + for(var/FD in firedoors) + var/obj/machinery/door/firedoor/D = FD + var/cont = !D.welded + if(cont && opening) //don't open if adjacent area is on fire + for(var/I in D.affecting_areas) + var/area/A = I + if(A.fire) + cont = FALSE + break + if(cont && D.is_operational()) + if(D.operating) + D.nextstate = opening ? FD_OPEN : FD_CLOSED + else if(!(D.density ^ opening)) + INVOKE_ASYNC(D, (opening ? /obj/machinery/door/firedoor.proc/open : /obj/machinery/door/firedoor.proc/close)) + +/** + * Generate a firealarm alert for this area + * + * Sends to all ai players, alert consoles, drones and alarm monitor programs in the world + * + * Also starts the area processing on SSobj + */ +/area/proc/firealert(obj/source) + if(always_unpowered) //no fire alarms in space/asteroid return - //Trigger alarm effect - set_fire_alarm_effect() + if(!fire) + set_fire_alarm_effect() + ModifyFiredoors(FALSE) + for(var/item in firealarms) + var/obj/machinery/firealarm/F = item + F.update_icon() - //Lockdown airlocks - for(var/obj/machinery/door/airlock/A in src) - spawn(0) - A.close() - if(A.density) - A.lock() + for(var/thing in cameras) + var/obj/machinery/camera/C = locateUID(thing) + if(!QDELETED(C) && is_station_level(C.z)) + C.network |= "Fire Alarms" - SSalarms.burglar_alarm.triggerAlarm(src, trigger) - spawn(600) - SSalarms.burglar_alarm.clearAlarm(src, trigger) + SSalarm.triggerAlarm("Fire", src, cameras, source) -/area/proc/set_fire_alarm_effect() - fire = 1 - updateicon() - mouse_opacity = MOUSE_OPACITY_TRANSPARENT + START_PROCESSING(SSobj, src) + +/** + * Reset the firealarm alert for this area + * + * resets the alert sent to all ai players, alert consoles, drones and alarm monitor programs + * in the world + * + * Also cycles the icons of all firealarms and deregisters the area from processing on SSOBJ + */ +/area/proc/firereset(obj/source) + if(fire) + unset_fire_alarm_effects() + ModifyFiredoors(TRUE) + for(var/item in firealarms) + var/obj/machinery/firealarm/F = item + F.update_icon() + + for(var/thing in cameras) + var/obj/machinery/camera/C = locateUID(thing) + if(!QDELETED(C) && is_station_level(C.z)) + C.network -= "Fire Alarms" + + SSalarm.cancelAlarm("Fire", src, source) + + STOP_PROCESSING(SSobj, src) + +/** + * If 100 ticks has elapsed, toggle all the firedoors closed again + */ +/area/process() + if(firedoors_last_closed_on + 100 < world.time) //every 10 seconds + ModifyFiredoors(FALSE) + +/** + * Close and lock a door passed into this proc + * + * Does this need to exist on area? probably not + */ +/area/proc/close_and_lock_door(obj/machinery/door/DOOR) + set waitfor = FALSE + DOOR.close() + if(DOOR.density) + DOOR.lock() /area/proc/readyalert() if(!eject) @@ -240,13 +323,62 @@ mouse_opacity = MOUSE_OPACITY_TRANSPARENT updateicon() +/** + * Raise a burglar alert for this area + * + * Close and locks all doors in the area and alerts silicon mobs of a break in + * + * Alarm auto resets after 600 ticks + */ +/area/proc/burglaralert(obj/trigger) + if(always_unpowered) //no burglar alarms in space/asteroid + return + + //Trigger alarm effect + set_fire_alarm_effect() + //Lockdown airlocks + for(var/obj/machinery/door/DOOR in src) + close_and_lock_door(DOOR) + + if(SSalarm.triggerAlarm("Burglar", src, cameras, trigger)) + //Cancel silicon alert after 1 minute + addtimer(CALLBACK(SSalarm, /datum/controller/subsystem/alarm.proc/cancelAlarm, "Burglar", src, trigger), 600) + +/** + * Trigger the fire alarm visual affects in an area + * + * Updates the fire light on fire alarms in the area and sets all lights to emergency mode + */ +/area/proc/set_fire_alarm_effect() + fire = TRUE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + for(var/alarm in firealarms) + var/obj/machinery/firealarm/F = alarm + F.update_fire_light(fire) + for(var/obj/machinery/light/L in src) + L.update() + +/** + * unset the fire alarm visual affects in an area + * + * Updates the fire light on fire alarms in the area and sets all lights to emergency mode + */ +/area/proc/unset_fire_alarm_effects() + fire = FALSE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + for(var/alarm in firealarms) + var/obj/machinery/firealarm/F = alarm + F.update_fire_light(fire) + for(var/obj/machinery/light/L in src) + L.update() + /area/proc/updateicon() - if((fire || eject || party) && (!requires_power||power_environ))//If it doesn't require power, can still activate this proc. - if(fire && !eject && !party) + if((eject || party) && (!requires_power||power_environ))//If it doesn't require power, can still activate this proc. + if(!eject && !party) icon_state = "red" - else if(!fire && eject && !party) + else if(eject && !party) icon_state = "red" - else if(party && !fire && !eject) + else if(party && !eject) icon_state = "party" else icon_state = "blue-red" diff --git a/code/game/machinery/alarm.dm b/code/game/machinery/alarm.dm index 9fda2cfe45c..5561327573d 100644 --- a/code/game/machinery/alarm.dm +++ b/code/game/machinery/alarm.dm @@ -314,7 +314,7 @@ temperature_dangerlevel ) - if(old_danger_level!=danger_level) + if(old_danger_level != danger_level) apply_danger_level() if(mode == AALARM_MODE_REPLACEMENT && environment_pressure < ONE_ATMOSPHERE * 0.05) @@ -534,28 +534,35 @@ "checks"= 0, )) -/obj/machinery/alarm/proc/apply_danger_level(var/new_danger_level) - if(report_danger_level && alarm_area.atmosalert(new_danger_level, src)) - post_alert(new_danger_level) +/obj/machinery/alarm/proc/apply_danger_level() + var/new_area_danger_level = ATMOS_ALARM_NONE + for(var/obj/machinery/alarm/AA in alarm_area) + if(!(AA.stat & (NOPOWER|BROKEN)) && !AA.shorted) + new_area_danger_level = max(new_area_danger_level, AA.danger_level) + if(alarm_area.atmosalert(new_area_danger_level, src)) //if area was in normal state or if area was in alert state + post_alert(new_area_danger_level) update_icon() /obj/machinery/alarm/proc/post_alert(alert_level) + if(!report_danger_level) + return var/datum/radio_frequency/frequency = SSradio.return_frequency(alarm_frequency) + if(!frequency) return var/datum/signal/alert_signal = new alert_signal.source = src alert_signal.transmission_method = 1 - alert_signal.data["zone"] = alarm_area.name + alert_signal.data["zone"] = get_area_name(src, TRUE) alert_signal.data["type"] = "Atmospheric" - if(alert_level==2) + if(alert_level == ATMOS_ALARM_DANGER) alert_signal.data["alert"] = "severe" - else if(alert_level==1) + else if(alert_level == ATMOS_ALARM_WARNING) alert_signal.data["alert"] = "minor" - else if(alert_level==0) + else if(alert_level == ATMOS_ALARM_NONE) alert_signal.data["alert"] = "clear" frequency.post_signal(src, alert_signal) @@ -889,14 +896,14 @@ if(href_list["atmos_alarm"]) if(alarm_area.atmosalert(ATMOS_ALARM_DANGER, src)) - apply_danger_level(ATMOS_ALARM_DANGER) + post_alert(ATMOS_ALARM_DANGER) alarmActivated = 1 update_icon() return 1 if(href_list["atmos_reset"]) if(alarm_area.atmosalert(ATMOS_ALARM_NONE, src, TRUE)) - apply_danger_level(ATMOS_ALARM_NONE) + post_alert(ATMOS_ALARM_NONE) alarmActivated = 0 update_icon() return 1 diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index b66cad8d328..9c2b6c77925 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -28,13 +28,14 @@ var/view_range = 7 var/short_range = 2 + var/alarm_on = FALSE var/busy = FALSE var/emped = FALSE //Number of consecutive EMP's on this camera var/in_use_lights = 0 // TO BE IMPLEMENTED var/toggle_sound = 'sound/items/wirecutter.ogg' -/obj/machinery/camera/Initialize() +/obj/machinery/camera/Initialize(mapload) . = ..() wires = new(src) assembly = new(src) @@ -44,10 +45,15 @@ GLOB.cameranet.cameras += src GLOB.cameranet.addCamera(src) + if(isturf(loc)) + LAZYADD(myArea.cameras, UID()) if(is_station_level(z) && prob(3) && !start_active) toggle_cam(null, FALSE) wires.CutAll() +/obj/machinery/camera/proc/set_area_motion(area/A) + area_motion = A + /obj/machinery/camera/Destroy() toggle_cam(null, FALSE) //kick anyone viewing out QDEL_NULL(assembly) @@ -59,10 +65,14 @@ QDEL_NULL(wires) GLOB.cameranet.removeCamera(src) //Will handle removal from the camera network and the chunks, so we don't need to worry about that GLOB.cameranet.cameras -= src + if(isarea(myArea)) + LAZYREMOVE(myArea.cameras, UID()) var/area/ai_monitored/A = get_area(src) if(istype(A)) - A.motioncamera = null + A.motioncameras -= src area_motion = null + cancelCameraAlarm() + cancelAlarm() return ..() /obj/machinery/camera/emp_act(severity) @@ -282,9 +292,16 @@ status = !status if(can_use()) GLOB.cameranet.addCamera(src) + if(isturf(loc)) + myArea = get_area(src) + LAZYADD(myArea.cameras, UID()) + else + myArea = null else set_light(0) GLOB.cameranet.removeCamera(src) + if(isarea(myArea)) + LAZYREMOVE(myArea.cameras, UID()) GLOB.cameranet.updateChunk(x, y, z) var/change_msg = "deactivates" if(status) @@ -313,12 +330,12 @@ to_chat(O, "The screen bursts into static.") /obj/machinery/camera/proc/triggerCameraAlarm() - if(is_station_contact(z)) - SSalarms.camera_alarm.triggerAlarm(loc, src) + alarm_on = TRUE + SSalarm.triggerAlarm("Camera", get_area(src), list(UID()), src) /obj/machinery/camera/proc/cancelCameraAlarm() - if(is_station_contact(z)) - SSalarms.camera_alarm.clearAlarm(loc, src) + alarm_on = FALSE + SSalarm.cancelAlarm("Camera", get_area(src), src) /obj/machinery/camera/proc/can_use() if(!status) @@ -414,8 +431,8 @@ /obj/machinery/camera/portable //Cameras which are placed inside of things, such as helmets. var/turf/prev_turf -/obj/machinery/camera/portable/New() - ..() +/obj/machinery/camera/portable/Initialize(mapload) + . = ..() assembly.state = 0 //These cameras are portable, and so shall be in the portable state if removed. assembly.anchored = 0 assembly.update_icon() diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm index 16fb6eca204..7d33361e4b9 100644 --- a/code/game/machinery/camera/motion.dm +++ b/code/game/machinery/camera/motion.dm @@ -1,60 +1,60 @@ /obj/machinery/camera - - var/list/motionTargets = list() + var/list/localMotionTargets = list() var/detectTime = 0 var/area/ai_monitored/area_motion = null - var/alarm_delay = 100 - + var/alarm_delay = 30 // Don't forget, there's another 3 seconds in queueAlarm() /obj/machinery/camera/process() // motion camera event loop - if(stat & (EMPED|NOPOWER)) - return if(!isMotion()) . = PROCESS_KILL return + if(stat & (EMPED|NOPOWER)) + return if(detectTime > 0) var/elapsed = world.time - detectTime if(elapsed > alarm_delay) triggerAlarm() else if(detectTime == -1) - for(var/mob/target in motionTargets) - if(target.stat == 2) lostTarget(target) - // If not detecting with motion camera... - if(!area_motion) - // See if the camera is still in range - if(!in_range(src, target)) - // If they aren't in range, lose the target. - lostTarget(target) + for(var/thing in getTargetList()) + var/mob/target = locateUID(thing) + if(QDELETED(target) || target.stat == DEAD || (!area_motion && !in_range(src, target))) + //If not part of a monitored area and the camera is not in range or the target is dead + lostTargetRef(thing) -/obj/machinery/camera/proc/newTarget(var/mob/target) - if(istype(target, /mob/living/silicon/ai)) return 0 +/obj/machinery/camera/proc/getTargetList() + if(area_motion) + return area_motion.motionTargets + return localMotionTargets + +/obj/machinery/camera/proc/newTarget(mob/target) + if(isAI(target)) + return FALSE if(detectTime == 0) detectTime = world.time // start the clock - if(!(target in motionTargets)) - motionTargets += target - return 1 + var/list/targets = getTargetList() + targets |= target.UID() + return TRUE -/obj/machinery/camera/proc/lostTarget(var/mob/target) - if(target in motionTargets) - motionTargets -= target - if(motionTargets.len == 0) +/obj/machinery/camera/proc/lostTargetRef(uid) + var/list/targets = getTargetList() + targets -= uid + if(length(targets)) cancelAlarm() /obj/machinery/camera/proc/cancelAlarm() - if(!status || (stat & NOPOWER)) - return FALSE - if(detectTime == -1 && is_station_contact(z)) - SSalarms.motion_alarm.clearAlarm(loc, src) + if(detectTime == -1) + if(status) + SSalarm.cancelAlarm("Motion", get_area(src), src) detectTime = 0 return TRUE /obj/machinery/camera/proc/triggerAlarm() - if(!status || (stat & NOPOWER)) + if(!detectTime) return FALSE - if(!detectTime || !is_station_contact(z)) - return FALSE - SSalarms.motion_alarm.triggerAlarm(loc, src) + if(status) + SSalarm.triggerAlarm("Motion", get_area(src), list(UID()), src) + visible_message("A red light flashes on the [src]!") detectTime = -1 return TRUE diff --git a/code/game/machinery/camera/presets.dm b/code/game/machinery/camera/presets.dm index b7ae75cd13f..33c970639e5 100644 --- a/code/game/machinery/camera/presets.dm +++ b/code/game/machinery/camera/presets.dm @@ -2,7 +2,7 @@ // EMP -/obj/machinery/camera/emp_proof/Initialize() +/obj/machinery/camera/emp_proof/Initialize(mapload) . = ..() upgradeEmpProof() @@ -11,19 +11,23 @@ /obj/machinery/camera/xray icon_state = "xraycam" // Thanks to Krutchen for the icons. -/obj/machinery/camera/xray/Initialize() +/obj/machinery/camera/xray/Initialize(mapload) . = ..() upgradeXRay() // MOTION +/obj/machinery/camera/motion + name = "motion-sensitive security camera" -/obj/machinery/camera/motion/Initialize() +/obj/machinery/camera/motion/Initialize(mapload) . = ..() upgradeMotion() // ALL UPGRADES +/obj/machinery/camera/all + icon_state = "xraycamera" //mapping icon. -/obj/machinery/camera/all/Initialize() +/obj/machinery/camera/all/Initialize(mapload) . = ..() upgradeEmpProof() upgradeXRay() @@ -78,6 +82,10 @@ // If you are upgrading Motion, and it isn't in the camera's New(), add it to the machines list. /obj/machinery/camera/proc/upgradeMotion() + if(isMotion()) + return + if(name == initial(name)) + name = "motion-sensitive security camera" assembly.upgrades.Add(new /obj/item/assembly/prox_sensor(assembly)) setPowerUsage() // Add it to machines that process diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm index 123cee3ae4e..0be4aa0449b 100644 --- a/code/game/machinery/computer/atmos_alert.dm +++ b/code/game/machinery/computer/atmos_alert.dm @@ -1,84 +1,90 @@ -GLOBAL_LIST_EMPTY(priority_air_alarms) -GLOBAL_LIST_EMPTY(minor_air_alarms) - - /obj/machinery/computer/atmos_alert name = "atmospheric alert computer" desc = "Used to access the station's atmospheric sensors." circuit = /obj/item/circuitboard/atmos_alert + var/ui_x = 350 + var/ui_y = 300 icon_keyboard = "atmos_key" icon_screen = "alert:0" light_color = LIGHT_COLOR_CYAN + var/list/priority_alarms = list() + var/list/minor_alarms = list() + var/receive_frequency = ATMOS_FIRE_FREQ + var/datum/radio_frequency/radio_connection -/obj/machinery/computer/atmos_alert/New() - ..() - SSalarms.atmosphere_alarm.register(src, /obj/machinery/computer/station_alert/.proc/update_icon) +/obj/machinery/computer/atmos_alert/Initialize(mapload) + . = ..() + set_frequency(receive_frequency) /obj/machinery/computer/atmos_alert/Destroy() - SSalarms.atmosphere_alarm.unregister(src) - return ..() + SSradio.remove_object(src, receive_frequency) + return ..() /obj/machinery/computer/atmos_alert/attack_hand(mob/user) - ui_interact(user) + tgui_interact(user) -/obj/machinery/computer/atmos_alert/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/computer/atmos_alert/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "atmos_alert.tmpl", src.name, 500, 500) + ui = new(user, src, ui_key, "AtmosAlertConsole", name, ui_x, ui_y, master_ui, state) ui.open() - ui.set_auto_update(1) -/obj/machinery/computer/atmos_alert/ui_data(mob/user, datum/topic_state/state) - var/data[0] - var/major_alarms[0] - var/minor_alarms[0] +/obj/machinery/computer/atmos_alert/tgui_data(mob/user) + var/list/data = list() - for(var/datum/alarm/alarm in SSalarms.atmosphere_alarm.major_alarms()) - major_alarms[++major_alarms.len] = list("name" = sanitize(alarm.alarm_name()), "ref" = "\ref[alarm]") - - for(var/datum/alarm/alarm in SSalarms.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 + data["priority"] = list() + for(var/zone in priority_alarms) + data["priority"] |= zone + data["minor"] = list() + for(var/zone in minor_alarms) + data["minor"] |= zone return data -/obj/machinery/computer/atmos_alert/update_icon() - var/list/alarms = SSalarms.atmosphere_alarm.major_alarms() - if(alarms.len) - icon_screen = "alert:2" - else - alarms = SSalarms.atmosphere_alarm.minor_alarms() - if(alarms.len) - icon_screen = "alert:1" - else - icon_screen = "alert:0" - ..() - -/obj/machinery/computer/atmos_alert/Topic(href, href_list) +/obj/machinery/computer/atmos_alert/tgui_act(action, params) if(..()) - return 1 + return + switch(action) + if("clear") + var/zone = params["zone"] + if(zone in priority_alarms) + to_chat(usr, "Priority alarm for [zone] cleared.") + priority_alarms -= zone + . = TRUE + if(zone in minor_alarms) + to_chat(usr, "Minor alarm for [zone] cleared.") + minor_alarms -= zone + . = TRUE + update_icon() - if(href_list["clear_alarm"]) - var/datum/alarm/alarm = locate(href_list["clear_alarm"]) in SSalarms.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, state = GLOB.air_alarm_topic) - update_icon() - return 1 +/obj/machinery/computer/atmos_alert/proc/set_frequency(new_frequency) + SSradio.remove_object(src, receive_frequency) + receive_frequency = new_frequency + radio_connection = SSradio.add_object(src, receive_frequency, RADIO_ATMOSIA) -GLOBAL_DATUM_INIT(air_alarm_topic, /datum/topic_state/air_alarm_topic, new) +/obj/machinery/computer/atmos_alert/receive_signal(datum/signal/signal) + if(!signal) + return -/datum/topic_state/air_alarm_topic/href_list(var/mob/user) - var/list/extra_href = list() - extra_href["remote_connection"] = 1 - extra_href["remote_access"] = 1 + var/zone = signal.data["zone"] + var/severity = signal.data["alert"] - return extra_href + if(!zone || !severity) + return -/datum/topic_state/air_alarm_topic/can_use_topic(var/src_object, var/mob/user) - return STATUS_INTERACTIVE + minor_alarms -= zone + priority_alarms -= zone + if(severity == "severe") + priority_alarms += zone + else if(severity == "minor") + minor_alarms += zone + update_icon() + +/obj/machinery/computer/atmos_alert/update_icon() + if(length(priority_alarms)) + icon_screen = "alert:2" + else if(length(minor_alarms)) + icon_screen = "alert:1" + else + icon_screen = "alert:0" + ..() diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm index b502d6e5441..a07f3c04348 100644 --- a/code/game/machinery/computer/buildandrepair.dm +++ b/code/game/machinery/computer/buildandrepair.dm @@ -165,12 +165,9 @@ /obj/item/circuitboard/stationalert_engineering name = "Circuit Board (Station Alert Console (Engineering))" build_path = /obj/machinery/computer/station_alert -/obj/item/circuitboard/stationalert_security - name = "Circuit Board (Station Alert Console (Security))" +/obj/item/circuitboard/stationalert + name = "Circuit Board (Station Alert Console)" build_path = /obj/machinery/computer/station_alert -/obj/item/circuitboard/stationalert_all - name = "Circuit Board (Station Alert Console (All))" - build_path = /obj/machinery/computer/station_alert/all /obj/item/circuitboard/atmos_alert name = "Circuit Board (Atmospheric Alert Computer)" build_path = /obj/machinery/computer/atmos_alert diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm index 607918442ea..3b4631b171a 100644 --- a/code/game/machinery/computer/station_alert.dm +++ b/code/game/machinery/computer/station_alert.dm @@ -6,48 +6,91 @@ icon_screen = "alert:0" light_color = LIGHT_COLOR_CYAN circuit = /obj/item/circuitboard/stationalert_engineering - var/datum/nano_module/alarm_monitor/alarm_monitor - var/monitor_type = /datum/nano_module/alarm_monitor/engineering + var/ui_x = 325 + var/ui_y = 500 + var/list/alarms_listend_for = list("Fire", "Atmosphere", "Power") -/obj/machinery/computer/station_alert/security - monitor_type = /datum/nano_module/alarm_monitor/security - circuit = /obj/item/circuitboard/stationalert_security - -/obj/machinery/computer/station_alert/all - monitor_type = /datum/nano_module/alarm_monitor/all - circuit = /obj/item/circuitboard/stationalert_all - -/obj/machinery/computer/station_alert/New() - ..() - alarm_monitor = new monitor_type(src) - alarm_monitor.register(src, /obj/machinery/computer/station_alert/.proc/update_icon) +/obj/machinery/computer/station_alert/Initialize(mapload) + . = ..() + GLOB.alert_consoles += src + RegisterSignal(SSalarm, COMSIG_TRIGGERED_ALARM, .proc/alarm_triggered) + RegisterSignal(SSalarm, COMSIG_CANCELLED_ALARM, .proc/alarm_cancelled) /obj/machinery/computer/station_alert/Destroy() - alarm_monitor.unregister(src) - QDEL_NULL(alarm_monitor) + GLOB.alert_consoles -= src return ..() /obj/machinery/computer/station_alert/attack_ai(mob/user) add_fingerprint(user) if(stat & (BROKEN|NOPOWER)) return - interact(user) + tgui_interact(user) /obj/machinery/computer/station_alert/attack_hand(mob/user) add_fingerprint(user) if(stat & (BROKEN|NOPOWER)) return - interact(user) + tgui_interact(user) -/obj/machinery/computer/station_alert/interact(mob/user) - alarm_monitor.ui_interact(user) +/obj/machinery/computer/station_alert/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "StationAlertConsole", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/computer/station_alert/tgui_data(mob/user) + var/list/data = list() + + data["alarms"] = list() + for(var/class in SSalarm.alarms) + if(!(class in alarms_listend_for)) + continue + data["alarms"][class] = list() + for(var/area in SSalarm.alarms[class]) + for(var/thing in SSalarm.alarms[class][area][3]) + var/atom/A = locateUID(thing) + if(atoms_share_level(A, src)) + data["alarms"][class] += area + + return data + +/obj/machinery/computer/station_alert/proc/alarm_triggered(src, class, area/A, list/O, obj/alarmsource) + if(!(class in alarms_listend_for)) + return + if(alarmsource.z != z) + return + if(stat & (BROKEN)) + return + update_icon() + +/obj/machinery/computer/station_alert/proc/alarm_cancelled(src, class, area/A, obj/origin, cleared) + if(!(class in alarms_listend_for)) + return + if(origin.z != z) + return + if(stat & (BROKEN)) + return + update_icon() /obj/machinery/computer/station_alert/update_icon() - if(alarm_monitor) - var/list/alarms = alarm_monitor.major_alarms() - if(alarms.len) - icon_screen = "alert:2" - else - icon_screen = "alert:0" + var/active_alarms = FALSE + var/list/list/temp_alarm_list = SSalarm.alarms.Copy() + for(var/cat in temp_alarm_list) + if(!(cat in alarms_listend_for)) + continue + var/list/list/L = temp_alarm_list[cat].Copy() + for(var/alarm in L) + var/list/list/alm = L[alarm].Copy() + var/list/list/sources = alm[3].Copy() + for(var/thing in sources) + var/atom/A = locateUID(thing) + if(A && A.z != z) + L -= alarm + if(length(L)) + active_alarms = TRUE + if(active_alarms) + icon_screen = "alert:2" + else + icon_screen = "alert:0" ..() diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index 37dbdb6c9c0..c509675868b 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -28,6 +28,11 @@ var/nextstate = null var/boltslocked = TRUE var/active_alarm = FALSE + var/list/affecting_areas + +/obj/machinery/door/firedoor/Initialize(mapload) + . = ..() + CalculateAffectingAreas() /obj/machinery/door/firedoor/examine(mob/user) . = ..() @@ -40,11 +45,31 @@ else . += "The bolt locks have been unscrewed, but the bolts themselves are still wrenched to the floor." +/obj/machinery/door/firedoor/proc/CalculateAffectingAreas() + remove_from_areas() + affecting_areas = get_adjacent_open_areas(src) | get_area(src) + for(var/I in affecting_areas) + var/area/A = I + LAZYADD(A.firedoors, src) + /obj/machinery/door/firedoor/closed icon_state = "door_closed" opacity = TRUE density = TRUE +//see also turf/AfterChange for adjacency shennanigans + +/obj/machinery/door/firedoor/proc/remove_from_areas() + if(affecting_areas) + for(var/I in affecting_areas) + var/area/A = I + LAZYREMOVE(A.firedoors, src) + +/obj/machinery/door/firedoor/Destroy() + remove_from_areas() + affecting_areas.Cut() + return ..() + /obj/machinery/door/firedoor/Bumped(atom/AM) if(panel_open || operating) return diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm index f12361f1664..b387f404d8c 100644 --- a/code/game/machinery/firealarm.dm +++ b/code/game/machinery/firealarm.dm @@ -25,6 +25,11 @@ FIRE ALARM active_power_usage = 6 power_channel = ENVIRON resistance_flags = FIRE_PROOF + + light_power = 0 + light_range = 7 + light_color = "#ff3232" + var/last_process = 0 var/wiresexposed = 0 var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone @@ -191,6 +196,7 @@ FIRE ALARM /obj/machinery/firealarm/obj_break(damage_flag) if(!(stat & BROKEN) && !(flags & NODECONSTRUCT) && buildstage != 0) //can't break the electronics if there isn't any inside. stat |= BROKEN + LAZYREMOVE(myArea.firealarms, src) update_icon() /obj/machinery/firealarm/deconstruct(disassembled = TRUE) @@ -203,6 +209,14 @@ FIRE ALARM new /obj/item/stack/cable_coil(loc, 3) qdel(src) +/obj/machinery/firealarm/proc/update_fire_light(fire) + if(fire == !!light_power) + return // do nothing if we're already active + if(fire) + set_light(l_power = 0.8) + else + set_light(l_power = 0) + /obj/machinery/firealarm/process()//Note: this processing was mostly phased out due to other code, and only runs when needed if(stat & (NOPOWER|BROKEN)) return @@ -286,26 +300,16 @@ FIRE ALARM time = min(max(round(time), 0), 120) /obj/machinery/firealarm/proc/reset() - if(!working) + if(!working || !report_fire_alarms) return var/area/A = get_area(src) - A.fire_reset() + A.firereset(src) - for(var/obj/machinery/firealarm/FA in A) - if(is_station_contact(z) && FA.report_fire_alarms) - SSalarms.fire_alarm.clearAlarm(loc, FA) - -/obj/machinery/firealarm/proc/alarm(var/duration = 0) - if(!working) +/obj/machinery/firealarm/proc/alarm() + if(!working || !report_fire_alarms) return - var/area/A = get_area(src) - for(var/obj/machinery/firealarm/FA in A) - if(is_station_contact(z) && FA.report_fire_alarms) - SSalarms.fire_alarm.triggerAlarm(loc, FA, duration) - else - A.fire_alert() // Manually trigger alarms if the alarm isn't reported - + A.firealert(src) // Manually trigger alarms if the alarm isn't reported update_icon() /obj/machinery/firealarm/New(location, direction, building) @@ -323,8 +327,14 @@ FIRE ALARM else overlays += image('icons/obj/monitors.dmi', "overlay_green") + myArea = get_area(src) + LAZYADD(myArea.firealarms, src) update_icon() +/obj/machinery/firealarm/Destroy() + LAZYREMOVE(myArea.firealarms, src) + return ..() + /* FIRE ALARM CIRCUIT Just a object used in constructing fire alarms diff --git a/code/game/objects/items/blueprints.dm b/code/game/objects/items/blueprints.dm index 5f5c62abd21..97e46e3abb5 100644 --- a/code/game/objects/items/blueprints.dm +++ b/code/game/objects/items/blueprints.dm @@ -221,6 +221,12 @@ A.contents += thing thing.change_area(old_area, A) + var/area/oldA = get_area(get_turf(usr)) + var/list/firedoors = oldA.firedoors + for(var/door in firedoors) + var/obj/machinery/door/firedoor/FD = door + FD.CalculateAffectingAreas() + interact() area_created = TRUE return area_created @@ -236,6 +242,10 @@ return set_area_machinery_title(A,str,prevname) A.name = str + if(A.firedoors) + for(var/D in A.firedoors) + var/obj/machinery/door/firedoor/FD = D + FD.CalculateAffectingAreas() to_chat(usr, "You rename the '[prevname]' to '[str]'.") interact() return 1 diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 8d1f3920c0c..2f90e4d7cc4 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -263,6 +263,13 @@ if(SSair && !ignore_air) SSair.add_to_active(src) + //update firedoor adjacency + var/list/turfs_to_check = get_adjacent_open_turfs(src) | src + for(var/I in turfs_to_check) + var/turf/T = I + for(var/obj/machinery/door/firedoor/FD in T) + FD.CalculateAffectingAreas() + if(!keep_cabling && !can_have_cabling()) for(var/obj/structure/cable/C in contents) qdel(C) diff --git a/code/modules/alarm/alarm.dm b/code/modules/alarm/alarm.dm deleted file mode 100644 index 752d3232d23..00000000000 --- a/code/modules/alarm/alarm.dm +++ /dev/null @@ -1,136 +0,0 @@ -#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/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 = duration SECONDS - 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 - -/area/get_alarm_area() - return src - -/atom/proc/get_alarm_name() - var/area/A = get_area(src) - return A.name - -/area/get_alarm_name() - return 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() diff --git a/code/modules/alarm/alarm_handler.dm b/code/modules/alarm/alarm_handler.dm deleted file mode 100644 index 56a673bb5b4..00000000000 --- a/code/modules/alarm/alarm_handler.dm +++ /dev/null @@ -1,103 +0,0 @@ -#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/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/has_major_alarms() - if(alarms && alarms.len) - return 1 - return 0 - -/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) - 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 // 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 deleted file mode 100644 index cc29b40ac44..00000000000 --- a/code/modules/alarm/atmosphere_alarm.dm +++ /dev/null @@ -1,19 +0,0 @@ -/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/burglar_alarm.dm b/code/modules/alarm/burglar_alarm.dm deleted file mode 100644 index c55cb12deef..00000000000 --- a/code/modules/alarm/burglar_alarm.dm +++ /dev/null @@ -1,2 +0,0 @@ -/datum/alarm_handler/burglar - category = "Burglar Alarms" diff --git a/code/modules/alarm/camera_alarm.dm b/code/modules/alarm/camera_alarm.dm deleted file mode 100644 index bef53ad466f..00000000000 --- a/code/modules/alarm/camera_alarm.dm +++ /dev/null @@ -1,2 +0,0 @@ -/datum/alarm_handler/camera - category = "Camera Alarms" diff --git a/code/modules/alarm/fire_alarm.dm b/code/modules/alarm/fire_alarm.dm deleted file mode 100644 index dfae3cc8177..00000000000 --- a/code/modules/alarm/fire_alarm.dm +++ /dev/null @@ -1,11 +0,0 @@ -/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 deleted file mode 100644 index fd7e6febe48..00000000000 --- a/code/modules/alarm/motion_alarm.dm +++ /dev/null @@ -1,2 +0,0 @@ -/datum/alarm_handler/motion - category = "Motion Alarms" diff --git a/code/modules/alarm/power_alarm.dm b/code/modules/alarm/power_alarm.dm deleted file mode 100644 index 4a0947a8f94..00000000000 --- a/code/modules/alarm/power_alarm.dm +++ /dev/null @@ -1,10 +0,0 @@ -/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 670088fd0bc..a5a9269d63b 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -50,7 +50,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list( var/list/connected_robots = list() var/aiRestorePowerRoutine = 0 //var/list/laws = list() - var/alarms = list("Motion" = list(), "Fire" = list(), "Atmosphere" = list(), "Power" = list(), "Camera" = list()) + alarms_listend_for = list("Motion", "Fire", "Atmosphere", "Power", "Camera", "Burglar") var/viewalerts = 0 var/icon/holo_icon//Default is assigned when AI is created. var/obj/mecha/controlled_mech //For controlled_mech a mech, to determine whether to relaymove or use the AI eye. @@ -242,6 +242,46 @@ GLOBAL_LIST_INIT(ai_verbs_default, list( return show_borg_info() +/mob/living/silicon/ai/proc/ai_alerts() + var/list/dat = list("