/tg/ Plumbing on yogs

This commit is contained in:
Codeatmos
2019-07-12 20:32:06 -05:00
parent be63e05196
commit d2c2bca3cd
10 changed files with 670 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
#define FIRST_DUCT_LAYER 1
#define SECOND_DUCT_LAYER 2
#define THIRD_DUCT_LAYER 4
#define FOURTH_DUCT_LAYER 8
#define FIFTH_DUCT_LAYER 16
#define DUCT_LAYER_DEFAULT THIRD_DUCT_LAYER

View File

@@ -0,0 +1,5 @@
PROCESSING_SUBSYSTEM_DEF(fluids)
name = "Fluids"
wait = 20
stat_tag = "FD" //its actually Fluid Ducts
flags = SS_NO_INIT | SS_TICKER

View File

@@ -0,0 +1,173 @@
/datum/component/plumbing
var/list/datum/ductnet/ducts = list() //Index with "1" = /datum/ductnet/theductpointingnorth etc. "1" being the num2text from NORTH define
var/datum/reagents/reagents
var/use_overlays = TRUE //TRUE if we wanna add proper pipe outless under our parent object
var/list/image/ducterlays //We can't just cut all of the parents' overlays, so we'll track them here
var/supply_connects //directions in wich we act as a supplier
var/demand_connects //direction in wich we act as a demander
var/active = FALSE //FALSE to pretty much just not exist in the plumbing world so we can be moved, TRUE to go plumbo mode
var/turn_connects = TRUE
/datum/component/plumbing/Initialize(start=TRUE, _turn_connects=TRUE) //turn_connects for wheter or not we spin with the object to change our pipes
if(parent && !ismovableatom(parent))
return COMPONENT_INCOMPATIBLE
var/atom/movable/AM = parent
if(!AM.reagents)
return COMPONENT_INCOMPATIBLE
reagents = AM.reagents
turn_connects = _turn_connects
RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED,COMSIG_PARENT_PREQDELETED), .proc/disable)
if(start)
start()
if(use_overlays)
create_overlays()
/datum/component/plumbing/process()
if(!demand_connects || !reagents)
STOP_PROCESSING(SSfluids, src)
return
if(reagents.total_volume < reagents.maximum_volume)
for(var/D in GLOB.cardinals)
if(D & demand_connects)
send_request(D)
/datum/component/plumbing/proc/can_add(datum/ductnet/D, dir)
if(!active)
return
if(!dir || !D)
return FALSE
if(num2text(dir) in ducts)
return FALSE
return TRUE
/datum/component/plumbing/proc/send_request(dir) //this should usually be overwritten when dealing with custom pipes
process_request(amount = 10, reagent = null, dir = dir)
/datum/component/plumbing/proc/process_request(amount, reagent, dir)
var/list/valid_suppliers = list()
var/datum/ductnet/net
if(!ducts.Find(num2text(dir)))
return
net = ducts[num2text(dir)]
for(var/A in net.suppliers)
var/datum/component/plumbing/supplier = A
if(supplier.can_give(amount, reagent))
valid_suppliers += supplier
for(var/A in valid_suppliers)
var/datum/component/plumbing/give = A
give.transfer_to(src, amount / valid_suppliers.len, reagent)
/datum/component/plumbing/proc/can_give(amount, reagent)
if(!reagents || amount <= 0)
return
if(reagent) //only asked for one type of reagent
if(reagent in reagents.reagent_list)
return TRUE
else if(reagents.total_volume > 0) //take whatever
return TRUE
/datum/component/plumbing/proc/transfer_to(datum/component/plumbing/target, amount, reagent)
if(!reagents || !target || !target.reagents)
return FALSE
if(reagent)
reagents.trans_id_to(target.reagents, reagent, amount)
else
reagents.trans_to(target.reagents, amount)
/datum/component/plumbing/proc/create_overlays()
var/atom/movable/AM = parent
for(var/image/I in ducterlays)
AM.overlays.Remove(I)
qdel(I)
ducterlays = list()
for(var/D in GLOB.cardinals)
var/color
var/direction
if(D & demand_connects)
color = "red" //red because red is mean and it takes
else if(D & supply_connects)
color = "blue" //blue is nice and gives
else
continue
var/image/I
if(turn_connects)
switch(D)
if(NORTH)
direction = "north"
if(SOUTH)
direction = "south"
if(EAST)
direction = "east"
if(WEST)
direction = "west"
I = image('icons/obj/plumbing/plumbers.dmi', "[direction]-[color]", layer = AM.layer - 1)
else
I = image('icons/obj/plumbing/plumbers.dmi', color, layer = AM.layer - 1) //color is not color as in the var, it's just the name
I.dir = D
AM.add_overlay(I)
ducterlays += I
/datum/component/plumbing/proc/disable() //we stop acting like a plumbing thing and disconnect if we are, so we can safely be moved and stuff
if(!active)
return
STOP_PROCESSING(SSfluids, src)
for(var/A in ducts)
var/datum/ductnet/D = ducts[A]
D.remove_plumber(src)
active = FALSE
/datum/component/plumbing/proc/start() //settle wherever we are, and start behaving like a piece of plumbing
if(active)
return
update_dir()
active = TRUE
if(demand_connects)
START_PROCESSING(SSfluids, src)
for(var/D in GLOB.cardinals)
if(D & (demand_connects + supply_connects))
for(var/obj/machinery/duct/duct in get_step(parent, D))
var/turned_dir = turn(D, 180)
if(turned_dir & duct.connects)
duct.attempt_connect()
//TODO: Let plumbers directly plumb into one another without ducts if placed adjacent to each other
/datum/component/plumbing/proc/update_dir() //note that this is only called when we settle down. If someone wants it to fucking spin while connected to something go actually knock yourself out
if(!turn_connects)
return
var/atom/movable/AM = parent
var/new_demand_connects
var/new_supply_connects
var/new_dir = AM.dir
var/angle = 180 - dir2angle(new_dir)
if(new_dir == SOUTH)
demand_connects = initial(demand_connects)
supply_connects = initial(supply_connects)
else
for(var/D in GLOB.cardinals)
if(D & initial(demand_connects))
new_demand_connects += turn(D, angle)
if(D & initial(supply_connects))
new_supply_connects += turn(D, angle)
demand_connects = new_demand_connects
supply_connects = new_supply_connects
/datum/component/plumbing/simple_demand
demand_connects = NORTH
/datum/component/plumbing/simple_supply
supply_connects = NORTH
/datum/component/plumbing/tank
demand_connects = WEST
supply_connects = EAST

