Crate, Closet Refactors & Access Secured Stuff (#74754)

## About The Pull Request
This PR is actually 2 parts, one that fixes runtimes with crates & the
other that allows secured closets to be crafted
along with a secured suit storage unit

**Crate Fixes**

Fixes #74708

The problem starts here

https://github.com/tgstation/tgstation/blob/f1178342084bf89897a46f6ce9dc849233bed21b/code/game/objects/structures/crates_lockers/crates.dm#L31-L34
Not only does this if condition look ugly but it's highly error prone
because one single call to `update_appearance()` can cause this to fail,
and sure enough if you look at the parent `Initialize()` proc it calls
just that

https://github.com/tgstation/tgstation/blob/f1178342084bf89897a46f6ce9dc849233bed21b/code/game/objects/structures/crates_lockers/closets.dm#L81-L88
Since we know the appearance is guaranteed to be changed in some way
before the if condition gets executed let's check what the final state
of the crate would be before this if check

https://github.com/tgstation/tgstation/blob/f1178342084bf89897a46f6ce9dc849233bed21b/code/game/objects/structures/crates_lockers/crates.dm#L54-L56
We see that the final icon state depends on the variable `opened` so if
we want to place/spawn a crate that is opened at round start we have to
ensure that `opened = TRUE` so the `if(icon_state ==
"[initial(icon_state)]open")` succeeds and does its job correctly.
Sadly we did dum shit like this
```
/obj/structure/closet/crate{
	icon_state = "crateopen"
}
```
throughout the entire code base, we thought backwards and were only
concerned in making the closet look open rather than setting its correct
variables to actually say that it is opened. because none of these
crates actually set `opened = TRUE` the final icon state becomes just
"crate" NOT "crateopen" therefore the if condition fails and we add the
component

https://github.com/tgstation/tgstation/blob/f1178342084bf89897a46f6ce9dc849233bed21b/code/game/objects/structures/crates_lockers/crates.dm#L36-L37
with the wrong parameters, so when closing the closet after_close()
removes the component with the wrong arguments

https://github.com/tgstation/tgstation/blob/f1178342084bf89897a46f6ce9dc849233bed21b/code/game/objects/structures/crates_lockers/crates.dm#L81-L84
that is does not unregister the signals and readds the component i.e.
re-registers the signals causing runtime.

The solution just do this
```
/obj/structure/closet/crate/open[mapping helper]
```
To clearly state that you want the closet to be open, that way you don't
have to memorize the icon_state for each different type of crate, it's
consistent across all crates & you don't get runtimes.

And that's exactly what i did everywhere

Another issue that is fixed is "Houdini crates" i.e. crates which are
open & appear empty but when you close & reopen them magical loot
appears, Go ahead walk upto to cargo and find any empty crate that is
open and do this

Fixes #69779


https://user-images.githubusercontent.com/110812394/232234489-0193acde-22c8-4c19-af89-e897f3c23d53.mp4

You will be surprised, This is seriously harmful to players because they
can just walk by a crate that appears to be open & empty only to realize
later that it had some awesome loot. Just mean

The reason this happens is because of the Late Initialization inside
closets

https://github.com/tgstation/tgstation/blob/f1178342084bf89897a46f6ce9dc849233bed21b/code/game/objects/structures/crates_lockers/closets.dm#L85-L86

What late initialization does is suck up all stuff on its turf

https://github.com/tgstation/tgstation/blob/f1178342084bf89897a46f6ce9dc849233bed21b/code/game/objects/structures/crates_lockers/closets.dm#L97-L100

In theory this is supposed to work perfectly, if the closet is closed
move everything on the turf into the closet and so when the player opens
it, they all pop back out.
But what happens if the closet is opened before ` LateInitialize()` is
called? This breaking behaviour is caused by object spawners

https://github.com/tgstation/tgstation/blob/f1178342084bf89897a46f6ce9dc849233bed21b/code/game/objects/effects/spawners/random/structure.dm#L94-L100
And maint crates

https://github.com/tgstation/tgstation/blob/f1178342084bf89897a46f6ce9dc849233bed21b/code/game/objects/structures/crates_lockers/crates.dm#L141-L143
These 2 spawners open up the crate based on random probability before `
LateInitialize()` is called on the crate and so what happens is the
crate is first opened and then stuff on the turf is sucked in causing an
open but empty crate to appear.

The solution is simple just check again in ` LateInitialize()` if our
crate is still closed before we proceed.That's fixed now too

**Code Refactors**
1. Introduced 2 new signals COMSIG_CLOSET_PRE/POST CLOSE which are the
counter parts for the open signals. hook into them if you ever need to
do stuff before & after closing the closet while return BLOCK_CLOSE for
COMSIG_CLOSET_PRE_CLOSE if you want to block closing the closet for some
reason
2. 2 new procs `before_open()` & `before_close()` which are the counter
parts for `after_open()` & `after_close()`. If you need to write checks
and do actions before opening the closet or before closing the closet
override these procs & not the `open()` & `close()` procs directly

**Secured Craftables** 
This is just a reopened version of #74115 after i accidently merged
another branch without resolving the conflicts first so i'll just
repaste everything here, since crates & closets are related might as
well do all in one

1. **Access secured closets**
   
   - **What about them?**
          **1. Existing System**
If you wanted to create a access secured closet with the existing system
its an 4 step process
            - First construct a normal closet
            - Weld it shut so you can install the airlock electronics
            - Install the electronics [4 seconds]
            - Unweld
This is a 4 step process which takes time & requires a welding tool
         **2. New system**
Combine the 4 steps into 1 by crafting the secure closet directly
                    
![Screenshot
(184)](https://user-images.githubusercontent.com/110812394/235904926-c2ea231c-eba7-45d0-a5af-e0456fdd40bc.png)

    - **Bonus Features**
              **1. Card reader**
The card reader acts as an interface between the airlock electronics &
the player. Usually if you want to change access on a locker you have to
                  - Weld the closet shut
                  - Screw driver out the electronics
                  - Change the settings
                  - Install it back
                  - Unweld
With a card reader there is no need of a welder & screwdriver. You can
change the access of the locker while its operational

        **How do i install the card reader?**
             1. Weld the closet shut
             3. Insert card reader with hand
4. To remove the card reader use crowbar or just deconstruct the whole
closet with a welding tool
             5. Unweld closet

         **How to change its access?**
This will overwrite the settings on your airlock electronics. To do this
1. make sure the closet is first unlocked. This is important so that no
random person who doesn't have access to the closet can change its
access while its locked. It would be like giving the privilege of
changing your current password without first confirming if you know the
old password
2. attack/swipe the closet with your PDA. Make sure your ID card is
inside the PDA for this to work. You can also just use your ID card
directly without a PDA
         3. You will get 3 options to decide the new access levels
           
![Screenshot
(174)](https://user-images.githubusercontent.com/110812394/233454364-d99a2fb6-9f26-4db3-9fac-a10689955484.png)


        They work as follows
- **Personal**: As the name implies only you can access this locker and
no one else. Make sure to have your ID on you at all times cause if you
loose it then no one can open it
- **Departmental**: This copies the access levels of your ID and will
allow people having those exact same access levels. Say you want to
create a closet accessible to only miners. Then have an miner choose
this option and now only miners can open this closet. If the Hop sets
custom access on your ID then only people with those specific access
levels can open this closet
         - **None**: No access, free for all just like a normal closet

**Security:** After you have set the access level it is important to
lock the access panel with a "multi-tool", so no one else can change it.
Unlock the panel again with the "multi-tool" to set the new access type

       **2. Give your own name & description**
To rename the closet or change its description you must first make the
closet access type as personel i.e. make it yours, then use an pen to
complete the job. You cannot change names of departmental or no access
closets because that's vandelism

       **3. Custom Paint Job**
    Use airlock painter. Not intuitive but does the job. 
   
![Screenshot
(181)](https://user-images.githubusercontent.com/110812394/234202905-00946b88-2513-489d-b0a2-d618a72f3e49.png)

      **4. Personal closets**
Round start personal closets can have their access overridden by a new
ID when in it's unlocked state. This is useful if the last person has no
use for the closet & someone else wants to use it.


    - **Why its good for the game?**      
1. Having your own personal closet with your own name & description
gives you more privacy & security for your belongings so people don't
steal your stuff. Personal access is more secure because it requires you
to have the physical ID card you used to set this access and not an ID
which has the same access levels as your previous ID
2. Make secure closets faster without an welding tool & screw driver
3. Bug fix where electronics could be screwed out from round start
secured closets countless times spawning a new airlock electronic each
time
      
2. **Access secured freezers**

    - **What about them?**
The craftable freezer from #73942 has been modified to support secure
access. These can be deconstructed with welders just as before

![Screenshot
(185)](https://user-images.githubusercontent.com/110812394/235905000-ba165feb-4384-4759-b46b-dba77c9e6ba3.png)


    - **How does it work?**
The access stuff works exactly the same as secure closets described
above. You can rename & change description with pen just like the above
described secure closets. No paint job for this. Install card reader
with the same steps described above.

    - **Why it's good for the game?**
1. Make access secured freezers faster without a welder and screwdriver
2. Your own personally named & locked freezer for storing dead bodies is
always a good thing

4. **Access secured suit storage unit**
   - **What about them?**
Suit storage units now require airlock electronics for construction. The
access levels you set on it will be used to decide
       1. If a player can unlock the unit
       2. If the player can open the unit after unlocking
       3. If the player can disinfect whatever is inside
       
      By default all round start suit storage units have free access

   - **Install card reader**
Provides the same functionality as secured closets described above. To
install it
     1. Open its panel with a screw driver
     2. Add a card reader to it with hand
     3. Close the panel
     
     When you deconstruct the machine the card reader pops back out

   - **Why it's good for the game?**
1. Having your own access protected and named suit storage unit so
random people don't steal your mod suits? Who wouldn't want that.?
Provides security for department storage units.
2. If you have the unit locked then you cannot deconstruct the machine
with a crowbar providing additional security
3. Fixes #70552 , random people can't open/unlock the suit storage unit
without access. You can set personal access to make sure only you can
access the unit

## Changelog
🆑
add: Access secured closets. Personal closets can have their access
overwritten by an new id in it's unlocked state
add: Access secured freezers.
add: Access secured suit storage units.
fix: Suit storage unit not having access restrictions.
fix: airlock electronics not properly getting removed after screwing
them out from round start lockers
fix: round spawned open crates run timing when closed
fix: open crates hiding stuff in plain sight
fix: open closets/crates sucking up contents during late initialize
causing them appear empty & open
/🆑

---------

Co-authored-by: Tim <timothymtorres@gmail.com>
This commit is contained in:
SyncIt21
2023-05-08 23:12:54 +05:30
committed by GitHub
parent 8f885edff6
commit 2068ea9ab5
60 changed files with 1062 additions and 642 deletions
@@ -11,6 +11,10 @@
integrity_failure = 0.25
armor_type = /datum/armor/structure_closet
blocks_emissive = EMISSIVE_BLOCK_GENERIC
/// How close being inside of the thing provides complete pressure safety. Must be between 0 and 1!
contents_pressure_protection = 0
/// How insulated the thing is, for the purposes of calculating body temperature. Must be between 0 and 1!
contents_thermal_insulation = 0
/// The overlay for the closet's door
var/obj/effect/overlay/closet_door/door_obj
@@ -24,13 +28,13 @@
var/door_hinge_x = -6.5
/// Amount of time it takes for the door animation to play
var/door_anim_time = 1.5 // set to 0 to make the door not animate at all
/// Paint jobs for this closet, crates are a subtype of closet so they override these values
var/list/paint_jobs = TRUE
/// Controls whether a door overlay should be applied using the icon_door value as the icon state
var/enable_door_overlay = TRUE
var/has_opened_overlay = TRUE
var/has_closed_overlay = TRUE
var/icon_door = null
var/secure = FALSE //secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights
var/opened = FALSE
var/welded = FALSE
var/locked = FALSE
@@ -56,19 +60,23 @@
var/delivery_icon = "deliverycloset" //which icon to use when packagewrapped. null to be unwrappable.
var/anchorable = TRUE
var/icon_welded = "welded"
/// How close being inside of the thing provides complete pressure safety. Must be between 0 and 1!
contents_pressure_protection = 0
/// How insulated the thing is, for the purposes of calculating body temperature. Must be between 0 and 1!
contents_thermal_insulation = 0
/// Whether a skittish person can dive inside this closet. Disable if opening the closet causes "bad things" to happen or that it leads to a logical inconsistency.
var/divable = TRUE
/// true whenever someone with the strong pull component (or magnet modsuit module) is dragging this, preventing opening
var/strong_grab = FALSE
///electronics for access
var/obj/item/electronics/airlock/electronics
/// secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights
var/secure = FALSE
var/can_install_electronics = TRUE
var/contents_initialized = FALSE
/// is this closet locked by an exclusive id, i.e. your own personal locker
var/datum/weakref/id_card = null
/// should we prevent furthur access change
var/access_locked = FALSE
/// is the card reader installed in this machine
var/card_reader_installed = FALSE
/// access types for card reader
var/list/access_choices = TRUE
/datum/armor/structure_closet
melee = 20
@@ -81,11 +89,32 @@
/obj/structure/closet/Initialize(mapload)
. = ..()
var/static/list/closet_paint_jobs = list(
"Cargo" = list("icon_state" = "qm"),
"Engineering" = list("icon_state" = "ce"),
"Engineering Secure" = list("icon_state" = "eng_secure"),
"Radiation" = list("icon_state" = "eng", "icon_door" = "eng_rad"),
"Tool Storage" = list("icon_state" = "eng", "icon_door" = "eng_tool"),
"Fire Equipment" = list("icon_state" = "fire"),
"Emergency" = list("icon_state" = "emergency"),
"Hydroponics" = list("icon_state" = "hydro"),
"Medical" = list("icon_state" = "med"),
"Science" = list("icon_state" = "rd"),
"Security" = list("icon_state" = "cap"),
"Mining" = list("icon_state" = "mining"),
"Virology" = list("icon_state" = "bio_viro"),
)
if(paint_jobs)
paint_jobs = closet_paint_jobs
if(access_choices)
var/static/list/choices = list("Personal", "Departmental", "None")
access_choices = choices
// if closed, any item at the crate's loc is put in the contents
if (mapload && !opened)
. = INITIALIZE_HINT_LATELOAD
update_appearance()
populate_contents_immediate()
var/static/list/loc_connections = list(
COMSIG_CARBON_DISARM_COLLIDE = PROC_REF(locker_carbon),
@@ -94,10 +123,16 @@
AddElement(/datum/element/connect_loc, loc_connections)
register_context()
if(opened)
opened = FALSE //nessassary because open() proc will early return if its true
if(open(special_effects = FALSE)) //closets which are meant to be open by default dont need to be animated open
return
update_appearance()
/obj/structure/closet/LateInitialize()
. = ..()
take_contents()
if(!opened)
take_contents()
//USE THIS TO FILL IT, NOT INITIALIZE OR NEW
/obj/structure/closet/proc/PopulateContents()
@@ -109,8 +144,8 @@
return
/obj/structure/closet/Destroy()
id_card = null
QDEL_NULL(door_obj)
QDEL_NULL(electronics)
return ..()
/obj/structure/closet/update_appearance(updates=ALL)
@@ -124,7 +159,6 @@
. = ..()
if(issupplypod(src))
return
layer = opened ? BELOW_OBJ_LAYER : OBJ_LAYER
/obj/structure/closet/update_overlays()
@@ -134,12 +168,13 @@
/obj/structure/closet/proc/closet_update_overlays(list/new_overlays)
. = new_overlays
if(enable_door_overlay && !is_animating_door)
var/overlay_state = isnull(base_icon_state) ? initial(icon_state) : base_icon_state
if(opened && has_opened_overlay)
var/mutable_appearance/door_overlay = mutable_appearance(icon, "[icon_state]_open", alpha = src.alpha)
var/mutable_appearance/door_overlay = mutable_appearance(icon, "[overlay_state]_open", alpha = src.alpha)
. += door_overlay
door_overlay.overlays += emissive_blocker(door_overlay.icon, door_overlay.icon_state, src, alpha = door_overlay.alpha) // If we don't do this the door doesn't block emissives and it looks weird.
else if(has_closed_overlay)
. += "[icon_door || icon_state]_door"
. += "[icon_door || overlay_state]_door"
if(opened)
return
@@ -157,10 +192,10 @@
if(vname == NAMEOF(src, opened))
if(vval == opened)
return FALSE
if(vval && !opened && open(null, TRUE))
if(vval && !opened && open(force = TRUE))
datum_flags |= DF_VAR_EDITED
return TRUE
else if(!vval && opened && close(null))
else if(!vval && opened && close())
datum_flags |= DF_VAR_EDITED
return TRUE
return FALSE
@@ -188,6 +223,7 @@
for(var/step in 0 to num_steps)
var/angle = door_anim_angle * (closing ? 1 - (step/num_steps) : (step/num_steps))
var/matrix/door_transform = get_door_transform(angle)
var/door_state
var/door_layer
@@ -225,13 +261,33 @@
/obj/structure/closet/examine(mob/user)
. = ..()
. += span_notice("It can be [EXAMINE_HINT("welded")] apart.")
. += span_notice("It can be [EXAMINE_HINT("bolted")] to the ground.")
if(id_card)
. += span_notice("It can be [EXAMINE_HINT("marked")] with a pen.")
if(can_weld_shut && !welded)
. += span_notice("Its can be [EXAMINE_HINT("welded")] shut.")
if(welded)
. += span_notice("Its [EXAMINE_HINT("welded")] shut.")
if(anchorable && !anchored)
. += span_notice("It can be [EXAMINE_HINT("bolted")] to the ground.")
if(anchored)
. += span_notice("Its [EXAMINE_HINT("bolted")] to the ground.")
if(length(paint_jobs))
. += span_notice("It can be [EXAMINE_HINT("painted")] another texture.")
if(HAS_TRAIT(user, TRAIT_SKITTISH) && divable)
. += span_notice("If you bump into [p_them()] while running, you will jump inside.")
if(can_install_electronics)
if(!secure)
. += span_notice("You can install airlock electronics for access control.")
else
. += span_notice("Its airlock electronics are [EXAMINE_HINT("screwed")] in place.")
if(!card_reader_installed && length(access_choices))
. += span_notice("You can install a card reader for furthur access control.")
else if(card_reader_installed)
. += span_notice("The card reader could be [EXAMINE_HINT("pried")] out.")
. += span_notice("Swipe your PDA with an ID card/Just ID to change access levels.")
. += span_notice("Use multitool to [access_locked ? "unlock" : "lock"] the access panel.")
/obj/structure/closet/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = ..()
var/screentip_change = FALSE
@@ -247,13 +303,45 @@
if(opened)
context[SCREENTIP_CONTEXT_LMB] = "Deconstruct"
else
context[SCREENTIP_CONTEXT_LMB] = welded ? "Unweld" : "Weld"
screentip_change = TRUE
if(!welded && can_weld_shut)
context[SCREENTIP_CONTEXT_LMB] = "Weld"
screentip_change = TRUE
else if(welded)
context[SCREENTIP_CONTEXT_LMB] = "Unweld"
screentip_change = TRUE
if(istype(held_item) && held_item.tool_behaviour == TOOL_WRENCH)
context[SCREENTIP_CONTEXT_RMB] = anchored ? "Unanchor" : "Anchor"
screentip_change = TRUE
if(!locked && (welded || !can_weld_shut))
if(!secure)
if(!broken && can_install_electronics && istype(held_item, /obj/item/electronics/airlock))
context[SCREENTIP_CONTEXT_LMB] = "Install Electronics"
screentip_change = TRUE
else
if(istype(held_item) && held_item.tool_behaviour == TOOL_SCREWDRIVER)
context[SCREENTIP_CONTEXT_LMB] = "Remove Electronics"
screentip_change = TRUE
if(!card_reader_installed && length(access_choices) && !broken && can_install_electronics && istype(held_item, /obj/item/stock_parts/card_reader))
context[SCREENTIP_CONTEXT_LMB] = "Install Reader"
screentip_change = TRUE
if(card_reader_installed && istype(held_item) && held_item.tool_behaviour == TOOL_CROWBAR)
context[SCREENTIP_CONTEXT_LMB] = "Remove Reader"
screentip_change = TRUE
if(!locked && !opened)
if(id_card && istype(held_item, /obj/item/pen))
context[SCREENTIP_CONTEXT_LMB] = "Rename"
screentip_change = TRUE
if(secure && card_reader_installed && !broken)
if(!access_locked && istype(held_item) && !isnull(held_item.GetID()))
context[SCREENTIP_CONTEXT_LMB] = "Change Access"
screentip_change = TRUE
if(istype(held_item) && istype(held_item) && held_item.tool_behaviour == TOOL_MULTITOOL)
context[SCREENTIP_CONTEXT_LMB] = "[access_locked ? "Unlock" : "Lock"] Access Panel"
screentip_change = TRUE
return screentip_change ? CONTEXTUAL_SCREENTIP_SET : NONE
/obj/structure/closet/CanAllowThrough(atom/movable/mover, border_dir)
@@ -267,7 +355,8 @@
if(welded || locked)
return FALSE
if(strong_grab)
to_chat(user, span_danger("[pulledby] has an incredibly strong grip on [src], preventing it from opening."))
if(user)
to_chat(user, span_danger("[pulledby] has an incredibly strong grip on [src], preventing it from opening."))
return FALSE
var/turf/T = get_turf(src)
for(var/mob/living/L in T)
@@ -317,25 +406,28 @@
var/atom/movable/thing = i
thing.atom_storage?.close_all()
/obj/structure/closet/proc/open(mob/living/user, force = FALSE)
if(!can_open(user, force))
///Proc to write checks before opening a door
/obj/structure/closet/proc/before_open(mob/living/user, force)
return TRUE
/obj/structure/closet/proc/open(mob/living/user, force = FALSE, special_effects = TRUE)
if(opened || !can_open(user, force))
return FALSE
if(opened)
return FALSE
if(SEND_SIGNAL(src, COMSIG_CLOSET_PRE_OPEN, user, force) & BLOCK_OPEN)
if(!before_open(user, force) || (SEND_SIGNAL(src, COMSIG_CLOSET_PRE_OPEN, user, force) & BLOCK_OPEN))
return FALSE
welded = FALSE
locked = FALSE
playsound(loc, open_sound, open_sound_volume, TRUE, -3)
if(special_effects)
playsound(loc, open_sound, open_sound_volume, TRUE, -3)
opened = TRUE
if(!dense_when_open)
set_density(FALSE)
dump_contents()
animate_door(FALSE)
if(special_effects)
animate_door(FALSE)
update_appearance()
after_open(user, force)
SEND_SIGNAL(src, COMSIG_CLOSET_POST_OPEN, force)
SEND_SIGNAL(src, COMSIG_CLOSET_POST_OPEN, user, force)
return TRUE
///Proc to override for effects after opening a door
@@ -388,9 +480,15 @@
return TRUE
///Proc to write checks before closing a door
/obj/structure/closet/proc/before_close(mob/living/user)
return TRUE
/obj/structure/closet/proc/close(mob/living/user)
if(!opened || !can_close(user))
return FALSE
if(!before_close(user) || (SEND_SIGNAL(src, COMSIG_CLOSET_PRE_CLOSE, user) & BLOCK_CLOSE))
return FALSE
take_contents()
playsound(loc, close_sound, close_sound_volume, TRUE, -3)
opened = FALSE
@@ -398,9 +496,10 @@
animate_door(TRUE)
update_appearance()
after_close(user)
SEND_SIGNAL(src, COMSIG_CLOSET_POST_CLOSE, user)
return TRUE
///Proc to override for effects after closing a door
///Proc to do effects after closet has closed
/obj/structure/closet/proc/after_close(mob/living/user)
return
@@ -417,10 +516,15 @@
if (!(flags_1 & NODECONSTRUCT_1))
if(ispath(material_drop) && material_drop_amount)
new material_drop(loc, material_drop_amount)
if (electronics)
var/obj/item/electronics/airlock/electronics_ref = electronics
electronics = null
electronics_ref.forceMove(drop_location())
if (secure)
var/obj/item/electronics/airlock/electronics = new(drop_location())
if(length(req_one_access))
electronics.one_access = TRUE
electronics.accesses = req_one_access
else
electronics.accesses = req_access
if(card_reader_installed)
new /obj/item/stock_parts/card_reader(drop_location())
dump_contents()
qdel(src)
@@ -429,6 +533,38 @@
if(!broken && !(flags_1 & NODECONSTRUCT_1))
bust_open()
/obj/structure/closet/CheckParts(list/parts_list)
var/obj/item/electronics/airlock/access_control = locate() in parts_list
if(QDELETED(access_control))
return
if (access_control.one_access)
req_one_access = access_control.accesses
req_access = null
else
req_access = access_control.accesses
req_one_access = null
access_control.moveToNullspace()
parts_list -= access_control
qdel(access_control)
/obj/structure/closet/multitool_act(mob/living/user, obj/item/tool)
if(!secure || !card_reader_installed || broken || locked || opened)
return
access_locked = !access_locked
balloon_alert(user, "access panel [access_locked ? "locked" : "unlocked"]")
return TRUE
/// sets the access for the closets from the swiped ID card
/obj/structure/closet/proc/set_access(list/accesses)
if(length(req_one_access))
req_one_access = accesses
req_access = null
else
req_access = accesses
req_one_access = null
/obj/structure/closet/attackby(obj/item/W, mob/user, params)
if(user in src)
return
@@ -437,94 +573,253 @@
else
return ..()
/obj/structure/closet/proc/tool_interact(obj/item/W, mob/living/user)//returns TRUE if attackBy call shouldn't be continued (because tool was used/closet was of wrong type), FALSE if otherwise
/// check if we can install airlock electronics in this closet
/obj/structure/closet/proc/can_install_airlock_electronics(mob/user)
if(secure || !can_install_electronics || !(welded || !can_weld_shut))
return FALSE
if(broken)
balloon_alert(user, "its broken!")
return FALSE
if(locked)
balloon_alert(user, "unlock first!")
return FALSE
return TRUE
/// check if we can unscrew airlock electronics from this closet
/obj/structure/closet/proc/can_unscrew_airlock_electronics(mob/user)
if(!secure || !(welded || !can_weld_shut))
return FALSE
if(locked)
balloon_alert(user, "unlock first!")
return FALSE
return TRUE
/// check if we can install card reader in this closet
/obj/structure/closet/proc/can_install_card_reader(mob/user)
if(card_reader_installed || !can_install_electronics || !length(access_choices) || !(welded || !can_weld_shut))
return FALSE
if(broken)
balloon_alert(user, "its broken!")
return FALSE
if(!secure)
balloon_alert(user, "no electronics inside!")
return FALSE
if(locked)
balloon_alert(user, "unlock first!")
return FALSE
return TRUE
/// check if we can pry out the card reader from this closet
/obj/structure/closet/proc/can_pryout_card_reader(mob/user)
if(!card_reader_installed || !(welded || !can_weld_shut))
return FALSE
if(locked)
balloon_alert(user, "unlock first!")
return FALSE
return TRUE
/// returns TRUE if attackBy call shouldn't be continued (because tool weaponas used/closet weaponas of weaponrong type), FALSE if otherweaponise
/obj/structure/closet/proc/tool_interact(obj/item/weapon, mob/living/user)
. = TRUE
if(opened)
if(istype(W, cutting_tool))
if(W.tool_behaviour == TOOL_WELDER)
if(!W.tool_start_check(user, amount=0))
var/obj/item/card/id/id = null
if(!opened && istype(weapon, /obj/item/airlock_painter))
if(!length(paint_jobs))
return
var/choice = tgui_input_list(user, "Set Closet Paintjob", "Paintjob", paint_jobs)
if(isnull(choice))
return
var/obj/item/airlock_painter/painter = weapon
if(!painter.use_paint(user))
return
var/list/paint_job = paint_jobs[choice]
icon_state = paint_job["icon_state"]
base_icon_state = icon_state
icon_door = paint_job["icon_door"]
update_appearance()
else if(istype(weapon, /obj/item/electronics/airlock) && can_install_airlock_electronics(user))
user.visible_message(span_notice("[user] installs the electronics into the [src]."),\
span_notice("You start to install electronics into the [src]..."))
if(!do_after(user, 4 SECONDS, target = src, extra_checks = CALLBACK(src, PROC_REF(can_install_airlock_electronics), user)))
return
if(!user.transferItemToLoc(weapon, src))
return
CheckParts(list(weapon))
secure = TRUE
balloon_alert(user, "electronics installed")
update_appearance()
else if(weapon.tool_behaviour == TOOL_SCREWDRIVER && can_unscrew_airlock_electronics(user))
user.visible_message(span_notice("[user] begins to remove the electronics from the [src]."),\
span_notice("You begin to remove the electronics from the [src]..."))
if (!weapon.use_tool(src, user, 40, volume = 50, extra_checks = CALLBACK(src, PROC_REF(can_unscrew_airlock_electronics), user)))
return
var/obj/item/electronics/airlock/airlock_electronics = new(drop_location())
if(length(req_one_access))
airlock_electronics.one_access = TRUE
airlock_electronics.accesses = req_one_access
else
airlock_electronics.accesses = req_access
req_access = list()
req_one_access = null
id_card = null
secure = FALSE
balloon_alert(user, "electronics removed")
update_appearance()
else if(istype(weapon, /obj/item/stock_parts/card_reader) && can_install_card_reader(user))
user.visible_message(span_notice("[user] is installing a card reader."),
span_notice("You begin installing the card reader."))
if(!do_after(user, 4 SECONDS, target = src, extra_checks = CALLBACK(src, PROC_REF(can_install_card_reader), user)))
return
qdel(weapon)
card_reader_installed = TRUE
balloon_alert(user, "card reader installed")
else if(weapon.tool_behaviour == TOOL_CROWBAR && can_pryout_card_reader(user))
user.visible_message(span_notice("[user] begins to pry the card reader out from [src]."),\
span_notice("You begin to pry the card reader out from [src]..."))
if(!weapon.use_tool(src, user, 4 SECONDS, extra_checks = CALLBACK(src, PROC_REF(can_pryout_card_reader), user)))
return
new /obj/item/stock_parts/card_reader(drop_location())
card_reader_installed = FALSE
balloon_alert(user, "card reader removed")
else if(secure && !broken && card_reader_installed && !locked && !opened && !access_locked && !isnull((id = weapon.GetID())))
var/num_choices = length(access_choices)
if(!num_choices)
return
var/choice
if(num_choices == 1)
choice = access_choices[1]
else
choice = tgui_input_list(user, "Set Access Type", "Access Type", access_choices)
if(isnull(choice))
return
id_card = null
switch(choice)
if("Personal") //only the player weaponho sweaponiped their id has access.
id_card = WEAKREF(id)
name = "[id.registered_name] locker"
desc = "now owned by [id.registered_name]. [initial(desc)]"
if("Departmental") //anyone weaponho has the same access permissions as this id has access
name = "[id.assignment] closet"
desc = "Its a [id.assignment] closet. [initial(desc)]"
set_access(id.GetAccess())
if("None") //free for all
name = initial(name)
desc = initial(desc)
req_access = list()
req_one_access = null
set_access(list())
if(!isnull(id_card))
balloon_alert(user, "now owned by [id.registered_name]")
else
balloon_alert(user, "set to [choice]")
else if(!opened && istype(weapon, /obj/item/pen))
if(locked)
balloon_alert(user, "unlock first!")
return
if(isnull(id_card))
balloon_alert(user, "not yours to rename!")
return
var/name_set = FALSE
var/desc_set = FALSE
var/str = tgui_input_text(user, "Personal Locker Name", "Locker Name")
if(!isnull(str))
name = str
name_set = TRUE
str = tgui_input_text(user, "Personal Locker Description", "Locker Description")
if(!isnull(str))
desc = str
desc_set = TRUE
var/bit_flag = NONE
if(name_set)
bit_flag |= UPDATE_NAME
if(desc_set)
bit_flag |= UPDATE_DESC
if(bit_flag)
update_appearance(bit_flag)
else if(opened)
if(istype(weapon, cutting_tool))
if(weapon.tool_behaviour == TOOL_WELDER)
if(!weapon.tool_start_check(user, amount=0))
return
to_chat(user, span_notice("You begin cutting \the [src] apart..."))
if(W.use_tool(src, user, 40, volume=50))
if(weapon.use_tool(src, user, 40, volume=50))
if(!opened)
return
user.visible_message(span_notice("[user] slices apart \the [src]."),
span_notice("You cut \the [src] apart with \the [W]."),
span_hear("You hear welding."))
span_notice("You cut \the [src] apart weaponith \the [weapon]."),
span_hear("You hear weaponelding."))
deconstruct(TRUE)
return
else // for example cardboard box is cut with wirecutters
else // for example cardboard box is cut weaponith weaponirecutters
user.visible_message(span_notice("[user] cut apart \the [src]."), \
span_notice("You cut \the [src] apart with \the [W]."))
span_notice("You cut \the [src] apart weaponith \the [weapon]."))
deconstruct(TRUE)
return
if (user.combat_mode)
return FALSE
if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too
return
else if(W.tool_behaviour == TOOL_WELDER && can_weld_shut)
if(!W.tool_start_check(user, amount=0))
if(user.transferItemToLoc(weapon, drop_location())) // so weapone put in unlit weaponelder too
return
to_chat(user, span_notice("You begin [welded ? "unwelding":"welding"] \the [src]..."))
if(W.use_tool(src, user, 40, volume=50))
else if(weapon.tool_behaviour == TOOL_WELDER && can_weld_shut)
if(!weapon.tool_start_check(user, amount=0))
return
if(weapon.use_tool(src, user, 40, volume=50))
if(opened)
return
welded = !welded
after_weld(welded)
user.visible_message(span_notice("[user] [welded ? "welds shut" : "unwelded"] \the [src]."),
span_notice("You [welded ? "weld" : "unwelded"] \the [src] with \the [W]."),
span_notice("You [welded ? "weld" : "unwelded"] \the [src] with \the [weapon]."),
span_hear("You hear welding."))
user.log_message("[welded ? "welded":"unwelded"] closet [src] with [W]", LOG_GAME)
user.log_message("[welded ? "welded":"unwelded"] closet [src] with [weapon]", LOG_GAME)
update_appearance()
else if (can_install_electronics && istype(W, /obj/item/electronics/airlock)\
&& !secure && !electronics && !locked && (welded || !can_weld_shut) && !broken)
user.visible_message(span_notice("[user] installs the electronics into the [src]."),\
span_notice("You start to install electronics into the [src]..."))
if (!do_after(user, 4 SECONDS, target = src))
return FALSE
if (electronics || secure)
return FALSE
if (!user.transferItemToLoc(W, src))
return FALSE
W.moveToNullspace()
to_chat(user, span_notice("You install the electronics."))
electronics = W
if (electronics.one_access)
req_one_access = electronics.accesses
else
req_access = electronics.accesses
secure = TRUE
update_appearance()
else if (can_install_electronics && W.tool_behaviour == TOOL_SCREWDRIVER\
&& (secure || electronics) && !locked && (welded || !can_weld_shut))
user.visible_message(span_notice("[user] begins to remove the electronics from the [src]."),\
span_notice("You begin to remove the electronics from the [src]..."))
var/had_electronics = !!electronics
var/was_secure = secure
if (!do_after(user, 4 SECONDS, target = src))
return FALSE
if ((had_electronics && !electronics) || (was_secure && !secure))
return FALSE
var/obj/item/electronics/airlock/electronics_ref
if (!electronics)
electronics_ref = new /obj/item/electronics/airlock(loc)
if (req_one_access.len)
electronics_ref.one_access = 1
electronics_ref.accesses = req_one_access
else
electronics_ref.accesses = req_access
else
electronics_ref = electronics
electronics = null
electronics_ref.forceMove(drop_location())
secure = FALSE
update_appearance()
else if(!user.combat_mode)
var/item_is_id = W.GetID()
var/item_is_id = weapon.GetID()
if(!item_is_id)
return FALSE
return
if((item_is_id || !toggle(user)) && !opened)
togglelock(user)
else
@@ -692,7 +987,7 @@
welded = FALSE //applies to all lockers
locked = FALSE //applies to critter crates and secure lockers only
broken = TRUE //applies to secure lockers only
open()
open(force = TRUE, special_effects = FALSE)
/obj/structure/closet/attack_hand_secondary(mob/user, modifiers)
. = ..()
@@ -705,19 +1000,37 @@
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/obj/structure/closet/proc/togglelock(mob/living/user, silent)
if(secure && !broken)
if(allowed(user))
if(iscarbon(user))
add_fingerprint(user)
balloon_alert_to_viewers(locked ? "unlocked" : "locked")
locked = !locked
user.visible_message(span_notice("[user] [locked ? null : "un"]locks [src]."),
span_notice("You [locked ? null : "un"]lock [src]."))
update_appearance()
else if(!silent)
balloon_alert(user, "access denied!")
else if(secure && broken)
balloon_alert(user, "broken!")
if(!secure || broken)
return
if(locked) //only apply checks while unlocking else allow anyone to lock it
var/error_msg = ""
if(!isnull(id_card))
var/obj/item/card/id/advanced/prisoner/registered_id = id_card.resolve()
if(!registered_id) //id was deleted at some point. make this closet public access again
name = initial(name)
desc = initial(desc)
id_card = null
req_access = list()
req_one_access = null
togglelock(user, silent)
return
if(registered_id != user.get_idcard())
error_msg = "not your locker!"
else if(!allowed(user)) //allow anyone to lock the closet for safe keeping but apply checks only when unlocking
error_msg = "access denied!"
if(error_msg)
if(!silent)
balloon_alert(user, error_msg)
return
if(iscarbon(user))
add_fingerprint(user)
locked = !locked
user.visible_message(span_notice("[user] [locked ? "locks" : "unlocks"][src]."),
span_notice("You [locked ? "locked" : "unlocked"] [src]."))
update_appearance()
/obj/structure/closet/emag_act(mob/user)
if(secure && !broken)
@@ -805,4 +1118,7 @@
locked = FALSE
INVOKE_ASYNC(src, PROC_REF(open))
/obj/structure/closet/preopen
opened = TRUE
#undef LOCKER_FULL
@@ -1,4 +1,3 @@
/obj/structure/closet/body_bag
name = "body bag"
desc = "A plastic bag designed for the storage and transportation of cadavers."
@@ -18,11 +17,13 @@
drag_slowdown = 0
has_closed_overlay = FALSE
can_install_electronics = FALSE
paint_jobs = null
///The tagged name of the bodybag, also used to check if the bodybag IS tagged.
var/tag_name
var/foldedbag_path = /obj/item/bodybag
var/obj/item/bodybag/foldedbag_instance = null
/// The tagged name of the bodybag, also used to check if the bodybag IS tagged.
var/tag_name
/obj/structure/closet/body_bag/Initialize(mapload)
. = ..()
@@ -72,10 +73,9 @@
if(tag_name)
. += "bodybag_label"
/obj/structure/closet/body_bag/close(mob/living/user)
/obj/structure/closet/body_bag/after_close(mob/living/user)
. = ..()
if(.)
set_density(FALSE)
set_density(FALSE)
/obj/structure/closet/body_bag/attack_hand_secondary(mob/user, list/modifiers)
. = ..()
@@ -257,6 +257,18 @@
to_chat(the_folder, span_warning("You wrestle with [src], but it won't fold while its straps are fastened."))
return ..()
/obj/structure/closet/body_bag/environmental/prisoner/before_open(mob/living/user, force)
. = ..()
if(!.)
return FALSE
if(sinched && !force)
to_chat(user, span_danger("The buckles on [src] are sinched down, preventing it from opening."))
return FALSE
sinched = FALSE //in case it was forced open unsinch it
return TRUE
/obj/structure/closet/body_bag/environmental/prisoner/update_icon()
. = ..()
if(sinched)
@@ -264,22 +276,6 @@
else
icon_state = initial(icon_state)
/obj/structure/closet/body_bag/environmental/prisoner/open(mob/living/user, force = FALSE)
if(sinched && !force)
to_chat(user, span_danger("The buckles on [src] are sinched down, preventing it from opening."))
return TRUE
if(opened)
return FALSE
sinched = FALSE
playsound(loc, open_sound, open_sound_volume, TRUE, -3)
opened = TRUE
if(!dense_when_open)
set_density(FALSE)
dump_contents()
update_appearance()
after_open(user, force)
return TRUE
/obj/structure/closet/body_bag/environmental/prisoner/container_resist_act(mob/living/user)
/// copy-pasted with changes because flavor text as well as some other misc stuff
if(opened)
@@ -291,7 +287,7 @@
location.relay_container_resist_act(user, src)
return
if(!sinched)
open()
open(user)
return
user.changeNext_move(CLICK_CD_BREAKOUT)
@@ -17,15 +17,19 @@
close_sound_volume = 35
has_closed_overlay = FALSE
door_anim_time = 0 // no animation
var/move_speed_multiplier = 1
var/move_delay = FALSE
can_install_electronics = FALSE
paint_jobs = null
/// Cooldown controlling when the box can trigger the Metal Gear Solid-style '!' alert.
COOLDOWN_DECLARE(alert_cooldown)
/// How much time must pass before the box can trigger the next Metal Gear Solid-style '!' alert.
var/time_between_alerts = 60 SECONDS
/// List of viewers around the box
var/list/alerted
/// How fast a mob can move inside this box
var/move_speed_multiplier = 1
/// If the speed multiplier should be applied to mobs inside this box
var/move_delay = FALSE
/obj/structure/closet/cardboard/relaymove(mob/living/user, direction)
if(opened || move_delay || user.incapacitated() || !isturf(loc) || !has_gravity(loc))
@@ -41,32 +45,34 @@
/obj/structure/closet/cardboard/proc/ResetMoveDelay()
move_delay = FALSE
/obj/structure/closet/cardboard/open(mob/living/user, force = FALSE)
var/do_alert = (COOLDOWN_FINISHED(src, alert_cooldown) && (locate(/mob/living) in contents))
if(!do_alert)
return ..()
// Get mobs in view before we open the box.
var/list/alerted = list()
for(var/mob/living/alerted_mob in viewers(7, src))
if(alerted_mob.stat != CONSCIOUS || alerted_mob.is_blind())
continue
alerted += alerted_mob
// There are no mobs to alert?
if(!length(alerted))
return ..()
/obj/structure/closet/cardboard/before_open(mob/living/user, force)
. = ..()
// Box didn't open?
if(!.)
return FALSE
alerted = null
var/do_alert = (COOLDOWN_FINISHED(src, alert_cooldown) && (locate(/mob/living) in contents))
if(!do_alert)
return TRUE
// Cache the list before we open the box.
alerted = viewers(7, src)
// There are no mobs to alert? clear the list & prevent furthur action after opening the box
if(!(locate(/mob/living) in alerted))
alerted = null
return TRUE
/obj/structure/closet/cardboard/after_open(mob/living/user, force)
. = ..()
if(!length(alerted))
return
COOLDOWN_START(src, alert_cooldown, time_between_alerts)
for(var/mob/living/alerted_mob as anything in alerted)
if(alerted_mob.stat != CONSCIOUS || alerted_mob.is_blind())
continue
if(!alerted_mob.incapacitated(IGNORE_RESTRAINTS))
alerted_mob.face_atom(src)
alerted_mob.do_alert_animation()
@@ -23,9 +23,9 @@
if(replicating_type && !opened && (length(contents) < stop_replicating_at))
new replicating_type(src)
/obj/structure/closet/infinite/open(mob/living/user, force = FALSE)
/obj/structure/closet/infinite/after_close(mob/living/user, force)
. = ..()
if(. && auto_close_time)
if(auto_close_time)
addtimer(CALLBACK(src, PROC_REF(close_on_my_own)), auto_close_time, TIMER_OVERRIDE | TIMER_UNIQUE)
/obj/structure/closet/infinite/proc/close_on_my_own()
@@ -9,6 +9,7 @@
open_sound_volume = 25
close_sound_volume = 50
door_anim_time = 0 // no animation
paint_jobs = null
/obj/structure/closet/secure_closet/bar/PopulateContents()
..()
@@ -1,11 +1,25 @@
/obj/structure/closet/secure_closet/freezer
icon_state = "freezer"
base_icon_state = "freezer"
flags_1 = PREVENT_CONTENTS_EXPLOSION_1
door_anim_squish = 0.22
door_anim_angle = 123
door_anim_time = 4
/// If FALSE, we will protect the first person in the freezer from an explosion / nuclear blast.
var/jones = FALSE
paint_jobs = null
/obj/structure/closet/secure_closet/freezer/before_open(mob/living/user, force)
. = ..()
if(!.)
return FALSE
toggle_organ_decay(src)
return TRUE
/obj/structure/closet/secure_closet/freezer/after_close(mob/living/user)
. = ..()
toggle_organ_decay(src)
/obj/structure/closet/secure_closet/freezer/Destroy()
toggle_organ_decay(src)
@@ -15,17 +29,6 @@
. = ..()
toggle_organ_decay(src)
/obj/structure/closet/secure_closet/freezer/open(mob/living/user, force = FALSE)
if(opened || !can_open(user, force)) //dupe check just so we don't let the organs decay when someone fails to open the locker
return FALSE
toggle_organ_decay(src)
return ..()
/obj/structure/closet/secure_closet/freezer/close(mob/living/user)
if(..()) //if we actually closed the locker
toggle_organ_decay(src)
return TRUE
/obj/structure/closet/secure_closet/freezer/ex_act()
if(jones)
return ..()
@@ -33,27 +36,11 @@
flags_1 &= ~PREVENT_CONTENTS_EXPLOSION_1
return FALSE
/obj/structure/closet/secure_closet/freezer/atom_destruction(damage_flag)
new /obj/item/stack/sheet/iron(drop_location(), 1)
new /obj/item/assembly/igniter/condenser(drop_location())
return ..()
/obj/structure/closet/secure_closet/freezer/welder_act(mob/living/user, obj/item/tool)
/obj/structure/closet/secure_closet/freezer/deconstruct(disassembled)
if (!(flags_1 & NODECONSTRUCT_1))
new /obj/item/assembly/igniter/condenser(drop_location())
. = ..()
if(!opened)
balloon_alert(user, "open it first!")
return TRUE
if(!tool.use_tool(src, user, 40, volume=50))
return TRUE
new /obj/item/stack/sheet/iron(drop_location(), 2)
new /obj/item/assembly/igniter/condenser(drop_location())
qdel(src)
return TRUE
/obj/structure/closet/secure_closet/freezer/empty
name = "freezer"
@@ -98,8 +85,8 @@
new /obj/item/food/meat/slab/monkey(src)
/obj/structure/closet/secure_closet/freezer/meat/open
req_access = list()
locked = FALSE
req_access = list()
/obj/structure/closet/secure_closet/freezer/gulag_fridge
name = "refrigerator"
@@ -125,6 +112,11 @@
req_access = null
locked = FALSE
/obj/structure/closet/secure_closet/freezer/fridge/preopen
req_access = null
locked = FALSE
opened = TRUE
/obj/structure/closet/secure_closet/freezer/money
name = "freezer"
desc = "This contains cold hard cash."
@@ -1,8 +1,13 @@
/obj/structure/closet/secure_closet/personal
desc = "It's a secure locker for personnel. The first card swiped gains control."
desc = "It's a secure locker for personnel. The first person to open this closet gains control."
name = "personal closet"
req_access = list(ACCESS_ALL_PERSONAL_LOCKERS)
var/registered_name = null
card_reader_installed = TRUE
/obj/structure/closet/secure_closet/personal/Initialize(mapload)
. = ..()
var/static/list/choices = list("Personal")
access_choices = choices
/obj/structure/closet/secure_closet/personal/PopulateContents()
..()
@@ -34,33 +39,3 @@
new /obj/item/storage/backpack/satchel/leather/withwallet( src )
new /obj/item/instrument/piano_synth(src)
new /obj/item/radio/headset( src )
/obj/structure/closet/secure_closet/personal/attackby(obj/item/W, mob/user, params)
var/obj/item/card/id/I = W.GetID()
if(istype(I))
if(broken)
to_chat(user, span_danger("It appears to be broken."))
return
if(!I || !I.registered_name)
return
if(allowed(user) || !registered_name || (istype(I) && (registered_name == I.registered_name)))
//they can open all lockers, or nobody owns this, or they own this locker
locked = !locked
update_appearance()
if(!registered_name)
registered_name = I.registered_name
desc = "Owned by [I.registered_name]."
else
to_chat(user, span_danger("Access Denied."))
else
return ..()
/obj/structure/closet/secure_closet/personal/allowed(mob/mob_to_check)
. = ..()
if (. || !ishuman(mob_to_check))
return
var/mob/living/carbon/human/human_to_check = mob_to_check
var/obj/item/card/id/id_card = human_to_check.wear_id?.GetID()
if (istype(id_card) && id_card.registered_name == registered_name)
return TRUE
@@ -7,6 +7,7 @@
armor_type = /datum/armor/closet_secure_closet
secure = TRUE
damage_deflection = 20
material_drop_amount = 5
/datum/armor/closet_secure_closet
melee = 30
@@ -1,7 +1,7 @@
/obj/structure/closet/secure_closet/captains
name = "captain's locker"
req_access = list(ACCESS_CAPTAIN)
icon_state = "cap"
req_access = list(ACCESS_CAPTAIN)
/obj/structure/closet/secure_closet/captains/PopulateContents()
..()
@@ -23,8 +23,8 @@
/obj/structure/closet/secure_closet/hop
name = "head of personnel's locker"
req_access = list(ACCESS_HOP)
icon_state = "hop"
req_access = list(ACCESS_HOP)
/obj/structure/closet/secure_closet/hop/PopulateContents()
..()
@@ -47,8 +47,8 @@
/obj/structure/closet/secure_closet/hos
name = "head of security's locker"
req_access = list(ACCESS_HOS)
icon_state = "hos"
req_access = list(ACCESS_HOS)
/obj/structure/closet/secure_closet/hos/PopulateContents()
..()
@@ -75,8 +75,8 @@
/obj/structure/closet/secure_closet/warden
name = "warden's locker"
req_access = list(ACCESS_ARMORY)
icon_state = "warden"
req_access = list(ACCESS_ARMORY)
/obj/structure/closet/secure_closet/warden/PopulateContents()
..()
@@ -101,8 +101,8 @@
/obj/structure/closet/secure_closet/security
name = "security officer's locker"
req_access = list(ACCESS_BRIG)
icon_state = "sec"
req_access = list(ACCESS_BRIG)
/obj/structure/closet/secure_closet/security/PopulateContents()
..()
@@ -150,13 +150,13 @@
/obj/structure/closet/secure_closet/detective
name = "\improper detective's cabinet"
req_access = list(ACCESS_DETECTIVE)
icon_state = "cabinet"
resistance_flags = FLAMMABLE
max_integrity = 70
door_anim_time = 0 // no animation
open_sound = 'sound/machines/wooden_closet_open.ogg'
close_sound = 'sound/machines/wooden_closet_close.ogg'
req_access = list(ACCESS_DETECTIVE)
/obj/structure/closet/secure_closet/detective/PopulateContents()
..()
@@ -183,62 +183,54 @@
/obj/structure/closet/secure_closet/brig
name = "brig locker"
req_one_access = list(ACCESS_BRIG)
anchored = TRUE
req_one_access = list(ACCESS_BRIG)
var/id = null
/obj/structure/closet/secure_closet/brig/genpop
name = "genpop storage locker"
desc = "Used for storing the belongings of genpop's tourists visiting the locals."
///Reference to the ID linked to the locker, done by swiping a prisoner ID on it
var/datum/weakref/assigned_id_ref = null
/obj/structure/closet/secure_closet/brig/genpop/Destroy()
assigned_id_ref = null
return ..()
access_choices = FALSE
paint_jobs = null
/obj/structure/closet/secure_closet/brig/genpop/examine(mob/user)
. = ..()
. += span_notice("<b>Right-click</b> with a Security-level ID to reset [src]'s registered ID.")
/obj/structure/closet/secure_closet/brig/genpop/attackby(obj/item/card/id/advanced/prisoner/used_id, mob/user, params)
. = ..()
if(!istype(used_id, /obj/item/card/id/advanced/prisoner))
return
/obj/structure/closet/secure_closet/brig/genpop/attackby(obj/item/card/id/advanced/prisoner/user_id, mob/user, params)
if(!secure || !istype(user_id))
return ..()
if(!assigned_id_ref)
if(isnull(id_card))
say("Prisoner ID linked to locker.")
assigned_id_ref = WEAKREF(used_id)
name = "genpop storage locker - [used_id.registered_name]"
return
var/obj/item/card/id/advanced/prisoner/registered_id = assigned_id_ref.resolve()
if(used_id == registered_id)
say("Authorized ID detected. Unlocking locker and resetting ID.")
locked = FALSE
assigned_id_ref = null
name = initial(name)
update_appearance()
id_card = WEAKREF(user_id)
name = "genpop storage locker - [user_id.registered_name]"
/obj/structure/closet/secure_closet/brig/genpop/proc/clear_access()
say("Authorized ID detected. Unlocking locker and resetting ID.")
locked = FALSE
id_card = null
name = initial(name)
update_appearance()
/obj/structure/closet/secure_closet/brig/genpop/attackby_secondary(obj/item/card/id/advanced/used_id, mob/user, params)
. = ..()
if(!secure || !istype(used_id))
return ..()
var/list/id_access = used_id.GetAccess()
if(assigned_id_ref && (ACCESS_BRIG in id_access))
say("Authorized ID detected. Unlocking locker and resetting ID.")
locked = FALSE
assigned_id_ref = null
name = initial(name)
update_appearance()
if(!isnull(id_card) && (ACCESS_BRIG in id_access))
clear_access()
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/obj/structure/closet/secure_closet/evidence
anchored = TRUE
name = "secure evidence closet"
req_one_access = list("armory","detective")
req_one_access = list(ACCESS_ARMORY, ACCESS_DETECTIVE)
/obj/structure/closet/secure_closet/brig/PopulateContents()
..()
new /obj/item/clothing/under/rank/prisoner( src )
new /obj/item/clothing/under/rank/prisoner/skirt( src )
new /obj/item/clothing/shoes/sneakers/orange( src )
@@ -265,14 +257,14 @@
req_access = list(ACCESS_ARMORY)
/obj/structure/closet/secure_closet/contraband/heads
anchored = TRUE
name = "contraband locker"
req_access = list(ACCESS_COMMAND)
anchored = TRUE
/obj/structure/closet/secure_closet/armory1
name = "armory armor locker"
req_access = list(ACCESS_ARMORY)
icon_state = "armory"
req_access = list(ACCESS_ARMORY)
/obj/structure/closet/secure_closet/armory1/PopulateContents()
..()
@@ -291,8 +283,8 @@
/obj/structure/closet/secure_closet/armory2
name = "armory ballistics locker"
req_access = list(ACCESS_ARMORY)
icon_state = "armory"
req_access = list(ACCESS_ARMORY)
/obj/structure/closet/secure_closet/armory2/PopulateContents()
..()
@@ -304,8 +296,8 @@
/obj/structure/closet/secure_closet/armory3
name = "armory energy gun locker"
req_access = list(ACCESS_ARMORY)
icon_state = "armory"
req_access = list(ACCESS_ARMORY)
/obj/structure/closet/secure_closet/armory3/PopulateContents()
..()
@@ -320,8 +312,8 @@
/obj/structure/closet/secure_closet/tac
name = "armory tac locker"
req_access = list(ACCESS_ARMORY)
icon_state = "tac"
req_access = list(ACCESS_ARMORY)
/obj/structure/closet/secure_closet/tac/PopulateContents()
..()
@@ -332,8 +324,8 @@
/obj/structure/closet/secure_closet/labor_camp_security
name = "labor camp security locker"
req_access = list(ACCESS_SECURITY)
icon_state = "sec"
req_access = list(ACCESS_SECURITY)
/obj/structure/closet/secure_closet/labor_camp_security/PopulateContents()
..()
@@ -2,6 +2,8 @@
name = "armory closet"
desc = "Why is this here?"
icon_state = "syndicate"
armor_type = /datum/armor/closet_syndicate
paint_jobs = null
/datum/armor/closet_syndicate
melee = 70
@@ -3,6 +3,7 @@
desc = "A rectangular steel crate."
icon = 'icons/obj/storage/crates.dmi'
icon_state = "crate"
base_icon_state = "crate"
req_access = null
can_weld_shut = FALSE
horizontal = TRUE
@@ -29,13 +30,20 @@
var/lid_y = 0
/obj/structure/closet/crate/Initialize(mapload)
AddElement(/datum/element/climbable, climb_time = crate_climb_time, climb_stun = 0) //add element in closed state before parent init opens it(if it does)
. = ..()
if(icon_state == "[initial(icon_state)]open")
opened = TRUE
AddElement(/datum/element/climbable, climb_time = crate_climb_time * 0.5, climb_stun = 0)
else
AddElement(/datum/element/climbable, climb_time = crate_climb_time, climb_stun = 0)
update_appearance()
var/static/list/crate_paint_jobs = list(
"Internals" = list("icon_state" = "o2crate"),
"Medical" = list("icon_state" = "medicalcrate"),
"Radiation" = list("icon_state" = "radiation"),
"Hydrophonics" = list("icon_state" = "hydrocrate"),
"Science" = list("icon_state" = "scicrate"),
"Solar" = list("icon_state" = "engi_e_crate"),
"Engineering" = list("icon_state" = "engi_crate")
)
if(!isnull(paint_jobs))
paint_jobs = crate_paint_jobs
/obj/structure/closet/crate/Destroy()
QDEL_NULL(manifest)
@@ -52,7 +60,7 @@
return TRUE
/obj/structure/closet/crate/update_icon_state()
icon_state = "[initial(icon_state)][opened ? "open" : ""]"
icon_state = "[isnull(base_icon_state) ? initial(icon_state) : base_icon_state][opened ? "open" : ""]"
return ..()
/obj/structure/closet/crate/closet_update_overlays(list/new_overlays)
@@ -65,6 +73,12 @@
. += "securecrater"
else if(secure)
. += "securecrateg"
if(opened && lid_icon_state)
var/mutable_appearance/lid = mutable_appearance(icon = lid_icon, icon_state = lid_icon_state)
lid.pixel_x = lid_x
lid.pixel_y = lid_y
lid.layer = layer
. += lid
/obj/structure/closet/crate/attack_hand(mob/user, list/modifiers)
. = ..()
@@ -77,22 +91,17 @@
. = ..()
RemoveElement(/datum/element/climbable, climb_time = crate_climb_time, climb_stun = 0)
AddElement(/datum/element/climbable, climb_time = crate_climb_time * 0.5, climb_stun = 0)
/obj/structure/closet/crate/after_close(mob/living/user, force)
. = ..()
RemoveElement(/datum/element/climbable, climb_time = crate_climb_time * 0.5, climb_stun = 0)
AddElement(/datum/element/climbable, climb_time = crate_climb_time, climb_stun = 0)
/obj/structure/closet/crate/open(mob/living/user, force = FALSE)
. = ..()
if(. && !QDELETED(manifest))
to_chat(user, span_notice("The manifest is torn off [src]."))
if(!QDELETED(manifest))
playsound(src, 'sound/items/poster_ripped.ogg', 75, TRUE)
manifest.forceMove(get_turf(src))
manifest = null
update_appearance()
/obj/structure/closet/crate/after_close(mob/living/user)
. = ..()
RemoveElement(/datum/element/climbable, climb_time = crate_climb_time * 0.5, climb_stun = 0)
AddElement(/datum/element/climbable, climb_time = crate_climb_time, climb_stun = 0)
/obj/structure/closet/crate/proc/tear_manifest(mob/user)
to_chat(user, span_notice("You tear the manifest off of [src]."))
playsound(src, 'sound/items/poster_ripped.ogg', 75, TRUE)
@@ -103,20 +112,15 @@
manifest = null
update_appearance()
/obj/structure/closet/crate/closet_update_overlays(list/new_overlays)
. = new_overlays
if(opened && lid_icon_state)
var/mutable_appearance/lid = mutable_appearance(icon = lid_icon, icon_state = lid_icon_state)
lid.pixel_x = lid_x
lid.pixel_y = lid_y
lid.layer = layer
. += lid
. += ..()
/obj/structure/closet/crate/preopen
opened = TRUE
icon_state = "crateopen"
/obj/structure/closet/crate/coffin
name = "coffin"
desc = "It's a burial receptacle for the dearly departed."
icon_state = "coffin"
base_icon_state = "coffin"
resistance_flags = FLAMMABLE
max_integrity = 70
material_drop = /obj/item/stack/sheet/mineral/wood
@@ -126,6 +130,7 @@
open_sound_volume = 25
close_sound_volume = 50
can_install_electronics = FALSE
paint_jobs = null
/obj/structure/closet/crate/maint
@@ -135,12 +140,10 @@
var/static/list/possible_crates = RANDOM_CRATE_LOOT
var/crate_path = pick_weight(possible_crates)
var/obj/structure/closet/crate = new crate_path(loc)
crate.RegisterSignal(crate, COMSIG_CLOSET_POPULATE_CONTENTS, TYPE_PROC_REF(/obj/structure/closet/, populate_with_random_maint_loot))
var/obj/structure/closet/crate/random_crate = new crate_path(loc)
random_crate.RegisterSignal(random_crate, COMSIG_CLOSET_POPULATE_CONTENTS, TYPE_PROC_REF(/obj/structure/closet/, populate_with_random_maint_loot))
if (prob(50))
crate.opened = TRUE
crate.update_appearance()
random_crate.open(null, special_effects = FALSE) //crates spawned as immediatly opened don't need to animate into being opened
return INITIALIZE_HINT_QDEL
@@ -172,12 +175,15 @@
desc = "An internals crate."
name = "internals crate"
icon_state = "o2crate"
base_icon_state = "o2crate"
/obj/structure/closet/crate/trashcart //please make this a generic cart path later after things calm down a little
desc = "A heavy, metal trashcart with wheels."
name = "trash cart"
icon_state = "trashcart"
base_icon_state = "trashcart"
can_install_electronics = FALSE
paint_jobs = null
/obj/structure/closet/crate/trashcart/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
@@ -188,36 +194,37 @@
name = "laundry cart"
desc = "A large cart for hauling around large amounts of laundry."
icon_state = "laundry"
base_icon_state = "laundry"
/obj/structure/closet/crate/medical
desc = "A medical crate."
name = "medical crate"
icon_state = "medicalcrate"
base_icon_state = "medicalcrate"
/obj/structure/closet/crate/freezer
desc = "A freezer."
name = "freezer"
icon_state = "freezer"
base_icon_state = "freezer"
paint_jobs = null
//Snowflake organ freezer code
//Order is important, since we check source, we need to do the check whenever we have all the organs in the crate
/obj/structure/closet/crate/freezer/before_open(mob/living/user, force)
. = ..()
if(!.)
return FALSE
/obj/structure/closet/crate/freezer/open(mob/living/user, force = FALSE)
toggle_organ_decay(src)
..()
return TRUE
/obj/structure/closet/crate/freezer/close()
..()
/obj/structure/closet/crate/freezer/after_close(mob/living/user)
. = ..()
toggle_organ_decay(src)
/obj/structure/closet/crate/freezer/Destroy()
toggle_organ_decay(src)
return ..()
/obj/structure/closet/crate/freezer/Initialize(mapload)
. = ..()
toggle_organ_decay(src)
/obj/structure/closet/crate/freezer/blood
name = "blood freezer"
desc = "A freezer containing packs of blood."
@@ -255,23 +262,28 @@
desc = "A crate with a radiation sign on it."
name = "radiation crate"
icon_state = "radiation"
base_icon_state = "radiation"
/obj/structure/closet/crate/hydroponics
name = "hydroponics crate"
desc = "All you need to destroy those pesky weeds and pests."
icon_state = "hydrocrate"
base_icon_state = "hydrocrate"
/obj/structure/closet/crate/engineering
name = "engineering crate"
icon_state = "engi_crate"
base_icon_state = "engi_crate"
/obj/structure/closet/crate/engineering/electrical
icon_state = "engi_e_crate"
base_icon_state = "engi_e_crate"
/obj/structure/closet/crate/rcd
desc = "A crate for the storage of an RCD."
name = "\improper RCD crate"
icon_state = "engi_crate"
base_icon_state = "engi_crate"
/obj/structure/closet/crate/rcd/PopulateContents()
..()
@@ -283,10 +295,12 @@
name = "science crate"
desc = "A science crate."
icon_state = "scicrate"
base_icon_state = "scicrate"
/obj/structure/closet/crate/solarpanel_small
name = "budget solar panel crate"
icon_state = "engi_e_crate"
base_icon_state = "engi_e_crate"
/obj/structure/closet/crate/solarpanel_small/PopulateContents()
..()
@@ -319,6 +333,7 @@
/obj/structure/closet/crate/decorations
icon_state = "engi_crate"
base_icon_state = "engi_crate"
/obj/structure/closet/crate/decorations/PopulateContents()
. = ..()
@@ -2,12 +2,14 @@
desc = "A trash bin, place your trash here for the janitor to collect."
name = "trash bin"
icon_state = "largebins"
base_icon_state = "largebins"
open_sound = 'sound/effects/bin_open.ogg'
close_sound = 'sound/effects/bin_close.ogg'
anchored = TRUE
horizontal = FALSE
delivery_icon = null
can_install_electronics = FALSE
paint_jobs = null
/obj/structure/closet/crate/bin/Initialize(mapload)
. = ..()
@@ -5,17 +5,21 @@
material_drop = /obj/item/stack/sheet/cardboard
material_drop_amount = 4
icon_state = "cardboard"
base_icon_state = "cardboard"
open_sound = 'sound/items/poster_ripped.ogg'
close_sound = 'sound/machines/cardboard_box.ogg'
open_sound_volume = 25
close_sound_volume = 25
paint_jobs = null
/obj/structure/closet/crate/cardboard/mothic
name = "\improper Mothic Fleet box"
desc = "For holding moths, presumably."
icon_state = "cardboard_moth"
base_icon_state = "cardboard_moth"
/obj/structure/closet/crate/cardboard/tiziran
name = "\improper Tiziran shipment box"
desc = "For holding lizards, presumably."
icon_state = "cardboard_tiziran"
base_icon_state = "cardboard_tiziran"
@@ -2,6 +2,7 @@
name = "critter crate"
desc = "A crate designed for safe transport of animals. It has an oxygen tank for safe transport in space."
icon_state = "crittercrate"
base_icon_state = "crittercrate"
horizontal = FALSE
allow_objects = FALSE
breakout_time = 600
@@ -13,9 +14,10 @@
open_sound_volume = 25
close_sound_volume = 50
contents_pressure_protection = 0.8
var/obj/item/tank/internals/emergency_oxygen/tank
can_install_electronics = FALSE
var/obj/item/tank/internals/emergency_oxygen/tank
/obj/structure/closet/crate/critter/Initialize(mapload)
. = ..()
tank = new
@@ -2,6 +2,7 @@
name = "large crate"
desc = "A hefty wooden crate. You'll need a crowbar to get it open."
icon_state = "largecrate"
base_icon_state = "largecrate"
density = TRUE
pass_flags_self = PASSSTRUCTURE
material_drop = /obj/item/stack/sheet/mineral/wood
@@ -2,13 +2,15 @@
desc = "A secure crate."
name = "secure crate"
icon_state = "securecrate"
base_icon_state = "securecrate"
secure = TRUE
locked = TRUE
max_integrity = 500
armor_type = /datum/armor/crate_secure
var/tamperproof = 0
damage_deflection = 25
var/tamperproof = 0
/datum/armor/crate_secure
melee = 30
bullet = 50
@@ -39,32 +41,38 @@
desc = "A secure weapons crate."
name = "weapons crate"
icon_state = "weaponcrate"
base_icon_state = "weaponcrate"
/obj/structure/closet/crate/secure/plasma
desc = "A secure plasma crate."
name = "plasma crate"
icon_state = "plasmacrate"
base_icon_state = "plasmacrate"
/obj/structure/closet/crate/secure/gear
desc = "A secure gear crate."
name = "gear crate"
icon_state = "secgearcrate"
base_icon_state = "secgearcrate"
/obj/structure/closet/crate/secure/hydroponics
desc = "A crate with a lock on it, painted in the scheme of the station's botanists."
name = "secure hydroponics crate"
icon_state = "hydrosecurecrate"
base_icon_state = "hydrosecurecrate"
/obj/structure/closet/crate/secure/freezer //for consistency with other "freezer" closets/crates
desc = "An insulated crate with a lock on it, used to secure perishables."
name = "secure kitchen crate"
icon_state = "kitchen_secure_crate"
base_icon_state = "kitchen_secure_crate"
paint_jobs = null
/obj/structure/closet/crate/secure/freezer/pizza
name = "secure pizza crate"
desc = "An insulated crate with a lock on it, used to secure pizza."
req_access = list(ACCESS_KITCHEN)
tamperproof = 10
req_access = list(ACCESS_KITCHEN)
/obj/structure/closet/crate/secure/freezer/pizza/PopulateContents()
. = ..()
@@ -74,16 +82,19 @@
desc = "A crate with a lock on it, painted in the scheme of the station's engineers."
name = "secure engineering crate"
icon_state = "engi_secure_crate"
base_icon_state = "engi_secure_crate"
/obj/structure/closet/crate/secure/science
name = "secure science crate"
desc = "A crate with a lock on it, painted in the scheme of the station's scientists."
icon_state = "scisecurecrate"
base_icon_state = "scisecurecrate"
/obj/structure/closet/crate/secure/owned
name = "private crate"
desc = "A crate cover designed to only open for who purchased its contents."
icon_state = "privatecrate"
base_icon_state = "privatecrate"
///Account of the person buying the crate if private purchasing.
var/datum/bank_account/buyer_account
///Department of the person buying the crate if buying via the NIRN app.
@@ -2,10 +2,12 @@
name = "surplus syndicrate"
desc = "A conspicuous crate with the Syndicate logo on it. You don't know how to open it."
icon_state = "syndicrate"
base_icon_state = "syndicrate"
max_integrity = 500
armor_type = /datum/armor/crate_syndicrate
resistance_flags = FIRE_PROOF | ACID_PROOF
integrity_failure = 0 //prevents bust_open from activating
paint_jobs = null
/// variable that only lets the crate open if opened by a key from the uplink
var/created_items = FALSE
/// this is what will spawn when it is opened with a syndicrate key
@@ -18,6 +20,17 @@
laser = 50
energy = 100
/obj/structure/closet/crate/syndicrate/before_open(mob/living/user, force)
. = ..()
if(!.)
return FALSE
if(!broken && !force && !created_items)
balloon_alert(user, "locked!")
return FALSE
return TRUE
/obj/structure/closet/crate/syndicrate/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1)
if(created_items)
return ..()
@@ -46,13 +59,6 @@
/obj/structure/closet/crate/syndicrate/attackby_secondary(obj/item/weapon, mob/user, params)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
///overwrites default opening behavior until it is unlocked via the syndicrate key
/obj/structure/closet/crate/syndicrate/can_open(mob/living/user, force = FALSE)
if(!created_items)
balloon_alert(user, "locked!")
return FALSE
return ..()
///syndicrate has a unique overlay for being unlocked
/obj/structure/closet/crate/syndicrate/closet_update_overlays(list/new_overlays)
. = new_overlays
@@ -70,7 +76,7 @@
. = ..()
register_item_context()
/obj/item/add_item_context(obj/item/source, list/context, atom/target, mob/living/user,)
/obj/item/syndicrate_key/add_item_context(obj/item/source, list/context, atom/target, mob/living/user)
. = ..()
var/obj/structure/closet/crate/syndicrate/target_structure = target
@@ -4,10 +4,12 @@
material_drop = /obj/item/stack/sheet/mineral/wood
material_drop_amount = 6
icon_state = "wooden"
base_icon_state = "wooden"
open_sound = 'sound/machines/wooden_closet_open.ogg'
close_sound = 'sound/machines/wooden_closet_close.ogg'
open_sound_volume = 25
close_sound_volume = 50
paint_jobs = null
/obj/structure/closet/crate/wooden/toy
name = "toy box"