diff --git a/code/datums/supplypacks/atmospherics.dm b/code/datums/supplypacks/atmospherics.dm
index e6fb05f2207..3b93492c799 100644
--- a/code/datums/supplypacks/atmospherics.dm
+++ b/code/datums/supplypacks/atmospherics.dm
@@ -82,6 +82,14 @@
containername = "Disposal Dispenser Crate"
access = access_atmospherics
+/datum/supply_pack/atmos/rapid_pipe_dispenser
+ contains = list(/obj/item/pipe_dispenser)
+ name = "Rapid Pipe Dispenser"
+ cost = 100
+ containertype = /obj/structure/closet/crate/secure/aether
+ containername = "Rapid Pipe Dispenser Crate"
+ access = access_atmospherics
+
/datum/supply_pack/atmos/internals
name = "Internals crate"
contains = list(
diff --git a/code/game/objects/items/weapons/RPD.dm b/code/game/objects/items/weapons/RPD.dm
new file mode 100644
index 00000000000..4e32d69bb05
--- /dev/null
+++ b/code/game/objects/items/weapons/RPD.dm
@@ -0,0 +1,294 @@
+#define ATMOS_CATEGORY 0
+#define DISPOSALS_CATEGORY 1
+#define TRANSIT_CATEGORY 2
+
+#define BUILD_MODE (1<<0)
+#define WRENCH_MODE (1<<1)
+#define DESTROY_MODE (1<<2)
+#define PAINT_MODE (1<<3)
+
+/obj/item/pipe_dispenser
+ name = "Rapid Piping Device (RPD)"
+ desc = "A device used to rapidly pipe things."
+ icon = 'icons/obj/tools.dmi'
+ icon_state = "rpd"
+ item_state = "rpd"
+ item_icons = list(
+ slot_l_hand_str = 'icons/mob/items/lefthand.dmi',
+ slot_r_hand_str = 'icons/mob/items/righthand.dmi',
+ )
+ item_flags = NOBLUDGEON
+ force = 10
+ throwforce = 10
+ throw_speed = 1
+ throw_range = 5
+ w_class = ITEMSIZE_NORMAL
+ matter = list(MAT_STEEL = 50000, MAT_GLASS = 25000)
+ var/datum/effect_system/spark_spread/spark_system
+ var/p_dir = NORTH // Next pipe will be built with this dir
+ var/p_flipped = FALSE // If the next pipe should be built flipped
+ var/paint_color = "grey" // Pipe color index for next pipe painted/built.
+ var/category = ATMOS_CATEGORY
+ var/piping_layer = PIPING_LAYER_DEFAULT
+ var/obj/item/tool/wrench/tool
+ var/datum/pipe_recipe/recipe // pipe recipie selected for display/construction
+ var/static/datum/pipe_recipe/first_atmos
+ var/static/datum/pipe_recipe/first_disposal
+ var/mode = BUILD_MODE | DESTROY_MODE | WRENCH_MODE
+ var/static/list/pipe_layers = list(
+ "Regular" = PIPING_LAYER_REGULAR,
+ "Supply" = PIPING_LAYER_SUPPLY,
+ "Scrubber" = PIPING_LAYER_SCRUBBER,
+ "Fuel" = PIPING_LAYER_FUEL,
+ "Aux" = PIPING_LAYER_AUX
+ )
+
+/obj/item/pipe_dispenser/Initialize()
+ . = ..()
+ src.spark_system = new /datum/effect_system/spark_spread
+ spark_system.set_up(5, 0, src)
+ spark_system.attach(src)
+ tool = new /obj/item/tool/wrench/cyborg(src) // RPDs have wrenches inside of them, so that they can wrench down spawned pipes without being used as superior wrenches themselves.
+
+/obj/item/pipe_dispenser/proc/SetupPipes()
+ if(!first_atmos)
+ first_atmos = GLOB.atmos_pipe_recipes[GLOB.atmos_pipe_recipes[1]][1]
+ recipe = first_atmos
+ if(!first_disposal)
+ first_disposal = GLOB.disposal_pipe_recipes[GLOB.disposal_pipe_recipes[1]][1]
+
+/obj/item/pipe_dispenser/Destroy()
+ QDEL_NULL(spark_system)
+ QDEL_NULL(tool)
+ return ..()
+
+/obj/item/pipe_dispenser/attack_self(mob/user)
+ tgui_interact(user)
+
+/obj/item/pipe_dispenser/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/pipes),
+ )
+
+/obj/item/pipe_dispenser/tgui_state(mob/user)
+ return GLOB.tgui_inventory_state
+
+/obj/item/pipe_dispenser/tgui_interact(mob/user, datum/tgui/ui)
+ SetupPipes()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "RapidPipeDispenser", name)
+ ui.open()
+
+/obj/item/pipe_dispenser/tgui_data(mob/user)
+ var/list/data = list(
+ "category" = category,
+ "piping_layer" = piping_layer,
+ "pipe_layers" = pipe_layers,
+ "preview_rows" = recipe.get_preview(p_dir),
+ "categories" = list(),
+ "selected_color" = paint_color,
+ "paint_colors" = pipe_colors,
+ "mode" = mode
+ )
+
+ var/list/recipes
+ switch(category)
+ if(ATMOS_CATEGORY)
+ recipes = GLOB.atmos_pipe_recipes
+ if(DISPOSALS_CATEGORY)
+ recipes = GLOB.disposal_pipe_recipes
+ // if(TRANSIT_CATEGORY)
+ // recipes = transit_tube_recipes
+ for(var/c in recipes)
+ var/list/cat = recipes[c]
+ var/list/r = list()
+ for(var/i in 1 to cat.len)
+ var/datum/pipe_recipe/info = cat[i]
+ r += list(list("pipe_name" = info.name, "pipe_index" = i, "selected" = (info == recipe)))
+ data["categories"] += list(list("cat_name" = c, "recipes" = r))
+
+ return data
+
+/obj/item/pipe_dispenser/tgui_act(action, params)
+ SetupPipes()
+ if(..())
+ return TRUE
+ if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr))
+ return TRUE
+ var/playeffect = TRUE
+ switch(action)
+ if("color")
+ paint_color = params["paint_color"]
+ if("category")
+ category = text2num(params["category"])
+ switch(category)
+ if(DISPOSALS_CATEGORY)
+ recipe = first_disposal
+ if(ATMOS_CATEGORY)
+ recipe = first_atmos
+ // if(TRANSIT_CATEGORY)
+ // recipe = first_transit
+ p_dir = NORTH
+ playeffect = FALSE
+ if("piping_layer")
+ piping_layer = text2num(params["piping_layer"])
+ playeffect = FALSE
+ // if("ducting_layer")
+ // ducting_layer = text2num(params["ducting_layer"])
+ // playeffect = FALSE
+ if("pipe_type")
+ var/static/list/recipes
+ if(!recipes)
+ recipes = GLOB.disposal_pipe_recipes + GLOB.atmos_pipe_recipes
+ recipe = recipes[params["category"]][text2num(params["pipe_type"])]
+ p_dir = NORTH
+ if("setdir")
+ p_dir = text2dir(params["dir"])
+ p_flipped = text2num(params["flipped"])
+ playeffect = FALSE
+ if("mode")
+ var/n = text2num(params["mode"])
+ if(mode & n)
+ mode &= ~n
+ else
+ mode |= n
+ if(playeffect)
+ spark_system.start()
+ playsound(get_turf(src), 'sound/effects/pop.ogg', 50, FALSE)
+ return TRUE
+
+/obj/item/pipe_dispenser/afterattack(atom/A, mob/user as mob, proximity)
+ if(!user.IsAdvancedToolUser() || istype(A, /turf/space/transit) || !proximity)
+ return ..()
+
+ //So that changing the menu settings doesn't affect the pipes already being built.
+ var/queued_piping_layer = piping_layer
+ var/queued_p_dir = p_dir
+ var/queued_p_flipped = p_flipped
+
+ //make sure what we're clicking is valid for the current mode
+ var/static/list/make_pipe_whitelist // This should probably be changed to be in line with polaris standards. Oh well.
+ if(!make_pipe_whitelist)
+ make_pipe_whitelist = typecacheof(list(/obj/structure/lattice, /obj/structure/girder, /obj/item/pipe))
+ var/can_make_pipe = (isturf(A) || is_type_in_typecache(A, make_pipe_whitelist))
+
+ var/can_destroy_pipe = istype(A, /obj/item/pipe) || istype(A, /obj/item/pipe_meter) || istype(A, /obj/structure/disposalconstruct)
+
+ . = TRUE
+ if((mode & DESTROY_MODE) && can_destroy_pipe)
+ to_chat(user, "You start destroying a pipe...")
+ playsound(src, 'sound/machines/click.ogg', 50, 1)
+ if(do_after(user, 2, target = A))
+ activate()
+ animate_deletion(A)
+ return
+
+ if((mode & PAINT_MODE)) //Paint pipes
+ if(istype(A, /obj/machinery/atmospherics/pipe))
+ var/obj/machinery/atmospherics/pipe/P = A
+ playsound(src, 'sound/machines/click.ogg', 50, 1)
+ P.change_color(pipe_colors[paint_color])
+ user.visible_message("[user] paints \the [P] [paint_color].", "You paint \the [P] [paint_color].")
+ return
+
+ if(mode & BUILD_MODE) //Making pipes
+ switch(category)
+ if(ATMOS_CATEGORY)
+ if(!can_make_pipe)
+ return ..()
+ playsound(src, 'sound/machines/click.ogg', 50, 1)
+ if(istype(recipe, /datum/pipe_recipe/meter))
+ to_chat(user, "You start building a meter...")
+ if(do_after(user, 2, target = A))
+ activate()
+ var/obj/item/pipe_meter/PM = new /obj/item/pipe_meter(get_turf(A))
+ PM.setAttachLayer(queued_piping_layer)
+ if(mode & WRENCH_MODE)
+ do_wrench(PM, user)
+ else if(istype(recipe, /datum/pipe_recipe/pipe))
+ var/datum/pipe_recipe/pipe/R = recipe
+ to_chat(user, "You start building a pipe...")
+ if(do_after(user, 2, target = A))
+ activate()
+ var/obj/machinery/atmospherics/path = R.pipe_type
+ var/pipe_item_type = initial(path.construction_type) || /obj/item/pipe
+ var/obj/item/pipe/P = new pipe_item_type(get_turf(A), path, queued_p_dir)
+
+ P.update()
+ P.add_fingerprint(usr)
+ if(R.paintable)
+ P.color = pipe_colors[paint_color]
+ P.setPipingLayer(queued_piping_layer)
+ if(queued_p_flipped)
+ P.do_a_flip()
+ if(mode & WRENCH_MODE)
+ do_wrench(P, user)
+ else
+ build_effect(P)
+
+ if(DISPOSALS_CATEGORY) //Making disposals pipes
+ var/datum/pipe_recipe/disposal/R = recipe
+ if(!istype(R) || !can_make_pipe)
+ return ..()
+ A = get_turf(A)
+ if(istype(A, /turf/unsimulated))
+ to_chat(user, "[src]'s error light flickers; there's something in the way!")
+ return
+ to_chat(user, "You start building a disposals pipe...")
+ playsound(src, 'sound/machines/click.ogg', 50, 1)
+ if(do_after(user, 4, target = A))
+ var/obj/structure/disposalconstruct/C = new(A, R.pipe_type, queued_p_dir, queued_p_flipped, R.subtype)
+
+ if(!C.can_place())
+ to_chat(user, "There's not enough room to build that here!")
+ qdel(C)
+ return
+
+ activate()
+
+ C.add_fingerprint(usr)
+ C.update_icon()
+ if(mode & WRENCH_MODE)
+ do_wrench(C, user)
+ else
+ build_effect(C)
+
+ else
+ return ..()
+
+/obj/item/pipe_dispenser/proc/build_effect(var/obj/P, var/time = 1.5)
+ set waitfor = FALSE
+ P.filters += filter(type = "angular_blur", size = 30)
+ animate(P.filters[P.filters.len], size = 0, time = time)
+ var/outline = filter(type = "outline", size = 1, color = "#22AAFF")
+ P.filters += outline
+ sleep(time)
+ P.filters -= outline
+ P.filters -= filter(type = "angular_blur", size = 0)
+
+/obj/item/pipe_dispenser/proc/animate_deletion(var/obj/P, var/time = 1.5)
+ set waitfor = FALSE
+ P.filters += filter(type = "angular_blur", size = 0)
+ animate(P.filters[P.filters.len], size = 30, time = time)
+ sleep(time)
+ if(!QDELETED(P))
+ P.filters -= filter(type = "angular_blur", size = 30)
+ qdel(P)
+
+/obj/item/pipe_dispenser/proc/activate()
+ playsound(src, 'sound/items/deconstruct.ogg', 50, 1)
+
+/obj/item/pipe_dispenser/proc/do_wrench(var/atom/target, mob/user)
+ var/resolved = target.attackby(tool,user)
+ if(!resolved && tool && target)
+ tool.afterattack(target,user,1)
+
+#undef ATMOS_CATEGORY
+#undef DISPOSALS_CATEGORY
+#undef TRANSIT_CATEGORY
+
+#undef BUILD_MODE
+#undef WRENCH_MODE
+#undef DESTROY_MODE
+#undef PAINT_MODE
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station.dm b/code/modules/mob/living/silicon/robot/robot_modules/station.dm
index a178a60606a..46a41fb4c42 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules/station.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules/station.dm
@@ -418,6 +418,7 @@ var/global/list/robot_modules = list(
src.modules += new /obj/item/device/t_scanner(src)
src.modules += new /obj/item/device/analyzer(src)
src.modules += new /obj/item/taperoll/engineering(src)
+<<<<<<< HEAD
src.modules += new /obj/item/weapon/gripper(src)
src.modules += new /obj/item/weapon/gripper/circuit(src)
src.modules += new /obj/item/device/lightreplacer(src)
@@ -431,6 +432,20 @@ var/global/list/robot_modules = list(
src.modules += new /obj/item/weapon/rcd/electric/mounted/borg(src)
src.modules += new /obj/item/weapon/pickaxe/plasmacutter/borg(src)
src.modules += new /obj/item/weapon/gripper/no_use/loader(src)
+=======
+ src.modules += new /obj/item/gripper(src)
+ src.modules += new /obj/item/gripper/circuit(src)
+ src.modules += new /obj/item/lightreplacer(src)
+ src.modules += new /obj/item/pipe_painter(src)
+ src.modules += new /obj/item/floor_painter(src)
+ src.modules += new /obj/item/inflatable_dispenser/robot(src)
+ src.emag = new /obj/item/melee/baton/robot/arm(src)
+ src.modules += new /obj/item/geiger(src)
+ src.modules += new /obj/item/rcd/electric/mounted/borg(src)
+ src.modules += new /obj/item/pipe_dispenser(src)
+ src.modules += new /obj/item/pickaxe/plasmacutter(src)
+ src.modules += new /obj/item/gripper/no_use/loader(src)
+>>>>>>> 7402020403d... Finally allows RPD to be used. (#9028)
var/datum/matter_synth/metal = new /datum/matter_synth/metal(40000)
var/datum/matter_synth/glass = new /datum/matter_synth/glass(40000)
@@ -945,7 +960,13 @@ var/global/list/robot_modules = list(
P.synths = list(plastic)
src.modules += P
+<<<<<<< HEAD
/obj/item/weapon/robot_module/drone/construction
+=======
+ src.modules += new /obj/item/pipe_dispenser(src) // At the end to go beside the construction's RCD.
+
+/obj/item/robot_module/drone/construction
+>>>>>>> 7402020403d... Finally allows RPD to be used. (#9028)
name = "construction drone module"
hide_on_manifest = TRUE
channels = list("Engineering" = 1)