mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-08-18 10:31:41 +01:00
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 |
866 lines
29 KiB
Plaintext
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]"
|