Skip to content

Commit

Permalink
Crate shelf (#2374)
Browse files Browse the repository at this point in the history
<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->

## About The Pull Request
This PR intends to add a new method of organizing and storing items in a
way that allows ships to keep more stuff off the floor.

![githubcrate](https://github.com/shiptest-ss13/Shiptest/assets/34109002/4301cb94-907d-40fa-b84a-014b17ae1f29)

Todo:

- [x] Add shelves
- [x] Add the ability for shelves to hold crate
- [x] Add the ability for crates to be removed from shelves
- [x] Arbitrarily tall shelves?
- [x] Add a way to craft them ingame
- [x] Add a way to disassemble them ingame
- [x] Add a check on unloading to ensure they can't be stacked forever
(Kapu's turf.Enter() changes may be ported for this)
- [ ] Fix (or mitigate) the layering problems
- [x] Bring the code up to consistent standards

<!-- Describe The Pull Request. Please be sure every change is
documented or this can delay review and even discourage maintainers from
merging your PR! -->

## Why It's Good For The Game
I believe that adding more options for organization is a good thing.
Specifically, this will ideally make players more likely to sort items
into crates, rather than just dumping everything in.
I built this with balance in mind, so once finished, there should be no
way to abuse this to get infinitely dense storage, or anything like
that.

<!-- Please add a short description of why you think these changes would
benefit the game. If you can't justify it in words, it might not be
worth adding. -->

## Changelog

:cl: Generic DM, PositiveEntropy
add: Shelves! For crates!
fix: Racks can no longer be stacked infinitely.
/:cl:

<!-- Both :cl:'s are required for the changelog to work! You can put
your name to the right of the first :cl: if you want to overwrite your
GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the
icon ingame) and delete the unneeded ones. Despite some of the tags,
changelogs should generally represent how a player might be affected by
the changes rather than a summary of the PR's contents. -->

---------

Signed-off-by: GenericDM <[email protected]>
Co-authored-by: Imaginos16 <[email protected]>
Co-authored-by: Mark Suckerberg <[email protected]>
  • Loading branch information
3 people authored Oct 26, 2023
1 parent ef330ef commit 126e46e
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 6 deletions.
1 change: 1 addition & 0 deletions code/game/objects/items/stacks/sheets/sheet_types.dm
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \
)),
null, \
new/datum/stack_recipe("rack parts", /obj/item/rack_parts), \
new/datum/stack_recipe("crate shelf parts", /obj/item/rack_parts/shelf), \
new/datum/stack_recipe_list("closets", list(
new/datum/stack_recipe("closet", /obj/structure/closet, 2, time = 15, one_per_turf = TRUE, on_floor = TRUE),
new/datum/stack_recipe("emergency closet", /obj/structure/closet/emcloset/empty, 2, time = 15, one_per_turf = TRUE, on_floor = TRUE),
Expand Down
22 changes: 19 additions & 3 deletions code/game/objects/structures/crates_lockers/crates.dm
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,27 @@
. += "manifest"

/obj/structure/closet/crate/attack_hand(mob/user)
. = ..()
if(.)
return
if(istype(src.loc, /obj/structure/crate_shelf))
return FALSE // No opening crates in shelves!!
if(manifest)
tear_manifest(user)
return ..()

/obj/structure/closet/crate/MouseDrop(atom/drop_atom, src_location, over_location)
. = ..()
var/mob/living/user = usr
if(!isliving(user))
return // Ghosts busted.
if(!isturf(user.loc) || user.incapacitated() || user.body_position == LYING_DOWN)
return // If the user is in a weird state, don't bother trying.
if(get_dist(drop_atom, src) != 1 || get_dist(drop_atom, user) != 1)
return // Check whether the crate is exactly 1 tile from the shelf and the user.
if(istype(drop_atom, /turf/open) && istype(loc, /obj/structure/crate_shelf) && user.Adjacent(drop_atom))
var/obj/structure/crate_shelf/shelf = loc
return shelf.unload(src, user, drop_atom) // If we're being dropped onto a turf, and we're inside of a crate shelf, unload.
if(istype(drop_atom, /obj/structure/crate_shelf) && isturf(loc) && user.Adjacent(src))
var/obj/structure/crate_shelf/shelf = drop_atom
return shelf.load(src, user) // If we're being dropped onto a crate shelf, and we're in a turf, load.

/obj/structure/closet/crate/open(mob/living/user, force = FALSE)
. = ..()
Expand Down
139 changes: 139 additions & 0 deletions code/game/objects/structures/crateshelf.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#define DEFAULT_SHELF_CAPACITY 3 // Default capacity of the shelf
#define DEFAULT_SHELF_USE_DELAY 1 SECONDS // Default interaction delay of the shelf
#define DEFAULT_SHELF_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.

/obj/structure/crate_shelf
name = "crate shelf"
desc = "It's a shelf! For storing crates!"
icon = 'icons/obj/objects.dmi'
icon_state = "shelf_base"
density = TRUE
anchored = TRUE
max_integrity = 50 // Not hard to break

var/capacity = DEFAULT_SHELF_CAPACITY
var/use_delay = DEFAULT_SHELF_USE_DELAY
var/list/shelf_contents

/obj/structure/crate_shelf/tall
capacity = 12

/obj/structure/crate_shelf/Initialize()
. = ..()
shelf_contents = new/list(capacity) // Initialize our shelf's contents list, this will be used later.
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 = DEFAULT_SHELF_VERTICAL_OFFSET * i // Make each shelf piece physically above the last.
overlays += image(icon = 'icons/obj/objects.dmi', icon_state = "shelf_stack", layer = stack_layer, pixel_y = stack_offset)
return

/obj/structure/crate_shelf/Destroy()
QDEL_LIST(shelf_contents)
return ..()

/obj/structure/crate_shelf/examine(mob/user)
. = ..()
. += "<span class='notice'>There are some <b>bolts</b> holding [src] together.</span>"
if(shelf_contents.Find(null)) // If there's an empty space in the shelf, let the examiner know.
. += "<span class='notice'>You could <b>drag</b> a crate into [src]."
if(contents.len) // If there are any crates in the shelf, let the examiner know.
. += "<span class='notice'>You could <b>drag</b> a crate out of [src]."
. += "<span class='notice'>[src] contains:</span>"
for(var/obj/structure/closet/crate/crate in shelf_contents)
. += " [icon2html(crate, user)] [crate]"

/obj/structure/crate_shelf/attackby(obj/item/item, mob/living/user, params)
if (item.tool_behaviour == TOOL_WRENCH && !(flags_1&NODECONSTRUCT_1))
item.play_tool_sound(src)
if(do_after(user, 3 SECONDS, target = src))
deconstruct(TRUE)
return TRUE
return ..()

/obj/structure/crate_shelf/relay_container_resist_act(mob/living/user, obj/structure/closet/crate)
to_chat(user, "<span class='notice'>You begin attempting to knock [crate] out of [src].</span>")
if(do_after(user, 30 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.
visible_message("<span class='warning'>[crate] falls off of [src]!</span>",
"<span class='notice'>You manage to knock [crate] free of [src].</span>",
"<span class='notice>You hear a thud.</span>")
crate.forceMove(drop_location()) // 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)
var/next_free = shelf_contents.Find(null) // Find the first empty slot in the shelf.
if(!next_free) // If we don't find an empty slot, return early.
balloon_alert(user, "shelf full!")
return FALSE
if(do_after(user, use_delay, target = crate))
if(shelf_contents[next_free] != null)
return FALSE // Something has been added to the shelf while we were waiting, abort!
if(crate.opened) // If the crate is open, try to close it.
if(!crate.close())
return FALSE // If we fail to close it, don't load it into the shelf.
shelf_contents[next_free] = crate // Insert a reference to the crate into the free slot.
crate.forceMove(src) // Insert the crate into the shelf.
crate.pixel_y = DEFAULT_SHELF_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()
return TRUE
return FALSE // If the do_after() is interrupted, return FALSE!

/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, no_side_effects = TRUE)) // 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
unload_turf.balloon_alert(user, "no room!")
return FALSE
if(do_after(user, use_delay, target = crate))
if(!shelf_contents.Find(crate))
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()
return TRUE
return FALSE // If the do_after() is interrupted, return FALSE!

/obj/structure/crate_shelf/deconstruct(disassembled = TRUE)
var/turf/dump_turf = drop_location()
for(var/obj/structure/closet/crate/crate in shelf_contents)
crate.layer = initial(crate.layer) // Reset the crates back to default visual state
crate.pixel_y = initial(crate.pixel_y)
crate.forceMove(dump_turf)
step(crate, pick(GLOB.alldirs)) // Shuffle the crates around as though they've fallen down.
crate.SpinAnimation(rand(4,7), 1) // Spin the crates around a little as they fall. Randomness is applied so it doesn't look weird.
switch(pick(1, 1, 1, 1, 2, 2, 3)) // Randomly pick whether to do nothing, open the crate, or break it open.
if(1) // Believe it or not, this does nothing.
if(2) // Open the crate!
if(crate.open()) // Break some open, cause a little chaos.
crate.visible_message("<span class='warning'>[crate]'s lid falls open!</span>")
else // If we somehow fail to open the crate, just break it instead!
crate.visible_message("<span class='warning'>[crate] falls apart!")
crate.deconstruct()
if(3) // Break that crate!
crate.visible_message("<span class='warning'>[crate] falls apart!")
crate.deconstruct()
shelf_contents[shelf_contents.Find(crate)] = null
if(!(flags_1&NODECONSTRUCT_1))
density = FALSE
var/obj/item/rack_parts/shelf/newparts = new(loc)
transfer_fingerprints_to(newparts)
return ..()

/obj/item/rack_parts/shelf
name = "crate shelf parts"
desc = "Parts of a shelf."
construction_type = /obj/structure/crate_shelf
8 changes: 6 additions & 2 deletions code/game/objects/structures/tables_racks.dm
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,7 @@
flags_1 = CONDUCT_1
custom_materials = list(/datum/material/iron=2000)
var/building = FALSE
var/obj/construction_type = /obj/structure/rack

/obj/item/rack_parts/attackby(obj/item/W, mob/user, params)
if (W.tool_behaviour == TOOL_WRENCH)
Expand All @@ -744,14 +745,17 @@
. = ..()

/obj/item/rack_parts/attack_self(mob/user)
if(locate(construction_type) in get_turf(user))
balloon_alert(user, "no room!")
return
if(building)
return
building = TRUE
to_chat(user, "<span class='notice'>You start constructing a rack...</span>")
to_chat(user, "<span class='notice'>You start assembling [src]...</span>")
if(do_after(user, 50, target = user, progress=TRUE))
if(!user.temporarilyRemoveItemFromInventory(src))
return
var/obj/structure/rack/R = new /obj/structure/rack(user.loc)
var/obj/structure/R = new construction_type(user.loc)
user.visible_message("<span class='notice'>[user] assembles \a [R].\
</span>", "<span class='notice'>You assemble \a [R].</span>")
R.add_fingerprint(user)
Expand Down
5 changes: 4 additions & 1 deletion code/game/turfs/turf.dm
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,8 @@ GLOBAL_LIST_EMPTY(created_baseturf_lists)
return FALSE

//There's a lot of QDELETED() calls here if someone can figure out how to optimize this but not runtime when something gets deleted by a Bump/CanPass/Cross call, lemme know or go ahead and fix this mess - kevinz000
/turf/Enter(atom/movable/mover, atom/oldloc)
// Test if a movable can enter this turf. Send no_side_effects = TRUE to prevent bumping.
/turf/Enter(atom/movable/mover, atom/oldloc, no_side_effects = FALSE)
// Do not call ..()
// Byond's default turf/Enter() doesn't have the behaviour we want with Bump()
// By default byond will call Bump() on the first dense object in contents
Expand All @@ -356,6 +357,8 @@ GLOBAL_LIST_EMPTY(created_baseturf_lists)
if(thing == mover || thing == mover.loc) // Multi tile objects and moving out of other objects
continue
if(!thing.Cross(mover))
if(no_side_effects)
return FALSE
if(QDELETED(mover)) //Mover deleted from Cross/CanPass, do not proceed.
return FALSE
if((mover.movement_type & PHASING))
Expand Down
Binary file modified icons/obj/objects.dmi
Binary file not shown.
1 change: 1 addition & 0 deletions shiptest.dme
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,7 @@
#include "code\game\objects\structures\barsigns.dm"
#include "code\game\objects\structures\bedsheet_bin.dm"
#include "code\game\objects\structures\catwalk.dm"
#include "code\game\objects\structures\crateshelf.dm"
#include "code\game\objects\structures\curtains.dm"
#include "code\game\objects\structures\destructible_structures.dm"
#include "code\game\objects\structures\displaycase.dm"
Expand Down

0 comments on commit 126e46e

Please sign in to comment.