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 |
This commit is contained in:
Casper3667
2025-09-10 19:05:19 +00:00
committed by GitHub
parent e2b00ebe46
commit 9a9e259104
50 changed files with 367 additions and 328 deletions
+141
View File
@@ -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 <b>CTRL-Click</b> 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 <b>wrench</b> or a <b>welder</b>. Or a <b>plasma cutter</b>, 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("<b>[user]</b> 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()
. = ..()
@@ -0,0 +1,322 @@
/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"
var/list/my_glass = list()
var/list/my_metal = list()
var/list/my_plasteel = 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
/// Amount of stacks the cart is capable to store.
var/stack_capacity = 2
var/static/list/allowed_types = typecacheof(list(
/obj/item/stack/material/glass,
/obj/item/stack/material/steel,
/obj/item/stack/material/plasteel,
/obj/item/device/lightreplacer,
/obj/item/storage/toolbox/mechanical,
/obj/item/storage/toolbox/electrical,
/obj/item/storage/toolbox/emergency
))
/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."
/obj/structure/cart/storage/engineeringcart/feedback_hints(mob/user, distance, is_adjacent)
. += ..()
if(distance <= 1)
if(locate(/obj/item/stack/material) in storage_contents)
var/metal_amount
var/plasteel_amount
var/glass_amount
var/obj/item/stack/material/steel/S
var/obj/item/stack/material/plasteel/P
var/obj/item/stack/material/glass/G
if(LAZYLEN(my_metal))
for(var/I in my_metal) // we already handle the type-check in `atttackby()` so it's safe to assume the lists contain what they're intended to
S = I
metal_amount += S.amount
. += "[icon2html(S, user)] This cart contains <b>[metal_amount] sheet\s</b> of steel!"
if(LAZYLEN(my_plasteel))
for(var/I in my_plasteel)
P = I
plasteel_amount += P.amount
. += "[icon2html(P, user)] This cart contains <b>[plasteel_amount] sheet\s</b> of plasteel!"
if(LAZYLEN(my_glass))
for(var/I in my_glass)
G = I
glass_amount += G.amount
. += "[icon2html(G, user)] This cart contains <b>[glass_amount] sheet\s</b> of glass!"
else
. += "[icon2html(src, user)] There is no material in this cart!"
/obj/structure/cart/storage/engineeringcart/get_storage_contents_list()
storage_contents.Cut()
var/list/lists_to_check = list(
my_glass, my_metal, my_plasteel
)
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_lightreplacer, my_blue_toolbox, my_yellow_toolbox, my_red_toolbox)
for(var/obj/O in non_sheet_objects)
if(O)
storage_contents += O
/obj/structure/cart/storage/engineeringcart/Destroy()
QDEL_NULL(my_glass)
QDEL_NULL(my_metal)
QDEL_NULL(my_plasteel)
QDEL_NULL(my_lightreplacer)
QDEL_NULL(my_blue_toolbox)
QDEL_NULL(my_yellow_toolbox)
QDEL_NULL(my_red_toolbox)
return ..()
/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
if(istype(attacking_item, /obj/item/stack/material)) //---- stack materials
switch(attacking_item.type)
if(/obj/item/stack/material/glass, /obj/item/stack/material/glass/full)
if(my_glass.len < stack_capacity)
my_glass += attacking_item
should_store = TRUE
else
storage_is_full = TRUE
if(/obj/item/stack/material/steel, /obj/item/stack/material/steel/full)
if(my_metal.len < stack_capacity)
my_metal += attacking_item
should_store = TRUE
else
storage_is_full = TRUE
if(/obj/item/stack/material/plasteel, /obj/item/stack/material/plasteel/full)
if(my_plasteel.len < stack_capacity)
my_plasteel += attacking_item
should_store = TRUE
else
storage_is_full = TRUE
handle_storing(attacking_item, user, should_store, storage_is_full)
return TRUE
if(istype(attacking_item, /obj/item/storage/toolbox)) //---- toolboxes
switch(attacking_item.type)
if(/obj/item/storage/toolbox/mechanical)
if(!my_blue_toolbox)
my_blue_toolbox = attacking_item
should_store = TRUE
else
storage_is_full = TRUE
if(/obj/item/storage/toolbox/electrical)
if(!my_yellow_toolbox)
my_yellow_toolbox = attacking_item
should_store = TRUE
else
storage_is_full = TRUE
if(/obj/item/storage/toolbox/emergency)
if(!my_red_toolbox)
my_red_toolbox = attacking_item
should_store = TRUE
else
storage_is_full = TRUE
handle_storing(attacking_item, user, should_store, storage_is_full)
return TRUE
if(istype(attacking_item, /obj/item/device/lightreplacer)) //---- light replacer
if(!my_lightreplacer)
my_lightreplacer = 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/engineeringcart/spill(var/chance = 100)
var/turf/dropspot = get_turf(src)
if(LAZYLEN(my_glass) && prob(chance))
var/obj/item/stack/material/glass/G
for(var/I in my_glass)
G = I
G.forceMove(dropspot)
G.tumble(1)
my_glass -= G
my_glass.Cut()
if(LAZYLEN(my_metal) && prob(chance))
var/obj/item/stack/material/glass/M
for(var/I in my_metal)
M = I
M.forceMove(dropspot)
M.tumble(1)
my_metal -= M
my_metal.Cut()
if(LAZYLEN(my_plasteel) && prob(chance))
var/obj/item/stack/material/glass/P
for(var/I in my_plasteel)
P = I
P.forceMove(dropspot)
P.tumble(1)
my_plasteel -= P
my_plasteel.Cut()
if(my_lightreplacer && prob(chance))
my_lightreplacer.forceMove(dropspot)
my_lightreplacer.tumble(2)
my_lightreplacer = null
if(my_blue_toolbox && prob(chance))
my_blue_toolbox.forceMove(dropspot)
my_blue_toolbox.tumble(2)
my_blue_toolbox = null
if(my_yellow_toolbox && prob(chance))
my_yellow_toolbox.forceMove(dropspot)
my_yellow_toolbox.tumble(2)
my_yellow_toolbox = null
if(my_red_toolbox && prob(chance))
my_red_toolbox.forceMove(dropspot)
my_red_toolbox.tumble(2)
my_red_toolbox = null
update_icon()
/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()
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/engineeringcart/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/stack/material/glass, /obj/item/stack/material/glass/full)
if(my_glass.len)
user.put_in_hands(chosen_item)
to_chat(user, SPAN_NOTICE("You take [my_glass[chosen_item]] from [src]."))
my_glass -= chosen_item
if(/obj/item/stack/material/steel, /obj/item/stack/material/steel/full)
if(my_metal.len)
user.put_in_hands(chosen_item)
to_chat(user, SPAN_NOTICE("You take [my_metal[chosen_item]] from [src]."))
my_metal -= chosen_item
if(/obj/item/stack/material/plasteel, /obj/item/stack/material/plasteel/full)
if(my_plasteel.len)
user.put_in_hands(chosen_item)
to_chat(user, SPAN_NOTICE("You take [my_plasteel[chosen_item]] from [src]."))
my_plasteel -= chosen_item
if(/obj/item/device/lightreplacer)
if(my_lightreplacer)
user.put_in_hands(my_lightreplacer)
to_chat(user, SPAN_NOTICE("You take [my_lightreplacer] from [src]."))
my_lightreplacer = null
if(/obj/item/storage/toolbox/mechanical)
if(my_blue_toolbox)
user.put_in_hands(my_blue_toolbox)
to_chat(user, SPAN_NOTICE("You take [my_blue_toolbox] from [src]."))
my_blue_toolbox = null
if(/obj/item/storage/toolbox/electrical)
if(my_yellow_toolbox)
user.put_in_hands(my_yellow_toolbox)
to_chat(user, SPAN_NOTICE("You take [my_yellow_toolbox] from [src]."))
my_yellow_toolbox = null
if(/obj/item/storage/toolbox/emergency)
if(my_red_toolbox)
user.put_in_hands(my_red_toolbox)
to_chat(user, SPAN_NOTICE("You take [my_red_toolbox] from [src]."))
my_red_toolbox = null
get_storage_contents_list()
update_icon()
else
to_chat(user, SPAN_WARNING("\The [chosen_item] is not in the cart anymore!"))
/obj/structure/cart/storage/engineeringcart/update_icon()
ClearOverlays()
has_items = FALSE
if(my_plasteel.len)
AddOverlays("cart_plasteel")
has_items = TRUE
if(my_metal.len)
AddOverlays("cart_metal")
has_items = TRUE
if(my_glass.len)
AddOverlays("cart_glass")
has_items = TRUE
if(my_lightreplacer)
AddOverlays("cart_flashlight")
has_items = TRUE
if(my_blue_toolbox)
AddOverlays("cart_bluetoolbox")
has_items = TRUE
if(my_yellow_toolbox)
AddOverlays("cart_yellowtoolbox")
has_items = TRUE
if(my_red_toolbox)
AddOverlays("cart_redtoolbox")
has_items = TRUE
/obj/structure/cart/storage/engineeringcart/half_filled
/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)
my_red_toolbox = new /obj/item/storage/toolbox/emergency(src)
get_storage_contents_list()
update_icon()
/obj/structure/cart/storage/engineeringcart/full
/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)
my_yellow_toolbox = new /obj/item/storage/toolbox/electrical(src)
my_red_toolbox = new /obj/item/storage/toolbox/emergency(src)
my_glass += new /obj/item/stack/material/glass/full(src)
my_glass += new /obj/item/stack/material/glass/full(src)
my_metal += new /obj/item/stack/material/steel/full(src)
my_metal += new /obj/item/stack/material/steel/full(src)
my_plasteel += new /obj/item/stack/material/plasteel/full(src)
my_plasteel += new /obj/item/stack/material/plasteel/full(src)
get_storage_contents_list()
update_icon()
@@ -0,0 +1,330 @@
/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"
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
var/obj/item/mop/mymop = null
var/obj/item/reagent_containers/spray/myspray = null
var/obj/item/device/lightreplacer/myreplacer = null
var/obj/structure/mopbucket/mybucket = null
var/signs = 0 //maximum capacity hardcoded below
/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/cart/storage/janitorialcart/feedback_hints(mob/user, distance, is_adjacent)
. += ..()
if(distance <= 1)
if (mybucket)
var/contains = mybucket.reagents.total_volume
. += "[icon2html(src, user)] The bucket contains <b>[contains] unit\s</b> of liquid!"
else
. += "[icon2html(src, user)] There is no bucket mounted on it!"
//everything else is visible, so doesn't need to be mentioned
// Regular Variant
// No trashbag and no light replacer, this is inside the custodian's locker.
/obj/structure/cart/storage/janitorialcart/Initialize()
. = ..()
mymop = new /obj/item/mop(src)
myspray = new /obj/item/reagent_containers/spray/cleaner(src)
mybucket = new /obj/structure/mopbucket(src)
for(signs, signs < 4, signs++)
new /obj/item/clothing/suit/caution(src)
update_icon()
// Full Variant
// Has everything.
/obj/structure/cart/storage/janitorialcart/full/Initialize()
. = ..()
mybag = new /obj/item/storage/bag/trash(src)
mymop = new /obj/item/mop(src)
myspray = new /obj/item/reagent_containers/spray/cleaner(src)
myreplacer = new /obj/item/device/lightreplacer(src)
mybucket = new /obj/structure/mopbucket(src)
for(signs, signs < 4, signs++)
new /obj/item/clothing/suit/caution(src)
update_icon()
// Full with Water Variant
// Has everything as well as water in the mop bucket.
/obj/structure/cart/storage/janitorialcart/full/water/Initialize()
. = ..()
mybag = new /obj/item/storage/bag/trash(src)
mymop = new /obj/item/mop(src)
myspray = new /obj/item/reagent_containers/spray/cleaner(src)
myreplacer = new /obj/item/device/lightreplacer(src)
mybucket = new /obj/structure/mopbucket(src)
mybucket.reagents.add_reagent(/singleton/reagent/water, mybucket.bucketsize)
for(signs, signs < 4, signs++)
new /obj/item/clothing/suit/caution(src)
update_icon()
/obj/structure/cart/storage/janitorialcart/New()
..()
if(is_station_turf(get_turf(src)))
GLOB.janitorial_supplies |= src
/obj/structure/cart/storage/janitorialcart/Destroy()
if(src in GLOB.janitorial_supplies)
GLOB.janitorial_supplies -= src
QDEL_NULL(mybag)
QDEL_NULL(mymop)
QDEL_NULL(myspray)
QDEL_NULL(myreplacer)
QDEL_NULL(mybucket)
return ..()
/obj/structure/cart/storage/janitorialcart/proc/get_short_status()
return "Contents: [english_list(contents)]"
/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)
mybucket = O
to_chat(user, "You mount the [O] on the janicart.")
update_icon()
else
..()
//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/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))
if(!mymop)
usr.drop_from_inventory(I,src)
mymop = I
update_icon()
updateUsrDialog()
to_chat(usr, SPAN_NOTICE("You put [I] into [src]."))
else
to_chat(usr, SPAN_NOTICE("The cart already has a mop attached"))
return
else if(istype(I, /obj/item/reagent_containers) && mybucket)
var/obj/item/reagent_containers/C = I
C.afterattack(mybucket, usr, 1)
else if(istype (I, /obj/item/device/lightreplacer))
var/obj/item/device/lightreplacer/LR = I
if (LR.store_broken)
return mybag.attackby(I, usr)
/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)
if(mybucket.reagents.total_volume < 1)
to_chat(user, SPAN_NOTICE("[mybucket] is empty!"))
update_icon()
else
mybucket.reagents.trans_to_obj(attacking_item, 5) //
to_chat(user, SPAN_NOTICE("You wet [attacking_item] in [mybucket]."))
playsound(loc, 'sound/effects/slosh.ogg', 25, 1)
update_icon()
else
to_chat(user, SPAN_NOTICE("[attacking_item] can't absorb anymore liquid!"))
else
to_chat(user, SPAN_NOTICE("There is no bucket mounted here to dip [attacking_item] into!"))
return 1
else if(istype(attacking_item, /obj/item/reagent_containers/spray) && !myspray)
user.drop_from_inventory(attacking_item, src)
myspray = attacking_item
update_icon()
updateUsrDialog()
to_chat(user, SPAN_NOTICE("You put [attacking_item] into [src]."))
return 1
else if(istype(attacking_item, /obj/item/device/lightreplacer) && !myreplacer)
user.drop_from_inventory(attacking_item, src)
myreplacer = attacking_item
update_icon()
updateUsrDialog()
to_chat(user, SPAN_NOTICE("You put [attacking_item] into [src]."))
return 1
else if(istype(attacking_item, /obj/item/storage/bag/trash) && !mybag)
user.drop_from_inventory(attacking_item, src)
mybag = attacking_item
attacking_item.forceMove(src)
update_icon()
updateUsrDialog()
to_chat(user, SPAN_NOTICE("You put [attacking_item] into [src]."))
return 1
else if(istype(attacking_item, /obj/item/clothing/suit/caution))
if(signs < 4)
user.drop_from_inventory(attacking_item, src)
signs++
update_icon()
updateUsrDialog()
to_chat(user, SPAN_NOTICE("You put [attacking_item] into [src]."))
else
to_chat(user, SPAN_NOTICE("[src] can't hold any more signs."))
return 1
else if(mybag)
return mybag.attackby(attacking_item, user)
//This return will prevent afterattack from executing if the object goes into the trashbag,
//This prevents dumb stuff like splashing the cart with the contents of a container, after putting said container into trash
else if (!has_items && (attacking_item.iswrench() || attacking_item.iswelder() || istype(attacking_item, /obj/item/gun/energy/plasmacutter)))
take_apart(user, attacking_item)
return
..()
//This is called if the cart is caught in an explosion, or destroyed by weapon fire
/obj/structure/cart/storage/janitorialcart/spill(var/chance = 100)
var/turf/dropspot = get_turf(src)
if (mymop && prob(chance))
mymop.forceMove(dropspot)
mymop.tumble(2)
mymop = null
if (myspray && prob(chance))
myspray.forceMove(dropspot)
myspray.tumble(3)
myspray = null
if (myreplacer && prob(chance))
myreplacer.forceMove(dropspot)
myreplacer.tumble(3)
myreplacer = null
if (mybucket && prob(chance*0.5))//bucket is heavier, harder to knock off
mybucket.forceMove(dropspot)
mybucket.tumble(1)
mybucket = null
if (signs)
for (var/obj/item/clothing/suit/caution/Sign in src)
if (prob(min((chance*2),100)))
signs--
Sign.forceMove(dropspot)
Sign.tumble(3)
if (signs < 0)//safety for something that shouldn't happen
signs = 0
update_icon()
return
if (mybag && prob(min((chance*2),100)))//Bag is flimsy
mybag.forceMove(dropspot)
mybag.tumble(1)
mybag.spill()//trashbag spills its contents too
mybag = null
update_icon()
/obj/structure/cart/storage/janitorialcart/attack_hand(mob/user)
ui_interact(user)
return
/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
data["bucket"] = mybucket ? capitalize(mybucket.name) : null
data["mop"] = mymop ? capitalize(mymop.name) : null
data["spray"] = myspray ? capitalize(myspray.name) : null
data["replacer"] = myreplacer ? capitalize(myreplacer.name) : null
data["signs"] = signs ? "[signs] sign\s" : null
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if(!ui)
ui = new(user, src, ui_key, "janitorcart.tmpl", "Janitorial cart", 240, 160)
ui.set_initial_data(data)
ui.open()
/obj/structure/cart/storage/janitorialcart/Topic(href, href_list)
if(!in_range(src, usr))
return
if(!isliving(usr))
return
var/mob/living/user = usr
if(href_list["take"])
switch(href_list["take"])
if("garbage")
if(mybag)
user.put_in_hands(mybag)
to_chat(user, SPAN_NOTICE("You take [mybag] from [src]."))
mybag = null
if("mop")
if(mymop)
user.put_in_hands(mymop)
to_chat(user, SPAN_NOTICE("You take [mymop] from [src]."))
mymop = null
if("spray")
if(myspray)
user.put_in_hands(myspray)
to_chat(user, SPAN_NOTICE("You take [myspray] from [src]."))
myspray = null
if("replacer")
if(myreplacer)
user.put_in_hands(myreplacer)
to_chat(user, SPAN_NOTICE("You take [myreplacer] from [src]."))
myreplacer = null
if("sign")
if(signs)
var/obj/item/clothing/suit/caution/Sign = locate() in src
if(Sign)
user.put_in_hands(Sign)
to_chat(user, SPAN_NOTICE("You take \a [Sign] from [src]."))
signs--
else
warning("[src] signs ([signs]) didn't match contents")
signs = 0
if("bucket")
if(mybucket)
mybucket.forceMove(get_turf(user))
to_chat(user, SPAN_NOTICE("You unmount [mybucket] from [src]."))
mybucket.update_icon()
mybucket = null
update_icon()
updateUsrDialog()
/obj/structure/cart/storage/janitorialcart/update_icon()
ClearOverlays()
has_items = 0
if(mybucket)
AddOverlays("cart_bucket")
has_items = 1
if(mybucket.reagents.total_volume > 0)
AddOverlays("cart_water")
if(mybag)
AddOverlays("cart_garbage")
has_items = 1
if(mymop)
AddOverlays("cart_mop")
has_items = 1
if(myspray)
AddOverlays("cart_spray")
has_items = 1
if(myreplacer)
if (istype(myreplacer, /obj/item/device/lightreplacer/advanced))
AddOverlays("cart_adv_lightreplacer")
else
AddOverlays("cart_replacer")
has_items = 1
if(signs)
AddOverlays("cart_sign[signs]")
has_items = 1
@@ -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