diff --git a/code/_globalvars/lists/faxes.dm b/code/_globalvars/lists/faxes.dm
new file mode 100644
index 00000000000000..54037f2ca5005f
--- /dev/null
+++ b/code/_globalvars/lists/faxes.dm
@@ -0,0 +1,21 @@
+/**
+ * This defines the list of faxes managed by the server administrators. They are not physically present in
+ * the game, but are shown in the fax list as existing.
+ * Lists:
+ * * additional_faxes_list - A list of "legal" faxes available with authorization.
+ * * syndicate_faxes_list - List of faxes available after hacking.
+ *
+ * The list consists of the following elements:
+ * * fax_name - The name displayed in the fax list.
+ * * button_color - The color of this fax button in the list of all faxes.
+ */
+GLOBAL_LIST_INIT(additional_faxes_list, list(
+ list("fax_name" = "Central Command", "button_color" = "#34c924"),
+ list("fax_name" = "Clown Planet", "button_color" = "#f4c800"),
+))
+
+GLOBAL_LIST_INIT(syndicate_faxes_list, list(
+ list("fax_name" = "Syndicate Coordination Center", "button_color" = "#ff0000"),
+ list("fax_name" = "Federation of Wizards", "button_color" = "#8b00ff"),
+ list("fax_name" = "Nar-Sie Church", "button_color" = "#8b0000"),
+))
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 912f056518c0fa..8d95c7a633cffb 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -20,7 +20,8 @@ GLOBAL_PROTECT(admin_verbs_default)
/client/proc/tag_datum_mapview,
/client/proc/debugstatpanel,
/client/proc/fix_air, /*resets air in designated radius to its default atmos composition*/
- /client/proc/requests
+ /client/proc/requests,
+ /client/proc/fax_manager,
)
GLOBAL_LIST_INIT(admin_verbs_admin, world.AVerbsAdmin())
GLOBAL_PROTECT(admin_verbs_admin)
diff --git a/code/modules/admin/verbs/fax_manager.dm b/code/modules/admin/verbs/fax_manager.dm
new file mode 100644
index 00000000000000..127cbf7df5fe5d
--- /dev/null
+++ b/code/modules/admin/verbs/fax_manager.dm
@@ -0,0 +1,6 @@
+/client/proc/fax_manager()
+ set name = "Fax Manager"
+ set desc = "Open the manager panel to view all requests during the round in progress."
+ set category = "Admin.Game"
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "Fax Manager") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ GLOB.fax_manager.ui_interact(usr)
diff --git a/code/modules/paperwork/fax.dm b/code/modules/paperwork/fax.dm
index 40255285be9a14..a73790abc51653 100644
--- a/code/modules/paperwork/fax.dm
+++ b/code/modules/paperwork/fax.dm
@@ -18,6 +18,10 @@
var/seconds_electrified = MACHINE_NOT_ELECTRIFIED
/// If true, the fax machine is jammed and needs cleaning
var/jammed = FALSE
+ /// Determines the possibility of sending papers to the additional faxes.
+ var/access_additional_faxes = FALSE
+ /// Defines a list of accesses whose owners can open a connection with the additional faxes.
+ var/static/access_additional_faxes_required = list(ACCESS_CAPTAIN)
/// Necessary to hide syndicate faxes from the general list. Doesn't mean he's EMAGGED!
var/syndicate_network = FALSE
/// True if the fax machine should be visible to other fax machines in general.
@@ -150,6 +154,26 @@
return
return ..()
+// Checks if the card has access to switch "legal" faxes of administrators.
+/obj/machinery/fax/proc/access_additional_faxes_check(mob/living/user)
+ if(isAdminGhostAI(user))
+ return TRUE
+
+ var/obj/item/card/id/used_id = user.get_idcard(TRUE)
+ // We check if it makes sense to check access at all.
+ if(!access_additional_faxes_required || !used_id.access)
+ return FALSE
+
+ for(var/requested_access in access_additional_faxes_required)
+ if(requested_access in used_id.access)
+ return TRUE
+ return FALSE
+
+// Switches access to the "legal" administrator's fax list. Access to the "illegal" is switched by hacking.
+/obj/machinery/fax/proc/access_additional_faxes_toggle()
+ access_additional_faxes = !access_additional_faxes
+ say("The channel of communication with CentCom is [access_additional_faxes ? "open" : "close"].")
+
/**
* Attempts to clean out a jammed machine using a passed item.
* Returns true if successful.
@@ -204,6 +228,12 @@
ui = new(user, src, "Fax")
ui.open()
+/obj/machinery/fax/ui_static_data(mob/user)
+ var/list/data = list()
+ data["additional_faxes_list"] = GLOB.additional_faxes_list
+ data["syndicate_faxes_list"] = GLOB.syndicate_faxes_list
+ return data
+
/obj/machinery/fax/ui_data(mob/user)
var/list/data = list()
//Record a list of all existing faxes.
@@ -223,6 +253,8 @@
data["fax_id"] = fax_id
data["fax_name"] = fax_name
data["visible"] = visible_to_network
+ data["access_additional_faxes"] = access_additional_faxes
+ data["сan_switch_access"] = access_additional_faxes_check(user)
// In this case, we don't care if the fax is hacked or in the syndicate's network. The main thing is to check the visibility of other faxes.
data["syndicate_network"] = (syndicate_network || (obj_flags & EMAGGED))
data["has_paper"] = !!loaded_item_ref?.resolve()
@@ -245,6 +277,8 @@
playsound(src, 'sound/machines/eject.ogg', 50, FALSE)
update_appearance()
return TRUE
+ if("access_additional_faxes_toggle")
+ access_additional_faxes_toggle()
if("send")
var/obj/item/loaded = loaded_item_ref?.resolve()
if (!loaded)
@@ -255,6 +289,17 @@
loaded_item_ref = null
update_appearance()
return TRUE
+ if("send_to_additional_fax")
+ var/obj/item/loaded = loaded_item_ref?.resolve()
+ if (!loaded)
+ return
+ if(istype(loaded, /obj/item/paper))
+ if(send_to_additional_faxes(loaded, usr, params["name"], params["color"]))
+ loaded_item_ref = null
+ update_appearance()
+ return TRUE
+ else
+ say("The destination fax blocks the reception of this item.")
if("history_clear")
history_clear()
return TRUE
@@ -293,12 +338,39 @@
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
return FALSE
FAX.receive(loaded, fax_name)
- history_add("Send", FAX.fax_name)
- INVOKE_ASYNC(src, .proc/animate_object_travel, loaded, "fax_receive", find_overlay_state(loaded, "send"))
- playsound(src, 'sound/machines/high_tech_confirm.ogg', 50, FALSE)
+ playback_sending(loaded, FAX.fax_name)
return TRUE
return FALSE
+/**
+ * The procedure for sending a paper to virtual admins fax machine.
+ *
+ * This procedure is similar to the send procedure except that it sends the paper to
+ * a "virtual" fax to a special administrator list.
+ * Arguments:
+ * * paper - The paper to be sent.
+ * * sender - Reference to the sender's substance.
+ * * receiver_name - The recipient's fax name, which will be displayed in the administrator's list.
+ * * receiver_color - The color the receiver_name will be colored in.
+ */
+/obj/machinery/fax/proc/send_to_additional_faxes(obj/item/paper/paper, mob/sender, receiver_name, receiver_color)
+ GLOB.fax_manager.receive_request(sender, src, receiver_name, paper, receiver_color)
+ playback_sending(paper, receiver_name)
+ return TRUE
+
+/**
+ * The procedure for playing the animation and the sending sound.
+ *
+ * Procedure called to add to the history of sending messages, playing the sending animation and sound.
+ * Arguments:
+ * * loaded - Sending item to determine the animation..
+ * * receiver_name - Recipient's name to be added to the message history.
+ */
+/obj/machinery/fax/proc/playback_sending(obj/item/loaded, receiver_name)
+ history_add("Send", receiver_name)
+ INVOKE_ASYNC(src, .proc/animate_object_travel, loaded, "fax_receive", find_overlay_state(loaded, "send"))
+ playsound(src, 'sound/machines/high_tech_confirm.ogg', 50, FALSE)
+
/**
* Procedure for accepting papers from another fax machine.
*
diff --git a/code/modules/paperwork/fax_manager.dm b/code/modules/paperwork/fax_manager.dm
new file mode 100644
index 00000000000000..67fd231dc87166
--- /dev/null
+++ b/code/modules/paperwork/fax_manager.dm
@@ -0,0 +1,149 @@
+GLOBAL_DATUM_INIT(fax_manager, /datum/fax_manager, new)
+
+/**
+ * Fax Request Manager
+ *
+ * In its functionality it is similar to the usual Request Manager, but respectively for faxes.
+ * This manager allows you to send faxes on behalf of certain virtual faxes to all existing faxes,
+ * as well as receive faxes in their name from the players.
+ */
+/datum/fax_manager
+ /// A list that contains faxes from players and other related information. You can view the filling of its fields in procedure receive_request.
+ var/list/requests = list()
+
+/datum/fax_manager/Destroy(force, ...)
+ QDEL_LIST(requests)
+ return ..()
+
+/datum/fax_manager/ui_interact(mob/user, datum/tgui/ui = null)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if (!ui)
+ ui = new(user, src, "FaxManager")
+ ui.open()
+
+/datum/fax_manager/ui_state(mob/user)
+ return GLOB.admin_state
+
+/datum/fax_manager/ui_static_data(mob/user)
+ var/list/data = list()
+ // Record additional faxes on a separate list
+ data["additional_faxes"] = GLOB.additional_faxes_list + GLOB.syndicate_faxes_list
+ return data
+
+/datum/fax_manager/ui_data(mob/user)
+ var/list/data = list()
+ //Record a list of all existing faxes.
+ for(var/obj/machinery/fax/FAX in GLOB.machines)
+ var/list/fax_data = list()
+ fax_data["fax_name"] = FAX.fax_name
+ fax_data["fax_id"] = FAX.fax_id
+ fax_data["syndicate_network"] = FAX.syndicate_network
+ data["faxes"] += list(fax_data)
+ for(var/list/REQUEST in requests)
+ var/list/request = list()
+ request["id_message"] = REQUEST["id_message"]
+ request["time"] = REQUEST["time"]
+ var/mob/sender = REQUEST["sender"]
+ request["sender_name"] = sender.name
+ request["sender_fax_id"] = REQUEST["sender_fax_id"]
+ request["sender_fax_name"] = REQUEST["sender_fax_name"]
+ request["receiver_fax_name"] = REQUEST["receiver_fax_name"]
+ data["requests"] += list(request)
+ return data
+
+/datum/fax_manager/ui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("send")
+ for(var/obj/machinery/fax/FAX in GLOB.machines)
+ if (FAX.fax_id == params["fax_id"])
+ var/obj/item/paper/paper = new()
+ paper.add_raw_text(params["message"])
+ FAX.receive(paper, params["fax_name"])
+ return TRUE
+ if("flw_fax")
+ for(var/obj/machinery/fax/FAX in GLOB.machines)
+ if (FAX.fax_id == params["fax_id"])
+ usr.client.admin_follow(FAX)
+ return TRUE
+ if("read_message")
+ var/list/REQUEST = get_request(params["id_message"])
+ var/obj/item/paper/request/paper = REQUEST["paper"]
+ paper.ui_interact(usr)
+ return TRUE
+ if("flw")
+ var/list/REQUEST = get_request(params["id_message"])
+ usr.client.admin_follow(REQUEST["sender"])
+ return TRUE
+ if("pp")
+ var/list/REQUEST = get_request(params["id_message"])
+ usr.client.holder.show_player_panel(REQUEST["sender"])
+ return TRUE
+ if("vv")
+ var/list/REQUEST = get_request(params["id_message"])
+ usr.client.debug_variables(REQUEST["sender"])
+ return TRUE
+ if("sm")
+ var/list/REQUEST = get_request(params["id_message"])
+ usr.client.cmd_admin_subtle_message(REQUEST["sender"])
+ return TRUE
+ if("logs")
+ var/list/REQUEST = get_request(params["id_message"])
+ if(!ismob(REQUEST["sender"]))
+ to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE)
+ return TRUE
+ show_individual_logging_panel(REQUEST["sender"], null, null)
+ return TRUE
+ if("smite")
+ var/list/REQUEST = get_request(params["id_message"])
+ if(!check_rights(R_FUN))
+ to_chat(usr, "Insufficient permissions to smite, you require +FUN", confidential = TRUE)
+ return TRUE
+ var/mob/living/carbon/human/H = REQUEST["sender"]
+ if (!H || !istype(H))
+ to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human", confidential = TRUE)
+ return TRUE
+ usr.client.smite(H)
+ return TRUE
+
+/datum/fax_manager/proc/get_request(id_message)
+ for(var/list/REQUEST in requests)
+ if(REQUEST["id_message"] == id_message)
+ return REQUEST
+
+/datum/fax_manager/proc/receive_request(mob/sender, obj/machinery/fax/sender_fax, receiver_fax_name, obj/item/paper/paper, receiver_color)
+ var/list/request = list()
+ var/obj/item/paper/request/message = new()
+ request["id_message"] = requests.len
+ request["time"] = gameTimestamp()
+ request["sender"] = sender
+ request["sender_fax_id"] = sender_fax.fax_id
+ request["sender_fax_name"] = sender_fax.fax_name
+ request["receiver_fax_name"] = receiver_fax_name
+ message.copy_properties(paper)
+ request["paper"] = message
+ requests += list(request)
+ var/msg = span_adminnotice("[sanitize(receiver_fax_name)] fax received a message from [sanitize(sender_fax.fax_name)][ADMIN_JMP(sender_fax)]/[ADMIN_FULLMONTY(sender)]")
+ to_chat(GLOB.admins, msg, confidential = TRUE)
+ for(var/client/admin in GLOB.admins)
+ if((admin.prefs.chat_toggles & CHAT_PRAYER) && (admin.prefs.toggles & SOUND_PRAYERS))
+ SEND_SOUND(admin, sound('sound/machines/printer.ogg'))
+
+// A special piece of paper for the administrator that will open the interface no matter what.
+/obj/item/paper/request/ui_status()
+ return UI_INTERACTIVE
+
+// I'm sure there's a better way to transfer it, I just couldn't find it
+/obj/item/paper/request/proc/copy_properties(obj/item/paper/paper)
+ raw_text_inputs = paper.raw_text_inputs
+ raw_stamp_data = paper.raw_stamp_data
+ raw_field_input_data = paper.raw_field_input_data
+ show_written_words = paper.show_written_words
+ stamp_cache = paper.stamp_cache
+ contact_poison = paper.contact_poison
+ contact_poison_volume = paper.contact_poison_volume
+ default_raw_text = paper.default_raw_text
+ input_field_count = paper.input_field_count
diff --git a/tgstation.dme b/tgstation.dme
index 9470644eaa48e1..beff93e271b164 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -403,6 +403,7 @@
#include "code\_globalvars\lists\ambience.dm"
#include "code\_globalvars\lists\client.dm"
#include "code\_globalvars\lists\color.dm"
+#include "code\_globalvars\lists\faxes.dm"
#include "code\_globalvars\lists\flavor_misc.dm"
#include "code\_globalvars\lists\keybindings.dm"
#include "code\_globalvars\lists\maintenance_loot.dm"
@@ -2097,6 +2098,7 @@
#include "code\modules\admin\verbs\debug.dm"
#include "code\modules\admin\verbs\diagnostics.dm"
#include "code\modules\admin\verbs\ert.dm"
+#include "code\modules\admin\verbs\fax_manager.dm"
#include "code\modules\admin\verbs\fix_air.dm"
#include "code\modules\admin\verbs\fov.dm"
#include "code\modules\admin\verbs\fps.dm"
@@ -3912,6 +3914,7 @@
#include "code\modules\paperwork\contract.dm"
#include "code\modules\paperwork\desk_bell.dm"
#include "code\modules\paperwork\fax.dm"
+#include "code\modules\paperwork\fax_manager.dm"
#include "code\modules\paperwork\filingcabinet.dm"
#include "code\modules\paperwork\folders.dm"
#include "code\modules\paperwork\folders_premade.dm"
diff --git a/tgui/packages/tgui/interfaces/Fax.tsx b/tgui/packages/tgui/interfaces/Fax.tsx
index 6c5a2256089d23..febb9f0cd7c40f 100644
--- a/tgui/packages/tgui/interfaces/Fax.tsx
+++ b/tgui/packages/tgui/interfaces/Fax.tsx
@@ -9,7 +9,11 @@ type FaxData = {
fax_name: string;
visible: boolean;
has_paper: string;
+ access_additional_faxes: boolean;
+ сan_switch_access: boolean;
+ additional_faxes_list: AdditionalFaxesList[];
syndicate_network: boolean;
+ syndicate_faxes_list: AdditionalFaxesList[];
fax_history: FaxHistory[];
};
@@ -21,6 +25,11 @@ type FaxInfo = {
syndicate_network: boolean;
};
+type AdditionalFaxesList = {
+ fax_name: string;
+ button_color: string;
+};
+
type FaxHistory = {
history_type: string;
history_fax_name: string;
@@ -67,8 +76,50 @@ export const Fax = (props, context) => {
)}
-
+ act('access_additional_faxes_toggle')}
+ disabled={!data.сan_switch_access}
+ tooltip="Manage access to the expanded fax list.">
+ {data.access_additional_faxes ? 'Unlogin' : 'Login'}
+
+ }>
+ {!!data.access_additional_faxes &&
+ data.additional_faxes_list.map((fax: AdditionalFaxesList) => (
+
+ ))}
+ {!!data.syndicate_network &&
+ data.syndicate_faxes_list.map((fax: AdditionalFaxesList) => (
+
+ ))}
{faxes.map((fax: FaxInfo) => (