60
code/datums/ductnet.dm Normal file
View File

@@ -0,0 +1,60 @@
/datum/ductnet
var/list/suppliers = list()
var/list/demanders = list()
var/list/obj/machinery/duct/ducts = list()
var/capacity
/datum/ductnet/proc/add_duct(obj/machinery/duct/D)
if(!D || D in ducts)
return
ducts += D
D.duct = src
/datum/ductnet/proc/remove_duct(obj/machinery/duct/ducting)
destroy_network(FALSE)
for(var/A in ducting.neighbours)
var/obj/machinery/duct/D = A
D.attempt_connect() //we destroyed the network, so now we tell the disconnected ducts neighbours they can start making a new ductnet
qdel(src)
/datum/ductnet/proc/add_plumber(datum/component/plumbing/P, dir)
if(!P.can_add(src, dir))
return
P.ducts[num2text(dir)] = src
if(dir & P.supply_connects)
suppliers += P
else if(dir & P.demand_connects)
demanders += P
/datum/ductnet/proc/remove_plumber(datum/component/plumbing/P)
suppliers.Remove(P) //we're probably only in one of these, but Remove() is inherently sane so this is fine
demanders.Remove(P)
for(var/dir in P.ducts)
if(P.ducts[dir] == src)
P.ducts -= dir
/datum/ductnet/proc/assimilate(datum/ductnet/D)
ducts.Add(D.ducts)
suppliers.Add(D.suppliers)
demanders.Add(D.demanders)
for(var/A in D.suppliers + D.demanders)
var/datum/component/plumbing/P = A
for(var/s in P.ducts)
if(P.ducts[s] != D)
continue
P.ducts[s] = src //all your ducts are belong to us
for(var/A in D.ducts)
var/obj/machinery/duct/M = A
M.duct = src //forget your old master
qdel(D)
/datum/ductnet/proc/destroy_network(delete=TRUE)
for(var/A in suppliers + demanders)
remove_plumber(A)
for(var/A in ducts)
var/obj/machinery/duct/D = A
D.duct = null
if(delete) //I don't want code to run with qdeleted objects because that can never be good, so keep this in-case the ductnet has some business left to attend to before commiting suicide
qdel(src)

