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/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm
index d4ab59cbc1..68a9033d7f 100644
--- a/code/__DEFINES/jobs.dm
+++ b/code/__DEFINES/jobs.dm
@@ -91,3 +91,22 @@
#define JOB_DISPLAY_ORDER_DETECTIVE 32
#define JOB_DISPLAY_ORDER_SECURITY_OFFICER 33
#define JOB_DISPLAY_ORDER_PRISONER 34
+
+#define DEPARTMENT_UNASSIGNED "No department assigned"
+
+#define DEPARTMENT_BITFLAG_SECURITY (1<<0)
+#define DEPARTMENT_SECURITY "Security"
+#define DEPARTMENT_BITFLAG_COMMAND (1<<1)
+#define DEPARTMENT_COMMAND "Command"
+#define DEPARTMENT_BITFLAG_SERVICE (1<<2)
+#define DEPARTMENT_SERVICE "Service"
+#define DEPARTMENT_BITFLAG_CARGO (1<<3)
+#define DEPARTMENT_CARGO "Cargo"
+#define DEPARTMENT_BITFLAG_ENGINEERING (1<<4)
+#define DEPARTMENT_ENGINEERING "Engineering"
+#define DEPARTMENT_BITFLAG_SCIENCE (1<<5)
+#define DEPARTMENT_SCIENCE "Science"
+#define DEPARTMENT_BITFLAG_MEDICAL (1<<6)
+#define DEPARTMENT_MEDICAL "Medical"
+#define DEPARTMENT_BITFLAG_SILICON (1<<7)
+#define DEPARTMENT_SILICON "Silicon"
diff --git a/code/_globalvars/tgui.dm b/code/_globalvars/tgui.dm
index 1a2c46c10d..aaaab9c5e4 100644
--- a/code/_globalvars/tgui.dm
+++ b/code/_globalvars/tgui.dm
@@ -1 +1,2 @@
+GLOBAL_DATUM(crew_manifest_tgui, /datum/crew_manifest)
GLOBAL_DATUM(changelog_tgui, /datum/changelog)
diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm
index 0f392a47da..419de492ef 100644
--- a/code/_onclick/hud/ai.dm
+++ b/code/_onclick/hud/ai.dm
@@ -74,7 +74,7 @@
if(..())
return
var/mob/living/silicon/ai/AI = usr
- AI.ai_alerts()
+ AI.alert_control.ui_interact(AI)
/atom/movable/screen/ai/announcement
name = "Make Vox Announcement"
diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm
index f905c31aa1..35fd31cbdb 100644
--- a/code/_onclick/hud/robot.dm
+++ b/code/_onclick/hud/robot.dm
@@ -361,7 +361,7 @@
if(.)
return
var/mob/living/silicon/robot/borgo = usr
- borgo.robot_alerts()
+ borgo.alert_control.ui_interact(borgo)
/atom/movable/screen/robot/thrusters
name = "ion thrusters"
diff --git a/code/datums/alarm.dm b/code/datums/alarm.dm
new file mode 100644
index 0000000000..7fc2245949
--- /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_REF(add_alarm))
+ RegisterSignal(SSdcs, COMSIG_ALARM_CLEAR(alarm_type), PROC_REF(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_REF(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/datacore.dm b/code/datums/datacore.dm
index 2351b7b4ba..455ba5f6b8 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -91,7 +91,7 @@
if(foundrecord)
foundrecord.fields["rank"] = assignment
-/datum/datacore/proc/get_manifest_tg() //copypasted from tg, renamed to avoid namespace conflicts
+/datum/datacore/proc/get_manifest()
var/list/manifest_out = list()
var/list/departments = list(
"Command" = GLOB.command_positions,
@@ -113,130 +113,30 @@
if(department_check in jobs)
if(!manifest_out[department])
manifest_out[department] = list()
- manifest_out[department] += list(list(
- "name" = name,
- "rank" = rank
- ))
+ // Append to beginning of list if captain or department head
+ if (department_check == "Captain" || (department != "Command" && (rank in GLOB.command_positions)))
+ manifest_out[department] = list(list(
+ "name" = name,
+ "rank" = rank,
+ "department_check" = department_check
+ )) + manifest_out[department]
+ else
+ manifest_out[department] += list(list(
+ "name" = name,
+ "rank" = rank,
+ "department_check" = department_check
+ ))
has_department = TRUE
- break
if(!has_department)
if(!manifest_out["Misc"])
manifest_out["Misc"] = list()
manifest_out["Misc"] += list(list(
"name" = name,
- "rank" = rank
+ "rank" = rank,
+ "department_check" = department_check
))
return manifest_out
-/datum/datacore/proc/get_manifest(monochrome, OOC)
- var/list/heads = list()
- var/list/sec = list()
- var/list/eng = list()
- var/list/med = list()
- var/list/sci = list()
- var/list/sup = list()
- var/list/civ = list()
- var/list/bot = list()
- var/list/misc = list()
- var/dat = {"
-
-
-
Name
Rank
- "}
- var/even = 0
- // sort mobs
- for(var/datum/data/record/t in GLOB.data_core.general)
- var/name = t.fields["name"]
- var/rank = t.fields["rank"]
- var/department_check = GetJobName(t.fields["rank"])
- var/department = 0
- if(department_check in GLOB.command_positions)
- heads[name] = rank
- department = 1
- if(department_check in GLOB.security_positions)
- sec[name] = rank
- department = 1
- if(department_check in GLOB.engineering_positions)
- eng[name] = rank
- department = 1
- if(department_check in GLOB.medical_positions)
- med[name] = rank
- department = 1
- if(department_check in GLOB.science_positions)
- sci[name] = rank
- department = 1
- if(department_check in GLOB.supply_positions)
- sup[name] = rank
- department = 1
- if(department_check in GLOB.civilian_positions)
- civ[name] = rank
- department = 1
- if(department_check in GLOB.nonhuman_positions)
- bot[name] = rank
- department = 1
- if(!department && !(name in heads))
- misc[name] = rank
- if(heads.len > 0)
- dat += "
Heads
"
- for(var/name in heads)
- dat += "
[name]
[heads[name]]
"
- even = !even
- if(sec.len > 0)
- dat += "
Security
"
- for(var/name in sec)
- dat += "
[name]
[sec[name]]
"
- even = !even
- if(eng.len > 0)
- dat += "
Engineering
"
- for(var/name in eng)
- dat += "
[name]
[eng[name]]
"
- even = !even
- if(med.len > 0)
- dat += "
Medical
"
- for(var/name in med)
- dat += "
[name]
[med[name]]
"
- even = !even
- if(sci.len > 0)
- dat += "
Science
"
- for(var/name in sci)
- dat += "
[name]
[sci[name]]
"
- even = !even
- if(sup.len > 0)
- dat += "
Supply
"
- for(var/name in sup)
- dat += "
[name]
[sup[name]]
"
- even = !even
- if(civ.len > 0)
- dat += "
Civilian
"
- for(var/name in civ)
- dat += "
[name]
[civ[name]]
"
- even = !even
- // in case somebody is insane and added them to the manifest, why not
- if(bot.len > 0)
- dat += "
Silicon
"
- for(var/name in bot)
- dat += "
[name]
[bot[name]]
"
- even = !even
- // misc guys
- if(misc.len > 0)
- dat += "
Miscellaneous
"
- for(var/name in misc)
- dat += "
[name]
[misc[name]]
"
- even = !even
-
- dat += "
"
- dat = replacetext(dat, "\n", "")
- dat = replacetext(dat, "\t", "")
- return dat
-
-
/datum/datacore/proc/manifest_inject(mob/living/carbon/human/H, client/C, datum/preferences/prefs)
set waitfor = FALSE
var/static/list/show_directions = list(SOUTH, WEST)
diff --git a/code/datums/elements/weather_listener.dm b/code/datums/elements/weather_listener.dm
index e82ce06aac..1eb26dca0a 100644
--- a/code/datums/elements/weather_listener.dm
+++ b/code/datums/elements/weather_listener.dm
@@ -37,7 +37,7 @@
if(!(new_z in fitting_z_levels))
return
var/datum/component/our_comp = source.AddComponent(/datum/component/area_sound_manager, playlist, list(), COMSIG_MOB_CLIENT_LOGOUT, fitting_z_levels)
- our_comp.RegisterSignal(SSdcs, sound_change_signals, /datum/component/area_sound_manager/proc/handle_change)
+ our_comp.RegisterSignal(SSdcs, sound_change_signals, TYPE_PROC_REF(/datum/component/area_sound_manager, handle_change))
/datum/element/weather_listener/proc/handle_logout(datum/source, client/this_is_a_null_ref)
SIGNAL_HANDLER
diff --git a/code/datums/station_alert.dm b/code/datums/station_alert.dm
new file mode 100644
index 0000000000..edec9f5cb5
--- /dev/null
+++ b/code/datums/station_alert.dm
@@ -0,0 +1,103 @@
+/datum/station_alert
+ /// Holder of the datum
+ var/holder
+ /// List of all alarm types we are listening to
+ var/list/alarm_types
+ /// Listens for alarms, provides the alarms list for our UI
+ var/datum/alarm_listener/listener
+ /// Title of our UI
+ var/title
+ /// If UI will also show and allow jumping to cameras connected to each alert area
+ var/camera_view
+
+/datum/station_alert/ui_host(mob/user)
+ return holder
+
+/datum/station_alert/New(holder, list/alarm_types, list/listener_z_level, list/listener_areas, title = "Station Alerts", camera_view = FALSE)
+ src.holder = holder
+ src.alarm_types = alarm_types
+ src.title = title
+ src.camera_view = camera_view
+ listener = new(alarm_types, listener_z_level, listener_areas)
+
+/datum/station_alert/Destroy()
+ QDEL_NULL(listener)
+ return ..()
+
+/datum/station_alert/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "StationAlertConsole", title)
+ ui.open()
+
+/datum/station_alert/ui_data(mob/user)
+ var/list/data = list()
+ data["cameraView"] = camera_view
+ data["alarms"] = list()
+ var/list/nominal_types = alarm_types.Copy()
+ var/list/alarms = listener.alarms
+ for(var/alarm_type in alarms)
+ var/list/alarm_category = list(
+ "name" = alarm_type,
+ "alerts" = list(),
+ )
+ var/list/alerts = alarms[alarm_type]
+ for(var/alert in alerts)
+ var/list/alert_details = alerts[alert]
+ alarm_category["alerts"] += list(list(
+ "name" = get_area_name(alert_details[1], TRUE),
+ "cameras" = camera_view ? length(alert_details[2]) : null,
+ "sources" = camera_view ? length(alert_details[3]) : null,
+ "ref" = camera_view ? REF(alert) : null,
+ ))
+ data["alarms"] += list(alarm_category)
+ nominal_types -= alarm_type
+ if(length(nominal_types))
+ for(var/nominal_type in nominal_types)
+ var/list/nominal_category = list(
+ "name" = nominal_type,
+ "alerts" = list(),
+ )
+ data["alarms"] += list(nominal_category)
+ return data
+
+/datum/station_alert/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("select_camera")
+ var/mob/living/silicon/ai/ai = usr
+ if(!istype(ai))
+ return
+
+ var/list/alarms = listener.alarms
+ var/list/alerts = list()
+ for(var/alarm_type in alarms)
+ alerts += alarms[alarm_type]
+
+ var/list/our_alert = locate(params["alert"]) in alerts
+ if(!length(our_alert))
+ return
+ var/chosen_alert = alerts[our_alert]
+ var/list/cameras = chosen_alert[2]
+ if(!length(cameras))
+ return
+ var/list/named_cameras = list()
+ for(var/obj/machinery/camera/camera in cameras)
+ named_cameras[camera.c_tag] = camera
+
+ var/chosen_camera
+ if(length(named_cameras) == 1)
+ chosen_camera = named_cameras[1]
+ else
+ chosen_camera = tgui_input_list(ai, "Choose a camera to jump to", "Camera Selection", named_cameras)
+ if(!chosen_camera)
+ return
+ var/obj/machinery/camera/selected_camera = named_cameras[chosen_camera]
+ if(!selected_camera.can_use())
+ to_chat(ai, span_warning("Camera is unavailable!"))
+ return
+ ai.switchCamera(selected_camera)
+ return TRUE
diff --git a/code/datums/station_traits/positive_traits.dm b/code/datums/station_traits/positive_traits.dm
index a1deb8899c..94772966ee 100644
--- a/code/datums/station_traits/positive_traits.dm
+++ b/code/datums/station_traits/positive_traits.dm
@@ -149,7 +149,7 @@
trait_flags = STATION_TRAIT_ABSTRACT
blacklist = list(/datum/station_trait/deathrattle_all)
- var/department_head
+ var/department_to_apply_to
var/department_name = "department"
var/datum/deathrattle_group/deathrattle_group
@@ -157,72 +157,65 @@
. = ..()
deathrattle_group = new("[department_name] group")
blacklist += subtypesof(/datum/station_trait/deathrattle_department) - type //All but ourselves
+ name = "deathrattled [department_name]"
report_message = "All members of [department_name] have received an implant to notify each other if one of them dies. This should help improve job-safety!"
RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, PROC_REF(on_job_after_spawn))
-
-/datum/station_trait/deathrattle_department/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/spawned, client/player_client)
+/datum/station_trait/deathrattle_department/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/living_mob, mob/M, joined_late)
SIGNAL_HANDLER
- if(department_head != job.title && !(src.department_head in job.department_head))
+ if(!(job.departments & department_to_apply_to))
return
var/obj/item/implant/deathrattle/implant_to_give = new()
deathrattle_group.register(implant_to_give)
- implant_to_give.implant(spawned, spawned, TRUE, TRUE)
+ implant_to_give.implant(living_mob, living_mob, TRUE, TRUE)
/datum/station_trait/deathrattle_department/service
- name = "Deathrattled Service"
trait_flags = NONE
weight = 1
- department_head = "Head of Personnel"
+ department_to_apply_to = DEPARTMENT_BITFLAG_SERVICE
department_name = "Service"
/datum/station_trait/deathrattle_department/cargo
- name = "Deathrattled Cargo"
trait_flags = NONE
- weight = 2
- department_head = "Quartermaster"
+ weight = 1
+ department_to_apply_to = DEPARTMENT_BITFLAG_CARGO
department_name = "Cargo"
/datum/station_trait/deathrattle_department/engineering
- name = "Deathrattled Engineering"
trait_flags = NONE
- weight = 2
- department_head = "Chief Engineer"
+ weight = 1
+ department_to_apply_to = DEPARTMENT_BITFLAG_ENGINEERING
department_name = "Engineering"
/datum/station_trait/deathrattle_department/command
- name = "Deathrattled Command"
trait_flags = NONE
weight = 1
- department_head = "Captain"
+ department_to_apply_to = DEPARTMENT_BITFLAG_COMMAND
department_name = "Command"
/datum/station_trait/deathrattle_department/science
- name = "Deathrattled Science"
trait_flags = NONE
weight = 1
- department_head = "Research Director"
+ department_to_apply_to = DEPARTMENT_BITFLAG_SCIENCE
department_name = "Science"
/datum/station_trait/deathrattle_department/security
- name = "Deathrattled Security"
trait_flags = NONE
weight = 1
- department_head = "Head of Security"
+ department_to_apply_to = DEPARTMENT_BITFLAG_SECURITY
department_name = "Security"
/datum/station_trait/deathrattle_department/medical
- name = "Deathrattled Medical"
trait_flags = NONE
weight = 1
- department_head = "Chief Medical Officer"
+ department_to_apply_to = DEPARTMENT_BITFLAG_MEDICAL
department_name = "Medical"
/datum/station_trait/deathrattle_all
- name = "Deathrattled Station"
+ name = "deathrattled station"
trait_type = STATION_TRAIT_POSITIVE
show_in_report = TRUE
weight = 1
@@ -236,14 +229,12 @@
blacklist = subtypesof(/datum/station_trait/deathrattle_department)
RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, PROC_REF(on_job_after_spawn))
-
-/datum/station_trait/deathrattle_all/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/spawned, client/player_client)
+/datum/station_trait/deathrattle_all/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/living_mob, mob/M, joined_late)
SIGNAL_HANDLER
var/obj/item/implant/deathrattle/implant_to_give = new()
deathrattle_group.register(implant_to_give)
- implant_to_give.implant(spawned, spawned, TRUE, TRUE)
-
+ implant_to_give.implant(living_mob, living_mob, TRUE, TRUE)
/datum/station_trait/wallets
name = "Wallets!"
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..ccac91d39a 100644
--- a/code/game/machinery/computer/station_alert.dm
+++ b/code/game/machinery/computer/station_alert.dm
@@ -4,91 +4,26 @@
icon_screen = "alert:0"
icon_keyboard = "atmos_key"
circuit = /obj/item/circuitboard/computer/stationalert
- var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list())
-
light_color = LIGHT_COLOR_CYAN
+ /// Station alert datum for showing alerts UI
+ var/datum/station_alert/alert_control
/obj/machinery/computer/station_alert/Initialize(mapload)
- . = ..()
- GLOB.alert_consoles += src
-
-/obj/machinery/computer/station_alert/Destroy()
- GLOB.alert_consoles -= src
+ alert_control = new(src, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), list(z), title = name)
return ..()
-/obj/machinery/computer/station_alert/ui_interact(mob/user, datum/tgui/ui)
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "StationAlertConsole", name)
- ui.open()
+/obj/machinery/computer/station_alert/Destroy()
+ QDEL_NULL(alert_control)
+ return ..()
-/obj/machinery/computer/station_alert/ui_data(mob/user)
- var/list/data = list()
+/obj/machinery/computer/station_alert/ui_interact(mob/user)
+ alert_control.ui_interact(user)
- data["alarms"] = list()
- for(var/class in alarms)
- data["alarms"][class] = list()
- for(var/area in alarms[class])
- data["alarms"][class] += area
-
- 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)
+ alert_control.listener.prevent_alarm_changes()
+ else
+ alert_control.listener.allow_alarm_changes()
/obj/machinery/computer/station_alert/update_overlays()
. = ..()
@@ -98,12 +33,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(alert_control.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 43f649aae3..7e5d7e5188 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/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm
index 730e16fc61..0e9384a246 100644
--- a/code/modules/jobs/job_types/_job.dm
+++ b/code/modules/jobs/job_types/_job.dm
@@ -76,6 +76,9 @@
var/bounty_types = CIV_JOB_BASIC
+ ///Bitfield of departments this job belongs wit
+ var/departments = NONE
+
/// Goodies that can be received via the mail system.
// this is a weighted list.
/// Keep the _job definition for this empty and use /obj/item/mail to define general gifts.
diff --git a/code/modules/jobs/job_types/ai.dm b/code/modules/jobs/job_types/ai.dm
index 526f541443..2dc575145e 100644
--- a/code/modules/jobs/job_types/ai.dm
+++ b/code/modules/jobs/job_types/ai.dm
@@ -15,6 +15,7 @@
exp_type_department = EXP_TYPE_SILICON
display_order = JOB_DISPLAY_ORDER_AI
allow_bureaucratic_error = FALSE
+ departments = DEPARTMENT_BITFLAG_SILICON
random_spawns_possible = FALSE
var/do_special_check = TRUE
threat = 5
diff --git a/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm
index 90ac6be8dd..3a2335bb11 100644
--- a/code/modules/jobs/job_types/assistant.dm
+++ b/code/modules/jobs/job_types/assistant.dm
@@ -16,6 +16,7 @@ Assistant
antag_rep = 7
paycheck = PAYCHECK_ASSISTANT // Get a job. Job reassignment changes your paycheck now. Get over it.
paycheck_department = ACCOUNT_CIV
+ departments = DEPARTMENT_BITFLAG_SERVICE
display_order = JOB_DISPLAY_ORDER_ASSISTANT
dresscodecompliant = FALSE
always_can_respawn_as = TRUE
diff --git a/code/modules/jobs/job_types/atmospheric_technician.dm b/code/modules/jobs/job_types/atmospheric_technician.dm
index 551ac63d00..4b17fc8b9f 100644
--- a/code/modules/jobs/job_types/atmospheric_technician.dm
+++ b/code/modules/jobs/job_types/atmospheric_technician.dm
@@ -21,12 +21,13 @@
paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_ENG
bounty_types = CIV_JOB_ENG
+ departments = DEPARTMENT_BITFLAG_ENGINEERING
starting_modifiers = list(/datum/skill_modifier/job/level/wiring, /datum/skill_modifier/job/affinity/wiring)
display_order = JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN
threat = 0.5
-
+
family_heirlooms = list(
/obj/item/lighter,
/obj/item/lighter/greyscale,
diff --git a/code/modules/jobs/job_types/bartender.dm b/code/modules/jobs/job_types/bartender.dm
index bde15b24db..6d8f4256b5 100644
--- a/code/modules/jobs/job_types/bartender.dm
+++ b/code/modules/jobs/job_types/bartender.dm
@@ -18,6 +18,7 @@
paycheck = PAYCHECK_EASY
paycheck_department = ACCOUNT_SRV
bounty_types = CIV_JOB_DRINK
+ departments = DEPARTMENT_BITFLAG_SERVICE
display_order = JOB_DISPLAY_ORDER_BARTENDER
threat = 0.5
diff --git a/code/modules/jobs/job_types/botanist.dm b/code/modules/jobs/job_types/botanist.dm
index fcd71d7275..f30375201e 100644
--- a/code/modules/jobs/job_types/botanist.dm
+++ b/code/modules/jobs/job_types/botanist.dm
@@ -17,6 +17,7 @@
paycheck = PAYCHECK_EASY
paycheck_department = ACCOUNT_SRV
bounty_types = CIV_JOB_GROW
+ departments = DEPARTMENT_BITFLAG_SERVICE
display_order = JOB_DISPLAY_ORDER_BOTANIST
threat = 1.5 // lol powergame
diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm
index 00fb4822e2..fe594e4660 100644
--- a/code/modules/jobs/job_types/captain.dm
+++ b/code/modules/jobs/job_types/captain.dm
@@ -29,6 +29,7 @@
mind_traits = list(TRAIT_CAPTAIN_METABOLISM, TRAIT_DISK_VERIFIER)
display_order = JOB_DISPLAY_ORDER_CAPTAIN
+ departments = DEPARTMENT_BITFLAG_COMMAND
blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity)
threat = 5
diff --git a/code/modules/jobs/job_types/cargo_technician.dm b/code/modules/jobs/job_types/cargo_technician.dm
index 823aec3078..c70e1078aa 100644
--- a/code/modules/jobs/job_types/cargo_technician.dm
+++ b/code/modules/jobs/job_types/cargo_technician.dm
@@ -20,6 +20,7 @@
display_order = JOB_DISPLAY_ORDER_CARGO_TECHNICIAN
bounty_types = CIV_JOB_RANDOM
+ departments = DEPARTMENT_BITFLAG_CARGO
threat = 0.2
family_heirlooms = list(
diff --git a/code/modules/jobs/job_types/chaplain.dm b/code/modules/jobs/job_types/chaplain.dm
index 522f022072..2d6f436cb0 100644
--- a/code/modules/jobs/job_types/chaplain.dm
+++ b/code/modules/jobs/job_types/chaplain.dm
@@ -18,6 +18,7 @@
paycheck_department = ACCOUNT_CIV
display_order = JOB_DISPLAY_ORDER_CHAPLAIN
+ departments = DEPARTMENT_BITFLAG_SERVICE
threat = 0.5
family_heirlooms = list(
diff --git a/code/modules/jobs/job_types/chemist.dm b/code/modules/jobs/job_types/chemist.dm
index be145b24b2..317b9f7a33 100644
--- a/code/modules/jobs/job_types/chemist.dm
+++ b/code/modules/jobs/job_types/chemist.dm
@@ -19,6 +19,7 @@
paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_MED
bounty_types = CIV_JOB_CHEM
+ departments = DEPARTMENT_BITFLAG_MEDICAL
display_order = JOB_DISPLAY_ORDER_CHEMIST
threat = 1.5
diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm
index 5ec4cca512..0e0083bd3c 100644
--- a/code/modules/jobs/job_types/chief_engineer.dm
+++ b/code/modules/jobs/job_types/chief_engineer.dm
@@ -18,6 +18,7 @@
considered_combat_role = TRUE
outfit = /datum/outfit/job/ce
+ departments = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_COMMAND
plasma_outfit = /datum/outfit/plasmaman/ce
access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm
index ed58d67a1f..3472052b53 100644
--- a/code/modules/jobs/job_types/chief_medical_officer.dm
+++ b/code/modules/jobs/job_types/chief_medical_officer.dm
@@ -18,6 +18,7 @@
considered_combat_role = TRUE
outfit = /datum/outfit/job/cmo
+ departments = DEPARTMENT_BITFLAG_MEDICAL | DEPARTMENT_BITFLAG_COMMAND
plasma_outfit = /datum/outfit/plasmaman/cmo
access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_HEADS, ACCESS_MINERAL_STOREROOM,
diff --git a/code/modules/jobs/job_types/clown.dm b/code/modules/jobs/job_types/clown.dm
index c54fb13c0a..009560d7c7 100644
--- a/code/modules/jobs/job_types/clown.dm
+++ b/code/modules/jobs/job_types/clown.dm
@@ -10,6 +10,7 @@
selection_color = "#dddddd"
outfit = /datum/outfit/job/clown
+ departments = DEPARTMENT_BITFLAG_SERVICE
plasma_outfit = /datum/outfit/plasmaman/clown
access = list(ACCESS_THEATRE)
diff --git a/code/modules/jobs/job_types/cook.dm b/code/modules/jobs/job_types/cook.dm
index 246f8d002f..b3d3180367 100644
--- a/code/modules/jobs/job_types/cook.dm
+++ b/code/modules/jobs/job_types/cook.dm
@@ -18,6 +18,7 @@
paycheck = PAYCHECK_EASY
paycheck_department = ACCOUNT_SRV
bounty_types = CIV_JOB_CHEF
+ departments = DEPARTMENT_BITFLAG_SERVICE
display_order = JOB_DISPLAY_ORDER_COOK
threat = 0.2
diff --git a/code/modules/jobs/job_types/curator.dm b/code/modules/jobs/job_types/curator.dm
index e6e9394109..6b5707d76f 100644
--- a/code/modules/jobs/job_types/curator.dm
+++ b/code/modules/jobs/job_types/curator.dm
@@ -18,8 +18,9 @@
paycheck_department = ACCOUNT_CIV
display_order = JOB_DISPLAY_ORDER_CURATOR
+ departments = DEPARTMENT_BITFLAG_SERVICE
threat = 0.3
-
+
family_heirlooms = list(
/obj/item/pen/fountain,
/obj/item/storage/dice
diff --git a/code/modules/jobs/job_types/cyborg.dm b/code/modules/jobs/job_types/cyborg.dm
index 280d7616c2..97ad58d058 100644
--- a/code/modules/jobs/job_types/cyborg.dm
+++ b/code/modules/jobs/job_types/cyborg.dm
@@ -16,6 +16,7 @@
starting_modifiers = list(/datum/skill_modifier/job/level/wiring/basic)
display_order = JOB_DISPLAY_ORDER_CYBORG
+ departments = DEPARTMENT_BITFLAG_SILICON
/datum/job/cyborg/equip(mob/living/carbon/human/H, visualsOnly = FALSE, announce = TRUE, latejoin = FALSE, datum/outfit/outfit_override = null, client/preference_source = null)
if(visualsOnly)
diff --git a/code/modules/jobs/job_types/detective.dm b/code/modules/jobs/job_types/detective.dm
index cfa93ecd5a..82679c4680 100644
--- a/code/modules/jobs/job_types/detective.dm
+++ b/code/modules/jobs/job_types/detective.dm
@@ -14,6 +14,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/detective
+ departments = DEPARTMENT_BITFLAG_SECURITY
plasma_outfit = /datum/outfit/plasmaman/detective
considered_combat_role = TRUE
diff --git a/code/modules/jobs/job_types/geneticist.dm b/code/modules/jobs/job_types/geneticist.dm
index fb830b3ece..46125339be 100644
--- a/code/modules/jobs/job_types/geneticist.dm
+++ b/code/modules/jobs/job_types/geneticist.dm
@@ -12,6 +12,7 @@
exp_requirements = 60
outfit = /datum/outfit/job/geneticist
+ departments = DEPARTMENT_BITFLAG_MEDICAL
plasma_outfit = /datum/outfit/plasmaman/genetics
access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_ROBOTICS, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE)
diff --git a/code/modules/jobs/job_types/head_of_personnel.dm b/code/modules/jobs/job_types/head_of_personnel.dm
index 508dcfda7b..c20e5284d1 100644
--- a/code/modules/jobs/job_types/head_of_personnel.dm
+++ b/code/modules/jobs/job_types/head_of_personnel.dm
@@ -17,6 +17,7 @@
exp_type_department = EXP_TYPE_SERVICE
considered_combat_role = TRUE
outfit = /datum/outfit/job/hop
+ departments = DEPARTMENT_BITFLAG_COMMAND | DEPARTMENT_BITFLAG_SERVICE
plasma_outfit = /datum/outfit/plasmaman/hop
access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_COURT, ACCESS_WEAPONS,
diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm
index 15fd567a96..5f8fa7ac51 100644
--- a/code/modules/jobs/job_types/head_of_security.dm
+++ b/code/modules/jobs/job_types/head_of_security.dm
@@ -18,6 +18,7 @@
exp_type_department = EXP_TYPE_SECURITY
outfit = /datum/outfit/job/hos
+ departments = DEPARTMENT_BITFLAG_SECURITY | DEPARTMENT_BITFLAG_COMMAND
plasma_outfit = /datum/outfit/plasmaman/hos
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
diff --git a/code/modules/jobs/job_types/janitor.dm b/code/modules/jobs/job_types/janitor.dm
index f7e79cb769..fcfbc92e40 100644
--- a/code/modules/jobs/job_types/janitor.dm
+++ b/code/modules/jobs/job_types/janitor.dm
@@ -18,6 +18,7 @@
paycheck_department = ACCOUNT_SRV
display_order = JOB_DISPLAY_ORDER_JANITOR
+ departments = DEPARTMENT_BITFLAG_SERVICE
threat = 0.2
family_heirlooms = list(
diff --git a/code/modules/jobs/job_types/lawyer.dm b/code/modules/jobs/job_types/lawyer.dm
index 4105bd4e6e..8df949a143 100644
--- a/code/modules/jobs/job_types/lawyer.dm
+++ b/code/modules/jobs/job_types/lawyer.dm
@@ -21,8 +21,9 @@
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
display_order = JOB_DISPLAY_ORDER_LAWYER
+ departments = DEPARTMENT_BITFLAG_SERVICE
threat = 0.3
-
+
family_heirlooms = list(
/obj/item/gavelhammer,
/obj/item/book/manual/wiki/security_space_law
diff --git a/code/modules/jobs/job_types/medical_doctor.dm b/code/modules/jobs/job_types/medical_doctor.dm
index b9a313d0e7..52a668b182 100644
--- a/code/modules/jobs/job_types/medical_doctor.dm
+++ b/code/modules/jobs/job_types/medical_doctor.dm
@@ -10,6 +10,7 @@
selection_color = "#74b5e0"
outfit = /datum/outfit/job/doctor
+ departments = DEPARTMENT_BITFLAG_MEDICAL
plasma_outfit = /datum/outfit/plasmaman/medical
access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_VIROLOGY, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/mime.dm b/code/modules/jobs/job_types/mime.dm
index 3929b22f9d..2813c32e14 100644
--- a/code/modules/jobs/job_types/mime.dm
+++ b/code/modules/jobs/job_types/mime.dm
@@ -18,6 +18,7 @@
paycheck_department = ACCOUNT_SRV
display_order = JOB_DISPLAY_ORDER_MIME
+ departments = DEPARTMENT_BITFLAG_SERVICE
threat = 0
diff --git a/code/modules/jobs/job_types/paramedic.dm b/code/modules/jobs/job_types/paramedic.dm
index c3d42ba365..19a955ffe2 100644
--- a/code/modules/jobs/job_types/paramedic.dm
+++ b/code/modules/jobs/job_types/paramedic.dm
@@ -17,6 +17,7 @@
paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_MED
bounty_types = CIV_JOB_MED
+ departments = DEPARTMENT_BITFLAG_MEDICAL
display_order = JOB_DISPLAY_ORDER_PARAMEDIC
diff --git a/code/modules/jobs/job_types/prisoner.dm b/code/modules/jobs/job_types/prisoner.dm
index 5de351573f..732ac03016 100644
--- a/code/modules/jobs/job_types/prisoner.dm
+++ b/code/modules/jobs/job_types/prisoner.dm
@@ -13,6 +13,7 @@
plasma_outfit = /datum/outfit/plasmaman/prisoner
display_order = JOB_DISPLAY_ORDER_PRISONER
+ departments = DEPARTMENT_BITFLAG_SECURITY
family_heirlooms = list(
/obj/item/pen/blue
diff --git a/code/modules/jobs/job_types/quartermaster.dm b/code/modules/jobs/job_types/quartermaster.dm
index efcab13381..b0cdf94825 100644
--- a/code/modules/jobs/job_types/quartermaster.dm
+++ b/code/modules/jobs/job_types/quartermaster.dm
@@ -28,6 +28,7 @@
paycheck = PAYCHECK_HARD //They can already buy stuff using cargo budget, don't give em a command-level paycheck. //alright i'll agree to that -qweq
paycheck_department = ACCOUNT_CAR
bounty_types = CIV_JOB_RANDOM
+ departments = DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_COMMAND
display_order = JOB_DISPLAY_ORDER_QUARTERMASTER
blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity)
diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm
index f4589cae3d..89c9aafeb4 100644
--- a/code/modules/jobs/job_types/research_director.dm
+++ b/code/modules/jobs/job_types/research_director.dm
@@ -18,6 +18,7 @@
considered_combat_role = TRUE
outfit = /datum/outfit/job/rd
+ departments = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_COMMAND
plasma_outfit = /datum/outfit/plasmaman/rd
access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE,
diff --git a/code/modules/jobs/job_types/roboticist.dm b/code/modules/jobs/job_types/roboticist.dm
index 089e5cf506..097023e643 100644
--- a/code/modules/jobs/job_types/roboticist.dm
+++ b/code/modules/jobs/job_types/roboticist.dm
@@ -12,6 +12,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/roboticist
+ departments = DEPARTMENT_BITFLAG_SCIENCE
plasma_outfit = /datum/outfit/plasmaman/robotics
access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM, ACCESS_XENOBIOLOGY, ACCESS_GENETICS)
diff --git a/code/modules/jobs/job_types/scientist.dm b/code/modules/jobs/job_types/scientist.dm
index 5fcfb7e6f6..f7d5c4a11c 100644
--- a/code/modules/jobs/job_types/scientist.dm
+++ b/code/modules/jobs/job_types/scientist.dm
@@ -19,6 +19,7 @@
paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_SCI
bounty_types = CIV_JOB_SCI
+ departments = DEPARTMENT_BITFLAG_SCIENCE
starting_modifiers = list(/datum/skill_modifier/job/level/wiring/basic)
display_order = JOB_DISPLAY_ORDER_SCIENTIST
threat = 1.2
diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm
index 951c517d45..4767987577 100644
--- a/code/modules/jobs/job_types/security_officer.dm
+++ b/code/modules/jobs/job_types/security_officer.dm
@@ -22,6 +22,7 @@
paycheck = PAYCHECK_HARD
paycheck_department = ACCOUNT_SEC
bounty_types = CIV_JOB_SEC
+ departments = DEPARTMENT_BITFLAG_SECURITY
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm
index c6192280c7..7c512ecc0d 100644
--- a/code/modules/jobs/job_types/shaft_miner.dm
+++ b/code/modules/jobs/job_types/shaft_miner.dm
@@ -20,11 +20,12 @@
paycheck = PAYCHECK_EASY ///Not necessarily easy itself, but it can be trivial to make lot of cash on this job.
paycheck_department = ACCOUNT_CAR
bounty_types = CIV_JOB_MINE
+ departments = DEPARTMENT_BITFLAG_CARGO
display_order = JOB_DISPLAY_ORDER_SHAFT_MINER
threat = 1.5
-
+
family_heirlooms = list(
/obj/item/pickaxe/mini,
/obj/item/shovel
diff --git a/code/modules/jobs/job_types/station_engineer.dm b/code/modules/jobs/job_types/station_engineer.dm
index d8ea7e1df5..3ae4adb49b 100644
--- a/code/modules/jobs/job_types/station_engineer.dm
+++ b/code/modules/jobs/job_types/station_engineer.dm
@@ -21,6 +21,7 @@
paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_ENG
bounty_types = CIV_JOB_ENG
+ departments = DEPARTMENT_BITFLAG_ENGINEERING
starting_modifiers = list(/datum/skill_modifier/job/level/wiring, /datum/skill_modifier/job/affinity/wiring)
diff --git a/code/modules/jobs/job_types/virologist.dm b/code/modules/jobs/job_types/virologist.dm
index e5c64acbc8..b9103af84b 100644
--- a/code/modules/jobs/job_types/virologist.dm
+++ b/code/modules/jobs/job_types/virologist.dm
@@ -19,6 +19,7 @@
paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_MED
bounty_types = CIV_JOB_VIRO
+ departments = DEPARTMENT_BITFLAG_MEDICAL
display_order = JOB_DISPLAY_ORDER_VIROLOGIST
diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm
index eda6257653..7ab010b356 100644
--- a/code/modules/jobs/job_types/warden.dm
+++ b/code/modules/jobs/job_types/warden.dm
@@ -23,6 +23,7 @@
paycheck = PAYCHECK_HARD
paycheck_department = ACCOUNT_SEC
bounty_types = CIV_JOB_SEC
+ departments = DEPARTMENT_BITFLAG_SECURITY
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
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/dead/crew_manifest.dm b/code/modules/mob/dead/crew_manifest.dm
new file mode 100644
index 0000000000..c7eb369794
--- /dev/null
+++ b/code/modules/mob/dead/crew_manifest.dm
@@ -0,0 +1,52 @@
+/datum/crew_manifest
+
+/datum/crew_manifest/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/crew_manifest/ui_status(mob/user, datum/ui_state/state)
+ return (isnewplayer(user) || isobserver(user) || isAI(user) || ispAI(user)) ? UI_INTERACTIVE : UI_CLOSE
+
+/datum/crew_manifest/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if (!ui)
+ ui = new(user, src, "CrewManifest")
+ ui.open()
+
+/datum/crew_manifest/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ if (..())
+ return
+
+/datum/crew_manifest/ui_data(mob/user)
+ var/list/positions = list(
+ DEPARTMENT_COMMAND = 0,
+ DEPARTMENT_SECURITY = 0,
+ DEPARTMENT_ENGINEERING = 0,
+ DEPARTMENT_MEDICAL = 0,
+ DEPARTMENT_SCIENCE = 0,
+ DEPARTMENT_CARGO = 0,
+ DEPARTMENT_SERVICE = 0,
+ DEPARTMENT_SILICON = 0
+ )
+ var/list/departments = list(
+ list("flag" = DEPARTMENT_BITFLAG_COMMAND, "name" = DEPARTMENT_COMMAND),
+ list("flag" = DEPARTMENT_BITFLAG_SECURITY, "name" = DEPARTMENT_SECURITY),
+ list("flag" = DEPARTMENT_BITFLAG_ENGINEERING, "name" = DEPARTMENT_ENGINEERING),
+ list("flag" = DEPARTMENT_BITFLAG_MEDICAL, "name" = DEPARTMENT_MEDICAL),
+ list("flag" = DEPARTMENT_BITFLAG_SCIENCE, "name" = DEPARTMENT_SCIENCE),
+ list("flag" = DEPARTMENT_BITFLAG_CARGO, "name" = DEPARTMENT_CARGO),
+ list("flag" = DEPARTMENT_BITFLAG_SERVICE, "name" = DEPARTMENT_SERVICE),
+ list("flag" = DEPARTMENT_BITFLAG_SILICON, "name" = DEPARTMENT_SILICON)
+ )
+
+ for(var/datum/job/job in SSjob.occupations)
+ for(var/department in departments)
+ // Check if the job is part of a department using its flag
+ // Will return true for Research Director if the department is Science or Command, for example
+ if(job.departments & department["flag"])
+ // Add open positions to current department
+ positions[department["name"]] += (job.total_positions - job.current_positions)
+
+ return list(
+ "manifest" = GLOB.data_core.get_manifest(),
+ "positions" = positions
+ )
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index e5fc4ff9a0..49c6baf0d6 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -728,11 +728,10 @@
return
client.crew_manifest_delay = world.time + (1 SECONDS)
- var/dat = ""
- dat += "
Crew Manifest
"
- dat += GLOB.data_core.get_manifest(OOC = 1)
+ if(!GLOB.crew_manifest_tgui)
+ GLOB.crew_manifest_tgui = new /datum/crew_manifest(src)
- src << browse(dat, "window=manifest;size=387x420;can_close=1")
+ GLOB.crew_manifest_tgui.ui_interact(src)
/mob/dead/new_player/Move()
return FALSE
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index bec324fba8..901a3e68d7 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -730,11 +730,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
return
client.crew_manifest_delay = world.time + (1 SECONDS)
- var/dat
- dat += "
Crew Manifest
"
- dat += GLOB.data_core.get_manifest()
+ if(!GLOB.crew_manifest_tgui)
+ GLOB.crew_manifest_tgui = new /datum/crew_manifest(src)
- src << browse(dat, "window=manifest;size=387x420;can_close=1")
+ GLOB.crew_manifest_tgui.ui_interact(src)
//this is called when a ghost is drag clicked to something.
/mob/dead/observer/MouseDrop(atom/over)
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 96fdbb0ffb..83f3eea520 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -37,8 +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.
var/radio_enabled = TRUE //Determins if a carded AI can speak with its built in radio or not.
@@ -101,7 +99,6 @@
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
/// Announcement UI
@@ -189,6 +186,9 @@
builtInCamera = new (src)
builtInCamera.network = list("ss13")
+ alert_control = new(src, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z), camera_view = TRUE)
+ RegisterSignal(alert_control.listener, COMSIG_ALARM_TRIGGERED, PROC_REF(alarm_triggered))
+ RegisterSignal(alert_control.listener, COMSIG_ALARM_CLEARED, PROC_REF(alarm_cleared))
/mob/living/silicon/ai/Destroy()
GLOB.ai_list -= src
@@ -207,7 +207,7 @@
QDEL_NULL(malf_picker)
QDEL_NULL(doomsday_device)
QDEL_NULL(robot_control)
- // QDEL_NULL(alert_control)
+ QDEL_NULL(alert_control)
QDEL_NULL(ai_announcement)
QDEL_NULL(announcement_help)
QDEL_NULL(aiMulti)
@@ -263,12 +263,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"
@@ -279,42 +279,9 @@
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
-
-/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/area/A = alm[1]
- var/C = alm[2]
- var/list/sources = alm[3]
- dat += ""
- 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")
- else if (C && istype(C, /obj/machinery/camera))
- var/obj/machinery/camera/Ctmp = C
- dat += text("-- [] ([])", A.name, Ctmp.c_tag)
- else
- dat += text("-- [] (No Camera)", A.name)
- if (sources.len > 1)
- dat += text("- [] sources", sources.len)
- dat += " \n"
- else
- dat += "-- All Systems Nominal \n"
- dat += " \n"
-
- viewalerts = 1
- src << browse(dat, "window=aialerts&can_close=0")
+ . += "[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_call_shuttle()
if(control_disabled)
@@ -443,15 +410,13 @@
return
..()
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"])
switchCamera(locate(href_list["switchcamera"])) in GLOB.cameranet.cameras
if (href_list["showalerts"])
- ai_alerts()
+ alert_control.ui_interact(src)
#ifdef AI_VOX
if(href_list["say_word"])
play_vox_word(href_list["say_word"], null, src)
@@ -559,77 +524,29 @@
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)
- if (viewalerts)
- ai_alerts()
+ queueAlarm("--- [alarm_type] alarm detected in [home_name]! (No Camera)", alarm_type)
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)
//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 718fa0a8e8..fbc7febe1e 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.")
+ alert_control = new(src, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z))
+ RegisterSignal(alert_control.listener, COMSIG_ALARM_TRIGGERED, PROC_REF(alarm_triggered))
+ RegisterSignal(alert_control.listener, COMSIG_ALARM_CLEARED, PROC_REF(alarm_cleared))
+ alert_control.listener.RegisterSignal(src, COMSIG_LIVING_PREDEATH, TYPE_PROC_REF(/datum/alarm_listener, prevent_alarm_changes))
+ alert_control.listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, TYPE_PROC_REF(/datum/alarm_listener, 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(alert_control)
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"])
+ alert_control.ui_interact(src)
/mob/living/silicon/robot/proc/pick_module()
if(module.type != /obj/item/robot_module)
@@ -199,27 +206,7 @@
if(usr.stat == DEAD)
to_chat(src, "Alert: You are dead.")
return //won't work if dead
- robot_alerts()
-
-/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/area/A = alm[1]
- dat += ""
- dat += text("-- [A.name]")
- dat += " \n"
- else
- dat += "-- All Systems Nominal \n"
- dat += " \n"
-
- var/datum/browser/alerts = new(usr, "robotalerts", "Current Station Alerts", 400, 410)
- alerts.set_content(dat)
- alerts.open()
+ alert_control.ui_interact(src)
/mob/living/silicon/robot/proc/ionpulse()
if(!ionpulse_on)
@@ -267,62 +254,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..43f02f8e30 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
+ /// Station alert datum for showing alerts UI
+ var/datum/station_alert/alert_control
+
//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 590c14a41c..10339b12c9 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -26,8 +26,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]
@@ -85,16 +85,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
@@ -102,70 +93,39 @@
else
alarms_to_clear += message
alarm_types_clear[type] += 1
+ if(in_cooldown)
+ return
+ addtimer(CALLBACK(src, PROC_REF(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)
@@ -412,13 +372,10 @@
return
client.crew_manifest_delay = world.time + (1 SECONDS)
- var/dat = "Crew RosterCrew Roster:
"
+ if(!GLOB.crew_manifest_tgui)
+ GLOB.crew_manifest_tgui = new /datum/crew_manifest(src)
- dat += GLOB.data_core.get_manifest()
- dat += ""
-
- src << browse(dat, "window=airoster")
- onclose(src, "airoster")
+ GLOB.crew_manifest_tgui.ui_interact(src)
/mob/living/silicon/is_literate()
return TRUE
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 55215f7869..61e82583c6 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_REF(alarm_triggered))
+ RegisterSignal(listener, COMSIG_ALARM_CLEARED, PROC_REF(alarm_cleared))
+ listener.RegisterSignal(src, COMSIG_LIVING_PREDEATH, TYPE_PROC_REF(/datum/alarm_listener, prevent_alarm_changes))
+ listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, TYPE_PROC_REF(/datum/alarm_listener, 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()
@@ -235,42 +243,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/computers/item/processor.dm b/code/modules/modular_computers/computers/item/processor.dm
index d569ad24fd..0eaecc5aa2 100644
--- a/code/modules/modular_computers/computers/item/processor.dm
+++ b/code/modules/modular_computers/computers/item/processor.dm
@@ -38,7 +38,7 @@
integrity_failure = machinery_computer.integrity_failure
base_active_power_usage = machinery_computer.base_active_power_usage
base_idle_power_usage = machinery_computer.base_idle_power_usage
- machinery_computer.RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, /obj/machinery/modular_computer/proc/relay_icon_update) //when we update_icon, also update the computer
+ machinery_computer.RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, TYPE_PROC_REF(/obj/machinery/modular_computer, relay_icon_update)) //when we update_icon, also update the computer
/obj/item/modular_computer/processor/relay_qdel()
qdel(machinery_computer)
diff --git a/code/modules/modular_computers/file_system/programs/alarm.dm b/code/modules/modular_computers/file_system/programs/alarm.dm
index 88d52646ac..fd9fc0425f 100644
--- a/code/modules/modular_computers/file_system/programs/alarm.dm
+++ b/code/modules/modular_computers/file_system/programs/alarm.dm
@@ -8,8 +8,22 @@
size = 5
tgui_id = "NtosStationAlertConsole"
program_icon = "bell"
- var/has_alert = 0
- var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list())
+ /// If there is any station alert
+ var/has_alert = FALSE
+ /// Station alert datum for showing alerts UI
+ var/datum/station_alert/alert_control
+
+/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)
+ alert_control = new(computer, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), listener_areas = allowed_areas)
+ RegisterSignal(alert_control.listener, list(COMSIG_ALARM_TRIGGERED, COMSIG_ALARM_CLEARED), PROC_REF(update_alarm_display))
+ return ..()
+
+/datum/computer_file/program/alarm_monitor/Destroy()
+ QDEL_NULL(alert_control)
+ return ..()
/datum/computer_file/program/alarm_monitor/process_tick()
..()
@@ -27,89 +41,14 @@
/datum/computer_file/program/alarm_monitor/ui_data(mob/user)
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
-
+ data += alert_control.ui_data(user)
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(alert_control.listener.alarms))
+ has_alert = TRUE
/datum/computer_file/program/alarm_monitor/run_program(mob/user)
. = ..(user)
@@ -117,4 +56,4 @@
/datum/computer_file/program/alarm_monitor/kill_program(forced = FALSE)
GLOB.alarmdisplay -= src
- ..()
+ return ..()
diff --git a/code/modules/modular_computers/file_system/programs/crewmanifest.dm b/code/modules/modular_computers/file_system/programs/crewmanifest.dm
index 6837f315a4..bc6923773e 100644
--- a/code/modules/modular_computers/file_system/programs/crewmanifest.dm
+++ b/code/modules/modular_computers/file_system/programs/crewmanifest.dm
@@ -12,7 +12,7 @@
/datum/computer_file/program/crew_manifest/ui_static_data(mob/user)
var/list/data = list()
- data["manifest"] = GLOB.data_core.get_manifest_tg()
+ data["manifest"] = GLOB.data_core.get_manifest()
return data
/datum/computer_file/program/crew_manifest/ui_data(mob/user)
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/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm
index 08a804550e..ba5e6a83a0 100644
--- a/code/modules/power/gravitygenerator.dm
+++ b/code/modules/power/gravitygenerator.dm
@@ -161,7 +161,7 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
part.main_part = src
parts += part
part.update_appearance()
- part.RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, /obj/machinery/gravity_generator/part/proc/on_update_icon)
+ part.RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, TYPE_PROC_REF(/obj/machinery/gravity_generator/part, on_update_icon))
/obj/machinery/gravity_generator/main/proc/connected_parts()
return parts.len == 8
diff --git a/tgstation.dme b/tgstation.dme
index 82b0d324bd..e52664207d 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"
@@ -493,6 +494,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"
@@ -529,6 +531,7 @@
#include "code\datums\shuttles.dm"
#include "code\datums\soullink.dm"
#include "code\datums\spawners_menu.dm"
+#include "code\datums\station_alert.dm"
#include "code\datums\tgs_event_handler.dm"
#include "code\datums\verbs.dm"
#include "code\datums\view.dm"
@@ -2662,6 +2665,7 @@
#include "code\modules\mob\transform_procs.dm"
#include "code\modules\mob\typing_indicator.dm"
#include "code\modules\mob\camera\camera.dm"
+#include "code\modules\mob\dead\crew_manifest.dm"
#include "code\modules\mob\dead\dead.dm"
#include "code\modules\mob\dead\new_player\login.dm"
#include "code\modules\mob\dead\new_player\logout.dm"
@@ -3249,7 +3253,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"
diff --git a/tgui/packages/tgui/interfaces/CrewManifest.js b/tgui/packages/tgui/interfaces/CrewManifest.js
index 21ab5eece5..d7ca60141f 100644
--- a/tgui/packages/tgui/interfaces/CrewManifest.js
+++ b/tgui/packages/tgui/interfaces/CrewManifest.js
@@ -1,14 +1,15 @@
-import { classes } from 'common/react';
import { useBackend } from "../backend";
-import { Icon, Section, Table, Tooltip } from "../components";
+import { Icon, Section, Table } from "../components";
import { Window } from "../layouts";
const commandJobs = [
+ "Captain",
"Head of Personnel",
"Head of Security",
"Chief Engineer",
"Research Director",
"Chief Medical Officer",
+ "Quartermaster",
];
export const CrewManifest = (props, context) => {
@@ -17,13 +18,12 @@ export const CrewManifest = (props, context) => {
return (
- {Object.entries(manifest).map(([dept, crew]) => (
+ {Object.entries(manifest).map(([department, crew]) => (