diff --git a/code/game/objects/items/weapons/storage/secure.dm b/code/game/objects/items/weapons/storage/secure.dm
index e069f12a6c0..3d530f35751 100644
--- a/code/game/objects/items/weapons/storage/secure.dm
+++ b/code/game/objects/items/weapons/storage/secure.dm
@@ -12,24 +12,28 @@
// -----------------------------
/obj/item/storage/secure
name = "secstorage"
- var/icon_locking = "secureb"
- var/icon_sparking = "securespark"
- var/icon_opened = "secure0"
- var/locked = TRUE
- var/code = ""
- var/l_code = null
- var/l_set = FALSE
- var/l_setshort = FALSE
- var/l_hacking = FALSE
- var/open = FALSE
w_class = WEIGHT_CLASS_NORMAL
max_w_class = WEIGHT_CLASS_SMALL
max_combined_w_class = 14
+ var/icon_locking = "secureb"
+ var/icon_sparking = "securespark"
+ var/icon_opened = "secure0"
+
+ /// Are we locked?
+ var/locked = TRUE
+ /// What is our code to open?
+ var/code
+ /// Is our hacking panel open?
+ var/panel_open = FALSE
+ /// What has the user entered to guess the code
+ var/user_entered_code
+ /// Stops people from spamming enter like an idiot
+ COOLDOWN_DECLARE(enter_spam)
/obj/item/storage/secure/examine(mob/user)
. = ..()
if(in_range(user, src))
- . += "The service panel is [open ? "open" : "closed"]."
+ . += "The service panel is [panel_open ? "open" : "closed"]."
/obj/item/storage/secure/populate_contents()
new /obj/item/paper(src)
@@ -40,23 +44,6 @@
if((istype(W, /obj/item/melee/energy/blade)) && (!emagged))
emag_act(user, W)
- if(istype(W, /obj/item/multitool) && open && !l_hacking)
- user.show_message("Now attempting to reset internal memory, please hold.", 1)
- l_hacking = TRUE
- if(do_after(usr, 100 * W.toolspeed, target = src))
- if(prob(40))
- l_setshort = TRUE
- l_set = FALSE
- user.show_message("Internal memory reset. Please give it a few seconds to reinitialize.", 1)
- sleep(80)
- l_setshort = FALSE
- l_hacking = FALSE
- else
- user.show_message("Unable to reset internal memory.", 1)
- l_hacking = FALSE
- else
- l_hacking = FALSE
- return
//At this point you have exhausted all the special things to do when locked
// ... but it's still locked.
return
@@ -64,19 +51,32 @@
return ..()
/obj/item/storage/secure/screwdriver_act(mob/living/user, obj/item/I)
- if(do_after(user, 20 * I.toolspeed, target = src))
- open = !open
- user.visible_message("[user] [open ? "opens" : "closes"] the service panel on [src].", "You [open ? "open" : "close"] the service panel.")
+ if(I.use_tool(src, user, 2 SECONDS * I.toolspeed, volume = 10))
+ panel_open = !panel_open
+ user.visible_message("[user] [panel_open ? "opens" : "closes"] the service panel on [src].", "You [panel_open ? "open" : "close"] the service panel.")
return TRUE
-/obj/item/storage/secure/emag_act(user as mob, weapon as obj)
+/obj/item/storage/secure/multitool_act(mob/living/user, obj/item/I)
+ if(!panel_open)
+ return
+ if(!I.use_tool(src, user, 0, volume = 0))
+ return
+ . = TRUE
+ to_chat(user, "You start fiddling with the internal memory mechanisms.")
+ if(do_after_once(user, 10 SECONDS * I.toolspeed, target = src))
+ if(prob(40))
+ to_chat(user, "The screen dims, the internal memory seems to be reset.")
+ code = null
+ else
+ to_chat(user, "The screen flashes, and then goes back to normal.")
+
+
+/obj/item/storage/secure/emag_act(user, weapon)
if(!emagged)
emagged = TRUE
- overlays += image('icons/obj/storage.dmi', icon_sparking)
- sleep(6)
- overlays = null
- overlays += image('icons/obj/storage.dmi', icon_locking)
+ flick_overlay_view(image(icon, src, icon_sparking), src, 0.9 SECONDS)
locked = FALSE
+ update_icon(UPDATE_OVERLAYS)
if(istype(weapon, /obj/item/melee/energy/blade))
do_sparks(5, 0, loc)
playsound(loc, 'sound/weapons/blade1.ogg', 50, 1)
@@ -84,7 +84,6 @@
to_chat(user, "You slice through the lock on [src].")
else
to_chat(user, "You short out the lock on [src].")
- return
/obj/item/storage/secure/AltClick(mob/user)
if(!try_to_open())
@@ -103,74 +102,22 @@
return FALSE
return TRUE
-/obj/item/storage/secure/attack_self(mob/user as mob)
- user.set_machine(src)
- var/dat = text("[]
\n\nLock Status: []", src, (locked ? "LOCKED" : "UNLOCKED"))
- var/message = "Code"
- if(!l_set && !emagged && !l_setshort)
- dat += text("
\n5-DIGIT PASSCODE NOT SET.
ENTER NEW PASSCODE.")
- if(emagged)
- dat += text("
\nLOCKING SYSTEM ERROR - 1701")
- if(l_setshort)
- dat += text("
\nALERT: MEMORY SYSTEM ERROR - 6040 201")
- message = text("[]", code)
- if(!locked)
- message = "*****"
- dat += {"
\n>[message]
\n
- 1-
- 2-
- 3
\n
- 4-
- 5-
- 6
\n
- 7-
- 8-
- 9
\n
- R-
- 0-
- E
\n"}
- user << browse(dat, "window=caselock;size=300x280")
-
-/obj/item/storage/secure/Topic(href, href_list)
- ..()
- if(usr.incapacitated() || (get_dist(src, usr) > 1))
- return
- if(href_list["type"])
- if(href_list["type"] == "E")
- if(!l_set && length(code) == 5 && !l_setshort && code != "ERROR")
- l_code = code
- l_set = TRUE
- else if(code == l_code && !emagged && l_set)
- locked = FALSE
- overlays = null
- overlays += image('icons/obj/storage.dmi', icon_opened)
- code = null
- else
- code = "ERROR"
- else
- if(href_list["type"] == "R" && !emagged && !l_setshort)
- locked = TRUE
- overlays = null
- code = null
- if(usr.s_active == src)
- close(usr)
- else
- code += text("[]", href_list["type"])
- if(length(code) > 5)
- code = "ERROR"
- add_fingerprint(usr)
- for(var/mob/M in viewers(1, loc))
- if((M.client && M.machine == src))
- attack_self(M)
- return
- return
/obj/item/storage/secure/can_be_inserted(obj/item/W as obj, stop_messages = 0)
if(!locked)
return ..()
if(!stop_messages)
to_chat(usr, "[src] is locked!")
- return 0
+ return FALSE
+
+/obj/item/storage/secure/update_overlays()
+ . = ..()
+ if(isnull(code))
+ return
+ if(locked)
+ . += icon_locking
+ else
+ . += icon_opened
/obj/item/storage/secure/hear_talk(mob/living/M as mob, list/message_pieces)
return
@@ -178,6 +125,74 @@
/obj/item/storage/secure/hear_message(mob/living/M as mob, msg)
return
+/obj/item/storage/secure/attack_self(mob/user)
+ ui_interact(user)
+
+/obj/item/storage/secure/ui_interact(mob/user, ui_key, datum/tgui/ui, force_open, datum/tgui/master_ui, datum/ui_state/state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "SecureStorage", name, 275, 500, master_ui, state)
+ ui.open()
+
+/obj/item/storage/secure/ui_data(mob/user)
+ var/list/data = list()
+ data["locked"] = locked
+ data["user_entered_code"] = user_entered_code
+ data["no_passcode"] = isnull(code)
+ data["emagged"] = emagged
+ return data
+
+/obj/item/storage/secure/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ if(..())
+ return
+
+ add_fingerprint(usr)
+
+ if(!COOLDOWN_FINISHED(src, enter_spam))
+ return
+
+ . = TRUE
+
+ switch(action)
+ if("keypad")
+ if(emagged)
+ return FALSE
+
+ var/digit = params["digit"]
+ switch(digit)
+ if("E")
+ if(isnull(code))
+ if(length(user_entered_code) != 5)
+ return FALSE
+ code = user_entered_code
+ to_chat(ui.user, "You set the code to [code].")
+ locked = FALSE
+ else if(!locked)
+ locked = TRUE
+ to_chat(ui.user, "You lock [src].")
+ else if(user_entered_code == code) // correct code!
+ locked = FALSE
+ to_chat(ui.user, "You unlock [src].")
+ update_icon(UPDATE_OVERLAYS)
+ COOLDOWN_START(src, enter_spam, 0.1 SECONDS)
+ if("C")
+ user_entered_code = null
+ COOLDOWN_START(src, enter_spam, 0.1 SECONDS)
+ else
+ if(!isnum(text2num(digit)))
+ return FALSE
+ if(length(user_entered_code) >= 5)
+ return FALSE
+ user_entered_code = copytext("[user_entered_code][digit]", 1, 6)
+
+ if("backspace")
+ if(emagged)
+ return FALSE
+ user_entered_code = copytext(user_entered_code, 1, length(user_entered_code))
+ COOLDOWN_START(src, enter_spam, 0.1 SECONDS)
+
+ playsound(src, "terminal_type", 10, 1)
+
// -----------------------------
// Secure Briefcase
// -----------------------------
@@ -233,7 +248,7 @@
icon = 'icons/obj/storage.dmi'
icon_state = "safe"
icon_opened = "safe0"
- icon_locking = "safeb"
+ icon_locking = null
icon_sparking = "safespark"
force = 8
w_class = WEIGHT_CLASS_HUGE
@@ -243,4 +258,4 @@
cant_hold = list(/obj/item/storage/secure/briefcase)
/obj/item/storage/secure/safe/attack_hand(mob/user as mob)
- return attack_self(user)
+ ui_interact(user)
diff --git a/tgui/packages/tgui/hotkeys.js b/tgui/packages/tgui/hotkeys.js
index 36a21bc8a89..186905f41fb 100644
--- a/tgui/packages/tgui/hotkeys.js
+++ b/tgui/packages/tgui/hotkeys.js
@@ -48,6 +48,16 @@ export const KEY_W = 87;
export const KEY_X = 88;
export const KEY_Y = 89;
export const KEY_Z = 90;
+export const KEY_NUMPAD_0 = 96;
+export const KEY_NUMPAD_1 = 97;
+export const KEY_NUMPAD_2 = 98;
+export const KEY_NUMPAD_3 = 99;
+export const KEY_NUMPAD_4 = 100;
+export const KEY_NUMPAD_5 = 101;
+export const KEY_NUMPAD_6 = 102;
+export const KEY_NUMPAD_7 = 103;
+export const KEY_NUMPAD_8 = 104;
+export const KEY_NUMPAD_9 = 105;
export const KEY_EQUAL = 187;
export const KEY_MINUS = 189;
diff --git a/tgui/packages/tgui/index.js b/tgui/packages/tgui/index.js
index a05d3907226..886247b7fbd 100644
--- a/tgui/packages/tgui/index.js
+++ b/tgui/packages/tgui/index.js
@@ -27,6 +27,7 @@ import './styles/themes/malfunction.scss';
import './styles/themes/ntos.scss';
import './styles/themes/retro.scss';
import './styles/themes/safe.scss';
+import './styles/themes/securestorage.scss';
import './styles/themes/security.scss';
import './styles/themes/syndicate.scss';
import './styles/themes/nologo.scss';
diff --git a/tgui/packages/tgui/interfaces/SecureStorage.js b/tgui/packages/tgui/interfaces/SecureStorage.js
new file mode 100644
index 00000000000..350946413ae
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/SecureStorage.js
@@ -0,0 +1,124 @@
+import { classes } from 'common/react';
+import { useBackend } from '../backend';
+import { Box, Button, Flex, Section, Table } from '../components';
+import { Window } from '../layouts';
+import { TableCell, TableRow } from '../components/Table';
+import {
+ KEY_BACKSPACE,
+ KEY_ENTER,
+ KEY_ESCAPE,
+ KEY_0,
+ KEY_9,
+ KEY_NUMPAD_0,
+ KEY_NUMPAD_9,
+} from '../hotkeys';
+
+export const SecureStorage = (props, context) => {
+ return (
+
+
+
+
+
+ );
+};
+
+const handleKeyCodeEvent = (e, context) => {
+ const { act } = useBackend(context);
+ const keyCode = window.event ? e.which : e.keyCode;
+
+ if (keyCode === KEY_ENTER) {
+ e.preventDefault();
+ act('keypad', { digit: 'E' });
+ return;
+ }
+ if (keyCode === KEY_ESCAPE) {
+ e.preventDefault();
+ act('keypad', { digit: 'C' });
+ return;
+ }
+ if (keyCode === KEY_BACKSPACE) {
+ e.preventDefault();
+ act('backspace');
+ return;
+ }
+ if (keyCode >= KEY_0 && keyCode <= KEY_9) {
+ e.preventDefault();
+ act('keypad', { digit: keyCode - KEY_0 });
+ return;
+ }
+ if (keyCode >= KEY_NUMPAD_0 && keyCode <= KEY_NUMPAD_9) {
+ e.preventDefault();
+ act('keypad', { digit: keyCode - KEY_NUMPAD_0 });
+ return;
+ }
+};
+
+const MainPage = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { locked, no_passcode, emagged, user_entered_code } = data;
+
+ const keypadKeys = [
+ ['1', '2', '3'],
+ ['4', '5', '6'],
+ ['7', '8', '9'],
+ ['C', '0', 'E'],
+ ];
+
+ const status = no_passcode ? '' : locked ? 'bad' : 'good';
+
+ return (
+ handleKeyCodeEvent(e, context)}
+ >
+
+
+ {emagged ? 'ERROR' : user_entered_code}
+
+
+
+ {keypadKeys.map((keyColumn) => (
+
+ {keyColumn.map((key) => (
+
+
+
+ ))}
+
+ ))}
+
+
+ );
+};
+
+const NumberButton = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { number } = props;
+
+ return (
+