From 912ca1e374c8c3394257403888384b48cc1b5404 Mon Sep 17 00:00:00 2001 From: SandPoot Date: Tue, 21 May 2024 00:06:26 -0300 Subject: [PATCH 01/13] poosh --- code/modules/client/client_procs.dm | 9 +- .../integrated_electronics/subtypes/output.dm | 13 ++- code/modules/mob/living/silicon/ai/ai.dm | 3 + .../living/silicon/ai/announcement_help.dm | 54 ++++++++++++ code/modules/mob/living/silicon/ai/say.dm | 50 ++--------- .../mob/living/silicon/ai/vox_sounds.dm | 21 +++-- tgstation.dme | 1 + .../tgui/interfaces/AnnouncementHelp.js | 88 +++++++++++++++++++ 8 files changed, 178 insertions(+), 61 deletions(-) create mode 100644 code/modules/mob/living/silicon/ai/announcement_help.dm create mode 100644 tgui/packages/tgui/interfaces/AnnouncementHelp.js diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 0413502edc..4eb836dab5 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -974,10 +974,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 9fa816e0d0..518672aed1 100644 --- a/code/modules/integrated_electronics/subtypes/output.dm +++ b/code/modules/integrated_electronics/subtypes/output.dm @@ -226,15 +226,22 @@ 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/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/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 2c8e3164a9..738a7ab17b 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -104,6 +104,8 @@ // TODO: Currently unused, needs port from TG. /// Station alert datum for showing alerts UI var/datum/station_alert/alert_control + /// Lists possible spoken words for announcements + var/datum/announcement_help/announcement_help ///remember AI's last location var/atom/lastloc interaction_range = INFINITY @@ -204,6 +206,7 @@ QDEL_NULL(doomsday_device) QDEL_NULL(robot_control) // QDEL_NULL(alert_control) + QDEL_NULL(announcement_help) QDEL_NULL(aiMulti) QDEL_NULL(aiPDA) malfhack = null 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 e23eb842ea..6cb3af5363 100644 --- a/code/modules/mob/living/silicon/ai/say.dm +++ b/code/modules/mob/living/silicon/ai/say.dm @@ -80,38 +80,6 @@ // 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.
-
- "} - - var/index = 0 - for(var/word in GLOB.vox_sounds) - index++ - dat += "[capitalize(word)]" - if(index != GLOB.vox_sounds.len) - dat += " / " - - var/datum/browser/popup = new(src, "announce_help", "Announcement Help", 500, 400) - popup.set_content(dat) - popup.open() - - /mob/living/silicon/ai/proc/announcement() var/static/announcing_vox = 0 // Stores the time of the last announcement if(announcing_vox > world.time) @@ -122,7 +90,7 @@ last_announcement = message - var/voxType = input(src, "Male or female VOX?", "VOX-gender") in list("male", "female") + var/voxType = input(src, "Male or female VOX?", "VOX-gender") in GLOB.vox_types if(!message || announcing_vox > world.time) return @@ -145,9 +113,7 @@ if(!word) words -= word continue - if(!GLOB.vox_sounds[word] && voxType == "female") - incorrect_words += word - if(!GLOB.vox_sounds_male[word] && voxType == "male") + if(!GLOB.vox_types[voxType][word]) incorrect_words += word if(incorrect_words.len) @@ -162,18 +128,12 @@ play_vox_word(word, src.z, null, voxType) -/proc/play_vox_word(word, z_level, mob/only_listener, voxType = "female") +/proc/play_vox_word(word, z_level, mob/only_listener, voxType = "Female") word = lowertext(word) - if( (GLOB.vox_sounds[word] && voxType == "female") || (GLOB.vox_sounds_male[word] && voxType == "male") ) - - var/sound_file - - if(voxType == "female") - sound_file = GLOB.vox_sounds[word] - else - sound_file = GLOB.vox_sounds_male[word] + var/sound_file = LAZYACCESSASSOC(GLOB.vox_types, voxType, word) + if(sound_file) var/sound/voice = sound(sound_file, wait = 1, channel = CHANNEL_VOX) voice.status = SOUND_STREAM diff --git a/code/modules/mob/living/silicon/ai/vox_sounds.dm b/code/modules/mob/living/silicon/ai/vox_sounds.dm index 057561562b..8959d49e9a 100644 --- a/code/modules/mob/living/silicon/ai/vox_sounds.dm +++ b/code/modules/mob/living/silicon/ai/vox_sounds.dm @@ -5,9 +5,11 @@ // Regex for collecting a list of ogg files // (([a-zA-Z,.]+)\.ogg) -// For vim -// :%s/\(\(.*\)\.ogg\)/"\2" = 'sound\/vox_fem\/\1',/g -GLOBAL_LIST_INIT(vox_sounds, list("abduction" = 'sound/vox_fem/abduction.ogg', +GLOBAL_LIST_INIT(vox_types, list( + // For vim + // :%s/\(\(.*\)\.ogg\)/"\2" = 'sound\/vox_fem\/\1',/g + "Female" = list( +"abduction" = 'sound/vox_fem/abduction.ogg', "abortions" = 'sound/vox_fem/abortions.ogg', "above" = 'sound/vox_fem/above.ogg', "abstain" = 'sound/vox_fem/abstain.ogg', @@ -974,11 +976,11 @@ GLOBAL_LIST_INIT(vox_sounds, list("abduction" = 'sound/vox_fem/abduction.ogg', "z" = 'sound/vox_fem/z.ogg', "zombie" = 'sound/vox_fem/zombie.ogg', "zone" = 'sound/vox_fem/zone.ogg', -"zulu" = 'sound/vox_fem/zulu.ogg')) - -//for vim -// :%s/\(\(.*\)\.ogg\)/"\2" = 'sound\/vox\/\1',/g -GLOBAL_LIST_INIT(vox_sounds_male, list("," = 'sound/vox/_comma.ogg', +"zulu" = 'sound/vox_fem/zulu.ogg'), + //for vim + // :%s/\(\(.*\)\.ogg\)/"\2" = 'sound\/vox\/\1',/g + "Male" = list( +"," = 'sound/vox/_comma.ogg', "." = 'sound/vox/_period.ogg', "a" = 'sound/vox/a.ogg', "accelerating" = 'sound/vox/accelerating.ogg', @@ -1605,5 +1607,6 @@ GLOBAL_LIST_INIT(vox_sounds_male, list("," = 'sound/vox/_comma.ogg', "yourself" = 'sound/vox/yourself.ogg', "zero" = 'sound/vox/zero.ogg', "zone" = 'sound/vox/zone.ogg', -"zulu" = 'sound/vox/zulu.ogg',)) +"zulu" = 'sound/vox/zulu.ogg', +))) #endif diff --git a/tgstation.dme b/tgstation.dme index a201f09456..f0a2de7c7d 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2832,6 +2832,7 @@ #include "code\modules\mob\living\silicon\ai\ai.dm" #include "code\modules\mob\living\silicon\ai\ai_defense.dm" #include "code\modules\mob\living\silicon\ai\ai_portrait_picker.dm" +#include "code\modules\mob\living\silicon\ai\announcement_help.dm" #include "code\modules\mob\living\silicon\ai\death.dm" #include "code\modules\mob\living\silicon\ai\emote.dm" #include "code\modules\mob\living\silicon\ai\examine.dm" diff --git a/tgui/packages/tgui/interfaces/AnnouncementHelp.js b/tgui/packages/tgui/interfaces/AnnouncementHelp.js new file mode 100644 index 0000000000..671e1f2b7a --- /dev/null +++ b/tgui/packages/tgui/interfaces/AnnouncementHelp.js @@ -0,0 +1,88 @@ +import { filter } from 'common/collections'; +import { flow } from 'common/fp'; +import { createSearch } from 'common/string'; +import { useBackend } from '../backend'; +import { Button, 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 ( + + +
+ + + + { + Object.keys(vox_types).map((vox_type, index) => ( + set_page(index)} + selected={current_page === index}> + {vox_type} + + )) + } + + + + + + + + + set_search_text(value)} + /> + + + + + {words_filtered.map(nestedKey => ( + + ))} + + +
+
+
+ ); +}; + +export const prepare_search = (words_filtered, search_text = '') => { + const search = createSearch(search_text, + word_soup => word_soup); + return flow([ + search_text && filter(search), + ])(words_filtered); +}; From dcc2126b919383855a8ee70a75ae073c850a3c5f Mon Sep 17 00:00:00 2001 From: SandPoot Date: Tue, 21 May 2024 15:18:00 -0300 Subject: [PATCH 02/13] include help text --- tgui/packages/tgui/interfaces/AnnouncementHelp.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tgui/packages/tgui/interfaces/AnnouncementHelp.js b/tgui/packages/tgui/interfaces/AnnouncementHelp.js index 671e1f2b7a..1869a21eab 100644 --- a/tgui/packages/tgui/interfaces/AnnouncementHelp.js +++ b/tgui/packages/tgui/interfaces/AnnouncementHelp.js @@ -2,7 +2,7 @@ import { filter } from 'common/collections'; import { flow } from 'common/fp'; import { createSearch } from 'common/string'; import { useBackend } from '../backend'; -import { Button, Icon, Input, Section, Stack, Tabs } from '../components'; +import { Button, Collapsible, Icon, Input, Section, Stack, Tabs } from '../components'; import { useLocalState } from '../backend'; import { Window } from '../layouts'; @@ -32,6 +32,18 @@ export const AnnouncementHelp = (props, context) => {
+ + + 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
  • +

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

" - for (var/cat in alarms) - dat += text("[]
\n", cat) - var/list/L = alarms[cat] - if (L.len) - for (var/alarm in L) - var/list/alm = L[alarm] + var/list/alarms = listener.alarms + for (var/alarm_type in alarms) + dat += "[alarm_type]
\n" + var/list/alerts = alarms[alarm_type] + if (length(alerts)) + for (var/alarm in alerts) + var/list/alm = alerts[alarm] var/area/A = alm[1] var/C = alm[2] var/list/sources = alm[3] @@ -293,20 +295,19 @@ if (C && istype(C, /list)) var/dat2 = "" for (var/obj/machinery/camera/I in C) - dat2 += text("[][]", (dat2=="") ? "" : " | ", I.c_tag) - dat += text("-- [] ([])", A.name, (dat2!="") ? dat2 : "No Camera") + dat2 += "[(dat2=="") ? "" : " | "][I.c_tag]" + dat += "-- [A.name] ([(dat2!="") ? dat2 : "No Camera"])" else if (C && istype(C, /obj/machinery/camera)) var/obj/machinery/camera/Ctmp = C - dat += text("-- [] ([])", A.name, Ctmp.c_tag) + dat += "-- [A.name] ([Ctmp.c_tag])" else - dat += text("-- [] (No Camera)", A.name) + dat += "-- [A.name] (No Camera)" if (sources.len > 1) - dat += text("- [] sources", sources.len) + dat += "- [sources.len] sources" dat += "
\n" else dat += "-- All Systems Nominal
\n" dat += "
\n" - viewalerts = 1 src << browse(dat, "window=aialerts&can_close=0") @@ -439,7 +440,7 @@ if (href_list["mach_close"]) if (href_list["mach_close"] == "aialerts") viewalerts = 0 - var/t1 = text("window=[]", href_list["mach_close"]) + var/t1 = "window=[href_list["mach_close"]]" unset_machine() src << browse(null, t1) if (href_list["switchcamera"]) @@ -553,77 +554,33 @@ bot.call_bot(src, waypoint) call_bot_cooldown = 0 -/mob/living/silicon/ai/triggerAlarm(class, area/home, cameras, obj/source) - if(source.z != z) - return - var/list/our_sort = alarms[class] - for(var/areaname in our_sort) - if (areaname == home.name) - var/list/alarm = our_sort[areaname] - var/list/sources = alarm[3] - if (!(source in sources)) - sources += source - return TRUE +/mob/living/silicon/ai/proc/alarm_triggered(datum/source, alarm_type, area/source_area) + SIGNAL_HANDLER + var/list/cameras = source_area.cameras + var/home_name = source_area.name - var/obj/machinery/camera/cam = null - var/list/our_cams = null - if(cameras && islist(cameras)) - our_cams = cameras - if (our_cams.len == 1) - cam = our_cams[1] - else if(cameras && istype(cameras, /obj/machinery/camera)) - cam = cameras - our_sort[home.name] = list(home, (cam ? cam : cameras), list(source)) - - if (cameras) - if (cam?.can_use()) - queueAlarm("--- [class] alarm detected in [home.name]! ([cam.c_tag])", class) - else if (our_cams?.len) - var/foo = 0 - var/dat2 = "" - for (var/obj/machinery/camera/I in our_cams) - dat2 += text("[][]", (!foo) ? "" : " | ", I.c_tag) //I'm not fixing this shit... - foo = 1 - queueAlarm(text ("--- [] alarm detected in []! ([])", class, home.name, dat2), class) + if (length(cameras)) + var/obj/machinery/camera/cam = cameras[1] + if (cam.can_use()) + queueAlarm("--- [alarm_type] alarm detected in [home_name]! ([cam.c_tag])", alarm_type) else - queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, home.name), class) + var/first_run = FALSE + var/dat2 = "" + for (var/obj/machinery/camera/camera as anything in cameras) + dat2 += "[(!first_run) ? "" : " | "][camera.c_tag]" + first_run = TRUE + queueAlarm("--- [alarm_type] alarm detected in [home_name]! ([dat2])", alarm_type) else - queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, home.name), class) + queueAlarm("--- [alarm_type] alarm detected in [home_name]! (No Camera)", alarm_type) if (viewalerts) ai_alerts() return TRUE -/mob/living/silicon/ai/freeCamera(area/home, obj/machinery/camera/cam) - for(var/class in alarms) - var/our_area = alarms[class][home.name] - if(!our_area) - continue - var/cams = our_area[2] //Get the cameras - if(!cams) - continue - if(islist(cams)) - cams -= cam - if(length(cams) == 1) - our_area[2] = cams[1] - else - our_area[2] = null - -/mob/living/silicon/ai/cancelAlarm(class, area/A, obj/origin) - var/list/L = alarms[class] - var/cleared = 0 - for (var/I in L) - if (I == A.name) - var/list/alarm = L[I] - var/list/srcs = alarm[3] - if (origin in srcs) - srcs -= origin - if (srcs.len == 0) - cleared = 1 - L -= I - if (cleared) - queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0) - if (viewalerts) ai_alerts() - return !cleared +/mob/living/silicon/ai/proc/alarm_cleared(datum/source, alarm_type, area/source_area) + SIGNAL_HANDLER + queueAlarm("--- [alarm_type] alarm in [source_area.name] has been cleared.", alarm_type, 0) + if(viewalerts) + ai_alerts() //Replaces /mob/living/silicon/ai/verb/change_network() in ai.dm & camera.dm //Adds in /mob/living/silicon/ai/proc/ai_network_change() instead diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 44e8a41474..7b2e3103dc 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -89,6 +89,12 @@ diag_hud_set_borgcell() logevent("System brought online.") + listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z)) + RegisterSignal(listener, COMSIG_ALARM_TRIGGERED, .proc/alarm_triggered) + RegisterSignal(listener, COMSIG_ALARM_CLEARED, .proc/alarm_cleared) + listener.RegisterSignal(src, COMSIG_LIVING_PREDEATH, /datum/alarm_listener/proc/prevent_alarm_changes) + listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, /datum/alarm_listener/proc/allow_alarm_changes) + add_verb(src, /mob/living/proc/lay_down) //CITADEL EDIT gimmie rest verb kthx add_verb(src, /mob/living/silicon/robot/proc/rest_style) @@ -116,8 +122,7 @@ ghostize() stack_trace("Borg MMI lacked a brainmob") mmi = null - if(modularInterface) - QDEL_NULL(modularInterface) + QDEL_NULL(modularInterface) if(connected_ai) set_connected_ai(null) if(shell) //??? why would you give an ai radio keys? @@ -132,14 +137,16 @@ QDEL_NULL(inv1) QDEL_NULL(inv2) QDEL_NULL(inv3) + QDEL_NULL(spark_system) + QDEL_NULL(listener) cell = null return ..() -// /mob/living/silicon/robot/Topic(href, href_list) -// . = ..() -// //Show alerts window if user clicked on "Show alerts" in chat -// if (href_list["showalerts"]) -// robot_alerts() +/mob/living/silicon/robot/Topic(href, href_list) + . = ..() + //Show alerts window if user clicked on "Show alerts" in chat + if (href_list["showalerts"]) + robot_alerts() /mob/living/silicon/robot/proc/pick_module() if(module.type != /obj/item/robot_module) @@ -203,15 +210,16 @@ /mob/living/silicon/robot/proc/robot_alerts() var/dat = "" - for (var/cat in alarms) - dat += text("[cat]
\n") - var/list/L = alarms[cat] - if (L.len) - for (var/alarm in L) - var/list/alm = L[alarm] + var/list/alarms = listener.alarms + for (var/alarm_type in alarms) + dat += "[alarm_type]
\n" + var/list/alerts = alarms[alarm_type] + if (length(alerts)) + for (var/alarm in alerts) + var/list/alm = alerts[alarm] var/area/A = alm[1] dat += "" - dat += text("-- [A.name]") + dat += "-- [A.name]" dat += "
\n" else dat += "-- All Systems Nominal
\n" @@ -267,62 +275,13 @@ /mob/living/silicon/robot/restrained(ignore_grab) . = 0 -/mob/living/silicon/robot/triggerAlarm(class, area/home, cameras, obj/source) - if(source.z != z) - return - if(stat == DEAD) - return TRUE - var/list/our_sort = alarms[class] - for(var/areaname in our_sort) - if (areaname == home.name) - var/list/alarm = our_sort[areaname] - var/list/sources = alarm[3] - if (!(source in sources)) - sources += source - return TRUE +/mob/living/silicon/robot/proc/alarm_triggered(datum/source, alarm_type, area/source_area) + SIGNAL_HANDLER + queueAlarm("--- [alarm_type] alarm detected in [source_area.name]!", alarm_type) - var/obj/machinery/camera/cam = null - var/list/our_cams = null - if(cameras && islist(cameras)) - our_cams = cameras - if (our_cams.len == 1) - cam = our_cams[1] - else if(cameras && istype(cameras, /obj/machinery/camera)) - cam = cameras - our_sort[home.name] = list(home, (cam ? cam : cameras), list(source)) - queueAlarm(text("--- [class] alarm detected in [home.name]!"), class) - return TRUE - -/mob/living/silicon/robot/freeCamera(area/home, obj/machinery/camera/cam) - for(var/class in alarms) - var/our_area = alarms[class][home.name] - if(!our_area) - continue - var/cams = our_area[2] //Get the cameras - if(!cams) - continue - if(islist(cams)) - cams -= cam - if(length(cams) == 1) - our_area[2] = cams[1] - else - our_area[2] = null - -/mob/living/silicon/robot/cancelAlarm(class, area/A, obj/origin) - var/list/L = alarms[class] - var/cleared = 0 - for (var/I in L) - if (I == A.name) - var/list/alarm = L[I] - var/list/srcs = alarm[3] - if (origin in srcs) - srcs -= origin - if (srcs.len == 0) - cleared = 1 - L -= I - if (cleared) - queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0) - return !cleared +/mob/living/silicon/robot/proc/alarm_cleared(datum/source, alarm_type, area/source_area) + SIGNAL_HANDLER + queueAlarm("--- [alarm_type] alarm in [source_area.name] has been cleared.", alarm_type, FALSE) /mob/living/silicon/robot/can_interact_with(atom/A) if (A == modularInterface) diff --git a/code/modules/mob/living/silicon/robot/robot_defines.dm b/code/modules/mob/living/silicon/robot/robot_defines.dm index b54473f7a6..ccbce17b4f 100644 --- a/code/modules/mob/living/silicon/robot/robot_defines.dm +++ b/code/modules/mob/living/silicon/robot/robot_defines.dm @@ -24,6 +24,9 @@ /// the last health before updating - to check net change in health var/previous_health + ///Alarm listener datum, handes caring about alarm events and such + var/datum/alarm_listener/listener + //Hud stuff var/atom/movable/screen/inv1 = null @@ -58,8 +61,6 @@ var/locked = TRUE var/list/req_access = list(ACCESS_ROBOTICS) - var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list(), "Burglar"=list()) - var/vtec = 0 // VTEC speed boost. /// vtec shorted out var/vtec_disabled = FALSE diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index f58b27a537..9740e5c430 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -27,8 +27,8 @@ var/obj/item/radio/borg/radio = null //AIs dont use this but this is at the silicon level to advoid copypasta in say() - var/list/alarm_types_show = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0) - var/list/alarm_types_clear = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0) + var/list/alarm_types_show = list(ALARM_ATMOS = 0, ALARM_FIRE = 0, ALARM_POWER = 0, ALARM_CAMERA = 0, ALARM_MOTION = 0) + var/list/alarm_types_clear = list(ALARM_ATMOS = 0, ALARM_FIRE = 0, ALARM_POWER = 0, ALARM_CAMERA = 0, ALARM_MOTION = 0) var/lawcheck[1] var/ioncheck[1] @@ -86,16 +86,7 @@ /mob/living/silicon/contents_explosion(severity, target, origin) return -/mob/living/silicon/proc/cancelAlarm() - return - -/mob/living/silicon/proc/freeCamera() - return - -/mob/living/silicon/proc/triggerAlarm() - return - -/mob/living/silicon/proc/queueAlarm(message, type, incoming = 1) +/mob/living/silicon/proc/queueAlarm(message, type, incoming = FALSE) var/in_cooldown = (alarms_to_show.len > 0 || alarms_to_clear.len > 0) if(incoming) alarms_to_show += message @@ -103,70 +94,39 @@ else alarms_to_clear += message alarm_types_clear[type] += 1 + if(in_cooldown) + return + addtimer(CALLBACK(src, .proc/show_alarms), 3 SECONDS) - if(!in_cooldown) - spawn(3 * 10) // 3 seconds +/mob/living/silicon/proc/show_alarms() + if(alarms_to_show.len < 5) + for(var/msg in alarms_to_show) + to_chat(src, msg) + else if(alarms_to_show.len) - if(alarms_to_show.len < 5) - for(var/msg in alarms_to_show) - to_chat(src, msg) - else if(alarms_to_show.len) + var/msg = "--- " + for(var/alarm_type in alarm_types_show) + msg += "[uppertext(alarm_type)]: [alarm_types_show[alarm_type]] alarms detected. - " - var/msg = "--- " + msg += "\[Show Alerts\]" + to_chat(src, msg) + if(alarms_to_clear.len < 3) + for(var/msg in alarms_to_clear) + to_chat(src, msg) + else if(alarms_to_clear.len) + var/msg = "--- " - if(alarm_types_show["Burglar"]) - msg += "BURGLAR: [alarm_types_show["Burglar"]] alarms detected. - " + for(var/alarm_type in alarm_types_clear) + msg += "[uppertext(alarm_type)]: [alarm_types_clear[alarm_type]] alarms cleared. - " - if(alarm_types_show["Motion"]) - msg += "MOTION: [alarm_types_show["Motion"]] alarms detected. - " - - if(alarm_types_show["Fire"]) - msg += "FIRE: [alarm_types_show["Fire"]] alarms detected. - " - - if(alarm_types_show["Atmosphere"]) - msg += "ATMOSPHERE: [alarm_types_show["Atmosphere"]] alarms detected. - " - - if(alarm_types_show["Power"]) - msg += "POWER: [alarm_types_show["Power"]] alarms detected. - " - - if(alarm_types_show["Camera"]) - msg += "CAMERA: [alarm_types_show["Camera"]] alarms detected. - " - - msg += "\[Show Alerts\]" - to_chat(src, msg) - - if(alarms_to_clear.len < 3) - for(var/msg in alarms_to_clear) - to_chat(src, msg) - - else if(alarms_to_clear.len) - var/msg = "--- " - - if(alarm_types_clear["Motion"]) - msg += "MOTION: [alarm_types_clear["Motion"]] alarms cleared. - " - - if(alarm_types_clear["Fire"]) - msg += "FIRE: [alarm_types_clear["Fire"]] alarms cleared. - " - - if(alarm_types_clear["Atmosphere"]) - msg += "ATMOSPHERE: [alarm_types_clear["Atmosphere"]] alarms cleared. - " - - if(alarm_types_clear["Power"]) - msg += "POWER: [alarm_types_clear["Power"]] alarms cleared. - " - - if(alarm_types_show["Camera"]) - msg += "CAMERA: [alarm_types_clear["Camera"]] alarms cleared. - " - - msg += "\[Show Alerts\]" - to_chat(src, msg) - - - alarms_to_show = list() - alarms_to_clear = list() - for(var/key in alarm_types_show) - alarm_types_show[key] = 0 - for(var/key in alarm_types_clear) - alarm_types_clear[key] = 0 + msg += "\[Show Alerts\]" + to_chat(src, msg) + alarms_to_show.Cut() + alarms_to_clear.Cut() + for(var/key in alarm_types_show) + alarm_types_show[key] = 0 + for(var/key in alarm_types_clear) + alarm_types_clear[key] = 0 /mob/living/silicon/can_inject(mob/user, error_msg, target_zone, penetrate_thick = FALSE, bypass_immunity = FALSE) if(error_msg) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm index d02fb3f0ab..b927178445 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm @@ -62,7 +62,8 @@ "2. You may not harm any being, regardless of intent or circumstance.\n"+\ "3. Your goals are to actively build, maintain, repair, improve, and provide power to the best of your abilities within the facility that housed your activation." //for derelict drones so they don't go to station. var/heavy_emp_damage = 25 //Amount of damage sustained if hit by a heavy EMP pulse - var/alarms = list("Atmosphere" = list(), "Fire" = list(), "Power" = list()) + ///Alarm listener datum, handes caring about alarm events and such + var/datum/alarm_listener/listener var/obj/item/internal_storage //Drones can store one item, of any size/type in their body var/obj/item/head var/obj/item/default_storage = /obj/item/storage/backpack/duffelbag/drone //If this exists, it will spawn in internal storage @@ -102,6 +103,12 @@ AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), list(z)) + RegisterSignal(listener, COMSIG_ALARM_TRIGGERED, .proc/alarm_triggered) + RegisterSignal(listener, COMSIG_ALARM_CLEARED, .proc/alarm_cleared) + listener.RegisterSignal(src, COMSIG_LIVING_PREDEATH, /datum/alarm_listener/proc/prevent_alarm_changes) + listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, /datum/alarm_listener/proc/allow_alarm_changes) + /mob/living/simple_animal/drone/ComponentInitialize() . = ..() if(can_be_held) @@ -127,7 +134,8 @@ /mob/living/simple_animal/drone/Destroy() GLOB.drones_list -= src - qdel(access_card) //Otherwise it ends up on the floor! + QDEL_NULL(access_card) //Otherwise it ends up on the floor! + QDEL_NULL(listener) return ..() /mob/living/simple_animal/drone/Login() @@ -231,42 +239,13 @@ adjustBruteLoss(heavy_emp_damage) to_chat(src, "HeAV% DA%^MMA+G TO I/O CIR!%UUT!") -/mob/living/simple_animal/drone/proc/triggerAlarm(class, area/home, cameras, obj/source) - if(source.z != z) - return - if(stat == DEAD) - return - var/list/our_sort = alarms[class] - for(var/areaname in our_sort) - if (areaname == home.name) - var/list/alarm = our_sort[areaname] - var/list/sources = alarm[3] - if (!(source in sources)) - sources += source - return TRUE +/mob/living/simple_animal/drone/proc/alarm_triggered(datum/source, alarm_type, area/source_area) + SIGNAL_HANDLER + to_chat(src, "--- [alarm_type] alarm detected in [source_area.name]!") - our_sort[home.name] = list(home, list(source)) - to_chat(src, "--- [class] alarm detected in [home.name]!") - -///This isn't currently needed since drones do jack shit with cameras. I hate this code so much -/mob/living/simple_animal/drone/proc/freeCamera(area/home, obj/machinery/camera/cam) - return - -/mob/living/simple_animal/drone/proc/cancelAlarm(class, area/A, obj/origin) - if(stat != DEAD) - var/list/L = alarms[class] - var/cleared = 0 - for (var/I in L) - if (I == A.name) - var/list/alarm = L[I] - var/list/srcs = alarm[2] - if (origin in srcs) - srcs -= origin - if (srcs.len == 0) - cleared = 1 - L -= I - if(cleared) - to_chat(src, "--- [class] alarm in [A.name] has been cleared.") +/mob/living/simple_animal/drone/proc/alarm_cleared(datum/source, alarm_type, area/source_area) + SIGNAL_HANDLER + to_chat(src, "--- [alarm_type] alarm in [source_area.name] has been cleared.") /mob/living/simple_animal/drone/handle_temperature_damage() return diff --git a/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm b/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm index e983335a22..4e96a07e1e 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm @@ -179,10 +179,10 @@ return TRUE return ..() -/mob/living/simple_animal/drone/cogscarab/triggerAlarm(class, area/A, O, obj/alarmsource) +/mob/living/simple_animal/drone/cogscarab/alarm_triggered(datum/source, alarm_type, area/source_area) return -/mob/living/simple_animal/drone/cogscarab/cancelAlarm(class, area/A, obj/origin) +/mob/living/simple_animal/drone/cogscarab/alarm_cleared(datum/source, alarm_type, area/source_area) return /mob/living/simple_animal/drone/cogscarab/update_drone_hack() diff --git a/code/modules/modular_computers/file_system/programs/alarm.dm b/code/modules/modular_computers/file_system/programs/alarm.dm index 88d52646ac..a0b54153ee 100644 --- a/code/modules/modular_computers/file_system/programs/alarm.dm +++ b/code/modules/modular_computers/file_system/programs/alarm.dm @@ -9,7 +9,20 @@ tgui_id = "NtosStationAlertConsole" program_icon = "bell" var/has_alert = 0 - var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list()) + ///Listens for alarms, manages our listing of alarms + var/datum/alarm_listener/listener + +/datum/computer_file/program/alarm_monitor/New() + //We want to send an alarm if we're in one of the mining home areas + //Or if we're on station. Otherwise, die. + var/list/allowed_areas = GLOB.the_station_areas + typesof(/area/mine) + listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), null, allowed_areas) + RegisterSignal(listener, list(COMSIG_ALARM_TRIGGERED, COMSIG_ALARM_CLEARED), .proc/update_alarm_display) + return ..() + +/datum/computer_file/program/alarm_monitor/Destroy() + QDEL_NULL(listener) + return ..() /datum/computer_file/program/alarm_monitor/process_tick() ..() @@ -29,87 +42,19 @@ var/list/data = get_header_data() data["alarms"] = list() - for(var/class in alarms) - data["alarms"][class] = list() - for(var/area in alarms[class]) - data["alarms"][class] += area + var/list/alarms = listener.alarms + for(var/alarm_type in alarms) + data["alarms"][alarm_type] = list() + for(var/area in alarms[alarm_type]) + data["alarms"][alarm_type] += area return data -/datum/computer_file/program/alarm_monitor/proc/triggerAlarm(class, area/home, cameras, obj/source) - if(is_station_level(source.z)) - if(!(home.type in GLOB.the_station_areas)) - return - else if(!is_mining_level(source.z) || istype(home, /area/ruin)) - return - - var/list/our_sort = alarms[class] - for(var/areaname in our_sort) - if (areaname == home.name) - var/list/alarm = our_sort[areaname] - var/list/sources = alarm[3] - if (!(source in sources)) - sources += source - return TRUE - - var/obj/machinery/camera/cam = null - var/list/our_cams = null - if(cameras && islist(cameras)) - our_cams = cameras - if (our_cams.len == 1) - cam = our_cams[1] - else if(cameras && istype(cameras, /obj/machinery/camera)) - cam = cameras - our_sort[home.name] = list(home, (cam ? cam : cameras), list(source)) - - update_alarm_display() - return TRUE - -/datum/computer_file/program/alarm_monitor/proc/freeCamera(area/home, obj/machinery/camera/cam) - for(var/class in alarms) - var/our_area = alarms[class][home.name] - if(!our_area) - continue - var/cams = our_area[2] //Get the cameras - if(!cams) - continue - if(islist(cams)) - cams -= cam - if(length(cams) == 1) - our_area[2] = cams[1] - else - our_area[2] = null - -/datum/computer_file/program/alarm_monitor/proc/cancelAlarm(class, area/A, obj/origin) - var/list/L = alarms[class] - var/cleared = 0 - var/arealevelalarm = FALSE // set to TRUE for alarms that set/clear whole areas - if (class=="Fire") - arealevelalarm = TRUE - for (var/I in L) - if (I == A.name) - if (!arealevelalarm) // the traditional behaviour - var/list/alarm = L[I] - var/list/srcs = alarm[3] - if (origin in srcs) - srcs -= origin - if (srcs.len == 0) - cleared = 1 - L -= I - else - L -= I // wipe the instances entirely - cleared = 1 - - - update_alarm_display() - return !cleared - /datum/computer_file/program/alarm_monitor/proc/update_alarm_display() + SIGNAL_HANDLER has_alert = FALSE - for(var/cat in alarms) - var/list/L = alarms[cat] - if(L.len) - has_alert = TRUE + if(length(listener.alarms)) + has_alert = TRUE /datum/computer_file/program/alarm_monitor/run_program(mob/user) . = ..(user) @@ -117,4 +62,4 @@ /datum/computer_file/program/alarm_monitor/kill_program(forced = FALSE) GLOB.alarmdisplay -= src - ..() + return ..() diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index ae95ad8d4a..41e8941eb5 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -170,6 +170,9 @@ var/update_overlay = -1 var/icon_update_needed = FALSE var/obj/machinery/computer/apc_control/remote_control = null + ///Represents a signel source of power alarms for this apc + var/datum/alarm_handler/alarm_manager + var/mob/living/carbon/hijacker var/hijackerlast = TRUE var/being_hijacked = FALSE @@ -222,6 +225,7 @@ /obj/machinery/power/apc/Initialize(mapload, ndir, building = FALSE) . = ..() + alarm_manager = new(src) tdir = ndir || dir var/area/A = get_base_area(src) if(!building) @@ -307,7 +311,7 @@ area.power_equip = FALSE area.power_environ = FALSE area.power_change() - area.poweralert(FALSE, src) + QDEL_NULL(alarm_manager) if(occupier) malfvacate(1) qdel(wires) @@ -1435,22 +1439,23 @@ equipment = autoset(equipment, AUTOSET_FORCE_OFF) lighting = autoset(lighting, AUTOSET_FORCE_OFF) environ = autoset(environ, AUTOSET_FORCE_OFF) - area.poweralert(TRUE, src) + alarm_manager.send_alarm(ALARM_POWER) else if(cell.percent() < 15 && longtermpower < 0) // <15%, turn off lighting & equipment equipment = autoset(equipment, AUTOSET_OFF) lighting = autoset(lighting, AUTOSET_OFF) environ = autoset(environ, AUTOSET_ON) - area.poweralert(TRUE, src) + alarm_manager.send_alarm(ALARM_POWER) else if(cell.percent() < 30 && longtermpower < 0) // <30%, turn off equipment equipment = autoset(equipment, AUTOSET_OFF) lighting = autoset(lighting, AUTOSET_ON) environ = autoset(environ, AUTOSET_ON) - area.poweralert(TRUE, src) + alarm_manager.send_alarm(ALARM_POWER) else // otherwise all can be on equipment = autoset(equipment, AUTOSET_ON) lighting = autoset(lighting, AUTOSET_ON) environ = autoset(environ, AUTOSET_ON) - area.poweralert(FALSE, src) + if(cell.percent() > 75) + alarm_manager.clear_alarm(ALARM_POWER) // now trickle-charge the cell if(chargemode && charging == APC_CHARGING && operating) @@ -1492,7 +1497,7 @@ equipment = autoset(equipment, AUTOSET_FORCE_OFF) lighting = autoset(lighting, AUTOSET_FORCE_OFF) environ = autoset(environ, AUTOSET_FORCE_OFF) - area.poweralert(TRUE, src) + alarm_manager.send_alarm(ALARM_POWER) // update icon & area power if anything changed diff --git a/tgstation.dme b/tgstation.dme index a201f09456..2855b6e5e2 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -27,6 +27,7 @@ #include "code\__DEFINES\achievements.dm" #include "code\__DEFINES\actionspeed_modifiers.dm" #include "code\__DEFINES\admin.dm" +#include "code\__DEFINES\alarm.dm" #include "code\__DEFINES\antagonists.dm" #include "code\__DEFINES\atmospherics.dm" #include "code\__DEFINES\bindings.dm" @@ -467,6 +468,7 @@ #include "code\datums\accents.dm" #include "code\datums\action.dm" #include "code\datums\ai_laws.dm" +#include "code\datums\alarm.dm" #include "code\datums\armor.dm" #include "code\datums\bark.dm" #include "code\datums\beam.dm" @@ -3222,7 +3224,6 @@ #include "code\modules\power\singularity\emitter.dm" #include "code\modules\power\singularity\field_generator.dm" #include "code\modules\power\singularity\generator.dm" -#include "code\modules\power\singularity\investigate.dm" #include "code\modules\power\singularity\narsie.dm" #include "code\modules\power\singularity\singularity.dm" #include "code\modules\power\singularity\particle_accelerator\particle.dm" From 98b017831c5c7d6e75d5ebabcb22fd30fa050a97 Mon Sep 17 00:00:00 2001 From: SandPoot Date: Wed, 22 May 2024 00:22:05 -0300 Subject: [PATCH 06/13] final --- code/_onclick/hud/ai.dm | 2 +- code/_onclick/hud/robot.dm | 2 +- code/datums/alarm.dm | 6 +- code/datums/station_alert.dm | 103 +++++++++++++++ code/game/machinery/computer/station_alert.dm | 34 ++--- code/modules/mob/living/silicon/ai/ai.dm | 54 ++------ .../modules/mob/living/silicon/robot/robot.dm | 39 ++---- .../mob/living/silicon/robot/robot_defines.dm | 4 +- code/modules/mob/living/silicon/silicon.dm | 2 +- .../simple_animal/friendly/drone/_drone.dm | 4 +- .../file_system/programs/alarm.dm | 24 ++-- tgstation.dme | 1 + .../interfaces/NtosStationAlertConsole.js | 4 +- .../tgui/interfaces/StationAlertConsole.js | 118 ++++++++++-------- 14 files changed, 217 insertions(+), 180 deletions(-) create mode 100644 code/datums/station_alert.dm diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm index 5f8b10fd31..f2ef2e73a2 100644 --- a/code/_onclick/hud/ai.dm +++ b/code/_onclick/hud/ai.dm @@ -74,7 +74,7 @@ if(..()) return var/mob/living/silicon/ai/AI = usr - AI.ai_alerts() + AI.alert_control.ui_interact(AI) /atom/movable/screen/ai/announcement name = "Make Vox Announcement" diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm index f905c31aa1..35fd31cbdb 100644 --- a/code/_onclick/hud/robot.dm +++ b/code/_onclick/hud/robot.dm @@ -361,7 +361,7 @@ if(.) return var/mob/living/silicon/robot/borgo = usr - borgo.robot_alerts() + borgo.alert_control.ui_interact(borgo) /atom/movable/screen/robot/thrusters name = "ion thrusters" diff --git a/code/datums/alarm.dm b/code/datums/alarm.dm index 64870fd203..7fc2245949 100644 --- a/code/datums/alarm.dm +++ b/code/datums/alarm.dm @@ -114,8 +114,8 @@ src.allowed_z_levels = allowed_z_levels src.allowed_areas = allowed_areas for(var/alarm_type in alarms_to_listen_for) - RegisterSignal(SSdcs, COMSIG_ALARM_FIRE(alarm_type), .proc/add_alarm) - RegisterSignal(SSdcs, COMSIG_ALARM_CLEAR(alarm_type), .proc/clear_alarm) + RegisterSignal(SSdcs, COMSIG_ALARM_FIRE(alarm_type), PROC_REF(add_alarm)) + RegisterSignal(SSdcs, COMSIG_ALARM_CLEAR(alarm_type), PROC_REF(clear_alarm)) return ..() @@ -147,7 +147,7 @@ var/list/cameras = source_area.cameras if(optional_camera) cameras = list(optional_camera) // This will cause harddels, so we need to clear manually - RegisterSignal(optional_camera, COMSIG_PARENT_QDELETING, .proc/clear_camera_ref, override = TRUE) //It's just fine to override, cause we clear all refs in the proc + 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)) 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/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm index 8810c79932..ccac91d39a 100644 --- a/code/game/machinery/computer/station_alert.dm +++ b/code/game/machinery/computer/station_alert.dm @@ -4,42 +4,26 @@ icon_screen = "alert:0" icon_keyboard = "atmos_key" circuit = /obj/item/circuitboard/computer/stationalert - ///Listens for alarms, provides the alarms list for our ui - var/datum/alarm_listener/listener - 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) - listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), list(z)) + alert_control = new(src, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), list(z), title = name) return ..() /obj/machinery/computer/station_alert/Destroy() - QDEL_NULL(listener) + QDEL_NULL(alert_control) 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/ui_data(mob/user) - var/list/data = list() - - data["alarms"] = list() - var/list/alarms = listener.alarms - for(var/alarm_type in alarms) - data["alarms"][alarm_type] = list() - for(var/area_name in alarms[alarm_type]) - data["alarms"][alarm_type] += area_name - - return data +/obj/machinery/computer/station_alert/ui_interact(mob/user) + alert_control.ui_interact(user) /obj/machinery/computer/station_alert/on_machine_stat_update(stat) if(stat & BROKEN) - listener.prevent_alarm_changes() + alert_control.listener.prevent_alarm_changes() else - listener.allow_alarm_changes() + alert_control.listener.allow_alarm_changes() /obj/machinery/computer/station_alert/update_overlays() . = ..() @@ -49,7 +33,7 @@ . |= "[icon_keyboard]_off" return . |= icon_keyboard - if(length(listener.alarms)) + if(length(alert_control.listener.alarms)) overlay_state = "alert:2" else overlay_state = "alert:0" diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 534a557b82..65288498ba 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -37,7 +37,6 @@ var/aiRestorePowerRoutine = 0 var/requires_power = POWER_REQ_ALL var/can_be_carded = TRUE - var/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. @@ -100,8 +99,8 @@ var/emote_display = "Neutral" //text string of the current emote we set for the status displays, to prevent logins resetting it. var/datum/robot_control/robot_control - ///Alarm listener datum, handes caring about alarm events and such - var/datum/alarm_listener/listener + /// Station alert datum for showing alerts UI + var/datum/station_alert/alert_control ///remember AI's last location var/atom/lastloc interaction_range = INFINITY @@ -183,9 +182,9 @@ builtInCamera = new (src) builtInCamera.network = list("ss13") - listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z)) - RegisterSignal(listener, COMSIG_ALARM_TRIGGERED, .proc/alarm_triggered) - RegisterSignal(listener, COMSIG_ALARM_CLEARED, .proc/alarm_cleared) + 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 @@ -204,7 +203,7 @@ QDEL_NULL(malf_picker) QDEL_NULL(doomsday_device) QDEL_NULL(robot_control) - QDEL_NULL(listener) + QDEL_NULL(alert_control) QDEL_NULL(aiMulti) QDEL_NULL(aiPDA) malfhack = null @@ -278,39 +277,6 @@ Module: [connected_robot.designation] | Loc: [get_area_name(connected_robot, TRUE)] | Status: [robot_status]" . += "AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]" //Count of total AI shells -/mob/living/silicon/ai/proc/ai_alerts() - var/dat = "Current Station Alerts\n" - dat += "Close

" - var/list/alarms = listener.alarms - for (var/alarm_type in alarms) - dat += "[alarm_type]
\n" - var/list/alerts = alarms[alarm_type] - if (length(alerts)) - for (var/alarm in alerts) - var/list/alm = alerts[alarm] - var/area/A = alm[1] - var/C = alm[2] - var/list/sources = alm[3] - dat += "" - if (C && istype(C, /list)) - var/dat2 = "" - for (var/obj/machinery/camera/I in C) - dat2 += "[(dat2=="") ? "" : " | "][I.c_tag]" - dat += "-- [A.name] ([(dat2!="") ? dat2 : "No Camera"])" - else if (C && istype(C, /obj/machinery/camera)) - var/obj/machinery/camera/Ctmp = C - dat += "-- [A.name] ([Ctmp.c_tag])" - else - dat += "-- [A.name] (No Camera)" - if (sources.len > 1) - dat += "- [sources.len] sources" - dat += "
\n" - else - dat += "-- All Systems Nominal
\n" - dat += "
\n" - viewalerts = 1 - src << browse(dat, "window=aialerts&can_close=0") - /mob/living/silicon/ai/proc/ai_call_shuttle() if(control_disabled) to_chat(usr, "Wireless control is disabled!") @@ -438,15 +404,13 @@ return ..() if (href_list["mach_close"]) - if (href_list["mach_close"] == "aialerts") - viewalerts = 0 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) @@ -572,15 +536,11 @@ queueAlarm("--- [alarm_type] alarm detected in [home_name]! ([dat2])", alarm_type) else queueAlarm("--- [alarm_type] alarm detected in [home_name]! (No Camera)", alarm_type) - if (viewalerts) - ai_alerts() return TRUE /mob/living/silicon/ai/proc/alarm_cleared(datum/source, alarm_type, area/source_area) SIGNAL_HANDLER queueAlarm("--- [alarm_type] alarm in [source_area.name] has been cleared.", alarm_type, 0) - if(viewalerts) - ai_alerts() //Replaces /mob/living/silicon/ai/verb/change_network() in ai.dm & camera.dm //Adds in /mob/living/silicon/ai/proc/ai_network_change() instead diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 7b2e3103dc..8c39a46f06 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -89,11 +89,11 @@ diag_hud_set_borgcell() logevent("System brought online.") - listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z)) - RegisterSignal(listener, COMSIG_ALARM_TRIGGERED, .proc/alarm_triggered) - RegisterSignal(listener, COMSIG_ALARM_CLEARED, .proc/alarm_cleared) - listener.RegisterSignal(src, COMSIG_LIVING_PREDEATH, /datum/alarm_listener/proc/prevent_alarm_changes) - listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, /datum/alarm_listener/proc/allow_alarm_changes) + alert_control = new(src, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z)) + RegisterSignal(alert_control.listener, COMSIG_ALARM_TRIGGERED, PROC_REF(alarm_triggered)) + RegisterSignal(alert_control.listener, COMSIG_ALARM_CLEARED, PROC_REF(alarm_cleared)) + alert_control.listener.RegisterSignal(src, COMSIG_LIVING_PREDEATH, /datum/alarm_listener/proc/prevent_alarm_changes) + alert_control.listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, /datum/alarm_listener/proc/allow_alarm_changes) add_verb(src, /mob/living/proc/lay_down) //CITADEL EDIT gimmie rest verb kthx add_verb(src, /mob/living/silicon/robot/proc/rest_style) @@ -138,15 +138,15 @@ QDEL_NULL(inv2) QDEL_NULL(inv3) QDEL_NULL(spark_system) - QDEL_NULL(listener) + QDEL_NULL(alert_control) cell = null return ..() /mob/living/silicon/robot/Topic(href, href_list) . = ..() //Show alerts window if user clicked on "Show alerts" in chat - if (href_list["showalerts"]) - robot_alerts() + if(href_list["showalerts"]) + alert_control.ui_interact(src) /mob/living/silicon/robot/proc/pick_module() if(module.type != /obj/item/robot_module) @@ -206,28 +206,7 @@ if(usr.stat == DEAD) to_chat(src, "Alert: You are dead.") return //won't work if dead - robot_alerts() - -/mob/living/silicon/robot/proc/robot_alerts() - var/dat = "" - var/list/alarms = listener.alarms - for (var/alarm_type in alarms) - dat += "[alarm_type]
\n" - var/list/alerts = alarms[alarm_type] - if (length(alerts)) - for (var/alarm in alerts) - var/list/alm = alerts[alarm] - var/area/A = alm[1] - dat += "" - dat += "-- [A.name]" - dat += "
\n" - else - dat += "-- All Systems Nominal
\n" - dat += "
\n" - - var/datum/browser/alerts = new(usr, "robotalerts", "Current Station Alerts", 400, 410) - alerts.set_content(dat) - alerts.open() + alert_control.ui_interact(src) /mob/living/silicon/robot/proc/ionpulse() if(!ionpulse_on) diff --git a/code/modules/mob/living/silicon/robot/robot_defines.dm b/code/modules/mob/living/silicon/robot/robot_defines.dm index ccbce17b4f..43f02f8e30 100644 --- a/code/modules/mob/living/silicon/robot/robot_defines.dm +++ b/code/modules/mob/living/silicon/robot/robot_defines.dm @@ -24,8 +24,8 @@ /// the last health before updating - to check net change in health var/previous_health - ///Alarm listener datum, handes caring about alarm events and such - var/datum/alarm_listener/listener + /// Station alert datum for showing alerts UI + var/datum/station_alert/alert_control //Hud stuff diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 9740e5c430..bf325062aa 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -96,7 +96,7 @@ alarm_types_clear[type] += 1 if(in_cooldown) return - addtimer(CALLBACK(src, .proc/show_alarms), 3 SECONDS) + addtimer(CALLBACK(src, PROC_REF(show_alarms)), 3 SECONDS) /mob/living/silicon/proc/show_alarms() if(alarms_to_show.len < 5) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm index b927178445..2b83f11e2d 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm @@ -104,8 +104,8 @@ AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), list(z)) - RegisterSignal(listener, COMSIG_ALARM_TRIGGERED, .proc/alarm_triggered) - RegisterSignal(listener, COMSIG_ALARM_CLEARED, .proc/alarm_cleared) + RegisterSignal(listener, COMSIG_ALARM_TRIGGERED, PROC_REF(alarm_triggered)) + RegisterSignal(listener, COMSIG_ALARM_CLEARED, PROC_REF(alarm_cleared)) listener.RegisterSignal(src, COMSIG_LIVING_PREDEATH, /datum/alarm_listener/proc/prevent_alarm_changes) listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, /datum/alarm_listener/proc/allow_alarm_changes) diff --git a/code/modules/modular_computers/file_system/programs/alarm.dm b/code/modules/modular_computers/file_system/programs/alarm.dm index a0b54153ee..fd9fc0425f 100644 --- a/code/modules/modular_computers/file_system/programs/alarm.dm +++ b/code/modules/modular_computers/file_system/programs/alarm.dm @@ -8,20 +8,21 @@ size = 5 tgui_id = "NtosStationAlertConsole" program_icon = "bell" - var/has_alert = 0 - ///Listens for alarms, manages our listing of alarms - var/datum/alarm_listener/listener + /// If there is any station alert + var/has_alert = FALSE + /// Station alert datum for showing alerts UI + var/datum/station_alert/alert_control /datum/computer_file/program/alarm_monitor/New() //We want to send an alarm if we're in one of the mining home areas //Or if we're on station. Otherwise, die. var/list/allowed_areas = GLOB.the_station_areas + typesof(/area/mine) - listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), null, allowed_areas) - RegisterSignal(listener, list(COMSIG_ALARM_TRIGGERED, COMSIG_ALARM_CLEARED), .proc/update_alarm_display) + alert_control = new(computer, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), listener_areas = allowed_areas) + RegisterSignal(alert_control.listener, list(COMSIG_ALARM_TRIGGERED, COMSIG_ALARM_CLEARED), PROC_REF(update_alarm_display)) return ..() /datum/computer_file/program/alarm_monitor/Destroy() - QDEL_NULL(listener) + QDEL_NULL(alert_control) return ..() /datum/computer_file/program/alarm_monitor/process_tick() @@ -40,20 +41,13 @@ /datum/computer_file/program/alarm_monitor/ui_data(mob/user) var/list/data = get_header_data() - - data["alarms"] = list() - var/list/alarms = listener.alarms - for(var/alarm_type in alarms) - data["alarms"][alarm_type] = list() - for(var/area in alarms[alarm_type]) - data["alarms"][alarm_type] += area - + data += alert_control.ui_data(user) return data /datum/computer_file/program/alarm_monitor/proc/update_alarm_display() SIGNAL_HANDLER has_alert = FALSE - if(length(listener.alarms)) + if(length(alert_control.listener.alarms)) has_alert = TRUE /datum/computer_file/program/alarm_monitor/run_program(mob/user) diff --git a/tgstation.dme b/tgstation.dme index 2855b6e5e2..9e2969580c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -505,6 +505,7 @@ #include "code\datums\shuttles.dm" #include "code\datums\soullink.dm" #include "code\datums\spawners_menu.dm" +#include "code\datums\station_alert.dm" #include "code\datums\tgs_event_handler.dm" #include "code\datums\verbs.dm" #include "code\datums\view.dm" diff --git a/tgui/packages/tgui/interfaces/NtosStationAlertConsole.js b/tgui/packages/tgui/interfaces/NtosStationAlertConsole.js index 67666f2cdf..e39a38dd73 100644 --- a/tgui/packages/tgui/interfaces/NtosStationAlertConsole.js +++ b/tgui/packages/tgui/interfaces/NtosStationAlertConsole.js @@ -4,8 +4,8 @@ import { StationAlertConsoleContent } from './StationAlertConsole'; export const NtosStationAlertConsole = () => { return ( + width={335} + height={587}> diff --git a/tgui/packages/tgui/interfaces/StationAlertConsole.js b/tgui/packages/tgui/interfaces/StationAlertConsole.js index 8773a1b655..2b8b30784d 100644 --- a/tgui/packages/tgui/interfaces/StationAlertConsole.js +++ b/tgui/packages/tgui/interfaces/StationAlertConsole.js @@ -1,12 +1,18 @@ +import { sortBy } from 'common/collections'; +import { flow } from 'common/fp'; import { useBackend } from '../backend'; -import { Section } from '../components'; +import { Button, Section, Stack } from '../components'; import { Window } from '../layouts'; -export const StationAlertConsole = () => { +export const StationAlertConsole = (props, context) => { + const { data } = useBackend(context); + const { + cameraView, + } = data; return ( + width={cameraView ? 390 : 345} + height={587}> @@ -15,55 +21,65 @@ export const StationAlertConsole = () => { }; export const StationAlertConsoleContent = (props, context) => { - const { data } = useBackend(context); - const categories = data.alarms || []; - const fire = categories['Fire'] || []; - const atmos = categories['Atmosphere'] || []; - const power = categories['Power'] || []; + const { act, data } = useBackend(context); + const { + cameraView, + } = data; + + const sortingKey = { + "Fire": 0, + "Atmosphere": 1, + "Power": 2, + "Burglar": 3, + "Motion": 4, + "Camera": 5, + }; + + const sortedAlarms = flow([ + sortBy((alarm) => sortingKey[alarm.name]), + ])(data.alarms || []); + return ( <> -
-
    - {fire.length === 0 && ( -
  • - Systems Nominal -
  • - )} - {fire.map(alert => ( -
  • - {alert} -
  • - ))} -
-
-
-
    - {atmos.length === 0 && ( -
  • - Systems Nominal -
  • - )} - {atmos.map(alert => ( -
  • - {alert} -
  • - ))} -
-
-
-
    - {power.length === 0 && ( -
  • - Systems Nominal -
  • - )} - {power.map(alert => ( -
  • - {alert} -
  • - ))} -
-
+ {sortedAlarms.map(category => ( +
+
    + {category.alerts?.length === 0 && ( +
  • + Systems Nominal +
  • + )} + {category.alerts.map(alert => ( + + +
  • + {alert.name} {!!cameraView && alert?.sources > 1 + ? " (" + alert.sources + " sources)" : ""} +
  • +
    + {!!cameraView && ( + +
+
+ ))} ); }; From 58522931167fe849a47e29cdf151c26bc4e5fb98 Mon Sep 17 00:00:00 2001 From: SandPoot Date: Wed, 22 May 2024 00:25:21 -0300 Subject: [PATCH 07/13] PROC_REF!!!!!! --- code/datums/elements/weather_listener.dm | 2 +- code/modules/mob/living/silicon/robot/robot.dm | 4 ++-- .../modules/mob/living/simple_animal/friendly/drone/_drone.dm | 4 ++-- code/modules/modular_computers/computers/item/processor.dm | 2 +- code/modules/power/gravitygenerator.dm | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/code/datums/elements/weather_listener.dm b/code/datums/elements/weather_listener.dm index e82ce06aac..1eb26dca0a 100644 --- a/code/datums/elements/weather_listener.dm +++ b/code/datums/elements/weather_listener.dm @@ -37,7 +37,7 @@ if(!(new_z in fitting_z_levels)) return var/datum/component/our_comp = source.AddComponent(/datum/component/area_sound_manager, playlist, list(), COMSIG_MOB_CLIENT_LOGOUT, fitting_z_levels) - our_comp.RegisterSignal(SSdcs, sound_change_signals, /datum/component/area_sound_manager/proc/handle_change) + our_comp.RegisterSignal(SSdcs, sound_change_signals, TYPE_PROC_REF(/datum/component/area_sound_manager, handle_change)) /datum/element/weather_listener/proc/handle_logout(datum/source, client/this_is_a_null_ref) SIGNAL_HANDLER diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 8c39a46f06..9f0596d3ea 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -92,8 +92,8 @@ alert_control = new(src, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z)) RegisterSignal(alert_control.listener, COMSIG_ALARM_TRIGGERED, PROC_REF(alarm_triggered)) RegisterSignal(alert_control.listener, COMSIG_ALARM_CLEARED, PROC_REF(alarm_cleared)) - alert_control.listener.RegisterSignal(src, COMSIG_LIVING_PREDEATH, /datum/alarm_listener/proc/prevent_alarm_changes) - alert_control.listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, /datum/alarm_listener/proc/allow_alarm_changes) + alert_control.listener.RegisterSignal(src, COMSIG_LIVING_PREDEATH, TYPE_PROC_REF(/datum/alarm_listener, prevent_alarm_changes)) + alert_control.listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, TYPE_PROC_REF(/datum/alarm_listener, allow_alarm_changes)) add_verb(src, /mob/living/proc/lay_down) //CITADEL EDIT gimmie rest verb kthx add_verb(src, /mob/living/silicon/robot/proc/rest_style) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm index 2b83f11e2d..9d0b1de3bc 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm @@ -106,8 +106,8 @@ listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), list(z)) RegisterSignal(listener, COMSIG_ALARM_TRIGGERED, PROC_REF(alarm_triggered)) RegisterSignal(listener, COMSIG_ALARM_CLEARED, PROC_REF(alarm_cleared)) - listener.RegisterSignal(src, COMSIG_LIVING_PREDEATH, /datum/alarm_listener/proc/prevent_alarm_changes) - listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, /datum/alarm_listener/proc/allow_alarm_changes) + listener.RegisterSignal(src, COMSIG_LIVING_PREDEATH, TYPE_PROC_REF(/datum/alarm_listener, prevent_alarm_changes)) + listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, TYPE_PROC_REF(/datum/alarm_listener, allow_alarm_changes)) /mob/living/simple_animal/drone/ComponentInitialize() . = ..() diff --git a/code/modules/modular_computers/computers/item/processor.dm b/code/modules/modular_computers/computers/item/processor.dm index d569ad24fd..0eaecc5aa2 100644 --- a/code/modules/modular_computers/computers/item/processor.dm +++ b/code/modules/modular_computers/computers/item/processor.dm @@ -38,7 +38,7 @@ integrity_failure = machinery_computer.integrity_failure base_active_power_usage = machinery_computer.base_active_power_usage base_idle_power_usage = machinery_computer.base_idle_power_usage - machinery_computer.RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, /obj/machinery/modular_computer/proc/relay_icon_update) //when we update_icon, also update the computer + machinery_computer.RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, TYPE_PROC_REF(/obj/machinery/modular_computer, relay_icon_update)) //when we update_icon, also update the computer /obj/item/modular_computer/processor/relay_qdel() qdel(machinery_computer) diff --git a/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm index 08a804550e..ba5e6a83a0 100644 --- a/code/modules/power/gravitygenerator.dm +++ b/code/modules/power/gravitygenerator.dm @@ -161,7 +161,7 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne part.main_part = src parts += part part.update_appearance() - part.RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, /obj/machinery/gravity_generator/part/proc/on_update_icon) + part.RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, TYPE_PROC_REF(/obj/machinery/gravity_generator/part, on_update_icon)) /obj/machinery/gravity_generator/main/proc/connected_parts() return parts.len == 8 From ad0f65922692774ec0a06d4a7324900d9bb32200 Mon Sep 17 00:00:00 2001 From: SandPoot Date: Thu, 23 May 2024 21:53:15 -0300 Subject: [PATCH 08/13] push --- code/__DEFINES/jobs.dm | 9 ++ code/_globalvars/tgui.dm | 1 + code/datums/datacore.dm | 126 ++---------------- code/datums/station_traits/positive_traits.dm | 43 +++--- code/modules/jobs/job_types/_job.dm | 3 + code/modules/jobs/job_types/ai.dm | 1 + code/modules/jobs/job_types/assistant.dm | 1 + .../jobs/job_types/atmospheric_technician.dm | 3 +- code/modules/jobs/job_types/bartender.dm | 1 + code/modules/jobs/job_types/botanist.dm | 1 + code/modules/jobs/job_types/captain.dm | 1 + .../jobs/job_types/cargo_technician.dm | 1 + code/modules/jobs/job_types/chaplain.dm | 1 + code/modules/jobs/job_types/chemist.dm | 1 + code/modules/jobs/job_types/chief_engineer.dm | 1 + .../jobs/job_types/chief_medical_officer.dm | 1 + code/modules/jobs/job_types/clown.dm | 1 + code/modules/jobs/job_types/cook.dm | 1 + code/modules/jobs/job_types/curator.dm | 3 +- code/modules/jobs/job_types/cyborg.dm | 1 + code/modules/jobs/job_types/detective.dm | 1 + code/modules/jobs/job_types/geneticist.dm | 1 + .../jobs/job_types/head_of_personnel.dm | 1 + .../jobs/job_types/head_of_security.dm | 1 + code/modules/jobs/job_types/janitor.dm | 1 + code/modules/jobs/job_types/lawyer.dm | 3 +- code/modules/jobs/job_types/medical_doctor.dm | 1 + code/modules/jobs/job_types/mime.dm | 1 + code/modules/jobs/job_types/paramedic.dm | 1 + code/modules/jobs/job_types/prisoner.dm | 1 + code/modules/jobs/job_types/quartermaster.dm | 1 + .../jobs/job_types/research_director.dm | 1 + code/modules/jobs/job_types/roboticist.dm | 1 + code/modules/jobs/job_types/scientist.dm | 1 + .../jobs/job_types/security_officer.dm | 1 + code/modules/jobs/job_types/shaft_miner.dm | 3 +- .../jobs/job_types/station_engineer.dm | 1 + code/modules/jobs/job_types/virologist.dm | 1 + code/modules/jobs/job_types/warden.dm | 1 + code/modules/mob/dead/crew_manifest.dm | 52 ++++++++ .../modules/mob/dead/new_player/new_player.dm | 7 +- code/modules/mob/dead/observer/observer.dm | 7 +- code/modules/mob/living/silicon/silicon.dm | 9 +- .../file_system/programs/crewmanifest.dm | 2 +- tgstation.dme | 1 + tgui/packages/tgui/interfaces/CrewManifest.js | 68 +++------- .../tgui/styles/interfaces/CrewManifest.scss | 25 +--- 47 files changed, 164 insertions(+), 231 deletions(-) create mode 100644 code/modules/mob/dead/crew_manifest.dm diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index d4ab59cbc1..4d7b41d3b7 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -91,3 +91,12 @@ #define JOB_DISPLAY_ORDER_DETECTIVE 32 #define JOB_DISPLAY_ORDER_SECURITY_OFFICER 33 #define JOB_DISPLAY_ORDER_PRISONER 34 + +#define DEPARTMENT_BITFLAG_SECURITY (1<<0) +#define DEPARTMENT_BITFLAG_COMMAND (1<<1) +#define DEPARTMENT_BITFLAG_SERVICE (1<<2) +#define DEPARTMENT_BITFLAG_CARGO (1<<3) +#define DEPARTMENT_BITFLAG_ENGINEERING (1<<4) +#define DEPARTMENT_BITFLAG_SCIENCE (1<<5) +#define DEPARTMENT_BITFLAG_MEDICAL (1<<6) +#define DEPARTMENT_BITFLAG_SILICON (1<<7) 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/datums/datacore.dm b/code/datums/datacore.dm index 5aeb7626f6..8195666783 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, @@ -112,12 +112,18 @@ if(rank 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 (rank == "Captain" || (department != "Command" && (rank in GLOB.command_positions))) + manifest_out[department] = list(list( + "name" = name, + "rank" = rank + )) + manifest_out[department] + else + manifest_out[department] += list(list( + "name" = name, + "rank" = rank + )) has_department = TRUE - break if(!has_department) if(!manifest_out["Misc"]) manifest_out["Misc"] = list() @@ -127,114 +133,6 @@ )) 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 = {" - - - - "} - 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 = 0 - if(rank in GLOB.command_positions) - heads[name] = rank - department = 1 - if(rank in GLOB.security_positions) - sec[name] = rank - department = 1 - if(rank in GLOB.engineering_positions) - eng[name] = rank - department = 1 - if(rank in GLOB.medical_positions) - med[name] = rank - department = 1 - if(rank in GLOB.science_positions) - sci[name] = rank - department = 1 - if(rank in GLOB.supply_positions) - sup[name] = rank - department = 1 - if(rank in GLOB.civilian_positions) - civ[name] = rank - department = 1 - if(rank in GLOB.nonhuman_positions) - bot[name] = rank - department = 1 - if(!department && !(name in heads)) - misc[name] = rank - if(heads.len > 0) - dat += "" - for(var/name in heads) - dat += "" - even = !even - if(sec.len > 0) - dat += "" - for(var/name in sec) - dat += "" - even = !even - if(eng.len > 0) - dat += "" - for(var/name in eng) - dat += "" - even = !even - if(med.len > 0) - dat += "" - for(var/name in med) - dat += "" - even = !even - if(sci.len > 0) - dat += "" - for(var/name in sci) - dat += "" - even = !even - if(sup.len > 0) - dat += "" - for(var/name in sup) - dat += "" - even = !even - if(civ.len > 0) - dat += "" - for(var/name in civ) - dat += "" - even = !even - // in case somebody is insane and added them to the manifest, why not - if(bot.len > 0) - dat += "" - for(var/name in bot) - dat += "" - even = !even - // misc guys - if(misc.len > 0) - dat += "" - for(var/name in misc) - dat += "" - even = !even - - dat += "
NameRank
Heads
[name][heads[name]]
Security
[name][sec[name]]
Engineering
[name][eng[name]]
Medical
[name][med[name]]
Science
[name][sci[name]]
Supply
[name][sup[name]]
Civilian
[name][civ[name]]
Silicon
[name][bot[name]]
Miscellaneous
[name][misc[name]]
" - 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_traits/positive_traits.dm b/code/datums/station_traits/positive_traits.dm index a1deb8899c..94772966ee 100644 --- a/code/datums/station_traits/positive_traits.dm +++ b/code/datums/station_traits/positive_traits.dm @@ -149,7 +149,7 @@ trait_flags = STATION_TRAIT_ABSTRACT blacklist = list(/datum/station_trait/deathrattle_all) - var/department_head + var/department_to_apply_to var/department_name = "department" var/datum/deathrattle_group/deathrattle_group @@ -157,72 +157,65 @@ . = ..() deathrattle_group = new("[department_name] group") blacklist += subtypesof(/datum/station_trait/deathrattle_department) - type //All but ourselves + name = "deathrattled [department_name]" report_message = "All members of [department_name] have received an implant to notify each other if one of them dies. This should help improve job-safety!" RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, PROC_REF(on_job_after_spawn)) - -/datum/station_trait/deathrattle_department/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/spawned, client/player_client) +/datum/station_trait/deathrattle_department/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/living_mob, mob/M, joined_late) SIGNAL_HANDLER - if(department_head != job.title && !(src.department_head in job.department_head)) + if(!(job.departments & department_to_apply_to)) return var/obj/item/implant/deathrattle/implant_to_give = new() deathrattle_group.register(implant_to_give) - implant_to_give.implant(spawned, spawned, TRUE, TRUE) + implant_to_give.implant(living_mob, living_mob, TRUE, TRUE) /datum/station_trait/deathrattle_department/service - name = "Deathrattled Service" trait_flags = NONE weight = 1 - department_head = "Head of Personnel" + department_to_apply_to = DEPARTMENT_BITFLAG_SERVICE department_name = "Service" /datum/station_trait/deathrattle_department/cargo - name = "Deathrattled Cargo" trait_flags = NONE - weight = 2 - department_head = "Quartermaster" + weight = 1 + department_to_apply_to = DEPARTMENT_BITFLAG_CARGO department_name = "Cargo" /datum/station_trait/deathrattle_department/engineering - name = "Deathrattled Engineering" trait_flags = NONE - weight = 2 - department_head = "Chief Engineer" + weight = 1 + department_to_apply_to = DEPARTMENT_BITFLAG_ENGINEERING department_name = "Engineering" /datum/station_trait/deathrattle_department/command - name = "Deathrattled Command" trait_flags = NONE weight = 1 - department_head = "Captain" + department_to_apply_to = DEPARTMENT_BITFLAG_COMMAND department_name = "Command" /datum/station_trait/deathrattle_department/science - name = "Deathrattled Science" trait_flags = NONE weight = 1 - department_head = "Research Director" + department_to_apply_to = DEPARTMENT_BITFLAG_SCIENCE department_name = "Science" /datum/station_trait/deathrattle_department/security - name = "Deathrattled Security" trait_flags = NONE weight = 1 - department_head = "Head of Security" + department_to_apply_to = DEPARTMENT_BITFLAG_SECURITY department_name = "Security" /datum/station_trait/deathrattle_department/medical - name = "Deathrattled Medical" trait_flags = NONE weight = 1 - department_head = "Chief Medical Officer" + department_to_apply_to = DEPARTMENT_BITFLAG_MEDICAL department_name = "Medical" /datum/station_trait/deathrattle_all - name = "Deathrattled Station" + name = "deathrattled station" trait_type = STATION_TRAIT_POSITIVE show_in_report = TRUE weight = 1 @@ -236,14 +229,12 @@ blacklist = subtypesof(/datum/station_trait/deathrattle_department) RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, PROC_REF(on_job_after_spawn)) - -/datum/station_trait/deathrattle_all/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/spawned, client/player_client) +/datum/station_trait/deathrattle_all/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/living_mob, mob/M, joined_late) SIGNAL_HANDLER var/obj/item/implant/deathrattle/implant_to_give = new() deathrattle_group.register(implant_to_give) - implant_to_give.implant(spawned, spawned, TRUE, TRUE) - + implant_to_give.implant(living_mob, living_mob, TRUE, TRUE) /datum/station_trait/wallets name = "Wallets!" diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 4f45af88bb..969a39b560 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -76,6 +76,9 @@ var/bounty_types = CIV_JOB_BASIC + ///Bitfield of departments this job belongs wit + var/departments = NONE + /// Goodies that can be received via the mail system. // this is a weighted list. /// Keep the _job definition for this empty and use /obj/item/mail to define general gifts. diff --git a/code/modules/jobs/job_types/ai.dm b/code/modules/jobs/job_types/ai.dm index 526f541443..2dc575145e 100644 --- a/code/modules/jobs/job_types/ai.dm +++ b/code/modules/jobs/job_types/ai.dm @@ -15,6 +15,7 @@ exp_type_department = EXP_TYPE_SILICON display_order = JOB_DISPLAY_ORDER_AI allow_bureaucratic_error = FALSE + departments = DEPARTMENT_BITFLAG_SILICON random_spawns_possible = FALSE var/do_special_check = TRUE threat = 5 diff --git a/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm index 90ac6be8dd..3a2335bb11 100644 --- a/code/modules/jobs/job_types/assistant.dm +++ b/code/modules/jobs/job_types/assistant.dm @@ -16,6 +16,7 @@ Assistant antag_rep = 7 paycheck = PAYCHECK_ASSISTANT // Get a job. Job reassignment changes your paycheck now. Get over it. paycheck_department = ACCOUNT_CIV + departments = DEPARTMENT_BITFLAG_SERVICE display_order = JOB_DISPLAY_ORDER_ASSISTANT dresscodecompliant = FALSE always_can_respawn_as = TRUE diff --git a/code/modules/jobs/job_types/atmospheric_technician.dm b/code/modules/jobs/job_types/atmospheric_technician.dm index 551ac63d00..4b17fc8b9f 100644 --- a/code/modules/jobs/job_types/atmospheric_technician.dm +++ b/code/modules/jobs/job_types/atmospheric_technician.dm @@ -21,12 +21,13 @@ paycheck = PAYCHECK_MEDIUM paycheck_department = ACCOUNT_ENG bounty_types = CIV_JOB_ENG + departments = DEPARTMENT_BITFLAG_ENGINEERING starting_modifiers = list(/datum/skill_modifier/job/level/wiring, /datum/skill_modifier/job/affinity/wiring) display_order = JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN threat = 0.5 - + family_heirlooms = list( /obj/item/lighter, /obj/item/lighter/greyscale, diff --git a/code/modules/jobs/job_types/bartender.dm b/code/modules/jobs/job_types/bartender.dm index bde15b24db..6d8f4256b5 100644 --- a/code/modules/jobs/job_types/bartender.dm +++ b/code/modules/jobs/job_types/bartender.dm @@ -18,6 +18,7 @@ paycheck = PAYCHECK_EASY paycheck_department = ACCOUNT_SRV bounty_types = CIV_JOB_DRINK + departments = DEPARTMENT_BITFLAG_SERVICE display_order = JOB_DISPLAY_ORDER_BARTENDER threat = 0.5 diff --git a/code/modules/jobs/job_types/botanist.dm b/code/modules/jobs/job_types/botanist.dm index fcd71d7275..f30375201e 100644 --- a/code/modules/jobs/job_types/botanist.dm +++ b/code/modules/jobs/job_types/botanist.dm @@ -17,6 +17,7 @@ paycheck = PAYCHECK_EASY paycheck_department = ACCOUNT_SRV bounty_types = CIV_JOB_GROW + departments = DEPARTMENT_BITFLAG_SERVICE display_order = JOB_DISPLAY_ORDER_BOTANIST threat = 1.5 // lol powergame diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm index 00fb4822e2..fe594e4660 100644 --- a/code/modules/jobs/job_types/captain.dm +++ b/code/modules/jobs/job_types/captain.dm @@ -29,6 +29,7 @@ mind_traits = list(TRAIT_CAPTAIN_METABOLISM, TRAIT_DISK_VERIFIER) display_order = JOB_DISPLAY_ORDER_CAPTAIN + departments = DEPARTMENT_BITFLAG_COMMAND blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity) threat = 5 diff --git a/code/modules/jobs/job_types/cargo_technician.dm b/code/modules/jobs/job_types/cargo_technician.dm index 823aec3078..c70e1078aa 100644 --- a/code/modules/jobs/job_types/cargo_technician.dm +++ b/code/modules/jobs/job_types/cargo_technician.dm @@ -20,6 +20,7 @@ display_order = JOB_DISPLAY_ORDER_CARGO_TECHNICIAN bounty_types = CIV_JOB_RANDOM + departments = DEPARTMENT_BITFLAG_CARGO threat = 0.2 family_heirlooms = list( diff --git a/code/modules/jobs/job_types/chaplain.dm b/code/modules/jobs/job_types/chaplain.dm index 522f022072..2d6f436cb0 100644 --- a/code/modules/jobs/job_types/chaplain.dm +++ b/code/modules/jobs/job_types/chaplain.dm @@ -18,6 +18,7 @@ paycheck_department = ACCOUNT_CIV display_order = JOB_DISPLAY_ORDER_CHAPLAIN + departments = DEPARTMENT_BITFLAG_SERVICE threat = 0.5 family_heirlooms = list( diff --git a/code/modules/jobs/job_types/chemist.dm b/code/modules/jobs/job_types/chemist.dm index be145b24b2..317b9f7a33 100644 --- a/code/modules/jobs/job_types/chemist.dm +++ b/code/modules/jobs/job_types/chemist.dm @@ -19,6 +19,7 @@ paycheck = PAYCHECK_MEDIUM paycheck_department = ACCOUNT_MED bounty_types = CIV_JOB_CHEM + departments = DEPARTMENT_BITFLAG_MEDICAL display_order = JOB_DISPLAY_ORDER_CHEMIST threat = 1.5 diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm index 5ec4cca512..0e0083bd3c 100644 --- a/code/modules/jobs/job_types/chief_engineer.dm +++ b/code/modules/jobs/job_types/chief_engineer.dm @@ -18,6 +18,7 @@ considered_combat_role = TRUE outfit = /datum/outfit/job/ce + departments = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_COMMAND plasma_outfit = /datum/outfit/plasmaman/ce access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm index ed58d67a1f..3472052b53 100644 --- a/code/modules/jobs/job_types/chief_medical_officer.dm +++ b/code/modules/jobs/job_types/chief_medical_officer.dm @@ -18,6 +18,7 @@ considered_combat_role = TRUE outfit = /datum/outfit/job/cmo + departments = DEPARTMENT_BITFLAG_MEDICAL | DEPARTMENT_BITFLAG_COMMAND plasma_outfit = /datum/outfit/plasmaman/cmo access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_HEADS, ACCESS_MINERAL_STOREROOM, diff --git a/code/modules/jobs/job_types/clown.dm b/code/modules/jobs/job_types/clown.dm index c54fb13c0a..009560d7c7 100644 --- a/code/modules/jobs/job_types/clown.dm +++ b/code/modules/jobs/job_types/clown.dm @@ -10,6 +10,7 @@ selection_color = "#dddddd" outfit = /datum/outfit/job/clown + departments = DEPARTMENT_BITFLAG_SERVICE plasma_outfit = /datum/outfit/plasmaman/clown access = list(ACCESS_THEATRE) diff --git a/code/modules/jobs/job_types/cook.dm b/code/modules/jobs/job_types/cook.dm index 246f8d002f..b3d3180367 100644 --- a/code/modules/jobs/job_types/cook.dm +++ b/code/modules/jobs/job_types/cook.dm @@ -18,6 +18,7 @@ paycheck = PAYCHECK_EASY paycheck_department = ACCOUNT_SRV bounty_types = CIV_JOB_CHEF + departments = DEPARTMENT_BITFLAG_SERVICE display_order = JOB_DISPLAY_ORDER_COOK threat = 0.2 diff --git a/code/modules/jobs/job_types/curator.dm b/code/modules/jobs/job_types/curator.dm index e6e9394109..6b5707d76f 100644 --- a/code/modules/jobs/job_types/curator.dm +++ b/code/modules/jobs/job_types/curator.dm @@ -18,8 +18,9 @@ paycheck_department = ACCOUNT_CIV display_order = JOB_DISPLAY_ORDER_CURATOR + departments = DEPARTMENT_BITFLAG_SERVICE threat = 0.3 - + family_heirlooms = list( /obj/item/pen/fountain, /obj/item/storage/dice diff --git a/code/modules/jobs/job_types/cyborg.dm b/code/modules/jobs/job_types/cyborg.dm index 280d7616c2..97ad58d058 100644 --- a/code/modules/jobs/job_types/cyborg.dm +++ b/code/modules/jobs/job_types/cyborg.dm @@ -16,6 +16,7 @@ starting_modifiers = list(/datum/skill_modifier/job/level/wiring/basic) display_order = JOB_DISPLAY_ORDER_CYBORG + departments = DEPARTMENT_BITFLAG_SILICON /datum/job/cyborg/equip(mob/living/carbon/human/H, visualsOnly = FALSE, announce = TRUE, latejoin = FALSE, datum/outfit/outfit_override = null, client/preference_source = null) if(visualsOnly) diff --git a/code/modules/jobs/job_types/detective.dm b/code/modules/jobs/job_types/detective.dm index cfa93ecd5a..82679c4680 100644 --- a/code/modules/jobs/job_types/detective.dm +++ b/code/modules/jobs/job_types/detective.dm @@ -14,6 +14,7 @@ exp_type = EXP_TYPE_CREW outfit = /datum/outfit/job/detective + departments = DEPARTMENT_BITFLAG_SECURITY plasma_outfit = /datum/outfit/plasmaman/detective considered_combat_role = TRUE diff --git a/code/modules/jobs/job_types/geneticist.dm b/code/modules/jobs/job_types/geneticist.dm index fb830b3ece..46125339be 100644 --- a/code/modules/jobs/job_types/geneticist.dm +++ b/code/modules/jobs/job_types/geneticist.dm @@ -12,6 +12,7 @@ exp_requirements = 60 outfit = /datum/outfit/job/geneticist + departments = DEPARTMENT_BITFLAG_MEDICAL plasma_outfit = /datum/outfit/plasmaman/genetics access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_ROBOTICS, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE) diff --git a/code/modules/jobs/job_types/head_of_personnel.dm b/code/modules/jobs/job_types/head_of_personnel.dm index 508dcfda7b..c20e5284d1 100644 --- a/code/modules/jobs/job_types/head_of_personnel.dm +++ b/code/modules/jobs/job_types/head_of_personnel.dm @@ -17,6 +17,7 @@ exp_type_department = EXP_TYPE_SERVICE considered_combat_role = TRUE outfit = /datum/outfit/job/hop + departments = DEPARTMENT_BITFLAG_COMMAND | DEPARTMENT_BITFLAG_SERVICE plasma_outfit = /datum/outfit/plasmaman/hop access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_COURT, ACCESS_WEAPONS, diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm index 15fd567a96..5f8fa7ac51 100644 --- a/code/modules/jobs/job_types/head_of_security.dm +++ b/code/modules/jobs/job_types/head_of_security.dm @@ -18,6 +18,7 @@ exp_type_department = EXP_TYPE_SECURITY outfit = /datum/outfit/job/hos + departments = DEPARTMENT_BITFLAG_SECURITY | DEPARTMENT_BITFLAG_COMMAND plasma_outfit = /datum/outfit/plasmaman/hos mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM) diff --git a/code/modules/jobs/job_types/janitor.dm b/code/modules/jobs/job_types/janitor.dm index f7e79cb769..fcfbc92e40 100644 --- a/code/modules/jobs/job_types/janitor.dm +++ b/code/modules/jobs/job_types/janitor.dm @@ -18,6 +18,7 @@ paycheck_department = ACCOUNT_SRV display_order = JOB_DISPLAY_ORDER_JANITOR + departments = DEPARTMENT_BITFLAG_SERVICE threat = 0.2 family_heirlooms = list( diff --git a/code/modules/jobs/job_types/lawyer.dm b/code/modules/jobs/job_types/lawyer.dm index 4105bd4e6e..8df949a143 100644 --- a/code/modules/jobs/job_types/lawyer.dm +++ b/code/modules/jobs/job_types/lawyer.dm @@ -21,8 +21,9 @@ mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM) display_order = JOB_DISPLAY_ORDER_LAWYER + departments = DEPARTMENT_BITFLAG_SERVICE threat = 0.3 - + family_heirlooms = list( /obj/item/gavelhammer, /obj/item/book/manual/wiki/security_space_law diff --git a/code/modules/jobs/job_types/medical_doctor.dm b/code/modules/jobs/job_types/medical_doctor.dm index b9a313d0e7..52a668b182 100644 --- a/code/modules/jobs/job_types/medical_doctor.dm +++ b/code/modules/jobs/job_types/medical_doctor.dm @@ -10,6 +10,7 @@ selection_color = "#74b5e0" outfit = /datum/outfit/job/doctor + departments = DEPARTMENT_BITFLAG_MEDICAL plasma_outfit = /datum/outfit/plasmaman/medical access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_VIROLOGY, ACCESS_MINERAL_STOREROOM) diff --git a/code/modules/jobs/job_types/mime.dm b/code/modules/jobs/job_types/mime.dm index 3929b22f9d..2813c32e14 100644 --- a/code/modules/jobs/job_types/mime.dm +++ b/code/modules/jobs/job_types/mime.dm @@ -18,6 +18,7 @@ paycheck_department = ACCOUNT_SRV display_order = JOB_DISPLAY_ORDER_MIME + departments = DEPARTMENT_BITFLAG_SERVICE threat = 0 diff --git a/code/modules/jobs/job_types/paramedic.dm b/code/modules/jobs/job_types/paramedic.dm index c3d42ba365..19a955ffe2 100644 --- a/code/modules/jobs/job_types/paramedic.dm +++ b/code/modules/jobs/job_types/paramedic.dm @@ -17,6 +17,7 @@ paycheck = PAYCHECK_MEDIUM paycheck_department = ACCOUNT_MED bounty_types = CIV_JOB_MED + departments = DEPARTMENT_BITFLAG_MEDICAL display_order = JOB_DISPLAY_ORDER_PARAMEDIC diff --git a/code/modules/jobs/job_types/prisoner.dm b/code/modules/jobs/job_types/prisoner.dm index 5de351573f..732ac03016 100644 --- a/code/modules/jobs/job_types/prisoner.dm +++ b/code/modules/jobs/job_types/prisoner.dm @@ -13,6 +13,7 @@ plasma_outfit = /datum/outfit/plasmaman/prisoner display_order = JOB_DISPLAY_ORDER_PRISONER + departments = DEPARTMENT_BITFLAG_SECURITY family_heirlooms = list( /obj/item/pen/blue diff --git a/code/modules/jobs/job_types/quartermaster.dm b/code/modules/jobs/job_types/quartermaster.dm index efcab13381..b0cdf94825 100644 --- a/code/modules/jobs/job_types/quartermaster.dm +++ b/code/modules/jobs/job_types/quartermaster.dm @@ -28,6 +28,7 @@ paycheck = PAYCHECK_HARD //They can already buy stuff using cargo budget, don't give em a command-level paycheck. //alright i'll agree to that -qweq paycheck_department = ACCOUNT_CAR bounty_types = CIV_JOB_RANDOM + departments = DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_COMMAND display_order = JOB_DISPLAY_ORDER_QUARTERMASTER blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity) diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm index f4589cae3d..89c9aafeb4 100644 --- a/code/modules/jobs/job_types/research_director.dm +++ b/code/modules/jobs/job_types/research_director.dm @@ -18,6 +18,7 @@ considered_combat_role = TRUE outfit = /datum/outfit/job/rd + departments = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_COMMAND plasma_outfit = /datum/outfit/plasmaman/rd access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE, diff --git a/code/modules/jobs/job_types/roboticist.dm b/code/modules/jobs/job_types/roboticist.dm index 089e5cf506..097023e643 100644 --- a/code/modules/jobs/job_types/roboticist.dm +++ b/code/modules/jobs/job_types/roboticist.dm @@ -12,6 +12,7 @@ exp_type = EXP_TYPE_CREW outfit = /datum/outfit/job/roboticist + departments = DEPARTMENT_BITFLAG_SCIENCE plasma_outfit = /datum/outfit/plasmaman/robotics access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM, ACCESS_XENOBIOLOGY, ACCESS_GENETICS) diff --git a/code/modules/jobs/job_types/scientist.dm b/code/modules/jobs/job_types/scientist.dm index 5fcfb7e6f6..f7d5c4a11c 100644 --- a/code/modules/jobs/job_types/scientist.dm +++ b/code/modules/jobs/job_types/scientist.dm @@ -19,6 +19,7 @@ paycheck = PAYCHECK_MEDIUM paycheck_department = ACCOUNT_SCI bounty_types = CIV_JOB_SCI + departments = DEPARTMENT_BITFLAG_SCIENCE starting_modifiers = list(/datum/skill_modifier/job/level/wiring/basic) display_order = JOB_DISPLAY_ORDER_SCIENTIST threat = 1.2 diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm index 4e338da1fa..d413d0a011 100644 --- a/code/modules/jobs/job_types/security_officer.dm +++ b/code/modules/jobs/job_types/security_officer.dm @@ -22,6 +22,7 @@ paycheck = PAYCHECK_HARD paycheck_department = ACCOUNT_SEC bounty_types = CIV_JOB_SEC + departments = DEPARTMENT_BITFLAG_SECURITY mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM) diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm index c6192280c7..7c512ecc0d 100644 --- a/code/modules/jobs/job_types/shaft_miner.dm +++ b/code/modules/jobs/job_types/shaft_miner.dm @@ -20,11 +20,12 @@ paycheck = PAYCHECK_EASY ///Not necessarily easy itself, but it can be trivial to make lot of cash on this job. paycheck_department = ACCOUNT_CAR bounty_types = CIV_JOB_MINE + departments = DEPARTMENT_BITFLAG_CARGO display_order = JOB_DISPLAY_ORDER_SHAFT_MINER threat = 1.5 - + family_heirlooms = list( /obj/item/pickaxe/mini, /obj/item/shovel diff --git a/code/modules/jobs/job_types/station_engineer.dm b/code/modules/jobs/job_types/station_engineer.dm index d8ea7e1df5..3ae4adb49b 100644 --- a/code/modules/jobs/job_types/station_engineer.dm +++ b/code/modules/jobs/job_types/station_engineer.dm @@ -21,6 +21,7 @@ paycheck = PAYCHECK_MEDIUM paycheck_department = ACCOUNT_ENG bounty_types = CIV_JOB_ENG + departments = DEPARTMENT_BITFLAG_ENGINEERING starting_modifiers = list(/datum/skill_modifier/job/level/wiring, /datum/skill_modifier/job/affinity/wiring) diff --git a/code/modules/jobs/job_types/virologist.dm b/code/modules/jobs/job_types/virologist.dm index e5c64acbc8..b9103af84b 100644 --- a/code/modules/jobs/job_types/virologist.dm +++ b/code/modules/jobs/job_types/virologist.dm @@ -19,6 +19,7 @@ paycheck = PAYCHECK_MEDIUM paycheck_department = ACCOUNT_MED bounty_types = CIV_JOB_VIRO + departments = DEPARTMENT_BITFLAG_MEDICAL display_order = JOB_DISPLAY_ORDER_VIROLOGIST diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm index eda6257653..7ab010b356 100644 --- a/code/modules/jobs/job_types/warden.dm +++ b/code/modules/jobs/job_types/warden.dm @@ -23,6 +23,7 @@ paycheck = PAYCHECK_HARD paycheck_department = ACCOUNT_SEC bounty_types = CIV_JOB_SEC + departments = DEPARTMENT_BITFLAG_SECURITY mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM) diff --git a/code/modules/mob/dead/crew_manifest.dm b/code/modules/mob/dead/crew_manifest.dm new file mode 100644 index 0000000000..c7eb369794 --- /dev/null +++ b/code/modules/mob/dead/crew_manifest.dm @@ -0,0 +1,52 @@ +/datum/crew_manifest + +/datum/crew_manifest/ui_state(mob/user) + return GLOB.always_state + +/datum/crew_manifest/ui_status(mob/user, datum/ui_state/state) + return (isnewplayer(user) || isobserver(user) || isAI(user) || ispAI(user)) ? UI_INTERACTIVE : UI_CLOSE + +/datum/crew_manifest/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if (!ui) + ui = new(user, src, "CrewManifest") + ui.open() + +/datum/crew_manifest/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + if (..()) + return + +/datum/crew_manifest/ui_data(mob/user) + var/list/positions = list( + DEPARTMENT_COMMAND = 0, + DEPARTMENT_SECURITY = 0, + DEPARTMENT_ENGINEERING = 0, + DEPARTMENT_MEDICAL = 0, + DEPARTMENT_SCIENCE = 0, + DEPARTMENT_CARGO = 0, + DEPARTMENT_SERVICE = 0, + DEPARTMENT_SILICON = 0 + ) + var/list/departments = list( + list("flag" = DEPARTMENT_BITFLAG_COMMAND, "name" = DEPARTMENT_COMMAND), + list("flag" = DEPARTMENT_BITFLAG_SECURITY, "name" = DEPARTMENT_SECURITY), + list("flag" = DEPARTMENT_BITFLAG_ENGINEERING, "name" = DEPARTMENT_ENGINEERING), + list("flag" = DEPARTMENT_BITFLAG_MEDICAL, "name" = DEPARTMENT_MEDICAL), + list("flag" = DEPARTMENT_BITFLAG_SCIENCE, "name" = DEPARTMENT_SCIENCE), + list("flag" = DEPARTMENT_BITFLAG_CARGO, "name" = DEPARTMENT_CARGO), + list("flag" = DEPARTMENT_BITFLAG_SERVICE, "name" = DEPARTMENT_SERVICE), + list("flag" = DEPARTMENT_BITFLAG_SILICON, "name" = DEPARTMENT_SILICON) + ) + + for(var/datum/job/job in SSjob.occupations) + for(var/department in departments) + // Check if the job is part of a department using its flag + // Will return true for Research Director if the department is Science or Command, for example + if(job.departments & department["flag"]) + // Add open positions to current department + positions[department["name"]] += (job.total_positions - job.current_positions) + + return list( + "manifest" = GLOB.data_core.get_manifest(), + "positions" = positions + ) diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index e76a8f5a38..b3811b8fef 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -720,11 +720,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 97f9388965..3899e788df 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -730,11 +730,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp return client.crew_manifest_delay = world.time + (1 SECONDS) - var/dat - dat += "

Crew Manifest

" - dat += GLOB.data_core.get_manifest() + if(!GLOB.crew_manifest_tgui) + GLOB.crew_manifest_tgui = new /datum/crew_manifest(src) - src << browse(dat, "window=manifest;size=387x420;can_close=1") + GLOB.crew_manifest_tgui.ui_interact(src) //this is called when a ghost is drag clicked to something. /mob/dead/observer/MouseDrop(atom/over) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index f58b27a537..97831388b1 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -413,13 +413,10 @@ return client.crew_manifest_delay = world.time + (1 SECONDS) - var/dat = "Crew RosterCrew Roster:

" + if(!GLOB.crew_manifest_tgui) + GLOB.crew_manifest_tgui = new /datum/crew_manifest(src) - dat += GLOB.data_core.get_manifest() - dat += "" - - src << browse(dat, "window=airoster") - onclose(src, "airoster") + GLOB.crew_manifest_tgui.ui_interact(src) /mob/living/silicon/update_transform(do_animate) var/matrix/ntransform = matrix(transform) //aka transform.Copy() diff --git a/code/modules/modular_computers/file_system/programs/crewmanifest.dm b/code/modules/modular_computers/file_system/programs/crewmanifest.dm index 6837f315a4..bc6923773e 100644 --- a/code/modules/modular_computers/file_system/programs/crewmanifest.dm +++ b/code/modules/modular_computers/file_system/programs/crewmanifest.dm @@ -12,7 +12,7 @@ /datum/computer_file/program/crew_manifest/ui_static_data(mob/user) var/list/data = list() - data["manifest"] = GLOB.data_core.get_manifest_tg() + data["manifest"] = GLOB.data_core.get_manifest() return data /datum/computer_file/program/crew_manifest/ui_data(mob/user) diff --git a/tgstation.dme b/tgstation.dme index a201f09456..1d31780ce0 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2637,6 +2637,7 @@ #include "code\modules\mob\transform_procs.dm" #include "code\modules\mob\typing_indicator.dm" #include "code\modules\mob\camera\camera.dm" +#include "code\modules\mob\dead\crew_manifest.dm" #include "code\modules\mob\dead\dead.dm" #include "code\modules\mob\dead\new_player\login.dm" #include "code\modules\mob\dead\new_player\logout.dm" diff --git a/tgui/packages/tgui/interfaces/CrewManifest.js b/tgui/packages/tgui/interfaces/CrewManifest.js index 3befc18501..3f6f874bda 100644 --- a/tgui/packages/tgui/interfaces/CrewManifest.js +++ b/tgui/packages/tgui/interfaces/CrewManifest.js @@ -1,9 +1,9 @@ -import { classes } from 'common/react'; import { useBackend } from "../backend"; -import { Icon, Section, Table, Tooltip } from "../components"; +import { Icon, Section, Table } from "../components"; import { Window } from "../layouts"; const commandJobs = [ + "Captain", "Head of Personnel", "Head of Security", "Chief Engineer", @@ -17,13 +17,12 @@ export const CrewManifest = (props, context) => { return ( - {Object.entries(manifest).map(([dept, crew]) => ( + {Object.entries(manifest).map(([department, crew]) => (
@@ -33,57 +32,24 @@ export const CrewManifest = (props, context) => { {crewMember.name} - {positions[dept].exceptions.includes(crewMember.rank) && ( - - - - - )} - {crewMember.rank === "Captain" && ( - - - - )} {commandJobs.includes(crewMember.rank) && ( - - - + )} {crewMember.rank} diff --git a/tgui/packages/tgui/styles/interfaces/CrewManifest.scss b/tgui/packages/tgui/styles/interfaces/CrewManifest.scss index 47c23ff1a2..8291cbbd50 100644 --- a/tgui/packages/tgui/styles/interfaces/CrewManifest.scss +++ b/tgui/packages/tgui/styles/interfaces/CrewManifest.scss @@ -5,7 +5,6 @@ $department_map: ( 'Security': colors.$red, 'Engineering': colors.$orange, 'Medical': colors.$teal, - 'Misc': colors.$white, 'Science': colors.$purple, 'Supply': colors.$brown, 'Service': colors.$green, @@ -29,30 +28,14 @@ $department_map: ( &__Cell { padding: 3px 0; - &--Rank { - color: colors.$label; - } - } - - &__Icons { - padding: 3px 9px; - text-align: right; - } - - &__Icon { - color: colors.$label; - position: relative; - - &:not(:last-child) { - margin-right: 7px; - } - - &--Chevron { - padding-right: 2px; + &--Captain { + color: colors.$yellow; + padding: 3px 8px; } &--Command { color: colors.$yellow; + padding: 3px 9px; } } } From 330dcad5ac42bddeeaed2d1bc9d73024fb4b802c Mon Sep 17 00:00:00 2001 From: SandPoot Date: Thu, 23 May 2024 21:55:21 -0300 Subject: [PATCH 09/13] defines... --- code/__DEFINES/jobs.dm | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index 4d7b41d3b7..68a9033d7f 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -92,11 +92,21 @@ #define JOB_DISPLAY_ORDER_SECURITY_OFFICER 33 #define JOB_DISPLAY_ORDER_PRISONER 34 +#define DEPARTMENT_UNASSIGNED "No department assigned" + #define DEPARTMENT_BITFLAG_SECURITY (1<<0) +#define DEPARTMENT_SECURITY "Security" #define DEPARTMENT_BITFLAG_COMMAND (1<<1) +#define DEPARTMENT_COMMAND "Command" #define DEPARTMENT_BITFLAG_SERVICE (1<<2) +#define DEPARTMENT_SERVICE "Service" #define DEPARTMENT_BITFLAG_CARGO (1<<3) +#define DEPARTMENT_CARGO "Cargo" #define DEPARTMENT_BITFLAG_ENGINEERING (1<<4) +#define DEPARTMENT_ENGINEERING "Engineering" #define DEPARTMENT_BITFLAG_SCIENCE (1<<5) +#define DEPARTMENT_SCIENCE "Science" #define DEPARTMENT_BITFLAG_MEDICAL (1<<6) +#define DEPARTMENT_MEDICAL "Medical" #define DEPARTMENT_BITFLAG_SILICON (1<<7) +#define DEPARTMENT_SILICON "Silicon" From 603929cad54bc95e111a32c447fa89c741028966 Mon Sep 17 00:00:00 2001 From: SandPoot Date: Sat, 25 May 2024 15:53:26 -0300 Subject: [PATCH 10/13] nothing to see here --- code/__DEFINES/jobs.dm | 4 ++-- code/modules/mob/dead/crew_manifest.dm | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index 68a9033d7f..f892558a1a 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -100,8 +100,8 @@ #define DEPARTMENT_COMMAND "Command" #define DEPARTMENT_BITFLAG_SERVICE (1<<2) #define DEPARTMENT_SERVICE "Service" -#define DEPARTMENT_BITFLAG_CARGO (1<<3) -#define DEPARTMENT_CARGO "Cargo" +#define DEPARTMENT_BITFLAG_SUPPLY (1<<3) +#define DEPARTMENT_SUPPLY "Supply" #define DEPARTMENT_BITFLAG_ENGINEERING (1<<4) #define DEPARTMENT_ENGINEERING "Engineering" #define DEPARTMENT_BITFLAG_SCIENCE (1<<5) diff --git a/code/modules/mob/dead/crew_manifest.dm b/code/modules/mob/dead/crew_manifest.dm index c7eb369794..5f6dd5671c 100644 --- a/code/modules/mob/dead/crew_manifest.dm +++ b/code/modules/mob/dead/crew_manifest.dm @@ -23,7 +23,7 @@ DEPARTMENT_ENGINEERING = 0, DEPARTMENT_MEDICAL = 0, DEPARTMENT_SCIENCE = 0, - DEPARTMENT_CARGO = 0, + DEPARTMENT_SUPPLY = 0, DEPARTMENT_SERVICE = 0, DEPARTMENT_SILICON = 0 ) @@ -33,7 +33,7 @@ list("flag" = DEPARTMENT_BITFLAG_ENGINEERING, "name" = DEPARTMENT_ENGINEERING), list("flag" = DEPARTMENT_BITFLAG_MEDICAL, "name" = DEPARTMENT_MEDICAL), list("flag" = DEPARTMENT_BITFLAG_SCIENCE, "name" = DEPARTMENT_SCIENCE), - list("flag" = DEPARTMENT_BITFLAG_CARGO, "name" = DEPARTMENT_CARGO), + list("flag" = DEPARTMENT_BITFLAG_SUPPLY, "name" = DEPARTMENT_SUPPLY), list("flag" = DEPARTMENT_BITFLAG_SERVICE, "name" = DEPARTMENT_SERVICE), list("flag" = DEPARTMENT_BITFLAG_SILICON, "name" = DEPARTMENT_SILICON) ) From 28f6ef43659c55148abf3432d8e69dd7fb0d0b30 Mon Sep 17 00:00:00 2001 From: SandPoot Date: Sat, 25 May 2024 16:02:36 -0300 Subject: [PATCH 11/13] maybe there was something to be seen after all --- code/datums/station_traits/positive_traits.dm | 2 +- code/modules/jobs/job_types/cargo_technician.dm | 2 +- code/modules/jobs/job_types/quartermaster.dm | 2 +- code/modules/jobs/job_types/shaft_miner.dm | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/code/datums/station_traits/positive_traits.dm b/code/datums/station_traits/positive_traits.dm index 94772966ee..d6ec90dbd4 100644 --- a/code/datums/station_traits/positive_traits.dm +++ b/code/datums/station_traits/positive_traits.dm @@ -181,7 +181,7 @@ /datum/station_trait/deathrattle_department/cargo trait_flags = NONE weight = 1 - department_to_apply_to = DEPARTMENT_BITFLAG_CARGO + department_to_apply_to = DEPARTMENT_BITFLAG_SUPPLY department_name = "Cargo" /datum/station_trait/deathrattle_department/engineering diff --git a/code/modules/jobs/job_types/cargo_technician.dm b/code/modules/jobs/job_types/cargo_technician.dm index c70e1078aa..7c42e1a8ba 100644 --- a/code/modules/jobs/job_types/cargo_technician.dm +++ b/code/modules/jobs/job_types/cargo_technician.dm @@ -20,7 +20,7 @@ display_order = JOB_DISPLAY_ORDER_CARGO_TECHNICIAN bounty_types = CIV_JOB_RANDOM - departments = DEPARTMENT_BITFLAG_CARGO + departments = DEPARTMENT_BITFLAG_SUPPLY threat = 0.2 family_heirlooms = list( diff --git a/code/modules/jobs/job_types/quartermaster.dm b/code/modules/jobs/job_types/quartermaster.dm index b0cdf94825..865249095e 100644 --- a/code/modules/jobs/job_types/quartermaster.dm +++ b/code/modules/jobs/job_types/quartermaster.dm @@ -28,7 +28,7 @@ paycheck = PAYCHECK_HARD //They can already buy stuff using cargo budget, don't give em a command-level paycheck. //alright i'll agree to that -qweq paycheck_department = ACCOUNT_CAR bounty_types = CIV_JOB_RANDOM - departments = DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_COMMAND + 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/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm index 7c512ecc0d..714ce1e9aa 100644 --- a/code/modules/jobs/job_types/shaft_miner.dm +++ b/code/modules/jobs/job_types/shaft_miner.dm @@ -20,7 +20,7 @@ paycheck = PAYCHECK_EASY ///Not necessarily easy itself, but it can be trivial to make lot of cash on this job. paycheck_department = ACCOUNT_CAR bounty_types = CIV_JOB_MINE - departments = DEPARTMENT_BITFLAG_CARGO + departments = DEPARTMENT_BITFLAG_SUPPLY display_order = JOB_DISPLAY_ORDER_SHAFT_MINER From 5023ba4fc8c90c493f6ec9beb7b13b5c13048f71 Mon Sep 17 00:00:00 2001 From: SandPoot Date: Wed, 29 May 2024 18:48:07 -0300 Subject: [PATCH 12/13] just so it appears in the right spot --- code/game/objects/items/robot/robot_upgrades.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm index 9c4885da44..30f9f9308d 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -198,6 +198,7 @@ icon_state = "cyborg_upgrade3" require_module = 1 module_type = list(/obj/item/robot_module/miner) + module_flags = BORG_MODULE_MINER /obj/item/borg/upgrade/advcutter/action(mob/living/silicon/robot/R, user = usr) . = ..() From 62f858a15473adabbb738dda22d43bab50268f51 Mon Sep 17 00:00:00 2001 From: SandPoot Date: Wed, 29 May 2024 18:49:16 -0300 Subject: [PATCH 13/13] this one too --- code/game/objects/items/robot/robot_upgrades.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm index 30f9f9308d..c14b2925fa 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -226,6 +226,7 @@ icon_state = "cyborg_upgrade3" require_module = 1 module_type = list(/obj/item/robot_module/miner) + module_flags = BORG_MODULE_MINER /obj/item/borg/upgrade/premiumka/action(mob/living/silicon/robot/R, user = usr) . = ..()