Files
BatrachophrenoandGitHub 8111a8489b Restore many missing inhand sprites for items, add/port others (#21831)
Many, many, many items have inhand sprites in their .dmis but for
whatever reasons do not display them in-game. This PR:

1. Updates many item definitions to point to their already-existing
inhands correctly. This consists largely of held tools, but also gas
tanks and jetpacks mounted in the suit storage slot.

2. Adds a few codersprites made by me for objects with either missing
inhands or poorly matching mishands (IE, the tape recorder, which has a
black case, reused the white inhand sprites of the health analyzer). The
new sprites are modified or recolored variations of other inhand sprites
from our repo, except for circuitboards which are new.
<img width="444" height="400" alt="image"
src="https://github.com/user-attachments/assets/7f107b9a-fe24-4e31-8f16-4d34768ee117"
/>


3. Adds inhand sprites for Inflatables and Inflatable Boxes made by
Tomixcomics.
<img width="424" height="101" alt="image"
src="https://github.com/user-attachments/assets/434107c4-8577-49a2-a58e-d6b014c03933"
/>

4. Ports inhand sprites for the Hydraulic Rescue Tool from tg's Jaws of
Life.
<img width="224" height="94" alt="Screenshot 2026-02-07 172931"
src="https://github.com/user-attachments/assets/070c7956-f6a8-4fb5-870f-10c64afcc8b3"
/>

5. Some additional cleanup while in the area. The 'analyzer' has been
renamed the 'gas analyzer' to be consistent with the other analyzer
objects, standardized icon_state naming conventions where I saw
oddballs, updated code docs to use DMDocs when in the area, etc.

### Asset Licenses
The following assets that **have not** been created by myself are
included in this PR:

| Path | Original Author | License |
| --- | --- | --- |
| icons/obj/item/hydraulic_rescue_tool.dmi | [SomeAngryMiner (bee
station)](https://github.com/BeeStation/BeeStation-Hornet/pull/2487),
[maxymax
(/tg/station)](https://github.com/tgstation/tgstation/pull/58616) |
CC-BY-SA |
| icons/obj/item/inflatables.dmi |
[Tomixcomics](https://github.com/tomixcomics) | CC-BY-SA |
2026-02-17 17:51:22 +00:00

866 lines
29 KiB
Plaintext

/obj/structure/closet
name = "closet"
desc = "It's a basic storage unit."
icon = 'icons/obj/containers/closet.dmi'
icon_state = "generic"
density = TRUE
build_amt = 2
slowdown = 5
pass_flags_self = PASSSTRUCTURE | LETPASSCLICKS | PASSTRACE
var/icon_door = null
/// Override to have open overlay use icon different to its base's
var/icon_door_override = FALSE
/// Handles secure locker overlays like the locking lights
var/icon_door_overlay = ""
/// Secure locker or not. typically it shouldn't need lights if it's insecure
var/secure = FALSE
/// Whether to display secure lights when open.
var/secure_lights = FALSE
var/opened = FALSE
var/welded = FALSE
var/locked = FALSE
var/broken = FALSE
/// If you can shove people in it.
var/large = TRUE
/// If it can be moved by people using the right tools. basically means if you can change the anchored var.
var/canbemoved = FALSE
/// If its screwed in place
var/screwed = TRUE
/// If its wrenched down
var/wrenched = TRUE
/// Never solid (You can always pass over it)
var/wall_mounted = FALSE
var/health = 100
/// If someone is currently breaking out. mutex
var/breakout = 0
/// 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/storage_capacity = 45 //Tying this to mob sizes was dumb
var/open_sound = 'sound/effects/closet_open.ogg'
var/close_sound = 'sound/effects/closet_close.ogg'
var/open_sound_volume = 35
var/close_sound_volume = 50
var/store_misc = TRUE
var/store_items = TRUE
var/store_mobs = TRUE
var/store_structure = FALSE
var/dense_when_open = FALSE
var/maximum_mob_size = 15
var/const/default_mob_size = 15
var/obj/item/closet_teleporter/linked_teleporter
var/double_doors = FALSE
/// The overlay for the closet's door
var/obj/effect/overlay/closet_door/door_obj
var/obj/effect/overlay/closet_door/door_obj_alt
var/is_animating_door = FALSE
/// Used if you want to have an overlay below the door. used for guncabinets.
var/door_underlay = FALSE
/// Multiplier on proc/get_door_transform. basically, how far you want this to swing out. value of 1 means the length of the door is unchanged (and will swing out of the tile), 0 means it will just slide back and forth.
var/door_anim_squish = 0.12 // DON'T TOUCH!!!
/// The maximum angle the door will be drawn at
var/door_anim_angle = 136 // DON'T TOUCH!!!
/// X position of the closet door hinge, relative to the center of the sprite
var/door_hinge_x = -6.5
/// For closets with two doors. why a seperate var? because some closets may be weirdly shaped or something.
var/door_hinge_x_alt = 6.5
/// Set to 0 to make the door not animate at all
var/door_anim_time = 2.5
/// Used by body bags and air bubbles.
var/contains_body = FALSE
/obj/structure/closet/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
. += "When closed, a <b>welder</b> could be used to weld the closet shut."
/obj/structure/closet/disassembly_hints(mob/user, distance, is_adjacent)
. += ..()
. += "When opened, a <b>welder</b> could be used to cut the closet back into steel sheets."
/obj/structure/closet/antagonist_hints(mob/user, distance, is_adjacent)
. += ..()
. += "Using a Closet Teleporter (Stealth & Camouflage uplink item) on this can turn it into a quick transportation method- just don't get caught!"
/obj/structure/closet/feedback_hints(mob/user, distance, is_adjacent)
. += ..()
if(!src.opened && isghost(user))
. += "It contains: [counting_english_list(contents)]"
if(distance <= 1 && !src.opened)
var/content_size = 0
for(var/obj/item/I in contents)
if(!I.anchored)
content_size += Ceiling(I.w_class/2)
if(!content_size)
. += "\The [src] is empty."
else if(storage_capacity > content_size*4)
. += "\The [src] is barely filled."
else if(storage_capacity > content_size*2)
. += "\The [src] is less than half full."
else if(storage_capacity > content_size)
. += "\The [src] still has some free space."
else
. += "\The [src] is full."
if(src.opened && linked_teleporter && is_adjacent)
. += SPAN_WARNING("There appears to be a device <b>screwed</b> onto the interior backplate of \the [src]...")
/obj/structure/closet/Initialize(mapload, var/no_fill)
. = ..()
update_icon()
if(!no_fill)
fill()
if(secure)
verbs += /obj/structure/closet/proc/verb_togglelock
return mapload ? INITIALIZE_HINT_LATELOAD : INITIALIZE_HINT_NORMAL
/obj/structure/closet/LateInitialize()
if(opened) // if closed, any item at the crate's loc is put in the contents
return
var/obj/I
for(I in loc)
if (!istype(I, /obj/item) && !istype(I, /obj/random))
continue
if (I.density || I.anchored || I == src)
continue
I.forceMove(src)
// adjust locker size to hold all items with 5 units of free store room
var/content_size = 0
for(I in contents)
content_size += Ceiling(I.w_class/2)
if(content_size > storage_capacity-5)
storage_capacity = content_size + 5
/obj/structure/closet/Destroy()
QDEL_NULL(linked_teleporter)
QDEL_NULL(door_obj)
QDEL_NULL(door_obj_alt)
. = ..()
/// Fill lockers with this.
/obj/structure/closet/proc/fill()
return
/obj/structure/closet/proc/stored_weight()
var/content_size = 0
for(var/obj/item/I in contents)
if(!I.anchored)
content_size += Ceiling(I.w_class/2)
return content_size
/obj/structure/closet/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(air_group || (height==0 || wall_mounted))
return TRUE
return ..()
/obj/structure/closet/proc/can_open()
if(welded || locked)
return FALSE
if(istype(loc, /obj/structure/crate_shelf))
return FALSE
return TRUE
/obj/structure/closet/proc/can_close()
for(var/obj/structure/closet/closet in get_turf(src))
if(closet != src)
return 1
return 1
/obj/structure/closet/dump_contents()
//Cham Projector Exception
for(var/obj/effect/dummy/chameleon/AD in contents)
AD.forceMove(loc)
for(var/obj/I in contents - linked_teleporter)
I.forceMove(loc)
for(var/mob/M in contents)
M.forceMove(loc)
if(M.client)
M.client.eye = M.client.mob
M.client.perspective = MOB_PERSPECTIVE
M.set_fullscreen(FALSE, "closet_impaired", /atom/movable/screen/fullscreen/closet_impaired)
/obj/structure/closet/proc/open()
if(opened)
return 0
if(!can_open())
return 0
if(climbable)
structure_shaken()
opened = TRUE
dump_contents()
animate_door(FALSE)
if(double_doors)
animate_door_alt(FALSE)
update_icon()
playsound(loc, open_sound, open_sound_volume, 0, -3)
if(!dense_when_open)
density = FALSE
return 1
/obj/structure/closet/proc/close()
if(!opened)
return 0
if(!can_close())
return 0
var/stored_units = 0
if(store_misc)
stored_units += store_misc(stored_units)
if(store_items)
stored_units += store_items(stored_units)
if(store_mobs)
stored_units += store_mobs(stored_units)
if(store_structure)
stored_units += store_structure(stored_units)
opened = FALSE
animate_door(TRUE)
if(double_doors)
animate_door_alt(TRUE)
update_icon()
if(linked_teleporter && linked_teleporter.last_use + 600 < world.time)
var/did_teleport = FALSE
for(var/mob/M in contents)
if(linked_teleporter.do_teleport(M))
did_teleport = TRUE
if(did_teleport)
linked_teleporter.last_use = world.time
playsound(get_turf(src), close_sound, close_sound_volume, 0, -3)
density = initial(density)
return TRUE
//Chem Projector Exception
/obj/structure/closet/proc/store_misc(var/stored_units)
var/added_units = 0
for(var/obj/effect/dummy/chameleon/AD in loc)
if((stored_units + added_units) > storage_capacity)
break
AD.forceMove(src)
added_units++
return added_units
/obj/structure/closet/proc/store_items(var/stored_units)
var/added_units = 0
for(var/obj/item/I in loc)
var/item_size = Ceiling(I.w_class / 2)
if(stored_units + added_units + item_size > storage_capacity)
continue
if(!I.anchored)
I.forceMove(src)
added_units += item_size
return added_units
/obj/structure/closet/proc/store_mobs(var/stored_units, var/mob_limit)
var/added_units = 0
for(var/mob/living/M in loc)
if(M.buckled_to || M.pinned.len)
continue
if(M.mob_size >= maximum_mob_size)
continue
if(stored_units + added_units + M.mob_size > storage_capacity)
break
if(M.client)
M.client.perspective = EYE_PERSPECTIVE
M.client.eye = src
M.set_fullscreen(TRUE, "closet_impaired", /atom/movable/screen/fullscreen/closet_impaired)
M.forceMove(src)
added_units += M.mob_size
if(mob_limit) //We only want to store one valid mob
break
return added_units
/obj/structure/closet/proc/store_structure(var/stored_units)
var/added_units = 0
for(var/obj/O in loc)
if((stored_units + added_units) > storage_capacity)
break
if(O.density || O.anchored || istype(O,/obj/structure/closet))
continue
if(istype(O, /obj/structure/bed))
var/obj/structure/bed/B = O
if(B.buckled)
continue
O.forceMove(src)
added_units++
return added_units
/obj/structure/closet/proc/toggle(mob/user as mob)
if(!(opened ? close(user) : open(user)))
to_chat(user, SPAN_WARNING("It won't budge!"))
return
return 1
/obj/structure/closet/ex_act(severity)
switch(severity)
if(1)
health -= rand(120, 240)
if(2)
health -= rand(60, 120)
if(3)
health -= rand(30, 60)
if(health <= 0)
for (var/atom/movable/A as mob|obj in src)
A.ex_act(severity + 1)
dump_contents()
new /obj/item/stack/material/steel(get_turf(src))
qdel(src)
/obj/structure/closet/proc/damage(var/damage)
health -= damage
if(health <= 0)
dump_contents()
new /obj/item/stack/material/steel(get_turf(src))
qdel(src)
/obj/structure/closet/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return .
var/proj_damage = hitting_projectile.get_structure_damage()
if(!proj_damage)
return BULLET_ACT_BLOCK
if(hitting_projectile.penetrating || istype(hitting_projectile, /obj/projectile/bullet))
for(var/mob/living/L in contents)
hitting_projectile.Impact(L)
damage(proj_damage)
/obj/structure/closet/attackby(obj/item/attacking_item, mob/user)
if(istype(attacking_item, /obj/item/closet_teleporter))
if(linked_teleporter)
to_chat(user, SPAN_WARNING("\The [src] already has a linked teleporter!"))
return
var/obj/item/closet_teleporter/CT = attacking_item
user.visible_message(SPAN_NOTICE("\The [user] starts attaching \the [CT] to \the [src]..."), SPAN_NOTICE("You begin attaching \the [CT] to \the [src]..."), range = 3)
if(do_after(user, 30, src, DO_REPAIR_CONSTRUCT))
user.visible_message(SPAN_NOTICE("\The [user] attaches \the [CT] to \the [src]."), SPAN_NOTICE("You attach \the [CT] to \the [src]."), range = 3)
linked_teleporter = CT
CT.attached_closet = src
user.drop_from_inventory(CT, src)
return
if(opened)
if(istype(attacking_item, /obj/item/grab))
var/obj/item/grab/G = attacking_item
mouse_drop_receive(G.affecting, user) //act like they were dragged onto the closet
return 0
if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER) // Moved here so you can only detach linked teleporters when the door is open. So you can like unscrew and bolt the locker normally in most circumstances.
if(linked_teleporter)
user.visible_message(SPAN_NOTICE("\The [user] starts detaching \the [linked_teleporter] from \the [src]..."), SPAN_NOTICE("You begin detaching \the [linked_teleporter] from \the [src]..."), range = 3)
if(do_after(user, 30, src, DO_REPAIR_CONSTRUCT))
user.visible_message(SPAN_NOTICE("\The [user] detaches \the [linked_teleporter] from \the [src]."), SPAN_NOTICE("You detach \the [linked_teleporter] from \the [src]."), range = 3)
linked_teleporter.attached_closet = null
user.put_in_hands(linked_teleporter)
linked_teleporter = null
return
if(attacking_item.tool_behaviour == TOOL_WELDER)
var/obj/item/weldingtool/WT = attacking_item
if(WT.isOn())
user.visible_message(
SPAN_WARNING("[user] begins cutting [src] apart."),
SPAN_NOTICE("You begin cutting [src] apart."),
"You hear a welding torch on metal."
)
playsound(loc, 'sound/items/welder_pry.ogg', 50, 1)
if (!do_after(user, 2 SECONDS, src, DO_REPAIR_CONSTRUCT))
return
if(!WT.use(0,user))
to_chat(user, SPAN_NOTICE("You need more welding fuel to complete this task."))
return
else
user.visible_message(
SPAN_NOTICE("[src] has been cut apart by [user] with [WT]."),
SPAN_NOTICE("You cut apart [src] with [WT].")
)
if(linked_teleporter)
linked_teleporter.forceMove(get_turf(src))
linked_teleporter = null
dismantle()
return
if(istype(attacking_item, /obj/item/storage/laundry_basket) && attacking_item.contents.len)
var/obj/item/storage/laundry_basket/LB = attacking_item
var/turf/T = get_turf(src)
for(var/obj/item/I in LB.contents)
LB.remove_from_storage(I, T)
user.visible_message(
SPAN_NOTICE("[user] empties \the [LB] into \the [src]."),
SPAN_NOTICE("You empty \the [LB] into \the [src]."),
SPAN_NOTICE("You hear rustling of clothes.")
)
return
if(!attacking_item.dropsafety())
return
if(attacking_item)
user.drop_from_inventory(attacking_item,loc)
else
user.drop_item()
else if(istype(attacking_item, /obj/item/cratescanner))
var/obj/item/cratescanner/Cscanner = attacking_item
if(locked)
to_chat(user, SPAN_WARNING("[attacking_item] refuses to scan \the [src]. Unlock it first!"))
return
if(welded)
to_chat(user, SPAN_WARNING("[attacking_item] detects that \the [src] is welded shut, and refuses to scan."))
return
if(istype(loc, /obj/structure/crate_shelf))
to_chat(user, SPAN_WARNING("[attacking_item] can't scan \the [src] while it is on \the [loc]."))
return
Cscanner.print_contents(name, contents, src.loc)
else if(istype(attacking_item, /obj/item/stack/packageWrap))
return
else if(istype(attacking_item, /obj/item/ducttape))
return
else if(attacking_item.tool_behaviour == TOOL_WELDER)
var/obj/item/weldingtool/WT = attacking_item
if(WT.isOn())
user.visible_message(
SPAN_WARNING("[user] begins welding [src] [welded ? "open" : "shut"]."),
SPAN_NOTICE("You begin welding [src] [welded ? "open" : "shut"]."),
"You hear a welding torch on metal."
)
playsound(loc, 'sound/items/welder_pry.ogg', 50, 1)
if(!attacking_item.use_tool(src, user, 20, volume = 50, extra_checks = CALLBACK(src, PROC_REF(is_closed))))
return
if(!WT.use(0,user))
to_chat(user, SPAN_NOTICE("You need more welding fuel to complete this task."))
return
welded = !welded
update_icon()
user.visible_message(
SPAN_WARNING("[src] has been [welded ? "welded shut" : "unwelded"] by [user]."),
SPAN_NOTICE("You weld [src] [!welded ? "open" : "shut"].")
)
else
attack_hand(user)
else if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER && canbemoved)
if(screwed)
to_chat(user, SPAN_NOTICE("You start to unscrew \the [src] from the floor..."))
attacking_item.play_tool_sound(get_turf(src), 50)
if (do_after(user, 10/attacking_item.toolspeed SECONDS, src, DO_REPAIR_CONSTRUCT))
to_chat(user, SPAN_NOTICE("You unscrew the locker!"))
attacking_item.play_tool_sound(get_turf(src), 50)
screwed = FALSE
else if(!screwed && wrenched)
to_chat(user, SPAN_NOTICE("You start to screw the \the [src] to the floor..."))
playsound(src, 'sound/items/Welder.ogg', 80, 1)
if (do_after(user, 15/attacking_item.toolspeed SECONDS, src, DO_REPAIR_CONSTRUCT))
to_chat(user, SPAN_NOTICE("You screw \the [src]!"))
attacking_item.play_tool_sound(get_turf(src), 50)
screwed = TRUE
else if(attacking_item.tool_behaviour == TOOL_WRENCH && canbemoved)
if(wrenched && !screwed)
to_chat(user, SPAN_NOTICE("You start to unfasten the bolts holding \the [src] in place..."))
attacking_item.play_tool_sound(get_turf(src), 50)
if (do_after(user, 15/attacking_item.toolspeed SECONDS, src, DO_REPAIR_CONSTRUCT))
to_chat(user, SPAN_NOTICE("You unfasten \the [src]'s bolts!"))
attacking_item.play_tool_sound(get_turf(src), 50)
wrenched = FALSE
anchored = FALSE
else if(!wrenched)
to_chat(user, SPAN_NOTICE("You start to fasten the bolts holding the locker in place..."))
attacking_item.play_tool_sound(get_turf(src), 50)
if (do_after(user, 15/attacking_item.toolspeed SECONDS, src, DO_REPAIR_CONSTRUCT))
to_chat(user, SPAN_NOTICE("You fasten the \the [src]'s bolts!"))
attacking_item.play_tool_sound(get_turf(src), 50)
wrenched = TRUE
anchored = TRUE
else if(istype(attacking_item, /obj/item/hand_labeler))
var/obj/item/hand_labeler/HL = attacking_item
if(HL.mode == 1)
return
else
attack_hand(user)
else if(istype(attacking_item,/obj/item/card/id) && secure)
togglelock(user)
// Secure locker cutting open stuff.
else if(!opened && secure)
if(!broken && istype(attacking_item,/obj/item/material/twohanded/chainsaw))
var/obj/item/material/twohanded/chainsaw/ChainSawVar = attacking_item
ChainSawVar.cutting = 1
user.visible_message(\
SPAN_DANGER("[user.name] starts cutting \the [src] with the [attacking_item]!"),\
SPAN_WARNING("You start cutting the [src]..."),\
SPAN_NOTICE("You hear a loud buzzing sound and metal grinding on metal...")\
)
if(do_after(user, ChainSawVar.opendelay SECONDS, user, DO_REPAIR_CONSTRUCT))
user.visible_message(\
SPAN_WARNING("[user.name] finishes cutting open \the [src] with the [attacking_item]."),\
SPAN_WARNING("You finish cutting open the [src]."),\
SPAN_NOTICE("You hear a metal clank and some sparks.")\
)
emag_act(INFINITY, user, SPAN_DANGER("\The [src] has been sliced open by [user] with \an [attacking_item]!"), SPAN_DANGER("You hear metal being sliced and sparks flying."))
ChainSawVar.cutting = 0
else if(istype(attacking_item, /obj/item/melee/energy/blade))//Attempt to cut open locker if locked
if(emag_act(INFINITY, user, SPAN_DANGER("\The [src] has been sliced open by [user] with \an [attacking_item]!"), SPAN_DANGER("You hear metal being sliced and sparks flying.")))
playsound(loc, 'sound/weapons/blade.ogg', 50, 1)
else
attack_hand(user)
else
attack_hand(user)
return
// helper procs for callbacks
/obj/structure/closet/proc/is_closed()
SHOULD_NOT_SLEEP(TRUE)
SHOULD_BE_PURE(TRUE)
. = !opened
/obj/structure/closet/proc/is_open()
SHOULD_NOT_SLEEP(TRUE)
SHOULD_BE_PURE(TRUE)
. = opened
/obj/structure/closet/mouse_drop_receive(atom/dropped, mob/user, params)
var/atom/movable/screen/O = dropped
if(istype(O, /atom/movable/screen)) //fix for HUD elements making their way into the world -Pete
return
if(O.loc == user)
return
if(user.restrained() || user.stat || user.weakened || user.stunned || user.paralysis)
return
if((!( istype(O, /atom/movable) ) || O.anchored || !Adjacent(user) || !Adjacent(O) || !user.Adjacent(O) || user.contents.Find(src)))
return
if(!isturf(user.loc)) // are you in a container/closet/pod/etc?
return
if(!opened)
return
if(istype(O, /obj/structure/closet))
return
if(user != O)
var/turf/T = get_turf(src)
if(ismob(O))
if(large)
user.visible_message(SPAN_DANGER("<b>[user]</b> stuffs \the [O] into \the [src]!"), SPAN_NOTICE("You stuff \the [O] into \the [src]."), range = 3)
O.forceMove(T)
close()
else
to_chat(user, SPAN_NOTICE("\The [src] is too small to stuff [O] into!"))
else
O.forceMove(T)
add_fingerprint(user)
return
/obj/structure/closet/attack_ai(mob/user)
if(istype(user, /mob/living/silicon/robot) && Adjacent(user)) // Robots can open/close it, but not the AI.
attack_hand(user)
/obj/structure/closet/relaymove(mob/living/user, direction)
. = ..()
if(user.stat || !isturf(loc) || user.loc != src || open())
return
to_chat(user, SPAN_NOTICE("It won't budge!"))
/obj/structure/closet/attack_hand(mob/user as mob)
add_fingerprint(user)
if(locked)
togglelock(user)
else
toggle(user)
/obj/structure/closet/verb/verb_toggleopen()
set src in oview(1)
set category = "Object"
set name = "Toggle Open"
if(!usr.canmove || usr.stat || usr.restrained())
return
if(ishuman(usr) || (issilicon(usr) && Adjacent(usr)))
add_fingerprint(usr)
toggle(usr)
else
to_chat(usr, SPAN_WARNING("This mob type can't use this verb."))
/obj/structure/closet/update_icon()
if(!door_underlay)
ClearOverlays()
if(!opened)
layer = OBJ_LAYER
if(welded)
AddOverlays("[icon_door_overlay]welded")
if(!is_animating_door)
if(icon_door)
AddOverlays("[icon_door]_door")
if(double_doors)
AddOverlays("[icon_door]_door_alt")
if(!icon_door)
AddOverlays("[icon_state]_door")
if(double_doors)
AddOverlays("[icon_state]_door_alt")
if(secure)
update_secure_overlays()
if(secure && secure_lights)
update_secure_overlays()
else if(opened)
layer = BELOW_OBJ_LAYER
if(!is_animating_door)
AddOverlays("[icon_door_override ? icon_door : icon_state]_open")
if(secure && secure_lights)
update_secure_overlays()
/obj/structure/closet/proc/update_secure_overlays()
AddOverlays("[icon_door_overlay]securitypanel")
if(broken)
AddOverlays("[icon_door_overlay]emag")
else
if(locked)
AddOverlays("[icon_door_overlay]locked")
else
AddOverlays("[icon_door_overlay]unlocked")
/obj/structure/closet/proc/animate_door(var/closing = FALSE)
if(!door_anim_time)
return
if(!door_obj)
door_obj = new
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_door_override ? icon_door : icon_state]_back"
door_layer = FLOAT_LAYER
else
door_state = default_door_icon
door_layer = ABOVE_HUMAN_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)
/obj/structure/closet/proc/end_door_animation()
is_animating_door = FALSE // comment this out and the line below to manually tweak the animation end state by fiddling with the door_anim vars to match the open door icon
remove_vis_contents(door_obj)
update_icon()
UpdateOverlays(src)
/obj/structure/closet/proc/animate_door_alt(var/closing = FALSE)
if(!door_anim_time)
return
if(!door_obj_alt)
door_obj_alt = new
var/default_door_icon = "[icon_door || icon_state]_door_alt"
vis_contents |= door_obj_alt
door_obj_alt.icon = icon
door_obj_alt.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, TRUE)
var/door_state
var/door_layer
if (angle >= 90)
door_state = "[icon_door_override ? icon_door : icon_state]_back_alt"
door_layer = FLOAT_LAYER
else
door_state = default_door_icon
door_layer = ABOVE_HUMAN_LAYER
if(step == 0)
door_obj_alt.transform = door_transform
door_obj_alt.icon_state = door_state
door_obj_alt.layer = door_layer
else if(step == 1)
animate(door_obj_alt, 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_alt)), door_anim_time, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_CLIENT_TIME)
/obj/structure/closet/proc/end_door_animation_alt()
is_animating_door = FALSE // comment this out and the line below to manually tweak the animation end state by fiddling with the door_anim vars to match the open door icon
remove_vis_contents(door_obj_alt)
update_icon()
UpdateOverlays(src)
/obj/structure/closet/proc/get_door_transform(angle, var/inverse_hinge = FALSE)
var/matrix/door_matrix = matrix()
var/matrix_door_hinge = inverse_hinge ? door_hinge_x_alt : door_hinge_x
door_matrix.Translate(-matrix_door_hinge, 0)
door_matrix.Multiply(matrix(cos(angle), 0, 0, ((matrix_door_hinge >= 0) ? sin(angle) : -sin(angle)) * door_anim_squish, 1, 0)) // this matrix door hinge >= 0 check is for door hinges on the right, so they swing out instead of upwards
door_matrix.Translate(matrix_door_hinge, 0)
return door_matrix
/obj/structure/closet/hear_talk(mob/M as mob, text, verb, datum/language/speaking)
for (var/atom/A in src)
if(istype(A,/obj/))
var/obj/O = A
O.hear_talk(M, text, verb, speaking)
/obj/structure/closet/attack_generic(mob/user, damage, attack_message, environment_smash, armor_penetration, attack_flags, damage_type)
if(!damage || !environment_smash)
return
user.do_attack_animation(src)
visible_message(SPAN_DANGER("[user] [attack_message] \the [src]!"))
damage(damage)
return TRUE
/obj/structure/closet/proc/req_breakout()
if(!opened) //Door's open... wait, why are you in it's contents then?
if(locked)
if(welded)
return 2
else
return 1
if(breakout)
return -1 //Already breaking out.
else
return 0
/obj/structure/closet/proc/mob_breakout(var/mob/living/escapee)
if(istype(loc, /obj/structure/crate_shelf))
var/obj/structure/crate_shelf/shelf = loc
shelf.relay_container_resist_act(escapee, src)
return
//Improved by nanako
//Now it actually works, also locker breakout time stacks with locking and welding
//This means secure lockers are more useful for imprisoning people
var/breakout_time = 1.5 * req_breakout()//1.5 minutes if locked or welded, 3 minutes if both
if(breakout_time <= 0)
return
//okay, so the closet is either welded or locked... resist!!!
escapee.next_move = world.time + 100
escapee.last_special = world.time + 100
to_chat(escapee, SPAN_WARNING("You lean on the back of \the [src] and start pushing the door open. (This will take about [breakout_time] minutes.)"))
visible_message(SPAN_DANGER("\The [src] begins to shake violently!"), SPAN_DANGER("You hear the sound of metal thrashing around nearby."), intent_message = THUNK_SOUND)
var/time = 6 * breakout_time * 2
var/datum/progressbar/bar
if (escapee.client && escapee.client.prefs.toggles_secondary & PROGRESS_BARS)
bar = new(escapee, time, src)
breakout = 1
for(var/i in 1 to time) //minutes * 6 * 5seconds * 2
playsound(loc, 'sound/effects/grillehit.ogg', 100, 1)
shake_animation()
intent_message(THUNK_SOUND)
if (bar)
bar.update(i)
if(!do_after(escapee, 50, do_flags = DO_DEFAULT & ~DO_SHOW_PROGRESS)) //5 seconds
breakout = 0
qdel(bar)
return
if(!escapee || escapee.stat || escapee.loc != src)
breakout = 0
qdel(bar)
return //closet/user destroyed OR user dead/unconcious OR user no longer in closet OR closet opened
//Perform the same set of checks as above for weld and lock status to determine if there is even still a point in 'resisting'...
if(!req_breakout())
breakout = 0
qdel(bar)
return
//Well then break it!
breakout = 0
playsound(loc, 'sound/effects/grillehit.ogg', 100, 1)
break_open(escapee)
shake_animation()
qdel(bar)
/obj/structure/closet/proc/break_open(mob/user)
if(secure && !broken)
to_chat(user, SPAN_WARNING("You successfully break out!"))
welded = FALSE
emag_act(INFINITY, user, SPAN_DANGER("\The [user] successfully breaks out of \the [src]!"), SPAN_DANGER("You hear the sound of metal being forced apart!"))
//Do this to prevent contents from being opened into nullspace (read: bluespace)
if(istype(loc, /obj/structure/bigDelivery))
var/obj/structure/bigDelivery/BD = loc
BD.unwrap()
open()
/obj/structure/closet/onDropInto(var/atom/movable/AM)
return
/obj/structure/closet/crush_act()
for (var/atom/movable/A in src)
if(istype(A, /mob/living))
var/mob/living/M = A
M.gib()
else if(A.simulated)
A.ex_act(1)
if(linked_teleporter)
linked_teleporter.forceMove(get_turf(src))
linked_teleporter = null
dump_contents()
new /obj/item/stack/material/steel(get_turf(src))
qdel(src)
/*
==========================
Contents Scanner
==========================
*/
/obj/item/cratescanner
name = "crate contents scanner"
desc = "A handheld device used to scan and print a manifest of a container's contents. Does not work on locked crates, for privacy reasons."
icon = 'icons/obj/item/scanner.dmi'
icon_state = "crate_scanner"
item_state = "crate_scanner"
matter = list(DEFAULT_WALL_MATERIAL = 250, MATERIAL_GLASS = 140)
w_class = WEIGHT_CLASS_SMALL
obj_flags = OBJ_FLAG_CONDUCTABLE
slot_flags = SLOT_BELT
/obj/item/cratescanner/proc/print_contents(targetname, targetcontents, targetloc)
var/output = list()
var/list/outputstring
for(var/atom/item in targetcontents)
if(item.name in output)
output[item.name] = output[item.name] + 1
else
output[item.name] = 1
for(var/entry in output)
outputstring += "- [entry]: [output[entry]]x<br>"
var/obj/item/paper/printout = new /obj/item/paper(targetloc)
printout.info = "contents of [targetname]<br>"
printout.info += "[outputstring]"