From 7f953a03afeb3bd46fb1c623f69485d581da22b0 Mon Sep 17 00:00:00 2001 From: SandPoot Date: Wed, 22 May 2024 00:06:08 -0300 Subject: [PATCH] listener update --- code/__DEFINES/alarm.dm | 13 ++ code/__DEFINES/dcs/signals.dm | 10 + code/datums/alarm.dm | 199 ++++++++++++++++++ code/datums/wires/airalarm.dm | 6 +- code/game/area/areas.dm | 139 ++---------- code/game/area/areas/ruins/lavaland.dm | 1 - code/game/machinery/camera/camera.dm | 13 +- code/game/machinery/camera/motion.dm | 14 +- code/game/machinery/computer/station_alert.dm | 86 ++------ code/game/objects/structures/displaycase.dm | 121 ++++++----- .../atmospherics/machinery/airalarm.dm | 33 +-- .../mission_code/moonoutpost19.dm | 2 - .../mission_code/undergroundoutpost45.dm | 1 - .../mapping/space_management/traits.dm | 16 +- code/modules/mob/living/silicon/ai/ai.dm | 133 ++++-------- .../modules/mob/living/silicon/robot/robot.dm | 97 +++------ .../mob/living/silicon/robot/robot_defines.dm | 5 +- code/modules/mob/living/silicon/silicon.dm | 102 +++------ .../simple_animal/friendly/drone/_drone.dm | 53 ++--- .../friendly/drone/extra_drone_types.dm | 4 +- .../file_system/programs/alarm.dm | 101 ++------- code/modules/power/apc.dm | 17 +- tgstation.dme | 3 +- 23 files changed, 518 insertions(+), 651 deletions(-) create mode 100644 code/__DEFINES/alarm.dm create mode 100644 code/datums/alarm.dm diff --git a/code/__DEFINES/alarm.dm b/code/__DEFINES/alarm.dm new file mode 100644 index 0000000000..f5142f5a16 --- /dev/null +++ b/code/__DEFINES/alarm.dm @@ -0,0 +1,13 @@ +//A set of defines to be used by the alarm datums +///Sent by air alarms, indecates something wrong with thier attached atmosphere +#define ALARM_ATMOS "Atmosphere" +///Sent by fire alarms when they are toggled +#define ALARM_FIRE "Fire" +///Sent by apcs when their power starts to fail +#define ALARM_POWER "Power" +///Sent by cameras when they're disabled in some manner +#define ALARM_CAMERA "Camera" +///Sent by display cases when they're broken into +#define ALARM_BURGLAR "Burglar" +///Sent by motion detecting cameras when they well, detect motion +#define ALARM_MOTION "Motion" diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index f371ccccfb..de4bd46139 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -36,6 +36,10 @@ #define COMSIG_WEATHER_START(event_type) "!weather_start [event_type]" #define COMSIG_WEATHER_WINDDOWN(event_type) "!weather_winddown [event_type]" #define COMSIG_WEATHER_END(event_type) "!weather_end [event_type]" +/// An alarm of some form was sent (datum/alarm_handler/source, alarm_type, area/source_area) +#define COMSIG_ALARM_FIRE(alarm_type) "!alarm_fire [alarm_type]" +/// An alarm of some form was cleared (datum/alarm_handler/source, alarm_type, area/source_area) +#define COMSIG_ALARM_CLEAR(alarm_type) "!alarm_clear [alarm_type]" /// called by auxgm add_gas: (gas_id) #define COMSIG_GLOB_NEW_GAS "!new_gas" @@ -698,3 +702,9 @@ ///from base of [/datum/component/multiple_lives/proc/respawn]: (mob/respawned_mob, gibbed, lives_left) #define COMSIG_ON_MULTIPLE_LIVES_RESPAWN "on_multiple_lives_respawn" + +// Alarm listener datum signals +///Sent when an alarm is fired (alarm, area/source_area) +#define COMSIG_ALARM_TRIGGERED "comsig_alarm_triggered" +///Send when an alarm source is cleared (alarm_type, area/source_area) +#define COMSIG_ALARM_CLEARED "comsig_alarm_clear" diff --git a/code/datums/alarm.dm b/code/datums/alarm.dm new file mode 100644 index 0000000000..64870fd203 --- /dev/null +++ b/code/datums/alarm.dm @@ -0,0 +1,199 @@ +//This files deals with the generic sending and receiving of "alarms" +//This is a somewhat blanket term, it covers things like fire/power/atmos alarms, along with some oddballs +//Side effect of how the system is used, these are mostly things that are of interest to ais and borgs +//Though it could easily be expanded to cover other senders/revievers +//The system as a whole differs from reading off a global list in a few ways. +//In that A, it allows us to send cameras for ais/borgs/potentially others to jump to +//And B, it's not like we're giving you all the alarms that have been sent, because of the seperate listing for each reviever +//You only recieve alarms sent after you start to listen +//Also of note, due to an optimzation done on areas, one alarm handler will only ever send one "on" or "off" alarm +//So the whole only receving stuff sent post creation thing actually matters +//Honestly I'm not sure how much of this is a feature, and how much is just old code +//But I'm leaving it how I found it + +///Represents a single source of alarms, one alarm handler will only ever count for one alarm per listener +/datum/alarm_handler + ///A list of alarm type -> list of areas we currently have alarms in + var/list/sent_alarms = list() + ///Our source atom + var/atom/source_atom + +/datum/alarm_handler/New(atom/source_atom) + if(istype(source_atom)) + src.source_atom = source_atom + else + var/source_type = (istype(source_atom, /datum)) ? source_atom.type : "" + stack_trace("a non atom was passed into alarm_handler! [source_atom] [source_type]") + return ..() + +/datum/alarm_handler/Destroy() + for(var/alarm_type in sent_alarms) + for(var/area/area_to_clear as anything in sent_alarms[alarm_type]) + //Yeet all connected alarms + clear_alarm_from_area(alarm_type, area_to_clear) + source_atom = null + return ..() + +///Sends an alarm to any interested things, does some checks to prevent unneeded work +///Important to note is that source_atom is not held as a ref, we're used as a proxy to prevent hard deletes +///optional_camera should only be used when you have one camera you want to pass along to alarm listeners, most of the time you should have no use for it +/datum/alarm_handler/proc/send_alarm(alarm_type, atom/use_as_source_atom, optional_camera) + if(!use_as_source_atom) + use_as_source_atom = source_atom + if(!use_as_source_atom) + return + + var/area/our_area = get_area(use_as_source_atom) + var/our_z_level = use_as_source_atom.z + + if (our_area.area_flags & NO_ALERTS) + return FALSE + + var/list/existing_alarms = sent_alarms[alarm_type] + if(existing_alarms) + if(our_area in existing_alarms) + return FALSE + else + sent_alarms[alarm_type] = list() + existing_alarms = sent_alarms[alarm_type] + + existing_alarms += our_area + + our_area.active_alarms[alarm_type] += 1 + + SEND_GLOBAL_SIGNAL(COMSIG_ALARM_FIRE(alarm_type), src, alarm_type, our_area, our_z_level, optional_camera) + + return TRUE + +///Clears an alarm from any interested listeners +/datum/alarm_handler/proc/clear_alarm(alarm_type, use_as_source_atom) + SIGNAL_HANDLER + if(!use_as_source_atom) + use_as_source_atom = source_atom + if(!use_as_source_atom) + return + + return clear_alarm_from_area(alarm_type, get_area(use_as_source_atom)) + +///Exists so we can request that the alarms from an area are cleared, even if our source atom is no longer in that area +/datum/alarm_handler/proc/clear_alarm_from_area(alarm_type, area/our_area) + if (our_area.area_flags & NO_ALERTS) + return FALSE + + var/list/existing_alarms = sent_alarms[alarm_type] + if(!existing_alarms) + return FALSE + + if(!(our_area in existing_alarms)) + return FALSE + + existing_alarms -= our_area + if(!length(existing_alarms)) + sent_alarms -= alarm_type + + our_area.active_alarms[alarm_type] -= 1 + if(!length(our_area.active_alarms)) + our_area.active_alarms -= alarm_type + + SEND_GLOBAL_SIGNAL(COMSIG_ALARM_CLEAR(alarm_type), src, alarm_type, our_area) + return TRUE + +/datum/alarm_listener + ///List of valid source z levels, ignored if null + var/list/allowed_z_levels + ///List of allowed areas. if this is null it's ignored + var/list/allowed_areas + + ///List of alarm type -> list of area name -> list(area, ref to area's cameras, list(sources)) + var/list/alarms = list() + ///Should we allow alarm changes to go through or not + var/accepting_alarm_changes = TRUE + +///Accepts a list of alarm types to pay attention to, a list of valid z levels, and a list of valid areas. areas and zlevels are ignored if null +/datum/alarm_listener/New(alarms_to_listen_for, allowed_z_levels, allowed_areas) + src.allowed_z_levels = allowed_z_levels + src.allowed_areas = allowed_areas + for(var/alarm_type in alarms_to_listen_for) + RegisterSignal(SSdcs, COMSIG_ALARM_FIRE(alarm_type), .proc/add_alarm) + RegisterSignal(SSdcs, COMSIG_ALARM_CLEAR(alarm_type), .proc/clear_alarm) + + return ..() + +///Adds an alarm to our alarms list, you shouldn't be calling this manually +///It should all be handled by the signal listening we do, unless you want to only send an alarm to one listener +/datum/alarm_listener/proc/add_alarm(datum/source, datum/alarm_handler/handler, alarm_type, area/source_area, source_z, optional_camera) + if (!accepting_alarm_changes) + return + + if(allowed_z_levels && !(source_z in allowed_z_levels)) + return + + if(allowed_areas && !(source_area.type in allowed_areas)) + return + + var/list/alarms_of_our_type = alarms[alarm_type] + if(!alarms_of_our_type) + alarms[alarm_type] = list() + alarms_of_our_type = alarms[alarm_type] + + if(alarms_of_our_type[source_area.name]) + var/list/alarm = alarms_of_our_type[source_area.name] + var/list/sources = alarm[3] + sources |= handler + //Return if a source already exists, we don't want to send a signal or add a new entry + return + + //We normally directly pass in a ref to the area's camera's list to prevent hanging refs + var/list/cameras = source_area.cameras + if(optional_camera) + cameras = list(optional_camera) // This will cause harddels, so we need to clear manually + RegisterSignal(optional_camera, COMSIG_PARENT_QDELETING, .proc/clear_camera_ref, override = TRUE) //It's just fine to override, cause we clear all refs in the proc + + //This does mean that only the first alarm of that camera type in the area will send a ping, but jesus what else can ya do + alarms_of_our_type[source_area.name] = list(source_area, cameras, list(handler)) + SEND_SIGNAL(src, COMSIG_ALARM_TRIGGERED, alarm_type, source_area) + +///Removes an alarm to our alarms list, you probably shouldn't be calling this manually +///It should all be handled by the signal listening we do, unless you want to only remove an alarm to one listener +/datum/alarm_listener/proc/clear_alarm(datum/source, datum/alarm_handler/handler, alarm_type, area/source_area) + if(!accepting_alarm_changes) + return + + var/list/alarms_of_our_type = alarms[alarm_type] + + if(!alarms_of_our_type) + return + + if(!alarms_of_our_type[source_area.name]) + return + + var/list/alarm = alarms_of_our_type[source_area.name] + var/list/sources = alarm[3] + sources -= handler + + if (length(sources)) + return //Return if there's still sources left, no sense clearing the list or bothering anyone about it + + alarms_of_our_type -= source_area.name + SEND_SIGNAL(src, COMSIG_ALARM_CLEARED, alarm_type, source_area) + +///Does what it says on the tin, exists for signal hooking +/datum/alarm_listener/proc/prevent_alarm_changes() + SIGNAL_HANDLER + accepting_alarm_changes = FALSE + +///Does what it says on the tin, exists for signal hooking +/datum/alarm_listener/proc/allow_alarm_changes() + SIGNAL_HANDLER + accepting_alarm_changes = TRUE + +///Used to manually clear camera refs if one is ref'd directly +/datum/alarm_listener/proc/clear_camera_ref(obj/machinery/camera/source) + SIGNAL_HANDLER + var/list/alarms_cache = alarms //Cache for sonic speec + for(var/alarm_type in alarms_cache) + var/list/alarms_of_type = alarms_cache[alarm_type] //Sonic cache speed forads + for(var/area_name as anything in alarms_of_type) + var/list/alarm_packet = alarms_of_type[area_name] + var/list/cameras = alarm_packet[2] + cameras -= source // REF FOUND AND CLEARED BOYSSSS diff --git a/code/datums/wires/airalarm.dm b/code/datums/wires/airalarm.dm index addacb315d..0b9fb450a1 100644 --- a/code/datums/wires/airalarm.dm +++ b/code/datums/wires/airalarm.dm @@ -47,8 +47,7 @@ A.mode = 1 // AALARM_MODE_SCRUB A.apply_mode() if(WIRE_ALARM) // Clear alarms. - var/area/AA = get_base_area(A) - if(AA.atmosalert(0, holder)) + if(A.alarm_manager.clear_alarm(ALARM_ATMOS)) A.post_alert(0) A.update_icon() @@ -69,7 +68,6 @@ A.mode = 3 // AALARM_MODE_PANIC A.apply_mode() if(WIRE_ALARM) // Post alarm. - var/area/AA = get_base_area(A) - if(AA.atmosalert(2, holder)) + if(A.alarm_manager.send_alarm(ALARM_ATMOS)) A.post_alert(2) A.update_icon() diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index 2bd3bfac9c..95322812ed 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -19,9 +19,11 @@ var/fire = FALSE ///How many fire alarm sources do we have? var/triggered_firealarms = 0 - ///Whether there is an atmos alarm in this area - var/atmosalm = FALSE - var/poweralm = FALSE + ///Alarm type to count of sources. Not usable for ^ because we handle fires differently + var/list/active_alarms = list() + ///We use this just for fire alarms, because they're area based right now so one alarm going poof shouldn't prevent you from clearing your alarms listing + var/datum/alarm_handler/alarm_manager + var/lightswitch = TRUE /// All beauty in this area combined, only includes indoor area. @@ -186,6 +188,8 @@ GLOBAL_LIST_EMPTY(teleportlocs) if (area_flags & UNIQUE_AREA) GLOB.areas_by_type[type] = src + alarm_manager = new(src) // just in case + if(!minimap_color) // goes in New() because otherwise it doesn't fucking work // generate one using the icon_state if(icon_state && icon_state != "unknown") @@ -324,86 +328,12 @@ GLOBAL_LIST_EMPTY(teleportlocs) A.power_environ = FALSE A.power_change() STOP_PROCESSING(SSobj, src) + QDEL_NULL(alarm_manager) return ..() /** - * Generate a power alert for this area - * - * Sends to all ai players, alert consoles, drones and alarm monitor programs in the world + * Try to close all the firedoors in the area */ -/area/proc/poweralert(set_alarm, obj/source) - if (area_flags & NO_ALERTS) - return - if (set_alarm != poweralm) - poweralm = set_alarm - if(istype(source)) //Only report power alarms on the z-level where the source is located. - for (var/item in GLOB.silicon_mobs) - var/mob/living/silicon/aiPlayer = item - if (set_alarm) - aiPlayer.triggerAlarm("Power", src, cameras, source) - else - aiPlayer.cancelAlarm("Power", src, source) - - for (var/item in GLOB.alert_consoles) - var/obj/machinery/computer/station_alert/a = item - if (set_alarm) - a.triggerAlarm("Power", src, cameras, source) - else - a.cancelAlarm("Power", src, source) - - for (var/item in GLOB.drones_list) - var/mob/living/simple_animal/drone/D = item - if (set_alarm) - D.triggerAlarm("Power", src, cameras, source) - else - D.cancelAlarm("Power", src, source) - for(var/item in GLOB.alarmdisplay) - var/datum/computer_file/program/alarm_monitor/p = item - if (set_alarm) - p.triggerAlarm("Power", src, cameras, source) - else - p.cancelAlarm("Power", src, source) - -/area/proc/atmosalert(danger_level, obj/source) - if (area_flags & NO_ALERTS) - return - if(danger_level != atmosalm) - if (danger_level==2) - - for (var/item in GLOB.silicon_mobs) - var/mob/living/silicon/aiPlayer = item - aiPlayer.triggerAlarm("Atmosphere", src, cameras, source) - for (var/item in GLOB.alert_consoles) - var/obj/machinery/computer/station_alert/a = item - a.triggerAlarm("Atmosphere", src, cameras, source) - for (var/item in GLOB.drones_list) - var/mob/living/simple_animal/drone/D = item - D.triggerAlarm("Atmosphere", src, cameras, source) - for(var/item in GLOB.alarmdisplay) - var/datum/computer_file/program/alarm_monitor/p = item - p.triggerAlarm("Atmosphere", src, cameras, source) - - else if (src.atmosalm == 2) - for (var/item in GLOB.silicon_mobs) - var/mob/living/silicon/aiPlayer = item - aiPlayer.cancelAlarm("Atmosphere", src, source) - for (var/item in GLOB.alert_consoles) - var/obj/machinery/computer/station_alert/a = item - a.cancelAlarm("Atmosphere", src, source) - for (var/item in GLOB.drones_list) - var/mob/living/simple_animal/drone/D = item - D.cancelAlarm("Atmosphere", src, source) - for(var/item in GLOB.alarmdisplay) - var/datum/computer_file/program/alarm_monitor/p = item - p.cancelAlarm("Atmosphere", src, source) - - atmosalm = danger_level - for(var/i in sub_areas) - var/area/A = i - A.atmosalm = danger_level - return TRUE - return FALSE - /area/proc/ModifyFiredoors(opening) if(firedoors) firedoors_last_closed_on = world.time @@ -429,19 +359,7 @@ GLOBAL_LIST_EMPTY(teleportlocs) if (!fire) set_fire_alarm_effects(TRUE) ModifyFiredoors(FALSE) - if (!(area_flags & NO_ALERTS)) //Check here instead at the start of the proc so that fire alarms can still work locally even in areas that don't send alerts - for (var/item in GLOB.alert_consoles) - var/obj/machinery/computer/station_alert/a = item - a.triggerAlarm("Fire", src, cameras, source) - for (var/item in GLOB.silicon_mobs) - var/mob/living/silicon/aiPlayer = item - aiPlayer.triggerAlarm("Fire", src, cameras, source) - for (var/item in GLOB.drones_list) - var/mob/living/simple_animal/drone/D = item - D.triggerAlarm("Fire", src, cameras, source) - for(var/item in GLOB.alarmdisplay) - var/datum/computer_file/program/alarm_monitor/p = item - p.triggerAlarm("Fire", src, cameras, source) + alarm_manager.send_alarm(ALARM_FIRE, source) START_PROCESSING(SSobj, src) @@ -460,35 +378,10 @@ GLOBAL_LIST_EMPTY(teleportlocs) if (should_reset_alarms) // if there's a source, make sure there's no fire alarms left set_fire_alarm_effects(FALSE) ModifyFiredoors(TRUE) - - if (!(area_flags & NO_ALERTS)) //Check here instead at the start of the proc so that fire alarms can still work locally even in areas that don't send alerts - for (var/item in GLOB.silicon_mobs) - var/mob/living/silicon/aiPlayer = item - aiPlayer.cancelAlarm("Fire", src, source) - for (var/item in GLOB.alert_consoles) - var/obj/machinery/computer/station_alert/a = item - a.cancelAlarm("Fire", src, source) - for (var/item in GLOB.drones_list) - var/mob/living/simple_animal/drone/D = item - D.cancelAlarm("Fire", src, source) - for(var/item in GLOB.alarmdisplay) - var/datum/computer_file/program/alarm_monitor/p = item - p.cancelAlarm("Fire", src, source) + alarm_manager.clear_alarm(ALARM_FIRE, source) STOP_PROCESSING(SSobj, src) -///Get rid of any dangling camera refs -/area/proc/clear_camera(obj/machinery/camera/cam) - LAZYREMOVE(cameras, cam) - for (var/mob/living/silicon/aiPlayer as anything in GLOB.silicon_mobs) - aiPlayer.freeCamera(src, cam) - for (var/obj/machinery/computer/station_alert/comp as anything in GLOB.alert_consoles) - comp.freeCamera(src, cam) - for (var/mob/living/simple_animal/drone/drone_on as anything in GLOB.drones_list) - drone_on.freeCamera(src, cam) - for(var/datum/computer_file/program/alarm_monitor/monitor as anything in GLOB.alarmdisplay) - monitor.freeCamera(src, cam) - /area/process() if(firedoors_last_closed_on + 100 < world.time) //every 10 seconds ModifyFiredoors(FALSE) @@ -506,14 +399,8 @@ GLOBAL_LIST_EMPTY(teleportlocs) //Trigger alarm effect set_fire_alarm_effects(TRUE) //Lockdown airlocks - for(var/obj/machinery/door/DOOR in get_sub_areas_contents(src)) - close_and_lock_door(DOOR) - - for (var/i in GLOB.silicon_mobs) - var/mob/living/silicon/SILICON = i - if(SILICON.triggerAlarm("Burglar", src, cameras, trigger)) - //Cancel silicon alert after 1 minute - addtimer(CALLBACK(SILICON, TYPE_PROC_REF(/mob/living/silicon, cancelAlarm),"Burglar",src,trigger), 600) + for(var/obj/machinery/door/door in get_sub_areas_contents(src)) + close_and_lock_door(door) /area/proc/set_fire_alarm_effects(boolean) fire = boolean diff --git a/code/game/area/areas/ruins/lavaland.dm b/code/game/area/areas/ruins/lavaland.dm index a0cfcf2c04..dd88c0f994 100644 --- a/code/game/area/areas/ruins/lavaland.dm +++ b/code/game/area/areas/ruins/lavaland.dm @@ -99,7 +99,6 @@ power_environ = FALSE power_equip = FALSE power_light = FALSE - poweralm = FALSE //ash walker nest /area/ruin/lavaland/unpowered/ash_walkers diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index b6ab1af366..ce9ab30f2f 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -41,6 +41,8 @@ var/upgrades = 0 var/internal_light = TRUE //Whether it can light up when an AI views it + ///Represents a signel source of camera alarms about movement or camera tampering + var/datum/alarm_handler/alarm_manager /obj/machinery/camera/preset/toxins //Bomb test site in space name = "Hardened Bomb-Test Camera" @@ -72,6 +74,8 @@ if(mapload && is_station_level(z) && prob(3) && !start_active) toggle_cam() + alarm_manager = new(src) + /obj/machinery/camera/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) for(var/i in network) network -= i @@ -83,7 +87,8 @@ GLOB.cameranet.cameras -= src cancelCameraAlarm() if(isarea(myarea)) - myarea.clear_camera(src) + LAZYREMOVE(myarea.cameras, src) + QDEL_NULL(alarm_manager) QDEL_NULL(assembly) if(bug) bug.bugged_cameras -= c_tag @@ -347,13 +352,11 @@ /obj/machinery/camera/proc/triggerCameraAlarm() alarm_on = TRUE - for(var/mob/living/silicon/S in GLOB.silicon_mobs) - S.triggerAlarm("Camera", get_area(src), list(src), src) + alarm_manager.send_alarm(ALARM_CAMERA, src, src) /obj/machinery/camera/proc/cancelCameraAlarm() alarm_on = FALSE - for(var/mob/living/silicon/S in GLOB.silicon_mobs) - S.cancelAlarm("Camera", get_area(src), src) + alarm_manager.clear_alarm(ALARM_CAMERA) /obj/machinery/camera/proc/can_use() if(!status) diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm index df0c7412bb..21d40d7b8e 100644 --- a/code/game/machinery/camera/motion.dm +++ b/code/game/machinery/camera/motion.dm @@ -52,21 +52,17 @@ cancelAlarm() /obj/machinery/camera/proc/cancelAlarm() - if (detectTime == -1) - for (var/i in GLOB.silicon_mobs) - var/mob/living/silicon/aiPlayer = i - if (status) - aiPlayer.cancelAlarm("Motion", get_area(src), src) + if (detectTime == -1 && status) + alarm_manager.clear_alarm(ALARM_MOTION) detectTime = 0 return TRUE /obj/machinery/camera/proc/triggerAlarm() if (!detectTime) return FALSE - for (var/mob/living/silicon/aiPlayer in GLOB.player_list) - if (status) - aiPlayer.triggerAlarm("Motion", get_area(src), list(src), src) - visible_message("A red light flashes on the [src]!") + if(status) + if(alarm_manager.send_alarm(ALARM_MOTION, src, src)) + visible_message(span_warning("A red light flashes on the [src]!")) detectTime = -1 return TRUE diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm index df84d5a26a..8810c79932 100644 --- a/code/game/machinery/computer/station_alert.dm +++ b/code/game/machinery/computer/station_alert.dm @@ -4,16 +4,17 @@ icon_screen = "alert:0" icon_keyboard = "atmos_key" circuit = /obj/item/circuitboard/computer/stationalert - var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list()) + ///Listens for alarms, provides the alarms list for our ui + var/datum/alarm_listener/listener light_color = LIGHT_COLOR_CYAN /obj/machinery/computer/station_alert/Initialize(mapload) - . = ..() - GLOB.alert_consoles += src + listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), list(z)) + return ..() /obj/machinery/computer/station_alert/Destroy() - GLOB.alert_consoles -= src + QDEL_NULL(listener) return ..() /obj/machinery/computer/station_alert/ui_interact(mob/user, datum/tgui/ui) @@ -26,69 +27,19 @@ var/list/data = list() data["alarms"] = list() - for(var/class in alarms) - data["alarms"][class] = list() - for(var/area in alarms[class]) - data["alarms"][class] += area + var/list/alarms = listener.alarms + for(var/alarm_type in alarms) + data["alarms"][alarm_type] = list() + for(var/area_name in alarms[alarm_type]) + data["alarms"][alarm_type] += area_name return data -/obj/machinery/computer/station_alert/proc/triggerAlarm(class, area/home, cameras, obj/source) - if(source.z != z) - return - if(stat & (BROKEN)) - return - - var/list/our_sort = alarms[class] - for(var/areaname in our_sort) - if (areaname == home.name) - var/list/alarm = our_sort[areaname] - var/list/sources = alarm[3] - if (!(source in sources)) - sources += source - return TRUE - - var/obj/machinery/camera/cam = null - var/list/our_cams = null - if(cameras && islist(cameras)) - our_cams = cameras - if (our_cams.len == 1) - cam = our_cams[1] - else if(cameras && istype(cameras, /obj/machinery/camera)) - cam = cameras - our_sort[home.name] = list(home, (cam ? cam : cameras), list(source)) - return TRUE - -/obj/machinery/computer/station_alert/proc/freeCamera(area/home, obj/machinery/camera/cam) - for(var/class in alarms) - var/our_area = alarms[class][home.name] - if(!our_area) - continue - var/cams = our_area[2] //Get the cameras - if(!cams) - continue - if(islist(cams)) - cams -= cam - if(length(cams) == 1) - our_area[2] = cams[1] - else - our_area[2] = null - -/obj/machinery/computer/station_alert/proc/cancelAlarm(class, area/A, obj/origin) - if(stat & (BROKEN)) - return - var/list/L = alarms[class] - var/cleared = 0 - for (var/I in L) - if (I == A.name) - var/list/alarm = L[I] - var/list/srcs = alarm[3] - if (origin in srcs) - srcs -= origin - if (srcs.len == 0) - cleared = 1 - L -= I - return !cleared +/obj/machinery/computer/station_alert/on_machine_stat_update(stat) + if(stat & BROKEN) + listener.prevent_alarm_changes() + else + listener.allow_alarm_changes() /obj/machinery/computer/station_alert/update_overlays() . = ..() @@ -98,12 +49,7 @@ . |= "[icon_keyboard]_off" return . |= icon_keyboard - var/active_alarms = FALSE - for(var/cat in alarms) - if(length(alarms[cat])) - active_alarms = TRUE - break - if(active_alarms) + if(length(listener.alarms)) overlay_state = "alert:2" else overlay_state = "alert:0" diff --git a/code/game/objects/structures/displaycase.dm b/code/game/objects/structures/displaycase.dm index 12f1d1a367..b6bd0734cb 100644 --- a/code/game/objects/structures/displaycase.dm +++ b/code/game/objects/structures/displaycase.dm @@ -19,6 +19,8 @@ var/start_showpiece_type = null //add type for items on display var/list/start_showpieces = list() //Takes sublists in the form of list("type" = /obj/item/bikehorn, "trophy_message" = "henk") var/trophy_message = "" + ///Represents a signel source of screaming when broken + var/datum/alarm_handler/alarm_manager /obj/structure/displaycase/Initialize(mapload) . = ..() @@ -31,20 +33,20 @@ if(start_showpiece_type) showpiece = new start_showpiece_type (src) update_icon() + alarm_manager = new(src) /obj/structure/displaycase/Destroy() - if(electronics) - QDEL_NULL(electronics) - if(showpiece) - QDEL_NULL(showpiece) + QDEL_NULL(electronics) + QDEL_NULL(showpiece) + QDEL_NULL(alarm_manager) return ..() /obj/structure/displaycase/examine(mob/user) . = ..() if(alert) - . += "Hooked up with an anti-theft system." + . += span_notice("Hooked up with an anti-theft system.") if(showpiece) - . += "There's [showpiece] inside." + . += span_notice("There's [showpiece] inside.") if(trophy_message) . += "The plaque reads:" . += trophy_message @@ -79,12 +81,17 @@ update_icon() trigger_alarm() +///Anti-theft alarm triggered when broken. /obj/structure/displaycase/proc/trigger_alarm() - //Activate Anti-theft - if(alert) - var/area/alarmed = get_base_area(src) - alarmed.burglaralert(src) - playsound(src, 'sound/effects/alert.ogg', 50, 1) + if(!alert) + return + var/area/alarmed = get_area(src) + alarmed.burglaralert(src) + + alarm_manager.send_alarm(ALARM_BURGLAR) + addtimer(CALLBACK(alarm_manager, /datum/alarm_handler/proc/clear_alarm, ALARM_BURGLAR), 1 MINUTES) + + playsound(src, 'sound/effects/alert.ogg', 50, TRUE) /obj/structure/displaycase/update_icon_state() var/icon/I @@ -104,46 +111,46 @@ /obj/structure/displaycase/attackby(obj/item/W, mob/user, params) if(W.GetID() && !broken && openable) if(allowed(user)) - to_chat(user, "You [open ? "close":"open"] [src].") + to_chat(user, span_notice("You [open ? "close":"open"] [src].")) toggle_lock(user) else - to_chat(user, "Access denied.") + to_chat(user, span_alert("Access denied.")) else if(W.tool_behaviour == TOOL_WELDER && user.a_intent == INTENT_HELP && !broken) if(obj_integrity < max_integrity) if(!W.tool_start_check(user, amount=5)) return - to_chat(user, "You begin repairing [src].") + to_chat(user, span_notice("You begin repairing [src].")) if(W.use_tool(src, user, 40, amount=5, volume=50)) obj_integrity = max_integrity update_icon() - to_chat(user, "You repair [src].") + to_chat(user, span_notice("You repair [src].")) else - to_chat(user, "[src] is already in good condition!") + to_chat(user, span_warning("[src] is already in good condition!")) return else if(!alert && W.tool_behaviour == TOOL_CROWBAR && openable) //Only applies to the lab cage and player made display cases if(broken) if(showpiece) - to_chat(user, "Remove the displayed object first.") + to_chat(user, span_notice("Remove the displayed object first.")) else - to_chat(user, "You remove the destroyed case") + to_chat(user, span_notice("You remove the destroyed case")) qdel(src) else - to_chat(user, "You start to [open ? "close":"open"] [src].") + to_chat(user, span_notice("You start to [open ? "close":"open"] [src].")) if(W.use_tool(src, user, 20)) - to_chat(user, "You [open ? "close":"open"] [src].") + to_chat(user, span_notice("You [open ? "close":"open"] [src].")) toggle_lock(user) else if(open && !showpiece) if(user.transferItemToLoc(W, src)) showpiece = W - to_chat(user, "You put [W] on display") + to_chat(user, span_notice("You put [W] on display")) update_icon() else if(istype(W, /obj/item/stack/sheet/glass) && broken) var/obj/item/stack/sheet/glass/G = W if(G.get_amount() < 2) - to_chat(user, "You need two glass sheets to fix the case!") + to_chat(user, span_warning("You need two glass sheets to fix the case!")) return - to_chat(user, "You start fixing [src]...") + to_chat(user, span_notice("You start fixing [src]...")) if(do_after(user, 20, target = src)) G.use(2) broken = 0 @@ -161,7 +168,7 @@ /obj/structure/displaycase/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags) if (showpiece && (broken || open)) - to_chat(user, "You deactivate the hover field built into the case.") + to_chat(user, span_notice("You deactivate the hover field built into the case.")) log_combat(user, src, "deactivates the hover field of") dump() src.add_fingerprint(user) @@ -171,7 +178,7 @@ //prevents remote "kicks" with TK if (!Adjacent(user)) return - user.visible_message("[user] kicks the display case.", null, null, COMBAT_MESSAGE_RANGE) + user.visible_message(span_danger("[user] kicks the display case."), null, null, COMBAT_MESSAGE_RANGE) log_combat(user, src, "kicks") user.do_attack_animation(src, ATTACK_EFFECT_KICK) take_damage(2) @@ -188,7 +195,7 @@ /obj/structure/displaycase_chassis/attackby(obj/item/I, mob/user, params) if(I.tool_behaviour == TOOL_WRENCH) //The player can only deconstruct the wooden frame - to_chat(user, "You start disassembling [src]...") + to_chat(user, span_notice("You start disassembling [src]...")) I.play_tool_sound(src) if(I.use_tool(src, user, 30)) playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) @@ -196,18 +203,18 @@ qdel(src) else if(istype(I, /obj/item/electronics/airlock)) - to_chat(user, "You start installing the electronics into [src]...") + to_chat(user, span_notice("You start installing the electronics into [src]...")) I.play_tool_sound(src) if(do_after(user, 30, target = src) && user.transferItemToLoc(I,src)) electronics = I - to_chat(user, "You install the airlock electronics.") + to_chat(user, span_notice("You install the airlock electronics.")) else if(istype(I, /obj/item/stack/sheet/glass)) var/obj/item/stack/sheet/glass/G = I if(G.get_amount() < 10) - to_chat(user, "You need ten glass sheets to do this!") + to_chat(user, span_warning("You need ten glass sheets to do this!")) return - to_chat(user, "You start adding [G] to [src]...") + to_chat(user, span_notice("You start adding [G] to [src]...")) if(do_after(user, 20, target = src)) G.use(10) var/obj/structure/displaycase/display = new(src.loc) @@ -272,11 +279,11 @@ is_locked = !is_locked to_chat(user, "You [!is_locked ? "un" : ""]lock the case.") else - to_chat(user, "The lock is stuck shut!") + to_chat(user, span_danger("The lock is stuck shut!")) return if(is_locked) - to_chat(user, "The case is shut tight with an old fashioned physical lock. Maybe you should ask the curator for the key?") + to_chat(user, span_danger("The case is shut tight with an old fashioned physical lock. Maybe you should ask the curator for the key?")) return if(!added_roundstart) @@ -284,12 +291,12 @@ return if(is_type_in_typecache(W, GLOB.blacklisted_cargo_types)) - to_chat(user, "The case rejects the [W].") + to_chat(user, span_danger("The case rejects the [W].")) return for(var/a in W.GetAllContents()) if(is_type_in_typecache(a, GLOB.blacklisted_cargo_types)) - to_chat(user, "The case rejects the [W].") + to_chat(user, span_danger("The case rejects the [W].")) return if(user.transferItemToLoc(W, src)) @@ -319,14 +326,14 @@ return TRUE else - to_chat(user, "\The [W] is stuck to your hand, you can't put it in the [src.name]!") + to_chat(user, span_warning("\The [W] is stuck to your hand, you can't put it in the [src.name]!")) return /obj/structure/displaycase/trophy/dump() if (showpiece) if(added_roundstart) - visible_message("The [showpiece] crumbles to dust!") + visible_message(span_danger("The [showpiece] crumbles to dust!")) new /obj/effect/decal/cleanable/ash(loc) QDEL_NULL(showpiece) else @@ -411,32 +418,32 @@ switch(action) if("Buy") if(!showpiece) - to_chat(usr, "There's nothing for sale.") + to_chat(usr, span_notice("There's nothing for sale.")) return TRUE if(broken) - to_chat(usr, "[src] appears to be broken.") + to_chat(usr, span_notice("[src] appears to be broken.")) return TRUE if(!payments_acc) - to_chat(usr, "[src] hasn't been registered yet.") + to_chat(usr, span_notice("[src] hasn't been registered yet.")) return TRUE if(!usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) return TRUE if(!potential_acc) - to_chat(usr, "No ID card detected.") + to_chat(usr, span_notice("No ID card detected.")) return var/datum/bank_account/account = potential_acc.registered_account if(!account) - to_chat(usr, "[potential_acc] has no account registered!") + to_chat(usr, span_notice("[potential_acc] has no account registered!")) return if(!account.has_money(sale_price)) - to_chat(usr, "You do not possess the funds to purchase this.") + to_chat(usr, span_notice("You do not possess the funds to purchase this.")) return TRUE else account.adjust_money(-sale_price) if(payments_acc) payments_acc.adjust_money(sale_price) usr.put_in_hands(showpiece) - to_chat(usr, "You purchase [showpiece] for [sale_price] credits.") + to_chat(usr, span_notice("You purchase [showpiece] for [sale_price] credits.")) // playsound(src, 'sound/effects/cashregister.ogg', 40, TRUE) icon = 'icons/obj/stationobjs.dmi' flick("laserbox_vend", src) @@ -446,7 +453,7 @@ return TRUE if("Open") if(!payments_acc) - to_chat(usr, "[src] hasn't been registered yet.") + to_chat(usr, span_notice("[src] hasn't been registered yet.")) return TRUE if(!potential_acc || !potential_acc.registered_account) return @@ -472,14 +479,14 @@ var/new_price_input = input(usr,"Set the sale price for this vend-a-tray.","new price",0) as num|null if(isnull(new_price_input) || (payments_acc != potential_acc.registered_account)) - to_chat(usr, "[src] rejects your new price.") + to_chat(usr, span_warning("[src] rejects your new price.")) return if(!usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) ) - to_chat(usr, "You need to get closer!") + to_chat(usr, span_warning("You need to get closer!")) return new_price_input = clamp(round(new_price_input, 1), 10, 1000) sale_price = new_price_input - to_chat(usr, "The cost is now set to [sale_price].") + to_chat(usr, span_notice("The cost is now set to [sale_price].")) SStgui.update_uis(src) return TRUE . = TRUE @@ -488,7 +495,7 @@ //Card Registration var/obj/item/card/id/potential_acc = I if(!potential_acc.registered_account) - to_chat(user, "This ID card has no account registered!") + to_chat(user, span_warning("This ID card has no account registered!")) return if(payments_acc == potential_acc.registered_account) playsound(src, 'sound/machines/click.ogg', 20, TRUE) @@ -503,7 +510,7 @@ /obj/structure/displaycase/forsale/multitool_act(mob/living/user, obj/item/I) . = ..() if(obj_integrity <= (integrity_failure * max_integrity)) - to_chat(user, "You start recalibrating [src]'s hover field...") + to_chat(user, span_notice("You start recalibrating [src]'s hover field...")) if(do_after(user, 20, target = src)) broken = 0 obj_integrity = max_integrity @@ -514,34 +521,34 @@ . = ..() if(open && user.a_intent == INTENT_HELP ) if(anchored) - to_chat(user, "You start unsecuring [src]...") + to_chat(user, span_notice("You start unsecuring [src]...")) else - to_chat(user, "You start securing [src]...") + to_chat(user, span_notice("You start securing [src]...")) if(I.use_tool(src, user, 16, volume=50)) if(QDELETED(I)) return if(anchored) - to_chat(user, "You unsecure [src].") + to_chat(user, span_notice("You unsecure [src].")) else - to_chat(user, "You secure [src].") + to_chat(user, span_notice("You secure [src].")) anchored = !anchored return else if(!open && user.a_intent == INTENT_HELP) - to_chat(user, "[src] must be open to move it.") + to_chat(user, span_notice("[src] must be open to move it.")) return /obj/structure/displaycase/forsale/emag_act(mob/user) . = ..() payments_acc = null req_access = list() - to_chat(user, "[src]'s card reader fizzles and smokes, and the account owner is reset.") + to_chat(user, span_warning("[src]'s card reader fizzles and smokes, and the account owner is reset.")) /obj/structure/displaycase/forsale/examine(mob/user) . = ..() if(showpiece && !open) - . += "[showpiece] is for sale for [sale_price] credits." + . += span_notice("[showpiece] is for sale for [sale_price] credits.") if(broken) - . += "[src] is sparking and the hover field generator seems to be overloaded. Use a multitool to fix it." + . += span_notice("[src] is sparking and the hover field generator seems to be overloaded. Use a multitool to fix it.") /obj/structure/displaycase/forsale/obj_break(damage_flag) if(!broken && !(flags_1 & NODECONSTRUCT_1)) diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm index abfb7ca899..0e36d8dfdd 100644 --- a/code/modules/atmospherics/machinery/airalarm.dm +++ b/code/modules/atmospherics/machinery/airalarm.dm @@ -90,6 +90,9 @@ var/frequency = FREQ_ATMOS_CONTROL var/alarm_frequency = FREQ_ATMOS_ALARMS var/datum/radio_frequency/radio_connection + ///Represents a signel source of atmos alarms, complains to all the listeners if one of our thresholds is violated + var/datum/alarm_handler/alarm_manager + var/list/TLV = list( // Breathable air. "pressure" = new/datum/tlv(ONE_ATMOSPHERE * 0.8, ONE_ATMOSPHERE* 0.9, ONE_ATMOSPHERE * 1.1, ONE_ATMOSPHERE * 1.2), // kPa "temperature" = new/datum/tlv(T0C, T0C+10, T0C+40, T0C+66), @@ -231,6 +234,7 @@ if(name == initial(name)) name = "[get_area_name(src, get_base_area = TRUE)] Air Alarm" + alarm_manager = new(src) power_change() set_frequency(frequency) register_context() @@ -242,10 +246,8 @@ /obj/machinery/airalarm/Destroy() SSradio.remove_object(src, frequency) - qdel(wires) - wires = null - var/area/ourarea = get_area(src) - ourarea.atmosalert(FALSE, src) + QDEL_NULL(wires) + QDEL_NULL(alarm_manager) return ..() /obj/machinery/airalarm/examine(mob/user) @@ -289,7 +291,7 @@ ) var/area/A = get_base_area(src) - data["atmos_alarm"] = A.atmosalm + data["atmos_alarm"] = !!A.active_alarms[ALARM_ATMOS] data["fire_alarm"] = A.fire var/turf/T = get_turf(src) @@ -464,13 +466,11 @@ apply_mode() . = TRUE if("alarm") - var/area/A = get_base_area(src) - if(A.atmosalert(2, src)) + if(alarm_manager.send_alarm(ALARM_ATMOS)) post_alert(2) . = TRUE if("reset") - var/area/A = get_base_area(src) - if(A.atmosalert(0, src)) + if(alarm_manager.clear_alarm(ALARM_ATMOS)) post_alert(0) . = TRUE update_icon() @@ -706,8 +706,8 @@ . = ..() SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) var/overlay_state = AALARM_OVERLAY_OFF - var/area/A = get_base_area(src) - switch(max(danger_level, A.atmosalm)) + var/area/our_area = get_base_area(src) + switch(max(danger_level, !!our_area.active_alarms[ALARM_ATMOS])) if(0) overlay_state = AALARM_OVERLAY_GREEN light_color = LIGHT_COLOR_GREEN @@ -788,12 +788,17 @@ /obj/machinery/airalarm/proc/apply_danger_level() var/area/A = get_base_area(src) - var/new_area_danger_level = 0 for(var/obj/machinery/airalarm/AA in A) if (!(AA.stat & (NOPOWER|BROKEN)) && !AA.shorted) - new_area_danger_level = max(new_area_danger_level,AA.danger_level) - if(A.atmosalert(new_area_danger_level,src)) //if area was in normal state or if area was in alert state + new_area_danger_level = clamp(max(new_area_danger_level, AA.danger_level), 0, 1) + + var/did_anything_happen + if(new_area_danger_level) + did_anything_happen = alarm_manager.send_alarm(ALARM_ATMOS) + else + did_anything_happen = alarm_manager.clear_alarm(ALARM_ATMOS) + if(did_anything_happen) //if something actually changed post_alert(new_area_danger_level) update_icon() diff --git a/code/modules/awaymissions/mission_code/moonoutpost19.dm b/code/modules/awaymissions/mission_code/moonoutpost19.dm index c0af9cd08c..7709098405 100644 --- a/code/modules/awaymissions/mission_code/moonoutpost19.dm +++ b/code/modules/awaymissions/mission_code/moonoutpost19.dm @@ -23,7 +23,6 @@ power_environ = FALSE power_equip = FALSE power_light = FALSE - poweralm = FALSE ambientsounds = list('sound/ambience/ambimine.ogg') icon_state = "awaycontent5" @@ -33,7 +32,6 @@ power_environ = FALSE power_equip = FALSE power_light = FALSE - poweralm = FALSE icon_state = "awaycontent6" //Papers diff --git a/code/modules/awaymissions/mission_code/undergroundoutpost45.dm b/code/modules/awaymissions/mission_code/undergroundoutpost45.dm index 4153032024..7ec7d7728f 100644 --- a/code/modules/awaymissions/mission_code/undergroundoutpost45.dm +++ b/code/modules/awaymissions/mission_code/undergroundoutpost45.dm @@ -36,4 +36,3 @@ power_environ = FALSE power_equip = FALSE power_light = FALSE - poweralm = FALSE diff --git a/code/modules/mapping/space_management/traits.dm b/code/modules/mapping/space_management/traits.dm index 9ba8d96d5e..cd53a3632d 100644 --- a/code/modules/mapping/space_management/traits.dm +++ b/code/modules/mapping/space_management/traits.dm @@ -1,4 +1,4 @@ -// Look up levels[z].traits[trait] +/// Look up levels[z].traits[trait] /datum/controller/subsystem/mapping/proc/level_trait(z, trait) if (!isnum(z) || z < 1) return null @@ -15,21 +15,21 @@ return list() return default[z][DL_TRAITS][trait] -// Check if levels[z] has any of the specified traits +/// Check if levels[z] has any of the specified traits /datum/controller/subsystem/mapping/proc/level_has_any_trait(z, list/traits) for (var/I in traits) if (level_trait(z, I)) return TRUE return FALSE -// Check if levels[z] has all of the specified traits +/// Check if levels[z] has all of the specified traits /datum/controller/subsystem/mapping/proc/level_has_all_traits(z, list/traits) for (var/I in traits) if (!level_trait(z, I)) return FALSE return TRUE -// Get a list of all z which have the specified trait +/// Get a list of all z which have the specified trait /datum/controller/subsystem/mapping/proc/levels_by_trait(trait) . = list() var/list/_z_list = z_list @@ -38,7 +38,7 @@ if (S.traits[trait]) . += S.z_value -// Get a list of all z which have any of the specified traits +/// Get a list of all z which have any of the specified traits /datum/controller/subsystem/mapping/proc/levels_by_any_trait(list/traits) . = list() var/list/_z_list = z_list @@ -49,7 +49,7 @@ . += S.z_value break -// Attempt to get the turf below the provided one according to Z traits +/// Attempt to get the turf below the provided one according to Z traits /datum/controller/subsystem/mapping/proc/get_turf_below(turf/T) if (!T) return @@ -58,7 +58,7 @@ return return locate(T.x, T.y, T.z + offset) -// Attempt to get the turf above the provided one according to Z traits +/// Attempt to get the turf above the provided one according to Z traits /datum/controller/subsystem/mapping/proc/get_turf_above(turf/T) if (!T) return @@ -67,7 +67,7 @@ return return locate(T.x, T.y, T.z + offset) -// Prefer not to use this one too often +/// Prefer not to use this one too often /datum/controller/subsystem/mapping/proc/get_station_center() var/station_z = levels_by_trait(ZTRAIT_STATION)[1] return locate(round(world.maxx * 0.5, 1), round(world.maxy * 0.5, 1), station_z) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 2c8e3164a9..534a557b82 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -37,7 +37,6 @@ var/aiRestorePowerRoutine = 0 var/requires_power = POWER_REQ_ALL var/can_be_carded = TRUE - var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list(), "Burglar"=list()) var/viewalerts = 0 var/icon/holo_icon //Female is assigned when AI is created. var/obj/controlled_equipment //A piece of equipment, to determine whether to relaymove or use the AI eye. @@ -101,9 +100,8 @@ var/emote_display = "Neutral" //text string of the current emote we set for the status displays, to prevent logins resetting it. var/datum/robot_control/robot_control - // TODO: Currently unused, needs port from TG. - /// Station alert datum for showing alerts UI - var/datum/station_alert/alert_control + ///Alarm listener datum, handes caring about alarm events and such + var/datum/alarm_listener/listener ///remember AI's last location var/atom/lastloc interaction_range = INFINITY @@ -185,6 +183,9 @@ builtInCamera = new (src) builtInCamera.network = list("ss13") + listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z)) + RegisterSignal(listener, COMSIG_ALARM_TRIGGERED, .proc/alarm_triggered) + RegisterSignal(listener, COMSIG_ALARM_CLEARED, .proc/alarm_cleared) /mob/living/silicon/ai/Destroy() GLOB.ai_list -= src @@ -203,7 +204,7 @@ QDEL_NULL(malf_picker) QDEL_NULL(doomsday_device) QDEL_NULL(robot_control) - // QDEL_NULL(alert_control) + QDEL_NULL(listener) QDEL_NULL(aiMulti) QDEL_NULL(aiPDA) malfhack = null @@ -257,12 +258,12 @@ /mob/living/silicon/ai/get_status_tab_items() . = ..() if(stat != CONSCIOUS) - . += text("Systems nonfunctional") + . += "Systems nonfunctional" return - . += text("System integrity: [(health + 100) * 0.5]%") + . += "System integrity: [(health + 100) * 0.5]%" if(isturf(loc)) //only show if we're "in" a core . += "Backup Power: [battery * 0.5]%" - . += text("Connected cyborgs: [length(connected_robots)]") + . += "Connected cyborgs: [length(connected_robots)]" for(var/r in connected_robots) var/mob/living/silicon/robot/connected_robot = r var/robot_status = "Nominal" @@ -273,19 +274,20 @@ else if(!connected_robot.cell || connected_robot.cell.charge <= 0) robot_status = "DEPOWERED" //Name, Health, Battery, Module, Area, and Status! Everything an AI wants to know about its borgies! - . += text("[connected_robot.name] | S.Integrity: [connected_robot.health]% | Cell: [connected_robot.cell ? "[connected_robot.cell.charge]/[connected_robot.cell.maxcharge]" : "Empty"] | \ - Module: [connected_robot.designation] | Loc: [get_area_name(connected_robot, TRUE)] | Status: [robot_status]") - . += text("AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]") //Count of total AI shells + . += "[connected_robot.name] | S.Integrity: [connected_robot.health]% | Cell: [connected_robot.cell ? "[connected_robot.cell.charge]/[connected_robot.cell.maxcharge]" : "Empty"] | \ + Module: [connected_robot.designation] | Loc: [get_area_name(connected_robot, TRUE)] | Status: [robot_status]" + . += "AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]" //Count of total AI shells /mob/living/silicon/ai/proc/ai_alerts() var/dat = "Current Station Alerts\n" dat += "Close

