Files
Bubberstation/code/datums/components/plumbing/_plumbing.dm
Ghom 5d5492e111 Implements usage of the REVERSE_DIR macro throughout the code. (#77122)
## About The Pull Request
Replaces a ton of `turn(dir, 180)` calls with the aforementioned macro.

## Why It's Good For The Game
Afaik, `REVERSE_DIR` was coded to be faster than the classic `turn(dir,
180)` call, being a simple set of binary operations. To sum it up, micro
optimization.

## Changelog

N/A
2023-07-28 15:16:09 +02:00

408 lines
15 KiB
Plaintext

/datum/component/plumbing
dupe_mode = COMPONENT_DUPE_ALLOWED
///Index with "1" = /datum/ductnet/theductpointingnorth etc. "1" being the num2text from NORTH define
var/list/datum/ductnet/ducts = list()
///shortcut to our parents' reagent holder
var/datum/reagents/reagents
///TRUE if we wanna add proper pipe overlays under our parent object. this is pretty good if i may so so myself
var/use_overlays = TRUE
///Whether our tile is covered and we should hide our ducts
var/tile_covered = FALSE
///directions in wich we act as a supplier
var/supply_connects
///direction in wich we act as a demander
var/demand_connects
///FALSE to pretty much just not exist in the plumbing world so we can be moved, TRUE to go plumbo mode
var/active = FALSE
///if TRUE connects will spin with the parent object visually and codually, so you can have it work in any direction. FALSE if you want it to be static
var/turn_connects = TRUE
///The layer on which we connect. Don't add multiple. If you want multiple layer connects for some reason you can just add multiple components with different layers
var/ducting_layer = DUCT_LAYER_DEFAULT
///In-case we don't want the main machine to get the reagents, but perhaps whoever is buckled to it
var/recipient_reagents_holder
///How do we apply the new reagents to the receiver? Generally doesn't matter, but some stuff, like people, does care if its injected or whatevs
var/methods
///What color is our demand connect?
var/demand_color = COLOR_RED
///What color is our supply connect?
var/supply_color = COLOR_BLUE
///Extend the pipe to the edge for wall-mounted plumbed devices, like sinks and showers
var/extend_pipe_to_edge = FALSE
///turn_connects is for wheter or not we spin with the object to change our pipes
/datum/component/plumbing/Initialize(start=TRUE, ducting_layer, turn_connects=TRUE, datum/reagents/custom_receiver, extend_pipe_to_edge = FALSE)
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
if(ducting_layer)
src.ducting_layer = ducting_layer
var/atom/movable/parent_movable = parent
if(!parent_movable.reagents && !custom_receiver)
return COMPONENT_INCOMPATIBLE
reagents = parent_movable.reagents
src.turn_connects = turn_connects
src.extend_pipe_to_edge = extend_pipe_to_edge
set_recipient_reagents_holder(custom_receiver ? custom_receiver : parent_movable.reagents)
if(start)
//We're registering here because I need to check whether we start active or not, and this is just easier
//Should be called after we finished. Done this way because other networks need to finish setting up aswell
RegisterSignal(parent, COMSIG_COMPONENT_ADDED, PROC_REF(enable))
/datum/component/plumbing/RegisterWithParent()
RegisterSignals(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING), PROC_REF(disable))
RegisterSignals(parent, list(COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH), PROC_REF(toggle_active))
RegisterSignal(parent, COMSIG_OBJ_HIDE, PROC_REF(hide))
RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(create_overlays)) //called by lateinit on startup
RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, PROC_REF(on_parent_dir_change)) //called when placed on a shuttle and it moves, and other edge cases
RegisterSignal(parent, COMSIG_MOVABLE_CHANGE_DUCT_LAYER, PROC_REF(change_ducting_layer))
/datum/component/plumbing/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING, COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH, COMSIG_OBJ_HIDE, \
COMSIG_ATOM_UPDATE_OVERLAYS, COMSIG_ATOM_DIR_CHANGE, COMSIG_MOVABLE_CHANGE_DUCT_LAYER, COMSIG_COMPONENT_ADDED))
REMOVE_TRAIT(parent, TRAIT_UNDERFLOOR, REF(src))
/datum/component/plumbing/Destroy()
ducts = null
reagents = null
set_recipient_reagents_holder(null) //null is there so it's obvious we're setting this to nothing
return ..()
/datum/component/plumbing/process()
if(!demand_connects || !reagents)
STOP_PROCESSING(SSplumbing, src)
return
if(reagents.total_volume < reagents.maximum_volume)
for(var/D in GLOB.cardinals)
if(D & demand_connects)
send_request(D)
///Can we be added to the ductnet?
/datum/component/plumbing/proc/can_add(datum/ductnet/ductnet, dir)
if(!active)
return
if(!dir || !ductnet)
return FALSE
if(num2text(dir) in ducts)
return FALSE
return TRUE
///called from in process(). only calls process_request(), but can be overwritten for children with special behaviour
/datum/component/plumbing/proc/send_request(dir)
process_request(amount = MACHINE_REAGENT_TRANSFER, reagent = null, dir = dir)
///check who can give us what we want, and how many each of them will give us
/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/datum/component/plumbing/supplier as anything in net.suppliers)
if(supplier.can_give(amount, reagent, net))
valid_suppliers += supplier
// Need to ask for each in turn very carefully, making sure we get the total volume. This is to avoid a division that would always round down and become 0
var/targetVolume = reagents.total_volume + amount
var/suppliersLeft = valid_suppliers.len
for(var/datum/component/plumbing/give as anything in valid_suppliers)
var/currentRequest = (targetVolume - reagents.total_volume) / suppliersLeft
give.transfer_to(src, currentRequest, reagent, net)
suppliersLeft--
///returns TRUE when they can give the specified amount and reagent. called by process request
/datum/component/plumbing/proc/can_give(amount, reagent, datum/ductnet/net)
if(amount <= 0)
return
if(reagent) //only asked for one type of reagent
for(var/datum/reagent/contained_reagent as anything in reagents.reagent_list)
if(contained_reagent.type == reagent)
return TRUE
else if(reagents.total_volume > 0) //take whatever
return TRUE
///this is where the reagent is actually transferred and is thus the finish point of our process()
/datum/component/plumbing/proc/transfer_to(datum/component/plumbing/target, amount, reagent, datum/ductnet/net)
if(!reagents || !target || !target.reagents)
return FALSE
if(reagent)
reagents.trans_id_to(target.recipient_reagents_holder, reagent, amount)
else
reagents.trans_to(target.recipient_reagents_holder, amount, round_robin = TRUE, methods = methods)//we deal with alot of precise calculations so we round_robin=TRUE. Otherwise we get floating point errors, 1 != 1 and 2.5 + 2.5 = 6
///We create our luxurious piping overlays/underlays, to indicate where we do what. only called once if use_overlays = TRUE in Initialize()
/datum/component/plumbing/proc/create_overlays(atom/movable/parent_movable, list/overlays)
SIGNAL_HANDLER
if(tile_covered || !use_overlays)
return
//Copied from ducts handle_layer()
var/offset
switch(ducting_layer)
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
var/duct_x = offset - parent_movable.pixel_x - parent_movable.pixel_w
var/duct_y = offset - parent_movable.pixel_y - parent_movable.pixel_z
var/duct_layer = PLUMBING_PIPE_VISIBILE_LAYER + ducting_layer * 0.0003
var/extension_handled = FALSE
for(var/direction in GLOB.cardinals)
var/color
if(direction & initial(demand_connects))
color = demand_color
else if(direction & initial(supply_connects))
color = supply_color
else
continue
var/direction_text = dir2text(direction)
var/image/overlay
if(turn_connects)
overlay = image('icons/obj/pipes_n_cables/hydrochem/connects.dmi', "[direction_text]-[ducting_layer]", layer = duct_layer)
else
overlay = image('icons/obj/pipes_n_cables/hydrochem/connects.dmi', "[direction_text]-[ducting_layer]-s", layer = duct_layer)
overlay.dir = direction
overlay.color = color
overlay.pixel_x = duct_x
overlay.pixel_y = duct_y
overlays += overlay
// This is a little wiggley extension to make wallmounts like sinks and showers visually link to the pipe
if(extend_pipe_to_edge && !extension_handled)
var/image/edge_overlay = image('icons/obj/pipes_n_cables/hydrochem/connects.dmi', "edge-extension", layer = duct_layer)
edge_overlay.dir = parent_movable.dir
edge_overlay.color = color
edge_overlay.pixel_x = -parent_movable.pixel_x - parent_movable.pixel_w
edge_overlay.pixel_y = -parent_movable.pixel_y - parent_movable.pixel_z
overlays += edge_overlay
// only show extension for the first pipe. This means we'll only reflect that color.
extension_handled = TRUE
///we stop acting like a plumbing thing and disconnect if we are, so we can safely be moved and stuff
/datum/component/plumbing/proc/disable()
SIGNAL_HANDLER
if(!active)
return
STOP_PROCESSING(SSplumbing, src)
for(var/duct_dir in ducts)
var/datum/ductnet/duct = ducts[duct_dir]
duct.remove_plumber(src)
active = FALSE
for(var/direction in GLOB.cardinals)
if(!(direction & (demand_connects | supply_connects)))
continue
for(var/obj/machinery/duct/duct in get_step(parent, direction))
if(!(duct.duct_layer & ducting_layer))
continue
duct.remove_connects(REVERSE_DIR(direction))
duct.neighbours.Remove(parent)
duct.update_appearance()
///settle wherever we are, and start behaving like a piece of plumbing
/datum/component/plumbing/proc/enable(obj/object, datum/component/component)
SIGNAL_HANDLER
if(active || (component && component != src))
UnregisterSignal(parent, list(COMSIG_COMPONENT_ADDED))
return
update_dir()
active = TRUE
var/atom/movable/parent_movable = parent
// Destroy any ducts under us on the same layer.
// Ducts also self-destruct if placed under a plumbing machine.
// Machines disable when they get moved
for(var/obj/machinery/duct/duct in parent_movable.loc)
if(duct.anchored && (duct.duct_layer & ducting_layer))
duct.disconnect_duct()
if(demand_connects)
START_PROCESSING(SSplumbing, src)
for(var/direction in GLOB.cardinals)
if(!(direction & (demand_connects | supply_connects)))
continue
for(var/atom/movable/found_atom in get_step(parent, direction))
if(istype(found_atom, /obj/machinery/duct))
var/obj/machinery/duct/duct = found_atom
duct.attempt_connect()
continue
for(var/datum/component/plumbing/plumber as anything in found_atom.GetComponents(/datum/component/plumbing))
if(plumber.ducting_layer & ducting_layer)
direct_connect(plumber, direction)
/// Toggle our machinery on or off. This is called by a hook from default_unfasten_wrench with anchored as only param, so we dont have to copypaste this on every object that can move
/datum/component/plumbing/proc/toggle_active(obj/parent_obj, new_state)
SIGNAL_HANDLER
// Follow atmos's rule of exposing the connection if you unwrench it and only hiding again if tile is placed back down.
if(tile_covered)
tile_covered = FALSE
parent_obj.update_appearance()
if(new_state)
enable()
else
disable()
/** We update our connects only when we settle down by taking our current and original direction to find our new connects
* If someone wants it to fucking spin while connected to something go actually knock yourself out
*/
/datum/component/plumbing/proc/update_dir()
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/direction in GLOB.cardinals)
if(direction & initial(demand_connects))
new_demand_connects += turn(direction, angle)
if(direction & initial(supply_connects))
new_supply_connects += turn(direction, angle)
demand_connects = new_demand_connects
supply_connects = new_supply_connects
///Give the direction of a pipe, and it'll return wich direction it originally was when it's object pointed SOUTH
/datum/component/plumbing/proc/get_original_direction(dir)
var/atom/movable/parent_movable = parent
return turn(dir, dir2angle(parent_movable.dir) - 180)
//special case in-case we want to connect directly with another machine without a duct
/datum/component/plumbing/proc/direct_connect(datum/component/plumbing/plumbing, dir)
if(!plumbing.active)
return
var/opposite_dir = REVERSE_DIR(dir)
if(plumbing.demand_connects & opposite_dir && supply_connects & dir || plumbing.supply_connects & opposite_dir && demand_connects & dir) //make sure we arent connecting two supplies or demands
var/datum/ductnet/net = new()
net.add_plumber(src, dir)
net.add_plumber(plumbing, opposite_dir)
/datum/component/plumbing/proc/hide(atom/movable/parent_obj, underfloor_accessibility)
SIGNAL_HANDLER
// If machine is unanchored, keep connector visible.
// This doesn't necessary map to `active`, so check parent.
var/atom/movable/parent_movable = parent
var/should_hide = !underfloor_accessibility
if(should_hide)
ADD_TRAIT(parent_obj, TRAIT_UNDERFLOOR, REF(src))
else
REMOVE_TRAIT(parent_obj, TRAIT_UNDERFLOOR, REF(src))
if(parent_movable.anchored || !should_hide)
tile_covered = should_hide
parent_obj.update_appearance()
/datum/component/plumbing/proc/change_ducting_layer(obj/caller, obj/changer, new_layer = DUCT_LAYER_DEFAULT)
SIGNAL_HANDLER
ducting_layer = new_layer
var/atom/movable/parent_movable = parent
parent_movable.update_appearance()
if(changer)
playsound(changer, 'sound/items/ratchet.ogg', 10, TRUE) //sound
//quickly disconnect and reconnect the network.
if(active)
disable()
enable()
/datum/component/plumbing/proc/set_recipient_reagents_holder(datum/reagents/receiver)
if(recipient_reagents_holder)
UnregisterSignal(recipient_reagents_holder, COMSIG_QDELETING) //stop tracking whoever we were tracking
if(receiver)
RegisterSignal(receiver, COMSIG_QDELETING, PROC_REF(handle_reagent_del)) //on deletion call a wrapper proc that clears us, and maybe reagents too
recipient_reagents_holder = receiver
/datum/component/plumbing/proc/handle_reagent_del(datum/source)
SIGNAL_HANDLER
if(source == reagents)
reagents = null
if(source == recipient_reagents_holder)
set_recipient_reagents_holder(null)
/**
* Called when the dir changes. Need to adjust positioning of pipes.
*/
/datum/component/plumbing/proc/on_parent_dir_change(atom/movable/parent_obj, old_dir, new_dir)
SIGNAL_HANDLER
if(old_dir == new_dir)
return
// Defer to later frame because pixel_* is actually updated after all callbacks
addtimer(CALLBACK(parent_obj, TYPE_PROC_REF(/atom/, update_appearance)), 1)
///has one pipe input that only takes, example is manual output pipe
/datum/component/plumbing/simple_demand
demand_connects = SOUTH
///has one pipe output that only supplies. example is liquid pump and manual input pipe
/datum/component/plumbing/simple_supply
supply_connects = SOUTH
///input and output, like a holding tank
/datum/component/plumbing/tank
demand_connects = WEST
supply_connects = EAST
/datum/component/plumbing/manifold
demand_connects = NORTH
supply_connects = SOUTH
/datum/component/plumbing/manifold/change_ducting_layer(obj/caller, obj/changer, new_layer)
return
#define READY 2
///Baby component for the buffer plumbing machine
/datum/component/plumbing/buffer
demand_connects = WEST
supply_connects = EAST
/datum/component/plumbing/buffer/Initialize(start=TRUE, _turn_connects=TRUE, _ducting_layer, datum/reagents/custom_receiver)
if(!istype(parent, /obj/machinery/plumbing/buffer))
return COMPONENT_INCOMPATIBLE
return ..()
/datum/component/plumbing/buffer/can_give(amount, reagent, datum/ductnet/net)
var/obj/machinery/plumbing/buffer/buffer = parent
return (buffer.mode == READY) ? ..() : FALSE
#undef READY