diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm
index e267097ff81..bd9ac554882 100644
--- a/code/__DEFINES/admin.dm
+++ b/code/__DEFINES/admin.dm
@@ -68,6 +68,8 @@
#define ADMIN_TAG(datum) "(TAG)"
#define ADMIN_LUAVIEW(state) "(VIEW STATE)"
#define ADMIN_LUAVIEW_CHUNK(state, log_index) "(VIEW CODE)"
+/// Displays "(SHOW)" in the chat, when clicked it tries to show atom(paper). First you need to set the request_state variable to TRUE for the paper.
+#define ADMIN_SHOW_PAPER(atom) "(SHOW)"
/atom/proc/Admin_Coordinates_Readable(area_name, admin_jump_ref)
var/turf/T = Safe_COORD_Location()
diff --git a/code/modules/admin/admin_fax_panel.dm b/code/modules/admin/admin_fax_panel.dm
new file mode 100644
index 00000000000..169a089e519
--- /dev/null
+++ b/code/modules/admin/admin_fax_panel.dm
@@ -0,0 +1,145 @@
+/**
+ * If client have R_ADMIN flag, opens an admin fax panel.
+ */
+/client/proc/fax_panel()
+ set category = "Admin.Events"
+ set name = "Fax Panel"
+
+ if(!check_rights(R_ADMIN))
+ return
+
+ var/datum/fax_panel_interface/ui = new(usr)
+ ui.ui_interact(usr)
+
+/// Admin Fax Panel. Tool for sending fax messages faster.
+/datum/fax_panel_interface
+ /// All faxes in from machinery list()
+ var/available_faxes = list()
+ /// List with available stamps
+ var/stamp_list = list()
+
+ /// Paper which admin edit and send.
+ var/obj/item/paper/fax_paper = new /obj/item/paper(null)
+
+ /// Default name of fax. Used when field with fax name not edited.
+ var/sending_fax_name = "Secret"
+ /// Default name of paper. paper - bluh-bluh. Used when field with paper name not edited.
+ var/default_paper_name = "Standart Report"
+
+/datum/fax_panel_interface/New()
+ //Get all faxes, and save them to our list.
+ for(var/obj/machinery/fax/fax in GLOB.machines)
+ available_faxes += WEAKREF(fax)
+
+ //Get all stamps
+ for(var/stamp in subtypesof(/obj/item/stamp))
+ var/obj/item/stamp/real_stamp = new stamp()
+ if(!istype(real_stamp, /obj/item/stamp/chameleon) && !istype(real_stamp, /obj/item/stamp/mod))
+ var/stamp_detail = real_stamp.get_writing_implement_details()
+ stamp_list += list(list(real_stamp.name, real_stamp.icon_state, stamp_detail["stamp_class"]))
+
+ //Give our paper special status, to read everywhere.
+ fax_paper.request_state = TRUE
+
+/**
+ * Return fax if name exists
+ * Arguments:
+ * * name - Name of fax what we try to find.
+ */
+/datum/fax_panel_interface/proc/get_fax_by_name(name)
+ if(!length(available_faxes))
+ return null
+
+ for(var/datum/weakref/weakrefed_fax as anything in available_faxes)
+ var/obj/machinery/fax/potential_fax = weakrefed_fax.resolve()
+ if(potential_fax && istype(potential_fax))
+ if(potential_fax.fax_name == name)
+ return potential_fax
+ return null
+
+/datum/fax_panel_interface/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "AdminFax")
+ ui.open()
+
+/datum/fax_panel_interface/ui_state(mob/user)
+ return GLOB.admin_state
+
+/datum/fax_panel_interface/ui_static_data(mob/user)
+ var/list/data = list()
+
+ data["faxes"] = list()
+ data["stamps"] = list()
+
+ for(var/stamp in stamp_list)
+ data["stamps"] += list(stamp[1]) // send only names.
+
+ for(var/datum/weakref/weakrefed_fax as anything in available_faxes)
+ var/obj/machinery/fax/another_fax = weakrefed_fax.resolve()
+ if(another_fax && istype(another_fax))
+ data["faxes"] += list(another_fax.fax_name)
+
+ return data
+
+/datum/fax_panel_interface/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ if(..())
+ return
+
+ if(!check_rights(R_ADMIN))
+ return
+
+ var/obj/machinery/fax/action_fax
+
+ if(params["faxName"])
+ action_fax = get_fax_by_name(params["faxName"])
+
+ switch(action)
+
+ if("follow")
+ if(!isobserver(usr))
+ usr.client?.admin_ghost()
+
+ usr.client?.admin_follow(action_fax)
+
+ if("preview") // see saved variant
+ if(!fax_paper)
+ return
+ fax_paper.ui_interact(usr)
+
+ if("save") // save paper
+ if(params["paperName"])
+ default_paper_name = params["paperName"]
+ if(params["fromWho"])
+ sending_fax_name = params["fromWho"]
+
+ fax_paper.clear_paper()
+ var/stamp
+ var/stamp_class
+
+ for(var/needed_stamp in stamp_list)
+ if(needed_stamp[1] == params["stamp"])
+ stamp = needed_stamp[2]
+ stamp_class = needed_stamp[3]
+ break
+
+ fax_paper.name = "paper — [default_paper_name]"
+ fax_paper.add_raw_text(params["rawText"])
+
+ if(stamp)
+ fax_paper.add_stamp(stamp_class, params["stampX"], params["stampY"], params["stampAngle"], stamp)
+
+ fax_paper.update_static_data(usr) // OK, it's work, and update UI.
+
+ if("send")
+ //copy
+ var/obj/item/paper/our_fax = fax_paper.copy(/obj/item/paper)
+ our_fax.name = fax_paper.name
+ //send
+ action_fax.receive(our_fax, sending_fax_name)
+ message_admins("[key_name_admin(usr)] has send custom fax message to [action_fax.name][ADMIN_FLW(action_fax)][ADMIN_SHOW_PAPER(fax_paper)].")
+ log_admin("[key_name(usr)] has send custom fax message to [action_fax.name]")
+
+ if("createPaper")
+ var/obj/item/paper/our_paper = fax_paper.copy(/obj/item/paper, usr.loc)
+ our_paper.name = fax_paper.name
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 58a3b47f9ba..f0ef72fe19f 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -86,6 +86,7 @@ GLOBAL_PROTECT(admin_verbs_admin)
/client/proc/list_dna,
/client/proc/list_fingerprints,
/client/proc/message_pda, /*send a message to somebody on PDA*/
+ /client/proc/fax_panel, /*send a paper to fax*/
/datum/admins/proc/trophy_manager,
)
GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/ban_panel, /client/proc/stickybanpanel))
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index d675b81df26..94ba7af49fb 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1738,3 +1738,12 @@
editor.force_view_chunk = log_entry["chunk"]
editor.force_modal = "viewChunk"
editor.ui_interact(usr)
+
+ else if(href_list["show_paper"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ var/obj/item/paper/paper_to_show = locate(href_list["show_paper"])
+ if(!paper_to_show)
+ return
+ paper_to_show.ui_interact(usr)
diff --git a/code/modules/paperwork/fax.dm b/code/modules/paperwork/fax.dm
index 846705c61e4..f576dd0c238 100644
--- a/code/modules/paperwork/fax.dm
+++ b/code/modules/paperwork/fax.dm
@@ -44,6 +44,11 @@
/obj/item/holochip,
/obj/item/card
)
+ /// List with a fake-networks(not a fax actually), for request manager.
+ var/list/special_networks = list(
+ list(fax_name = "Central Command", fax_id = "central_command", color = "teal", emag_needed = FALSE),
+ list(fax_name = "Sabotage Department", fax_id = "syndicate", color = "red", emag_needed = TRUE),
+ )
/obj/machinery/fax/Initialize(mapload)
. = ..()
@@ -227,6 +232,7 @@
data["syndicate_network"] = (syndicate_network || (obj_flags & EMAGGED))
data["has_paper"] = !!loaded_item_ref?.resolve()
data["fax_history"] = fax_history
+ data["special_faxes"] = special_networks
return data
/obj/machinery/fax/ui_act(action, list/params)
@@ -245,6 +251,7 @@
playsound(src, 'sound/machines/eject.ogg', 50, FALSE)
update_appearance()
return TRUE
+
if("send")
var/obj/item/loaded = loaded_item_ref?.resolve()
if (!loaded)
@@ -255,6 +262,27 @@
loaded_item_ref = null
update_appearance()
return TRUE
+
+ if("send_special")
+ var/obj/item/paper/fax_paper = loaded_item_ref?.resolve()
+ if(!istype(fax_paper))
+ to_chat(usr, icon2html(src.icon, usr) + span_warning("Fax cannot send all above paper on this protected network, sorry."))
+ return
+
+ fax_paper.request_state = TRUE
+ fax_paper.loc = null
+
+ INVOKE_ASYNC(src, PROC_REF(animate_object_travel), fax_paper, "fax_receive", find_overlay_state(fax_paper, "send"))
+ playsound(src, 'sound/machines/high_tech_confirm.ogg', 50, vary = FALSE)
+
+ history_add("Send", params["name"])
+
+ GLOB.requests.fax_request(usr.client, "sent a fax message from [fax_name]/[fax_id] to [params["name"]]", fax_paper)
+ to_chat(GLOB.admins, span_adminnotice("[icon2html(src.icon, GLOB.admins)]FAX REQUEST: [ADMIN_FULLMONTY(usr)]: [span_linkify("sent a fax message from [fax_name]/[fax_id][ADMIN_FLW(src)] to [params["name"]]")] [ADMIN_SHOW_PAPER(fax_paper)]"), confidential = TRUE)
+ log_fax(fax_paper, params["id"], params["name"])
+ loaded_item_ref = null
+ update_appearance()
+
if("history_clear")
history_clear()
return TRUE
@@ -473,3 +501,4 @@
return CONTEXTUAL_SCREENTIP_SET
return .
+
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 4ce5a54cff7..924080fec6a 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -63,6 +63,9 @@
/// state checking on if it should be shown to a viewer.
var/datum/weakref/camera_holder
+ ///If TRUE, staff can read paper everywhere, but usually from requests panel.
+ var/request_state = FALSE
+
/obj/item/paper/Initialize(mapload)
. = ..()
pixel_x = base_pixel_x + rand(-9, 9)
@@ -328,7 +331,7 @@
// Are we on fire? Hard to read if so
if(resistance_flags & ON_FIRE)
return UI_CLOSE
- if(camera_holder && can_show_to_mob_through_camera(user))
+ if(camera_holder && can_show_to_mob_through_camera(user) || request_state)
return UI_UPDATE
if(!in_range(user, src) && !isobserver(user))
return UI_CLOSE
diff --git a/code/modules/requests/request.dm b/code/modules/requests/request.dm
index 1acace4f969..ddbca243528 100644
--- a/code/modules/requests/request.dm
+++ b/code/modules/requests/request.dm
@@ -1,12 +1,3 @@
-/// Requests from prayers
-#define REQUEST_PRAYER "request_prayer"
-/// Requests for Centcom
-#define REQUEST_CENTCOM "request_centcom"
-/// Requests for the Syndicate
-#define REQUEST_SYNDICATE "request_syndicate"
-/// Requests for the nuke code
-#define REQUEST_NUKE "request_nuke"
-
/**
* # Request
*
@@ -27,10 +18,12 @@
var/owner_name
/// The message associated with the request
var/message
+ /// Just any information, which you can to send with request. For example paper datum.
+ var/additional_information
/// When the request was created
var/timestamp
-/datum/request/New(client/requestee, type, request)
+/datum/request/New(client/requestee, type, request, additional_info)
if (!requestee)
qdel(src)
return
@@ -39,5 +32,6 @@
owner_ckey = owner.ckey
req_type = type
message = request
+ additional_information = additional_info
timestamp = world.time
owner_name = key_name(requestee, FALSE)
diff --git a/code/modules/requests/request_manager.dm b/code/modules/requests/request_manager.dm
index de8fc5e0f63..fef7e9fb0dd 100644
--- a/code/modules/requests/request_manager.dm
+++ b/code/modules/requests/request_manager.dm
@@ -1,3 +1,14 @@
+/// Requests from prayers
+#define REQUEST_PRAYER "request_prayer"
+/// Requests for Centcom
+#define REQUEST_CENTCOM "request_centcom"
+/// Requests for the Syndicate
+#define REQUEST_SYNDICATE "request_syndicate"
+/// Requests for the nuke code
+#define REQUEST_NUKE "request_nuke"
+/// Requests somebody from fax
+#define REQUEST_FAX "request_fax"
+
GLOBAL_DATUM_INIT(requests, /datum/request_manager, new)
/**
@@ -86,6 +97,16 @@ GLOBAL_DATUM_INIT(requests, /datum/request_manager, new)
/datum/request_manager/proc/nuke_request(client/C, message)
request_for_client(C, REQUEST_NUKE, message)
+/**
+ * Creates a request for fax answer
+ *
+ * Arguments:
+ * * requester - The client who is sending the request
+ * * message - Paper with text.. some stamps.. and another things.
+ */
+/datum/request_manager/proc/fax_request(client/requester, message, additional_info)
+ request_for_client(requester, REQUEST_FAX, message, additional_info)
+
/**
* Creates a request and registers the request with all necessary internal tracking lists
*
@@ -94,8 +115,8 @@ GLOBAL_DATUM_INIT(requests, /datum/request_manager, new)
* * type - The type of request, see defines
* * message - The message
*/
-/datum/request_manager/proc/request_for_client(client/C, type, message)
- var/datum/request/request = new(C, type, message)
+/datum/request_manager/proc/request_for_client(client/C, type, message, additional_info)
+ var/datum/request/request = new(C, type, message, additional_info)
if (!requests[C.ckey])
requests[C.ckey] = list()
requests[C.ckey] += request
@@ -193,6 +214,13 @@ GLOBAL_DATUM_INIT(requests, /datum/request_manager, new)
SD.r_code = code
message_admins("[key_name_admin(usr)] has set the self-destruct code to \"[code]\".")
return TRUE
+ if ("show")
+ if(request.req_type != REQUEST_FAX)
+ to_chat(usr, "Request doesn't have a paper to read.", confidential = TRUE)
+ return TRUE
+ var/obj/item/paper/request_message = request.additional_information
+ request_message.ui_interact(usr)
+ return TRUE
/datum/request_manager/ui_data(mob/user)
. = list(
@@ -207,7 +235,14 @@ GLOBAL_DATUM_INIT(requests, /datum/request_manager, new)
"owner_ckey" = request.owner_ckey,
"owner_name" = request.owner_name,
"message" = request.message,
+ "additional_info" = request.additional_information,
"timestamp" = request.timestamp,
"timestamp_str" = gameTimestamp(wtime = request.timestamp)
)
.["requests"] += list(data)
+
+#undef REQUEST_PRAYER
+#undef REQUEST_CENTCOM
+#undef REQUEST_SYNDICATE
+#undef REQUEST_NUKE
+#undef REQUEST_FAX
diff --git a/tgstation.dme b/tgstation.dme
index 5fa491c80a9..489cd66868d 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2062,6 +2062,7 @@
#include "code\modules\actionspeed\modifiers\mood.dm"
#include "code\modules\actionspeed\modifiers\status_effects.dm"
#include "code\modules\admin\admin.dm"
+#include "code\modules\admin\admin_fax_panel.dm"
#include "code\modules\admin\admin_investigate.dm"
#include "code\modules\admin\admin_pda_message.dm"
#include "code\modules\admin\admin_ranks.dm"
diff --git a/tgui/packages/tgui/interfaces/AdminFax.js b/tgui/packages/tgui/interfaces/AdminFax.js
new file mode 100644
index 00000000000..a2166fabda0
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AdminFax.js
@@ -0,0 +1,237 @@
+import { useBackend, useLocalState } from '../backend';
+import { Section, Box, Dropdown, Button, Input, TextArea, Divider, NumberInput, Tooltip, Knob } from '../components';
+import { Window } from '../layouts';
+
+export const AdminFax = (props, context) => {
+ return (
+