diff --git a/code/defines/obj/weapon.dm b/code/defines/obj/weapon.dm
index 8be56acbf3..ef15cfb26f 100644
--- a/code/defines/obj/weapon.dm
+++ b/code/defines/obj/weapon.dm
@@ -297,7 +297,7 @@
/obj/item/weapon/storage/part_replacer
name = "rapid part exchange device"
- desc = "Special mechanical module made to store, sort, and apply standard machine parts."
+ desc = "A special mechanical module made to store, sort, and apply standard machine parts."
icon_state = "RPED"
w_class = ITEMSIZE_HUGE
can_hold = list(/obj/item/weapon/stock_parts)
@@ -311,10 +311,11 @@
max_storage_space = 100
drop_sound = 'sound/items/drop/device.ogg'
pickup_sound = 'sound/items/pickup/device.ogg'
+ var/panel_req = TRUE
/obj/item/weapon/storage/part_replacer/adv
name = "advanced rapid part exchange device"
- desc = "Special mechanical module made to store, sort, and apply standard machine parts. This one has a greatly upgraded storage capacity"
+ desc = "A special mechanical module made to store, sort, and apply standard machine parts. This one has a greatly upgraded storage capacity."
icon_state = "RPED"
w_class = ITEMSIZE_HUGE
can_hold = list(/obj/item/weapon/stock_parts)
@@ -327,6 +328,31 @@
max_w_class = ITEMSIZE_NORMAL
max_storage_space = 400
+/obj/item/weapon/storage/part_replacer/adv/discount_bluespace
+ name = "discount bluespace rapid part exchange device"
+ desc = "A special mechanical module made to store, sort, and apply standard machine parts. This one has a further increased storage capacity, \
+ and the ability to work on machines with closed maintenance panels."
+ storage_slots = 400
+ max_storage_space = 800
+ panel_req = FALSE
+
+/obj/item/weapon/storage/part_replacer/drop_contents() // hacky-feeling tier-based drop system
+ hide_from(usr)
+ var/turf/T = get_turf(src)
+ var/lowest_rating = INFINITY // We want the lowest-part tier rating in the RPED so we only drop the lowest-tier parts.
+ /*
+ * Why not just use the stock part's rating variable?
+ * Future-proofing for a potential future where stock parts aren't the only thing that can fit in an RPED.
+ * see: /tg/ and /vg/'s RPEDs fitting power cells, beakers, etc.
+ */
+ for(var/obj/item/B in contents)
+ if(B.rped_rating() < lowest_rating)
+ lowest_rating = B.rped_rating()
+ for(var/obj/item/B in contents)
+ if(B.rped_rating() > lowest_rating)
+ continue
+ remove_from_storage(B, T)
+
/obj/item/weapon/stock_parts
name = "stock part"
desc = "What?"
@@ -342,6 +368,9 @@
src.pixel_y = rand(-5.0, 5)
..()
+/obj/item/weapon/stock_parts/get_rating()
+ return rating
+
//Rank 1
/obj/item/weapon/stock_parts/console_screen
diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm
index fe656d9e4a..a96ef4ed0a 100644
--- a/code/game/machinery/machinery.dm
+++ b/code/game/machinery/machinery.dm
@@ -327,7 +327,10 @@ Class Procs:
return 0
if(!component_parts)
return 0
- if(panel_open)
+ to_chat(user, "Following parts detected in [src]:")
+ for(var/obj/item/C in component_parts)
+ to_chat(user, " [C.name]")
+ if(panel_open || !R.panel_req)
var/obj/item/weapon/circuitboard/CB = circuit
var/P
for(var/obj/item/weapon/stock_parts/A in component_parts)
@@ -347,10 +350,6 @@ Class Procs:
break
update_icon()
RefreshParts()
- else
- to_chat(user, "Following parts detected in the machine:")
- for(var/var/obj/item/C in component_parts) //var/var/obj/item/C?
- to_chat(user, " [C.name]")
return 1
// Default behavior for wrenching down machines. Supports both delay and instant modes.
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index e90cf3aaea..6da2255e1b 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -942,3 +942,12 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
/obj/item/proc/openTip(location, control, params, user)
openToolTip(user, src, params, title = name, content = desc)
+
+// These procs are for RPEDs and part ratings. The concept for this was borrowed from /vg/station.
+// Gets the rating of the item, used in stuff like machine construction.
+/obj/item/proc/get_rating()
+ return FALSE
+
+// Like the above, but used for RPED sorting of parts.
+/obj/item/proc/rped_rating()
+ return get_rating()
\ No newline at end of file
diff --git a/code/game/objects/items/weapons/storage/storage.dm b/code/game/objects/items/weapons/storage/storage.dm
index 4759b06667..64de9b68a2 100644
--- a/code/game/objects/items/weapons/storage/storage.dm
+++ b/code/game/objects/items/weapons/storage/storage.dm
@@ -523,9 +523,11 @@
if(((!(ishuman(usr) || isrobot(usr))) && (src.loc != usr)) || usr.stat || usr.restrained())
return
+ drop_contents()
- var/turf/T = get_turf(src)
+/obj/item/weapon/storage/proc/drop_contents() // why is this a proc? literally just for RPEDs
hide_from(usr)
+ var/turf/T = get_turf(src)
for(var/obj/item/I in contents)
remove_from_storage(I, T)
diff --git a/code/modules/research/destructive_analyzer.dm b/code/modules/research/destructive_analyzer.dm
index 78d8f9d323..005c0f07d1 100644
--- a/code/modules/research/destructive_analyzer.dm
+++ b/code/modules/research/destructive_analyzer.dm
@@ -15,6 +15,7 @@ Note: Must be placed within 3 tiles of the R&D Console
use_power = USE_POWER_IDLE
idle_power_usage = 30
active_power_usage = 2500
+ var/rped_recycler_ready = TRUE
/obj/machinery/r_n_d/destructive_analyzer/Initialize()
. = ..()
@@ -22,7 +23,7 @@ Note: Must be placed within 3 tiles of the R&D Console
/obj/machinery/r_n_d/destructive_analyzer/RefreshParts()
var/T = 0
- for(var/obj/item/weapon/stock_parts/S in src)
+ for(var/obj/item/weapon/stock_parts/S in component_parts)
T += S.rating
decon_mod = T * 0.1
@@ -76,3 +77,44 @@ Note: Must be placed within 3 tiles of the R&D Console
busy = 0
return 1
return
+
+/obj/machinery/r_n_d/destructive_analyzer/MouseDrop_T(atom/dropping, mob/living/user)
+ if(istype(dropping, /obj/item/weapon/storage/part_replacer))
+ var/obj/item/weapon/storage/part_replacer/replacer = dropping
+ replacer.hide_from(user)
+ if(!linked_console)
+ to_chat(user, "\The [src] must be linked to an R&D console first.")
+ return 0
+ if(!linked_console.linked_lathe)
+ to_chat(user, "Link a protolathe to [src]'s R&D console first.")
+ return 0
+ if(!rped_recycler_ready)
+ to_chat(user, "\The [src]'s stock parts recycler isn't ready yet.")
+ return 0
+ var/obj/machinery/r_n_d/protolathe/lathe_to_fill = linked_console.linked_lathe
+ var/lowest_rating = INFINITY // We want the lowest-part tier rating in the RPED so we only recycle the lowest-tier parts.
+ for(var/obj/item/B in replacer.contents)
+ if(B.rped_rating() < lowest_rating)
+ lowest_rating = B.rped_rating()
+ if(lowest_rating == INFINITY)
+ to_chat(user, "Mass part deconstruction attempt canceled - no valid parts for recycling detected.")
+ return 0
+ for(var/obj/item/B in replacer.contents)
+ if(B.rped_rating() > lowest_rating)
+ continue
+ if(lathe_to_fill && B.matter) // Sending salvaged materials to the lathe...
+ for(var/t in B.matter)
+ if(t in lathe_to_fill.materials)
+ lathe_to_fill.materials[t] += B.matter[t] * src.decon_mod
+ qdel(B)
+ playsound(get_turf(src), 'sound/machines/click.ogg', 50, 1)
+ rped_recycler_ready = FALSE
+ addtimer(CALLBACK(src, .proc/rped_ready), 5 SECONDS)
+ to_chat(user, "You deconstruct all the parts of rating [lowest_rating] in [replacer] with [src].")
+ return 1
+ else
+ ..()
+
+/obj/machinery/r_n_d/destructive_analyzer/proc/rped_ready()
+ rped_recycler_ready = TRUE
+ playsound(get_turf(src), 'sound/machines/chime.ogg', 50, 1)
\ No newline at end of file