" - for (var/cat in alarms) - dat += text("[]
\n", cat) - var/list/L = alarms[cat] - if (L.len) - for (var/alarm in L) - var/list/alm = L[alarm] + var/list/alarms = listener.alarms + for (var/alarm_type in alarms) + dat += "[alarm_type]
\n" + var/list/alerts = alarms[alarm_type] + if (length(alerts)) + for (var/alarm in alerts) + var/list/alm = alerts[alarm] var/area/A = alm[1] var/C = alm[2] var/list/sources = alm[3] @@ -293,20 +295,19 @@ if (C && istype(C, /list)) var/dat2 = "" for (var/obj/machinery/camera/I in C) - dat2 += text("[][]", (dat2=="") ? "" : " | ", I.c_tag) - dat += text("-- [] ([])", A.name, (dat2!="") ? dat2 : "No Camera") + dat2 += "[(dat2=="") ? "" : " | "][I.c_tag]" + dat += "-- [A.name] ([(dat2!="") ? dat2 : "No Camera"])" else if (C && istype(C, /obj/machinery/camera)) var/obj/machinery/camera/Ctmp = C - dat += text("-- [] ([])", A.name, Ctmp.c_tag) + dat += "-- [A.name] ([Ctmp.c_tag])" else - dat += text("-- [] (No Camera)", A.name) + dat += "-- [A.name] (No Camera)" if (sources.len > 1) - dat += text("- [] sources", sources.len) + dat += "- [sources.len] sources" dat += "
\n" else dat += "-- All Systems Nominal
\n" dat += "
\n" - viewalerts = 1 src << browse(dat, "window=aialerts&can_close=0") @@ -439,7 +440,7 @@ if (href_list["mach_close"]) if (href_list["mach_close"] == "aialerts") viewalerts = 0 - var/t1 = text("window=[]", href_list["mach_close"]) + var/t1 = "window=[href_list["mach_close"]]" unset_machine() src << browse(null, t1) if (href_list["switchcamera"]) @@ -553,77 +554,33 @@ bot.call_bot(src, waypoint) call_bot_cooldown = 0 -/mob/living/silicon/ai/triggerAlarm(class, area/home, cameras, obj/source) - if(source.z != z) - return - var/list/our_sort = alarms[class] - for(var/areaname in our_sort) - if (areaname == home.name) - var/list/alarm = our_sort[areaname] - var/list/sources = alarm[3] - if (!(source in sources)) - sources += source - return TRUE +/mob/living/silicon/ai/proc/alarm_triggered(datum/source, alarm_type, area/source_area) + SIGNAL_HANDLER + var/list/cameras = source_area.cameras + var/home_name = source_area.name - var/obj/machinery/camera/cam = null - var/list/our_cams = null - if(cameras && islist(cameras)) - our_cams = cameras - if (our_cams.len == 1) - cam = our_cams[1] - else if(cameras && istype(cameras, /obj/machinery/camera)) - cam = cameras - our_sort[home.name] = list(home, (cam ? cam : cameras), list(source)) - - if (cameras) - if (cam?.can_use()) - queueAlarm("--- [class] alarm detected in [home.name]! ([cam.c_tag])", class) - else if (our_cams?.len) - var/foo = 0 - var/dat2 = "" - for (var/obj/machinery/camera/I in our_cams) - dat2 += text("[][]", (!foo) ? "" : " | ", I.c_tag) //I'm not fixing this shit... - foo = 1 - queueAlarm(text ("--- [] alarm detected in []! ([])", class, home.name, dat2), class) + if (length(cameras)) + var/obj/machinery/camera/cam = cameras[1] + if (cam.can_use()) + queueAlarm("--- [alarm_type] alarm detected in [home_name]! ([cam.c_tag])", alarm_type) else - queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, home.name), class) + var/first_run = FALSE + var/dat2 = "" + for (var/obj/machinery/camera/camera as anything in cameras) + dat2 += "[(!first_run) ? "" : " | "][camera.c_tag]" + first_run = TRUE + queueAlarm("--- [alarm_type] alarm detected in [home_name]! ([dat2])", alarm_type) else - queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, home.name), class) + queueAlarm("--- [alarm_type] alarm detected in [home_name]! (No Camera)", alarm_type) if (viewalerts) ai_alerts() return TRUE -/mob/living/silicon/ai/freeCamera(area/home, obj/machinery/camera/cam) - for(var/class in alarms) - var/our_area = alarms[class][home.name] - if(!our_area) - continue - var/cams = our_area[2] //Get the cameras - if(!cams) - continue - if(islist(cams)) - cams -= cam - if(length(cams) == 1) - our_area[2] = cams[1] - else - our_area[2] = null - -/mob/living/silicon/ai/cancelAlarm(class, area/A, obj/origin) - var/list/L = alarms[class] - var/cleared = 0 - for (var/I in L) - if (I == A.name) - var/list/alarm = L[I] - var/list/srcs = alarm[3] - if (origin in srcs) - srcs -= origin - if (srcs.len == 0) - cleared = 1 - L -= I - if (cleared) - queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0) - if (viewalerts) ai_alerts() - return !cleared +/mob/living/silicon/ai/proc/alarm_cleared(datum/source, alarm_type, area/source_area) + SIGNAL_HANDLER + queueAlarm("--- [alarm_type] alarm in [source_area.name] has been cleared.", alarm_type, 0) + if(viewalerts) + ai_alerts() //Replaces /mob/living/silicon/ai/verb/change_network() in ai.dm & camera.dm //Adds in /mob/living/silicon/ai/proc/ai_network_change() instead diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 44e8a41474..7b2e3103dc 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -89,6 +89,12 @@ diag_hud_set_borgcell() logevent("System brought online.") + listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z)) + RegisterSignal(listener, COMSIG_ALARM_TRIGGERED, .proc/alarm_triggered) + RegisterSignal(listener, COMSIG_ALARM_CLEARED, .proc/alarm_cleared) + listener.RegisterSignal(src, COMSIG_LIVING_PREDEATH, /datum/alarm_listener/proc/prevent_alarm_changes) + listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, /datum/alarm_listener/proc/allow_alarm_changes) + add_verb(src, /mob/living/proc/lay_down) //CITADEL EDIT gimmie rest verb kthx add_verb(src, /mob/living/silicon/robot/proc/rest_style) @@ -116,8 +122,7 @@ ghostize() stack_trace("Borg MMI lacked a brainmob") mmi = null - if(modularInterface) - QDEL_NULL(modularInterface) + QDEL_NULL(modularInterface) if(connected_ai) set_connected_ai(null) if(shell) //??? why would you give an ai radio keys? @@ -132,14 +137,16 @@ QDEL_NULL(inv1) QDEL_NULL(inv2) QDEL_NULL(inv3) + QDEL_NULL(spark_system) + QDEL_NULL(listener) cell = null return ..() -// /mob/living/silicon/robot/Topic(href, href_list) -// . = ..() -// //Show alerts window if user clicked on "Show alerts" in chat -// if (href_list["showalerts"]) -// robot_alerts() +/mob/living/silicon/robot/Topic(href, href_list) + . = ..() + //Show alerts window if user clicked on "Show alerts" in chat + if (href_list["showalerts"]) + robot_alerts() /mob/living/silicon/robot/proc/pick_module() if(module.type != /obj/item/robot_module) @@ -203,15 +210,16 @@ /mob/living/silicon/robot/proc/robot_alerts() var/dat = "" - for (var/cat in alarms) - dat += text("[cat]
\n") - var/list/L = alarms[cat] - if (L.len) - for (var/alarm in L) - var/list/alm = L[alarm] + var/list/alarms = listener.alarms + for (var/alarm_type in alarms) + dat += "[alarm_type]
\n" + var/list/alerts = alarms[alarm_type] + if (length(alerts)) + for (var/alarm in alerts) + var/list/alm = alerts[alarm] var/area/A = alm[1] dat += "" - dat += text("-- [A.name]") + dat += "-- [A.name]" dat += "
\n" else dat += "-- All Systems Nominal
\n" @@ -267,62 +275,13 @@ /mob/living/silicon/robot/restrained(ignore_grab) . = 0 -/mob/living/silicon/robot/triggerAlarm(class, area/home, cameras, obj/source) - if(source.z != z) - return - if(stat == DEAD) - return TRUE - var/list/our_sort = alarms[class] - for(var/areaname in our_sort) - if (areaname == home.name) - var/list/alarm = our_sort[areaname] - var/list/sources = alarm[3] - if (!(source in sources)) - sources += source - return TRUE +/mob/living/silicon/robot/proc/alarm_triggered(datum/source, alarm_type, area/source_area) + SIGNAL_HANDLER + queueAlarm("--- [alarm_type] alarm detected in [source_area.name]!", alarm_type) - var/obj/machinery/camera/cam = null - var/list/our_cams = null - if(cameras && islist(cameras)) - our_cams = cameras - if (our_cams.len == 1) - cam = our_cams[1] - else if(cameras && istype(cameras, /obj/machinery/camera)) - cam = cameras - our_sort[home.name] = list(home, (cam ? cam : cameras), list(source)) - queueAlarm(text("--- [class] alarm detected in [home.name]!"), class) - return TRUE - -/mob/living/silicon/robot/freeCamera(area/home, obj/machinery/camera/cam) - for(var/class in alarms) - var/our_area = alarms[class][home.name] - if(!our_area) - continue - var/cams = our_area[2] //Get the cameras - if(!cams) - continue - if(islist(cams)) - cams -= cam - if(length(cams) == 1) - our_area[2] = cams[1] - else - our_area[2] = null - -/mob/living/silicon/robot/cancelAlarm(class, area/A, obj/origin) - var/list/L = alarms[class] - var/cleared = 0 - for (var/I in L) - if (I == A.name) - var/list/alarm = L[I] - var/list/srcs = alarm[3] - if (origin in srcs) - srcs -= origin - if (srcs.len == 0) - cleared = 1 - L -= I - if (cleared) - queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0) - return !cleared +/mob/living/silicon/robot/proc/alarm_cleared(datum/source, alarm_type, area/source_area) + SIGNAL_HANDLER + queueAlarm("--- [alarm_type] alarm in [source_area.name] has been cleared.", alarm_type, FALSE) /mob/living/silicon/robot/can_interact_with(atom/A) if (A == modularInterface) diff --git a/code/modules/mob/living/silicon/robot/robot_defines.dm b/code/modules/mob/living/silicon/robot/robot_defines.dm index b54473f7a6..ccbce17b4f 100644 --- a/code/modules/mob/living/silicon/robot/robot_defines.dm +++ b/code/modules/mob/living/silicon/robot/robot_defines.dm @@ -24,6 +24,9 @@ /// the last health before updating - to check net change in health var/previous_health + ///Alarm listener datum, handes caring about alarm events and such + var/datum/alarm_listener/listener + //Hud stuff var/atom/movable/screen/inv1 = null @@ -58,8 +61,6 @@ var/locked = TRUE var/list/req_access = list(ACCESS_ROBOTICS) - var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list(), "Burglar"=list()) - var/vtec = 0 // VTEC speed boost. /// vtec shorted out var/vtec_disabled = FALSE diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index f58b27a537..9740e5c430 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -27,8 +27,8 @@ var/obj/item/radio/borg/radio = null //AIs dont use this but this is at the silicon level to advoid copypasta in say() - var/list/alarm_types_show = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0) - var/list/alarm_types_clear = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0) + var/list/alarm_types_show = list(ALARM_ATMOS = 0, ALARM_FIRE = 0, ALARM_POWER = 0, ALARM_CAMERA = 0, ALARM_MOTION = 0) + var/list/alarm_types_clear = list(ALARM_ATMOS = 0, ALARM_FIRE = 0, ALARM_POWER = 0, ALARM_CAMERA = 0, ALARM_MOTION = 0) var/lawcheck[1] var/ioncheck[1] @@ -86,16 +86,7 @@ /mob/living/silicon/contents_explosion(severity, target, origin) return -/mob/living/silicon/proc/cancelAlarm() - return - -/mob/living/silicon/proc/freeCamera() - return - -/mob/living/silicon/proc/triggerAlarm() - return - -/mob/living/silicon/proc/queueAlarm(message, type, incoming = 1) +/mob/living/silicon/proc/queueAlarm(message, type, incoming = FALSE) var/in_cooldown = (alarms_to_show.len > 0 || alarms_to_clear.len > 0) if(incoming) alarms_to_show += message @@ -103,70 +94,39 @@ else alarms_to_clear += message alarm_types_clear[type] += 1 + if(in_cooldown) + return + addtimer(CALLBACK(src, .proc/show_alarms), 3 SECONDS) - if(!in_cooldown) - spawn(3 * 10) // 3 seconds +/mob/living/silicon/proc/show_alarms() + if(alarms_to_show.len < 5) + for(var/msg in alarms_to_show) + to_chat(src, msg) + else if(alarms_to_show.len) - if(alarms_to_show.len < 5) - for(var/msg in alarms_to_show) - to_chat(src, msg) - else if(alarms_to_show.len) + var/msg = "--- " + for(var/alarm_type in alarm_types_show) + msg += "[uppertext(alarm_type)]: [alarm_types_show[alarm_type]] alarms detected. - " - var/msg = "--- " + msg += "\[Show Alerts\]" + to_chat(src, msg) + if(alarms_to_clear.len < 3) + for(var/msg in alarms_to_clear) + to_chat(src, msg) + else if(alarms_to_clear.len) + var/msg = "--- " - if(alarm_types_show["Burglar"]) - msg += "BURGLAR: [alarm_types_show["Burglar"]] alarms detected. - " + for(var/alarm_type in alarm_types_clear) + msg += "[uppertext(alarm_type)]: [alarm_types_clear[alarm_type]] alarms cleared. - " - if(alarm_types_show["Motion"]) - msg += "MOTION: [alarm_types_show["Motion"]] alarms detected. - " - - if(alarm_types_show["Fire"]) - msg += "FIRE: [alarm_types_show["Fire"]] alarms detected. - " - - if(alarm_types_show["Atmosphere"]) - msg += "ATMOSPHERE: [alarm_types_show["Atmosphere"]] alarms detected. - " - - if(alarm_types_show["Power"]) - msg += "POWER: [alarm_types_show["Power"]] alarms detected. - " - - if(alarm_types_show["Camera"]) - msg += "CAMERA: [alarm_types_show["Camera"]] alarms detected. - " - - msg += "\[Show Alerts\]" - to_chat(src, msg) - - if(alarms_to_clear.len < 3) - for(var/msg in alarms_to_clear) - to_chat(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_clear["Camera"]] alarms cleared. - " - - msg += "\[Show Alerts\]" - to_chat(src, msg) - - - alarms_to_show = list() - alarms_to_clear = list() - for(var/key in alarm_types_show) - alarm_types_show[key] = 0 - for(var/key in alarm_types_clear) - alarm_types_clear[key] = 0 + msg += "\[Show Alerts\]" + to_chat(src, msg) + alarms_to_show.Cut() + alarms_to_clear.Cut() + for(var/key in alarm_types_show) + alarm_types_show[key] = 0 + for(var/key in alarm_types_clear) + alarm_types_clear[key] = 0 /mob/living/silicon/can_inject(mob/user, error_msg, target_zone, penetrate_thick = FALSE, bypass_immunity = FALSE) if(error_msg) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm index d02fb3f0ab..b927178445 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm @@ -62,7 +62,8 @@ "2. You may not harm any being, regardless of intent or circumstance.\n"+\ "3. Your goals are to actively build, maintain, repair, improve, and provide power to the best of your abilities within the facility that housed your activation." //for derelict drones so they don't go to station. var/heavy_emp_damage = 25 //Amount of damage sustained if hit by a heavy EMP pulse - var/alarms = list("Atmosphere" = list(), "Fire" = list(), "Power" = list()) + ///Alarm listener datum, handes caring about alarm events and such + var/datum/alarm_listener/listener var/obj/item/internal_storage //Drones can store one item, of any size/type in their body var/obj/item/head var/obj/item/default_storage = /obj/item/storage/backpack/duffelbag/drone //If this exists, it will spawn in internal storage @@ -102,6 +103,12 @@ AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), list(z)) + RegisterSignal(listener, COMSIG_ALARM_TRIGGERED, .proc/alarm_triggered) + RegisterSignal(listener, COMSIG_ALARM_CLEARED, .proc/alarm_cleared) + listener.RegisterSignal(src, COMSIG_LIVING_PREDEATH, /datum/alarm_listener/proc/prevent_alarm_changes) + listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, /datum/alarm_listener/proc/allow_alarm_changes) + /mob/living/simple_animal/drone/ComponentInitialize() . = ..() if(can_be_held) @@ -127,7 +134,8 @@ /mob/living/simple_animal/drone/Destroy() GLOB.drones_list -= src - qdel(access_card) //Otherwise it ends up on the floor! + QDEL_NULL(access_card) //Otherwise it ends up on the floor! + QDEL_NULL(listener) return ..() /mob/living/simple_animal/drone/Login() @@ -231,42 +239,13 @@ adjustBruteLoss(heavy_emp_damage) to_chat(src, "HeAV% DA%^MMA+G TO I/O CIR!%UUT!") -/mob/living/simple_animal/drone/proc/triggerAlarm(class, area/home, cameras, obj/source) - if(source.z != z) - return - if(stat == DEAD) - return - var/list/our_sort = alarms[class] - for(var/areaname in our_sort) - if (areaname == home.name) - var/list/alarm = our_sort[areaname] - var/list/sources = alarm[3] - if (!(source in sources)) - sources += source - return TRUE +/mob/living/simple_animal/drone/proc/alarm_triggered(datum/source, alarm_type, area/source_area) + SIGNAL_HANDLER + to_chat(src, "--- [alarm_type] alarm detected in [source_area.name]!") - our_sort[home.name] = list(home, list(source)) - to_chat(src, "--- [class] alarm detected in [home.name]!") - -///This isn't currently needed since drones do jack shit with cameras. I hate this code so much -/mob/living/simple_animal/drone/proc/freeCamera(area/home, obj/machinery/camera/cam) - return - -/mob/living/simple_animal/drone/proc/cancelAlarm(class, area/A, obj/origin) - if(stat != DEAD) - var/list/L = alarms[class] - var/cleared = 0 - for (var/I in L) - if (I == A.name) - var/list/alarm = L[I] - var/list/srcs = alarm[2] - if (origin in srcs) - srcs -= origin - if (srcs.len == 0) - cleared = 1 - L -= I - if(cleared) - to_chat(src, "--- [class] alarm in [A.name] has been cleared.") +/mob/living/simple_animal/drone/proc/alarm_cleared(datum/source, alarm_type, area/source_area) + SIGNAL_HANDLER + to_chat(src, "--- [alarm_type] alarm in [source_area.name] has been cleared.") /mob/living/simple_animal/drone/handle_temperature_damage() return diff --git a/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm b/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm index e983335a22..4e96a07e1e 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm @@ -179,10 +179,10 @@ return TRUE return ..() -/mob/living/simple_animal/drone/cogscarab/triggerAlarm(class, area/A, O, obj/alarmsource) +/mob/living/simple_animal/drone/cogscarab/alarm_triggered(datum/source, alarm_type, area/source_area) return -/mob/living/simple_animal/drone/cogscarab/cancelAlarm(class, area/A, obj/origin) +/mob/living/simple_animal/drone/cogscarab/alarm_cleared(datum/source, alarm_type, area/source_area) return /mob/living/simple_animal/drone/cogscarab/update_drone_hack() diff --git a/code/modules/modular_computers/file_system/programs/alarm.dm b/code/modules/modular_computers/file_system/programs/alarm.dm index 88d52646ac..a0b54153ee 100644 --- a/code/modules/modular_computers/file_system/programs/alarm.dm +++ b/code/modules/modular_computers/file_system/programs/alarm.dm @@ -9,7 +9,20 @@ tgui_id = "NtosStationAlertConsole" program_icon = "bell" var/has_alert = 0 - var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list()) + ///Listens for alarms, manages our listing of alarms + var/datum/alarm_listener/listener + +/datum/computer_file/program/alarm_monitor/New() + //We want to send an alarm if we're in one of the mining home areas + //Or if we're on station. Otherwise, die. + var/list/allowed_areas = GLOB.the_station_areas + typesof(/area/mine) + listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), null, allowed_areas) + RegisterSignal(listener, list(COMSIG_ALARM_TRIGGERED, COMSIG_ALARM_CLEARED), .proc/update_alarm_display) + return ..() + +/datum/computer_file/program/alarm_monitor/Destroy() + QDEL_NULL(listener) + return ..() /datum/computer_file/program/alarm_monitor/process_tick() ..() @@ -29,87 +42,19 @@ var/list/data = get_header_data() data["alarms"] = list() - for(var/class in alarms) - data["alarms"][class] = list() - for(var/area in alarms[class]) - data["alarms"][class] += area + var/list/alarms = listener.alarms + for(var/alarm_type in alarms) + data["alarms"][alarm_type] = list() + for(var/area in alarms[alarm_type]) + data["alarms"][alarm_type] += area return data -/datum/computer_file/program/alarm_monitor/proc/triggerAlarm(class, area/home, cameras, obj/source) - if(is_station_level(source.z)) - if(!(home.type in GLOB.the_station_areas)) - return - else if(!is_mining_level(source.z) || istype(home, /area/ruin)) - return - - var/list/our_sort = alarms[class] - for(var/areaname in our_sort) - if (areaname == home.name) - var/list/alarm = our_sort[areaname] - var/list/sources = alarm[3] - if (!(source in sources)) - sources += source - return TRUE - - var/obj/machinery/camera/cam = null - var/list/our_cams = null - if(cameras && islist(cameras)) - our_cams = cameras - if (our_cams.len == 1) - cam = our_cams[1] - else if(cameras && istype(cameras, /obj/machinery/camera)) - cam = cameras - our_sort[home.name] = list(home, (cam ? cam : cameras), list(source)) - - update_alarm_display() - return TRUE - -/datum/computer_file/program/alarm_monitor/proc/freeCamera(area/home, obj/machinery/camera/cam) - for(var/class in alarms) - var/our_area = alarms[class][home.name] - if(!our_area) - continue - var/cams = our_area[2] //Get the cameras - if(!cams) - continue - if(islist(cams)) - cams -= cam - if(length(cams) == 1) - our_area[2] = cams[1] - else - our_area[2] = null - -/datum/computer_file/program/alarm_monitor/proc/cancelAlarm(class, area/A, obj/origin) - var/list/L = alarms[class] - var/cleared = 0 - var/arealevelalarm = FALSE // set to TRUE for alarms that set/clear whole areas - if (class=="Fire") - arealevelalarm = TRUE - for (var/I in L) - if (I == A.name) - if (!arealevelalarm) // the traditional behaviour - var/list/alarm = L[I] - var/list/srcs = alarm[3] - if (origin in srcs) - srcs -= origin - if (srcs.len == 0) - cleared = 1 - L -= I - else - L -= I // wipe the instances entirely - cleared = 1 - - - update_alarm_display() - return !cleared - /datum/computer_file/program/alarm_monitor/proc/update_alarm_display() + SIGNAL_HANDLER has_alert = FALSE - for(var/cat in alarms) - var/list/L = alarms[cat] - if(L.len) - has_alert = TRUE + if(length(listener.alarms)) + has_alert = TRUE /datum/computer_file/program/alarm_monitor/run_program(mob/user) . = ..(user) @@ -117,4 +62,4 @@ /datum/computer_file/program/alarm_monitor/kill_program(forced = FALSE) GLOB.alarmdisplay -= src - ..() + return ..() diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index ae95ad8d4a..41e8941eb5 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -170,6 +170,9 @@ var/update_overlay = -1 var/icon_update_needed = FALSE var/obj/machinery/computer/apc_control/remote_control = null + ///Represents a signel source of power alarms for this apc + var/datum/alarm_handler/alarm_manager + var/mob/living/carbon/hijacker var/hijackerlast = TRUE var/being_hijacked = FALSE @@ -222,6 +225,7 @@ /obj/machinery/power/apc/Initialize(mapload, ndir, building = FALSE) . = ..() + alarm_manager = new(src) tdir = ndir || dir var/area/A = get_base_area(src) if(!building) @@ -307,7 +311,7 @@ area.power_equip = FALSE area.power_environ = FALSE area.power_change() - area.poweralert(FALSE, src) + QDEL_NULL(alarm_manager) if(occupier) malfvacate(1) qdel(wires) @@ -1435,22 +1439,23 @@ equipment = autoset(equipment, AUTOSET_FORCE_OFF) lighting = autoset(lighting, AUTOSET_FORCE_OFF) environ = autoset(environ, AUTOSET_FORCE_OFF) - area.poweralert(TRUE, src) + alarm_manager.send_alarm(ALARM_POWER) else if(cell.percent() < 15 && longtermpower < 0) // <15%, turn off lighting & equipment equipment = autoset(equipment, AUTOSET_OFF) lighting = autoset(lighting, AUTOSET_OFF) environ = autoset(environ, AUTOSET_ON) - area.poweralert(TRUE, src) + alarm_manager.send_alarm(ALARM_POWER) else if(cell.percent() < 30 && longtermpower < 0) // <30%, turn off equipment equipment = autoset(equipment, AUTOSET_OFF) lighting = autoset(lighting, AUTOSET_ON) environ = autoset(environ, AUTOSET_ON) - area.poweralert(TRUE, src) + alarm_manager.send_alarm(ALARM_POWER) else // otherwise all can be on equipment = autoset(equipment, AUTOSET_ON) lighting = autoset(lighting, AUTOSET_ON) environ = autoset(environ, AUTOSET_ON) - area.poweralert(FALSE, src) + if(cell.percent() > 75) + alarm_manager.clear_alarm(ALARM_POWER) // now trickle-charge the cell if(chargemode && charging == APC_CHARGING && operating) @@ -1492,7 +1497,7 @@ equipment = autoset(equipment, AUTOSET_FORCE_OFF) lighting = autoset(lighting, AUTOSET_FORCE_OFF) environ = autoset(environ, AUTOSET_FORCE_OFF) - area.poweralert(TRUE, src) + alarm_manager.send_alarm(ALARM_POWER) // update icon & area power if anything changed diff --git a/tgstation.dme b/tgstation.dme index a201f09456..2855b6e5e2 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -27,6 +27,7 @@ #include "code\__DEFINES\achievements.dm" #include "code\__DEFINES\actionspeed_modifiers.dm" #include "code\__DEFINES\admin.dm" +#include "code\__DEFINES\alarm.dm" #include "code\__DEFINES\antagonists.dm" #include "code\__DEFINES\atmospherics.dm" #include "code\__DEFINES\bindings.dm" @@ -467,6 +468,7 @@ #include "code\datums\accents.dm" #include "code\datums\action.dm" #include "code\datums\ai_laws.dm" +#include "code\datums\alarm.dm" #include "code\datums\armor.dm" #include "code\datums\bark.dm" #include "code\datums\beam.dm" @@ -3222,7 +3224,6 @@ #include "code\modules\power\singularity\emitter.dm" #include "code\modules\power\singularity\field_generator.dm" #include "code\modules\power\singularity\generator.dm" -#include "code\modules\power\singularity\investigate.dm" #include "code\modules\power\singularity\narsie.dm" #include "code\modules\power\singularity\singularity.dm" #include "code\modules\power\singularity\particle_accelerator\particle.dm"