From a591aef35ca72fc65ab45250d1b9f456c800e15c Mon Sep 17 00:00:00 2001 From: Tastyfish Date: Tue, 24 Jan 2012 15:45:51 -0500 Subject: [PATCH] water pipes base + pda support --- baystation12.dme | 25 + code/defines/area/Space Station 13 areas.dm | 4 + code/game/master_controller.dm | 6 + code/game/objects/devices/PDA/PDA.dm | 9 + code/game/objects/devices/PDA/cart.dm | 1 + code/modules/chemical/Chemistry-Holder.dm | 20 +- code/modules/chemical/Chemistry-Tools.dm | 77 +- .../components/binary/binary_water_base.dm | 147 +++ .../water/components/binary/fixture.dm | 77 ++ .../water/components/binary/water_pump.dm | 198 ++++ .../components/trinary/trinary_water_base.dm | 189 ++++ .../water/components/trinary/water_filter.dm | 190 ++++ .../water/components/trinary/water_mixer.dm | 148 +++ code/modules/water/components/tvalve.dm | 337 ++++++ .../water/components/unary/sprinkler.dm | 105 ++ .../components/unary/unary_water_base.dm | 89 ++ code/modules/water/components/valve.dm | 320 ++++++ .../water/components/water_glass_connector.dm | 207 ++++ .../components/water_portables_connector.dm | 158 +++ code/modules/water/construction.dm | 433 ++++++++ code/modules/water/water.dm | 69 ++ code/modules/water/water_meter.dm | 121 +++ code/modules/water/water_pipe_network.dm | 200 ++++ code/modules/water/water_pipeline.dm | 127 +++ code/modules/water/water_pipes.dm | 961 ++++++++++++++++++ icons/obj/water/blue_water_tank.dmi | Bin 0 -> 2357 bytes icons/obj/water/water_fixtures.dmi | Bin 0 -> 1443 bytes icons/obj/water/water_glass_connector.dmi | Bin 0 -> 2781 bytes icons/obj/water/water_pipe_item.dmi | Bin 0 -> 20113 bytes 29 files changed, 4210 insertions(+), 8 deletions(-) create mode 100644 code/modules/water/components/binary/binary_water_base.dm create mode 100644 code/modules/water/components/binary/fixture.dm create mode 100644 code/modules/water/components/binary/water_pump.dm create mode 100644 code/modules/water/components/trinary/trinary_water_base.dm create mode 100644 code/modules/water/components/trinary/water_filter.dm create mode 100644 code/modules/water/components/trinary/water_mixer.dm create mode 100644 code/modules/water/components/tvalve.dm create mode 100644 code/modules/water/components/unary/sprinkler.dm create mode 100644 code/modules/water/components/unary/unary_water_base.dm create mode 100644 code/modules/water/components/valve.dm create mode 100644 code/modules/water/components/water_glass_connector.dm create mode 100644 code/modules/water/components/water_portables_connector.dm create mode 100644 code/modules/water/construction.dm create mode 100644 code/modules/water/water.dm create mode 100644 code/modules/water/water_meter.dm create mode 100644 code/modules/water/water_pipe_network.dm create mode 100644 code/modules/water/water_pipeline.dm create mode 100644 code/modules/water/water_pipes.dm create mode 100644 icons/obj/water/blue_water_tank.dmi create mode 100644 icons/obj/water/water_fixtures.dmi create mode 100644 icons/obj/water/water_glass_connector.dmi create mode 100644 icons/obj/water/water_pipe_item.dmi diff --git a/baystation12.dme b/baystation12.dme index c7b1de5ca8..5f8267390a 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -131,6 +131,11 @@ #define FILE_DIR "code/modules/recycling" #define FILE_DIR "code/modules/research" #define FILE_DIR "code/modules/security levels" +#define FILE_DIR "code/modules/water" +#define FILE_DIR "code/modules/water/components" +#define FILE_DIR "code/modules/water/components/binary" +#define FILE_DIR "code/modules/water/components/trinary" +#define FILE_DIR "code/modules/water/components/unary" #define FILE_DIR "code/unused" #define FILE_DIR "code/unused/beast" #define FILE_DIR "code/unused/computer2" @@ -161,6 +166,7 @@ #define FILE_DIR "icons/obj/doors" #define FILE_DIR "icons/obj/machines" #define FILE_DIR "icons/obj/pipes" +#define FILE_DIR "icons/obj/water" #define FILE_DIR "icons/pda_icons" #define FILE_DIR "icons/spideros_icons" #define FILE_DIR "icons/Testing" @@ -168,6 +174,7 @@ #define FILE_DIR "icons/vending_icons" #define FILE_DIR "interface" #define FILE_DIR "maps" +#define FILE_DIR "maps/backup" #define FILE_DIR "sound" #define FILE_DIR "sound/ambience" #define FILE_DIR "sound/announcer" @@ -1007,6 +1014,24 @@ #include "code\modules\research\server.dm" #include "code\modules\security levels\keycard authentication.dm" #include "code\modules\security levels\security levels.dm" +#include "code\modules\water\construction.dm" +#include "code\modules\water\water.dm" +#include "code\modules\water\water_meter.dm" +#include "code\modules\water\water_pipe_network.dm" +#include "code\modules\water\water_pipeline.dm" +#include "code\modules\water\water_pipes.dm" +#include "code\modules\water\components\tvalve.dm" +#include "code\modules\water\components\valve.dm" +#include "code\modules\water\components\water_glass_connector.dm" +#include "code\modules\water\components\water_portables_connector.dm" +#include "code\modules\water\components\binary\binary_water_base.dm" +#include "code\modules\water\components\binary\fixture.dm" +#include "code\modules\water\components\binary\water_pump.dm" +#include "code\modules\water\components\trinary\trinary_water_base.dm" +#include "code\modules\water\components\trinary\water_filter.dm" +#include "code\modules\water\components\trinary\water_mixer.dm" +#include "code\modules\water\components\unary\sprinkler.dm" +#include "code\modules\water\components\unary\unary_water_base.dm" #include "code\WorkInProgress\AI_Visibility.dm" #include "code\WorkInProgress\buildmode.dm" #include "code\WorkInProgress\copier.dm" diff --git a/code/defines/area/Space Station 13 areas.dm b/code/defines/area/Space Station 13 areas.dm index fa31915446..0434d866e9 100644 --- a/code/defines/area/Space Station 13 areas.dm +++ b/code/defines/area/Space Station 13 areas.dm @@ -466,6 +466,10 @@ proc/process_ghost_teleport_locs() name = "Atmospherics" icon_state = "atmos" +/area/atmos/plumbing + name = "Plumbing" + icon_state = "toilet" + //Maintenance /area/maintenance/atmos_control diff --git a/code/game/master_controller.dm b/code/game/master_controller.dm index a04c8421fd..6a3dbe3a01 100644 --- a/code/game/master_controller.dm +++ b/code/game/master_controller.dm @@ -58,6 +58,9 @@ datum/controller/game_controller for(var/obj/machinery/atmospherics/machine in world) machine.build_network() + for(var/obj/machinery/water/machine in world) + machine.build_network() + world << "\red \b Initializing atmos machinery." sleep(-1) for(var/obj/machinery/atmospherics/unary/vent_pump/T in world) @@ -120,6 +123,9 @@ datum/controller/game_controller for(var/datum/pipe_network/network in pipe_networks) network.process() + for(var/datum/water/pipe_network/network in water_pipe_networks) + network.process() + for(var/datum/powernet/P in powernets) P.reset() diff --git a/code/game/objects/devices/PDA/PDA.dm b/code/game/objects/devices/PDA/PDA.dm index 5ea474c722..d6197dd8a0 100644 --- a/code/game/objects/devices/PDA/PDA.dm +++ b/code/game/objects/devices/PDA/PDA.dm @@ -775,6 +775,15 @@ user << "\blue \t [re]" else user << "\blue No active chemical agents found in [A]." + else if(istype(A,/obj/machinery/water/pipe)) + var/datum/water/pipeline/P = A:parent + if(P.reagents.reagent_list.len > 0) + var/reagents_length = P.reagents.reagent_list.len + user << "\blue [reagents_length] chemical agent[reagents_length > 1 ? "s" : ""] found at [P.return_pressure()]kPa." + for (var/re in P.reagents.reagent_list) + user << "\blue \t [re]" + else + user << "\blue No active chemical agents found in [A]." else user << "\blue No significant chemical agents found in [A]." diff --git a/code/game/objects/devices/PDA/cart.dm b/code/game/objects/devices/PDA/cart.dm index b3e9698f6d..59e3b75405 100644 --- a/code/game/objects/devices/PDA/cart.dm +++ b/code/game/objects/devices/PDA/cart.dm @@ -34,6 +34,7 @@ name = "Power-ON Cartridge" icon_state = "cart-e" access_engine = 1 + access_reagent_scanner = 1 medical name = "Med-U Cartridge" diff --git a/code/modules/chemical/Chemistry-Holder.dm b/code/modules/chemical/Chemistry-Holder.dm index 9dccadb939..f69115f4ae 100644 --- a/code/modules/chemical/Chemistry-Holder.dm +++ b/code/modules/chemical/Chemistry-Holder.dm @@ -59,11 +59,15 @@ datum return the_id trans_to(var/obj/target, var/amount=1, var/multiplier=1, var/preserve_data=1)//if preserve_data=0, the reagents data will be lost. Usefull if you use data for some strange stuff and don't want it to be transferred. - if (!target ) + if(!target || src.total_volume <= 0) return - if (!target.reagents || src.total_volume<=0) + var/datum/reagents/R + if(istype(target,/datum/reagents)) + R = target + else if(target.reagents) + R = target.reagents + else return - var/datum/reagents/R = target.reagents amount = min(min(amount, src.total_volume), R.maximum_volume-R.total_volume) var/part = amount / src.total_volume var/trans_data = null @@ -81,11 +85,15 @@ datum return amount copy_to(var/obj/target, var/amount=1, var/multiplier=1, var/preserve_data=1) - if(!target) + if(!target || src.total_volume <= 0) return - if(!target.reagents || src.total_volume<=0) + var/datum/reagents/R + if(istype(target,/datum/reagents)) + R = target + else if(target.reagents) + R = target.reagents + else return - var/datum/reagents/R = target.reagents amount = min(min(amount, src.total_volume), R.maximum_volume-R.total_volume) var/part = amount / src.total_volume var/trans_data = null diff --git a/code/modules/chemical/Chemistry-Tools.dm b/code/modules/chemical/Chemistry-Tools.dm index 6363f94398..f687dad93e 100644 --- a/code/modules/chemical/Chemistry-Tools.dm +++ b/code/modules/chemical/Chemistry-Tools.dm @@ -597,8 +597,29 @@ var/amount_per_transfer_from_this = 10 var/possible_transfer_amounts = list(10,25,50,100) + var/obj/machinery/water/portables_connector/connected_port + var/max_pressure = 4*ONE_ATMOSPHERE + attackby(obj/item/weapon/W as obj, mob/user as mob) - return + if (istype(W, /obj/item/weapon/wrench)) + if(connected_port) + disconnect() + user << "\blue You disconnect [name] from the port." + update_icon() + return + else + var/obj/machinery/water/portables_connector/possible_port = locate(/obj/machinery/water/portables_connector/) in loc + if(possible_port) + if(connect(possible_port)) + user << "\blue You connect [name] to the port." + update_icon() + return + else + user << "\blue [name] failed to connect to the port." + return + else + user << "\blue Nothing happens." + return New() var/datum/reagents/R = new/datum/reagents(1000) @@ -650,6 +671,43 @@ new /obj/effect/effect/water(src.loc) del(src) + proc/connect(obj/machinery/water/portables_connector/new_port) + //Make sure not already connected to something else + if(connected_port || !new_port || new_port.connected_device) + return 0 + + //Make sure are close enough for a valid connection + if(new_port.loc != loc) + return 0 + + //Perform the connection + connected_port = new_port + connected_port.connected_device = src + + anchored = 1 //Prevent movement + + //Actually enforce the air sharing + var/datum/water/pipe_network/network = connected_port.return_network(src) + if(network && !network.reagents.Find(reagents)) + network.reagents += reagents + network.update = 1 + + return 1 + + proc/disconnect() + if(!connected_port) + return 0 + + var/datum/water/pipe_network/network = connected_port.return_network(src) + if(network) + network.reagents -= reagents + + anchored = 0 + + connected_port.connected_device = null + connected_port = null + + return 1 /obj/item/weapon/reagent_containers @@ -723,7 +781,8 @@ /obj/machinery/disease2/incubator, /obj/machinery/disease2/isolator, /obj/machinery/disease2/biodestroyer, - /mob/living/simple_animal/livestock/cow + /mob/living/simple_animal/livestock/cow, + /obj/machinery/water/glass_connector ) examine() @@ -2822,6 +2881,13 @@ ..() reagents.add_reagent("water",1000) + update_icon() + overlays = 0 + if(connected_port) + var/image/I = image('atmos.dmi', "can-connector") + I.pixel_x = 7 + overlays += I + /obj/structure/reagent_dispensers/fueltank name = "fueltank" desc = "A fueltank" @@ -2832,6 +2898,13 @@ ..() reagents.add_reagent("fuel",1000) + update_icon() + overlays = 0 + if(connected_port) + var/image/I = image('atmos.dmi', "can-connector") + I.pixel_x = 7 + overlays += I + /obj/structure/reagent_dispensers/peppertank name = "Pepper Spray Refiller" desc = "Refill pepper spray canisters." diff --git a/code/modules/water/components/binary/binary_water_base.dm b/code/modules/water/components/binary/binary_water_base.dm new file mode 100644 index 0000000000..5974356d3e --- /dev/null +++ b/code/modules/water/components/binary/binary_water_base.dm @@ -0,0 +1,147 @@ +obj/machinery/water/binary + dir = SOUTH + initialize_directions = SOUTH|NORTH + + var/datum/reagents/r1 + var/datum/reagents/r2 + + var/obj/machinery/water/node1 + var/obj/machinery/water/node2 + + var/datum/water/pipe_network/network1 + var/datum/water/pipe_network/network2 + + var/max_volume = 400 + var/max_pressure = 3 * ONE_ATMOSPHERE + + New() + ..() + switch(dir) + if(NORTH) + initialize_directions = NORTH|SOUTH + if(SOUTH) + initialize_directions = NORTH|SOUTH + if(EAST) + initialize_directions = EAST|WEST + if(WEST) + initialize_directions = EAST|WEST + r1 = new(max_volume) + r1.my_atom = src + r2 = new(max_volume) + r2.my_atom = src + +// Housekeeping and pipe network stuff below + network_expand(datum/water/pipe_network/new_network, obj/machinery/water/pipe/reference) + if(reference == node1) + network1 = new_network + + else if(reference == node2) + network2 = new_network + + if(new_network.normal_members.Find(src)) + return 0 + + new_network.normal_members += src + + return null + + Del() + loc = null + + if(node1) + node1.disconnect(src) + del(network1) + if(node2) + node2.disconnect(src) + del(network2) + + node1 = null + node2 = null + + ..() + + initialize() + if(node1 && node2) return + + var/node2_connect = dir + var/node1_connect = turn(dir, 180) + + for(var/obj/machinery/water/target in get_step(src,node1_connect)) + if(target.initialize_directions & get_dir(target,src)) + node1 = target + break + + for(var/obj/machinery/water/target in get_step(src,node2_connect)) + if(target.initialize_directions & get_dir(target,src)) + node2 = target + break + + update_icon() + + build_network() + if(!network1 && node1) + network1 = new /datum/water/pipe_network() + network1.normal_members += src + network1.build_network(node1, src) + + if(!network2 && node2) + network2 = new /datum/water/pipe_network() + network2.normal_members += src + network2.build_network(node2, src) + + + return_network(obj/machinery/water/reference) + build_network() + + if(reference==node1) + return network1 + + if(reference==node2) + return network2 + + return null + + reassign_network(datum/water/pipe_network/old_network, datum/water/pipe_network/new_network) + if(network1 == old_network) + network1 = new_network + if(network2 == old_network) + network2 = new_network + + return 1 + + return_network_reagents(datum/water/pipe_network/reference) + var/list/results = list() + + if(network1 == reference) + results += r1 + if(network2 == reference) + results += r2 + + return results + + disconnect(obj/machinery/water/reference) + if(reference==node1) + del(network1) + node1 = null + + else if(reference==node2) + del(network2) + node2 = null + + return null + + proc/return_pressure1() + return r1.total_volume / r1.maximum_volume * max_pressure + + proc/return_pressure2() + return r2.total_volume / r2.maximum_volume * max_pressure + + proc/mingle_dc1_with_turf() + mingle_outflow_with_turf(get_turf(src), r1.total_volume, + turn(dir, 180), + reagents = r1, pressure = return_pressure1()) + + proc/mingle_dc2_with_turf() + mingle_outflow_with_turf(get_turf(src), r2.total_volume, + dir, + reagents = r2, pressure = return_pressure2()) \ No newline at end of file diff --git a/code/modules/water/components/binary/fixture.dm b/code/modules/water/components/binary/fixture.dm new file mode 100644 index 0000000000..f861c33ccf --- /dev/null +++ b/code/modules/water/components/binary/fixture.dm @@ -0,0 +1,77 @@ +/obj/machinery/water/binary/fixture + name = "water fixture connection" + icon = 'water_fixtures.dmi' + icon_state = "fixture" + level = 1 + layer = 2.9 + + var/obj/parent + + hide(var/i) + if(level == 1 && istype(loc, /turf/simulated)) + invisibility = i ? 101 : 0 + update_icon() + + initialize() + ..() + var/turf/T = src.loc // hide if turf is not intact + hide(T.intact) + update_icon() + + process() + ..() + + // handle leaks + if(!network1 && r1.total_volume > 0) + mingle_dc1_with_turf() + + if(!network2 && r2.total_volume > 0) + mingle_dc2_with_turf() + + proc/fill(amount) + if(!parent || !parent.reagents || amount <= 0) return + amount = min(amount, parent.reagents.maximum_volume-parent.reagents.total_volume) + var/parent_pressure = parent.reagents.total_volume / parent.reagents.maximum_volume + + if(return_pressure1() > parent_pressure) + r1.trans_to(parent, amount) + if(network1) + network1.update = 1 + return amount + else + return 0 + + proc/drain(amount) + if(!parent || !parent.reagents || amount <= 0) return + amount = min(amount, r2.maximum_volume-r2.total_volume) + var/parent_pressure = parent.reagents.total_volume / parent.reagents.maximum_volume + + if(return_pressure2() < parent_pressure) + parent.reagents.trans_to(r2, amount) + if(network2) + network2.update = 1 + return amount + else + return 0 + + attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) + if (!istype(W, /obj/item/weapon/wrench)) + return ..() + var/turf/T = src.loc + if (level==1 && isturf(T) && T.intact) + user << "\red You must remove the plating first." + return 1 + var/datum/gas_mixture/env_air = loc.return_air() + if ((return_pressure1()-env_air.return_pressure()) > 2*ONE_ATMOSPHERE) + user << "\red You cannot unwrench this [src], it too exerted due to internal pressure." + add_fingerprint(user) + return 1 + playsound(src.loc, 'Ratchet.ogg', 50, 1) + user << "\blue You begin to unfasten \the [src]..." + if (do_after(user, 40)) + user.visible_message( \ + "[user] unfastens \the [src].", \ + "\blue You have unfastened \the [src].", \ + "You hear ratchet.") + new /obj/item/water_pipe(loc, make_from=src) + del(src) diff --git a/code/modules/water/components/binary/water_pump.dm b/code/modules/water/components/binary/water_pump.dm new file mode 100644 index 0000000000..6bfc05aab6 --- /dev/null +++ b/code/modules/water/components/binary/water_pump.dm @@ -0,0 +1,198 @@ +/* +Every cycle, the pump uses the reagents in reagents_in to try and make reagents_out the perfect pressure. + +node1, r1, network1 correspond to input +node2, r2, network2 correspond to output + +Thus, the two variables affect pump operation are set in New(): + r1.total_volume + This is the volume of gas available to the pump that may be transfered to the output + r2.total_volume + Higher quantities of this cause more air to be perfected later + but overall network volume is also increased as this increases... +*/ + +obj/machinery/water/binary/pump + icon = 'pump.dmi' + icon_state = "intact_off" + + name = "Water pump" + desc = "A pump" + + var/on = 0 + var/target_pressure = ONE_ATMOSPHERE + + var/frequency = 0 + var/id = null + var/datum/radio_frequency/radio_connection + +/* + attack_hand(mob/user) + on = !on + update_icon() +*/ + + update_icon() + if(node1&&node2) + icon_state = "intact_[on?("on"):("off")]" + else + if(node1) + icon_state = "exposed_1_off" + else if(node2) + icon_state = "exposed_2_off" + else + icon_state = "exposed_3_off" + return + + process() +// ..() + if(stat & (NOPOWER|BROKEN)) + return + if(!on) + return 0 + + var/output_starting_pressure = return_pressure2() + + if( (target_pressure - output_starting_pressure) < 0.01) + //No need to pump liquid if target is already reached! + return 1 + + //Calculate necessary moles to transfer using PV=nRT + if(r1.total_volume > 0) + var/pressure_delta = target_pressure - output_starting_pressure + var/transfer_vol = pressure_delta * r2.maximum_volume / max_pressure + + //Actually transfer the reagents + r1.trans_to(r2, transfer_vol) + + if(network1) + network1.update = 1 + + if(network2) + network2.update = 1 + else if(r2.total_volume > 0) // leak out 1->2 + mingle_dc2_with_turf() + + return 1 + + //Radio remote control + + proc + set_frequency(new_frequency) + radio_controller.remove_object(src, frequency) + frequency = new_frequency + if(frequency) + radio_connection = radio_controller.add_object(src, frequency, filter = RADIO_ATMOSIA) + + broadcast_status() + if(!radio_connection) + return 0 + + var/datum/signal/signal = new + signal.transmission_method = 1 //radio signal + signal.source = src + + signal.data = list( + "tag" = id, + "device" = "AGP", + "power" = on, + "target_output" = target_pressure, + "sigtype" = "status" + ) + + radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) + + return 1 + + interact(mob/user as mob) + var/dat = {"Power: [on?"On":"Off"]
+ Desirable output pressure: + [round(target_pressure,0.1)]kPa | Change + "} + + user << browse("[src.name] control[dat]", "window=atmo_pump") + onclose(user, "atmo_pump") + + initialize() + ..() + if(frequency) + set_frequency(frequency) + + receive_signal(datum/signal/signal) + if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command")) + return 0 + + if("power" in signal.data) + on = text2num(signal.data["power"]) + + if("power_toggle" in signal.data) + on = !on + + if("set_output_pressure" in signal.data) + target_pressure = between( + 0, + text2num(signal.data["set_output_pressure"]), + ONE_ATMOSPHERE*50 + ) + + if("status" in signal.data) + spawn(2) + broadcast_status() + return //do not update_icon + + spawn(2) + broadcast_status() + update_icon() + return + + + attack_hand(user as mob) + if(..()) + return + src.add_fingerprint(usr) + if(!src.allowed(user)) + user << "\red Access denied." + return + usr.machine = src + interact(user) + return + + Topic(href,href_list) + if(href_list["power"]) + on = !on + if(href_list["set_press"]) + var/new_pressure = input(usr,"Enter new output pressure (0-4500kPa)","Pressure control",src.target_pressure) as num + src.target_pressure = max(0, min(4500, new_pressure)) + usr.machine = src + src.update_icon() + src.updateUsrDialog() + return + + power_change() + ..() + update_icon() + + attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) + if (!istype(W, /obj/item/weapon/wrench)) + return ..() + if (!(stat & NOPOWER) && on) + user << "\red You cannot unwrench this [src], turn it off first." + return 1 + var/turf/T = src.loc + if (level==1 && isturf(T) && T.intact) + user << "\red You must remove the plating first." + return 1 + var/datum/gas_mixture/env_air = loc.return_air() + if ((return_pressure1()-env_air.return_pressure()) > 2*ONE_ATMOSPHERE) + user << "\red You cannot unwrench this [src], it too exerted due to internal pressure." + add_fingerprint(user) + return 1 + playsound(src.loc, 'Ratchet.ogg', 50, 1) + user << "\blue You begin to unfasten \the [src]..." + if (do_after(user, 40)) + user.visible_message( \ + "[user] unfastens \the [src].", \ + "\blue You have unfastened \the [src].", \ + "You hear ratchet.") + new /obj/item/water_pipe(loc, make_from=src) + del(src) diff --git a/code/modules/water/components/trinary/trinary_water_base.dm b/code/modules/water/components/trinary/trinary_water_base.dm new file mode 100644 index 0000000000..4cc1654627 --- /dev/null +++ b/code/modules/water/components/trinary/trinary_water_base.dm @@ -0,0 +1,189 @@ +obj/machinery/water/trinary + dir = SOUTH + initialize_directions = SOUTH|NORTH|WEST + + var/datum/reagents/r1 + var/datum/reagents/r2 + var/datum/reagents/r3 + + var/obj/machinery/water/node1 + var/obj/machinery/water/node2 + var/obj/machinery/water/node3 + + var/datum/water/pipe_network/network1 + var/datum/water/pipe_network/network2 + var/datum/water/pipe_network/network3 + + var/max_volume = 400 + var/max_pressure = 3 * ONE_ATMOSPHERE + + New() + ..() + switch(dir) + if(NORTH) + initialize_directions = EAST|NORTH|SOUTH + if(SOUTH) + initialize_directions = SOUTH|WEST|NORTH + if(EAST) + initialize_directions = EAST|WEST|SOUTH + if(WEST) + initialize_directions = WEST|NORTH|EAST + r1 = new(max_volume) + r1.my_atom = src + r2 = new(max_volume) + r2.my_atom = src + r3 = new(max_volume) + r3.my_atom = src + +// Housekeeping and pipe network stuff below + network_expand(datum/water/pipe_network/new_network, obj/machinery/water/pipe/reference) + if(reference == node1) + network1 = new_network + + else if(reference == node2) + network2 = new_network + + else if (reference == node3) + network3 = new_network + + if(new_network.normal_members.Find(src)) + return 0 + + new_network.normal_members += src + + return null + + Del() + loc = null + + if(node1) + node1.disconnect(src) + del(network1) + if(node2) + node2.disconnect(src) + del(network2) + if(node3) + node3.disconnect(src) + del(network3) + + node1 = null + node2 = null + node3 = null + + ..() + + initialize() + if(node1 && node2 && node3) return + + var/node1_connect = turn(dir, -180) + var/node2_connect = turn(dir, -90) + var/node3_connect = dir + + for(var/obj/machinery/water/target in get_step(src,node1_connect)) + if(target.initialize_directions & get_dir(target,src)) + node1 = target + break + + for(var/obj/machinery/water/target in get_step(src,node2_connect)) + if(target.initialize_directions & get_dir(target,src)) + node2 = target + break + + for(var/obj/machinery/water/target in get_step(src,node3_connect)) + if(target.initialize_directions & get_dir(target,src)) + node3 = target + break + + update_icon() + + build_network() + if(!network1 && node1) + network1 = new /datum/water/pipe_network() + network1.normal_members += src + network1.build_network(node1, src) + + if(!network2 && node2) + network2 = new /datum/water/pipe_network() + network2.normal_members += src + network2.build_network(node2, src) + + if(!network3 && node3) + network3 = new /datum/water/pipe_network() + network3.normal_members += src + network3.build_network(node3, src) + + + return_network(obj/machinery/water/reference) + build_network() + + if(reference==node1) + return network1 + + if(reference==node2) + return network2 + + if(reference==node3) + return network3 + + return null + + reassign_network(datum/water/pipe_network/old_network, datum/water/pipe_network/new_network) + if(network1 == old_network) + network1 = new_network + if(network2 == old_network) + network2 = new_network + if(network3 == old_network) + network3 = new_network + + return 1 + + return_network_reagents(datum/water/pipe_network/reference) + var/list/results = list() + + if(network1 == reference) + results += r1 + if(network2 == reference) + results += r2 + if(network3 == reference) + results += r3 + + return results + + disconnect(obj/machinery/water/reference) + if(reference==node1) + del(network1) + node1 = null + + else if(reference==node2) + del(network2) + node2 = null + + else if(reference==node3) + del(network3) + node3 = null + + return null + + proc/return_pressure1() + return r1.total_volume / r1.maximum_volume * max_pressure + + proc/return_pressure2() + return r2.total_volume / r2.maximum_volume * max_pressure + + proc/return_pressure3() + return r3.total_volume / r3.maximum_volume * max_pressure + + proc/mingle_dc1_with_turf() + mingle_outflow_with_turf(get_turf(src), r1.total_volume, + turn(dir, 180), + reagents = r1, pressure = return_pressure1()) + + proc/mingle_dc2_with_turf() + mingle_outflow_with_turf(get_turf(src), r2.total_volume, + turn(dir, -90), + reagents = r2, pressure = return_pressure2()) + + proc/mingle_dc3_with_turf() + mingle_outflow_with_turf(get_turf(src), r3.total_volume, + dir, + reagents = r3, pressure = return_pressure3()) \ No newline at end of file diff --git a/code/modules/water/components/trinary/water_filter.dm b/code/modules/water/components/trinary/water_filter.dm new file mode 100644 index 0000000000..52bd0fa418 --- /dev/null +++ b/code/modules/water/components/trinary/water_filter.dm @@ -0,0 +1,190 @@ +obj/machinery/water/trinary/filter + icon = 'filter.dmi' + icon_state = "intact_off" + density = 1 + + name = "Liquid filter" + + req_access = list(access_atmospherics) + + var/on = 0 + var/temp = null // -- TLE + + var/target_pressure = ONE_ATMOSPHERE + + var/list/filter_types = list() + var/filter_types_text + + var/frequency = 0 + var/datum/radio_frequency/radio_connection + + proc + set_frequency(new_frequency) + radio_controller.remove_object(src, frequency) + frequency = new_frequency + if(frequency) + radio_connection = radio_controller.add_object(src, frequency, RADIO_ATMOSIA) + + New() + if(filter_types_text) + filter_types = dd_text2list(filter_types_text, ";") + filter_types_text = null + + if(radio_controller) + initialize() + ..() + + update_icon() + if(node2 && node3 && node1) + icon_state = "intact_[on?("on"):("off")]" + else + icon_state = "hintact_off" + on = 0 + + return + + New() + ..() + + process() + ..() + if(!on) + return 0 + + var/output_starting_pressure = return_pressure3() + + if(output_starting_pressure >= target_pressure) + //No need to mix if target is already full! + return 1 + + //Calculate necessary volume to transfer + + var/pressure_delta = target_pressure - output_starting_pressure + var/transfer_vol = pressure_delta * r3.maximum_volume / max_pressure + + //Actually transfer the reagents + + if(transfer_vol > 0) + var/datum/reagents/removed = new(transfer_vol) + removed.my_atom = src + r1.trans_to(removed, transfer_vol) + + var/datum/reagents/filtered_out = new(transfer_vol) + filtered_out.my_atom = src + + // transfer each type in filter list + for(var/T in filter_types) + var/datum/reagent/R = removed.has_reagent(T) + if(!R) continue + filtered_out.add_reagent(T, R.volume) + removed.remove_reagent(T, R.volume) + + filtered_out.trans_to(r2, filtered_out.total_volume) + removed.trans_to(r3, removed.total_volume) + + if(network2) + network2.update = 1 + else if(r2.total_volume > 0) // leak out 1->2 + mingle_dc2_with_turf() + + if(network3) + network3.update = 1 + else if(r3.total_volume > 0) // leak out 1->3 + mingle_dc3_with_turf() + + if(network1) + network1.update = 1 + + return 1 + + initialize() + set_frequency(frequency) + ..() + + attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) + if (!istype(W, /obj/item/weapon/wrench)) + return ..() + var/turf/T = src.loc + if (level==1 && isturf(T) && T.intact) + user << "\red You must remove the plating first." + return 1 + var/datum/gas_mixture/env_air = loc.return_air() + if ((return_pressure1()-env_air.return_pressure()) > 2*ONE_ATMOSPHERE) + user << "\red You cannot unwrench this [src], it too exerted due to internal pressure." + add_fingerprint(user) + return 1 + playsound(src.loc, 'Ratchet.ogg', 50, 1) + user << "\blue You begin to unfasten \the [src]..." + if (do_after(user, 40)) + user.visible_message( \ + "[user] unfastens \the [src].", \ + "\blue You have unfastened \the [src].", \ + "You hear ratchet.") + new /obj/item/water_pipe(loc, make_from=src) + del(src) + + +obj/machinery/water/trinary/filter/attack_hand(user as mob) // -- TLE + if(..()) + return + + if(!src.allowed(user)) + user << "\red Access denied." + return + + var/dat + dat += {" + Power: [on?"On":"Off"]
+ Filtering: Add
"} + + for(var/T in filter_types) + dat += "[T]
" + + dat += {"
Desirable output pressure: + [src.target_pressure] | Change + "} +/* + user << browse("[src.name] control[dat]","window=atmo_filter") + onclose(user, "atmo_filter") + return + + if (src.temp) + dat = text("[]

Clear Screen", src.temp, src) + //else + // src.on != src.on +*/ + user << browse("[src.name] control[dat]", "window=atmo_filter") + onclose(user, "atmo_filter") + return + +obj/machinery/water/trinary/filter/Topic(href, href_list) // -- TLE + if(..()) + return + usr.machine = src + src.add_fingerprint(usr) + if (href_list["temp"]) + src.temp = null + if(href_list["set_press"]) + var/new_pressure = input(usr,"Enter new output pressure (0-4500kPa)","Pressure control",src.target_pressure) as num + src.target_pressure = max(0, min(4500, new_pressure)) + if(href_list["power"]) + on=!on + if(href_list["add"]) + var/list/choices = new() + for(var/T in typesof(/datum/reagent) - /datum/reagent) + var/datum/reagent/R = new T() + choices += R.id + var/choice = input("Choose Reagent", name) in choices + filter_types += choice + if(href_list["remove"]) + filter_types -= href_list["remove"] + src.update_icon() + src.updateUsrDialog() +/* + for(var/mob/M in viewers(1, src)) + if ((M.client && M.machine == src)) + src.attack_hand(M) +*/ + return + + diff --git a/code/modules/water/components/trinary/water_mixer.dm b/code/modules/water/components/trinary/water_mixer.dm new file mode 100644 index 0000000000..793e03d9a8 --- /dev/null +++ b/code/modules/water/components/trinary/water_mixer.dm @@ -0,0 +1,148 @@ +obj/machinery/water/trinary/mixer + icon = 'mixer.dmi' + icon_state = "intact_off" + density = 1 + + name = "Liquid mixer" + + req_access = list(access_atmospherics) + + var/on = 0 + + var/target_pressure = ONE_ATMOSPHERE + var/node1_concentration = 0.5 + var/node2_concentration = 0.5 + + //node 3 is the outlet, nodes 1 & 2 are intakes + + update_icon() + if(node2 && node3 && node1) + icon_state = "intact_[on?("on"):("off")]" + else + icon_state = "intact_off" + on = 0 + + return + + New() + ..() + r3.maximum_volume = 500 + + process() + ..() + if(!on) + return 0 + + var/output_starting_pressure = return_pressure3() + + if(output_starting_pressure >= target_pressure) + //No need to mix if target is already full! + return 1 + + //Calculate necessary moles to transfer using PV=nRT + + var/pressure_delta = target_pressure - output_starting_pressure + var/transfer_vol1 = (node1_concentration*pressure_delta) * r1.maximum_volume / max_pressure + var/transfer_vol2 = (node2_concentration*pressure_delta) * r2.maximum_volume / max_pressure + + var/r1_vol = r1.total_volume + var/r2_vol = r2.total_volume + + if((r1_vol < transfer_vol1) || (r2_vol < transfer_vol2)) + if(!transfer_vol1 || !transfer_vol2) return + var/ratio = min(r1_vol/transfer_vol1, r2_vol/transfer_vol2) + + transfer_vol1 *= ratio + transfer_vol2 *= ratio + + //Actually transfer the gas + + if(transfer_vol1 > 0) + r1.trans_to(r3, transfer_vol1) + + if(transfer_vol2 > 0) + r2.trans_to(r3, transfer_vol2) + + if(network1 && transfer_vol1) + network1.update = 1 + + if(network2 && transfer_vol2) + network2.update = 1 + + if(network3) + network3.update = 1 + else if(r3.total_volume > 0) // leak out 1,2->3 + mingle_dc3_with_turf() + + return 1 + + attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) + if (!istype(W, /obj/item/weapon/wrench)) + return ..() + var/turf/T = src.loc + if (level==1 && isturf(T) && T.intact) + user << "\red You must remove the plating first." + return 1 + var/datum/gas_mixture/env_air = loc.return_air() + if ((return_pressure3()-env_air.return_pressure()) > 2*ONE_ATMOSPHERE) + user << "\red You cannot unwrench this [src], it too exerted due to internal pressure." + add_fingerprint(user) + return 1 + playsound(src.loc, 'Ratchet.ogg', 50, 1) + user << "\blue You begin to unfasten \the [src]..." + if (do_after(user, 40)) + user.visible_message( \ + "[user] unfastens \the [src].", \ + "\blue You have unfastened \the [src].", \ + "You hear ratchet.") + new /obj/item/water_pipe(loc, make_from=src) + del(src) + + attack_hand(user as mob) + if(..()) + return + src.add_fingerprint(usr) + if(!src.allowed(user)) + user << "\red Access denied." + return + usr.machine = src + var/dat = {"Power: [on?"On":"Off"]
+ Desirable output pressure: + [target_pressure]kPa | Change +
+ Node 1 Concentration: + - + - + [node1_concentration]([node1_concentration*100]%) + + + + +
+ Node 2 Concentration: + - + - + [node2_concentration]([node2_concentration*100]%) + + + + + "} + + user << browse("[src.name] control[dat]", "window=atmo_mixer") + onclose(user, "atmo_mixer") + return + + Topic(href,href_list) + if(href_list["power"]) + on = !on + if(href_list["set_press"]) + var/new_pressure = input(usr,"Enter new output pressure (0-4500kPa)","Pressure control",src.target_pressure) as num + src.target_pressure = max(0, min(4500, new_pressure)) + if(href_list["node1_c"]) + var/value = text2num(href_list["node1_c"]) + src.node1_concentration = max(0, min(1, src.node1_concentration + value)) + src.node2_concentration = max(0, min(1, src.node2_concentration - value)) + if(href_list["node2_c"]) + var/value = text2num(href_list["node2_c"]) + src.node2_concentration = max(0, min(1, src.node2_concentration + value)) + src.node1_concentration = max(0, min(1, src.node1_concentration - value)) + src.update_icon() + src.updateUsrDialog() + return diff --git a/code/modules/water/components/tvalve.dm b/code/modules/water/components/tvalve.dm new file mode 100644 index 0000000000..351d8d4ef3 --- /dev/null +++ b/code/modules/water/components/tvalve.dm @@ -0,0 +1,337 @@ +obj/machinery/water/tvalve + icon = 'valve.dmi' + icon_state = "tvalve0" + + name = "manual switching water valve" + desc = "A pipe valve" + + dir = SOUTH + initialize_directions = SOUTH|NORTH|WEST + + var/state = 0 // 0 = go straight, 1 = go to side + + // like a trinary component, node1 is input, node2 is side output, node3 is straight output + var/obj/machinery/water/node1 + var/obj/machinery/water/node2 + var/obj/machinery/water/node3 + + var/datum/water/pipe_network/network_node1 + var/datum/water/pipe_network/network_node2 + var/datum/water/pipe_network/network_node3 + + update_icon(animation) + if(animation) + flick("tvalve[src.state][!src.state]",src) + else + icon_state = "tvalve[state]" + + New() + switch(dir) + if(NORTH) + initialize_directions = SOUTH|NORTH|EAST + if(SOUTH) + initialize_directions = NORTH|SOUTH|WEST + if(EAST) + initialize_directions = WEST|EAST|SOUTH + if(WEST) + initialize_directions = EAST|WEST|NORTH + ..() + + network_expand(datum/water/pipe_network/new_network, obj/machinery/water/pipe/reference) + if(reference == node1) + network_node1 = new_network + if(state) + network_node2 = new_network + else + network_node3 = new_network + else if(reference == node2) + network_node2 = new_network + if(state) + network_node1 = new_network + else if(reference == node3) + network_node3 = new_network + if(!state) + network_node1 = new_network + + if(new_network.normal_members.Find(src)) + return 0 + + new_network.normal_members += src + + if(state) + if(reference == node1) + if(node2) + return node2.network_expand(new_network, src) + else if(reference == node2) + if(node1) + return node1.network_expand(new_network, src) + else + if(reference == node1) + if(node3) + return node3.network_expand(new_network, src) + else if(reference == node3) + if(node1) + return node1.network_expand(new_network, src) + + return null + + Del() + loc = null + + if(node1) + node1.disconnect(src) + del(network_node1) + if(node2) + node2.disconnect(src) + del(network_node2) + if(node3) + node3.disconnect(src) + del(network_node3) + + node1 = null + node2 = null + node3 = null + + ..() + + proc/go_to_side() + + if(state) return 0 + + state = 1 + update_icon() + + if(network_node1) + del(network_node1) + if(network_node3) + del(network_node3) + build_network() + + if(network_node1&&network_node2) + network_node1.merge(network_node2) + network_node2 = network_node1 + + if(network_node1) + network_node1.update = 1 + else if(network_node2) + network_node2.update = 1 + + return 1 + + proc/go_straight() + + if(!state) + return 0 + + state = 0 + update_icon() + + if(network_node1) + del(network_node1) + if(network_node2) + del(network_node2) + build_network() + + if(network_node1&&network_node3) + network_node1.merge(network_node3) + network_node3 = network_node1 + + if(network_node1) + network_node1.update = 1 + else if(network_node3) + network_node3.update = 1 + + return 1 + + attack_ai(mob/user as mob) + return + + attack_paw(mob/user as mob) + return attack_hand(user) + + attack_hand(mob/user as mob) + src.add_fingerprint(usr) + update_icon(1) + sleep(10) + if (src.state) + src.go_straight() + else + src.go_to_side() + + process() + ..() + machines.Remove(src) + + if(!node1) + if(state && node2) + mingle_dc_with_turf(turn(dir,180), network_node2) // leak 2->1 + else if(!state && node3) + mingle_dc_with_turf(turn(dir,180), network_node3) // leak 3->1 + else + if(state && node2) + mingle_dc_with_turf(turn(dir,-90), network_node1) // leak 1->2 + else if(!state && node3) + mingle_dc_with_turf(dir, network_node1) // leak 1->3 + // if it's disconnected on both ends, do nothing + + return + + initialize() + var/node1_dir + var/node2_dir + var/node3_dir + + node1_dir = turn(dir, 180) + node2_dir = turn(dir, -90) + node3_dir = dir + + for(var/obj/machinery/water/target in get_step(src,node1_dir)) + if(target.initialize_directions & get_dir(target,src)) + node1 = target + break + for(var/obj/machinery/water/target in get_step(src,node2_dir)) + if(target.initialize_directions & get_dir(target,src)) + node2 = target + break + for(var/obj/machinery/water/target in get_step(src,node3_dir)) + if(target.initialize_directions & get_dir(target,src)) + node3 = target + break + + build_network() + if(!network_node1 && node1) + network_node1 = new /datum/water/pipe_network() + network_node1.normal_members += src + network_node1.build_network(node1, src) + + if(!network_node2 && node2) + network_node2 = new /datum/water/pipe_network() + network_node2.normal_members += src + network_node2.build_network(node2, src) + + if(!network_node3 && node3) + network_node3 = new /datum/water/pipe_network() + network_node3.normal_members += src + network_node3.build_network(node3, src) + + + return_network(obj/machinery/water/reference) + build_network() + + if(reference==node1) + return network_node1 + + if(reference==node2) + return network_node2 + + if(reference==node3) + return network_node3 + + return null + + reassign_network(datum/water/pipe_network/old_network, datum/water/pipe_network/new_network) + if(network_node1 == old_network) + network_node1 = new_network + if(network_node2 == old_network) + network_node2 = new_network + if(network_node3 == old_network) + network_node3 = new_network + + return 1 + + return_network_reagents(datum/water/pipe_network/reference) + return null + + disconnect(obj/machinery/water/reference) + if(reference==node1) + del(network_node1) + node1 = null + + else if(reference==node2) + del(network_node2) + node2 = null + + else if(reference==node3) + del(network_node3) + node2 = null + + return null + + digital // can be controlled by AI + name = "digital switching valve" + desc = "A digitally controlled valve." + icon = 'digital_valve.dmi' + + attack_ai(mob/user as mob) + return src.attack_hand(user) + + attack_hand(mob/user as mob) + if(!src.allowed(user)) + user << "\red Access denied." + return + ..() + + //Radio remote control + + proc + set_frequency(new_frequency) + radio_controller.remove_object(src, frequency) + frequency = new_frequency + if(frequency) + radio_connection = radio_controller.add_object(src, frequency, RADIO_ATMOSIA) + + var/frequency = 0 + var/id = null + var/datum/radio_frequency/radio_connection + + initialize() + ..() + if(frequency) + set_frequency(frequency) + + receive_signal(datum/signal/signal) + if(!signal.data["tag"] || (signal.data["tag"] != id)) + return 0 + + switch(signal.data["command"]) + if("valve_open") + if(!state) + go_to_side() + + if("valve_close") + if(state) + go_straight() + + if("valve_toggle") + if(state) + go_straight() + else + go_to_side() + + proc/return_pressure() + return network_node1.return_pressure_transient() + + attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) + if (!istype(W, /obj/item/weapon/wrench)) + return ..() + if (istype(src, /obj/machinery/water/tvalve/digital)) + user << "\red You cannot unwrench this [src], it's too complicated." + return 1 + var/turf/T = src.loc + if (level==1 && isturf(T) && T.intact) + user << "\red You must remove the plating first." + return 1 + var/datum/gas_mixture/env_air = loc.return_air() + if ((return_pressure()-env_air.return_pressure()) > 2*ONE_ATMOSPHERE) + user << "\red You cannot unwrench this [src], it too exerted due to internal pressure." + add_fingerprint(user) + return 1 + playsound(src.loc, 'Ratchet.ogg', 50, 1) + user << "\blue You begin to unfasten \the [src]..." + if (do_after(user, 40)) + user.visible_message( \ + "[user] unfastens \the [src].", \ + "\blue You have unfastened \the [src].", \ + "You hear ratchet.") + new /obj/item/water_pipe(loc, make_from=src) + del(src) diff --git a/code/modules/water/components/unary/sprinkler.dm b/code/modules/water/components/unary/sprinkler.dm new file mode 100644 index 0000000000..213edf6343 --- /dev/null +++ b/code/modules/water/components/unary/sprinkler.dm @@ -0,0 +1,105 @@ +#define SPRINKLER_VOLUME_PER_SPLASH 3 + +/obj/machinery/water/unary/sprinkler + name = "fire sprinkler" + icon = 'water_fixtures.dmi' + icon_state = "sprinkler0" + layer = 5 + max_volume = 100 + max_pressure = ONE_ATMOSPHERE + + var/on = 0 + + temperature_expose(datum/gas_mixture/air, temperature, volume) + on = temperature > T0C+200 ? 50 : 0 + update_icon() + + update_icon() + icon_state = "sprinkler[(on || stat) > 0]" + + process() + // make broken sprinkler stay on forever + if((!stat && !on) || reagents.total_volume < SPRINKLER_VOLUME_PER_SPLASH) + return + + if(on > 0) + on-- + + // from extinguisher + /*var/direction = turn(dir, 180) + + var/turf/T = get_turf(loc) + var/turf/T1 = get_step(T,turn(direction, 90)) + var/turf/T2 = get_step(T,turn(direction, -90)) + for(var/i = 1 to 5) + T2 = get_step(T2,direction) + + var/list/the_targets = block(T1, T2)*/ // 3x3 grid in front of sprinkler + + var/list/the_targets = view(5, src.loc) + if(the_targets.len == 0) + return + + for(var/a=0, a 2*ONE_ATMOSPHERE) + user << "\red You cannot unwrench this [src], it too exerted due to internal pressure." + add_fingerprint(user) + return 1 + playsound(src.loc, 'Ratchet.ogg', 50, 1) + user << "\blue You begin to unfasten \the [src]..." + if (do_after(user, 40)) + user.visible_message( \ + "[user] unfastens \the [src].", \ + "\blue You have unfastened \the [src].", \ + "You hear ratchet.") + new /obj/item/water_pipe(loc, make_from=src) + del(src) + else + ..() + +#undef SPRINKLER_VOLUME_PER_SPLASH \ No newline at end of file diff --git a/code/modules/water/components/unary/unary_water_base.dm b/code/modules/water/components/unary/unary_water_base.dm new file mode 100644 index 0000000000..c32310685e --- /dev/null +++ b/code/modules/water/components/unary/unary_water_base.dm @@ -0,0 +1,89 @@ +/obj/machinery/water/unary + dir = SOUTH + initialize_directions = SOUTH + + var/obj/machinery/water/node + var/datum/water/pipe_network/network + + var/max_volume = 400 + var/max_pressure = 3 * ONE_ATMOSPHERE + + New() + ..() + initialize_directions = dir + reagents = new(max_volume) + reagents.my_atom = src + + proc/return_pressure() + return reagents.total_volume / reagents.maximum_volume * max_pressure + +// Housekeeping and pipe network stuff below + network_expand(datum/water/pipe_network/new_network, obj/machinery/water/pipe/reference) + if(reference == node) + network = new_network + + if(new_network.normal_members.Find(src)) + return 0 + + new_network.normal_members += src + + return null + + Del() + loc = null + + if(node) + node.disconnect(src) + del(network) + + node = null + + ..() + + initialize() + if(node) return + + var/node_connect = dir + + for(var/obj/machinery/water/target in get_step(src,node_connect)) + if(target.initialize_directions & get_dir(target,src)) + node = target + break + + update_icon() + + build_network() + if(!network && node) + network = new /datum/water/pipe_network() + network.normal_members += src + network.build_network(node, src) + + + return_network(obj/machinery/water/reference) + build_network() + + if(reference==node) + return network + + return null + + reassign_network(datum/water/pipe_network/old_network, datum/water/pipe_network/new_network) + if(network == old_network) + network = new_network + + return 1 + + return_network_reagents(datum/water/pipe_network/reference) + var/list/results = list() + + if(network == reference) + results += reagents + + return results + + disconnect(obj/machinery/water/reference) + if(reference==node) + del(network) + node = null + + return null \ No newline at end of file diff --git a/code/modules/water/components/valve.dm b/code/modules/water/components/valve.dm new file mode 100644 index 0000000000..e6ff88900a --- /dev/null +++ b/code/modules/water/components/valve.dm @@ -0,0 +1,320 @@ +obj/machinery/water/valve + icon = 'valve.dmi' + icon_state = "valve0" + + name = "manual water valve" + desc = "A pipe valve" + + dir = SOUTH + initialize_directions = SOUTH|NORTH + + var/open = 0 + + var/obj/machinery/water/node1 + var/obj/machinery/water/node2 + + var/datum/water/pipe_network/network_node1 + var/datum/water/pipe_network/network_node2 + + update_icon(animation) + if(animation) + flick("valve[src.open][!src.open]",src) + else + icon_state = "valve[open]" + + New() + switch(dir) + if(NORTH || SOUTH) + initialize_directions = NORTH|SOUTH + if(EAST || WEST) + initialize_directions = EAST|WEST + ..() + + network_expand(datum/water/pipe_network/new_network, obj/machinery/water/pipe/reference) + if(reference == node1) + network_node1 = new_network + if(open) + network_node2 = new_network + else if(reference == node2) + network_node2 = new_network + if(open) + network_node1 = new_network + + if(new_network.normal_members.Find(src)) + return 0 + + new_network.normal_members += src + + if(open) + if(reference == node1) + if(node2) + return node2.network_expand(new_network, src) + else if(reference == node2) + if(node1) + return node1.network_expand(new_network, src) + + return null + + Del() + loc = null + + if(node1) + node1.disconnect(src) + del(network_node1) + if(node2) + node2.disconnect(src) + del(network_node2) + + node1 = null + node2 = null + + ..() + + proc/open() + + if(open) return 0 + + open = 1 + update_icon() + + if(network_node1&&network_node2) + network_node1.merge(network_node2) + network_node2 = network_node1 + + if(network_node1) + network_node1.update = 1 + else if(network_node2) + network_node2.update = 1 + + return 1 + + proc/close() + + if(!open) + return 0 + + open = 0 + update_icon() + + if(network_node1) + del(network_node1) + if(network_node2) + del(network_node2) + + build_network() + + return 1 + + proc/normalize_dir() + if(dir==3) + dir = 1 + else if(dir==12) + dir = 4 + + attack_ai(mob/user as mob) + return + + attack_paw(mob/user as mob) + return attack_hand(user) + + attack_hand(mob/user as mob) + src.add_fingerprint(usr) + update_icon(1) + sleep(10) + if (src.open) + src.close() + else + src.open() + + process() + ..() + machines.Remove(src) + + if(open && (!node1 || !node2)) + var/n2dir = (dir & (NORTH|SOUTH)) ? SOUTH : WEST + + if(node1 && !node2) + mingle_dc_with_turf(n2dir, network_node1) // leak 1->2 + else if(!node1 && node2) + mingle_dc_with_turf(turn(n2dir,180), network_node2) // leak 2->1 + // if it's disconnected on both ends, or closed, do nothing + + return + + initialize() + normalize_dir() + + var/node1_dir + var/node2_dir + + for(var/direction in cardinal) + if(direction&initialize_directions) + if (!node1_dir) + node1_dir = direction + else if (!node2_dir) + node2_dir = direction + + for(var/obj/machinery/water/target in get_step(src,node1_dir)) + if(target.initialize_directions & get_dir(target,src)) + node1 = target + break + for(var/obj/machinery/water/target in get_step(src,node2_dir)) + if(target.initialize_directions & get_dir(target,src)) + node2 = target + break +/* + var/connect_directions + switch(dir) + if(NORTH) + connect_directions = NORTH|SOUTH + if(SOUTH) + connect_directions = NORTH|SOUTH + if(EAST) + connect_directions = EAST|WEST + if(WEST) + connect_directions = EAST|WEST + else + connect_directions = dir + + for(var/direction in cardinal) + if(direction&connect_directions) + for(var/obj/machinery/atmospherics/target in get_step(src,direction)) + if(target.initialize_directions & get_dir(target,src)) + connect_directions &= ~direction + node1 = target + break + if(node1) + break + + for(var/direction in cardinal) + if(direction&connect_directions) + for(var/obj/machinery/atmospherics/target in get_step(src,direction)) + if(target.initialize_directions & get_dir(target,src)) + node2 = target + break + if(node1) + break +*/ + build_network() + if(!network_node1 && node1) + network_node1 = new /datum/water/pipe_network() + network_node1.normal_members += src + network_node1.build_network(node1, src) + + if(!network_node2 && node2) + network_node2 = new /datum/water/pipe_network() + network_node2.normal_members += src + network_node2.build_network(node2, src) + + + return_network(obj/machinery/water/reference) + build_network() + + if(reference==node1) + return network_node1 + + if(reference==node2) + return network_node2 + + return null + + reassign_network(datum/water/pipe_network/old_network, datum/water/pipe_network/new_network) + if(network_node1 == old_network) + network_node1 = new_network + if(network_node2 == old_network) + network_node2 = new_network + + return 1 + + return_network_reagents(datum/water/pipe_network/reference) + return null + + disconnect(obj/machinery/water/reference) + if(reference==node1) + del(network_node1) + node1 = null + + else if(reference==node2) + del(network_node2) + node2 = null + + return null + + digital // can be controlled by AI + name = "digital water valve" + desc = "A digitally controlled valve." + icon = 'digital_valve.dmi' + + attack_ai(mob/user as mob) + return src.attack_hand(user) + + attack_hand(mob/user as mob) + if(!src.allowed(user)) + user << "\red Access denied." + return + ..() + + //Radio remote control + + proc + set_frequency(new_frequency) + radio_controller.remove_object(src, frequency) + frequency = new_frequency + if(frequency) + radio_connection = radio_controller.add_object(src, frequency, RADIO_ATMOSIA) + + var/frequency = 0 + var/id = null + var/datum/radio_frequency/radio_connection + + initialize() + ..() + if(frequency) + set_frequency(frequency) + + receive_signal(datum/signal/signal) + if(!signal.data["tag"] || (signal.data["tag"] != id)) + return 0 + + switch(signal.data["command"]) + if("valve_open") + if(!open) + open() + + if("valve_close") + if(open) + close() + + if("valve_toggle") + if(open) + close() + else + open() + + proc/return_pressure() + return network_node1.return_pressure_transient() + + attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) + if (!istype(W, /obj/item/weapon/wrench)) + return ..() + if (istype(src, /obj/machinery/water/valve/digital)) + user << "\red You cannot unwrench this [src], it's too complicated." + return 1 + var/turf/T = src.loc + if (level==1 && isturf(T) && T.intact) + user << "\red You must remove the plating first." + return 1 + var/datum/gas_mixture/env_air = loc.return_air() + if ((return_pressure()-env_air.return_pressure()) > 2*ONE_ATMOSPHERE) + user << "\red You cannot unwrench this [src], it too exerted due to internal pressure." + add_fingerprint(user) + return 1 + playsound(src.loc, 'Ratchet.ogg', 50, 1) + user << "\blue You begin to unfasten \the [src]..." + if (do_after(user, 40)) + user.visible_message( \ + "[user] unfastens \the [src].", \ + "\blue You have unfastened \the [src].", \ + "You hear ratchet.") + new /obj/item/water_pipe(loc, make_from=src) + del(src) diff --git a/code/modules/water/components/water_glass_connector.dm b/code/modules/water/components/water_glass_connector.dm new file mode 100644 index 0000000000..6e38ef3905 --- /dev/null +++ b/code/modules/water/components/water_glass_connector.dm @@ -0,0 +1,207 @@ +/obj/machinery/water/glass_connector + icon = 'water_glass_connector.dmi' + icon_state = "intact" + name = "Beaker Connection" + desc = "For connecting portables devices related to reagents." + + dir = SOUTH + initialize_directions = SOUTH + density = 1 + + var/obj/item/weapon/reagent_containers/glass/connected_device + + var/obj/machinery/water/node + var/datum/water/pipe_network/network + + New() + initialize_directions = dir + ..() + + update_icon() + if(node) + icon_state = "[node.level == 1 && istype(loc, /turf/simulated) ? "h" : "" ]intact" + dir = get_dir(src, node) + else + icon_state = "exposed" + + overlays = new() + if(connected_device) + overlays += "inserted" + + if(connected_device.reagents.total_volume) + var/obj/effect/overlay = new/obj + overlay.icon = 'water_glass_connector.dmi' + overlay.icon_state = "window" + + var/list/rgbcolor = list(0,0,0) + var/finalcolor + for(var/datum/reagent/re in connected_device.reagents.reagent_list) // natural color mixing bullshit/algorithm + if(!finalcolor) + rgbcolor = GetColors(re.color) + finalcolor = re.color + else + var/newcolor[3] + var/prergbcolor[3] + prergbcolor = rgbcolor + newcolor = GetColors(re.color) + + rgbcolor[1] = (prergbcolor[1]+newcolor[1])/2 + rgbcolor[2] = (prergbcolor[2]+newcolor[2])/2 + rgbcolor[3] = (prergbcolor[3]+newcolor[3])/2 + + finalcolor = rgb(rgbcolor[1], rgbcolor[2], rgbcolor[3]) + // This isn't a perfect color mixing system, the more reagents that are inside, + // the darker it gets until it becomes absolutely pitch black! I dunno, maybe + // that's pretty realistic? I don't do a whole lot of color-mixing anyway. + // If you add brighter colors to it it'll eventually get lighter, though. + + overlay.icon += finalcolor + overlays += overlay + + on_reagent_change(var/mob/user) + update_icon() + + process() + ..() + if(!connected_device) + return + if(network) + network.update = 1 + return 1 + +// Housekeeping and pipe network stuff below + network_expand(datum/water/pipe_network/new_network, obj/machinery/water/pipe/reference) + if(reference == node) + network = new_network + + if(new_network.normal_members.Find(src)) + return 0 + + new_network.normal_members += src + + return null + + Del() + loc = null + + if(connected_device) + connected_device.loc = src.loc + + if(node) + node.disconnect(src) + del(network) + + node = null + + ..() + + initialize() + if(node) return + + var/node_connect = dir + + for(var/obj/machinery/water/target in get_step(src,node_connect)) + if(target.initialize_directions & get_dir(target,src)) + node = target + break + + update_icon() + + build_network() + if(!network && node) + network = new /datum/water/pipe_network() + network.normal_members += src + network.build_network(node, src) + + + return_network(obj/machinery/water/reference) + build_network() + + if(reference==node) + return network + + if(reference==connected_device) + return network + + return null + + reassign_network(datum/water/pipe_network/old_network, datum/water/pipe_network/new_network) + if(network == old_network) + network = new_network + + return 1 + + return_network_reagents(datum/water/pipe_network/reference) + var/list/results = list() + + if(connected_device) + results += connected_device.reagents + + return results + + disconnect(obj/machinery/water/reference) + if(reference==node) + del(network) + node = null + + return null + + proc/update_connection(inserting) + if(inserting) + if(network && !network.reagents.Find(connected_device.reagents)) + network.reagents += connected_device.reagents + connected_device.reagents.my_atom = src + network.update = 1 + else + if(network && network.reagents.Find(connected_device.reagents)) + network.reagents -= connected_device.reagents + connected_device.reagents.my_atom = connected_device + + attack_hand(mob/user as mob) + if (connected_device) + var/obj/item/weapon/reagent_containers/glass/B = connected_device + B.loc = src.loc + update_connection(0) + connected_device = null + update_icon() + else + ..() + + proc/return_pressure() + return network.return_pressure_transient() + + attackby(obj/item/weapon/W as obj, mob/user as mob) + if(istype(W, /obj/item/weapon/wrench)) + if(connected_device) + user << "\red You cannot unwrench this [src], remove [connected_device] first." + return 1 + var/turf/T = src.loc + if (level==1 && isturf(T) && T.intact) + user << "\red You must remove the plating first." + return 1 + var/datum/gas_mixture/env_air = loc.return_air() + if ((return_pressure()-env_air.return_pressure()) > 2*ONE_ATMOSPHERE) + user << "\red You cannot unwrench this [src], it too exerted due to internal pressure." + add_fingerprint(user) + return 1 + playsound(src.loc, 'Ratchet.ogg', 50, 1) + user << "\blue You begin to unfasten \the [src]..." + if (do_after(user, 40)) + user.visible_message( \ + "[user] unfastens \the [src].", \ + "\blue You have unfastened \the [src].", \ + "You hear ratchet.") + new /obj/item/water_pipe(loc, make_from=src) + del(src) + else if(istype(W, /obj/item/weapon/reagent_containers/glass)) + if(connected_device) + user << "\red You cannot insert this [src], remove [connected_device] first." + return 1 + connected_device = W + update_connection(1) + user.drop_item() + W.loc = src + update_icon() + user << "\blue You add \the [W] to \the [src]." + else + return ..() \ No newline at end of file diff --git a/code/modules/water/components/water_portables_connector.dm b/code/modules/water/components/water_portables_connector.dm new file mode 100644 index 0000000000..75372ae887 --- /dev/null +++ b/code/modules/water/components/water_portables_connector.dm @@ -0,0 +1,158 @@ +/obj/machinery/water/portables_connector + icon = 'portables_connector.dmi' + icon_state = "intact" + + name = "Water Connector Port" + desc = "For connecting reagent dispensers, such as water tanks." + + dir = SOUTH + initialize_directions = SOUTH + + var/obj/structure/reagent_dispensers/connected_device + + var/obj/machinery/water/node + + var/datum/water/pipe_network/network + + var/on = 0 + + level = 0 + + + New() + initialize_directions = dir + ..() + + update_icon() + if(node) + icon_state = "[level == 1 && istype(loc, /turf/simulated) ? "h" : "" ]intact" + dir = get_dir(src, node) + else + icon_state = "exposed" + + return + + hide(var/i) //to make the little pipe section invisible, the icon changes. + if(node) + icon_state = "[i == 1 && istype(loc, /turf/simulated) ? "h" : "" ]intact" + dir = get_dir(src, node) + else + icon_state = "exposed" + + process() + ..() + if(!on) + return + if(!connected_device) + on = 0 + return + if(network) + network.update = 1 + return 1 + +// Housekeeping and pipe network stuff below + network_expand(datum/water/pipe_network/new_network, obj/machinery/water/pipe/reference) + if(reference == node) + network = new_network + + if(new_network.normal_members.Find(src)) + return 0 + + new_network.normal_members += src + + return null + + Del() + loc = null + + if(connected_device) + connected_device.disconnect() + + if(node) + node.disconnect(src) + del(network) + + node = null + + ..() + + initialize() + if(node) return + + var/node_connect = dir + + for(var/obj/machinery/water/target in get_step(src,node_connect)) + if(target.initialize_directions & get_dir(target,src)) + node = target + break + + update_icon() + + build_network() + if(!network && node) + network = new /datum/water/pipe_network() + network.normal_members += src + network.build_network(node, src) + + + return_network(obj/machinery/water/reference) + build_network() + + if(reference==node) + return network + + if(reference==connected_device) + return network + + return null + + reassign_network(datum/water/pipe_network/old_network, datum/water/pipe_network/new_network) + if(network == old_network) + network = new_network + + return 1 + + return_network_reagents(datum/water/pipe_network/reference) + var/list/results = list() + + if(connected_device) + results += connected_device.reagents + + return results + + disconnect(obj/machinery/water/reference) + if(reference==node) + del(network) + node = null + + return null + + proc/return_pressure() + return network.return_pressure_transient() + + attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) + if (!istype(W, /obj/item/weapon/wrench)) + return ..() + if (connected_device) + user << "\red You cannot unwrench this [src], dettach [connected_device] first." + return 1 + if (locate(/obj/structure/reagent_dispensers, src.loc)) + return 1 + var/turf/T = src.loc + if (level==1 && isturf(T) && T.intact) + user << "\red You must remove the plating first." + return 1 + var/datum/gas_mixture/env_air = loc.return_air() + if ((return_pressure()-env_air.return_pressure()) > 2*ONE_ATMOSPHERE) + user << "\red You cannot unwrench this [src], it too exerted due to internal pressure." + add_fingerprint(user) + return 1 + playsound(src.loc, 'Ratchet.ogg', 50, 1) + user << "\blue You begin to unfasten \the [src]..." + if (do_after(user, 40)) + user.visible_message( \ + "[user] unfastens \the [src].", \ + "\blue You have unfastened \the [src].", \ + "You hear ratchet.") + new /obj/item/water_pipe(loc, make_from=src) + del(src) diff --git a/code/modules/water/construction.dm b/code/modules/water/construction.dm new file mode 100644 index 0000000000..62f65e62cf --- /dev/null +++ b/code/modules/water/construction.dm @@ -0,0 +1,433 @@ +/obj/machinery/pipedispenser/water + name = "Water Pipe Dispenser" + +/obj/machinery/pipedispenser/water/attack_hand(user as mob) + if(..()) + return + + var/dat = {" +Regular pipes:
+Pipe
+Bent Pipe
+Manifold
+Manual Valve
+Devices:
+Beaker Connection
+Portables Connection
+Water Pump
+Water Meter
+Water Filter
+Fixture Connection
+Sprinkler
+"} + + user << browse("[src][dat]", "window=pipedispenser") + return + +/obj/machinery/pipedispenser/water/Topic(href, href_list) + if(..()) + return + if(unwrenched) + usr << browse(null, "window=pipedispenser") + return + usr.machine = src + src.add_fingerprint(usr) + if(href_list["wmake"]) + if(!wait) + var/p_type = text2num(href_list["wmake"]) + var/p_dir = text2num(href_list["dir"]) + var/obj/item/water_pipe/P = new (/*usr.loc*/ src.loc, pipe_type=p_type, dir=p_dir) + P.update() + wait = 1 + spawn(10) + wait = 0 + if(href_list["makemeter"]) + if(!wait) + new /obj/item/water_pipe_meter(/*usr.loc*/ src.loc) + wait = 1 + spawn(15) + wait = 0 + return + +#define PIPE_SIMPLE_STRAIGHT 0 +#define PIPE_SIMPLE_BENT 1 +#define PIPE_GLASS_CONNECTOR 2 +#define PIPE_MANIFOLD 3 +#define PIPE_SPRINKLER 4 +#define PIPE_MVALVE 5 +#define PIPE_PUMP 6 +#define PIPE_FIXTURE 7 +#define PIPE_FILTER 8 +#define PIPE_CONNECTOR 9 + +/obj/item/water_pipe + name = "water pipe" + desc = "A pipe" + var/pipe_type = 0 + //var/pipe_dir = 0 + var/pipename + icon = 'water_pipe_item.dmi' + icon_state = "simple" + item_state = "buildpipe" + flags = TABLEPASS|FPRINT + w_class = 4 + level = 2 + +/obj/item/water_pipe/New(var/loc, var/pipe_type as num, var/dir as num, var/obj/machinery/water/make_from = null) + ..() + if (make_from) + src.dir = make_from.dir + src.pipename = make_from.name + var/is_bent + if (make_from.initialize_directions in list(NORTH|SOUTH, WEST|EAST)) + is_bent = 0 + else + is_bent = 1 + + if(istype(make_from, /obj/machinery/water/pipe/simple)) + src.pipe_type = PIPE_SIMPLE_STRAIGHT + is_bent + else if(istype(make_from, /obj/machinery/water/glass_connector)) + src.pipe_type = PIPE_GLASS_CONNECTOR + else if(istype(make_from, /obj/machinery/water/portables_connector)) + src.pipe_type = PIPE_CONNECTOR + else if(istype(make_from, /obj/machinery/water/pipe/manifold)) + src.pipe_type = PIPE_MANIFOLD + else if(istype(make_from, /obj/machinery/water/unary/sprinkler)) + src.pipe_type = PIPE_SPRINKLER + else if(istype(make_from, /obj/machinery/water/valve)) + src.pipe_type = PIPE_MVALVE + else if(istype(make_from, /obj/machinery/water/binary/pump)) + src.pipe_type = PIPE_PUMP + else if(istype(make_from, /obj/machinery/water/binary/fixture)) + src.pipe_type = PIPE_FIXTURE + else if(istype(make_from, /obj/machinery/water/trinary/filter)) + src.pipe_type = PIPE_FILTER + else + src.pipe_type = pipe_type + src.dir = dir + //src.pipe_dir = get_pipe_dir() + update() + src.pixel_x = rand(-5, 5) + src.pixel_y = rand(-5, 5) + +//update the name and icon of the pipe item depending on the type + +/obj/item/water_pipe/proc/update() + var/list/nlist = list( \ + "pipe", \ + "bent pipe", \ + "glass connector", \ + "manifold", \ + "sprinkler", \ + "mvalve", \ + "pump", \ + "fixture connection", \ + "liquid filter", \ + "connector", \ + ) + name = nlist[pipe_type+1] + " fitting" + var/list/islist = list( \ + "simple", \ + "simple", \ + "gconnector", \ + "manifold", \ + "sprinkler", \ + "mvalve", \ + "pump", \ + "fixture", \ + "filter", \ + "connector", \ + ) + icon_state = islist[pipe_type + 1] + +//called when a turf is attacked with a pipe item +// place the pipe on the turf, setting pipe level to 1 (underfloor) if the turf is not intact + +// rotate the pipe item clockwise + +/obj/item/water_pipe/verb/rotate() + set category = "Object" + set name = "Rotate Pipe" + set src in view(1) + + if ( usr.stat || usr.restrained() ) + return + + src.dir = turn(src.dir, -90) + + if (pipe_type in list (PIPE_SIMPLE_STRAIGHT, PIPE_MVALVE)) + if(dir==2) + dir = 1 + else if(dir==8) + dir = 4 + //src.pipe_dir = get_pipe_dir() + return + +/obj/item/water_pipe/Move() + ..() + if ((pipe_type in list (PIPE_SIMPLE_BENT)) \ + && (src.dir in cardinal)) + src.dir = src.dir|turn(src.dir, 90) + else if (pipe_type in list (PIPE_SIMPLE_STRAIGHT, PIPE_MVALVE)) + if(dir==2) + dir = 1 + else if(dir==8) + dir = 4 + return + +// returns all pipe's endpoints + +/obj/item/water_pipe/proc/get_pipe_dir() + if (!dir) + return 0 + var/flip = turn(dir, 180) + var/cw = turn(dir, -90) + var/acw = turn(dir, 90) + + switch(pipe_type) + if( PIPE_SIMPLE_STRAIGHT, \ + PIPE_PUMP ,\ + PIPE_MVALVE, \ + PIPE_FIXTURE \ + ) + return dir|flip + if(PIPE_SIMPLE_BENT) + return dir //dir|acw + if(PIPE_GLASS_CONNECTOR, PIPE_CONNECTOR, PIPE_SPRINKLER) + return dir + if(PIPE_MANIFOLD) + return flip|cw|acw + if(PIPE_FILTER) + return dir|flip|cw + return 0 + +/obj/item/water_pipe/proc/get_pdir() //endpoints for regular pipes + return get_pipe_dir() + +/obj/item/water_pipe/attack_self(mob/user as mob) + return rotate() + +/obj/item/water_pipe/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) + ..() + //* + if (!istype(W, /obj/item/weapon/wrench)) + return ..() + if (!isturf(src.loc)) + return 1 + if (pipe_type in list (PIPE_SIMPLE_STRAIGHT, PIPE_MVALVE)) + if(dir==2) + dir = 1 + else if(dir==8) + dir = 4 + var/pipe_dir = get_pipe_dir() + + for(var/obj/machinery/water/M in src.loc) + if(M.initialize_directions & pipe_dir) // matches at least one direction on either type of pipe + user << "\red There is already a pipe at that location." + return 1 + // no conflicts found + + var/pipefailtext = "\red There's nothing to connect this pipe section to! (with how the pipe code works, at least one end needs to be connected to something, otherwise the game deletes the segment)" + + switch(pipe_type) + if(PIPE_SIMPLE_STRAIGHT, PIPE_SIMPLE_BENT) + var/obj/machinery/water/pipe/simple/P = new( src.loc ) + P.dir = src.dir + P.initialize_directions = pipe_dir + var/turf/T = P.loc + P.level = T.intact ? 2 : 1 + P.initialize() + if (!P) + usr << pipefailtext + return 1 + P.build_network() + if (P.node1) + P.node1.initialize() + P.node1.build_network() + if (P.node2) + P.node2.initialize() + P.node2.build_network() + + if(PIPE_GLASS_CONNECTOR) // glass connector + var/obj/machinery/water/glass_connector/C = new( src.loc ) + C.dir = dir + C.initialize_directions = pipe_dir + if (pipename) + C.name = pipename + C.initialize() + C.build_network() + if (C.node) + C.node.initialize() + C.node.build_network() + + if(PIPE_CONNECTOR) // portables connector + var/obj/machinery/water/portables_connector/C = new( src.loc ) + C.dir = dir + C.initialize_directions = pipe_dir + if (pipename) + C.name = pipename + var/turf/T = C.loc + C.level = T.intact ? 2 : 1 + C.initialize() + C.build_network() + if (C.node) + C.node.initialize() + C.node.build_network() + + + if(PIPE_MANIFOLD) //manifold + var/obj/machinery/water/pipe/manifold/M = new( src.loc ) + M.dir = dir + M.initialize_directions = pipe_dir + //M.New() + var/turf/T = M.loc + M.level = T.intact ? 2 : 1 + M.initialize() + if (!M) + usr << "There's nothing to connect this manifold to! (with how the pipe code works, at least one end needs to be connected to something, otherwise the game deletes the segment)" + return 1 + M.build_network() + if (M.node1) + M.node1.initialize() + M.node1.build_network() + if (M.node2) + M.node2.initialize() + M.node2.build_network() + if (M.node3) + M.node3.initialize() + M.node3.build_network() + + if(PIPE_SPRINKLER) //sprinkler + var/obj/machinery/water/unary/sprinkler/V = new( src.loc ) + V.dir = dir + V.initialize_directions = pipe_dir + if (pipename) + V.name = pipename + var/turf/T = V.loc + V.level = T.intact ? 2 : 1 + V.initialize() + V.build_network() + if (V.node) + V.node.initialize() + V.node.build_network() + + if(PIPE_FIXTURE) //fixture connection + var/obj/machinery/water/binary/fixture/V = new( src.loc ) + V.dir = dir + V.initialize_directions = pipe_dir + if (pipename) + V.name = pipename + var/turf/T = V.loc + V.level = T.intact ? 2 : 1 + V.initialize() + V.build_network() + if (V.node1) + V.node1.initialize() + V.node1.build_network() + if (V.node2) + V.node2.initialize() + V.node2.build_network() + + + if(PIPE_MVALVE) //manual valve + var/obj/machinery/water/valve/V = new( src.loc) + V.dir = dir + V.initialize_directions = pipe_dir + if (pipename) + V.name = pipename + var/turf/T = V.loc + V.level = T.intact ? 2 : 1 + V.initialize() + V.build_network() + if (V.node1) +// world << "[V.node1.name] is connected to valve, forcing it to update its nodes." + V.node1.initialize() + V.node1.build_network() + if (V.node2) +// world << "[V.node2.name] is connected to valve, forcing it to update its nodes." + V.node2.initialize() + V.node2.build_network() + + if(PIPE_PUMP) //gas pump + var/obj/machinery/water/binary/pump/P = new(src.loc) + P.dir = dir + P.initialize_directions = pipe_dir + if (pipename) + P.name = pipename + var/turf/T = P.loc + P.level = T.intact ? 2 : 1 + P.initialize() + P.build_network() + if (P.node1) + P.node1.initialize() + P.node1.build_network() + if (P.node2) + P.node2.initialize() + P.node2.build_network() + + if(PIPE_FILTER) //liquid filter + var/obj/machinery/water/trinary/filter/P = new(src.loc) + P.dir = dir + P.initialize_directions = pipe_dir + if (pipename) + P.name = pipename + var/turf/T = P.loc + P.level = T.intact ? 2 : 1 + P.initialize() + P.build_network() + if (P.node1) + P.node1.initialize() + P.node1.build_network() + if (P.node2) + P.node2.initialize() + P.node2.build_network() + if (P.node3) + P.node3.initialize() + P.node3.build_network() + + playsound(src.loc, 'Ratchet.ogg', 50, 1) + user.visible_message( \ + "[user] fastens the [src].", \ + "\blue You have fastened the [src].", \ + "You hear ratchet.") + del(src) // remove the pipe item + + return + //TODO: DEFERRED + +// ensure that setterm() is called for a newly connected pipeline + + + +/obj/item/water_pipe_meter + name = "water meter" + desc = "A meter that can be laid on water pipes" + icon = 'water_pipe_item.dmi' + icon_state = "meter" + item_state = "buildpipe" + flags = TABLEPASS|FPRINT + w_class = 4 + +/obj/item/water_pipe_meter/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) + ..() + + if (!istype(W, /obj/item/weapon/wrench)) + return ..() + if(!locate(/obj/machinery/water/pipe, src.loc)) + user << "\red You need to fasten it to a pipe" + return 1 + new/obj/machinery/water_meter( src.loc ) + playsound(src.loc, 'Ratchet.ogg', 50, 1) + user << "\blue You have fastened the meter to the pipe" + del(src) + +#undef PIPE_SIMPLE_STRAIGHT +#undef PIPE_SIMPLE_BENT +#undef PIPE_GLASS_CONNECTOR +#undef PIPE_MANIFOLD +#undef PIPE_SPRINKLER +#undef PIPE_MVALVE +#undef PIPE_PUMP +#undef PIPE_FIXTURE +#undef PIPE_FILTER +#undef PIPE_CONNECTOR \ No newline at end of file diff --git a/code/modules/water/water.dm b/code/modules/water/water.dm new file mode 100644 index 0000000000..e53be207bc --- /dev/null +++ b/code/modules/water/water.dm @@ -0,0 +1,69 @@ +/* +You'll notice this is the atmospherics system, +but with reagents instead gas mixtures. + +Quick overview: + +Pipes combine to form pipelines +Pipelines and other water objects combine to form pipe_networks + Note: A single pipe_network represents a completely open space + +Pipes -> Pipelines +Pipelines + Other Objects -> Pipe network + +*/ + +obj/machinery/water + anchored = 1 + use_power = 0 + idle_power_usage = 0 + active_power_usage = 0 + power_channel = ENVIRON + var/nodealert = 0 + + + + var/initialize_directions = 0 + var/color + + process() + build_network() + ..() + + proc + network_expand(datum/water/pipe_network/new_network, obj/machinery/water/pipe/reference) + // Check to see if should be added to network. Add self if so and adjust variables appropriately. + // Note don't forget to have neighbors look as well! + + return null + + build_network() + // Called to build a network from this node + + return null + + return_network(obj/machinery/water/reference) + // Returns pipe_network associated with connection to reference + // Notes: should create network if necessary + // Should never return null + + return null + + reassign_network(datum/water/pipe_network/old_network, datum/water/pipe_network/new_network) + // Used when two pipe_networks are combining + + return_network_reagents(datum/water/pipe_network/reference) + // Return a list of reagents(s) in the object + // associated with reference pipe_network for use in rebuilding the networks gases list + // Is permitted to return null + + disconnect(obj/machinery/water/reference) + + mingle_dc_with_turf(D, datum/water/pipe_network/net) + // 250 being a pipe opening volume + mingle_outflow_with_turf(get_turf(src), \ + net.reagents_transient.total_volume / net.reagents_transient.maximum_volume * 250, \ + D, network = net) + + update_icon() + return null \ No newline at end of file diff --git a/code/modules/water/water_meter.dm b/code/modules/water/water_meter.dm new file mode 100644 index 0000000000..f9ddba4817 --- /dev/null +++ b/code/modules/water/water_meter.dm @@ -0,0 +1,121 @@ +/obj/machinery/water_meter + name = "water meter" + desc = "It measures water flow." + icon = 'meter.dmi' + icon_state = "meterX" + var/obj/machinery/water/pipe/target = null + anchored = 1.0 + var/frequency = 0 + var/id + use_power = 1 + idle_power_usage = 2 + active_power_usage = 4 + +/obj/machinery/water_meter/New() + ..() + + target = locate(/obj/machinery/water/pipe) in loc + return 1 + +/obj/machinery/water_meter/initialize() + if (!target) + target = locate(/obj/machinery/water/pipe) in loc + +/obj/machinery/water_meter/process() + if(!target) + icon_state = "meterX" + return 0 + + if(stat & (BROKEN|NOPOWER)) + icon_state = "meter0" + return 0 + + use_power(5) + + if(!target || !target.parent) + icon_state = "meterX" + return 0 + + var/datum/water/pipeline/pl = target.parent + var/pressure = pl.return_pressure() / target.max_pressure * 60*ONE_ATMOSPHERE + if(pressure <= 0.15*ONE_ATMOSPHERE) + icon_state = "meter0" + else if(pressure <= 1.8*ONE_ATMOSPHERE) + var/val = round(pressure/(ONE_ATMOSPHERE*0.3) + 0.5) + icon_state = "meter1_[val]" + else if(pressure <= 30*ONE_ATMOSPHERE) + var/val = round(pressure/(ONE_ATMOSPHERE*5)-0.35) + 1 + icon_state = "meter2_[val]" + else if(pressure <= 59*ONE_ATMOSPHERE) + var/val = round(pressure/(ONE_ATMOSPHERE*5) - 6) + 1 + icon_state = "meter3_[val]" + else + icon_state = "meter4" + + if(frequency) + var/datum/radio_frequency/radio_connection = radio_controller.return_frequency(frequency) + + if(!radio_connection) return + + var/datum/signal/signal = new + signal.source = src + signal.transmission_method = 1 + signal.data = list( + "tag" = id, + "device" = "AM", + "pressure" = round(pl.return_pressure()), + "sigtype" = "status" + ) + radio_connection.post_signal(src, signal) + +/obj/machinery/water_meter/examine() + set src in view(3) + + var/t = "A gas flow meter. " + if (target) + if(target.parent) + var/datum/water/pipeline/pl = target.parent + var/pressure = pl.return_pressure() + t += "The pressure gauge reads [round(pressure, 0.01)] kPa" + else + t += "The sensor error light is blinking." + else + t += "The connect error light is blinking." + + usr << t + + + +/obj/machinery/water_meter/attack_hand(mob/user as mob) + if(stat & (NOPOWER|BROKEN)) + return 1 + + var/t = null + if (get_dist(usr, src) <= 3 || istype(usr, /mob/living/silicon/ai) || istype(usr, /mob/dead)) + if (target) + if(target.parent) + var/datum/water/pipeline/pl = target.parent + var/pressure = pl.return_pressure() + t = "Pressure: [round(pressure, 0.01)] kPa" + else + t = "\red Results: Sensor Error!" + else + t = "\red Results: Connection Error!" + else + usr << "\blue You are too far away." + + usr << t + return 1 + +/obj/machinery/water_meter/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) + if (!istype(W, /obj/item/weapon/wrench)) + return ..() + playsound(src.loc, 'Ratchet.ogg', 50, 1) + user << "\blue You begin to unfasten \the [src]..." + if (do_after(user, 40)) + user.visible_message( \ + "[user] unfastens \the [src].", \ + "\blue You have unfastened \the [src].", \ + "You hear ratchet.") + new /obj/item/water_pipe_meter(src.loc) + del(src) \ No newline at end of file diff --git a/code/modules/water/water_pipe_network.dm b/code/modules/water/water_pipe_network.dm new file mode 100644 index 0000000000..96e323b38b --- /dev/null +++ b/code/modules/water/water_pipe_network.dm @@ -0,0 +1,200 @@ +var/global/list/datum/water/pipe_network/water_pipe_networks = list() + +/datum/water/pipe_network + var/list/datum/reagents/reagents = list() //All of the gas_mixtures continuously connected in this network + + var/list/obj/machinery/water/normal_members = list() + var/list/datum/water/pipeline/line_members = list() + //membership roster to go through for updates and what not + + var/update = 1 + var/datum/reagents/reagents_transient = null + var/max_pressure_transient = 3*ONE_ATMOSPHERE + + New() + reagents_transient = new() + + ..() + + proc/process() + //Equalize gases amongst pipe if called for + if(update) + update = 0 + reconcile_reagents() + + // release pipe leaks + var/dirs + for(var/datum/water/pipeline/P in line_members) + for(var/obj/machinery/water/pipe/E in P.edges) + dirs = E.initialize_directions + for(var/C in E.pipeline_expansion()) + if(C) // a connection, remove dir from possibilities + dirs &= ~(get_dir(E, C)) + if(dirs > 0) // there was a disconnect, release half of volume + P.mingle_with_turf(get_turf(E), \ + P.reagents.total_volume / P.reagents.maximum_volume * E.max_volume / 2, \ + dirs) + + proc/build_network(obj/machinery/water/start_normal, obj/machinery/water/reference) + //Purpose: Generate membership roster + //Notes: Assuming that members will add themselves to appropriate roster in network_expand() + + if(!start_normal) + del(src) + + start_normal.network_expand(src, reference) + + update_network_reagents() + + if((normal_members.len>0)||(line_members.len>0)) + water_pipe_networks += src + else + del(src) + + proc/merge(datum/water/pipe_network/giver) + if(giver==src) return 0 + + normal_members -= giver.normal_members + normal_members += giver.normal_members + + line_members -= giver.line_members + line_members += giver.line_members + + for(var/obj/machinery/water/normal_member in giver.normal_members) + normal_member.reassign_network(giver, src) + + for(var/datum/water/pipeline/line_member in giver.line_members) + line_member.network = src + + del(giver) + + update_network_reagents() + return 1 + + proc/update_network_reagents() + //Go through membership roster and make sure reagents is up to date + + reagents = list() + + for(var/obj/machinery/water/normal_member in normal_members) + var/result = normal_member.return_network_reagents(src) + if(result) reagents += result + + for(var/datum/water/pipeline/line_member in line_members) + reagents += line_member.reagents + + proc/reconcile_reagents() + //Perfectly equalize all reagent members instantly + + reagents_transient = new(0) + reagents_transient.my_atom = new/obj() + + for(var/datum/reagents/R in reagents) + // add in each reagents + reagents_transient.maximum_volume += R.maximum_volume + R.copy_to(reagents_transient, R.total_volume) + + if(reagents_transient.total_volume > 0) + update = 1 + + //Update individual reagents by volume ratio + for(var/datum/reagents/R in reagents) + R.clear_reagents() + for(var/datum/reagent/re in reagents_transient.reagent_list) + R.add_reagent(re.id, re.volume \ + * R.maximum_volume \ + / reagents_transient.maximum_volume) + return 1 + + proc/return_pressure_transient() + return reagents_transient.total_volume / reagents_transient.maximum_volume * max_pressure_transient + + + +proc/mingle_outflow_with_turf(turf/simulated/target, mingle_volume, dir = 0, datum/water/pipeline/pipeline = null, datum/water/pipe_network/network = null, datum/reagents/reagents = null, pressure = 0) + if(pipeline) + network = pipeline.network + if(!reagents) + reagents = pipeline.reagents + if(!pressure) + pressure = pipeline.return_pressure() + else + if(!reagents) + reagents = network.reagents_transient + if(!pressure) + pressure = network.return_pressure_transient() + + mingle_volume = min(mingle_volume, reagents.total_volume) + var/num_spots = round(pressure / 10) + + if(mingle_volume < num_spots) + return + + var/datum/reagents/mingle = new(mingle_volume) + mingle.my_atom = target + reagents.trans_to(mingle, mingle_volume) + + var/turf/T1 + var/turf/T2 + if(dir == 0) + T1 = get_step(target, NORTH) + T1 = get_step(T1, EAST) + + T2 = get_step(target, SOUTH) + T2 = get_step(T2, WEST) + else + T1 = get_step(target, turn(dir, -90)) + T1 = get_step(T1, dir) + + T2 = get_step(target, turn(dir, 90)) + for(var/i = 1 to 5) + T2 = get_step(T2, dir) + + var/box = block(T1, T2) + + for(var/i = 1 to num_spots) + spawn(0) + var/obj/effect/effect/water/W = new /obj/effect/effect/water(get_turf(target)) + var/turf/my_target = pick(box) + var/datum/reagents/R = new(mingle_volume/num_spots) + if(!W) return + W.reagents = R + R.my_atom = W + if(!W || !src) return + mingle.trans_to(W,mingle_volume/num_spots) + for(var/b=0, b<5, b++) + step_towards(W,my_target) + if(!W) return + W.reagents.reaction(get_turf(W)) + for(var/atom/atm in get_turf(W)) + if(!W) return + W.reagents.reaction(atm) + if(W.loc == my_target) break + + if(network) + network.update = 1 + +proc/equalize_reagents(var/list/datum/reagents/reagents) + //Perfectly equalize all reagent members instantly + + //Calculate totals from individual components + var/datum/reagents/reagents_transient = new(0) + reagents_transient.my_atom = new/obj() + + for(var/datum/reagents/R in reagents) + // add in each reagent + reagents_transient.maximum_volume += R.maximum_volume + R.copy_to(reagents_transient, R.total_volume) + + //Allow reagents to react + reagents_transient.handle_reactions() + + if(reagents_transient.total_volume > 0) + //Update individual reagents by volume ratio + for(var/datum/reagents/R in reagents) + R.clear_reagents() + for(var/datum/reagent/re in reagents_transient.reagent_list) + R.add_reagent(re.id, re.volume \ + * R.maximum_volume \ + / reagents_transient.maximum_volume) + return 1 diff --git a/code/modules/water/water_pipeline.dm b/code/modules/water/water_pipeline.dm new file mode 100644 index 0000000000..c0e6fbf836 --- /dev/null +++ b/code/modules/water/water_pipeline.dm @@ -0,0 +1,127 @@ +datum/water/pipeline + var/datum/reagents/reagents + var/max_pressure + + var/list/obj/machinery/water/pipe/members + var/list/obj/machinery/water/pipe/edges //Used for building networks + + var/datum/water/pipe_network/network + + var/alert_pressure = 0 + + Del() + if(network) + del(network) + + if(reagents && reagents.total_volume) + temporarily_store_reagents() + del(reagents) + + ..() + + proc/process()//This use to be called called from the pipe networks + //Check to see if pressure is within acceptable limits + var/pressure = return_pressure() + if(pressure > alert_pressure) + for(var/obj/machinery/water/pipe/member in members) + if(!member.check_pressure(pressure)) + break //Only delete 1 pipe per process + + //Allow for reactions + //air.react() //Should be handled by pipe_network now + + proc/temporarily_store_reagents() + //Update individual gas_mixtures by volume ratio + + for(var/obj/machinery/water/pipe/member in members) + member.reagents_temporary = new(member.max_volume) + member.reagents_temporary.my_atom = member + + for(var/datum/reagent/re in reagents.reagent_list) + re.volume = \ + reagents.get_reagent_amount(re.id) \ + * member.max_volume \ + / reagents.maximum_volume + + proc/build_pipeline(obj/machinery/water/pipe/base) + var/list/possible_expansions = list(base) + members = list(base) + edges = list() + + var/volume = base.max_volume + base.parent = src + alert_pressure = base.alert_pressure + + if(base.reagents_temporary) + reagents = base.reagents_temporary + base.reagents_temporary = null + else + reagents = new(base.max_volume) + reagents.my_atom = base + max_pressure = base.max_pressure + var/PT = 1 + + while(possible_expansions.len>0) + for(var/obj/machinery/water/pipe/borderline in possible_expansions) + + var/list/result = borderline.pipeline_expansion() + var/edge_check = result.len + + if(result.len>0) + for(var/obj/machinery/water/pipe/item in result) + if(!members.Find(item)) + members += item + possible_expansions += item + + volume += item.max_volume + max_pressure += item.max_pressure + PT++ + item.parent = src + + alert_pressure = min(alert_pressure, item.alert_pressure) + + if(item.reagents_temporary) + item.reagents_temporary.copy_to(reagents, item.reagents_temporary.total_volume) + + edge_check-- + + if(edge_check>0) + edges += borderline + + possible_expansions -= borderline + + reagents.maximum_volume = volume + max_pressure /= PT + + proc/network_expand(datum/water/pipe_network/new_network, obj/machinery/water/pipe/reference) + + if(new_network.line_members.Find(src)) + return 0 + + new_network.line_members += src + + network = new_network + + for(var/obj/machinery/water/pipe/edge in edges) + for(var/obj/machinery/water/result in edge.pipeline_expansion()) + if(!istype(result,/obj/machinery/water/pipe) && (result!=reference)) + result.network_expand(new_network, edge) + + return 1 + + proc/return_network(obj/machinery/water/reference) + if(!network) + network = new /datum/water/pipe_network() + network.build_network(src, null) + //technically passing these parameters should not be allowed + //however pipe_network.build_network(..) and pipeline.network_extend(...) + // were setup to properly handle this case + + return network + + proc/return_pressure() + return reagents.total_volume / reagents.maximum_volume * max_pressure + + proc/mingle_with_turf(turf/simulated/target, mingle_volume, dir = 0) + // dump all of section's volume + mingle_outflow_with_turf(target, mingle_volume, dir, pipeline=src) \ No newline at end of file diff --git a/code/modules/water/water_pipes.dm b/code/modules/water/water_pipes.dm new file mode 100644 index 0000000000..b252fa0772 --- /dev/null +++ b/code/modules/water/water_pipes.dm @@ -0,0 +1,961 @@ +obj/machinery/water/pipe + var/datum/reagents/reagents_temporary //used when reconstructing a pipeline that broke + var/datum/water/pipeline/parent + + var/max_volume = 0 + var/max_pressure = 3 * ONE_ATMOSPHERE + var/force = 20 + + layer = 2.3 //under water pipes with their 2.4 + + var/alert_pressure = 2.5*ONE_ATMOSPHERE + //minimum pressure before check_pressure(...) should be called + + New() + ..() + + proc/pipeline_expansion() + return null + + proc/check_pressure(pressure) + //Return 1 if parent should continue checking other pipes + //Return null if parent should stop checking other pipes. Recall: del(src) will by default return null + + return 1 + + proc/return_reagents() + if(!parent) + parent = new /datum/water/pipeline() + parent.build_pipeline(src) + + return parent.reagents + + build_network() + if(!parent) + parent = new /datum/water/pipeline() + parent.build_pipeline(src) + + return parent.return_network() + + network_expand(datum/water/pipe_network/new_network, obj/machinery/water/pipe/reference) + if(!parent) + parent = new /datum/water/pipeline() + parent.build_pipeline(src) + + return parent.network_expand(new_network, reference) + + return_network(obj/machinery/water/reference) + if(!parent) + parent = new /datum/water/pipeline() + parent.build_pipeline(src) + + return parent.return_network(reference) + + Del() + var/turf/simulated/target = get_turf(loc) + if(istype(target) && parent) + parent.mingle_with_turf(target, \ + parent.reagents.total_volume / parent.reagents.maximum_volume * max_volume) + del(parent) + ..() + + simple + icon = 'pipes.dmi' + icon_state = "intact-f" + + name = "pipe" + desc = "A one meter section of regular pipe" + + max_volume = 500 + + dir = SOUTH + initialize_directions = SOUTH|NORTH + + var/obj/machinery/water/node1 + var/obj/machinery/water/node2 + + var/burst_pressure = 2.8*ONE_ATMOSPHERE + var/fatigue_pressure = 2.5*ONE_ATMOSPHERE + + + level = 1 + + New() + ..() + switch(dir) + if(SOUTH || NORTH) + initialize_directions = SOUTH|NORTH + if(EAST || WEST) + initialize_directions = EAST|WEST + if(NORTHEAST) + initialize_directions = NORTH|EAST + if(NORTHWEST) + initialize_directions = NORTH|WEST + if(SOUTHEAST) + initialize_directions = SOUTH|EAST + if(SOUTHWEST) + initialize_directions = SOUTH|WEST + + hide(var/i) + if(level == 1 && istype(loc, /turf/simulated)) + invisibility = i ? 101 : 0 + update_icon() + + process() + if(!parent) //This should cut back on the overhead calling build_network thousands of times per cycle + ..() + else + machines.Remove(src) + + /*if(!node1) + parent.mingle_with_turf(loc, volume) + if(!nodealert) + //world << "Missing node from [src] at [src.x],[src.y],[src.z]" + nodealert = 1 + + else if(!node2) + parent.mingle_with_turf(loc, volume) + if(!nodealert) + //world << "Missing node from [src] at [src.x],[src.y],[src.z]" + nodealert = 1 + else if (nodealert) + nodealert = 0 + + + else if(parent) + var/environment_temperature = 0 + + if(istype(loc, /turf/simulated/)) + if(loc:blocks_air) + environment_temperature = loc:temperature + else + var/datum/gas_mixture/environment = loc.return_air() + environment_temperature = environment.temperature + + else + environment_temperature = loc:temperature + + var/datum/gas_mixture/pipe_air = return_air() + + if(abs(environment_temperature-pipe_air.temperature) > minimum_temperature_difference) + parent.temperature_interact(loc, volume, thermal_conductivity) + */ //Screw you heat lag + + check_pressure(pressure) + var/datum/gas_mixture/environment = loc.return_air() + + var/pressure_difference = pressure - environment.return_pressure() + + if(pressure_difference > burst_pressure) + burst() + + else if(pressure_difference > fatigue_pressure) + //TODO: leak to turf, doing pfshhhhh + if(prob(5)) + burst() + + else return 1 + + proc/burst() + src.visible_message("\red \bold [src] bursts!"); + playsound(src.loc, 'bang.ogg', 25, 1) + + var/obj/effect/effect/water/W = new(get_turf(src)) + W.reagents = reagents + W.reagents.my_atom = W + if(!W) return + W.reagents.reaction(get_turf(W)) + for(var/atom/atm in get_turf(W)) + if(!W) return + W.reagents.reaction(atm) + sleep(1) + + del(src) + + proc/normalize_dir() + if(dir==3) + dir = 1 + else if(dir==12) + dir = 4 + + Del() + if(node1) + node1.disconnect(src) + if(node2) + node2.disconnect(src) + + ..() + + pipeline_expansion() + return list(node1, node2) + + update_icon() + if(node1&&node2) + var/C = "" + switch(color) + if ("red") C = "-r" + if ("blue") C = "-b" + if ("cyan") C = "-c" + if ("green") C = "-g" + if ("yellow") C = "-y" + if ("purple") C = "-p" + icon_state = "intact[C][invisibility ? "-f" : "" ]" + + //var/node1_direction = get_dir(src, node1) + //var/node2_direction = get_dir(src, node2) + + //dir = node1_direction|node2_direction + + else + if(!node1&&!node2) + del(src) //TODO: silent deleting looks weird + var/have_node1 = node1?1:0 + var/have_node2 = node2?1:0 + icon_state = "exposed[have_node1][have_node2][invisibility ? "-f" : "" ]" + + + initialize() + normalize_dir() + var/node1_dir + var/node2_dir + + for(var/direction in cardinal) + if(direction&initialize_directions) + if (!node1_dir) + node1_dir = direction + else if (!node2_dir) + node2_dir = direction + + for(var/obj/machinery/water/target in get_step(src,node1_dir)) + if(target.initialize_directions & get_dir(target,src)) + node1 = target + break + for(var/obj/machinery/water/target in get_step(src,node2_dir)) + if(target.initialize_directions & get_dir(target,src)) + node2 = target + break + + + var/turf/T = src.loc // hide if turf is not intact + hide(T.intact) + update_icon() + //update_icon() + + disconnect(obj/machinery/water/reference) + if(reference == node1) + if(istype(node1, /obj/machinery/water/pipe)) + del(parent) + node1 = null + + if(reference == node2) + if(istype(node2, /obj/machinery/water/pipe)) + del(parent) + node2 = null + + update_icon() + + return null + + simple/drainage + name="Drainage pipe" + color="yellow" + icon_state = "" + + simple/drainage_waste + name="Drainage waste pipe" + color="green" + icon_state = "" + + simple/supply + name="Water supply pipe" + color="cyan" + icon_state = "" + + New() + ..() + reagents_temporary = new(max_volume) + reagents_temporary.my_atom = src + reagents_temporary.add_reagent("water", max_volume / 3) + + simple/general + name="Water pipe" + color="" + icon_state = "" + + simple/drainage/visible + level = 2 + icon_state = "intact-y" + + simple/drainage/hidden + level = 1 + icon_state = "intact-y-f" + + simple/drainage_waste/visible + level = 2 + icon_state = "intact-g" + + simple/drainage_waste/hidden + level = 1 + icon_state = "intact-g-f" + + simple/supply/visible + level = 2 + icon_state = "intact-c" + + simple/supply/hidden + level = 1 + icon_state = "intact-c-f" + + simple/general/visible + level = 2 + icon_state = "intact" + + simple/general/hidden + level = 1 + icon_state = "intact-f" + + tank + icon = 'pipe_tank.dmi' + icon_state = "intact" + + name = "Pressure Tank" + desc = "A large vessel containing pressurized liquid." + + max_volume = 23000 + max_pressure = 4*ONE_ATMOSPHERE + + dir = SOUTH + initialize_directions = SOUTH + density = 1 + + var/obj/machinery/water/node1 + + New() + initialize_directions = dir + reagents_temporary = new(max_volume) + reagents_temporary.my_atom = src + ..() + + process() + if(!parent) + ..() + else + machines.Remove(src) +/* if(!node1) + parent.mingle_with_turf(loc, 200) + if(!nodealert) + //world << "Missing node from [src] at [src.x],[src.y],[src.z]" + nodealert = 1 + else if (nodealert) + nodealert = 0 +*/ + + water + name = "Pressure Tank (Water)" + icon = 'blue_water_tank.dmi' + + New() + ..() + reagents_temporary.add_reagent("water", max_volume * 0.95) + + sugar_water + name = "Pressure Tank (Sugar Water)" + + New() + ..() + reagents_temporary.add_reagent("water", max_volume * 0.475) + reagents_temporary.add_reagent("sugar", max_volume * 0.475) + + blue_paint + name = "Pressure Tank (Blue Paint)" + icon = 'blue_pipe_tank.dmi' + + New() + ..() + reagents_temporary.add_reagent("paint_blue", max_volume * 0.95) + + Del() + if(node1) + node1.disconnect(src) + + ..() + + pipeline_expansion() + return list(node1) + + update_icon() + if(node1) + icon_state = "intact" + + dir = get_dir(src, node1) + + else + icon_state = "exposed" + + initialize() + + var/connect_direction = dir + + for(var/obj/machinery/water/target in get_step(src,connect_direction)) + if(target.initialize_directions & get_dir(target,src)) + node1 = target + break + + update_icon() + + disconnect(obj/machinery/water/reference) + if(reference == node1) + if(istype(node1, /obj/machinery/water/pipe)) + del(parent) + node1 = null + + update_icon() + + return null + +/* currently pointless because reagents can't just sit on a turf (currently) + drain + icon = 'pipe_vent.dmi' + icon_state = "intact" + + name = "Drain" + desc = "A large drain" + + level = 1 + + max_volume = 200 + + dir = SOUTH + initialize_directions = SOUTH + + var/build_killswitch = 1 + + var/obj/machinery/water/node1 + New() + initialize_directions = dir + ..() + + process() + if(!parent) + if(build_killswitch <= 0) + machines.Remove(src) + else + build_killswitch-- + ..() + return + else + var/pressure = parent.return_pressure() + var/datum/gas_mixture/env_air = loc.return_air() + + if(env_air.return_pressure() > pressure) + parent.mingle_with_turf(loc, 200) +/* + if(!node1) + if(!nodealert) + //world << "Missing node from [src] at [src.x],[src.y],[src.z]" + nodealert = 1 + else if (nodealert) + nodealert = 0 +*/ + Del() + if(node1) + node1.disconnect(src) + + ..() + + pipeline_expansion() + return list(node1) + + update_icon() + if(node1) + icon_state = "intact" + + dir = get_dir(src, node1) + + else + icon_state = "exposed" + + initialize() + var/connect_direction = dir + + for(var/obj/machinery/water/target in get_step(src,connect_direction)) + if(target.initialize_directions & get_dir(target,src)) + node1 = target + break + + update_icon() + + disconnect(obj/machinery/water/reference) + if(reference == node1) + if(istype(node1, /obj/machinery/water/pipe)) + del(parent) + node1 = null + + update_icon() + + return null + + hide(var/i) //to make the little pipe section invisible, the icon changes. + if(node1) + icon_state = "[i == 1 && istype(loc, /turf/simulated) ? "h" : "" ]intact" + dir = get_dir(src, node1) + else + icon_state = "exposed" +*/ + + manifold + icon = 'pipe_manifold.dmi' + icon_state = "manifold-f" + + name = "pipe manifold" + desc = "A liquid manifold composed of regular pipes" + + max_volume = 750 + + dir = SOUTH + initialize_directions = EAST|NORTH|WEST + + var/obj/machinery/water/node1 + var/obj/machinery/water/node2 + var/obj/machinery/water/node3 + + level = 1 + + New() + ..() + switch(dir) + if(NORTH) + initialize_directions = EAST|SOUTH|WEST + if(SOUTH) + initialize_directions = WEST|NORTH|EAST + if(EAST) + initialize_directions = SOUTH|WEST|NORTH + if(WEST) + initialize_directions = NORTH|EAST|SOUTH + + hide(var/i) + if(level == 1 && istype(loc, /turf/simulated)) + invisibility = i ? 101 : 0 + update_icon() + + pipeline_expansion() + return list(node1, node2, node3) + + process() + if(!parent) + ..() + else + machines.Remove(src) +/* + if(!node1) + parent.mingle_with_turf(loc, 70) + if(!nodealert) + //world << "Missing node from [src] at [src.x],[src.y],[src.z]" + nodealert = 1 + else if(!node2) + parent.mingle_with_turf(loc, 70) + if(!nodealert) + //world << "Missing node from [src] at [src.x],[src.y],[src.z]" + nodealert = 1 + else if(!node3) + parent.mingle_with_turf(loc, 70) + if(!nodealert) + //world << "Missing node from [src] at [src.x],[src.y],[src.z]" + nodealert = 1 + else if (nodealert) + nodealert = 0 +*/ + Del() + if(node1) + node1.disconnect(src) + if(node2) + node2.disconnect(src) + if(node3) + node3.disconnect(src) + + ..() + + disconnect(obj/machinery/water/reference) + if(reference == node1) + if(istype(node1, /obj/machinery/water/pipe)) + del(parent) + node1 = null + + if(reference == node2) + if(istype(node2, /obj/machinery/water/pipe)) + del(parent) + node2 = null + + if(reference == node3) + if(istype(node3, /obj/machinery/water/pipe)) + del(parent) + node3 = null + + update_icon() + + ..() + + update_icon() + if(node1&&node2&&node3) + var/C = "" + switch(color) + if ("red") C = "-r" + if ("blue") C = "-b" + if ("cyan") C = "-c" + if ("green") C = "-g" + if ("yellow") C = "-y" + if ("purple") C = "-p" + icon_state = "manifold[C][invisibility ? "-f" : ""]" + + else + var/connected = 0 + var/unconnected = 0 + var/connect_directions = (NORTH|SOUTH|EAST|WEST)&(~dir) + + if(node1) + connected |= get_dir(src, node1) + if(node2) + connected |= get_dir(src, node2) + if(node3) + connected |= get_dir(src, node3) + + unconnected = (~connected)&(connect_directions) + + icon_state = "manifold_[connected]_[unconnected]" + + if(!connected) + del(src) + + return + + initialize() + var/connect_directions = (NORTH|SOUTH|EAST|WEST)&(~dir) + + for(var/direction in cardinal) + if(direction&connect_directions) + for(var/obj/machinery/water/target in get_step(src,direction)) + if(target.initialize_directions & get_dir(target,src)) + node1 = target + connect_directions &= ~direction + break + if (node1) + break + + + for(var/direction in cardinal) + if(direction&connect_directions) + for(var/obj/machinery/water/target in get_step(src,direction)) + if(target.initialize_directions & get_dir(target,src)) + node2 = target + connect_directions &= ~direction + break + if (node2) + break + + + for(var/direction in cardinal) + if(direction&connect_directions) + for(var/obj/machinery/water/target in get_step(src,direction)) + if(target.initialize_directions & get_dir(target,src)) + node3 = target + connect_directions &= ~direction + break + if (node3) + break + + var/turf/T = src.loc // hide if turf is not intact + hide(T.intact) + //update_icon() + update_icon() + + manifold/drainage + name="Drainage pipe" + color="yellow" + icon_state = "" + + manifold/drainage_waste + name="Drainage waste pipe" + color="green" + icon_state = "" + + manifold/supply + name="Water supply pipe" + color="cyan" + icon_state = "" + + New() + ..() + reagents_temporary = new(max_volume) + reagents_temporary.my_atom = src + reagents_temporary.add_reagent("water", max_volume / 3) + + manifold/general + name="Water pipe" + color="gray" + icon_state = "" + + manifold/drainage/visible + level = 2 + icon_state = "manifold-y" + + manifold/drainage/hidden + level = 1 + icon_state = "manifold-y-f" + + manifold/drainage_waste/visible + level = 2 + icon_state = "manifold-g" + + manifold/drainage_waste/hidden + level = 1 + icon_state = "manifold-g-f" + + manifold/supply/visible + level = 2 + icon_state = "manifold-c" + + manifold/supply/hidden + level = 1 + icon_state = "manifold-c-f" + + manifold/general/visible + level = 2 + icon_state = "manifold" + + manifold/general/hidden + level = 1 + icon_state = "manifold-f" + + manifold4w + icon = 'pipe_manifold.dmi' + icon_state = "manifold4w-f" + + name = "4-way pipe manifold" + desc = "A liquid manifold composed of regular pipes" + + max_volume = 1000 + + dir = SOUTH + initialize_directions = EAST|NORTH|WEST|SOUTH + + var/obj/machinery/water/node1 + var/obj/machinery/water/node2 + var/obj/machinery/water/node3 + var/obj/machinery/water/node4 + + level = 1 + + hide(var/i) + if(level == 1 && istype(loc, /turf/simulated)) + invisibility = i ? 101 : 0 + update_icon() + + pipeline_expansion() + return list(node1, node2, node3, node4) + + process() + if(!parent) + ..() + else + machines.Remove(src) +/* + if(!node1) + parent.mingle_with_turf(loc, 70) + if(!nodealert) + //world << "Missing node from [src] at [src.x],[src.y],[src.z]" + nodealert = 1 + else if(!node2) + parent.mingle_with_turf(loc, 70) + if(!nodealert) + //world << "Missing node from [src] at [src.x],[src.y],[src.z]" + nodealert = 1 + else if(!node3) + parent.mingle_with_turf(loc, 70) + if(!nodealert) + //world << "Missing node from [src] at [src.x],[src.y],[src.z]" + nodealert = 1 + else if (nodealert) + nodealert = 0 +*/ + Del() + if(node1) + node1.disconnect(src) + if(node2) + node2.disconnect(src) + if(node3) + node3.disconnect(src) + if(node4) + node4.disconnect(src) + + ..() + + disconnect(obj/machinery/water/reference) + if(reference == node1) + if(istype(node1, /obj/machinery/water/pipe)) + del(parent) + node1 = null + + if(reference == node2) + if(istype(node2, /obj/machinery/water/pipe)) + del(parent) + node2 = null + + if(reference == node3) + if(istype(node3, /obj/machinery/water/pipe)) + del(parent) + node3 = null + + if(reference == node4) + if(istype(node4, /obj/machinery/water/pipe)) + del(parent) + node3 = null + + update_icon() + + ..() + + update_icon() + overlays = new() + if(node1&&node2&&node3&&node4) + var/C = "" + switch(color) + if ("red") C = "-r" + if ("blue") C = "-b" + if ("cyan") C = "-c" + if ("green") C = "-g" + if ("yellow") C = "-y" + if ("purple") C = "-p" + icon_state = "manifold4w[C][invisibility ? "-f" : ""]" + + else + icon_state = "manifold4w_ex" + var/icon/con = new/icon('pipe_manifold.dmi',"manifold4w_con") + + if(node1) + overlays += new/image(con,dir=1) + if(node2) + overlays += new/image(con,dir=2) + if(node3) + overlays += new/image(con,dir=4) + if(node4) + overlays += new/image(con,dir=8) + + if(!node1 && !node2 && !node3 && !node4) + del(src) + return + + initialize() + for(var/obj/machinery/water/target in get_step(src,1)) + if(target.initialize_directions & get_dir(target,src)) + node1 = target + break + + for(var/obj/machinery/water/target in get_step(src,2)) + if(target.initialize_directions & get_dir(target,src)) + node2 = target + break + + for(var/obj/machinery/water/target in get_step(src,4)) + if(target.initialize_directions & get_dir(target,src)) + node3 = target + break + + for(var/obj/machinery/water/target in get_step(src,8)) + if(target.initialize_directions & get_dir(target,src)) + node4 = target + break + + var/turf/T = src.loc // hide if turf is not intact + hide(T.intact) + //update_icon() + update_icon() + + manifold4w/drainage + name="Drainage pipe" + color="yellow" + icon_state = "" + + manifold4w/drainage_waste + name="Drainage waste pipe" + color="green" + icon_state = "" + + manifold4w/supply + name="Water supply pipe" + color="cyan" + icon_state = "" + + New() + ..() + reagents_temporary = new(max_volume) + reagents_temporary.my_atom = src + reagents_temporary.add_reagent("water", max_volume / 3) + + manifold4w/general + name="Water pipe" + color="gray" + icon_state = "" + + manifold4w/drainage/visible + level = 2 + icon_state = "manifold4w-y" + + manifold4w/drainage/hidden + level = 1 + icon_state = "manifold4w-y-f" + + manifold4w/drainage_waste/visible + level = 2 + icon_state = "manifold4w-g" + + manifold4w/drainage_waste/hidden + level = 1 + icon_state = "manifold4w-g-f" + + manifold4w/supply/visible + level = 2 + icon_state = "manifold4w-c" + + manifold4w/supply/hidden + level = 1 + icon_state = "manifold4w-c-f" + + manifold4w/general/visible + level = 2 + icon_state = "manifold4w" + + manifold4w/general/hidden + level = 1 + icon_state = "manifold4w-f" + +obj/machinery/water/pipe/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) + if (istype(src, /obj/machinery/water/pipe/tank)) + return ..() + if (istype(W, /obj/item/device/pda)) // allow reagent scanner to work + return + //if (istype(src, /obj/machinery/water/pipe/drain)) + // return ..() + if (!istype(W, /obj/item/weapon/wrench)) + return ..() + var/turf/T = src.loc + if (level==1 && isturf(T) && T.intact) + user << "\red You must remove the plating first." + return 1 + var/datum/gas_mixture/env_air = loc.return_air() + if ((parent.return_pressure()-env_air.return_pressure()) > 2*ONE_ATMOSPHERE) + user << "\red You cannot unwrench this [src], it too exerted due to internal pressure." + add_fingerprint(user) + return 1 + playsound(src.loc, 'Ratchet.ogg', 50, 1) + user << "\blue You begin to unfasten \the [src]..." + if (do_after(user, 40)) + user.visible_message( \ + "[user] unfastens \the [src].", \ + "\blue You have unfastened \the [src].", \ + "You hear ratchet.") + new /obj/item/water_pipe(loc, make_from=src) + for (var/obj/machinery/water_meter/meter in T) + if (meter.target == src) + new /obj/item/water_pipe_meter(T) + del(meter) + del(src) diff --git a/icons/obj/water/blue_water_tank.dmi b/icons/obj/water/blue_water_tank.dmi new file mode 100644 index 0000000000000000000000000000000000000000..2346afe5d8a6d604e51e10908f97bc7ea8733ef6 GIT binary patch literal 2357 zcmeGdYgCh0@&eI7&=RPKA*%_GES41{qBMtBLIeXM;h9zug8_oXMu3yGR*xt7xPk&$_3FEL)ZTBCZV*+l6Pmm8n^+!x~f(4jN zjr9);LTK+F5z5L%=mVz88b>J21)+)U2)Q3a$ZA(8zN{xo))$gC9xnT5)WO-Tam%hK3$!=!J&6aH$s>??Q7Q zH2(~37!JVY`=A{jmy{K!QT?i&JQJgdZ6I|C2OcI2m9SPEZ2@Ulch-)6$}T`3Y_+;7_b$AJD{f$XS$T;E?4BpscK$BMAl z7ZIKkFO>BuYUdp1%H#jb>zy+_^ z|6;z**v9GXRD&!bAGg`F4KJG>;{PCfJfHLM#_1#_i}TpVXFfaNO5=9Zldo#}oz|Z` zE=ZC8)h<<0mbi8zcXMYF>F=vI@t#g29HW%BvR|+#Okt|%dKiBi9mL~Ye>CjQV#Ug` zZWtNzZjVtq9Nso*+v&39qxpX_Ur|A8oq7Fyv{2=}=VR?XcQ-#vnz~}4+#=z8aLsB5 zYAEq}=-AUFC6&%!d7-^gT~*1?Tka{z7m$?lXVojsuQb+w(jV_1@`e{PyUx(&+khu! zwCiJ5OCLoNBko4;FSC7Fx|sJ3Iz4_wQc;=jag*-oLH72xT%c;gloO}+EHe0HN8HY>&O~Cd~zY z`6~J4&4x=>SPbPH8K5TDJae1;@_x^9YU+ezeA?wF&D>(f;jM$tcM59_i^6jzFPwRG z%z0JH%c0XJZWm%bw*Tpp>Ae%5J8x^rblS)N#~GY|sZFKhgL7-VYSK@I72Bbqz{9RM zmjmL|xtB|pa0={D2E$^+CcD>RU>R{`kHvcijp4CkPFesj$~bPyHM{s##ZBM&XzEvYG&nbY zU&)69O4b^tYu)QleY!S{faaJ!9K~|K<@G0dYbm+G;Q^WcQwCm|i#o09sXTO^qNL1b zxO72q2if8o-kU3;o`{b9EsK7RR$l^2u2?2*&X_yCjcAyTK4vw FUjS$p?->99 literal 0 HcmV?d00001 diff --git a/icons/obj/water/water_fixtures.dmi b/icons/obj/water/water_fixtures.dmi new file mode 100644 index 0000000000000000000000000000000000000000..4bd8e495d42ec1d6ce627566bf5425098f1d205b GIT binary patch literal 1443 zcmV;U1zh@xP)C0002AP)t-sz`(#6 z85t1`4IdvLSy@?TW@e?OrOeFCnVFe?e}Bv~0RR90|1$s^8yj9;UW0>!03ICxT2}zH zt^kC803aV46aZdH0GM_FP(A$TL8GS0EU5vXaKm6000^p0AgPNwy*%cw}ys> z03sm(z`3`#w;&k+06aPXsG>nZK|C)2sDc0#5C8xa6NF{}mzS5ci~zuu0Je<)0GN|v zOaNL&002-;A{qc57XTa<0KSv}N<_`L00001bW%=J06^y0W&i*HiF#C6bVOxyV{&P5 zbZKvH004NLQ&wnP1C^IiRC$-2xiHkEOv#1!PlZ!L0C^0t`#5TmD)DXK;pcN^pIf<1Z zT{=km(lRSbN{do)nXjzi>gNJ>I{+D=H<}=)VPgOQ1PDn)K~#90?N|+0sz4BBSrr5^ zGe8A_iW1X4Uf^7zXLp#nn)V40oq>L4$X-Dj&6d$J z+gJ+d3P=Jh!|rr!_(l_uE5H(f9Rkg9XxBM#90zHBP|yVd2YB@TWlxPUtYMbEcS-3 z8~h9?j{)Q?wsO7$uoIBMlnbELuxV4*o76TaRW5+t0?VKcLK@Vt5N-j>1C~Vz)KTn- zlE1Mt;r9gg>lVAmv0{HBjC>xC@2 zDjRJc(ljL!0mXbVQ;pZo zf^#OIJLyUV6!OJPH6B9H=K@N0ppY+Ss_~l3zzS*d@xNsZ626$J#%t4Ycrn#x=_H+f z$j>k1XfEN4nQFY|!2-R~71p=|a7fY_naK{>Chx8=C47b1JX_+*TOpf!f}gI9x!#(< zOuJo~fKC2rJU$X|FGy+vpIv4*98q-RuOW*v!C$yYt{4F04xhIsfEY^-**4!BCA}m`MrR3}0S-6=-V4Nh;k_<4&Vb;j zZgMt)F}-Be+c*Q_o)@F^6z__86kpCIyvP$ZqHG65rmPX3e|sYnAl?-<;>Ev{3J`D0 z8u9A?l?o8`LXGhJxFiJflI?#Ga9<)oUN!HG#iM)&%KC<#0U?M=LLlv&cE)RofU@3k xXDpuNiKwi5-WgZJfqy08ub&@+YYG1a`~%8@aIcSP4Qc=Y002ovPDHLkV1n7Re5(Ke literal 0 HcmV?d00001 diff --git a/icons/obj/water/water_glass_connector.dmi b/icons/obj/water/water_glass_connector.dmi new file mode 100644 index 0000000000000000000000000000000000000000..cdb5d50131c242ae07c71c5d5dcd3df528423ef4 GIT binary patch literal 2781 zcma)8XH=8f7X1(rLI^Eq$WR0lK&b&7 z0Z~d&KtgB{=>#EkkWP#wBq1-(_jsR>>g=;BjWmPSXd-1Gz0+d<|dN<6l~QL`+ZtJI_`qu zY1c;oR0-lI`~|K01>xc}FI@7*rvilohVpdEW~N@fB)MoAhjg?y;*%O=K5{*4xTUFg ztZutqSEKuqw!jB0<#?NmV5J;k|09nIEn)a&%~Ds3YQnS5TX!0_s=bnQ--|o9Jm7?M z>IpwBL|FJydOp}hBxM9L57-3IWEs9N|8Kfw4*=lcu9dl|^WEIFy#6Z_UNVnS{SKv< zRKJm%vur<9xW+a+oO)6zb>!ua?e7I$Y8OnIATf!8D@+v@sCtdra zcN5h=JI>xCi}G>j%-c@vjS7E_;Lp~%;c4l9lSCqux4_nRd(QeQ$eB(4~CB(c0a zwlgNDShdw2aB_0$SXxq<#NR(%i-+Q+%lEq5-OLZ!ySNm!v`h^c3MV-g3w=5Qe8ls~px6s`k>W+>I3=OT;G&$Fk%-`b6 z1HQjIn4?&(%gQ;p!acV|Vao7y&%ZwXglc3mLMu+ka?7S@0;6Q|A!amsiMTdAEa6z@ zJP`FGKa_bSBEa9@|2K8;b5)7>DgzKdQa$r*`A|?>|2&;ODpIaGb)^--F(&ag^bv?Ks*&Ua@zk=;E#<0CmMTgKjT z_ZZtRuk9^0Bt;q=_8zGmIWua;$B}L3^&ZjU%Jwz4?GV&KY?KjKh|OTl&d$p0&CDoB zNJ>^l(dZkK&21D4cF5ny2T)X0l#-Hqg*?E|AHFtzUg+>)-GZMGLcf!l{J-$zZdx>q{ei zYjvz9wj0`X*C9e7bWc5PZ0uL)V)&ZauA-z#E4@Q1GL7hGm&-_0xX5Gii=m+*Gkife z&Ys5rWZ~d46!;w-SChEJdj_yCbGBtqUf%2L>nod*l#uB2QtsK)P*bb@s}s^9y1ZPb z&$Ho+bx6)F^H9D3d@jO{FEHQ4Q1^DZQ-mNAj=TBB>ZRkQk^!#V)QA4~7YLFfWp2)| zmu07v4r@pn^S^$bcR6x=ge6LypJC?5j*-b1-g(7Vt(>+xqM6s~r(abOdzO?WjNt4Y<7z+5zUeCoqFIBfD0vjMZe^tUw5O+M7=Hbv z8Px3i&Q5q(82a+dhihxMa%r9NmiW>5GZ@lo2IA^n&(;_n6Jf|PDvXi(jaM5g4`%XY z+jRv%j#I4|tNeaoRSX;5qdZ>&N@2V}eCO51JR>1Z7SgxJL^=_U|5XfmgM1&XbF&G_ z9*;?{8N=}aT+YDE>|)UZp-UuihiYSOJ*bAs$}&NrP^&D&AmSGYRtSklfT1-IE*~u+ z#V90L6q!PR3PP|?FrhFQ@s;HC=g(<800x5k-cu4Mgi!q!tboKjmhETeHNA8^XmZZl zQ~PAfzJx*yi6QhTGeE{~o608&AwW{Pp0<{nt`UkB9!o!EP9DiZ1YPEK<8fVxH%-(ayd|JtVlAg`K@Z>X@b{R>P#ES&q2ViA?v=wR z$ud)AS2{B@Qylwyj7|_l{ye0|eA-Dd+j`G&gZ78GPot4bM*)%h)KFiW)t%9+i|GWC za@z4^$Uibk<2^Z zjS=i!aSZsrS)E5B4nyK?S~^PC??TtF4|hLl*{_iGS2LgTht`UY^8RU5kF&*ORMz zH@)XYYXo{H-fl)@S0~m-j}MD1^<pJt|9iAw@tk6^9lSeK1ppYb!yYExEW+IBkk>JV-Bkk{=Ym%a4@gl&pcu5Tz?pSYMUsngt(Wp;5 zmP=|@+9%qZs3+2iJ}=#IoJ z{gA9tOR$cc#p%>7-tk37Zjo2N);3?bYSKWCfVfWu8xf+Eecr}twYyc#EC_Tl6f(i0dnExACD9rK)Kjxa*x1A=CWvz6tRNPmN naa0;|+_LWzM+2VgzQ}o(KcPu0aVq8hR{^Vww&qo4J`ev5yHcv7 literal 0 HcmV?d00001 diff --git a/icons/obj/water/water_pipe_item.dmi b/icons/obj/water/water_pipe_item.dmi new file mode 100644 index 0000000000000000000000000000000000000000..6e477f7a71c29aeb33e968b21f62310855e90335 GIT binary patch literal 20113 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 Y0Z-lytNg-PmH+?%07*qoM6N<$f|KaMkN^Mx literal 0 HcmV?d00001