diff --git a/code/game/gamemodes/extended/extended.dm b/code/game/gamemodes/extended/extended.dm
index a6abcbefbd..bfc75a9dc1 100644
--- a/code/game/gamemodes/extended/extended.dm
+++ b/code/game/gamemodes/extended/extended.dm
@@ -30,4 +30,4 @@
/datum/game_mode/extended/announced/send_intercept(report = 0)
if(flipseclevel) //CIT CHANGE - allows the sec level to be flipped roundstart
return ..()
- priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", "commandreport")
+ priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", SSstation.announcer.get_rand_report_sound())
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 617be706cb..df1b0a701a 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -278,7 +278,7 @@
/datum/game_mode/proc/send_intercept()
if(flipseclevel && !(config_tag == "extended"))//CIT CHANGE - lets the security level be flipped roundstart
- priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", "commandreport")
+ priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", SSstation.announcer.get_rand_report_sound())
return
var/intercepttext = "Central Command Status Summary
"
intercepttext += "Central Command has intercepted and partially decoded a Syndicate transmission with vital information regarding their movements. The following report outlines the most \
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index 7c22846ece..d705057217 100755
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -244,7 +244,7 @@
nuke_request(reason, usr)
to_chat(usr, span_notice("Request sent."))
usr.log_message("has requested the nuclear codes from CentCom with reason \"[reason]\"", LOG_SAY)
- priority_announce("The codes for the on-station nuclear self-destruct have been requested by [usr]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self-Destruct Codes Requested", "commandreport")
+ priority_announce("The codes for the on-station nuclear self-destruct have been requested by [usr]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self-Destruct Codes Requested", SSstation.announcer.get_rand_report_sound())
playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE)
COOLDOWN_START(src, important_action_cooldown, IMPORTANT_ACTION_COOLDOWN)
if ("restoreBackupRoutingData")
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index 85b3d9eaa4..f1884d5fb4 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -560,7 +560,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
var/announce_command_report = TRUE
switch(confirm)
if("Yes")
- priority_announce(input, null, "commandreport")
+ priority_announce(input, null, SSstation.announcer.get_rand_report_sound())
announce_command_report = FALSE
if("Cancel")
return
diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm
index 087b57f9e2..02289684f5 100644
--- a/code/modules/admin/verbs/secrets.dm
+++ b/code/modules/admin/verbs/secrets.dm
@@ -352,7 +352,7 @@
if(is_station_level(W.z) && !istype(get_area(W), /area/command) && !istype(get_area(W), /area/commons) && !istype(get_area(W), /area/service) && !istype(get_area(W), /area/command/heads_quarters) && !istype(get_area(W), /area/security/prison))
W.req_access = list()
message_admins("[key_name_admin(holder)] activated Egalitarian Station mode")
- priority_announce("CentCom airlock control override activated. Please take this time to get acquainted with your coworkers.", null, "commandreport")
+ priority_announce("CentCom airlock control override activated. Please take this time to get acquainted with your coworkers.", null, SSstation.announcer.get_rand_report_sound())
if("ancap")
if(!is_funmin)
return
@@ -360,9 +360,9 @@
SSeconomy.full_ancap = !SSeconomy.full_ancap
message_admins("[key_name_admin(holder)] toggled Anarcho-capitalist mode")
if(SSeconomy.full_ancap)
- priority_announce("The NAP is now in full effect.", null, "commandreport")
+ priority_announce("The NAP is now in full effect.", null, SSstation.announcer.get_rand_report_sound())
else
- priority_announce("The NAP has been revoked.", null, "commandreport")
+ priority_announce("The NAP has been revoked.", null, SSstation.announcer.get_rand_report_sound())
if("blackout")
if(!is_funmin)
return
diff --git a/code/modules/events/pirates.dm b/code/modules/events/pirates.dm
index 47a466bf0a..ed67c84a43 100644
--- a/code/modules/events/pirates.dm
+++ b/code/modules/events/pirates.dm
@@ -37,7 +37,7 @@
// if(PIRATES_DUTCHMAN)
// ship_name = "Flying Dutchman"
- priority_announce("Incoming subspace communication. Secure channel opened at all communication consoles.", "Incoming Message", "commandreport")
+ priority_announce("Incoming subspace communication. Secure channel opened at all communication consoles.", "Incoming Message", SSstation.announcer.get_rand_report_sound())
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
if(D)
payoff = max(payoff_min, FLOOR(D.account_balance * 0.80, 1000))
diff --git a/code/modules/station_goals/station_goal.dm b/code/modules/station_goals/station_goal.dm
index b68fef3e2f..856bf08ff3 100644
--- a/code/modules/station_goals/station_goal.dm
+++ b/code/modules/station_goals/station_goal.dm
@@ -12,7 +12,7 @@
var/report_message = "Complete this goal."
/datum/station_goal/proc/send_report()
- priority_announce("Priority Nanotrasen directive received. Project \"[name]\" details inbound.", "Incoming Priority Message", "commandreport")
+ priority_announce("Priority Nanotrasen directive received. Project \"[name]\" details inbound.", "Incoming Priority Message", SSstation.announcer.get_rand_report_sound())
print_command_report(get_report(),"Nanotrasen Directive [pick(GLOB.phonetic_alphabet)] \Roman[rand(1,50)]", announce=FALSE)
on_report()
diff --git a/tgui/packages/common/exhaustive.ts b/tgui/packages/common/exhaustive.ts
new file mode 100644
index 0000000000..bc41757515
--- /dev/null
+++ b/tgui/packages/common/exhaustive.ts
@@ -0,0 +1,19 @@
+/**
+ * Throws an error such that a non-exhaustive check will error at compile time
+ * when using TypeScript, rather than at runtime.
+ *
+ * For example:
+ * enum Color { Red, Green, Blue }
+ * switch (color) {
+ * case Color.Red:
+ * return "red";
+ * case Color.Green:
+ * return "green";
+ * default:
+ * // This will error at compile time that we forgot blue.
+ * exhaustiveCheck(color);
+ * }
+ */
+export const exhaustiveCheck = (input: never) => {
+ throw new Error(`Unhandled case: ${input}`);
+};
diff --git a/tgui/packages/tgui/interfaces/StationTraitsPanel.tsx b/tgui/packages/tgui/interfaces/StationTraitsPanel.tsx
new file mode 100644
index 0000000000..d09d109d62
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/StationTraitsPanel.tsx
@@ -0,0 +1,251 @@
+import { filterMap } from 'common/collections';
+import { exhaustiveCheck } from 'common/exhaustive';
+import { BooleanLike } from 'common/react';
+import { useBackend, useLocalState } from '../backend';
+import { Box, Button, Divider, Dropdown, Stack, Tabs } from '../components';
+import { Window } from '../layouts';
+
+type CurrentStationTrait = {
+ can_revert: BooleanLike;
+ name: string;
+ ref: string;
+};
+
+type ValidStationTrait = {
+ name: string;
+ path: string;
+};
+
+type StationTraitsData = {
+ current_traits: CurrentStationTrait[];
+ future_station_traits?: ValidStationTrait[];
+ too_late_to_revert: BooleanLike;
+ valid_station_traits: ValidStationTrait[];
+};
+
+enum Tab {
+ SetupFutureStationTraits,
+ ViewStationTraits,
+}
+
+const FutureStationTraitsPage = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { future_station_traits } = data;
+
+ const [selectedTrait, setSelectedTrait] = useLocalState(
+ context,
+ 'selectedFutureTrait',
+ null
+ );
+
+ const traitsByName = Object.fromEntries(
+ data.valid_station_traits.map((trait) => {
+ return [trait.name, trait.path];
+ })
+ );
+
+ const traitNames = Object.keys(traitsByName);
+ traitNames.sort();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {Array.isArray(future_station_traits) ? (
+ future_station_traits.length > 0 ? (
+
+ {future_station_traits.map((trait) => (
+
+
+ {trait.name}
+
+
+
+
+
+
+ ))}
+
+ ) : (
+ <>
+ No station traits will run next round.
+
+
+
+
+ >
+ )
+ ) : (
+ <>
+ No future station traits are planned.
+
+
+
+
+ >
+ )}
+
+ );
+};
+
+const ViewStationTraitsPage = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return data.current_traits.length > 0 ? (
+
+ {data.current_traits.map((stationTrait) => (
+
+
+ {stationTrait.name}
+
+
+
+ act('revert', {
+ ref: stationTrait.ref,
+ })}
+ />
+
+
+
+ ))}
+
+ ) : (
+ There are no active station traits.
+ );
+};
+
+export const StationTraitsPanel = (props, context) => {
+ const [currentTab, setCurrentTab] = useLocalState(
+ context,
+ 'station_traits_tab',
+ Tab.ViewStationTraits
+ );
+
+ let currentPage;
+
+ switch (currentTab) {
+ case Tab.SetupFutureStationTraits:
+ currentPage = ;
+ break;
+ case Tab.ViewStationTraits:
+ currentPage = ;
+ break;
+ default:
+ exhaustiveCheck(currentTab);
+ }
+
+ return (
+
+
+
+ setCurrentTab(Tab.ViewStationTraits)}>
+ View
+
+
+ setCurrentTab(Tab.SetupFutureStationTraits)}>
+ Edit
+
+
+
+
+
+ {currentPage}
+
+
+ );
+};