From 9a9e25910446c4c42b159e02c40986969ca86912 Mon Sep 17 00:00:00 2001
From: Casper3667 <8396443+Casper3667@users.noreply.github.com>
Date: Wed, 10 Sep 2025 21:05:19 +0200
Subject: [PATCH] Adds the parcel cart to operations. (#21288)
This adds a parcel cart to the mail room in operations, which can be
loaded with up to 17 packages. These packages can be any that can be
held in the hand, so not crates but anything smaller effectively yes as
long as it can be wrapped in package wrapper.
When it is loaded with 5 packages, it gives a slowdown to the person
pushing it, and further slowdown at 11 packages.
The cart can have a total of 17 packages at the moment, as that is the
amount of different sprites I have for it.
The code is based on the engineering cart, and also contain a small
bugfix so the engineering cart give back steel when taken apart, as that
is what is used to build it.
https://github.com/user-attachments/assets/b8613c9e-55e1-4f76-926a-60f4acd6269e
### Asset Licenses
The following assets that **have not** been created by myself are
included in this PR:
| Path | Original Author | License |
| --- | --- | --- |
| icons/obj/parcelcart.dmi | Tomixcomics (Aurora Station) | CC-BY |
---
aurorastation.dme | 6 +-
code/game/machinery/doors/door.dm | 4 +-
code/game/objects/structures/carts/carts.dm | 141 ++++++++++++++++
.../structures/{ => carts}/engicart.dm | 151 ++----------------
.../structures/{ => carts}/janicart.dm | 147 ++---------------
.../objects/structures/carts/parcelcart.dm | 123 ++++++++++++++
code/game/objects/structures/mop_bucket.dm | 4 +-
code/modules/cargo/items/custodial.dm | 2 +-
code/modules/cargo/items/engineering.dm | 2 +-
code/modules/cargo/items/operations.dm | 2 +-
code/modules/item_worth/worths_list.dm | 2 +-
code/modules/materials/material_recipes.dm | 5 +-
code/modules/mob/mob_movement.dm | 2 +-
.../file_system/programs/civilian/janitor.dm | 4 +-
html/changelogs/ParcelCart.yml | 7 +
icons/obj/parcelcart.dmi | Bin 0 -> 1296 bytes
.../abandoned_diner/abandoned_diner.dmm | 2 +-
.../abandoned_industrial_station.dmm | 4 +-
.../away_site/hivebot_hub/hivebot_hub.dmm | 2 +-
.../konyang/point_verdant/point_verdant.dmm | 4 +-
maps/away/away_site/magshield/magshield.dmm | 2 +-
.../solarian_prewar/sensor_relay.dmm | 2 +-
.../uueoaesa/reclamation/ihss_reclamation.dmm | 2 +-
...nviro_testing_facility_submaps_layouts.dmm | 6 +-
.../enviro_testing_facility_submaps_main.dmm | 2 +-
.../biesel/tcaf_corvette/tcaf_corvette.dmm | 2 +-
maps/away/ships/coc/coc_ranger/coc_ship.dmm | 2 +-
.../ships/coc/coc_surveyor/coc_surveyor.dmm | 2 +-
.../dominian_corvette/dominian_corvette.dmm | 2 +-
.../dominian_unathi_privateer.dmm | 2 +-
maps/away/ships/golden_deep/golden_deep.dmm | 2 +-
.../hegemony_corvette/hegemony_corvette.dmm | 2 +-
.../heph/heph_security/heph_security.dmm | 2 +-
maps/away/ships/himeo/himeo_patrol_ship.dmm | 2 +-
maps/away/ships/iac/iac_rescue_ship.dmm | 2 +-
maps/away/ships/idris/idris_cruiser.dmm | 2 +-
maps/away/ships/orion/orion_express_ship.dmm | 2 +-
.../ships/pra/headmaster/headmaster_ship.dmm | 2 +-
maps/away/ships/sol/sol_splf/splf_raider.dmm | 2 +-
maps/away/ships/sol/sol_ssrm/ssrm_ship.dmm | 2 +-
.../tirakqi_smuggler/tirakqi_smuggler.dmm | 2 +-
maps/away/ships/xanu/xanu_frigate.dmm | 2 +-
maps/away/ships/yacht/yacht.dmm | 2 +-
maps/event/generic_dock/generic_dock-1.dmm | 2 +-
maps/event/idris_cruise/idris_cruise-1.dmm | 4 +-
maps/event/rooftop/rooftop-1.dmm | 2 +-
.../exoplanets/moghes/moghes_bar.dmm | 2 +-
.../exoplanets/ouerea/ouerea_bar.dmm | 2 +-
maps/sccv_horizon/sccv_horizon.dmm | 15 +-
.../submaps/ops_warehouse_small_storage.dmm | 4 +-
50 files changed, 367 insertions(+), 328 deletions(-)
create mode 100644 code/game/objects/structures/carts/carts.dm
rename code/game/objects/structures/{ => carts}/engicart.dm (69%)
rename code/game/objects/structures/{ => carts}/janicart.dm (70%)
create mode 100644 code/game/objects/structures/carts/parcelcart.dm
create mode 100644 html/changelogs/ParcelCart.yml
create mode 100644 icons/obj/parcelcart.dmi
diff --git a/aurorastation.dme b/aurorastation.dme
index f535063139d..1b1bcbf5fe8 100644
--- a/aurorastation.dme
+++ b/aurorastation.dme
@@ -1429,7 +1429,6 @@
#include "code\game\objects\structures\displaycase.dm"
#include "code\game\objects\structures\door_assembly.dm"
#include "code\game\objects\structures\ECD.dm"
-#include "code\game\objects\structures\engicart.dm"
#include "code\game\objects\structures\extinguisher.dm"
#include "code\game\objects\structures\fireaxe_cabinet.dm"
#include "code\game\objects\structures\flags_banners.dm"
@@ -1440,7 +1439,6 @@
#include "code\game\objects\structures\hadii_statue.dm"
#include "code\game\objects\structures\hivebot_head.dm"
#include "code\game\objects\structures\inflatable.dm"
-#include "code\game\objects\structures\janicart.dm"
#include "code\game\objects\structures\kitchen_spike.dm"
#include "code\game\objects\structures\large.dm"
#include "code\game\objects\structures\lattice.dm"
@@ -1477,6 +1475,10 @@
#include "code\game\objects\structures\barricades\metal.dm"
#include "code\game\objects\structures\barricades\plasteel.dm"
#include "code\game\objects\structures\barricades\wood.dm"
+#include "code\game\objects\structures\carts\carts.dm"
+#include "code\game\objects\structures\carts\engicart.dm"
+#include "code\game\objects\structures\carts\janicart.dm"
+#include "code\game\objects\structures\carts\parcelcart.dm"
#include "code\game\objects\structures\crates_lockers\closets.dm"
#include "code\game\objects\structures\crates_lockers\crates.dm"
#include "code\game\objects\structures\crates_lockers\largecrate.dm"
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 38e6a429f1f..4624f885319 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -194,8 +194,8 @@
else
do_animate("deny")
return
- if(istype(bumped_atom, /obj/structure/janitorialcart) || istype(bumped_atom, /obj/structure/engineeringcart))
- var/obj/structure/janitorialcart/cart = bumped_atom
+ if(istype(bumped_atom, /obj/structure/cart))
+ var/obj/structure/cart/cart = bumped_atom
if(density)
if(cart.pulling && (src.allowed(cart.pulling)))
open()
diff --git a/code/game/objects/structures/carts/carts.dm b/code/game/objects/structures/carts/carts.dm
new file mode 100644
index 00000000000..18acf44e6bf
--- /dev/null
+++ b/code/game/objects/structures/carts/carts.dm
@@ -0,0 +1,141 @@
+ABSTRACT_TYPE(/obj/structure/cart)
+ name = "cart parent item"
+ desc = DESC_PARENT
+ icon_state = "cart"
+ anchored = FALSE
+ density = TRUE
+ climbable = TRUE
+ build_amt = 15
+ material = DEFAULT_WALL_MATERIAL
+ slowdown = 0
+ var/movesound = 'sound/effects/roll.ogg'
+ var/driving
+ var/mob/living/pulling
+
+/obj/structure/cart/mechanics_hints(mob/user, distance, is_adjacent)
+ . += ..()
+ . += "\
+ You can CTRL-Click to start dragging this cart. This object has a special dragging behaviour: when dragged, character's movement \
+ directs the cart and the character is subsequently pulled by it. \
+ "
+
+/obj/structure/cart/disassembly_hints(mob/user, distance, is_adjacent)
+ . += ..()
+ . += "An empty cart can be taken apart with a wrench or a welder. Or a plasma cutter, if you're that hardcore."
+
+/obj/structure/cart/proc/take_apart(var/mob/user = null, var/obj/I)
+ if(user)
+ if(iswelder(I))
+ var/obj/item/welder = I
+ welder.play_tool_sound(get_turf(src), 50)
+
+ user.visible_message("[user] starts taking apart the [src]...", SPAN_NOTICE("You start disassembling the [src]..."))
+ if (!do_after(user, 30, do_flags = DO_DEFAULT & ~DO_USER_SAME_HAND))
+ return
+
+ dismantle()
+
+/obj/structure/cart/ex_act(severity)
+ spill(100 / severity)
+ ..()
+
+/obj/structure/cart/proc/spill(var/chance = 100)
+
+/obj/structure/cart/proc/update_slowdown()
+
+/obj/structure/cart/relaymove(mob/living/user, direction)
+ . = ..()
+
+ if(user.stat || user.stunned || user.weakened || user.paralysis || user.lying || user.restrained())
+ if(user==pulling)
+ pulling = null
+ user.pulledby = null
+ to_chat(user, SPAN_WARNING("You lost your grip!"))
+ return
+ if(user.pulling && (user == pulling))
+ pulling = null
+ user.pulledby = null
+ return
+ if(pulling && (get_dist(src, pulling) > 1))
+ pulling = null
+ user.pulledby = null
+ if(user==pulling)
+ return
+ if(pulling && (get_dir(src.loc, pulling.loc) == direction))
+ to_chat(user, SPAN_WARNING("You cannot go there."))
+ return
+
+ driving = 1
+ var/turf/T = null
+ if(pulling)
+ T = pulling.loc
+ if(get_dist(src, pulling) >= 1)
+ step(pulling, get_dir(pulling.loc, src.loc))
+ step(src, direction)
+ set_dir(direction)
+ if(pulling)
+ if(pulling.loc == src.loc)
+ pulling.forceMove(T)
+ else
+ spawn(0)
+ if(get_dist(src, pulling) > 1)
+ pulling = null
+ user.pulledby = null
+ pulling.set_dir(get_dir(pulling, src))
+ driving = 0
+
+
+/obj/structure/cart/Move()
+ . = ..()
+ if (pulling && (get_dist(src, pulling) > 1))
+ pulling.pulledby = null
+ to_chat(pulling, SPAN_WARNING("You lost your grip!"))
+ pulling = null
+ if(has_gravity())
+ playsound(src, movesound, 50, 1)
+
+/obj/structure/cart/CtrlClick(var/mob/user)
+ if(in_range(src, user))
+ if(!ishuman(user)) return
+ if(!pulling)
+ pulling = user
+ user.pulledby = src
+ if(user.pulling)
+ user.stop_pulling()
+ user.set_dir(get_dir(user, src))
+ to_chat(user, SPAN_NOTICE("You grip \the [name]'s handles."))
+ else
+ to_chat(user, SPAN_NOTICE("You let go of \the [name]'s handles."))
+ pulling.pulledby = null
+ pulling = null
+ return
+
+/obj/structure/cart/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
+ if(air_group || (height==0))
+ return TRUE
+ if(mover?.movement_type & PHASING)
+ return TRUE
+ if(istype(mover) && mover.pass_flags & PASSTABLE)
+ return TRUE
+ if(istype(mover, /mob/living) && mover == pulling)
+ return TRUE
+ else
+ if(istype(mover, /obj/projectile))
+ return prob(20)
+ else
+ return !density
+
+ABSTRACT_TYPE(/obj/structure/cart/storage)
+ var/has_items = FALSE
+
+ /// Used for displaying and handling radial menu. Collective list of every single item this object contains.
+ var/list/storage_contents = list()
+
+/obj/structure/cart/storage/proc/handle_storing(var/attacking_item, var/mob/user, var/should_store, var/storage_is_full)
+
+/obj/structure/cart/storage/proc/get_storage_contents_list()
+
+/obj/structure/cart/storage/take_apart(var/mob/user = null, var/obj/I)
+ if(has_items)
+ spill()
+ . = ..()
diff --git a/code/game/objects/structures/engicart.dm b/code/game/objects/structures/carts/engicart.dm
similarity index 69%
rename from code/game/objects/structures/engicart.dm
rename to code/game/objects/structures/carts/engicart.dm
index 70b76778cc1..7a10a2e75bc 100644
--- a/code/game/objects/structures/engicart.dm
+++ b/code/game/objects/structures/carts/engicart.dm
@@ -1,27 +1,16 @@
-/obj/structure/engineeringcart
+/obj/structure/cart/storage/engineeringcart
name = "engineering cart"
desc = "A cart for your engineering-related storage needs."
icon = 'icons/obj/engicart.dmi'
icon_state = "cart"
- anchored = FALSE
- density = TRUE
- climbable = TRUE
- build_amt = 15
- material = DEFAULT_TABLE_REINF_MATERIAL
- slowdown = 0
- var/has_items = FALSE
var/list/my_glass = list()
var/list/my_metal = list()
var/list/my_plasteel = list()
- /// Used for displaying and handling radial menu. Collective list of every single item this object contains.
- var/list/storage_contents = list()
var/obj/item/device/lightreplacer/my_lightreplacer = null
var/obj/item/storage/toolbox/mechanical/my_blue_toolbox = null
var/obj/item/storage/toolbox/electrical/my_yellow_toolbox = null
var/obj/item/storage/toolbox/emergency/my_red_toolbox = null
- var/driving
- var/mob/living/pulling
/// Amount of stacks the cart is capable to store.
var/stack_capacity = 2
@@ -35,19 +24,11 @@
/obj/item/storage/toolbox/emergency
))
-/obj/structure/engineeringcart/mechanics_hints(mob/user, distance, is_adjacent)
+/obj/structure/cart/storage/engineeringcart/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
. += "You can use steel, plasteel, glass sheets, toolboxes and light replacers on the cart to store them."
- . += "\
- You can CTRL-Click to start dragging this cart. This object has a special dragging behaviour: when dragged, character's movement \
- directs the cart and the character is subsequently pulled by it. \
- "
-/obj/structure/engineeringcart/disassembly_hints(mob/user, distance, is_adjacent)
- . += ..()
- . += "An empty engineering cart can be taken apart with a wrench or a welder. Or a plasma cutter, if you're that hardcore."
-
-/obj/structure/engineeringcart/feedback_hints(mob/user, distance, is_adjacent)
+/obj/structure/cart/storage/engineeringcart/feedback_hints(mob/user, distance, is_adjacent)
. += ..()
if(distance <= 1)
if(locate(/obj/item/stack/material) in storage_contents)
@@ -75,7 +56,7 @@
else
. += "[icon2html(src, user)] There is no material in this cart!"
-/obj/structure/engineeringcart/proc/get_storage_contents_list()
+/obj/structure/cart/storage/engineeringcart/get_storage_contents_list()
storage_contents.Cut()
var/list/lists_to_check = list(
my_glass, my_metal, my_plasteel
@@ -90,7 +71,7 @@
if(O)
storage_contents += O
-/obj/structure/engineeringcart/Destroy()
+/obj/structure/cart/storage/engineeringcart/Destroy()
QDEL_NULL(my_glass)
QDEL_NULL(my_metal)
QDEL_NULL(my_plasteel)
@@ -100,7 +81,7 @@
QDEL_NULL(my_red_toolbox)
return ..()
-/obj/structure/engineeringcart/attackby(obj/item/attacking_item, mob/user)
+/obj/structure/cart/storage/engineeringcart/attackby(obj/item/attacking_item, mob/user)
if(is_type_in_typecache(attacking_item, allowed_types))
var/should_store = FALSE
var/storage_is_full = FALSE
@@ -171,28 +152,7 @@
return
..()
-// copied from janicart code
-/obj/structure/engineeringcart/proc/take_apart(var/mob/user = null, var/obj/I)
- if(has_items)
- spill()
-
- if(user)
-
- if(iswelder(I))
- var/obj/item/welder = I
- welder.play_tool_sound(get_turf(src), 50)
-
- user.visible_message("[user] starts taking apart the [src]...", SPAN_NOTICE("You start disassembling the [src]..."))
- if (!do_after(user, 30, do_flags = DO_DEFAULT & ~DO_USER_SAME_HAND))
- return
-
- dismantle()
-
-/obj/structure/engineeringcart/ex_act(severity)
- spill(100 / severity)
- ..()
-
-/obj/structure/engineeringcart/proc/spill(var/chance = 100)
+/obj/structure/cart/storage/engineeringcart/spill(var/chance = 100)
var/turf/dropspot = get_turf(src)
if(LAZYLEN(my_glass) && prob(chance))
var/obj/item/stack/material/glass/G
@@ -243,7 +203,7 @@
update_icon()
-/obj/structure/engineeringcart/proc/handle_storing(var/attacking_item, var/mob/user, var/should_store, var/storage_is_full)
+/obj/structure/cart/storage/engineeringcart/handle_storing(var/attacking_item, var/mob/user, var/should_store, var/storage_is_full)
if(should_store)
user.drop_from_inventory(attacking_item, src)
get_storage_contents_list()
@@ -254,7 +214,7 @@
else
to_chat(user, SPAN_WARNING("You can't store this here!"))
-/obj/structure/engineeringcart/attack_hand(mob/user)
+/obj/structure/cart/storage/engineeringcart/attack_hand(mob/user)
if(!isliving(user))
return
@@ -309,7 +269,7 @@
else
to_chat(user, SPAN_WARNING("\The [chosen_item] is not in the cart anymore!"))
-/obj/structure/engineeringcart/update_icon()
+/obj/structure/cart/storage/engineeringcart/update_icon()
ClearOverlays()
has_items = FALSE
if(my_plasteel.len)
@@ -334,92 +294,9 @@
AddOverlays("cart_redtoolbox")
has_items = TRUE
+/obj/structure/cart/storage/engineeringcart/half_filled
-/// For future reference, this kind of pulling functions are dependant on a type-check in `mob_movement.dm` line:332
-/obj/structure/engineeringcart/relaymove(mob/living/user, direction)
- . = ..()
-
- if(user.stat || user.stunned || user.weakened || user.paralysis || user.lying || user.restrained())
- if(user==pulling)
- pulling = null
- user.pulledby = null
- to_chat(user, SPAN_WARNING("You lost your grip!"))
- return
- if(user.pulling && (user == pulling))
- pulling = null
- user.pulledby = null
- return
- if(pulling && (get_dist(src, pulling) > 1))
- pulling = null
- user.pulledby = null
- if(user==pulling)
- return
- if(pulling && (get_dir(src.loc, pulling.loc) == direction))
- to_chat(user, SPAN_WARNING("You cannot go there."))
- return
-
- driving = 1
- var/turf/T = null
- if(pulling)
- T = pulling.loc
- if(get_dist(src, pulling) >= 1)
- step(pulling, get_dir(pulling.loc, src.loc))
- step(src, direction)
- set_dir(direction)
- if(pulling)
- if(pulling.loc == src.loc)
- pulling.forceMove(T)
- else
- spawn(0)
- if(get_dist(src, pulling) > 1)
- pulling = null
- user.pulledby = null
- pulling.set_dir(get_dir(pulling, src))
- driving = 0
-
-/obj/structure/engineeringcart/Move()
- . = ..()
- if (pulling && (get_dist(src, pulling) > 1))
- pulling.pulledby = null
- to_chat(pulling, SPAN_WARNING("You lost your grip!"))
- pulling = null
- if(has_gravity())
- playsound(src, 'sound/effects/roll.ogg', 50, 1)
-
-/obj/structure/engineeringcart/CtrlClick(var/mob/user)
- if(in_range(src, user))
- if(!ishuman(user)) return
- if(!pulling)
- pulling = user
- user.pulledby = src
- if(user.pulling)
- user.stop_pulling()
- user.set_dir(get_dir(user, src))
- to_chat(user, "You grip \the [name]'s handles.")
- else
- to_chat(usr, "You let go of \the [name]'s handles.")
- pulling.pulledby = null
- pulling = null
- return
-
-/obj/structure/engineeringcart/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
- if(air_group || (height==0))
- return TRUE
- if(mover?.movement_type & PHASING)
- return TRUE
- if(istype(mover) && mover.pass_flags & PASSTABLE)
- return TRUE
- if(istype(mover, /mob/living) && mover == pulling)
- return TRUE
- else
- if(istype(mover, /obj/projectile))
- return prob(30)
- else
- return !density
-
-/obj/structure/engineeringcart/half_filled
-
-/obj/structure/engineeringcart/half_filled/Initialize()
+/obj/structure/cart/storage/engineeringcart/half_filled/Initialize()
. = ..()
my_blue_toolbox = new /obj/item/storage/toolbox/mechanical(src)
my_yellow_toolbox = new /obj/item/storage/toolbox/electrical(src)
@@ -427,9 +304,9 @@
get_storage_contents_list()
update_icon()
-/obj/structure/engineeringcart/full
+/obj/structure/cart/storage/engineeringcart/full
-/obj/structure/engineeringcart/full/Initialize()
+/obj/structure/cart/storage/engineeringcart/full/Initialize()
. = ..()
my_lightreplacer = new /obj/item/device/lightreplacer(src)
my_blue_toolbox = new /obj/item/storage/toolbox/mechanical(src)
diff --git a/code/game/objects/structures/janicart.dm b/code/game/objects/structures/carts/janicart.dm
similarity index 70%
rename from code/game/objects/structures/janicart.dm
rename to code/game/objects/structures/carts/janicart.dm
index 4e597edb0fb..620f03e73e0 100644
--- a/code/game/objects/structures/janicart.dm
+++ b/code/game/objects/structures/carts/janicart.dm
@@ -1,14 +1,8 @@
-/obj/structure/janitorialcart
+/obj/structure/cart/storage/janitorialcart
name = "custodial cart"
desc = "The ultimate in custodial carts. Has space for water, mops, signs, trash bags, and more."
icon = 'icons/obj/janitor.dmi'
icon_state = "cart"
- anchored = FALSE
- density = TRUE
- climbable = TRUE
- atom_flags = ATOM_FLAG_OPEN_CONTAINER
- build_amt = 15
- slowdown = 0
var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite
var/obj/item/storage/bag/trash/mybag = null
@@ -17,22 +11,15 @@
var/obj/item/device/lightreplacer/myreplacer = null
var/obj/structure/mopbucket/mybucket = null
var/signs = 0 //maximum capacity hardcoded below
- var/has_items = FALSE //This is set true whenever the cart has anything loaded/mounted on it
- var/driving
- var/mob/living/pulling
-/obj/structure/janitorialcart/mechanics_hints(mob/user, distance, is_adjacent)
+/obj/structure/cart/storage/janitorialcart/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
. += "Click and drag a mop bucket onto the cart to mount it."
. += "ALT-Click with a mop to put it away; a normal click will wet it in the bucket."
. += "ALT-Click with a container, such as a bucket, to pour its contents into the mounted bucket. A normal click will toss it into the trash."
. += "You can use a light replacer, spraybottle (of space cleaner) and four wet-floor signs on the cart to store them."
-/obj/structure/janitorialcart/disassembly_hints(mob/user, distance, is_adjacent)
- . += ..()
- . += "An empty custodial cart can be taken apart with a wrench or a welder. Or a plasma cutter, if you're that hardcore."
-
-/obj/structure/janitorialcart/feedback_hints(mob/user, distance, is_adjacent)
+/obj/structure/cart/storage/janitorialcart/feedback_hints(mob/user, distance, is_adjacent)
. += ..()
if(distance <= 1)
if (mybucket)
@@ -44,7 +31,7 @@
// Regular Variant
// No trashbag and no light replacer, this is inside the custodian's locker.
-/obj/structure/janitorialcart/Initialize()
+/obj/structure/cart/storage/janitorialcart/Initialize()
. = ..()
mymop = new /obj/item/mop(src)
myspray = new /obj/item/reagent_containers/spray/cleaner(src)
@@ -58,7 +45,7 @@
// Full Variant
// Has everything.
-/obj/structure/janitorialcart/full/Initialize()
+/obj/structure/cart/storage/janitorialcart/full/Initialize()
. = ..()
mybag = new /obj/item/storage/bag/trash(src)
mymop = new /obj/item/mop(src)
@@ -74,7 +61,7 @@
// Full with Water Variant
// Has everything as well as water in the mop bucket.
-/obj/structure/janitorialcart/full/water/Initialize()
+/obj/structure/cart/storage/janitorialcart/full/water/Initialize()
. = ..()
mybag = new /obj/item/storage/bag/trash(src)
mymop = new /obj/item/mop(src)
@@ -89,12 +76,12 @@
update_icon()
-/obj/structure/janitorialcart/New()
+/obj/structure/cart/storage/janitorialcart/New()
..()
if(is_station_turf(get_turf(src)))
GLOB.janitorial_supplies |= src
-/obj/structure/janitorialcart/Destroy()
+/obj/structure/cart/storage/janitorialcart/Destroy()
if(src in GLOB.janitorial_supplies)
GLOB.janitorial_supplies -= src
QDEL_NULL(mybag)
@@ -104,10 +91,10 @@
QDEL_NULL(mybucket)
return ..()
-/obj/structure/janitorialcart/proc/get_short_status()
+/obj/structure/cart/storage/janitorialcart/proc/get_short_status()
return "Contents: [english_list(contents)]"
-/obj/structure/janitorialcart/mouse_drop_receive(atom/dropped, mob/user, params)
+/obj/structure/cart/storage/janitorialcart/mouse_drop_receive(atom/dropped, mob/user, params)
var/atom/movable/O = dropped
if (istype(O, /obj/structure/mopbucket) && !mybucket)
O.forceMove(src)
@@ -120,7 +107,7 @@
//New Altclick functionality!
//Altclick the cart with a mop to stow the mop away
//Altclick the cart with a reagent container to pour things into the bucket without putting the bottle in trash
-/obj/structure/janitorialcart/AltClick()
+/obj/structure/cart/storage/janitorialcart/AltClick()
if(!usr || usr.stat || usr.lying || usr.restrained() || !Adjacent(usr)) return
var/obj/I = usr.get_active_hand()
if(istype(I, /obj/item/mop))
@@ -141,7 +128,7 @@
if (LR.store_broken)
return mybag.attackby(I, usr)
-/obj/structure/janitorialcart/attackby(obj/item/attacking_item, mob/user)
+/obj/structure/cart/storage/janitorialcart/attackby(obj/item/attacking_item, mob/user)
if(istype(attacking_item, /obj/item/mop) || istype(attacking_item, /obj/item/reagent_containers/glass/rag) || istype(attacking_item, /obj/item/soap))
if (mybucket)
if(attacking_item.reagents.total_volume < attacking_item.reagents.maximum_volume)
@@ -205,28 +192,8 @@
return
..()
-/obj/structure/janitorialcart/proc/take_apart(var/mob/user = null, var/obj/I)
- if(has_items)
- spill()
-
- if(user)
-
- if(iswelder(I))
- var/obj/item/welder = I
- welder.play_tool_sound(get_turf(src), 50)
-
- user.visible_message("[user] starts taking apart the [src]...", SPAN_NOTICE("You start disassembling the [src]..."))
- if (!do_after(user, 30, do_flags = DO_DEFAULT & ~DO_USER_SAME_HAND))
- return
-
- dismantle()
-
-/obj/structure/janitorialcart/ex_act(severity)
- spill(100 / severity)
- ..()
-
//This is called if the cart is caught in an explosion, or destroyed by weapon fire
-/obj/structure/janitorialcart/proc/spill(var/chance = 100)
+/obj/structure/cart/storage/janitorialcart/spill(var/chance = 100)
var/turf/dropspot = get_turf(src)
if (mymop && prob(chance))
mymop.forceMove(dropspot)
@@ -267,11 +234,11 @@
update_icon()
-/obj/structure/janitorialcart/attack_hand(mob/user)
+/obj/structure/cart/storage/janitorialcart/attack_hand(mob/user)
ui_interact(user)
return
-/obj/structure/janitorialcart/ui_interact(var/mob/user, var/ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
+/obj/structure/cart/storage/janitorialcart/ui_interact(var/mob/user, var/ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
var/data[0]
data["name"] = capitalize(name)
data["bag"] = mybag ? capitalize(mybag.name) : null
@@ -287,7 +254,7 @@
ui.set_initial_data(data)
ui.open()
-/obj/structure/janitorialcart/Topic(href, href_list)
+/obj/structure/cart/storage/janitorialcart/Topic(href, href_list)
if(!in_range(src, usr))
return
if(!isliving(usr))
@@ -335,7 +302,7 @@
update_icon()
updateUsrDialog()
-/obj/structure/janitorialcart/update_icon()
+/obj/structure/cart/storage/janitorialcart/update_icon()
ClearOverlays()
has_items = 0
if(mybucket)
@@ -361,83 +328,3 @@
if(signs)
AddOverlays("cart_sign[signs]")
has_items = 1
-
-//Shamelessly copied from wheelchair code
-/obj/structure/janitorialcart/relaymove(mob/living/user, direction)
- . = ..()
-
- if(user.stat || user.stunned || user.weakened || user.paralysis || user.lying || user.restrained())
- if(user==pulling)
- pulling = null
- user.pulledby = null
- to_chat(user, SPAN_WARNING("You lost your grip!"))
- return
- if(user.pulling && (user == pulling))
- pulling = null
- user.pulledby = null
- return
- if(pulling && (get_dist(src, pulling) > 1))
- pulling = null
- user.pulledby = null
- if(user==pulling)
- return
- if(pulling && (get_dir(src.loc, pulling.loc) == direction))
- to_chat(user, SPAN_WARNING("You cannot go there."))
- return
-
- driving = 1
- var/turf/T = null
- if(pulling)
- T = pulling.loc
- if(get_dist(src, pulling) >= 1)
- step(pulling, get_dir(pulling.loc, src.loc))
- step(src, direction)
- set_dir(direction)
- if(pulling)
- if(pulling.loc == src.loc)
- pulling.forceMove(T)
- else
- spawn(0)
- if(get_dist(src, pulling) > 1)
- pulling = null
- user.pulledby = null
- pulling.set_dir(get_dir(pulling, src))
- driving = 0
-
-/obj/structure/janitorialcart/Move()
- . = ..()
- if (pulling && (get_dist(src, pulling) > 1))
- pulling.pulledby = null
- to_chat(pulling, SPAN_WARNING("You lost your grip!"))
- pulling = null
-
-/obj/structure/janitorialcart/CtrlClick(var/mob/user)
- if(in_range(src, user))
- if(!ishuman(user)) return
- if(!pulling)
- pulling = user
- user.pulledby = src
- if(user.pulling)
- user.stop_pulling()
- user.set_dir(get_dir(user, src))
- to_chat(user, "You grip \the [name]'s handles.")
- else
- to_chat(usr, "You let go of \the [name]'s handles.")
- pulling.pulledby = null
- pulling = null
- return
-
-/obj/structure/janitorialcart/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
- if(air_group || (height==0))
- return TRUE
- if(mover?.movement_type & PHASING)
- return TRUE
- if(istype(mover) && mover.pass_flags & PASSTABLE)
- return TRUE
- if(istype(mover, /mob/living) && mover == pulling)
- return TRUE
- else
- if(istype(mover, /obj/projectile))
- return prob(30)
- else
- return !density
diff --git a/code/game/objects/structures/carts/parcelcart.dm b/code/game/objects/structures/carts/parcelcart.dm
new file mode 100644
index 00000000000..21113e621dd
--- /dev/null
+++ b/code/game/objects/structures/carts/parcelcart.dm
@@ -0,0 +1,123 @@
+/obj/structure/cart/storage/parcelcart
+ name = "parcel cart"
+ desc = "A cart for moving parcels and packages around."
+ icon = 'icons/obj/parcelcart.dmi'
+ icon_state = "cart"
+
+ var/list/my_parcels = list()
+
+ /// Amount of parcels the cart is capable to store.
+ var/parcel_capacity = 17
+
+ var/static/list/allowed_types = typecacheof(list(
+ /obj/item/smallDelivery
+ ))
+
+/obj/structure/cart/storage/parcelcart/mechanics_hints(mob/user, distance, is_adjacent)
+ . += ..()
+ . += "You can use hand held parcels on the cart to store them."
+ . += "It slows down at 5 packages, and becomes even slower when there is 11 packages or more loaded."
+
+/obj/structure/cart/storage/parcelcart/get_storage_contents_list()
+ storage_contents.Cut()
+ var/list/lists_to_check = list(
+ my_parcels
+ )
+ for(var/list/list_to_check in lists_to_check)
+ if(list_to_check?.len) //null check
+ storage_contents += list_to_check
+
+ var/list/non_sheet_objects = list(my_parcels)
+
+ for(var/obj/O in non_sheet_objects)
+ if(O)
+ storage_contents += O
+
+/obj/structure/cart/storage/parcelcart/Destroy()
+ QDEL_NULL(my_parcels)
+ return ..()
+
+/obj/structure/cart/storage/parcelcart/attackby(obj/item/attacking_item, mob/user)
+ if(is_type_in_typecache(attacking_item, allowed_types))
+ var/should_store = FALSE
+ var/storage_is_full = FALSE
+ if(my_parcels.len < parcel_capacity)
+ my_parcels += attacking_item
+ should_store = TRUE
+ else
+ storage_is_full = TRUE
+
+ handle_storing(attacking_item, user, should_store, storage_is_full)
+ return TRUE
+
+ else if (!has_items && (attacking_item.iswrench() || attacking_item.iswelder() || istype(attacking_item, /obj/item/gun/energy/plasmacutter)))
+ take_apart(user, attacking_item)
+ return
+ ..()
+
+/obj/structure/cart/storage/parcelcart/spill(var/chance = 100)
+ var/turf/dropspot = get_turf(src)
+
+ if(LAZYLEN(my_parcels) && prob(chance))
+ var/obj/item/smallDelivery/M
+ for(var/I in my_parcels)
+ M = I
+ M.forceMove(dropspot)
+ M.tumble(1)
+ my_parcels -= M
+ my_parcels.Cut()
+ update_icon()
+
+/obj/structure/cart/storage/parcelcart/handle_storing(var/attacking_item, var/mob/user, var/should_store, var/storage_is_full)
+ if(should_store)
+ user.drop_from_inventory(attacking_item, src)
+ get_storage_contents_list()
+ update_slowdown()
+ update_icon()
+ to_chat(user, SPAN_NOTICE("You put [attacking_item] into [src]."))
+ else if(storage_is_full)
+ to_chat(user, SPAN_WARNING("There isn't any space to store [attacking_item] in [src]!"))
+ else
+ to_chat(user, SPAN_WARNING("You can't store this here!"))
+
+/obj/structure/cart/storage/parcelcart/update_slowdown()
+ if(my_parcels.len <= 4)
+ slowdown = 0
+ else if(my_parcels.len > 4 && my_parcels.len < 11)
+ slowdown = 1
+ else if(my_parcels.len >= 11)
+ slowdown = 2
+
+/obj/structure/cart/storage/parcelcart/attack_hand(mob/user)
+ if(!isliving(user))
+ return
+
+ if(LAZYLEN(storage_contents))
+ for(var/obj/O in storage_contents)
+ storage_contents[O] = image(O.icon, O.icon_state)
+
+ var/obj/item/chosen_item = show_radial_menu(user, src, storage_contents, require_near = TRUE, tooltips = TRUE)
+
+ if(isnull(chosen_item))
+ return
+ if(chosen_item in storage_contents)
+ switch(chosen_item.type)
+ if(/obj/item/smallDelivery)
+ if(my_parcels.len)
+ user.put_in_hands(chosen_item)
+ to_chat(user, SPAN_NOTICE("You take [my_parcels[chosen_item]] from [src]."))
+ my_parcels -= chosen_item
+
+ get_storage_contents_list()
+ update_slowdown()
+ update_icon()
+ else
+ to_chat(user, SPAN_WARNING("\The [chosen_item] is not in the cart anymore!"))
+
+/obj/structure/cart/storage/parcelcart/update_icon()
+ ClearOverlays()
+ has_items = FALSE
+ if(my_parcels.len)
+ var/amount_to_show = min(length(my_parcels), parcel_capacity)
+ AddOverlays("pack_[amount_to_show]")
+ has_items = TRUE
diff --git a/code/game/objects/structures/mop_bucket.dm b/code/game/objects/structures/mop_bucket.dm
index 22c38ee95f0..c0e242a34f5 100644
--- a/code/game/objects/structures/mop_bucket.dm
+++ b/code/game/objects/structures/mop_bucket.dm
@@ -44,8 +44,8 @@
/obj/structure/mopbucket/on_reagent_change()
. = ..()
- if(istype(loc,/obj/structure/janitorialcart))
- var/obj/structure/janitorialcart/cart = loc
+ if(istype(loc,/obj/structure/cart/storage/janitorialcart))
+ var/obj/structure/cart/storage/janitorialcart/cart = loc
cart.update_icon()
else
update_icon()
diff --git a/code/modules/cargo/items/custodial.dm b/code/modules/cargo/items/custodial.dm
index 16b4c7479c8..20d6e15835d 100644
--- a/code/modules/cargo/items/custodial.dm
+++ b/code/modules/cargo/items/custodial.dm
@@ -145,7 +145,7 @@
description = "The ultimate in custodial carts. Has space for water, mops, signs, trash bags, and more."
price = 190
items = list(
- /obj/structure/janitorialcart
+ /obj/structure/cart/storage/janitorialcart
)
access = ACCESS_JANITOR
container_type = "crate"
diff --git a/code/modules/cargo/items/engineering.dm b/code/modules/cargo/items/engineering.dm
index 793d8ff94b2..a506f5395db 100644
--- a/code/modules/cargo/items/engineering.dm
+++ b/code/modules/cargo/items/engineering.dm
@@ -903,7 +903,7 @@
description = "A cart for your engineering-related storage needs."
price = 100
items = list(
- /obj/structure/engineeringcart
+ /obj/structure/cart/storage/engineeringcart
)
access = ACCESS_ENGINE
container_type = "crate"
diff --git a/code/modules/cargo/items/operations.dm b/code/modules/cargo/items/operations.dm
index edc03323285..18b15942094 100644
--- a/code/modules/cargo/items/operations.dm
+++ b/code/modules/cargo/items/operations.dm
@@ -185,7 +185,7 @@
description = "A set of items to restock the janitors closet."
price = 2000
items = list(
- /obj/structure/janitorialcart,
+ /obj/structure/cart/storage/janitorialcart,
/obj/structure/mopbucket,
/obj/item/mop,
/obj/item/storage/bag/trash,
diff --git a/code/modules/item_worth/worths_list.dm b/code/modules/item_worth/worths_list.dm
index 445d8911368..a1a99f08924 100644
--- a/code/modules/item_worth/worths_list.dm
+++ b/code/modules/item_worth/worths_list.dm
@@ -689,7 +689,7 @@ var/list/worths = list(
/obj/structure/girder = 15,
/obj/structure/grille = 5,
/obj/structure/inflatable = 1,
- /obj/structure/janitorialcart = 12,
+ /obj/structure/cart/storage/janitorialcart = 12,
/obj/structure/kitchenspike = 35,
/obj/structure/lattice = 1,
/obj/structure/morgue = 10,
diff --git a/code/modules/materials/material_recipes.dm b/code/modules/materials/material_recipes.dm
index 26c452f42f3..3cc43ed85a9 100644
--- a/code/modules/materials/material_recipes.dm
+++ b/code/modules/materials/material_recipes.dm
@@ -71,8 +71,9 @@
recipes += new /datum/stack_recipe_list("miscellaneous construction",
list(
new /datum/stack_recipe("key", /obj/item/key, 1, time = 10, one_per_turf = 0, on_floor = 1),
- new /datum/stack_recipe("custodial cart", /obj/structure/janitorialcart, BUILD_AMT, time = 120, one_per_turf = 1, on_floor = 1),
- new /datum/stack_recipe("engineering cart", /obj/structure/engineeringcart, BUILD_AMT, time = 120, one_per_turf = 1, on_floor = 1),
+ new /datum/stack_recipe("custodial cart", /obj/structure/cart/storage/janitorialcart, BUILD_AMT, time = 120, one_per_turf = 1, on_floor = 1),
+ new /datum/stack_recipe("engineering cart", /obj/structure/cart/storage/engineeringcart, BUILD_AMT, time = 120, one_per_turf = 1, on_floor = 1),
+ new /datum/stack_recipe("parcel cart", /obj/structure/cart/storage/parcelcart, BUILD_AMT, time = 120, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("steel closet", /obj/structure/closet, BUILD_AMT, time = 15, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("canister", /obj/machinery/portable_atmospherics/canister, 10, time = 15, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("target stake", /obj/structure/target_stake, BUILD_AMT, time = 15, one_per_turf = 1, on_floor = 1),
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index 59fb6e6773c..fe2a1d965f6 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -329,7 +329,7 @@
//Wheelchair pushing goes here for now.
//TODO: Fuck wheelchairs.
- if(istype(mob.pulledby, /obj/structure/bed/stool/chair/office/wheelchair) || istype(mob.pulledby, /obj/structure/janitorialcart) || istype(mob.pulledby, /obj/structure/engineeringcart))
+ if(istype(mob.pulledby, /obj/structure/bed/stool/chair/office/wheelchair) || istype(mob.pulledby, /obj/structure/cart))
var/obj/structure/S = mob.pulledby
move_delay += S.slowdown
return mob.pulledby.relaymove(mob, direct)
diff --git a/code/modules/modular_computers/file_system/programs/civilian/janitor.dm b/code/modules/modular_computers/file_system/programs/civilian/janitor.dm
index eb6c521040b..3520b22ad3b 100644
--- a/code/modules/modular_computers/file_system/programs/civilian/janitor.dm
+++ b/code/modules/modular_computers/file_system/programs/civilian/janitor.dm
@@ -48,8 +48,8 @@
var/mob/living/bot/cleanbot/C = A
status = C.on ? "Online" : "Offline"
supply_type = "Cleanbots"
- else if(istype(A, /obj/structure/janitorialcart))
- var/obj/structure/janitorialcart/J = A
+ else if(istype(A, /obj/structure/cart/storage/janitorialcart))
+ var/obj/structure/cart/storage/janitorialcart/J = A
status = J.get_short_status()
supply_type = "Janicarts"
else
diff --git a/html/changelogs/ParcelCart.yml b/html/changelogs/ParcelCart.yml
new file mode 100644
index 00000000000..b98c1a007c3
--- /dev/null
+++ b/html/changelogs/ParcelCart.yml
@@ -0,0 +1,7 @@
+author: TheGreyWolf & Tomixcomics
+
+delete-after: True
+
+changes:
+ - rscadd: "Added a parcel cart to the operations mail room. It can carry up to 17 handheld packages regardless of size."
+ - bugfix: "Fixed the engineering cart giving plasteel when taken apart."
diff --git a/icons/obj/parcelcart.dmi b/icons/obj/parcelcart.dmi
new file mode 100644
index 0000000000000000000000000000000000000000..aa89a986ce70f6cf68225943c312ddd792052b5e
GIT binary patch
literal 1296
zcmV+r1@HQaP)004jp0{{R3yS#y#xQok}VxXX)si~=(Y&y7wPL!0Crguc*+}w9}
zcchJAIYCb(BqY$Ye3NZN0jjY100001bW%=J06^y0W&i*Hr+QRabVOxyV{&P5bZKvH
z004NLjncge!Y~wu;W@dAL%T~)TmJ?ZX^{^079_OM1hJLmptoUwIQ$lD+mGVXs7kGz^*1NWh+@R1GaF2hOsmm{m?mp=4d!8&`-h%p
zxL+7!CB`a@)fii2Y=f~a#IngU?%l4(z~S2nI_qn1aC;494KUHF9eE=KJYC6YJ9w
zYYpH5000BSNklHjD-t6IP;wdh^vBbL%X03V=Pec!Nr$Yjb-FL
z^4!GYHm+w-qc30T8PtI$H5K$e#W@SA$+4RE#vJ^7Jj_{8O^!4V^fU{86)H#*1vL+x
zM{f@5`^%vw;+;50wE6I>T8eiy@1
z{h;o8f3~jm$o2O9^StT%_PlAsTyM8)defGsnaSIR0t
z69pAdeRL_9Q^e~$c;ur?Y5%9)ONvK6x)k<*gpwsZ@GHMwprGHA1yqt;={Mi;%lf^p
z<3L{6@5utoxYBQ4QCigRW#BFE3Efk5yX`3*hy|vU;!oa-xE4%rk#9%y?PeWd+E$D|
z`8hSw1`n4%sAj2%bpWnYj6eA~>b_@BTpVB+G$wE5wx9F7v7e?9DkrGM{H@&fbLtb{`X`Tw(GafjCaxVpW3ot1^
zXK*%p{^z$eH~pO0!F?GRoHNf0Il>8$>ll7c>~L|-gTeVLoXEAvdVXKP@N=Re^Shhr
zqXU0|6T(%2wS3;q^mCLup3~&?G`_$&Cm?=b!0>bGJR<(?C9VpP^?csV^mFPwBL40*
z&gD}`*7JFRxSxY?E=vHoPFcSv3IsW&alUz?f00x}2wkvKX}=faa!@Dz;B*6R;{W)#
zzz>%8doj-W3x06i%Kt6&gWLJPg??~5|F_T&Zs-4+A8cu9X<3}+_kh{j??v(l{a)oo
z$GlgvfZxkj5bEzjobxPys|fXX5uU3c%-=2ad+W2>-|=NUPr)_*j_Wy+;yD_w^LKNB
z6wlCboxi&mNO5RVelEoMbepC)I4M6D;i^E2gOl=eP!3MY&p|mjDL)70@Pa`