View File

@@ -0,0 +1,72 @@
//If you look at the "geyser_soup" overlay icon_state, you'll see that the first frame has 25 ticks.
//That's because the first 18~ ticks are completely skipped for some ungodly weird fucking byond reason
/obj/structure/geyser
name = "geyser"
icon = 'icons/obj/lavaland/terrain.dmi'
icon_state = "geyser"
anchored = TRUE
var/erupting_state = null //set to null to get it greyscaled from "[icon_state]_soup". Not very usable with the whole random thing, but more types can be added if you change the spawn prob
var/activated = FALSE //whether we are active and generating chems
var/reagent_id = /datum/reagent/oil
var/potency = 2 //how much reagents we add every process (2 seconds)
var/max_volume = 500
var/start_volume = 50
/obj/structure/geyser/proc/start_chemming()
activated = TRUE
create_reagents(max_volume, DRAINABLE)
reagents.add_reagent(reagent_id, start_volume)
START_PROCESSING(SSfluids, src) //It's main function is to be plumbed, so use SSfluids
if(erupting_state)
icon_state = erupting_state
else
var/mutable_appearance/I = mutable_appearance('icons/obj/lavaland/terrain.dmi', "[icon_state]_soup")
I.color = mix_color_from_reagents(reagents.reagent_list)
add_overlay(I)
/obj/structure/geyser/process()
if(activated && reagents.total_volume <= reagents.maximum_volume) //this is also evaluated in add_reagent, but from my understanding proc calls are expensive and should be avoided in continous
reagents.add_reagent(reagent_id, potency) //processes
/obj/structure/geyser/plunger_act(obj/item/plunger/P, mob/living/user, _reinforced)
if(!_reinforced)
to_chat(user, "<span class='warning'>The [P.name] isn't strong enough!</span>")
return
if(activated)
to_chat(user, "<span class'warning'>The [name] is already active!")
return
to_chat(user, "<span class='notice'>You start vigorously plunging [src]!")
if(do_after(user, 50*P.plunge_mod, target = src) && !activated)
start_chemming()
/obj/structure/geyser/random
erupting_state = null
var/list/options = list(/datum/reagent/oil = 2, /datum/reagent/clf3 = 1) //fucking add more
/obj/structure/geyser/random/Initialize()
. = ..()
reagent_id = pickweight(options)
/obj/item/plunger
name = "plunger"
desc = "It's a plunger for plunging."
icon = 'icons/obj/watercloset.dmi'
icon_state = "plunger"
var/plunge_mod = 1 //time*plunge_mod = total time we take to plunge an object
var/reinforced = FALSE //whether we do heavy duty stuff like geysers
/obj/item/plunger/attack_obj(obj/O, mob/living/user)
if(!O.plunger_act(src, user, reinforced))
return ..()
/obj/item/plunger/reinforced
name = "reinforced plunger"
desc = " It's an M. 7 Reinforced Plunger<65> for heavy duty plunging."
icon_state = "reinforced_plunger"
reinforced = TRUE
plunge_mod = 0.8

View File

