diff --git a/code/game/machinery/telecomms/logbrowser.dm b/code/game/machinery/telecomms/logbrowser.dm
index fbff4a4ec73..aaf54ca770c 100644
--- a/code/game/machinery/telecomms/logbrowser.dm
+++ b/code/game/machinery/telecomms/logbrowser.dm
@@ -19,205 +19,188 @@
req_access = list(access_tcomsat)
- attack_hand(mob/user as mob)
- if(stat & (BROKEN|NOPOWER))
- return
- user.set_machine(src)
- var/dat = "
Telecommunication Server MonitorTelecommunications Server Monitor"
+ var/last_print_time
- switch(screen)
-
-
- // --- Main Menu ---
-
- if(0)
- dat += "
[temp]
"
- dat += "
Current Network: [network]
"
- if(servers.len)
- dat += "
Detected Telecommunication Servers:"
- for(var/obj/machinery/telecomms/T in servers)
- dat += "- \ref[T] [T.name] ([T.id])
"
- dat += "
"
- dat += "
\[Flush Buffer\]"
-
- else
- dat += "
No servers detected. Scan for servers: \[Scan\]"
-
-
- // --- Viewing Server ---
-
- if(1)
- dat += "
[temp]
"
- dat += "\[Main Menu\] \[Refresh\]"
- dat += "
Current Network: [network]"
- dat += "
Selected Server: [SelectedServer.id]"
-
- if(SelectedServer.totaltraffic >= 1024)
- dat += "
Total recorded traffic: [round(SelectedServer.totaltraffic / 1024)] Terrabytes
"
- else
- dat += "
Total recorded traffic: [SelectedServer.totaltraffic] Gigabytes
"
-
- dat += "Stored Logs: "
-
- var/i = 0
- for(var/datum/comm_log_entry/C in SelectedServer.log_entries)
- i++
-
-
- // If the log is a speech file
- if(C.input_type == "Speech File")
-
- dat += "- [C.name] \[X\]
"
-
- // -- Determine race of orator --
-
- var/race = C.parameters["race"] // The actual race of the mob
- var/language = C.parameters["language"] // The language spoken, or null/""
-
- // -- If the orator is a human, or universal translate is active, OR mob has universal speech on --
-
- if(universal_translate || C.parameters["uspeech"] || C.parameters["intelligible"])
- dat += "Data type: [C.input_type]
"
- dat += "Source: [C.parameters["name"]] (Job: [C.parameters["job"]])
"
- dat += "Class: [race]
"
- dat += "Contents: \"[C.parameters["message"]]\"
"
- if(language)
- dat += "Language: [language]
"
-
- // -- Orator is not human and universal translate not active --
-
- else
- dat += "Data type: Audio File
"
- dat += "Source: Unidentifiable
"
- dat += "Class: [race]
"
- dat += "Contents: Unintelligble
"
-
- dat += "
"
-
- else if(C.input_type == "Execution Error")
-
- dat += "- [C.name] \[X\]
"
- dat += "Output: \"[C.parameters["message"]]\"
"
- dat += "
"
-
-
- dat += "
"
-
-
-
- user << browse(dat, "window=comm_monitor;size=575x400")
- onclose(user, "server_control")
-
- temp = ""
+/obj/machinery/computer/telecomms/server/attack_hand(mob/user)
+ if(stat & (BROKEN|NOPOWER))
return
+ user.set_machine(src)
+ var/dat = "Telecommunication Server MonitorTelecommunications Server Monitor"
+
+ switch(screen)
- Topic(href, href_list)
- if(..())
- return
+ // --- Main Menu ---
-
- add_fingerprint(usr)
- usr.set_machine(src)
-
- if(href_list["viewserver"])
- screen = 1
- for(var/obj/machinery/telecomms/T in servers)
- if(T.id == href_list["viewserver"])
- SelectedServer = T
- break
-
- if(href_list["operation"])
- switch(href_list["operation"])
-
- if("release")
- servers = list()
- screen = 0
-
- if("mainmenu")
- screen = 0
-
- if("scan")
- if(servers.len > 0)
- temp = "- FAILED: CANNOT PROBE WHEN BUFFER FULL -"
-
- else
- for(var/obj/machinery/telecomms/server/T in range(25, src))
- if(T.network == network)
- servers.Add(T)
-
- if(!servers.len)
- temp = "- FAILED: UNABLE TO LOCATE SERVERS IN \[[network]\] -"
- else
- temp = "- [servers.len] SERVERS PROBED & BUFFERED -"
-
- screen = 0
-
- if(href_list["delete"])
-
- if(!src.allowed(usr) && !emagged)
- to_chat(usr, "ACCESS DENIED.")
- return
-
- if(SelectedServer)
-
- var/datum/comm_log_entry/D = SelectedServer.log_entries[text2num(href_list["delete"])]
-
- temp = "- DELETED ENTRY: [D.name] -"
-
- SelectedServer.log_entries.Remove(D)
- qdel(D)
+ if(0)
+ dat += "
[temp]
"
+ dat += "
Current Network: [network]
"
+ if(servers.len)
+ dat += "
Detected Telecommunication Servers:"
+ for(var/obj/machinery/telecomms/T in servers)
+ dat += "- \ref[T] [T.name] ([T.id])
"
+ dat += "
"
+ dat += "
\[Flush Buffer\]"
else
- temp = "- FAILED: NO SELECTED MACHINE -"
+ dat += "
No servers detected. Scan for servers: \[Scan\]"
- if(href_list["network"])
- var/newnet = sanitize(input(usr, "Which network do you want to view?", "Comm Monitor", network) as null|text)
+ // --- Viewing Server ---
- if(newnet && ((usr in range(1, src) || issilicon(usr))))
- if(length(newnet) > 15)
- temp = "- FAILED: NETWORK TAG STRING TOO LENGHTLY -"
+ if(1)
+ dat += "
[temp]
"
+ dat += "\[Main Menu\] \[Refresh\] \[Print Logs\]"
+ dat += "
Current Network: [network]"
+ dat += "
Selected Server: [SelectedServer.id]"
+
+ if(SelectedServer.totaltraffic >= 1024)
+ dat += "
Total recorded traffic: [round(SelectedServer.totaltraffic / 1024)] Terrabytes
"
+ else
+ dat += "
Total recorded traffic: [SelectedServer.totaltraffic] Gigabytes
"
+
+ dat += log_entries_to_text(SelectedServer)
+
+ user << browse(dat, "window=comm_monitor;size=575x400")
+ onclose(user, "server_control")
+
+ temp = ""
+ return
+
+/obj/machinery/computer/telecomms/server/Topic(href, href_list)
+ if(..())
+ return
+
+
+ add_fingerprint(usr)
+ usr.set_machine(src)
+
+ if(href_list["viewserver"])
+ screen = 1
+ for(var/obj/machinery/telecomms/T in servers)
+ if(T.id == href_list["viewserver"])
+ SelectedServer = T
+ break
+
+ if(href_list["operation"])
+ switch(href_list["operation"])
+
+ if("release")
+ servers = list()
+ screen = 0
+
+ if("mainmenu")
+ screen = 0
+
+ if("scan")
+ if(servers.len > 0)
+ temp = "- FAILED: CANNOT PROBE WHEN BUFFER FULL -"
else
+ for(var/obj/machinery/telecomms/server/T in range(25, src))
+ if(T.network == network)
+ servers.Add(T)
+
+ if(!servers.len)
+ temp = "- FAILED: UNABLE TO LOCATE SERVERS IN \[[network]\] -"
+ else
+ temp = "- [servers.len] SERVERS PROBED & BUFFERED -"
- network = newnet
screen = 0
- servers = list()
- temp = "- NEW NETWORK TAG SET IN ADDRESS \[[network]\] -"
- updateUsrDialog()
- return
-
- attackby(var/obj/item/D as obj, var/mob/user as mob)
- if(D.isscrewdriver())
- playsound(src.loc, D.usesound, 50, 1)
- if(do_after(user, 20/D.toolspeed))
- if (src.stat & BROKEN)
- to_chat(user, "The broken glass falls out.")
- var/obj/structure/computerframe/A = new /obj/structure/computerframe( src.loc )
- new /obj/item/material/shard( src.loc )
- var/obj/item/circuitboard/comm_server/M = new /obj/item/circuitboard/comm_server( A )
- for (var/obj/C in src)
- C.forceMove(src.loc)
- A.circuit = M
- A.state = 3
- A.icon_state = "3"
- A.anchored = 1
- qdel(src)
+ if("printlog")
+ var/start_point = 1
+ var/end_point = SelectedServer.log_entries.len
+ if(SelectedServer)
+ if(SelectedServer.log_entries.len)
+ start_point = input(usr, "Type the start address to print from, cancel to print full log.", "Start Address") as null|num
+ if(!isnum(start_point))
+ start_point = 1
+ else
+ end_point = input(usr, "Type the end address to print to, cancel to print full log.", "End Address") as null|num
+ if(!isnum(end_point))
+ end_point = 0
+ else
+ to_chat(usr, SPAN_WARNING("Cannot print from server that has no logs."))
+ return
else
- to_chat(user, "You disconnect the monitor.")
- var/obj/structure/computerframe/A = new /obj/structure/computerframe( src.loc )
- var/obj/item/circuitboard/comm_server/M = new /obj/item/circuitboard/comm_server( A )
- for (var/obj/C in src)
- C.forceMove(src.loc)
- A.circuit = M
- A.state = 4
- A.icon_state = "4"
- A.anchored = 1
- qdel(src)
- src.updateUsrDialog()
- return
+ to_chat(usr, SPAN_WARNING("Select a server before trying to print logs."))
+ return
+
+ if(!last_print_time || (last_print_time + 2 SECONDS < world.time))
+ last_print_time = world.time
+ var/obj/item/paper/P = new /obj/item/paper(get_turf(src), log_entries_to_text(SelectedServer, start_point, end_point), "[SelectedServer.id] Logs ([start_point] - [end_point])")
+ var/mob/M = usr
+ if(M)
+ M.put_in_hands(P)
+ else
+ to_chat(usr, SPAN_WARNING("Please wait before trying to print more logs."))
+
+ if(href_list["delete"])
+
+ if(!src.allowed(usr) && !emagged)
+ to_chat(usr, "ACCESS DENIED.")
+ return
+
+ if(SelectedServer)
+
+ var/datum/comm_log_entry/D = SelectedServer.log_entries[text2num(href_list["delete"])]
+
+ temp = "- DELETED ENTRY: [D.name] -"
+
+ SelectedServer.log_entries.Remove(D)
+ qdel(D)
+
+ else
+ temp = "- FAILED: NO SELECTED MACHINE -"
+
+ if(href_list["network"])
+
+ var/newnet = sanitize(input(usr, "Which network do you want to view?", "Comm Monitor", network) as null|text)
+
+ if(newnet && ((usr in range(1, src) || issilicon(usr))))
+ if(length(newnet) > 15)
+ temp = "- FAILED: NETWORK TAG STRING TOO LENGHTLY -"
+
+ else
+
+ network = newnet
+ screen = 0
+ servers = list()
+ temp = "- NEW NETWORK TAG SET IN ADDRESS \[[network]\] -"
+
+ updateUsrDialog()
+ return
+
+/obj/machinery/computer/telecomms/server/attackby(var/obj/item/D, var/mob/user)
+ if(D.isscrewdriver())
+ playsound(src.loc, D.usesound, 50, 1)
+ if(do_after(user, 20/D.toolspeed))
+ if (src.stat & BROKEN)
+ to_chat(user, "The broken glass falls out.")
+ var/obj/structure/computerframe/A = new /obj/structure/computerframe( src.loc )
+ new /obj/item/material/shard( src.loc )
+ var/obj/item/circuitboard/comm_server/M = new /obj/item/circuitboard/comm_server( A )
+ for (var/obj/C in src)
+ C.forceMove(src.loc)
+ A.circuit = M
+ A.state = 3
+ A.icon_state = "3"
+ A.anchored = 1
+ qdel(src)
+ else
+ to_chat(user, "You disconnect the monitor.")
+ var/obj/structure/computerframe/A = new /obj/structure/computerframe( src.loc )
+ var/obj/item/circuitboard/comm_server/M = new /obj/item/circuitboard/comm_server( A )
+ for (var/obj/C in src)
+ C.forceMove(src.loc)
+ A.circuit = M
+ A.state = 4
+ A.icon_state = "4"
+ A.anchored = 1
+ qdel(src)
+ src.updateUsrDialog()
+ return
/obj/machinery/computer/telecomms/server/emag_act(var/remaining_charges, var/mob/user)
if(!emagged)
@@ -226,3 +209,55 @@
to_chat(user, "You you disable the security protocols")
src.updateUsrDialog()
return 1
+
+/obj/machinery/computer/telecomms/server/proc/log_entries_to_text(var/obj/machinery/telecomms/server/SelectedServer, start = 1, end = SelectedServer.log_entries.len)
+ if(!end)
+ end = SelectedServer.log_entries.len
+ start = between(1, start, SelectedServer.log_entries.len)
+ end = between(0, end, SelectedServer.log_entries.len)
+ var/list/log_entries = SelectedServer.log_entries.Copy(start, end + 1)
+
+ . += "Stored Logs: "
+
+ var/i = start - 1
+ for(var/datum/comm_log_entry/C as anything in log_entries)
+ i++
+
+ // If the log is a speech file
+ if(C.input_type == "Speech File")
+
+ . += "- [C.name] \[X\]
"
+
+ // -- Determine race of orator --
+
+ var/race = C.parameters["race"] // The actual race of the mob
+ var/language = C.parameters["language"] // The language spoken, or null/""
+
+ // -- If the orator is a human, or universal translate is active, OR mob has universal speech on --
+
+ if(universal_translate || C.parameters["uspeech"] || C.parameters["intelligible"])
+ . += "Data type: [C.input_type]
"
+ . += "Source: [C.parameters["name"]] (Job: [C.parameters["job"]])
"
+ . += "Class: [race]
"
+ . += "Contents: \"[C.parameters["message"]]\"
"
+ if(language)
+ . += "Language: [language]
"
+
+ // -- Orator is not human and universal translate not active --
+
+ else
+ . += "Data type: Audio File
"
+ . += "Source: Unidentifiable
"
+ . += "Class: [race]
"
+ . += "Contents: Unintelligble
"
+
+ . += "
"
+
+ else if(C.input_type == "Execution Error")
+
+ . += "- [C.name] \[X\]
"
+ . += "Output: \"[C.parameters["message"]]\"
"
+ . += "
"
+
+
+ . += "
"
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
index 94b8cbb79c8..01b7ee52ec8 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
@@ -337,6 +337,7 @@
new /obj/item/device/laser_pointer/blue(src)
new /obj/item/device/camera/detective(src)
new /obj/item/device/camera_film(src)
+ new /obj/item/stamp/investigations(src)
//Belts
new /obj/item/clothing/accessory/holster/waist(src)
new /obj/item/clothing/accessory/storage/pouches/black(src)
diff --git a/code/modules/modular_computers/file_system/programs/security/digitalwarrant.dm b/code/modules/modular_computers/file_system/programs/security/digitalwarrant.dm
index e247430e460..37014f64af0 100644
--- a/code/modules/modular_computers/file_system/programs/security/digitalwarrant.dm
+++ b/code/modules/modular_computers/file_system/programs/security/digitalwarrant.dm
@@ -51,11 +51,16 @@
active_warrant = null
if(href_list["editwarrant"])
- . = TRUE
for(var/datum/record/warrant/W in SSrecords.warrants)
if(W.id == text2num(href_list["editwarrant"]))
active_warrant = W
break
+ return TRUE
+
+ if(href_list["back"])
+ . = TRUE
+ active_warrant = null
+ return TRUE
// The following actions will only be possible if the user has an ID with security access equipped. This is in line with modular computer framework's authentication methods,
// which also use RFID scanning to allow or disallow access to some functions. Anyone can view warrants, editing requires ID.
@@ -64,10 +69,15 @@
if(!istype(user))
return
var/obj/item/card/id/I = user.GetIdCard()
- if(!istype(I) || !I.registered_name || !(access_armory in I.access) || issilicon(user))
+ if(!istype(I) || !I.registered_name || !(access_security in I.access) || issilicon(user))
to_chat(user, SPAN_WARNING("Authentication error: Unable to locate ID with appropriate access to allow this operation."))
return
+ // Require higher access to edit warrants that have already been authorized
+ if(active_warrant && active_warrant.authorization != "Unauthorized" && !(access_armory in I.access))
+ to_chat(user, SPAN_WARNING("Authentication error: Unable to locate ID with appropriate access to adjust an authorized warrant."))
+ return
+
if(href_list["addwarrant"])
. = TRUE
var/datum/record/warrant/W = new()
@@ -83,6 +93,8 @@
W.notes = "No reason given"
W.authorization = "Unauthorized"
W.wtype = "search"
+ if(isnull(temp))
+ return
active_warrant = W
if(href_list["savewarrant"])
@@ -123,10 +135,9 @@
active_warrant.notes = new_charges
if(href_list["editwarrantauth"])
+ if(!(access_armory in I.access))
+ to_chat(user, SPAN_WARNING("Authentication error: Unable to locate ID with appropriate access to allow this operation."))
+ return
. = TRUE
active_warrant.authorization = "[I.registered_name] - [I.assignment ? I.assignment : "(Unknown)"]"
-
- if(href_list["back"])
- . = TRUE
- active_warrant = null
\ No newline at end of file
diff --git a/code/modules/paperwork/stamps.dm b/code/modules/paperwork/stamps.dm
index d15c33cc30a..d19df4ac44b 100644
--- a/code/modules/paperwork/stamps.dm
+++ b/code/modules/paperwork/stamps.dm
@@ -35,6 +35,10 @@
name = "chief medical officer's rubber stamp"
icon_state = "stamp-cmo"
+/obj/item/stamp/investigations
+ name = "case closed stamp"
+ icon_state = "stamp-investigator"
+
/obj/item/stamp/denied
name = "\improper DENIED rubber stamp"
icon_state = "stamp-deny"
diff --git a/html/changelog.html b/html/changelog.html
index 9724a3ccd2b..539ee6794b5 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -35,6 +35,14 @@
-->
+
10 April 2022
+
mikomyazaki updated:
+
+ - Security Officers, Investigators and Security Cadets can now submit warrants for approval using the warrant program. They cannot authorize warrants or edit authorized warrants.
+ - Investigators now have a 'Case Closed' stamp in their lockers.
+ - The Telecomms Server Monitor can now print channel logs.
+
+
09 April 2022
MattAtlas updated:
diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml
index e7665a53cd9..3b4c3d60169 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -23076,3 +23076,10 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
don't deal with the poison within several minutes.
- tweak: Blast door buttons will synchronise state of the blast doors they control
when pressed.
+2022-04-10:
+ mikomyazaki:
+ - tweak: Security Officers, Investigators and Security Cadets can now submit warrants
+ for approval using the warrant program. They cannot authorize warrants or edit
+ authorized warrants.
+ - rscadd: Investigators now have a 'Case Closed' stamp in their lockers.
+ - rscadd: The Telecomms Server Monitor can now print channel logs.
diff --git a/icons/obj/bureaucracy.dmi b/icons/obj/bureaucracy.dmi
index 57548444046..0fbda2441c5 100644
Binary files a/icons/obj/bureaucracy.dmi and b/icons/obj/bureaucracy.dmi differ