Add crate shelves (#21493)

Ports https://github.com/shiptest-ss13/Shiptest/pull/2374
The warehous has been redesigned to incorporate new shelves that can
each carry up to 3 crates. The warehouse now spawns with the crates
already on the shelves and the related code has been updated to account
for this and still spawn things inside of them.
The back room in the warehouse also lost its camera, to give a chance
for antags and other shenanigans to occur back there.

<img width="595" height="971" alt="image"
src="https://github.com/user-attachments/assets/3cf8bd17-e91b-4d32-9ae8-5e461eceee42"
/>
<img width="282" height="556" alt="image"
src="https://github.com/user-attachments/assets/100f02b0-9165-4c35-a24b-f20d9bdda30e"
/>
This commit is contained in:
Casper3667
2025-11-03 08:09:08 +00:00
committed by GitHub
parent 1098d40e4a
commit ca66627f32
9 changed files with 459 additions and 226 deletions
@@ -168,8 +168,10 @@
/obj/structure/closet/proc/can_open()
if(welded || locked)
return 0
return 1
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))
@@ -743,6 +745,10 @@
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
@@ -134,6 +134,9 @@
if(!opened && tablestatus == UNDER_TABLE)
to_chat(user, SPAN_WARNING("You can't open \the [src] while the lid is obstructed!"))
return FALSE
if(istype(loc, /obj/structure/crate_shelf))
to_chat(user, SPAN_WARNING("You can't open \the [src] while it is on a shelf!"))
return FALSE
else
return ..()
@@ -155,12 +158,30 @@
//For putting on tables
/obj/structure/closet/crate/mouse_drop_dragged(atom/over, mob/user, src_location, over_location, params)
if (istype(over, /obj/structure/table))
if (istype(over, /obj/structure/table) && !istype(loc, /obj/structure/crate_shelf))
put_on_table(over, user)
return TRUE
else if(istype(over, /obj/structure/crate_shelf) && !istype(loc, /obj/structure/crate_shelf))
put_on_shelf(over, user)
return TRUE
if(istype(loc, /obj/structure/crate_shelf) && isturf(over) && !is_blocked_turf(over))
take_off_shelf(loc, user, over)
return TRUE
else
return ..()
/obj/structure/closet/crate/proc/put_on_shelf(var/obj/structure/crate_shelf/shelf, var/mob/user)
shelf.load(src, user)
/obj/structure/closet/crate/proc/take_off_shelf(var/obj/structure/crate_shelf/shelf, var/mob/user, turf/unload_turf)
shelf.unload(src, user, unload_turf)
/obj/structure/closet/crate/Adjacent(atom/neighbor, atom/target, atom/movable/mover)
. = ..()
if(istype(loc, /obj/structure/crate_shelf))
var/obj/structure/crate_shelf/S = loc
return S.Adjacent(neighbor, target, mover)
/obj/structure/closet/crate/proc/put_on_table(var/obj/structure/table/table, var/mob/user)
if (!table || !user || (tablestatus == UNDER_TABLE))
return
@@ -0,0 +1,164 @@
/obj/structure/crate_shelf
name = "crate shelf"
desc = "It's a shelf! For storing crates!"
icon = 'icons/obj/structure/crate_shelf.dmi'
icon_state = "shelf_base"
var/shelf_stack = "shelf_stack"
density = TRUE
anchored = TRUE
layer = BELOW_OBJ_LAYER
var/capacity = 3 // Default capacity of the shelf
var/use_delay = 1 SECONDS // Default interaction delay of the shelf
var/list/shelf_contents
var/vertical_offset = 10 // Vertical pixel offset of shelving-related things. Set to 10 by default due to this leaving more of the crate on-screen to be clicked.
var/manipulating = FALSE
var/obj/item/stack/dismantle_mat = /obj/item/stack/rods
build_amt = 10
// For testing only.
/obj/structure/crate_shelf/tall
capacity = 12
/obj/structure/crate_shelf/mechanics_hints()
. = list()
. += ..()
. += "Drag a crate on to the shelf, to put it on it."
. += "Drag a crate from the shelf to the ground to remove it."
/obj/structure/crate_shelf/Initialize(mapload)
. = ..()
LAZYSETLEN(shelf_contents, capacity)
update_icon()
return mapload ? INITIALIZE_HINT_LATELOAD : INITIALIZE_HINT_NORMAL
/obj/structure/crate_shelf/LateInitialize()
. = ..()
var/next_free
for(var/obj/I in loc)
if (!istype(I, /obj/structure/closet/crate))
continue
next_free = LAZYFIND(shelf_contents, null)
if(!next_free)
continue
put_in(I, next_free)
/obj/structure/crate_shelf/update_icon()
ClearOverlays()
var/stack_layer // This is used to generate the sprite layering of the shelf pieces.
var/stack_offset // This is used to generate the vertical offset of the shelf pieces.
for(var/i in 1 to (capacity - 1))
stack_layer = BELOW_OBJ_LAYER + (0.02 * i) - 0.01 // Make each shelf piece render above the last, but below the crate that should be on it.
stack_offset = vertical_offset * i // Make each shelf piece physically above the last.
var/image/I = image(icon, icon_state = shelf_stack, layer = stack_layer, pixel_y = stack_offset)
AddOverlays(I)
/obj/structure/crate_shelf/Destroy()
LAZYCLEARLIST(shelf_contents)
return ..()
/obj/structure/crate_shelf/attackby(obj/item/attacking_item, mob/user, params)
. = ..()
if(attacking_item.iswrench())
dismantle(attacking_item, user)
/obj/structure/crate_shelf/dismantle(obj/item/W, mob/user)
// if(!LAZYLEN(shelf_contents))
var/empty = TRUE
for(var/c in shelf_contents)
if(c != null)
empty = FALSE
if(!empty)
to_chat(user, SPAN_NOTICE("Remove any crates from \the [src] before dismantling it."))
return
if(manipulating)
return
manipulating = TRUE
user.visible_message("<b>[user]</b> begins dismantling \the [src].",
SPAN_NOTICE("You begin dismantling \the [src]."))
if(!W.use_tool(src, user, 2 SECONDS, volume = 50))
manipulating = FALSE
return
user.visible_message("\The [user] dismantles \the [src].",
SPAN_NOTICE("You dismantle \the [src]."))
new dismantle_mat(src.loc, 10)
qdel(src)
/obj/structure/crate_shelf/proc/relay_container_resist_act(mob/living/user, obj/structure/closet/crate)
to_chat(user, SPAN_NOTICE("You begin attempting to knock [crate] out of [src]."))
visible_message(SPAN_DANGER("\The [crate] begins to shake violently!"), SPAN_DANGER("You hear the sound of metal thrashing around nearby."), intent_message = THUNK_SOUND)
crate.shake_animation()
if(do_after(user, 20 SECONDS, target = crate))
if(!user || user.stat != CONSCIOUS || user.loc != crate || crate.loc != src)
return // If the user is in a strange condition, return early.
crate.shake_animation()
visible_message(SPAN_WARNING("\The [crate] falls off the [src]!"), SPAN_NOTICE("You hear a thud."), intent_message = THUNK_SOUND)
crate.forceMove(loc) // Drop the crate onto the shelf,
step_rand(crate, 1) // Then try to push it somewhere.
crate.layer = initial(crate.layer) // Reset the crate back to having the default layer, otherwise we might get strange interactions.
crate.pixel_y = initial(crate.pixel_y) // Reset the crate back to having no offset, otherwise it will be floating.
shelf_contents[shelf_contents.Find(crate)] = null // Remove the reference to the crate from the list.
handle_visuals()
/obj/structure/crate_shelf/proc/handle_visuals()
vis_contents = contents // It really do be that shrimple.
return
/obj/structure/crate_shelf/proc/load(obj/structure/closet/crate/crate, mob/user)
// Check if the shelf has an empty spot, notify the player if not.
var/next_free = LAZYFIND(shelf_contents, null)
if(!next_free)
to_chat(user, SPAN_NOTICE("The shelf is full."))
balloon_alert(user, "shelf full!")
return FALSE
visible_message("\The [user] starts to put \the [crate] on \the [src].")
if(do_after(user, use_delay, target = crate))
if(shelf_contents[next_free] != null)
// Something has been added to the shelf while we were waiting, abort!
visible_message("\The [user] stops putting \the [crate] on \the [src].")
to_chat(user, SPAN_NOTICE("Something else was added to the shelf first."))
return FALSE
if(crate.opened) // If the crate is open, try to close it.
if(!crate.close())
visible_message("\The [user] stops putting \the [crate] on \the [src].")
to_chat(user, SPAN_NOTICE("The crate couldn't be closed."))
return FALSE // If we fail to close it, don't load it into the shelf.
put_in(crate, next_free)
visible_message("\The [user] puts \the [crate] on \the [src].")
return TRUE
visible_message("\The [user] stops putting \the [crate] on \the [src].")
return FALSE // If the do_after() is interrupted, return FALSE!
/obj/structure/crate_shelf/proc/put_in(obj/structure/closet/crate/crate, var/next_free)
LAZYSET(shelf_contents, next_free, crate)
crate.forceMove(src) // Insert the crate into the shelf.
crate.pixel_y = vertical_offset * (next_free - 1) // Adjust the vertical offset of the crate to look like it's on the shelf.
crate.layer = BELOW_OBJ_LAYER + 0.02 * (next_free - 1) // Adjust the layer of the crate to look like it's in the shelf.
handle_visuals()
/obj/structure/crate_shelf/proc/unload(obj/structure/closet/crate/crate, mob/user, turf/unload_turf)
if(!unload_turf)
unload_turf = get_turf(user) // If a turf somehow isn't passed into the proc, put it at the user's feet.
if(!unload_turf.Enter(crate)) // If moving the crate from the shelf to the desired turf would bump, don't do it! Thanks Kapu1178 for the help here. - Generic DM
to_chat(user, SPAN_NOTICE("There is no room for the crate."))
return FALSE
visible_message("\The [user] starts unloading \the [crate] from \the [src].")
if(do_after(user, use_delay, target = crate))
if(!LAZYFIND(shelf_contents, crate))
visible_message("\The [user] stops unloading \the [crate] from \the [src].")
to_chat(user, SPAN_NOTICE("The crate can't be moved."))
return FALSE // If something has happened to the crate while we were waiting, abort!
crate.layer = initial(crate.layer) // Reset the crate back to having the default layer, otherwise we might get strange interactions.
crate.pixel_y = initial(crate.pixel_y) // Reset the crate back to having no offset, otherwise it will be floating.
crate.forceMove(unload_turf)
shelf_contents[shelf_contents.Find(crate)] = null // We do this instead of removing it from the list to preserve the order of the shelf.
handle_visuals()
visible_message("\The [user] unloads \the [crate] from \the [src].")
return TRUE
visible_message("\The [user] stops unloading \the [crate] from \the [src].")
return FALSE // If the do_after() is interrupted, return FALSE!