@@ -0,0 +1,279 @@
/*
All the important duct code:
/code/datums/components/plumbing/plumbing.dm
/code/datums/ductnet.dm
*/
/obj/machinery/duct
name = "fluid duct"
icon = 'icons/obj/plumbing/fluid_ducts.dmi'
icon_state = "nduct"
var/connects
var/dumb = FALSE //set to TRUE to disable smart cable behaviour
var/lock_connects = FALSE //wheter we allow our connects to be changed after initialization or not
var/datum/ductnet/duct
var/capacity = 10
var/duct_color = null
var/ignore_colors = FALSE //TRUE to ignore colors, so yeah we also connect with other colors without issue
var/duct_layer = DUCT_LAYER_DEFAULT //1,2,4,8,16
var/lock_layers = FALSE //whether we allow our layers to be altered
var/color_to_color_support = TRUE //TRUE to let colors connect when forced with a wrench, false to just not do that at all
var/active = TRUE //wheter to even bother with plumbing code or not
var/list/neighbours = list() //track ducts we're connected to. Mainly for ducts we connect to that we normally wouldn't, like different layers and colors, for when we regenerate the ducts
/obj/machinery/duct/Initialize(mapload, no_anchor, color_of_duct, layer_of_duct = DUCT_LAYER_DEFAULT, force_connects)
. = ..()
if(no_anchor)
active = FALSE
anchored = FALSE
else if(!can_anchor())
CRASH("Overlapping ducts detected")
qdel(src)
if(force_connects)
connects = force_connects //skip change_connects() because we're still initializing and we need to set our connects at one point
if(!lock_layers)
duct_layer = layer_of_duct
if(!ignore_colors)
duct_color = color_of_duct
if(duct_color)
add_atom_colour(duct_color, FIXED_COLOUR_PRIORITY)
handle_layer()
for(var/obj/machinery/duct/D in loc)
if(D == src)
continue
if(D.duct_layer & duct_layer)
qdel(src) //replace with dropping or something
if(active)
attempt_connect()
/obj/machinery/duct/proc/attempt_connect()
reset_connects(0) //All connects are gathered here again eitherway, we might aswell reset it so they properly update when reconnecting
for(var/D in GLOB.cardinals)
if(dumb && !(D & connects))
continue
for(var/atom/movable/AM in get_step(src, D))
if(connect_network(AM, D))
add_connects(D)
update_icon()
/obj/machinery/duct/proc/connect_network(atom/movable/AM, direction, ignore_color)
if(istype(AM, /obj/machinery/duct))
return connect_duct(AM, direction, ignore_color)
var/plumber = AM.GetComponent(/datum/component/plumbing)
if(!plumber)
return
connect_plumber(plumber, direction)
/obj/machinery/duct/proc/connect_duct(obj/machinery/duct/D, direction, ignore_color)
var/opposite_dir = turn(direction, 180)
if(!active || !D.active)
return
if(!dumb && D.dumb && !(opposite_dir & D.connects))
return
if(dumb && D.dumb && !(connects & D.connects)) //we eliminated a few more scenario in attempt connect
return
if((duct == D.duct) && duct)//check if we're not just comparing two null values
add_neighbour(D)
D.add_connects(opposite_dir)
D.update_icon()
return TRUE //tell the current pipe to also update it's sprite
if(!(D in neighbours)) //we cool
if((duct_color != D.duct_color) && !(ignore_colors || D.ignore_colors))
return
if(!(duct_layer & D.duct_layer))
return
if(D.duct)
if(duct)
duct.assimilate(D.duct)
else
D.duct.add_duct(src)
else
if(duct)
duct.add_duct(D)
else
create_duct()
duct.add_duct(D)
add_neighbour(D)
D.attempt_connect()//tell our buddy its time to pass on the torch of connecting to pipes. This shouldn't ever infinitely loop since it only works on pipes that havent been inductrinated
return TRUE
/obj/machinery/duct/proc/connect_plumber(datum/component/plumbing/P, direction)
var/opposite_dir = turn(direction, 180)
if(duct_layer != DUCT_LAYER_DEFAULT) //plumbing devices don't support multilayering. 3 is the default layer so we only use that. We can change this later
return
var/comp_directions = P.supply_connects + P.demand_connects //they should never, ever have supply and demand connects overlap or catastrophic failure
if(opposite_dir & comp_directions)
if(duct)
duct.add_plumber(P, opposite_dir)
else
create_duct()
duct.add_plumber(P, opposite_dir)
return TRUE
/obj/machinery/duct/proc/disconnect_duct()
anchored = FALSE
active = FALSE
if(duct)
duct.remove_duct(src)
lose_neighbours()
reset_connects(0)
update_icon()
/obj/machinery/duct/proc/create_duct()
duct = new()
duct.add_duct(src)
/obj/machinery/duct/proc/add_neighbour(obj/machinery/duct/D)
if(!(D in neighbours))
neighbours += D
if(!(src in D.neighbours))
D.neighbours += src
/obj/machinery/duct/proc/lose_neighbours()
for(var/A in neighbours)
var/obj/machinery/duct/D = A
D.neighbours.Remove(src)
neighbours = list()
/obj/machinery/duct/proc/add_connects(new_connects) //make this a define to cut proc calls?
if(!lock_connects)
connects |= new_connects
/obj/machinery/duct/proc/reset_connects()
if(!lock_connects)
connects = 0
/obj/machinery/duct/proc/get_adjacent_ducts()
var/list/adjacents = list()
for(var/A in GLOB.cardinals)
if(A & connects)
for(var/obj/machinery/duct/D in get_step(src, A))
if((turn(A, 180) & D.connects) && D.active)
adjacents += D
return adjacents
/obj/machinery/duct/update_icon() //setting connects isnt a parameter because sometimes we make more than one change, overwrite it completely or just add it to the bitfield
var/temp_icon = initial(icon_state)
for(var/D in GLOB.cardinals)
if(D & connects)
if(D == NORTH)
temp_icon += "_n"
if(D == SOUTH)
temp_icon += "_s"
if(D == EAST)
temp_icon += "_e"
if(D == WEST)
temp_icon += "_w"
icon_state = temp_icon
/obj/machinery/duct/proc/handle_layer()
var/offset
switch(duct_layer)//it's a bitfield, but it's fine because it only works when there's one layer, and multiple layers should be handled differently
if(FIRST_DUCT_LAYER)
offset = -10
if(SECOND_DUCT_LAYER)
offset = -5
if(THIRD_DUCT_LAYER)
offset = 0
if(FOURTH_DUCT_LAYER)
offset = 5
if(FIFTH_DUCT_LAYER)
offset = 10
pixel_x = offset
pixel_y = offset
/obj/machinery/duct/wrench_act(mob/living/user, obj/item/I) //I can also be the RPD
add_fingerprint(user)
I.play_tool_sound(src)
if(anchored)
user.visible_message( \
"[user] unfastens \the [src].", \
"<span class='notice'>You unfasten \the [src].</span>", \
"<span class='italics'>You hear ratcheting.</span>")
disconnect_duct()
else if(can_anchor())
anchored = TRUE
active = TRUE
user.visible_message( \
"[user] fastens \the [src].", \
"<span class='notice'>You fasten \the [src].</span>", \
"<span class='italics'>You hear ratcheting.</span>")
attempt_connect()
return TRUE
/obj/machinery/duct/proc/can_anchor(turf/T)
if(!T)
T = get_turf(src)
for(var/obj/machinery/duct/D in T)
if(!anchored)
continue
for(var/A in GLOB.cardinals)
if(A & connects && A & D.connects)
return FALSE
return TRUE
/obj/machinery/duct/doMove(destination)
. = ..()
disconnect_duct()
anchored = FALSE
/obj/machinery/duct/Destroy()
disconnect_duct()
return ..()
/obj/machinery/duct/MouseDrop_T(atom/A, mob/living/user)
if(!istype(A, /obj/machinery/duct))
return
var/obj/machinery/duct/D = A
var/obj/item/I = user.get_active_held_item()
if(I?.tool_behaviour != TOOL_WRENCH)
to_chat(user, "<span class='warning'>You need to be holding a wrench in your active hand to do that!</span>")
return
if(get_dist(src, D) != 1)
return
var/direction = get_dir(src, D)
if(!(direction in GLOB.cardinals))
return
connect_network(D, direction, TRUE)
add_connects(direction)
update_icon()
/obj/machinery/duct/multilayered
name = "duct layer-manifold"
icon = 'icons/obj/2x2.dmi'
icon_state = "multiduct"
color_to_color_support = FALSE
duct_layer = FIRST_DUCT_LAYER + SECOND_DUCT_LAYER + THIRD_DUCT_LAYER + FOURTH_DUCT_LAYER + FIFTH_DUCT_LAYER
lock_connects = TRUE
lock_layers = TRUE
ignore_colors = TRUE
dumb = TRUE
/obj/machinery/duct/multilayered/update_icon()
icon_state = initial(icon_state)
if((connects & NORTH) || (connects & SOUTH))
icon_state += "_vertical"
pixel_x = -15
pixel_y = -15
else
icon_state += "_horizontal"
pixel_x = -10
pixel_y = -12
/obj/machinery/duct/multilayered/connect_duct(obj/machinery/duct/D, direction, ignore_color)
if(istype(D, /obj/machinery/duct/multilayered)) //don't connect to other multilayered stuff because honestly it shouldnt be done and I dont wanna deal with it
return
return ..()

