diff --git a/code/__DEFINES/plumbing.dm b/code/__DEFINES/plumbing.dm
new file mode 100644
index 000000000000..bfc781e02f8a
--- /dev/null
+++ b/code/__DEFINES/plumbing.dm
@@ -0,0 +1,7 @@
+#define FIRST_DUCT_LAYER 1
+#define SECOND_DUCT_LAYER 2
+#define THIRD_DUCT_LAYER 4
+#define FOURTH_DUCT_LAYER 8
+#define FIFTH_DUCT_LAYER 16
+
+#define DUCT_LAYER_DEFAULT THIRD_DUCT_LAYER
\ No newline at end of file
diff --git a/code/controllers/subsystem/processing/fluids.dm b/code/controllers/subsystem/processing/fluids.dm
new file mode 100644
index 000000000000..c4fa13d69399
--- /dev/null
+++ b/code/controllers/subsystem/processing/fluids.dm
@@ -0,0 +1,5 @@
+PROCESSING_SUBSYSTEM_DEF(fluids)
+ name = "Fluids"
+ wait = 20
+ stat_tag = "FD" //its actually Fluid Ducts
+ flags = SS_NO_INIT | SS_TICKER
diff --git a/code/datums/components/plumbing/plumbing.dm b/code/datums/components/plumbing/plumbing.dm
new file mode 100644
index 000000000000..741997078ac9
--- /dev/null
+++ b/code/datums/components/plumbing/plumbing.dm
@@ -0,0 +1,173 @@
+/datum/component/plumbing
+ var/list/datum/ductnet/ducts = list() //Index with "1" = /datum/ductnet/theductpointingnorth etc. "1" being the num2text from NORTH define
+ var/datum/reagents/reagents
+ var/use_overlays = TRUE //TRUE if we wanna add proper pipe outless under our parent object
+ var/list/image/ducterlays //We can't just cut all of the parents' overlays, so we'll track them here
+
+ var/supply_connects //directions in wich we act as a supplier
+ var/demand_connects //direction in wich we act as a demander
+
+ var/active = FALSE //FALSE to pretty much just not exist in the plumbing world so we can be moved, TRUE to go plumbo mode
+ var/turn_connects = TRUE
+
+/datum/component/plumbing/Initialize(start=TRUE, _turn_connects=TRUE) //turn_connects for wheter or not we spin with the object to change our pipes
+ if(parent && !ismovableatom(parent))
+ return COMPONENT_INCOMPATIBLE
+ var/atom/movable/AM = parent
+ if(!AM.reagents)
+ return COMPONENT_INCOMPATIBLE
+ reagents = AM.reagents
+ turn_connects = _turn_connects
+
+ RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED,COMSIG_PARENT_PREQDELETED), .proc/disable)
+
+ if(start)
+ start()
+
+ if(use_overlays)
+ create_overlays()
+
+/datum/component/plumbing/process()
+ if(!demand_connects || !reagents)
+ STOP_PROCESSING(SSfluids, src)
+ return
+ if(reagents.total_volume < reagents.maximum_volume)
+ for(var/D in GLOB.cardinals)
+ if(D & demand_connects)
+ send_request(D)
+
+/datum/component/plumbing/proc/can_add(datum/ductnet/D, dir)
+ if(!active)
+ return
+ if(!dir || !D)
+ return FALSE
+ if(num2text(dir) in ducts)
+ return FALSE
+
+ return TRUE
+
+/datum/component/plumbing/proc/send_request(dir) //this should usually be overwritten when dealing with custom pipes
+ process_request(amount = 10, reagent = null, dir = dir)
+
+/datum/component/plumbing/proc/process_request(amount, reagent, dir)
+ var/list/valid_suppliers = list()
+ var/datum/ductnet/net
+ if(!ducts.Find(num2text(dir)))
+ return
+ net = ducts[num2text(dir)]
+ for(var/A in net.suppliers)
+ var/datum/component/plumbing/supplier = A
+ if(supplier.can_give(amount, reagent))
+ valid_suppliers += supplier
+ for(var/A in valid_suppliers)
+ var/datum/component/plumbing/give = A
+ give.transfer_to(src, amount / valid_suppliers.len, reagent)
+
+/datum/component/plumbing/proc/can_give(amount, reagent)
+ if(!reagents || amount <= 0)
+ return
+
+ if(reagent) //only asked for one type of reagent
+ if(reagent in reagents.reagent_list)
+ return TRUE
+ else if(reagents.total_volume > 0) //take whatever
+ return TRUE
+
+/datum/component/plumbing/proc/transfer_to(datum/component/plumbing/target, amount, reagent)
+ if(!reagents || !target || !target.reagents)
+ return FALSE
+ if(reagent)
+ reagents.trans_id_to(target.reagents, reagent, amount)
+ else
+ reagents.trans_to(target.reagents, amount)
+
+/datum/component/plumbing/proc/create_overlays()
+ var/atom/movable/AM = parent
+ for(var/image/I in ducterlays)
+ AM.overlays.Remove(I)
+ qdel(I)
+ ducterlays = list()
+ for(var/D in GLOB.cardinals)
+ var/color
+ var/direction
+ if(D & demand_connects)
+ color = "red" //red because red is mean and it takes
+ else if(D & supply_connects)
+ color = "blue" //blue is nice and gives
+ else
+ continue
+ var/image/I
+ if(turn_connects)
+ switch(D)
+ if(NORTH)
+ direction = "north"
+ if(SOUTH)
+ direction = "south"
+ if(EAST)
+ direction = "east"
+ if(WEST)
+ direction = "west"
+ I = image('icons/obj/plumbing/plumbers.dmi', "[direction]-[color]", layer = AM.layer - 1)
+ else
+ I = image('icons/obj/plumbing/plumbers.dmi', color, layer = AM.layer - 1) //color is not color as in the var, it's just the name
+ I.dir = D
+ AM.add_overlay(I)
+ ducterlays += I
+
+/datum/component/plumbing/proc/disable() //we stop acting like a plumbing thing and disconnect if we are, so we can safely be moved and stuff
+ if(!active)
+ return
+ STOP_PROCESSING(SSfluids, src)
+ for(var/A in ducts)
+ var/datum/ductnet/D = ducts[A]
+ D.remove_plumber(src)
+
+ active = FALSE
+
+/datum/component/plumbing/proc/start() //settle wherever we are, and start behaving like a piece of plumbing
+ if(active)
+ return
+ update_dir()
+ active = TRUE
+
+ if(demand_connects)
+ START_PROCESSING(SSfluids, src)
+
+ for(var/D in GLOB.cardinals)
+ if(D & (demand_connects + supply_connects))
+ for(var/obj/machinery/duct/duct in get_step(parent, D))
+ var/turned_dir = turn(D, 180)
+ if(turned_dir & duct.connects)
+ duct.attempt_connect()
+
+ //TODO: Let plumbers directly plumb into one another without ducts if placed adjacent to each other
+
+/datum/component/plumbing/proc/update_dir() //note that this is only called when we settle down. If someone wants it to fucking spin while connected to something go actually knock yourself out
+ if(!turn_connects)
+ return
+ var/atom/movable/AM = parent
+ var/new_demand_connects
+ var/new_supply_connects
+ var/new_dir = AM.dir
+ var/angle = 180 - dir2angle(new_dir)
+ if(new_dir == SOUTH)
+ demand_connects = initial(demand_connects)
+ supply_connects = initial(supply_connects)
+ else
+ for(var/D in GLOB.cardinals)
+ if(D & initial(demand_connects))
+ new_demand_connects += turn(D, angle)
+ if(D & initial(supply_connects))
+ new_supply_connects += turn(D, angle)
+ demand_connects = new_demand_connects
+ supply_connects = new_supply_connects
+
+/datum/component/plumbing/simple_demand
+ demand_connects = NORTH
+
+/datum/component/plumbing/simple_supply
+ supply_connects = NORTH
+
+/datum/component/plumbing/tank
+ demand_connects = WEST
+ supply_connects = EAST
\ No newline at end of file
diff --git a/code/datums/ductnet.dm b/code/datums/ductnet.dm
new file mode 100644
index 000000000000..d9e26bcfa23f
--- /dev/null
+++ b/code/datums/ductnet.dm
@@ -0,0 +1,60 @@
+/datum/ductnet
+ var/list/suppliers = list()
+ var/list/demanders = list()
+ var/list/obj/machinery/duct/ducts = list()
+
+ var/capacity
+
+/datum/ductnet/proc/add_duct(obj/machinery/duct/D)
+ if(!D || D in ducts)
+ return
+ ducts += D
+ D.duct = src
+
+/datum/ductnet/proc/remove_duct(obj/machinery/duct/ducting)
+ destroy_network(FALSE)
+ for(var/A in ducting.neighbours)
+ var/obj/machinery/duct/D = A
+ D.attempt_connect() //we destroyed the network, so now we tell the disconnected ducts neighbours they can start making a new ductnet
+ qdel(src)
+
+/datum/ductnet/proc/add_plumber(datum/component/plumbing/P, dir)
+ if(!P.can_add(src, dir))
+ return
+ P.ducts[num2text(dir)] = src
+ if(dir & P.supply_connects)
+ suppliers += P
+ else if(dir & P.demand_connects)
+ demanders += P
+
+/datum/ductnet/proc/remove_plumber(datum/component/plumbing/P)
+ suppliers.Remove(P) //we're probably only in one of these, but Remove() is inherently sane so this is fine
+ demanders.Remove(P)
+
+ for(var/dir in P.ducts)
+ if(P.ducts[dir] == src)
+ P.ducts -= dir
+
+/datum/ductnet/proc/assimilate(datum/ductnet/D)
+ ducts.Add(D.ducts)
+ suppliers.Add(D.suppliers)
+ demanders.Add(D.demanders)
+ for(var/A in D.suppliers + D.demanders)
+ var/datum/component/plumbing/P = A
+ for(var/s in P.ducts)
+ if(P.ducts[s] != D)
+ continue
+ P.ducts[s] = src //all your ducts are belong to us
+ for(var/A in D.ducts)
+ var/obj/machinery/duct/M = A
+ M.duct = src //forget your old master
+ qdel(D)
+
+/datum/ductnet/proc/destroy_network(delete=TRUE)
+ for(var/A in suppliers + demanders)
+ remove_plumber(A)
+ for(var/A in ducts)
+ var/obj/machinery/duct/D = A
+ D.duct = null
+ if(delete) //I don't want code to run with qdeleted objects because that can never be good, so keep this in-case the ductnet has some business left to attend to before commiting suicide
+ qdel(src)
\ No newline at end of file
diff --git a/code/game/objects/structures/lavaland/geyser.dm b/code/game/objects/structures/lavaland/geyser.dm
new file mode 100644
index 000000000000..6d525570bc23
--- /dev/null
+++ b/code/game/objects/structures/lavaland/geyser.dm
@@ -0,0 +1,72 @@
+//If you look at the "geyser_soup" overlay icon_state, you'll see that the first frame has 25 ticks.
+//That's because the first 18~ ticks are completely skipped for some ungodly weird fucking byond reason
+
+/obj/structure/geyser
+ name = "geyser"
+ icon = 'icons/obj/lavaland/terrain.dmi'
+ icon_state = "geyser"
+ anchored = TRUE
+
+ var/erupting_state = null //set to null to get it greyscaled from "[icon_state]_soup". Not very usable with the whole random thing, but more types can be added if you change the spawn prob
+ var/activated = FALSE //whether we are active and generating chems
+ var/reagent_id = /datum/reagent/oil
+ var/potency = 2 //how much reagents we add every process (2 seconds)
+ var/max_volume = 500
+ var/start_volume = 50
+
+/obj/structure/geyser/proc/start_chemming()
+ activated = TRUE
+ create_reagents(max_volume, DRAINABLE)
+ reagents.add_reagent(reagent_id, start_volume)
+ START_PROCESSING(SSfluids, src) //It's main function is to be plumbed, so use SSfluids
+ if(erupting_state)
+ icon_state = erupting_state
+ else
+ var/mutable_appearance/I = mutable_appearance('icons/obj/lavaland/terrain.dmi', "[icon_state]_soup")
+ I.color = mix_color_from_reagents(reagents.reagent_list)
+ add_overlay(I)
+
+/obj/structure/geyser/process()
+ if(activated && reagents.total_volume <= reagents.maximum_volume) //this is also evaluated in add_reagent, but from my understanding proc calls are expensive and should be avoided in continous
+ reagents.add_reagent(reagent_id, potency) //processes
+
+/obj/structure/geyser/plunger_act(obj/item/plunger/P, mob/living/user, _reinforced)
+ if(!_reinforced)
+ to_chat(user, "The [P.name] isn't strong enough!")
+ return
+ if(activated)
+ to_chat(user, "The [name] is already active!")
+ return
+
+ to_chat(user, "You start vigorously plunging [src]!")
+ if(do_after(user, 50*P.plunge_mod, target = src) && !activated)
+ start_chemming()
+
+/obj/structure/geyser/random
+ erupting_state = null
+ var/list/options = list(/datum/reagent/oil = 2, /datum/reagent/clf3 = 1) //fucking add more
+
+/obj/structure/geyser/random/Initialize()
+ . = ..()
+ reagent_id = pickweight(options)
+
+/obj/item/plunger
+ name = "plunger"
+ desc = "It's a plunger for plunging."
+ icon = 'icons/obj/watercloset.dmi'
+ icon_state = "plunger"
+
+ var/plunge_mod = 1 //time*plunge_mod = total time we take to plunge an object
+ var/reinforced = FALSE //whether we do heavy duty stuff like geysers
+
+/obj/item/plunger/attack_obj(obj/O, mob/living/user)
+ if(!O.plunger_act(src, user, reinforced))
+ return ..()
+
+/obj/item/plunger/reinforced
+ name = "reinforced plunger"
+ desc = " It's an M. 7 Reinforced Plunger© for heavy duty plunging."
+ icon_state = "reinforced_plunger"
+
+ reinforced = TRUE
+ plunge_mod = 0.8
diff --git a/code/modules/plumbing/ducts.dm b/code/modules/plumbing/ducts.dm
new file mode 100644
index 000000000000..b9caf198b64d
--- /dev/null
+++ b/code/modules/plumbing/ducts.dm
@@ -0,0 +1,279 @@
+/*
+All the important duct code:
+/code/datums/components/plumbing/plumbing.dm
+/code/datums/ductnet.dm
+*/
+/obj/machinery/duct
+ name = "fluid duct"
+ icon = 'icons/obj/plumbing/fluid_ducts.dmi'
+ icon_state = "nduct"
+
+ var/connects
+ var/dumb = FALSE //set to TRUE to disable smart cable behaviour
+ var/lock_connects = FALSE //wheter we allow our connects to be changed after initialization or not
+ var/datum/ductnet/duct
+ var/capacity = 10
+
+ var/duct_color = null
+ var/ignore_colors = FALSE //TRUE to ignore colors, so yeah we also connect with other colors without issue
+ var/duct_layer = DUCT_LAYER_DEFAULT //1,2,4,8,16
+ var/lock_layers = FALSE //whether we allow our layers to be altered
+ var/color_to_color_support = TRUE //TRUE to let colors connect when forced with a wrench, false to just not do that at all
+
+ var/active = TRUE //wheter to even bother with plumbing code or not
+ var/list/neighbours = list() //track ducts we're connected to. Mainly for ducts we connect to that we normally wouldn't, like different layers and colors, for when we regenerate the ducts
+
+/obj/machinery/duct/Initialize(mapload, no_anchor, color_of_duct, layer_of_duct = DUCT_LAYER_DEFAULT, force_connects)
+ . = ..()
+ if(no_anchor)
+ active = FALSE
+ anchored = FALSE
+ else if(!can_anchor())
+ CRASH("Overlapping ducts detected")
+ qdel(src)
+ if(force_connects)
+ connects = force_connects //skip change_connects() because we're still initializing and we need to set our connects at one point
+ if(!lock_layers)
+ duct_layer = layer_of_duct
+ if(!ignore_colors)
+ duct_color = color_of_duct
+ if(duct_color)
+ add_atom_colour(duct_color, FIXED_COLOUR_PRIORITY)
+ handle_layer()
+ for(var/obj/machinery/duct/D in loc)
+ if(D == src)
+ continue
+ if(D.duct_layer & duct_layer)
+ qdel(src) //replace with dropping or something
+ if(active)
+ attempt_connect()
+
+/obj/machinery/duct/proc/attempt_connect()
+ reset_connects(0) //All connects are gathered here again eitherway, we might aswell reset it so they properly update when reconnecting
+
+ for(var/D in GLOB.cardinals)
+ if(dumb && !(D & connects))
+ continue
+ for(var/atom/movable/AM in get_step(src, D))
+ if(connect_network(AM, D))
+ add_connects(D)
+ update_icon()
+
+/obj/machinery/duct/proc/connect_network(atom/movable/AM, direction, ignore_color)
+ if(istype(AM, /obj/machinery/duct))
+ return connect_duct(AM, direction, ignore_color)
+
+ var/plumber = AM.GetComponent(/datum/component/plumbing)
+ if(!plumber)
+ return
+ connect_plumber(plumber, direction)
+
+/obj/machinery/duct/proc/connect_duct(obj/machinery/duct/D, direction, ignore_color)
+ var/opposite_dir = turn(direction, 180)
+ if(!active || !D.active)
+ return
+
+ if(!dumb && D.dumb && !(opposite_dir & D.connects))
+ return
+ if(dumb && D.dumb && !(connects & D.connects)) //we eliminated a few more scenario in attempt connect
+ return
+
+ if((duct == D.duct) && duct)//check if we're not just comparing two null values
+ add_neighbour(D)
+
+ D.add_connects(opposite_dir)
+ D.update_icon()
+ return TRUE //tell the current pipe to also update it's sprite
+ if(!(D in neighbours)) //we cool
+ if((duct_color != D.duct_color) && !(ignore_colors || D.ignore_colors))
+ return
+ if(!(duct_layer & D.duct_layer))
+ return
+
+ if(D.duct)
+ if(duct)
+ duct.assimilate(D.duct)
+ else
+ D.duct.add_duct(src)
+ else
+ if(duct)
+ duct.add_duct(D)
+ else
+ create_duct()
+ duct.add_duct(D)
+ add_neighbour(D)
+ D.attempt_connect()//tell our buddy its time to pass on the torch of connecting to pipes. This shouldn't ever infinitely loop since it only works on pipes that havent been inductrinated
+ return TRUE
+
+/obj/machinery/duct/proc/connect_plumber(datum/component/plumbing/P, direction)
+ var/opposite_dir = turn(direction, 180)
+ if(duct_layer != DUCT_LAYER_DEFAULT) //plumbing devices don't support multilayering. 3 is the default layer so we only use that. We can change this later
+ return
+ var/comp_directions = P.supply_connects + P.demand_connects //they should never, ever have supply and demand connects overlap or catastrophic failure
+ if(opposite_dir & comp_directions)
+ if(duct)
+ duct.add_plumber(P, opposite_dir)
+ else
+ create_duct()
+ duct.add_plumber(P, opposite_dir)
+ return TRUE
+
+/obj/machinery/duct/proc/disconnect_duct()
+ anchored = FALSE
+ active = FALSE
+ if(duct)
+ duct.remove_duct(src)
+ lose_neighbours()
+ reset_connects(0)
+ update_icon()
+
+/obj/machinery/duct/proc/create_duct()
+ duct = new()
+ duct.add_duct(src)
+
+/obj/machinery/duct/proc/add_neighbour(obj/machinery/duct/D)
+ if(!(D in neighbours))
+ neighbours += D
+ if(!(src in D.neighbours))
+ D.neighbours += src
+
+/obj/machinery/duct/proc/lose_neighbours()
+ for(var/A in neighbours)
+ var/obj/machinery/duct/D = A
+ D.neighbours.Remove(src)
+ neighbours = list()
+
+/obj/machinery/duct/proc/add_connects(new_connects) //make this a define to cut proc calls?
+ if(!lock_connects)
+ connects |= new_connects
+
+/obj/machinery/duct/proc/reset_connects()
+ if(!lock_connects)
+ connects = 0
+
+/obj/machinery/duct/proc/get_adjacent_ducts()
+ var/list/adjacents = list()
+ for(var/A in GLOB.cardinals)
+ if(A & connects)
+ for(var/obj/machinery/duct/D in get_step(src, A))
+ if((turn(A, 180) & D.connects) && D.active)
+ adjacents += D
+ return adjacents
+
+/obj/machinery/duct/update_icon() //setting connects isnt a parameter because sometimes we make more than one change, overwrite it completely or just add it to the bitfield
+ var/temp_icon = initial(icon_state)
+ for(var/D in GLOB.cardinals)
+ if(D & connects)
+ if(D == NORTH)
+ temp_icon += "_n"
+ if(D == SOUTH)
+ temp_icon += "_s"
+ if(D == EAST)
+ temp_icon += "_e"
+ if(D == WEST)
+ temp_icon += "_w"
+ icon_state = temp_icon
+
+/obj/machinery/duct/proc/handle_layer()
+ var/offset
+ switch(duct_layer)//it's a bitfield, but it's fine because it only works when there's one layer, and multiple layers should be handled differently
+ if(FIRST_DUCT_LAYER)
+ offset = -10
+ if(SECOND_DUCT_LAYER)
+ offset = -5
+ if(THIRD_DUCT_LAYER)
+ offset = 0
+ if(FOURTH_DUCT_LAYER)
+ offset = 5
+ if(FIFTH_DUCT_LAYER)
+ offset = 10
+ pixel_x = offset
+ pixel_y = offset
+
+
+/obj/machinery/duct/wrench_act(mob/living/user, obj/item/I) //I can also be the RPD
+ add_fingerprint(user)
+ I.play_tool_sound(src)
+ if(anchored)
+ user.visible_message( \
+ "[user] unfastens \the [src].", \
+ "You unfasten \the [src].", \
+ "You hear ratcheting.")
+ disconnect_duct()
+ else if(can_anchor())
+ anchored = TRUE
+ active = TRUE
+ user.visible_message( \
+ "[user] fastens \the [src].", \
+ "You fasten \the [src].", \
+ "You hear ratcheting.")
+ attempt_connect()
+ return TRUE
+
+/obj/machinery/duct/proc/can_anchor(turf/T)
+ if(!T)
+ T = get_turf(src)
+ for(var/obj/machinery/duct/D in T)
+ if(!anchored)
+ continue
+ for(var/A in GLOB.cardinals)
+ if(A & connects && A & D.connects)
+ return FALSE
+ return TRUE
+
+/obj/machinery/duct/doMove(destination)
+ . = ..()
+ disconnect_duct()
+ anchored = FALSE
+
+/obj/machinery/duct/Destroy()
+ disconnect_duct()
+ return ..()
+
+/obj/machinery/duct/MouseDrop_T(atom/A, mob/living/user)
+ if(!istype(A, /obj/machinery/duct))
+ return
+ var/obj/machinery/duct/D = A
+ var/obj/item/I = user.get_active_held_item()
+ if(I?.tool_behaviour != TOOL_WRENCH)
+ to_chat(user, "You need to be holding a wrench in your active hand to do that!")
+ return
+ if(get_dist(src, D) != 1)
+ return
+ var/direction = get_dir(src, D)
+ if(!(direction in GLOB.cardinals))
+ return
+ connect_network(D, direction, TRUE)
+ add_connects(direction)
+ update_icon()
+
+/obj/machinery/duct/multilayered
+ name = "duct layer-manifold"
+ icon = 'icons/obj/2x2.dmi'
+ icon_state = "multiduct"
+
+
+ color_to_color_support = FALSE
+ duct_layer = FIRST_DUCT_LAYER + SECOND_DUCT_LAYER + THIRD_DUCT_LAYER + FOURTH_DUCT_LAYER + FIFTH_DUCT_LAYER
+
+ lock_connects = TRUE
+ lock_layers = TRUE
+ ignore_colors = TRUE
+ dumb = TRUE
+
+
+/obj/machinery/duct/multilayered/update_icon()
+ icon_state = initial(icon_state)
+ if((connects & NORTH) || (connects & SOUTH))
+ icon_state += "_vertical"
+ pixel_x = -15
+ pixel_y = -15
+ else
+ icon_state += "_horizontal"
+ pixel_x = -10
+ pixel_y = -12
+
+/obj/machinery/duct/multilayered/connect_duct(obj/machinery/duct/D, direction, ignore_color)
+ if(istype(D, /obj/machinery/duct/multilayered)) //don't connect to other multilayered stuff because honestly it shouldnt be done and I dont wanna deal with it
+ return
+ return ..()
diff --git a/code/modules/plumbing/plumbers/pumps.dm b/code/modules/plumbing/plumbers/pumps.dm
new file mode 100644
index 000000000000..3d920c3604ed
--- /dev/null
+++ b/code/modules/plumbing/plumbers/pumps.dm
@@ -0,0 +1,74 @@
+/obj/machinery/power/liquid_pump
+ name = "liquid pump"
+ desc = "Pump up those sweet liquids from under the surface."
+ icon = 'icons/obj/plumbing/plumbers.dmi'
+ icon_state = "pump"
+ anchored = FALSE
+ density = TRUE
+
+ idle_power_usage = 10
+ active_power_usage = 1000
+
+ var/powered = FALSE
+ var/pump_power = 2 //units we pump per process (2 seconds)
+
+ var/obj/structure/geyser/geyser
+ var/volume = 200
+
+
+/obj/machinery/power/liquid_pump/Initialize()
+ create_reagents(volume)
+ return ..()
+
+/obj/machinery/power/liquid_pump/ComponentInitialize()
+ AddComponent(/datum/component/plumbing/simple_supply)
+
+/obj/machinery/power/liquid_pump/wrench_act(mob/living/user, obj/item/I)
+ default_unfasten_wrench(user, I)
+ return TRUE
+
+/obj/machinery/power/liquid_pump/default_unfasten_wrench(mob/user, obj/item/I, time = 20)
+ . = ..()
+ if(. == SUCCESSFUL_UNFASTEN)
+ toggle_active()
+
+/obj/machinery/power/liquid_pump/proc/toggle_active(mob/user, obj/item/I) //we split this in a seperate proc so we can also deactivate if we got no geyser under us
+ geyser = null
+ if(user)
+ user.visible_message("[user.name] [anchored ? "fasten" : "unfasten"] [src]", \
+ "You [anchored ? "fasten" : "unfasten"] [src]")
+ var/datum/component/plumbing/P = GetComponent(/datum/component/plumbing)
+ if(anchored)
+ P.start()
+ connect_to_network()
+ else
+ P.disable()
+ disconnect_from_network()
+
+/obj/machinery/power/liquid_pump/process()
+ if(!anchored)
+ return
+ if(!geyser)
+ for(var/obj/structure/geyser/G in loc.contents)
+ geyser = G
+ if(!geyser) //we didnt find one, abort
+ toggle_active()
+ anchored = FALSE
+ visible_message("The [name] makes a sad beep!")
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 50)
+ return
+
+ if(avail(active_power_usage))
+ if(!powered) //we werent powered before this tick so update our sprite
+ powered = TRUE
+ icon_state = "[initial(icon_state)]-on"
+ add_load(active_power_usage)
+ pump()
+ else if(powered) //we were powered, but now we arent
+ powered = FALSE
+ icon_state = initial(icon_state)
+
+/obj/machinery/power/liquid_pump/proc/pump()
+ if(!geyser || !geyser.reagents)
+ return
+ geyser.reagents.trans_to(src, pump_power)
diff --git a/icons/obj/lavaland/terrain.dmi b/icons/obj/lavaland/terrain.dmi
new file mode 100644
index 000000000000..4db51145eeef
Binary files /dev/null and b/icons/obj/lavaland/terrain.dmi differ
diff --git a/icons/obj/plumbing/fluid_ducts.dmi b/icons/obj/plumbing/fluid_ducts.dmi
new file mode 100644
index 000000000000..7e0661646351
Binary files /dev/null and b/icons/obj/plumbing/fluid_ducts.dmi differ
diff --git a/icons/obj/plumbing/plumbers.dmi b/icons/obj/plumbing/plumbers.dmi
new file mode 100644
index 000000000000..801eb35dacc7
Binary files /dev/null and b/icons/obj/plumbing/plumbers.dmi differ