diff --git a/code/__DEFINES/dcs/signals/signals_lockable_storage.dm b/code/__DEFINES/dcs/signals/signals_lockable_storage.dm new file mode 100644 index 00000000000..1c770ef257f --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_lockable_storage.dm @@ -0,0 +1,4 @@ +// /datum/component/lockable_storage signals + +///from base of /datum/component/lockable_storage/proc/set_lock_code(new_code): (obj/source, new_code) +#define COMSIG_LOCKABLE_STORAGE_SET_CODE "lockable_storage_set_code" diff --git a/code/datums/components/crafting/structures.dm b/code/datums/components/crafting/structures.dm index 0332357f5f0..7d95f927999 100644 --- a/code/datums/components/crafting/structures.dm +++ b/code/datums/components/crafting/structures.dm @@ -105,3 +105,46 @@ /obj/item/stack/sheet/iron = 30, ) category = CAT_STRUCTURE + +/datum/crafting_recipe/secure_safe + name = "Secure safe" + result = /obj/item/wallframe/secure_safe + reqs = list( + /obj/item/stack/sheet/plasteel = 10, + /obj/item/stack/sheet/mineral/titanium = 5, + /obj/item/stack/cable_coil = 5, + /obj/item/stack/rods = 5, + /obj/item/wallframe/button = 1, + ) + tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WELDER, TOOL_DRILL) + time = 30 SECONDS + category = CAT_STRUCTURE + +/datum/crafting_recipe/vault + name = "Vault" + result = /obj/structure/safe/open + reqs = list( + /obj/item/stack/sheet/mineral/metal_hydrogen = 20, + /obj/item/stack/sheet/mineral/plastitanium = 10, + /obj/item/stack/cable_coil = 5, + /obj/item/stack/rods = 5, + /obj/item/wallframe/secure_safe = 1, + ) + tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WELDER, TOOL_DRILL) + time = 90 SECONDS + category = CAT_STRUCTURE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND + +/datum/crafting_recipe/vault_floor + name = "Floor Vault" + result = /obj/structure/safe/floor/open + reqs = list( + /obj/item/stack/sheet/mineral/metal_hydrogen = 20, + /obj/item/stack/sheet/mineral/plastitanium = 10, + /obj/item/stack/cable_coil = 5, + /obj/item/stack/rods = 5, + ) + tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WELDER, TOOL_DRILL) + time = 90 SECONDS + category = CAT_STRUCTURE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND diff --git a/code/datums/components/lockable_storage.dm b/code/datums/components/lockable_storage.dm index 456bac0c45d..51a0e48be6c 100644 --- a/code/datums/components/lockable_storage.dm +++ b/code/datums/components/lockable_storage.dm @@ -34,13 +34,8 @@ canthold = list(/obj/item/storage/briefcase/secure), ) - - src.lock_code = lock_code - if(!isnull(lock_code)) - atom_parent.atom_storage.set_locked(STORAGE_FULLY_LOCKED) src.can_hack_open = can_hack_open - - atom_parent.update_appearance() + set_lock_code(lock_code) /datum/component/lockable_storage/RegisterWithParent() . = ..() @@ -144,8 +139,18 @@ if(!tool.use_tool(parent, user, 40 SECONDS, volume = 50)) return source.balloon_alert(user, "hacked") + set_lock_code(null) + +/datum/component/lockable_storage/proc/break_lock() + can_hack_open = FALSE // since it's broken for good lock_code = null + var/obj/source = parent + source.obj_flags |= EMAGGED + source.atom_storage.locked = STORAGE_NOT_LOCKED + SEND_SIGNAL(source, COMSIG_LOCKABLE_STORAGE_SET_CODE, lock_code) + source.update_appearance() + /datum/component/lockable_storage/proc/on_emag(obj/source, mob/user, obj/item/card/emag/emag_card) SIGNAL_HANDLER @@ -154,17 +159,36 @@ if(source.obj_flags & EMAGGED) return FALSE - source.obj_flags |= EMAGGED - can_hack_open = FALSE // since it's broken for good source.visible_message(span_warning("Sparks fly from [source]!"), blind_message = span_hear("You hear a faint electrical spark.")) source.balloon_alert(user, "lock destroyed") playsound(source, SFX_SPARKS, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) - lock_code = null - source.atom_storage.locked = STORAGE_NOT_LOCKED - source.update_appearance() + break_lock() return ITEM_INTERACT_SUCCESS +/** + * Sets the lock code for this storage and updates the locked state accordingly + * Arguments: + * * new_code - The new lock code to set, can be null to remove the code + */ +/datum/component/lockable_storage/proc/set_lock_code(new_code, mob/living/user) + var/obj/source = parent + + // Can't set lock code if the electronics are emagged + if(source.obj_flags & EMAGGED) + return FALSE + + lock_code = new_code + SEND_SIGNAL(source, COMSIG_LOCKABLE_STORAGE_SET_CODE, new_code) + var/lock_state = lock_code ? STORAGE_FULLY_LOCKED : STORAGE_NOT_LOCKED + source.atom_storage.set_locked(lock_state) + source.update_appearance() + + if(istype(user) && new_code) + to_chat(user, span_notice("You set the [source] pincode to [lock_code].")) + + return TRUE + ///Updates the icon state depending on if we're locked or not. /datum/component/lockable_storage/proc/on_update_icon_state(obj/source) SIGNAL_HANDLER @@ -219,7 +243,7 @@ if(!lock_code) if(length(numeric_input) != 5) return TRUE - lock_code = numeric_input + set_lock_code(numeric_input, usr) numeric_input = "" return TRUE //unlocking the current code. diff --git a/code/game/objects/items/storage/briefcase.dm b/code/game/objects/items/storage/briefcase.dm index 16c6a02468b..3bb4f1d990b 100644 --- a/code/game/objects/items/storage/briefcase.dm +++ b/code/game/objects/items/storage/briefcase.dm @@ -85,10 +85,17 @@ icon_state = "secure" base_icon_state = "secure" inhand_icon_state = "sec-case" + var/stored_lock_code /obj/item/storage/briefcase/secure/Initialize(mapload) . = ..() - AddComponent(/datum/component/lockable_storage) + AddComponent(/datum/component/lockable_storage, stored_lock_code) + RegisterSignal(src, COMSIG_LOCKABLE_STORAGE_SET_CODE, PROC_REF(update_lock_code)) + +/obj/item/storage/briefcase/secure/proc/update_lock_code(obj/item/storage/briefcase/secure/briefacase, new_code) + SIGNAL_HANDLER + + stored_lock_code = new_code /// Base container used for gimmick disks. /obj/item/storage/briefcase/secure/digital_storage diff --git a/code/game/objects/structures/safe.dm b/code/game/objects/structures/safe.dm index ec7da473608..825c609f4a7 100644 --- a/code/game/objects/structures/safe.dm +++ b/code/game/objects/structures/safe.dm @@ -18,7 +18,14 @@ FLOOR SAFES anchored = TRUE density = TRUE resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + obj_flags = CONDUCTS_ELECTRICITY interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT + custom_materials = list( + /datum/material/metalhydrogen = SHEET_MATERIAL_AMOUNT*10, + /datum/material/alloy/plastitanium = SHEET_MATERIAL_AMOUNT*5, + ) + material_flags = MATERIAL_EFFECTS + /// The maximum combined w_class of stuff in the safe var/maxspace = 24 /// The amount of tumblers that will be generated @@ -41,14 +48,26 @@ FLOOR SAFES /obj/structure/safe/Initialize(mapload) . = ..() - update_appearance(UPDATE_ICON) + var/static/list/tool_behaviors = list( + TOOL_WRENCH = list( + SCREENTIP_CONTEXT_LMB = "Reset lock", + ), + ) + AddElement(/datum/element/contextual_screentip_tools, tool_behaviors) + // Combination generation for(var/iterating in 1 to number_of_tumblers) tumblers.Add(rand(0, 99)) - if(!mapload) + if(density) + AddElement(/datum/element/climbable) + AddElement(/datum/element/elevation, pixel_shift = 22) + + if(open && !locked) return + update_appearance(UPDATE_ICON) + // Put as many items on our turf inside as possible for(var/obj/item/inserting_item in loc) if(space >= maxspace) @@ -57,11 +76,46 @@ FLOOR SAFES space += inserting_item.w_class inserting_item.forceMove(src) +/obj/structure/safe/examine(mob/user) + . = ..() + . += span_notice("The locking mechanism gears are wrenched in place.") + /obj/structure/safe/update_icon_state() //uses the same icon as the captain's spare safe (therefore lockable storage) so keep it in line with that icon_state = "[initial(icon_state)][open ? null : "_locked"]" return ..() +/obj/structure/safe/wrench_act(mob/living/user, obj/item/tool) + if(!open) + balloon_alert(user, "must be open!") + return ITEM_INTERACT_BLOCKING + + balloon_alert(user, "resetting lock...") + to_chat(user, span_notice("You begin resetting the lock for [src]. You'll need to set [number_of_tumblers] numbers.")) + + var/list/new_tumblers = list() + for(var/tumbler_index in 1 to number_of_tumblers) + var/input_value = tgui_input_number(user, "Set tumbler #[tumbler_index] (0-99):", "Set Lock", 0, 99, 0) + if(isnull(input_value)) + balloon_alert(user, "reset cancelled!") + return ITEM_INTERACT_BLOCKING + if(!user.can_perform_action(src)) + balloon_alert(user, "reset interrupted!") + return ITEM_INTERACT_BLOCKING + new_tumblers.Add(input_value) + + tool.play_tool_sound(src) + if(!do_after(user, 10 SECONDS, target = src)) + return ITEM_INTERACT_BLOCKING + + tumblers = new_tumblers + current_tumbler_index = 1 + dial = 0 + tool.play_tool_sound(src) + to_chat(user, span_notice("You successfully reset the lock for [src]. The new combination is: [tumblers.Join("-")].")) + balloon_alert(user, "lock set!") + return ITEM_INTERACT_SUCCESS + /obj/structure/safe/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers) if(open) . = TRUE //no afterattack @@ -238,6 +292,10 @@ FLOOR SAFES if(total_ticks == 1 || prob(SOUND_CHANCE)) balloon_alert(user, pick(sounds)) +/obj/structure/safe/open + open = TRUE + locked = FALSE + //FLOOR SAFES /obj/structure/safe/floor name = "floor safe" @@ -249,6 +307,10 @@ FLOOR SAFES . = ..() AddElement(/datum/element/undertile) +/obj/structure/safe/floor/open + open = TRUE + locked = FALSE + ///Special safe for the station's vault. Not explicitly required, but the piggy bank inside it is. /obj/structure/safe/vault diff --git a/code/game/objects/structures/secure_safe.dm b/code/game/objects/structures/secure_safe.dm index e3a74c00300..167a6278f76 100644 --- a/code/game/objects/structures/secure_safe.dm +++ b/code/game/objects/structures/secure_safe.dm @@ -6,6 +6,16 @@ base_icon_state = "wall_safe" result_path = /obj/structure/secure_safe pixel_shift = 32 + w_class = WEIGHT_CLASS_GIGANTIC + obj_flags = CONDUCTS_ELECTRICITY + resistance_flags = FIRE_PROOF + custom_materials = list( + /datum/material/alloy/plasteel = SHEET_MATERIAL_AMOUNT*5, + /datum/material/titanium = SHEET_MATERIAL_AMOUNT*3, + ) + material_flags = MATERIAL_EFFECTS + /// The lock code transferred from the structure + var/stored_lock_code /obj/item/wallframe/secure_safe/Initialize(mapload) . = ..() @@ -13,12 +23,55 @@ max_specific_storage = WEIGHT_CLASS_GIGANTIC, max_total_storage = 20, ) - atom_storage.set_locked(STORAGE_FULLY_LOCKED) + if(stored_lock_code && !(obj_flags & EMAGGED)) + atom_storage.set_locked(STORAGE_FULLY_LOCKED) + update_appearance() -/obj/item/wallframe/secure_safe/after_attach(obj/attached_to) +/obj/item/wallframe/secure_safe/update_icon_state() . = ..() + if(obj_flags & EMAGGED) + icon_state = "[base_icon_state]_broken" + else + icon_state = "[base_icon_state][stored_lock_code ? "_locked" : null]" + +/obj/item/wallframe/secure_safe/emag_act(mob/user, obj/item/card/emag/emag_card) + . = ..() + if(obj_flags & EMAGGED) + return FALSE + + obj_flags |= EMAGGED + visible_message(span_warning("Sparks fly from [src]!"), blind_message = span_hear("You hear a faint electrical spark.")) + balloon_alert(user, "lock destroyed") + playsound(src, SFX_SPARKS, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + stored_lock_code = null + atom_storage.locked = STORAGE_NOT_LOCKED + update_appearance() + return TRUE + +/obj/item/wallframe/secure_safe/after_attach(obj/structure/secure_safe/safe) + if(!istype(safe)) + return ..() + for(var/obj/item in contents) - item.forceMove(attached_to) + item.forceMove(safe) + + var/datum/component/lockable_storage/storage_component = safe.GetComponent(/datum/component/lockable_storage) + if(stored_lock_code) + storage_component?.set_lock_code(stored_lock_code) + + if(obj_flags & EMAGGED) + storage_component?.break_lock() + + return ..() + +/datum/armor/secure_safe + melee = 30 + bullet = 30 + laser = 20 + energy = 20 + bomb = 30 + fire = 95 + acid = 30 /** * Wall safes @@ -33,21 +86,47 @@ base_icon_state = "wall_safe" anchored = TRUE density = FALSE + resistance_flags = FIRE_PROOF + obj_flags = CONDUCTS_ELECTRICITY + armor_type = /datum/armor/secure_safe + max_integrity = 300 + damage_deflection = 21 + custom_materials = list( + /datum/material/alloy/plasteel = SHEET_MATERIAL_AMOUNT*5, + /datum/material/titanium = SHEET_MATERIAL_AMOUNT*3, + ) + material_flags = MATERIAL_EFFECTS + /// The lock code used to open the safe + var/stored_lock_code MAPPING_DIRECTIONAL_HELPERS(/obj/structure/secure_safe, 32) /obj/structure/secure_safe/Initialize(mapload) . = ..() //this will create the storage for us. - AddComponent(/datum/component/lockable_storage) + AddComponent(/datum/component/lockable_storage, stored_lock_code) + if(!density) find_and_hang_on_wall() if(mapload) PopulateContents() + RegisterSignal(src, COMSIG_LOCKABLE_STORAGE_SET_CODE, PROC_REF(update_lock_code)) + /obj/structure/secure_safe/atom_deconstruct(disassembled) if(!density) //if we're a wall item, we'll drop a wall frame. var/obj/item/wallframe/secure_safe/new_safe = new(get_turf(src)) + + // Transfer lock code to the wallframe + var/datum/component/lockable_storage/storage_component = GetComponent(/datum/component/lockable_storage) + if(storage_component) + new_safe.stored_lock_code = storage_component.lock_code + + if(obj_flags & EMAGGED) + new_safe.obj_flags |= EMAGGED + + new_safe.update_appearance() + for(var/obj/item in contents) item.forceMove(new_safe) @@ -55,6 +134,16 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/secure_safe, 32) new /obj/item/paper(src) new /obj/item/pen(src) +/obj/structure/secure_safe/proc/update_lock_code(obj/structure/secure_safe/safe, new_code) + SIGNAL_HANDLER + + stored_lock_code = new_code + +/obj/structure/secure_safe/ex_act(severity, target) + if(severity <= EXPLODE_LIGHT) + return FALSE + return ..() + /obj/structure/secure_safe/hos name = "head of security's safe" @@ -77,11 +166,13 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/secure_safe, 32) icon_state = "spare_safe" base_icon_state = "spare_safe" armor_type = /datum/armor/safe_caps_spare - max_integrity = 300 damage_deflection = 30 // prevents stealing the captain's spare using null rods/lavaland monsters/AP projectiles density = TRUE anchored_tabletop_offset = 6 - custom_materials = list(/datum/material/gold = SMALL_MATERIAL_AMOUNT) + custom_materials = list( + /datum/material/alloy/plasteel = SHEET_MATERIAL_AMOUNT*5, + /datum/material/gold = SHEET_MATERIAL_AMOUNT*3, + ) material_flags = MATERIAL_EFFECTS /datum/armor/safe_caps_spare diff --git a/tgstation.dme b/tgstation.dme index 3ba5891ae7c..57c100d1865 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -344,6 +344,7 @@ #include "code\__DEFINES\dcs\signals\signals_leash.dm" #include "code\__DEFINES\dcs\signals\signals_lift.dm" #include "code\__DEFINES\dcs\signals\signals_light_eater.dm" +#include "code\__DEFINES\dcs\signals\signals_lockable_storage.dm" #include "code\__DEFINES\dcs\signals\signals_market.dm" #include "code\__DEFINES\dcs\signals\signals_material_container.dm" #include "code\__DEFINES\dcs\signals\signals_medical.dm"