diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm
index b464ea130c..97bf61779f 100644
--- a/code/__DEFINES/flags.dm
+++ b/code/__DEFINES/flags.dm
@@ -53,6 +53,9 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
// TESLA_IGNORE grants immunity from being targeted by tesla-style electricity
#define TESLA_IGNORE_2 512
+// Stops you from putting things like an RCD or other items into an ORM or protolathe for materials.
+#define NO_MAT_REDEMPTION_2 1024
+
//turf-only flags
#define NOJAUNT_1 1
#define UNUSED_TRANSIT_TURF_1 2
diff --git a/code/datums/components/material_container.dm b/code/datums/components/material_container.dm
index 55504d47d5..c1435b05a3 100644
--- a/code/datums/components/material_container.dm
+++ b/code/datums/components/material_container.dm
@@ -52,7 +52,10 @@
/datum/component/material_container/proc/OnAttackBy(obj/item/I, mob/living/user)
var/list/tc = allowed_typecache
- if(user.a_intent == INTENT_HARM || (I.flags_2 & HOLOGRAM_2) || (tc && !is_type_in_typecache(I, tc)))
+ if(user.a_intent == INTENT_HARM)
+ return FALSE
+ if((I.flags_2 & HOLOGRAM_2 | NO_MAT_REDEMPTION_2) || (tc && !is_type_in_typecache(I, tc)))
+ to_chat(user, "[parent] won't accept [I]!")
return FALSE
. = TRUE
last_insert_success = FALSE
diff --git a/code/game/mecha/equipment/tools/work_tools.dm b/code/game/mecha/equipment/tools/work_tools.dm
index b82f02353c..1fcc54ac2a 100644
--- a/code/game/mecha/equipment/tools/work_tools.dm
+++ b/code/game/mecha/equipment/tools/work_tools.dm
@@ -196,6 +196,7 @@
equip_cooldown = 10
energy_drain = 250
range = MELEE|RANGED
+ flags_2 = NO_MAT_REDEMPTION_2
var/mode = 0 //0 - deconstruct, 1 - wall or floor, 2 - airlock.
/obj/item/mecha_parts/mecha_equipment/rcd/New()
diff --git a/code/game/objects/items/RCD.dm b/code/game/objects/items/RCD.dm
index 6e094a5bbb..a2f80a94a9 100644
--- a/code/game/objects/items/RCD.dm
+++ b/code/game/objects/items/RCD.dm
@@ -123,6 +123,7 @@ obj/item/construction
lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
max_matter = 160
+ flags_2 = NO_MAT_REDEMPTION_2
var/mode = 1
var/canRturf = 0
var/ranged = FALSE
diff --git a/code/game/objects/items/bodybag.dm b/code/game/objects/items/bodybag.dm
index 2cb6c5b93f..f711aaba59 100644
--- a/code/game/objects/items/bodybag.dm
+++ b/code/game/objects/items/bodybag.dm
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
/obj/item/bodybag
name = "body bag"
@@ -68,3 +69,76 @@
return
loc.visible_message("[user] suddenly appears in front of [loc]!", "[user] breaks free of [src]!")
qdel(src)
+=======
+
+/obj/item/bodybag
+ name = "body bag"
+ desc = "A folded bag designed for the storage and transportation of cadavers."
+ icon = 'icons/obj/bodybag.dmi'
+ icon_state = "bodybag_folded"
+ var/unfoldedbag_path = /obj/structure/closet/body_bag
+ w_class = WEIGHT_CLASS_SMALL
+
+/obj/item/bodybag/attack_self(mob/user)
+ deploy_bodybag(user, user.loc)
+
+/obj/item/bodybag/afterattack(atom/target, mob/user, proximity)
+ if(proximity)
+ if(isopenturf(target))
+ deploy_bodybag(user, target)
+
+/obj/item/bodybag/proc/deploy_bodybag(mob/user, atom/location)
+ var/obj/structure/closet/body_bag/R = new unfoldedbag_path(location)
+ R.open(user)
+ R.add_fingerprint(user)
+ qdel(src)
+
+
+// Bluespace bodybag
+
+/obj/item/bodybag/bluespace
+ name = "bluespace body bag"
+ desc = "A folded bluespace body bag designed for the storage and transportation of cadavers."
+ icon = 'icons/obj/bodybag.dmi'
+ icon_state = "bluebodybag_folded"
+ unfoldedbag_path = /obj/structure/closet/body_bag/bluespace
+ w_class = WEIGHT_CLASS_SMALL
+ flags_2 = NO_MAT_REDEMPTION_2
+ origin_tech = "bluespace=4;materials=4;plasmatech=4"
+
+/obj/item/bodybag/bluespace/examine(mob/user)
+ ..()
+ if(contents.len)
+ to_chat(user, "You can make out the shapes of [contents.len] objects through the fabric.")
+
+/obj/item/bodybag/bluespace/Destroy()
+ for(var/atom/movable/A in contents)
+ A.forceMove(get_turf(src))
+ if(isliving(A))
+ to_chat(A, "You suddenly feel the space around you torn apart! You're free!")
+ return ..()
+
+/obj/item/bodybag/bluespace/deploy_bodybag(mob/user, atom/location)
+ var/obj/structure/closet/body_bag/R = new unfoldedbag_path(location)
+ for(var/atom/movable/A in contents)
+ A.forceMove(R)
+ if(isliving(A))
+ to_chat(A, "You suddenly feel air around you! You're free!")
+ R.open(user)
+ R.add_fingerprint(user)
+ qdel(src)
+
+/obj/item/bodybag/bluespace/container_resist(mob/living/user)
+ if(user.incapacitated())
+ to_chat(user, "You can't get out while you're restrained like this!")
+ return
+ user.changeNext_move(CLICK_CD_BREAKOUT)
+ user.last_special = world.time + CLICK_CD_BREAKOUT
+ to_chat(user, "You claw at the fabric of [src], trying to tear it open...")
+ to_chat(loc, "Someone starts trying to break free of [src]!")
+ if(!do_after(user, 200, target = src))
+ to_chat(loc, "The pressure subsides. It seems that they've stopped resisting...")
+ return
+ loc.visible_message("[user] suddenly appears in front of [loc]!", "[user] breaks free of [src]!")
+ qdel(src)
+>>>>>>> 51c4840... Stops putting RCD, bluespace bags, and staves into ORM (#31093)
diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm
index 2290ea91d3..3e9231f562 100644
--- a/code/game/objects/items/storage/backpack.dm
+++ b/code/game/objects/items/storage/backpack.dm
@@ -40,6 +40,7 @@
max_w_class = WEIGHT_CLASS_GIGANTIC
max_combined_w_class = 35
resistance_flags = FIRE_PROOF
+ flags_2 = NO_MAT_REDEMPTION_2
var/pshoom = 'sound/items/pshoom.ogg'
var/alt_sound = 'sound/items/pshoom_2.ogg'
armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 60, acid = 50)
diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm
index 8615e1cf8c..1a28e08297 100644
--- a/code/game/objects/items/storage/bags.dm
+++ b/code/game/objects/items/storage/bags.dm
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
/*
* These absorb the functionality of the plant bag, ore satchel, etc.
* They use the use_to_pickup, quick_gather, and quick_empty functions
@@ -379,3 +380,387 @@
preposition = "in"
can_hold = list(/obj/item/slime_extract, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/blood, /obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/food/snacks/deadmouse, /obj/item/reagent_containers/food/snacks/monkeycube)
resistance_flags = FLAMMABLE
+=======
+/*
+ * These absorb the functionality of the plant bag, ore satchel, etc.
+ * They use the use_to_pickup, quick_gather, and quick_empty functions
+ * that were already defined in weapon/storage, but which had been
+ * re-implemented in other classes.
+ *
+ * Contains:
+ * Trash Bag
+ * Mining Satchel
+ * Plant Bag
+ * Sheet Snatcher
+ * Book Bag
+ * Biowaste Bag
+ *
+ * -Sayu
+ */
+
+// Generic non-item
+/obj/item/storage/bag
+ allow_quick_gather = 1
+ allow_quick_empty = 1
+ display_contents_with_number = 1 // should work fine now
+ use_to_pickup = 1
+ slot_flags = SLOT_BELT
+
+// -----------------------------
+// Trash bag
+// -----------------------------
+/obj/item/storage/bag/trash
+ name = "trash bag"
+ desc = "It's the heavy-duty black polymer kind. Time to take out the trash!"
+ icon = 'icons/obj/janitor.dmi'
+ icon_state = "trashbag"
+ item_state = "trashbag"
+ lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi'
+
+ w_class = WEIGHT_CLASS_BULKY
+ max_w_class = WEIGHT_CLASS_SMALL
+ max_combined_w_class = 30
+ storage_slots = 30
+ can_hold = list() // any
+ cant_hold = list(/obj/item/disk/nuclear)
+
+/obj/item/storage/bag/trash/suicide_act(mob/user)
+ user.visible_message("[user] puts [src] over [user.p_their()] head and starts chomping at the insides! Disgusting!")
+ playsound(loc, 'sound/items/eatfood.ogg', 50, 1, -1)
+ return (TOXLOSS)
+
+/obj/item/storage/bag/trash/update_icon()
+ if(contents.len == 0)
+ icon_state = "[initial(icon_state)]"
+ else if(contents.len < 12)
+ icon_state = "[initial(icon_state)]1"
+ else if(contents.len < 21)
+ icon_state = "[initial(icon_state)]2"
+ else icon_state = "[initial(icon_state)]3"
+
+/obj/item/storage/bag/trash/cyborg
+
+/obj/item/storage/bag/trash/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J)
+ J.put_in_cart(src, user)
+ J.mybag=src
+ J.update_icon()
+
+/obj/item/storage/bag/trash/cyborg/janicart_insert(mob/user, obj/structure/janitorialcart/J)
+ return
+
+/obj/item/storage/bag/trash/bluespace
+ name = "trash bag of holding"
+ desc = "The latest and greatest in custodial convenience, a trashbag that is capable of holding vast quantities of garbage."
+ icon_state = "bluetrashbag"
+ origin_tech = "materials=4;bluespace=4;engineering=4;plasmatech=3"
+ max_combined_w_class = 60
+ storage_slots = 60
+ flags_2 = NO_MAT_REDEMPTION_2
+
+// -----------------------------
+// Mining Satchel
+// -----------------------------
+
+/obj/item/storage/bag/ore
+ name = "mining satchel"
+ desc = "This little bugger can be used to store and transport ores."
+ icon = 'icons/obj/mining.dmi'
+ icon_state = "satchel"
+ origin_tech = "engineering=2"
+ slot_flags = SLOT_BELT | SLOT_POCKET
+ w_class = WEIGHT_CLASS_NORMAL
+ storage_slots = 50
+ max_combined_w_class = 200 //Doesn't matter what this is, so long as it's more or equal to storage_slots * ore.w_class
+ max_w_class = WEIGHT_CLASS_NORMAL
+ can_hold = list(/obj/item/ore)
+ var/spam_protection = FALSE //If this is TRUE, the holder won't receive any messages when they fail to pick up ore through crossing it
+
+/obj/item/storage/bag/ore/cyborg
+ name = "cyborg mining satchel"
+
+/obj/item/storage/bag/ore/holding //miners, your messiah has arrived
+ name = "mining satchel of holding"
+ desc = "A revolution in convenience, this satchel allows for huge amounts of ore storage. It's been outfitted with anti-malfunction safety measures."
+ storage_slots = INFINITY
+ max_combined_w_class = INFINITY
+ origin_tech = "bluespace=4;materials=3;engineering=3"
+ icon_state = "satchel_bspace"
+
+// -----------------------------
+// Plant bag
+// -----------------------------
+
+/obj/item/storage/bag/plants
+ name = "plant bag"
+ icon = 'icons/obj/hydroponics/equipment.dmi'
+ icon_state = "plantbag"
+ storage_slots = 100; //the number of plant pieces it can carry.
+ max_combined_w_class = 100 //Doesn't matter what this is, so long as it's more or equal to storage_slots * plants.w_class
+ max_w_class = WEIGHT_CLASS_NORMAL
+ w_class = WEIGHT_CLASS_TINY
+ can_hold = list(/obj/item/reagent_containers/food/snacks/grown, /obj/item/seeds, /obj/item/grown, /obj/item/reagent_containers/honeycomb)
+ resistance_flags = FLAMMABLE
+
+////////
+
+/obj/item/storage/bag/plants/portaseeder
+ name = "portable seed extractor"
+ desc = "For the enterprising botanist on the go. Less efficient than the stationary model, it creates one seed per plant."
+ icon_state = "portaseeder"
+ origin_tech = "biotech=3;engineering=2"
+
+/obj/item/storage/bag/plants/portaseeder/verb/dissolve_contents()
+ set name = "Activate Seed Extraction"
+ set category = "Object"
+ set desc = "Activate to convert your plants into plantable seeds."
+ if(usr.stat || !usr.canmove || usr.restrained())
+ return
+ for(var/obj/item/O in contents)
+ seedify(O, 1)
+ close_all()
+
+
+// -----------------------------
+// Sheet Snatcher
+// -----------------------------
+// Because it stacks stacks, this doesn't operate normally.
+// However, making it a storage/bag allows us to reuse existing code in some places. -Sayu
+
+/obj/item/storage/bag/sheetsnatcher
+ name = "sheet snatcher"
+ desc = "A patented Nanotrasen storage system designed for any kind of mineral sheet."
+ icon = 'icons/obj/mining.dmi'
+ icon_state = "sheetsnatcher"
+
+ var/capacity = 300; //the number of sheets it can carry.
+ w_class = WEIGHT_CLASS_NORMAL
+
+ allow_quick_empty = 1 // this function is superceded
+
+/obj/item/storage/bag/sheetsnatcher/can_be_inserted(obj/item/W, stop_messages = 0)
+ if(!istype(W, /obj/item/stack/sheet) || istype(W, /obj/item/stack/sheet/mineral/sandstone) || istype(W, /obj/item/stack/sheet/mineral/wood))
+ if(!stop_messages)
+ to_chat(usr, "The snatcher does not accept [W].")
+ return 0 //I don't care, but the existing code rejects them for not being "sheets" *shrug* -Sayu
+ var/current = 0
+ for(var/obj/item/stack/sheet/S in contents)
+ current += S.amount
+ if(capacity == current)//If it's full, you're done
+ if(!stop_messages)
+ to_chat(usr, "The snatcher is full.")
+ return 0
+ return 1
+
+
+// Modified handle_item_insertion. Would prefer not to, but...
+/obj/item/storage/bag/sheetsnatcher/handle_item_insertion(obj/item/W, prevent_warning = 0)
+ var/obj/item/stack/sheet/S = W
+ if(!istype(S)) return 0
+
+ var/amount
+ var/inserted = 0
+ var/current = 0
+ for(var/obj/item/stack/sheet/S2 in contents)
+ current += S2.amount
+ if(capacity < current + S.amount)//If the stack will fill it up
+ amount = capacity - current
+ else
+ amount = S.amount
+
+ for(var/obj/item/stack/sheet/sheet in contents)
+ if(S.type == sheet.type) // we are violating the amount limitation because these are not sane objects
+ sheet.amount += amount // they should only be removed through procs in this file, which split them up.
+ S.amount -= amount
+ inserted = 1
+ break
+
+ if(!inserted || !S.amount)
+ usr.dropItemToGround(S)
+ if (usr.client && usr.s_active != src)
+ usr.client.screen -= S
+ S.dropped(usr)
+ if(!S.amount)
+ qdel(S)
+ else
+ if(S.pulledby)
+ S.pulledby.stop_pulling()
+ S.loc = src
+
+ orient2hud(usr)
+ if(usr.s_active)
+ usr.s_active.show_to(usr)
+ update_icon()
+ return 1
+
+
+// Sets up numbered display to show the stack size of each stored mineral
+// NOTE: numbered display is turned off currently because it's broken
+/obj/item/storage/bag/sheetsnatcher/orient2hud(mob/user)
+ var/adjusted_contents = contents.len
+
+ //Numbered contents display
+ var/list/datum/numbered_display/numbered_contents
+ if(display_contents_with_number)
+ numbered_contents = list()
+ adjusted_contents = 0
+ for(var/obj/item/stack/sheet/I in contents)
+ adjusted_contents++
+ var/datum/numbered_display/D = new/datum/numbered_display(I)
+ D.number = I.amount
+ numbered_contents.Add( D )
+
+ var/row_num = 0
+ var/col_count = min(7,storage_slots) -1
+ if (adjusted_contents > 7)
+ row_num = round((adjusted_contents-1) / 7) // 7 is the maximum allowed width.
+ src.standard_orient_objs(row_num, col_count, numbered_contents)
+ return
+
+
+// Modified quick_empty verb drops appropriate sized stacks
+/obj/item/storage/bag/sheetsnatcher/quick_empty()
+ var/location = get_turf(src)
+ for(var/obj/item/stack/sheet/S in contents)
+ while(S.amount)
+ var/obj/item/stack/sheet/N = new S.type(location)
+ var/stacksize = min(S.amount,N.max_amount)
+ N.amount = stacksize
+ S.amount -= stacksize
+ if(!S.amount)
+ qdel(S)// todo: there's probably something missing here
+ orient2hud(usr)
+ if(usr.s_active)
+ usr.s_active.show_to(usr)
+ update_icon()
+
+// Instead of removing
+/obj/item/storage/bag/sheetsnatcher/remove_from_storage(obj/item/W, atom/new_location)
+ var/obj/item/stack/sheet/S = W
+ if(!istype(S)) return 0
+
+ //I would prefer to drop a new stack, but the item/attack_hand code
+ // that calls this can't recieve a different object than you clicked on.
+ //Therefore, make a new stack internally that has the remainder.
+ // -Sayu
+
+ if(S.amount > S.max_amount)
+ var/obj/item/stack/sheet/temp = new S.type(src)
+ temp.amount = S.amount - S.max_amount
+ S.amount = S.max_amount
+
+ return ..(S,new_location)
+
+// -----------------------------
+// Sheet Snatcher (Cyborg)
+// -----------------------------
+
+/obj/item/storage/bag/sheetsnatcher/borg
+ name = "sheet snatcher 9000"
+ desc = ""
+ capacity = 500//Borgs get more because >specialization
+
+
+// -----------------------------
+// Book bag
+// -----------------------------
+
+/obj/item/storage/bag/books
+ name = "book bag"
+ desc = "A bag for books."
+ icon = 'icons/obj/library.dmi'
+ icon_state = "bookbag"
+ display_contents_with_number = 0 //This would look really stupid otherwise
+ storage_slots = 7
+ max_combined_w_class = 21
+ max_w_class = WEIGHT_CLASS_NORMAL
+ w_class = WEIGHT_CLASS_BULKY //Bigger than a book because physics
+ can_hold = list(/obj/item/book, /obj/item/storage/book, /obj/item/spellbook)
+ resistance_flags = FLAMMABLE
+
+/*
+ * Trays - Agouri
+ */
+/obj/item/storage/bag/tray
+ name = "tray"
+ icon = 'icons/obj/food/containers.dmi'
+ icon_state = "tray"
+ desc = "A metal tray to lay food on."
+ force = 5
+ throwforce = 10
+ throw_speed = 3
+ throw_range = 5
+ w_class = WEIGHT_CLASS_BULKY
+ flags_1 = CONDUCT_1
+ materials = list(MAT_METAL=3000)
+ preposition = "on"
+
+/obj/item/storage/bag/tray/attack(mob/living/M, mob/living/user)
+ ..()
+ // Drop all the things. All of them.
+ var/list/obj/item/oldContents = contents.Copy()
+ quick_empty()
+
+ // Make each item scatter a bit
+ for(var/obj/item/I in oldContents)
+ spawn()
+ for(var/i = 1, i <= rand(1,2), i++)
+ if(I)
+ step(I, pick(NORTH,SOUTH,EAST,WEST))
+ sleep(rand(2,4))
+
+ if(prob(50))
+ playsound(M, 'sound/items/trayhit1.ogg', 50, 1)
+ else
+ playsound(M, 'sound/items/trayhit2.ogg', 50, 1)
+
+ if(ishuman(M) || ismonkey(M))
+ if(prob(10))
+ M.Knockdown(40)
+
+/obj/item/storage/bag/tray/proc/rebuild_overlays()
+ cut_overlays()
+ for(var/obj/item/I in contents)
+ add_overlay(mutable_appearance(I.icon, I.icon_state))
+
+/obj/item/storage/bag/tray/remove_from_storage(obj/item/W as obj, atom/new_location)
+ ..()
+ rebuild_overlays()
+
+/obj/item/storage/bag/tray/handle_item_insertion(obj/item/I, prevent_warning = 0)
+ add_overlay(mutable_appearance(I.icon, I.icon_state))
+ . = ..()
+
+
+/*
+ * Chemistry bag
+ */
+
+/obj/item/storage/bag/chemistry
+ name = "chemistry bag"
+ icon = 'icons/obj/chemical.dmi'
+ icon_state = "bag"
+ desc = "A bag for storing pills, patches, and bottles."
+ storage_slots = 50
+ max_combined_w_class = 200
+ w_class = WEIGHT_CLASS_TINY
+ preposition = "in"
+ can_hold = list(/obj/item/reagent_containers/pill, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle)
+ resistance_flags = FLAMMABLE
+
+/*
+ * Biowaste bag (mostly for xenobiologists)
+ */
+
+/obj/item/storage/bag/bio
+ name = "bio bag"
+ icon = 'icons/obj/chemical.dmi'
+ icon_state = "biobag"
+ desc = "A bag for the safe transportation and disposal of biowaste and other biological materials."
+ storage_slots = 25
+ max_combined_w_class = 200
+ w_class = WEIGHT_CLASS_TINY
+ preposition = "in"
+ can_hold = list(/obj/item/slime_extract, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/blood, /obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/food/snacks/deadmouse, /obj/item/reagent_containers/food/snacks/monkeycube)
+ resistance_flags = FLAMMABLE
+>>>>>>> 51c4840... Stops putting RCD, bluespace bags, and staves into ORM (#31093)
diff --git a/code/modules/projectiles/guns/magic/staff.dm b/code/modules/projectiles/guns/magic/staff.dm
index 1f2285cdf5..3c8460b57d 100644
--- a/code/modules/projectiles/guns/magic/staff.dm
+++ b/code/modules/projectiles/guns/magic/staff.dm
@@ -2,6 +2,7 @@
slot_flags = SLOT_BACK
lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
+ flags_2 = NO_MAT_REDEMPTION_2
/obj/item/gun/magic/staff/change
name = "staff of change"