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