From 9eb7413a75babfd0977efb45a164eea0f76734ad Mon Sep 17 00:00:00 2001 From: mineymonkey Date: Sat, 1 Aug 2026 20:24:58 -0500 Subject: [PATCH] RPED Update (#22750) RPEDS now work on most machinery via attackby() handler. Part matching checks circuit-board requirements and the machine's component_types which fixes the science telepad RPED compatability. Alt-clicking an autolathe with an RPED now recycles the materials in the autolathe. Normal lmb activity is preserved on the autolathe. Signed-off-by: mineymonkey --- code/game/objects/structures/_machinery.dm | 56 +++++++++++++------ code/modules/fabrication/_fabricator.dm | 25 +++++++++ code/modules/fabrication/fabricator_intake.dm | 33 ++++++----- .../fabricators/fabricator_autolathe.dm | 23 ++++++++ code/modules/telesci/telepad.dm | 3 + html/changelogs/Mineymonkey-RPED-Changes.yml | 7 +++ 6 files changed, 117 insertions(+), 30 deletions(-) create mode 100644 html/changelogs/Mineymonkey-RPED-Changes.yml diff --git a/code/game/objects/structures/_machinery.dm b/code/game/objects/structures/_machinery.dm index 5b46b762635..fe4efc876cc 100644 --- a/code/game/objects/structures/_machinery.dm +++ b/code/game/objects/structures/_machinery.dm @@ -370,6 +370,9 @@ Class Procs: attack_hand(user) /obj/structure/machinery/attackby(obj/item/attacking_item, mob/user) + if(default_part_replacement(user, attacking_item)) + return TRUE + if(obj_flags & OBJ_FLAG_SIGNALER) if(issignaler(attacking_item)) if(signaler) @@ -488,23 +491,43 @@ Class Procs: update_icon() return TRUE +/obj/structure/machinery/proc/get_part_replacement_type(obj/item/part) + var/obj/item/circuitboard/board = locate(/obj/item/circuitboard) in component_parts + var/replacement_type + + if(board?.req_components) + for(var/component_type in board.req_components) + var/board_part_type = ispath(component_type) ? component_type : text2path(component_type) + if(!ispath(board_part_type, /obj/item/stock_parts) && !ispath(board_part_type, /obj/item/reagent_containers/glass)) + continue + if(istype(part, board_part_type) && (!replacement_type || ispath(board_part_type, replacement_type))) + replacement_type = board_part_type + + if(replacement_type) + return replacement_type + + for(var/component_type in component_types) + var/component_part_type = ispath(component_type) ? component_type : text2path(component_type) + if(!ispath(component_part_type, /obj/item/stock_parts) && !ispath(component_part_type, /obj/item/reagent_containers/glass)) + continue + if(istype(part, component_part_type) && (!replacement_type || ispath(component_part_type, replacement_type))) + replacement_type = component_part_type + + return replacement_type + /obj/structure/machinery/proc/default_part_replacement(var/mob/user, var/obj/item/storage/part_replacer/R) if(!LAZYLEN(component_parts)) return FALSE else if(istype(R)) var/parts_replaced = FALSE if(panel_open) - var/obj/item/circuitboard/CB = locate(/obj/item/circuitboard) in component_parts - var/P for(var/obj/item/reagent_containers/glass/G in component_parts) - for(var/D in CB.req_components) - var/T = text2path(D) - if(ispath(G.type, T)) - P = T - break + var/replacement_type = get_part_replacement_type(G) + if(!replacement_type) + continue for(var/obj/item/reagent_containers/glass/B in R.contents) if(B.reagents && B.reagents.total_volume > 0) continue - if(istype(B, P) && istype(G, P)) + if(istype(B, replacement_type)) if(B.volume > G.volume) R.remove_from_storage(B, src) R.handle_item_insertion(G, 1) @@ -512,15 +535,14 @@ Class Procs: component_parts += B B.forceMove(src) to_chat(user, SPAN_NOTICE("[G.name] replaced with [B.name].")) + parts_replaced = TRUE break for(var/obj/item/stock_parts/A in component_parts) - for(var/D in CB.req_components) - var/T = text2path(D) - if(ispath(A.type, T)) - P = T - break + var/replacement_type = get_part_replacement_type(A) + if(!replacement_type) + continue for(var/obj/item/stock_parts/B in R.contents) - if(istype(B, P) && istype(A, P)) + if(istype(B, replacement_type)) if(B.rating > A.rating) R.remove_from_storage(B, src) R.handle_item_insertion(A, 1) @@ -530,9 +552,9 @@ Class Procs: to_chat(user, SPAN_NOTICE("[A.name] replaced with [B.name].")) parts_replaced = TRUE break - RefreshParts() - update_icon() - if(parts_replaced) //only play sound when RPED actually replaces parts + if(parts_replaced) + RefreshParts() + update_icon() playsound(src, 'sound/items/rped.ogg', 40, TRUE) return TRUE else diff --git a/code/modules/fabrication/_fabricator.dm b/code/modules/fabrication/_fabricator.dm index 7cb8853658e..39dc6200199 100644 --- a/code/modules/fabrication/_fabricator.dm +++ b/code/modules/fabrication/_fabricator.dm @@ -188,6 +188,7 @@ ABSTRACT_TYPE(/obj/structure/machinery/fabricator) return TRUE if(default_deconstruction_crowbar(user, attacking_item)) return TRUE + if(default_part_replacement(user, attacking_item)) return TRUE @@ -212,6 +213,30 @@ ABSTRACT_TYPE(/obj/structure/machinery/fabricator) load_lathe(attacking_item, user) return TRUE +/obj/structure/machinery/fabricator/autolathe/proc/recycle_rped_contents(obj/item/storage/part_replacer/R, mob/user) + var/recycled = 0 + var/recyclable = 0 + var/rejected = 0 + + for(var/obj/item/item in R.contents.Copy()) + if(!item.matter || !item.recyclable) + continue + recyclable++ + if(load_lathe(item, user, FALSE)) + recycled++ + else + rejected++ + + if(recycled) + to_chat(user, SPAN_NOTICE("You recycle [recycled] item\s from \the [R] into \the [src].")) + else if(!recyclable) + to_chat(user, SPAN_WARNING("\The [R] contains no recyclable materials for \the [src].")) + else + to_chat(user, SPAN_WARNING("\The [src] could not accept any recyclable contents from \the [R].")) + + if(rejected) + to_chat(user, SPAN_WARNING("Some contents could not be accepted and remain in \the [R].")) + /obj/structure/machinery/fabricator/attack_hand(mob/user) user.set_machine(src) ui_interact(user) diff --git a/code/modules/fabrication/fabricator_intake.dm b/code/modules/fabrication/fabricator_intake.dm index 18c131f066f..4aedbffe3d1 100644 --- a/code/modules/fabrication/fabricator_intake.dm +++ b/code/modules/fabrication/fabricator_intake.dm @@ -3,13 +3,14 @@ #define FILL_INCOMPLETELY "Fill Incompletely" ///Loads the lathe with materials -/obj/structure/machinery/fabricator/proc/load_lathe(obj/item/loading_item, mob/user) +/obj/structure/machinery/fabricator/proc/load_lathe(obj/item/loading_item, mob/user, show_messages = TRUE) //Resources are being loaded. var/obj/item/eating = loading_item if(!eating.matter || !eating.recyclable) - to_chat(user, SPAN_WARNING("\The [eating] cannot be recycled by \the [src].")) - return + if(show_messages) + to_chat(user, SPAN_WARNING("\The [eating] cannot be recycled by \the [src].")) + return FALSE var/list/fill_status = list() // Used to determine message in cases of multiple materials. var/total_used = 0 // Amount of material used. @@ -44,17 +45,21 @@ total_used += total_material mass_per_sheet += eating.matter[material] - if(total_used <= 0 || mass_per_sheet <= 0) - if(fill_status[NO_SPACE]) - to_chat(user, SPAN_WARNING("\The [src] is full of [english_list(fill_status[NO_SPACE])]. Please remove some material in order to insert more.")) - else - to_chat(user, SPAN_WARNING("\The [src] cannot accept any materials from \the [eating].")) - return + if(total_used <= 0) + if(show_messages) + if(fill_status[NO_SPACE]) + to_chat(user, SPAN_WARNING("\The [src] is full of [english_list(fill_status[NO_SPACE])]. Please remove some material in order to insert more.")) + else + to_chat(user, SPAN_WARNING("\The [eating] contains no materials accepted by \the [src].")) + return FALSE - if(fill_status[FILL_COMPLETELY]) - to_chat(user, SPAN_NOTICE("You fill \the [src] to capacity with [english_list(fill_status[FILL_COMPLETELY])][is_stack ? "." : " from \the [eating]."]")) - else if(fill_status[FILL_INCOMPLETELY]) - to_chat(user, SPAN_NOTICE("You fill \the [src] with [english_list(fill_status[FILL_INCOMPLETELY])][is_stack ? "." : " from \the [eating]."]")) + if(show_messages) + if(fill_status[NO_SPACE]) + to_chat(user, SPAN_WARNING("\The [src] could not accept [english_list(fill_status[NO_SPACE])] from \the [eating].")) + if(fill_status[FILL_COMPLETELY]) + to_chat(user, SPAN_NOTICE("You fill \the [src] to capacity with [english_list(fill_status[FILL_COMPLETELY])][is_stack ? "." : " from \the [eating]."]")) + else if(fill_status[FILL_INCOMPLETELY]) + to_chat(user, SPAN_NOTICE("You fill \the [src] with [english_list(fill_status[FILL_INCOMPLETELY])][is_stack ? "." : " from \the [eating]."]")) // Plays metal insertion animation. if(istype(eating, /obj/item/stack/material) && does_flick) @@ -76,6 +81,8 @@ user.remove_from_mob(loading_item) qdel(loading_item) + return TRUE + #undef NO_SPACE #undef FILL_COMPLETELY #undef FILL_INCOMPLETELY diff --git a/code/modules/fabrication/fabricators/fabricator_autolathe.dm b/code/modules/fabrication/fabricators/fabricator_autolathe.dm index 6b8458ba8e7..ae2bc6e6380 100644 --- a/code/modules/fabrication/fabricators/fabricator_autolathe.dm +++ b/code/modules/fabrication/fabricators/fabricator_autolathe.dm @@ -4,6 +4,29 @@ icon = 'icons/obj/machinery/fabricators/autolathe.dmi' icon_state = "autolathe" +/obj/structure/machinery/fabricator/autolathe/mechanics_hints(mob/user, distance, is_adjacent) + . = ..() + . += "Alt-click with a rapid part exchange device in your active hand to recycle its compatible contents." + +/obj/structure/machinery/fabricator/autolathe/AltClick(mob/user) + . = ..() + if(use_check_and_message(user)) + return + + var/obj/item/storage/part_replacer/R = user.get_active_hand() + if(!istype(R)) + return + + if(fab_status_flags & FAB_BUSY) + to_chat(user, SPAN_NOTICE("\The [src] is busy. Please wait for the completion of the previous operation.")) + return + + if(stat || !is_functioning()) + to_chat(user, SPAN_WARNING("\The [src] cannot accept materials in its current state.")) + return + + recycle_rped_contents(R, user) + /obj/structure/machinery/fabricator/autolathe/mounted name = "\improper mounted autolathe" density = FALSE diff --git a/code/modules/telesci/telepad.dm b/code/modules/telesci/telepad.dm index 7dc71bcb19a..5ec0fcd49be 100644 --- a/code/modules/telesci/telepad.dm +++ b/code/modules/telesci/telepad.dm @@ -35,6 +35,9 @@ if(default_deconstruction_screwdriver(user, attacking_item)) return + if(default_part_replacement(user, attacking_item)) + return + if(panel_open) if(attacking_item.tool_behaviour == TOOL_MULTITOOL) var/obj/item/multitool/M = attacking_item diff --git a/html/changelogs/Mineymonkey-RPED-Changes.yml b/html/changelogs/Mineymonkey-RPED-Changes.yml new file mode 100644 index 00000000000..540549053ef --- /dev/null +++ b/html/changelogs/Mineymonkey-RPED-Changes.yml @@ -0,0 +1,7 @@ +author: Mineymonkey + +delete-after: True + +changes: + - bugfix: "Rapid part exchange devices can now upgrade stock parts in all compatible machinery, including science telepads." + - qol: "Alt-clicking an autolathe with a rapid part exchange device in your active hand now recycles its compatible contents, leaving rejected items inside."