View File

@@ -0,0 +1,74 @@
/obj/machinery/power/liquid_pump
name = "liquid pump"
desc = "Pump up those sweet liquids from under the surface."
icon = 'icons/obj/plumbing/plumbers.dmi'
icon_state = "pump"
anchored = FALSE
density = TRUE
idle_power_usage = 10
active_power_usage = 1000
var/powered = FALSE
var/pump_power = 2 //units we pump per process (2 seconds)
var/obj/structure/geyser/geyser
var/volume = 200
/obj/machinery/power/liquid_pump/Initialize()
create_reagents(volume)
return ..()
/obj/machinery/power/liquid_pump/ComponentInitialize()
AddComponent(/datum/component/plumbing/simple_supply)
/obj/machinery/power/liquid_pump/wrench_act(mob/living/user, obj/item/I)
default_unfasten_wrench(user, I)
return TRUE
/obj/machinery/power/liquid_pump/default_unfasten_wrench(mob/user, obj/item/I, time = 20)
. = ..()
if(. == SUCCESSFUL_UNFASTEN)
toggle_active()
/obj/machinery/power/liquid_pump/proc/toggle_active(mob/user, obj/item/I) //we split this in a seperate proc so we can also deactivate if we got no geyser under us
geyser = null
if(user)
user.visible_message("<span class='notice'>[user.name] [anchored ? "fasten" : "unfasten"] [src]</span>", \
"<span class='notice'>You [anchored ? "fasten" : "unfasten"] [src]</span>")
var/datum/component/plumbing/P = GetComponent(/datum/component/plumbing)
if(anchored)
P.start()
connect_to_network()
else
P.disable()
disconnect_from_network()
/obj/machinery/power/liquid_pump/process()
if(!anchored)
return
if(!geyser)
for(var/obj/structure/geyser/G in loc.contents)
geyser = G
if(!geyser) //we didnt find one, abort
toggle_active()
anchored = FALSE
visible_message("<span class='warning'>The [name] makes a sad beep!</span>")
playsound(src, 'sound/machines/buzz-sigh.ogg', 50)
return
if(avail(active_power_usage))
if(!powered) //we werent powered before this tick so update our sprite
powered = TRUE
icon_state = "[initial(icon_state)]-on"
add_load(active_power_usage)
pump()
else if(powered) //we were powered, but now we arent
powered = FALSE
icon_state = initial(icon_state)
/obj/machinery/power/liquid_pump/proc/pump()
if(!geyser || !geyser.reagents)
return
geyser.reagents.trans_to(src, pump_power)

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB