Files
Alexis 21b4095dfd [MDB IGNORE] [IDB IGNORE] Upstream Sync - 04/17/2026 (#5453)
Upstream 04/17/2026

fixes https://github.com/Bubberstation/Bubberstation/issues/5549

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com>
Co-authored-by: rageguy505 <54517726+rageguy505@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com>
Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
Co-authored-by: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com>
Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
Co-authored-by: Maxipat <108554989+Maxipat112@users.noreply.github.com>
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com>
Co-authored-by: loganuk <fakeemail123@aol.com>
Co-authored-by: Leland Kemble <70413276+lelandkemble@users.noreply.github.com>
Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com>
Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
Co-authored-by: Lucy <lucy@absolucy.moe>
Co-authored-by: siliconOpossum <138069572+siliconOpossum@users.noreply.github.com>
Co-authored-by: Isratosh <Isratosh@hotmail.com>
Co-authored-by: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com>
Co-authored-by: Neocloudy <88008002+Neocloudy@users.noreply.github.com>
Co-authored-by: Alexander V. <volas@ya.ru>
Co-authored-by: ElGitificador <168473461+ElGitificador@users.noreply.github.com>
Co-authored-by: Twaticus <46540570+Twaticus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
Co-authored-by: Tim <timothymtorres@gmail.com>
Co-authored-by: Iamgoofball <iamgoofball@gmail.com>
Co-authored-by: Layzu666 <121319428+Layzu666@users.noreply.github.com>
Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com>
Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com>
Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com>
Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
Co-authored-by: John F. Kennedy <54908920+MacaroniCritter@users.noreply.github.com>
Co-authored-by: Cursor <102828457+theselfish@users.noreply.github.com>
Co-authored-by: Josh <josh.adam.powell@gmail.com>
Co-authored-by: Josh Powell <josh.powell@softwire.com>
Co-authored-by: Yobrocharlie <Charliemiller5617@gmail.com>
Co-authored-by: Hardly3D <66234359+Hardly3D@users.noreply.github.com>
Co-authored-by: shayoki <96078776+shayoki@users.noreply.github.com>
Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
2026-05-16 00:56:00 +02:00

1311 lines
44 KiB
Plaintext

#define LOCKER_FULL -1
///A comprehensive list of all closets (NOT CRATES) in the game world
GLOBAL_LIST_EMPTY(roundstart_station_closets)
/obj/structure/closet
name = "closet"
desc = "It's a basic storage unit."
icon = 'icons/obj/storage/closet.dmi'
icon_state = "generic"
density = TRUE
drag_slowdown = 1.5 // Same as a prone mob
max_integrity = 200
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
pass_flags_self = PASSSTRUCTURE | LETPASSCLICKS
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 2)
/// The overlay for the closet's door
var/obj/effect/overlay/closet_door/door_obj
/// Whether or not this door is being animated
var/is_animating_door = FALSE
/// Vertical squish of the door
var/door_anim_squish = 0.2
/// The maximum angle the door will be drawn at
var/door_anim_angle = 140
/// X position of the closet door hinge, relative to the center of the sprite
var/door_hinge_x = -6.5
/// Amount of time it takes for the door animation to play
var/door_anim_time = 2 // 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/opened = FALSE
var/welded = FALSE
var/locked = FALSE
var/large = TRUE
var/wall_mounted = 0 //never solid (You can always pass over it)
var/breakout_time = 1200
var/message_cooldown
var/can_weld_shut = TRUE
var/horizontal = FALSE
var/allow_objects = FALSE
var/allow_dense = FALSE
var/dense_when_open = FALSE //if it's dense when open or not
var/max_mob_size = MOB_SIZE_HUMAN //Biggest mob_size accepted by the container
var/mob_storage_capacity = 3 // how many human sized mob/living can fit together inside a closet.
var/storage_capacity = 30 //This is so that someone can't pack hundreds of items in a locker/crate then open it in a populated area to crash clients.
var/cutting_tool = /obj/item/weldingtool
var/open_sound = 'sound/machines/closet/closet_open.ogg'
var/close_sound = 'sound/machines/closet/closet_close.ogg'
var/lock_sound = 'sound/machines/closet/closet_lock.ogg'
var/unlock_sound = 'sound/machines/closet/closet_unlock.ogg'
var/open_sound_volume = 35
var/close_sound_volume = 50
var/material_drop = /obj/item/stack/sheet/iron
var/material_drop_amount = 2
var/delivery_icon = "deliverycloset" //which icon to use when packagewrapped. null to be unwrappable.
var/anchorable = TRUE
var/icon_welded = "welded"
var/icon_broken = "sparking"
/// 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
/// 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/is_maploaded = FALSE
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 further 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
/// Whether this closet is sealed or not. If sealed, it'll have its own internal air
var/sealed = FALSE
/// Internal gas for this closet.
var/datum/gas_mixture/internal_air
/// Volume of the internal air
var/air_volume = TANK_STANDARD_VOLUME * 3
/// How many pixels the closet can shift on the x axis when shaking
var/x_shake_pixel_shift = 2
/// how many pixels the closet can shift on the y axes when shaking
var/y_shake_pixel_shift = 1
///Creates see through image for clients to see while inside closet
VAR_PRIVATE/datum/closet_see_inside/closet_see_inside
/datum/armor/structure_closet
melee = 20
bullet = 10
laser = 10
bomb = 10
fire = 70
acid = 60
/obj/structure/closet/get_save_vars()
. = ..()
. += NAMEOF(src, welded)
. += NAMEOF(src, opened)
. += NAMEOF(src, locked)
. += NAMEOF(src, anchorable)
return .
/obj/structure/closet/Initialize(mapload)
. = ..()
var/static/list/closet_paint_jobs
if(isnull(closet_paint_jobs))
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
var/static/list/card_reader_choices
if(isnull(card_reader_choices))
card_reader_choices = list(
"Personal",
"Job",
"None"
)
if(access_choices)
access_choices = card_reader_choices
if(is_station_level(z) && mapload)
add_to_roundstart_list()
closet_see_inside = new(src)
obj_flags |= UNIQUE_RENAME
// if closed, any item at the crate's loc is put in the contents
if (mapload)
is_maploaded = TRUE
. = INITIALIZE_HINT_LATELOAD
populate_contents_immediate()
var/static/list/loc_connections = list(
COMSIG_LIVING_DISARM_COLLIDE = PROC_REF(locker_living),
COMSIG_ATOM_MAGICALLY_UNLOCKED = PROC_REF(on_magic_unlock),
)
AddElement(/datum/element/connect_loc, loc_connections)
register_context()
if(opened)
opened = FALSE //necessary because open() proc will early return if its true
if(open(special_effects = FALSE)) //closets which are meant to be open by default don't need to be animated open
return
update_appearance()
/obj/structure/closet/LateInitialize()
if(is_maploaded)
// very rare chance that a maintenance closet gets linked to another closet at roundstart
// 0.1% chance -> metastation has ~36 maintenance closets -> P(links>=1) = 3.54% per round -> about once every 30 rounds
if(prob(0.1) && istype(get_area(src), /area/station/maintenance))
var/list/targets = GLOB.roundstart_station_closets.Copy() - src
var/list/picked = list(src)
for(var/i in 1 to pick(1, 2))
picked += pick_n_take(targets)
GLOB.closet_teleport_controller.create_new_link(picked, subtle = TRUE)
if(!opened)
open(force = TRUE, special_effects = FALSE)
if(!opened)
take_contents()
if(sealed)
var/datum/gas_mixture/external_air = loc.return_air()
if(external_air && is_maploaded)
internal_air = external_air.copy()
else
internal_air = new()
START_PROCESSING(SSobj, src)
/obj/structure/closet/return_air()
if(sealed)
return internal_air
else
return ..()
//USE THIS TO FILL IT, NOT INITIALIZE OR NEW
/obj/structure/closet/proc/PopulateContents()
return
/// Populate the closet with stuff that needs to be added before it is opened.
/// This is useful for things like traitor objectives.
/obj/structure/closet/proc/populate_contents_immediate()
return
/obj/structure/closet/Destroy()
id_card = null
QDEL_NULL(internal_air)
QDEL_NULL(door_obj)
QDEL_NULL(closet_see_inside)
GLOB.roundstart_station_closets -= src
return ..()
/obj/structure/closet/process(seconds_per_tick)
if(!sealed)
return PROCESS_KILL
process_internal_air(seconds_per_tick)
/obj/structure/closet/proc/process_internal_air(seconds_per_tick)
if(opened)
var/datum/gas_mixture/current_exposed_air = loc.return_air()
if(!current_exposed_air)
return
if(current_exposed_air.equalize(internal_air))
var/turf/location = get_turf(src)
location.air_update_turf()
/obj/structure/closet/update_appearance(updates=ALL)
. = ..()
if(opened || broken || !secure)
luminosity = 0
return
luminosity = 1
/obj/structure/closet/update_icon()
. = ..()
if(issupplypod(src))
return
layer = opened ? BELOW_OBJ_LAYER : OBJ_LAYER
/obj/structure/closet/update_overlays()
. = ..()
closet_update_overlays(.)
/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, "[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)
var/mutable_appearance/door_overlay = mutable_appearance(icon, "[icon_door || overlay_state]_door", alpha = src.alpha)
. += door_overlay
if(opened)
return
if(welded)
. += icon_welded
if(broken && secure)
. += mutable_appearance(icon, icon_broken, alpha = alpha)
. += emissive_appearance(icon, icon_broken, src, alpha = alpha)
return
if(broken || !secure)
return
//Overlay is similar enough for both that we can use the same mask for both
. += emissive_appearance(icon, "locked", src, alpha = src.alpha)
. += locked ? "locked" : "unlocked"
/obj/structure/closet/vv_edit_var(vname, vval)
if(vname == NAMEOF(src, opened))
if(vval == opened)
return FALSE
if(vval && !opened && open(force = TRUE))
datum_flags |= DF_VAR_EDITED
return TRUE
else if(!vval && opened && close())
datum_flags |= DF_VAR_EDITED
return TRUE
return FALSE
. = ..()
if(vname == NAMEOF(src, welded) && welded && !can_weld_shut)
can_weld_shut = TRUE
else if(vname == NAMEOF(src, can_weld_shut) && !can_weld_shut && welded)
welded = FALSE
update_appearance()
if(vname in list(NAMEOF(src, locked), NAMEOF(src, welded), NAMEOF(src, secure), NAMEOF(src, icon_welded), NAMEOF(src, delivery_icon)))
update_appearance()
// Clone of closet items
/obj/effect/appearance_clone/closet_item
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/// Animates the closet door opening and closing
/obj/structure/closet/proc/animate_door(closing = FALSE)
if(!door_anim_time)
return
if(!door_obj)
door_obj = new
if(closing && length(contents))
var/icon/mask_icon = icon(icon, has_closed_overlay ? "[icon_door || base_icon_state || initial(icon_state)]_door" : icon_state)
// When closing, all of our contents are already in src - they've already disappeared from the world
// So to make the animation less jarring, we create a clone of everything in the closet to show in the animation
for(var/content in src)
var/obj/effect/appearance_clone/closet_item/clone = new(loc, content)
// Mask keeps in in bounds of the inside of the closet
clone.add_filter("closet_mask", 1, alpha_mask_filter(x = -1 * (clone.pixel_x + clone.pixel_w), y = -1 * (clone.pixel_y + clone.pixel_z), icon = mask_icon))
clone.layer = FLOAT_LAYER - 1
vis_contents += clone
var/default_door_icon = "[icon_door || icon_state]_door"
vis_contents += door_obj
door_obj.icon = icon
door_obj.icon_state = default_door_icon
is_animating_door = TRUE
var/num_steps = door_anim_time / world.tick_lag
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
if (angle >= 90)
door_state = "[icon_state]_back"
door_layer = FLOAT_LAYER
else
door_state = default_door_icon
door_layer = ABOVE_MOB_LAYER
if(step == 0)
door_obj.transform = door_transform
door_obj.icon_state = door_state
door_obj.layer = door_layer
else if(step == 1)
animate(door_obj, transform = door_transform, icon_state = door_state, layer = door_layer, time = world.tick_lag, flags = ANIMATION_END_NOW)
else
animate(transform = door_transform, icon_state = door_state, layer = door_layer, time = world.tick_lag)
addtimer(CALLBACK(src, PROC_REF(end_door_animation)), door_anim_time, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_CLIENT_TIME)
/// Ends the door animation and removes the animated overlay
/obj/structure/closet/proc/end_door_animation()
is_animating_door = FALSE
vis_contents -= door_obj
for(var/obj/effect/appearance_clone/closet_item/clone in vis_contents)
vis_contents -= clone
qdel(clone)
update_icon()
/// Calculates the matrix to be applied to the animated door overlay
/obj/structure/closet/proc/get_door_transform(angle)
var/matrix/door_matrix = matrix()
door_matrix.Translate(-door_hinge_x, 0)
door_matrix.Multiply(matrix(cos(angle), 0, 0, -sin(angle) * door_anim_squish, 1, 0))
door_matrix.Translate(door_hinge_x, 0)
return door_matrix
/obj/structure/closet/examine(mob/user)
. = ..()
if(id_card)
. += span_notice("It can be [EXAMINE_HINT("marked")] with a pen.")
if(can_weld_shut && !welded)
. += span_notice("It can be [EXAMINE_HINT("welded")] shut.")
if(welded)
. += span_notice("It's [EXAMINE_HINT("welded")] shut.")
if(anchorable && !anchored)
. += span_notice("It can be [EXAMINE_HINT("bolted")] to the ground.")
if(anchored)
. += span_notice("It's [anchorable ? EXAMINE_HINT("bolted") : "attached firmly"] to the ground.")
if(length(paint_jobs))
. += span_notice("It can be [EXAMINE_HINT("painted")] with 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 further 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
if(isnull(held_item))
if(!welded)
context[SCREENTIP_CONTEXT_LMB] = opened ? "Close" : "Open"
context[SCREENTIP_CONTEXT_RMB] = opened ? "Close" : "Open"
if(secure && !broken)
if (opened)
context[SCREENTIP_CONTEXT_RMB] = "Close"
else
context[SCREENTIP_CONTEXT_RMB] = !locked ? "Lock" : "Unlock"
if (locked)
context[SCREENTIP_CONTEXT_LMB] = "Unlock"
screentip_change = TRUE
if(secure && !opened && istype(held_item, /obj/item/card/id))
context[SCREENTIP_CONTEXT_LMB] = !locked ? "Lock" : "Unlock"
context[SCREENTIP_CONTEXT_RMB] = !locked ? "Lock" : "Unlock"
screentip_change = TRUE
if(istype(held_item, cutting_tool))
if(opened)
context[SCREENTIP_CONTEXT_LMB] = "Deconstruct"
screentip_change = TRUE
else
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 && anchorable)
context[SCREENTIP_CONTEXT_RMB] = anchored ? "Unanchor" : "Anchor"
screentip_change = TRUE
if(!locked && !opened && (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 && IS_WRITING_UTENSIL(held_item))
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)
. = ..()
if(wall_mounted)
return TRUE
/obj/structure/closet/proc/can_open(mob/living/user, force = FALSE)
if(force)
return TRUE
if(welded || locked)
return FALSE
if(isliving(user))
if(!(user.mobility_flags & MOBILITY_USE))
return FALSE
if(pulledby && user && HAS_TRAIT(src, TRAIT_STRONGPULL) && user != pulledby)
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)
if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density)
if(user)
to_chat(user, span_danger("There's something large on top of [src], preventing it from opening."))
return FALSE
return TRUE
/obj/structure/closet/proc/can_close(mob/living/user)
var/turf/T = get_turf(src)
for(var/obj/structure/closet/closet in T)
if(closet != src && !closet.wall_mounted)
if(user)
balloon_alert(user, "[closet.name] is in the way!")
return FALSE
for(var/mob/living/L in T)
if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density)
if(user)
to_chat(user, span_danger("There's something too large in [src], preventing it from closing."))
return FALSE
return TRUE
/obj/structure/closet/dump_contents()
if (!contents_initialized)
contents_initialized = TRUE
PopulateContents()
SEND_SIGNAL(src, COMSIG_CLOSET_CONTENTS_INITIALIZED)
var/atom/L = drop_location()
for(var/atom/movable/AM in src)
AM.forceMove(L)
if(throwing) // you keep some momentum when getting out of a thrown closet
step(AM, dir)
if(throwing)
throwing.finalize(FALSE)
/obj/structure/closet/proc/take_contents(mapload = FALSE)
var/atom/location = drop_location()
if(!location)
return
for(var/atom/movable/AM in location)
if(AM != src && insert(AM, mapload) == LOCKER_FULL) // limit reached
if(mapload) // Yea, it's a mapping issue. Blame mappers.
log_mapping("Closet storage capacity of [type] exceeded on mapload at [AREACOORD(src)]")
break
for(var/i in reverse_range(location.get_all_contents()))
var/atom/movable/thing = i
thing.atom_storage?.close_all()
///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(!before_open(user, force) || (SEND_SIGNAL(src, COMSIG_CLOSET_PRE_OPEN, user, force) & BLOCK_OPEN))
return FALSE
welded = FALSE
unlock()
if(special_effects)
playsound(loc, open_sound, open_sound_volume, TRUE, -3)
opened = TRUE
if(!dense_when_open)
set_density(FALSE)
dump_contents()
if(special_effects)
animate_door(FALSE)
update_appearance()
after_open(user, force)
SEND_SIGNAL(src, COMSIG_CLOSET_POST_OPEN, user, force)
return TRUE
///Proc to override for effects after opening a door
/obj/structure/closet/proc/after_open(mob/living/user, force = FALSE)
return
/obj/structure/closet/proc/insert(atom/movable/inserted, mapload = FALSE)
if(length(contents) >= storage_capacity)
if(!mapload)
return LOCKER_FULL
//For maploading, we only return LOCKER_FULL if the movable was otherwise insertable. This way we can avoid logging false flags.
return insertion_allowed(inserted) ? LOCKER_FULL : FALSE
if(!insertion_allowed(inserted))
return FALSE
if(SEND_SIGNAL(src, COMSIG_CLOSET_INSERT, inserted) & COMPONENT_CLOSET_INSERT_INTERRUPT)
return TRUE
inserted.forceMove(src)
return TRUE
/obj/structure/closet/proc/insertion_allowed(atom/movable/AM)
if(ismob(AM))
if(!isliving(AM)) //let's not put ghosts or eye mobs inside closets...
return FALSE
var/mob/living/L = AM
if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs())
return FALSE
if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items.
if(horizontal && L.density)
return FALSE
if(L.mob_size > max_mob_size)
return FALSE
var/mobs_stored = 0
for(var/mob/living/M in contents)
mobs_stored++
if(mobs_stored >= mob_storage_capacity)
return FALSE
L.stop_pulling()
else if(istype(AM, /obj/structure/closet))
return FALSE
else if(isobj(AM))
if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs() || ismecha(AM))
return FALSE
else if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP))
return TRUE
else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon))
return FALSE
else
return FALSE
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
set_density(TRUE)
animate_door(TRUE)
update_appearance()
after_close(user)
SEND_SIGNAL(src, COMSIG_CLOSET_POST_CLOSE, user)
return TRUE
///Proc to do effects after closet has closed
/obj/structure/closet/proc/after_close(mob/living/user)
return
/**
* Toggles a closet open or closed, to the opposite state. Does not respect locked or welded states, however.
*/
/obj/structure/closet/proc/toggle(mob/living/user)
if(opened)
return close(user)
else
return open(user)
/obj/structure/closet/handle_deconstruct(disassembled)
dump_contents()
if(obj_flags & NO_DEBRIS_AFTER_DECONSTRUCTION)
return
if(ispath(material_drop) && material_drop_amount)
new material_drop(loc, material_drop_amount)
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())
/obj/structure/closet/atom_break(damage_flag)
. = ..()
if(!broken)
bust_open()
/obj/structure/closet/on_craft_completion(list/components, datum/crafting_recipe/current_recipe, atom/crafter)
var/obj/item/electronics/airlock/access_control = locate() in components
if(QDELETED(access_control))
return ..()
inherit_airlock_electronics_access(access_control)
return ..()
/obj/structure/closet/proc/inherit_airlock_electronics_access(obj/item/electronics/airlock/access_control)
if (access_control.one_access)
req_one_access = access_control.accesses
req_access = null
else
req_access = access_control.accesses
req_one_access = null
/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, list/modifiers, list/attack_modifiers)
if(user in src)
return
if(src.tool_interact(W,user))
return 1 // No afterattack
else
return ..()
/obj/structure/closet/item_interaction_secondary(mob/living/user, obj/item/tool, list/modifiers)
if (attack_hand(user, modifiers))
return ITEM_INTERACT_SUCCESS
return NONE
/// 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 || opened)
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 || opened)
return FALSE
if(card_reader_installed)
balloon_alert(user, "attached to reader!")
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) || opened)
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 || opened)
return FALSE
if(locked)
balloon_alert(user, "unlock first!")
return FALSE
return TRUE
/// returns TRUE if attackBy call shouldn't be continued (because tool was used/closet was of wrong type), FALSE if otherwise
/obj/structure/closet/proc/tool_interact(obj/item/weapon, mob/living/user)
. = TRUE
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
inherit_airlock_electronics_access(weapon)
qdel(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 who swiped their id has access.
id_card = WEAKREF(id)
name = "[id.registered_name]'s locker"
desc += " It has been ID locked to [id.registered_name]."
if("Job") //anyone who has the same access permissions as this id has access. Does NOT apply to the whole department.
name = "[id.assignment]'s locker"
desc += " It has been access locked to [id.assignment]s."
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)
if(istype(weapon, cutting_tool))
if(weapon.tool_behaviour == TOOL_WELDER)
if(!weapon.tool_start_check(user, amount=1))
return
to_chat(user, span_notice("You begin cutting \the [src] apart..."))
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 [weapon]."),
span_hear("You hear welding."))
deconstruct(TRUE)
return
else // for example cardboard box is cut with wirecutters
user.visible_message(span_notice("[user] cut apart \the [src]."), \
span_notice("You cut \the [src] apart with \the [weapon]."))
deconstruct(TRUE)
return
if (user.combat_mode)
return
if(user.transfer_item_to_turf(weapon, drop_location())) // so we put in unlit welder too
return
else if(weapon.tool_behaviour == TOOL_WELDER && can_weld_shut)
if(!weapon.tool_start_check(user, amount=1))
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 [weapon]."),
span_hear("You hear welding."))
user.log_message("[welded ? "welded":"unwelded"] closet [src] with [weapon]", LOG_GAME)
update_appearance()
else if(!user.combat_mode || (weapon.item_flags & NOBLUDGEON))
var/item_is_id = weapon.GetID()
if(!item_is_id)
return FALSE
if((item_is_id || !toggle(user)) && !opened)
togglelock(user)
else
return FALSE
/obj/structure/closet/wrench_act_secondary(mob/living/user, obj/item/tool)
if(!anchorable)
balloon_alert(user, "no anchor bolts!")
return TRUE
if(isinspace() && !anchored) // We want to prevent anchoring a locker in space, but we should still be able to unanchor it there
balloon_alert(user, "nothing to anchor to!")
return TRUE
set_anchored(!anchored)
tool.play_tool_sound(src, 75)
user.balloon_alert_to_viewers("[anchored ? "anchored" : "unanchored"]")
return TRUE
/obj/structure/closet/proc/after_weld(weld_state)
return
/obj/structure/closet/mouse_drop_receive(atom/movable/O, mob/living/user, params)
if(!istype(O) || O.anchored || istype(O, /atom/movable/screen))
return
if(user == O) //try to climb onto it
return ..()
if(!opened)
return
if(!isturf(O.loc))
return
var/actuallyismob = 0
if(isliving(O))
actuallyismob = 1
else if(!isitem(O))
return
var/turf/T = get_turf(src)
add_fingerprint(user)
user.visible_message(span_warning("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src]."), \
span_warning("You [actuallyismob ? "try to ":""]stuff [O] into [src]."), \
span_hear("You hear clanging."))
if(actuallyismob)
if(do_after(user, 4 SECONDS, O))
user.visible_message(span_notice("[user] stuffs [O] into [src]."), \
span_notice("You stuff [O] into [src]."), \
span_hear("You hear a loud metal bang."))
var/mob/living/L = O
if(!issilicon(L))
L.Paralyze(40)
if(istype(src, /obj/structure/closet/supplypod/extractionpod))
O.forceMove(src)
else
O.forceMove(T)
close()
log_combat(user, O, "stuffed", addition = "inside of [src]")
else
O.forceMove(T)
/obj/structure/closet/relaymove(mob/living/user, direction)
if(user.stat || !isturf(loc))
return
if(locked)
if(message_cooldown <= world.time)
message_cooldown = world.time + 50
to_chat(user, span_warning("[src]'s door won't budge!"))
return
container_resist_act(user)
/obj/structure/closet/attack_hand(mob/living/user, list/modifiers)
. = ..()
if(.)
return
if(user.body_position == LYING_DOWN && get_dist(src, user) > 0)
return
if(toggle(user))
return TRUE
if(!opened)
return togglelock(user)
/obj/structure/closet/attack_paw(mob/user, list/modifiers)
return attack_hand(user, modifiers)
/obj/structure/closet/attack_robot(mob/user)
if(user.Adjacent(src))
return attack_hand(user)
/obj/structure/closet/attack_robot_secondary(mob/user, list/modifiers)
if(!user.Adjacent(src))
return SECONDARY_ATTACK_CONTINUE_CHAIN
togglelock(user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
// tk grab then use on self
/obj/structure/closet/attack_self_tk(mob/user)
if(attack_hand(user))
return ITEM_INTERACT_BLOCKING
/obj/structure/closet/verb/verb_toggleopen()
set name = "Toggle Open"
set src in view(1)
if(!usr.can_perform_action(src) || !isturf(loc))
return
if(iscarbon(usr) || issilicon(usr) || isdrone(usr))
return toggle(usr)
else
to_chat(usr, span_warning("This mob type can't use this verb."))
// Objects that try to exit a locker by stepping were doing so successfully,
// and due to an oversight in turf/Enter() were going through walls. That
// should be independently resolved, but this is also an interesting twist.
/obj/structure/closet/Exit(atom/movable/leaving, direction)
open()
if(leaving.loc == src)
return FALSE
return TRUE
/obj/structure/closet/container_resist_act(mob/living/user, loc_required = TRUE)
if(opened)
return
if(ismovable(loc))
user.changeNext_move(CLICK_CD_BREAKOUT)
user.last_special = world.time + CLICK_CD_BREAKOUT
var/atom/movable/movable_parent = loc
movable_parent.relay_container_resist_act(user, src)
return
if(!welded && !locked)
open(user)
return
if(DOING_INTERACTION_WITH_TARGET(user, src))
return
//okay, so the closet is either welded or locked... resist!!!
user.changeNext_move(CLICK_CD_BREAKOUT)
user.last_special = world.time + CLICK_CD_BREAKOUT
user.visible_message(span_warning("[src] begins to shake violently!"), \
span_notice("You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)"), \
span_hear("You hear banging from [src]."))
addtimer(CALLBACK(src, PROC_REF(check_if_shake)), 1 SECONDS)
if(do_after(user,(breakout_time), target = src))
if(!user || user.stat != CONSCIOUS || (loc_required && (user.loc != src)) || opened || (!locked && !welded) )
return
//we check after a while whether there is a point of resisting anymore and whether the user is capable of resisting
user.visible_message(span_danger("[user] successfully broke out of [src]!"),
span_notice("You successfully break out of [src]!"))
bust_open()
else
if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded.
to_chat(user, span_warning("You fail to break out of [src]!"))
/obj/structure/closet/relay_container_resist_act(mob/living/user, obj/container)
container_resist_act(user)
/// Check if someone is still resisting inside, and choose to either keep shaking or stop shaking the closet
/obj/structure/closet/proc/check_if_shake()
// Assuming we decide to shake again, how long until we check to shake again
var/next_check_time = 1 SECONDS
// How long we shake between different calls of Shake(), so that it starts shaking and stops, instead of a steady shake
var/shake_duration = 0.3 SECONDS
for(var/mob/living/mob in contents)
if(DOING_INTERACTION_WITH_TARGET(mob, src))
// Shake and queue another check_if_shake
Shake(x_shake_pixel_shift, y_shake_pixel_shift, shake_duration, shake_interval = 0.1 SECONDS)
addtimer(CALLBACK(src, PROC_REF(check_if_shake)), next_check_time)
return TRUE
// If we reach here, nobody is resisting, so don't shake
return FALSE
/obj/structure/closet/proc/bust_open()
SIGNAL_HANDLER
welded = FALSE //applies to all lockers
unlock() //applies to critter crates and secure lockers only
broken = TRUE //applies to secure lockers only
open(force = TRUE, special_effects = FALSE)
/obj/structure/closet/attack_hand_secondary(mob/user, modifiers)
. = ..()
if(!user.can_perform_action(src) || !isturf(loc))
return
if(!opened && secure)
togglelock(user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/**
* returns TRUE if the closet is allowed to unlock
* * user: the player trying to unlock this closet
* * player_id: the id of the player trying to unlock this closet
* * registered_id: the id registered to this closet, null if no one registered
*/
/obj/structure/closet/proc/can_unlock(mob/living/user, obj/item/card/id/player_id, obj/item/card/id/registered_id)
if(isnull(registered_id))
return allowed(user)
return player_id == registered_id
/obj/structure/closet/proc/togglelock(mob/living/user, silent)
if(!secure || broken)
return FALSE
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/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
return togglelock(user, silent)
if(!can_unlock(user, user.get_idcard(), registered_id))
error_msg = "not your locker!"
else if(!can_unlock(user, user.get_idcard()))
error_msg = "access denied!"
if(error_msg)
if(!silent)
balloon_alert(user, error_msg)
return TRUE
if(iscarbon(user))
add_fingerprint(user)
locked = !locked
play_closet_lock_sound()
user.visible_message(
span_notice("[user] [locked ? "locks" : "unlocks"] [src]."),
span_notice("You [locked ? "locked" : "unlocked"] [src]."),
)
update_appearance()
return TRUE
/// toggles the lock state of a closet
/obj/structure/closet/proc/lock()
if(locked)
return
locked = TRUE
play_closet_lock_sound()
update_appearance()
/// unlocks the closet
/obj/structure/closet/proc/unlock()
if(!locked)
return
locked = FALSE
play_closet_lock_sound()
update_appearance()
/// plays the closet's lock/unlock sound, this should be placed AFTER you've changed the lock state
/obj/structure/closet/proc/play_closet_lock_sound()
playsound(src, locked ? lock_sound : unlock_sound, 50, TRUE)
/obj/structure/closet/emag_act(mob/user, obj/item/card/emag/emag_card)
if(secure && !broken)
visible_message(span_warning("Sparks fly from [src]!"), blind_message = span_hear("You hear a faint electrical spark."))
balloon_alert(user, "lock broken open")
playsound(src, SFX_SPARKS, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
broken = TRUE
locked = FALSE
update_appearance()
return TRUE
return FALSE
/obj/structure/closet/get_remote_view_fullscreens(mob/user)
if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS)))
user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 1)
/obj/structure/closet/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
if (!(. & EMP_PROTECT_CONTENTS))
for(var/obj/O in src)
O.emp_act(severity)
if(secure && !broken && !(. & EMP_PROTECT_SELF))
if(prob(50 / severity))
locked = !locked
update_appearance()
if(prob(20 / severity) && !opened)
if(!locked)
open()
else
req_access = list()
req_access += pick(SSid_access.get_region_access_list(list(REGION_ALL_STATION)))
/obj/structure/closet/contents_explosion(severity, target)
switch(severity)
if(EXPLODE_DEVASTATE)
SSexplosions.high_mov_atom += contents
if(EXPLODE_HEAVY)
SSexplosions.med_mov_atom += contents
if(EXPLODE_LIGHT)
SSexplosions.low_mov_atom += contents
/obj/structure/closet/singularity_act()
dump_contents()
..()
/obj/structure/closet/AllowDrop()
return TRUE
/obj/structure/closet/return_temperature()
return
/obj/structure/closet/proc/locker_living(datum/source, mob/living/shover, mob/living/target, shove_flags, obj/item/weapon)
SIGNAL_HANDLER
if(!opened && (locked || welded)) //Yes this could be less code, no I don't care
return
if(!opened && ((shove_flags & SHOVE_KNOCKDOWN_BLOCKED) || !(shove_flags & SHOVE_BLOCKED)))
return
if (!toggle())
return
if(!opened) // Reversed because we should've toggled already
if (target.loc != loc)
return
target.forceMove(src)
else
target.Knockdown(SHOVE_KNOCKDOWN_SOLID)
update_icon()
target.visible_message(span_danger("[shover.name] shoves [target.name] into [src]!"),
span_userdanger("You're shoved into [src] by [shover.name]!"),
span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, shover)
to_chat(src, span_danger("You shove [target.name] into [src]!"))
log_combat(shover, target, "shoved", "into [src] (locker/crate)[weapon ? " with [weapon]" : ""]")
return COMSIG_LIVING_SHOVE_HANDLED
/// Signal proc for [COMSIG_ATOM_MAGICALLY_UNLOCKED]. Unlock and open up when we get knock casted.
/obj/structure/closet/proc/on_magic_unlock(datum/source, datum/action/cooldown/spell/aoe/knock/spell, atom/caster)
SIGNAL_HANDLER
INVOKE_ASYNC(src, PROC_REF(unlock))
INVOKE_ASYNC(src, PROC_REF(open))
/obj/structure/closet/preopen
opened = TRUE
///Adds the closet to a global list. Placed in its own proc so that crates may be excluded.
/obj/structure/closet/proc/add_to_roundstart_list()
GLOB.roundstart_station_closets += src
/obj/structure/closet/rename_checks(mob/living/user)
. = TRUE
if(locked)
src.balloon_alert(user, "unlock first!")
return FALSE
if(isnull(id_card) && secure)
src.balloon_alert(user, "not yours to rename!")
return FALSE
///Spears deal bonus damages to lockers
/obj/structure/closet/secure_closet/attacked_by(obj/item/attacking_item, mob/living/user, list/modifiers, list/attack_modifiers)
if(istype(attacking_item, /obj/item/spear))
HIDE_ATTACK_MESSAGES(attack_modifiers)
MODIFY_ATTACK_FORCE_MULTIPLIER(attack_modifiers, 2)
user.visible_message(
span_danger("[user] stabs with precision [src]'s electronics with [attacking_item]!"),
span_danger("You stab with precision [src]'s electronics with [attacking_item]!"),
null,
COMBAT_MESSAGE_RANGE,
)
return ..()
/obj/structure/closet/secure_closet/hitby(atom/movable/hit_by, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
if(!istype(hit_by, /obj/item/spear))
return ..()
// We have to manually tweak throwforce for now
var/obj/item/spear = hit_by
spear.throwforce *= 2
. = ..()
spear.throwforce /= 2
return .
#undef LOCKER_FULL