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 47c9c9dec2..3f193ac3c2 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"
@@ -700,3 +704,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 032ae9ded0..14614218ac 100644
--- a/code/__DEFINES/jobs.dm
+++ b/code/__DEFINES/jobs.dm
@@ -103,3 +103,22 @@
#define JOB_DISPLAY_ORDER_BO 38
#define JOB_DISPLAY_ORDER_PRISONER 39
#define JOB_DISPLAY_ORDER_STOWAWAY 40
+
+#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_SUPPLY (1<<3)
+#define DEPARTMENT_SUPPLY "Supply"
+#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 5f8b10fd31..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"
@@ -84,7 +84,7 @@
if(..())
return
var/mob/living/silicon/ai/AI = usr
- AI.announcement()
+ AI.ai_announcement()
/atom/movable/screen/ai/call_shuttle
name = "Call Emergency Shuttle"
diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm
index b345459749..899ad72f2f 100644
--- a/code/_onclick/hud/robot.dm
+++ b/code/_onclick/hud/robot.dm
@@ -367,7 +367,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 0ca8f07c41..b6a6d776be 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/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..d6ec90dbd4 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_SUPPLY
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 22de4a8b27..23b53bd21b 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 bfef66a594..68d32b8178 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 8cdee8c099..1f07f66369 100644
--- a/code/game/machinery/camera/motion.dm
+++ b/code/game/machinery/camera/motion.dm
@@ -51,21 +51,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/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index 4b0b20c8ef..3ac4981927 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -226,7 +226,7 @@
icon_state = "cyborg_upgrade3"
require_module = TRUE
module_type = list(/obj/item/robot_module/miner)
- module_flags = BORG_MODULE_MINER // SANDSTORM EDIT
+ module_flags = BORG_MODULE_MINER
/obj/item/borg/upgrade/premiumka/action(mob/living/silicon/robot/R, user = usr)
. = ..()
diff --git a/code/game/objects/structures/displaycase.dm b/code/game/objects/structures/displaycase.dm
index 88b66e2056..f5c886c024 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
@@ -412,32 +419,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)
@@ -447,7 +454,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
@@ -473,14 +480,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
@@ -489,7 +496,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)
@@ -504,7 +511,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
@@ -515,34 +522,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/client/client_procs.dm b/code/modules/client/client_procs.dm
index bca3903efb..2d5cd2027b 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -981,10 +981,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
addtimer(CALLBACK(SSassets.transport, TYPE_PROC_REF(/datum/asset_transport, send_assets_slow), src, SSassets.transport.preload), 5 SECONDS)
#if (PRELOAD_RSC == 0)
- for (var/name in GLOB.vox_sounds)
- var/file = GLOB.vox_sounds[name]
- Export("##action=load_rsc", file)
- stoplag()
+ for (var/type in GLOB.vox_types)
+ for(var/word in GLOB.vox_types[type])
+ var/file = GLOB.vox_types[type][word]
+ Export("##action=load_rsc", file)
+ stoplag()
#endif
diff --git a/code/modules/integrated_electronics/subtypes/output.dm b/code/modules/integrated_electronics/subtypes/output.dm
index 512b4f3dd2..98137ee76c 100644
--- a/code/modules/integrated_electronics/subtypes/output.dm
+++ b/code/modules/integrated_electronics/subtypes/output.dm
@@ -226,15 +226,28 @@
spawn_flags = IC_SPAWN_RESEARCH
/obj/item/integrated_circuit/output/sound/vox
- name = "ai vox sound circuit"
+ name = "Female ai vox sound circuit"
desc = "Takes a sound name as an input, and will play said sound when pulsed. This circuit is often found in AI announcement systems."
spawn_flags = IC_SPAWN_RESEARCH
+ var/voice_type = "Female"
/obj/item/integrated_circuit/output/sound/vox/Initialize(mapload)
- .= ..()
- sounds = GLOB.vox_sounds
+ sounds = GLOB.vox_types[voice_type]
+ . = ..()
extended_desc = "The first input pin determines which sound is used. It uses the AI Vox Broadcast word list. So either experiment to find words that work, or ask the AI to help in figuring them out. The second pin determines the volume of sound that is played, and the third determines if the frequency of the sound will vary with each activation."
+/obj/item/integrated_circuit/output/sound/vox/male
+ name = "Male ai vox sound circuit"
+ desc = "Takes a sound name as an input, and will play said sound when pulsed. This circuit is often found in AI announcement systems."
+ spawn_flags = IC_SPAWN_RESEARCH
+ voice_type = "Male"
+
+/obj/item/integrated_circuit/output/sound/vox/military
+ name = "Military ai vox sound circuit"
+ desc = "Takes a sound name as an input, and will play said sound when pulsed. This circuit is often found in AI announcement systems."
+ spawn_flags = IC_SPAWN_RESEARCH
+ voice_type = "Military"
+
/obj/item/integrated_circuit/output/text_to_speech
name = "text-to-speech circuit"
desc = "Takes any string as an input and will make the device say the string when pulsed."
diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm
index d491ac7d77..d81684044c 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..7c42e1a8ba 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_SUPPLY
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 8240b457c9..0c526b3bce 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 e9f1c7f57a..c76747c592 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 75fddc9bbc..dd836de146 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 22e0fd8d5f..a8c8eac0ab 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 c998580cae..4a5f0a6280 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 177610bdc6..533475ca78 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 e2c5c7ad8b..47f9b657d6 100644
--- a/code/modules/jobs/job_types/quartermaster.dm
+++ b/code/modules/jobs/job_types/quartermaster.dm
@@ -29,6 +29,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_SUPPLY | 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 966d7677ac..4aa043ac4a 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..714ce1e9aa 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_SUPPLY
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..5f6dd5671c
--- /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_SUPPLY = 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_SUPPLY, "name" = DEPARTMENT_SUPPLY),
+ 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 f9cd9f749a..b001d08d9c 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -738,11 +738,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 41dd7f3630..c4a755cc2e 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -732,11 +732,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 f12d4207a1..dce6233112 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,9 +99,12 @@
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
+ var/datum/ai_announcement/ai_announcement
+ /// Lists possible spoken words for announcements
+ var/datum/announcement_help/announcement_help
///remember AI's last location
var/atom/lastloc
interaction_range = INFINITY
@@ -185,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
@@ -203,7 +207,9 @@
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)
QDEL_NULL(aiPDA)
malfhack = null
@@ -257,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"
@@ -273,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)
@@ -437,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)
@@ -553,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/ai/ai_announcement.dm b/code/modules/mob/living/silicon/ai/ai_announcement.dm
new file mode 100644
index 0000000000..4165dec6bd
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai/ai_announcement.dm
@@ -0,0 +1,59 @@
+#ifdef AI_VOX
+/mob/living/silicon/ai/verb/ai_announcement()
+ set name = "Vox Announcement"
+ set desc = "Make an audible announcement!"
+ set category = "AI Commands"
+
+ if(incapacitated())
+ return
+
+ if(!ai_announcement)
+ ai_announcement = new(src)
+
+ ai_announcement.ui_interact(src)
+
+/datum/ai_announcement
+ var/mob/living/silicon/ai/owner
+
+/datum/ai_announcement/New(mob/living/silicon/ai/new_owner)
+ if(!istype(new_owner))
+ qdel(src)
+ owner = new_owner
+
+/datum/ai_announcement/ui_status(mob/living/silicon/ai/user)
+ if(owner == user && !owner.incapacitated())
+ return ..()
+ return UI_CLOSE
+
+/datum/ai_announcement/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/ai_announcement/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "AIAnnouncement")
+ ui.open()
+
+/datum/ai_announcement/ui_data(mob/user)
+ var/list/data = ..()
+ data["last_announcement"] = owner.last_announcement
+ return data
+
+/datum/ai_announcement/ui_static_data(mob/user)
+ var/list/data = ..()
+ data["vox_types"] = GLOB.vox_types
+ return data
+
+/datum/ai_announcement/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ switch(action)
+ if("announce")
+ var/vox_type = params["vox_type"]
+ if(!vox_type)
+ return
+ var/to_speak = params["to_speak"]
+ if(!to_speak)
+ return
+ owner.announcement(vox_type, to_speak)
+ ui.close()
+#endif
diff --git a/code/modules/mob/living/silicon/ai/announcement_help.dm b/code/modules/mob/living/silicon/ai/announcement_help.dm
new file mode 100644
index 0000000000..f22b3ce4cb
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai/announcement_help.dm
@@ -0,0 +1,54 @@
+#ifdef AI_VOX
+/mob/living/silicon/ai/verb/announcement_help()
+
+ set name = "Announcement Help"
+ set desc = "Display a list of vocal words to announce to the crew."
+ set category = "AI Commands"
+
+ if(incapacitated())
+ return
+
+ if(!announcement_help)
+ announcement_help = new(src)
+
+ announcement_help.ui_interact(src)
+
+/datum/announcement_help
+ var/mob/living/silicon/ai/owner
+
+/datum/announcement_help/New(mob/living/silicon/ai/new_owner)
+ if(!istype(new_owner))
+ qdel(src)
+ owner = new_owner
+
+/datum/announcement_help/ui_status(mob/living/silicon/ai/user)
+ if(owner == user && !owner.incapacitated())
+ return ..()
+ return UI_CLOSE
+
+/datum/announcement_help/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/announcement_help/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "AnnouncementHelp")
+ ui.open()
+
+/datum/announcement_help/ui_static_data(mob/user)
+ var/list/data = ..()
+ data["vox_types"] = GLOB.vox_types
+ return data
+
+/datum/announcement_help/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ switch(action)
+ if("say_word")
+ var/vox_type = params["vox_type"]
+ if(!vox_type)
+ return
+ var/to_speak = params["to_speak"]
+ if(!to_speak)
+ return
+ play_vox_word(to_speak, null, owner, vox_type)
+#endif
diff --git a/code/modules/mob/living/silicon/ai/say.dm b/code/modules/mob/living/silicon/ai/say.dm
index 364fbbadf7..2a05266587 100644
--- a/code/modules/mob/living/silicon/ai/say.dm
+++ b/code/modules/mob/living/silicon/ai/say.dm
@@ -79,59 +79,23 @@
// Make sure that the code compiles with AI_VOX undefined
#ifdef AI_VOX
-#define VOX_DELAY 600
-/mob/living/silicon/ai/verb/announcement_help()
-
- set name = "Announcement Help"
- set desc = "Display a list of vocal words to announce to the crew."
- set category = "AI Commands"
-
- if(incapacitated())
- return
-
- var/dat = {"
- WARNING: Misuse of the announcement system will get you job banned.
- Here is a list of words you can type into the 'Announcement' button to create sentences to vocally announce to everyone on the same level at you.
-
You can also click on the word to PREVIEW it.
-
You can only say 30 words for every announcement.
-
Do not use punctuation as you would normally, if you want a pause you can use the full stop and comma characters by separating them with spaces, like so: 'Alpha . Test , Bravo'.
-
Numbers are in word format, e.g. eight, sixty, etc
-
Sound effects begin with an 's' before the actual word, e.g. scensor
These words are not available on the announcement system: {missing_words.join(" ")}
: null}
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/AnnouncementHelp.js b/tgui/packages/tgui/interfaces/AnnouncementHelp.js
new file mode 100644
index 0000000000..1869a21eab
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AnnouncementHelp.js
@@ -0,0 +1,100 @@
+import { filter } from 'common/collections';
+import { flow } from 'common/fp';
+import { createSearch } from 'common/string';
+import { useBackend } from '../backend';
+import { Button, Collapsible, Icon, Input, Section, Stack, Tabs } from '../components';
+import { useLocalState } from '../backend';
+import { Window } from '../layouts';
+
+export const AnnouncementHelp = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { vox_types = {} } = data;
+
+ const [
+ current_page,
+ set_page,
+ ] = useLocalState(context, 'current_page', 0);
+
+ const [
+ search_text,
+ set_search_text,
+ ] = useLocalState(context, 'search_text', '');
+
+ // I love `Object`s!!
+ const words_filtered = prepare_search(Object.keys(vox_types[Object.keys(vox_types)[current_page]]), search_text);
+
+ return (
+
+
+
+
+
+
+ WARNING: Misuse of the announcement system will get you job banned.
+ Here is a list of words you can type into the 'Announcement' button to create sentences to vocally announce to everyone on the same level at you.
+
You can also click on the word to PREVIEW it.
+
You can only say 30 words for every announcement.
+
Do not use punctuation as you would normally, if you want a pause you can use the full stop and comma characters by separating them with spaces, like so: 'Alpha . Test , Bravo'.
+
Numbers are in word format, e.g. eight, sixty, etc
+
Sound effects begin with an 's' before the actual word, e.g. scensor