Files

336 lines
12 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
///shortcut to our parents' reagent holder. The holder that sends reagents into the pipeline
var/datum/reagents/reagents
///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 = NONE
///direction in wich we act as a demander
var/demand_connects = NONE
///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
///What color is our demand connect?
var/demand_color = COLOR_RED
///What color is our supply connect?
var/supply_color = COLOR_BLUE
/// How many distinct reagents can we accept at once
/// Ex - if this was set to "3", our component would only request the first 3 reagents found, even if more are available
var/distinct_reagent_cap = INFINITY
/datum/component/plumbing/Initialize(ducting_layer)
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
var/atom/movable/parent_movable = parent
if(!parent_movable.reagents)
return COMPONENT_INCOMPATIBLE
if(GLOB.plumbing_layer_names["[ducting_layer]"])
src.ducting_layer = ducting_layer
ducts = list()
reagents = parent_movable.reagents
on_parent_dir_change(parent_movable, NONE, parent_movable.dir)
if(parent_movable.anchored)
if(PERFORM_ALL_TESTS(maptest_log_mapping))
var/datum/overlap = ducting_layer_check(parent_movable, ducting_layer)
if(!isnull(overlap))
var/message = GLOB.plumbing_layer_names["[ducting_layer]"]
if(istype(overlap, /obj/machinery/duct))
message = "plumbing duct on [message]"
else
message = "plumbing machine on [message]"
log_mapping("Overlapping [message] detected at [AREACOORD(parent_movable)]")
parent_movable.set_anchored(FALSE)
return
enable()
/datum/component/plumbing/RegisterWithParent()
// RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_WRENCH), PROC_REF(check_wrench)) // BUBBER EDIT REMOVAL - Let people still place machines on ducts and manifolds.
RegisterSignal(parent, COMSIG_MOVABLE_SET_ANCHORED, PROC_REF(toggle_active))
RegisterSignal(parent, COMSIG_OBJ_HIDE, PROC_REF(hide))
RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(create_overlays))
RegisterSignal(parent, COMSIG_ATOM_POST_DIR_CHANGE, PROC_REF(on_parent_dir_change))
RegisterSignal(parent, COMSIG_MOVABLE_CHANGE_DUCT_LAYER, PROC_REF(change_ducting_layer))
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
/datum/component/plumbing/UnregisterFromParent()
UnregisterSignal(parent, list(
COMSIG_ATOM_TOOL_ACT(TOOL_WRENCH),
COMSIG_MOVABLE_SET_ANCHORED,
COMSIG_OBJ_HIDE,
COMSIG_ATOM_UPDATE_OVERLAYS,
COMSIG_ATOM_POST_DIR_CHANGE,
COMSIG_MOVABLE_CHANGE_DUCT_LAYER,
COMSIG_ATOM_EXAMINE,
))
/datum/component/plumbing/Destroy()
disable()
ducts.Cut()
reagents = null
return ..()
///Returns if the machine is active or not
/datum/component/plumbing/proc/active()
var/atom/movable/parent_movable = parent
return parent_movable.anchored
///Returns the reagent holder meant to receive the reagents. Can be different from the one that sends reagents to the network
/datum/component/plumbing/proc/recipient_reagents_holder()
return reagents
///settle wherever we are, and start behaving like a piece of plumbing
/datum/component/plumbing/proc/enable()
if(demand_connects)
START_PROCESSING(SSplumbing, src)
for(var/direction in GLOB.cardinals)
if(!(direction & (demand_connects | supply_connects)))
continue
var/opposite_dir = REVERSE_DIR(direction)
for(var/atom/movable/found_atom in get_step(parent, direction))
var/obj/machinery/duct/duct = found_atom
if(istype(duct))
if(duct.neighbours && (duct.duct_layer & ducting_layer))
duct.neighbours[parent] = opposite_dir
duct.update_appearance(UPDATE_ICON)
duct.net.add_plumber(src, direction)
continue
for(var/datum/component/plumbing/plumber as anything in found_atom.GetComponents(/datum/component/plumbing))
if(plumber.active() && (plumber.ducting_layer & ducting_layer))
if((plumber.demand_connects & opposite_dir) && (supply_connects & direction) || (plumber.supply_connects & opposite_dir) && (demand_connects & direction)) //make sure we arent connecting two supplies or demands
var/datum/ductnet/net = new
net.add_plumber(src, direction)
net.add_plumber(plumber, opposite_dir)
/datum/component/plumbing/proc/disable()
SIGNAL_HANDLER
STOP_PROCESSING(SSplumbing, src)
while(ducts.len)
var/datum/ductnet/net = ducts[ducts[1]]
//disconnect ourself from any ducts connected to us
for(var/obj/machinery/duct/pipe as anything in net.ducts)
if(pipe.neighbours[parent])
pipe.neighbours -= parent
pipe.update_appearance(UPDATE_ICON)
//remove ourself from this network and delete it if emtpy
if(net.remove_plumber(src))
qdel(net)
//BUBBER EDIT REMOVAL - Let people still place machines on ducts and manifolds.
/*
/datum/component/plumbing/proc/check_wrench(obj/parent_obj, mob/user, tool, processing_recipes)
SIGNAL_HANDLER
if(!active())
var/datum/overlap = ducting_layer_check(parent_obj, -ducting_layer)
if(!isnull(overlap))
parent_obj.balloon_alert(user, "overlapping [istype(overlap, /obj/machinery/duct) ? "duct" : "machine"] detected!")
return ITEM_INTERACT_FAILURE
*/
/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()
/datum/component/plumbing/proc/hide(atom/movable/parent_obj, underfloor_accessibility)
SIGNAL_HANDLER
var/atom/movable/parent_movable = parent
// If machine is unanchored, keep connector visible.
// This doesn't necessary map to `active`, so check parent.
var/should_hide = !underfloor_accessibility
if(parent_movable.anchored || !should_hide)
tile_covered = should_hide
parent_obj.update_appearance()
/datum/component/plumbing/proc/create_overlays(atom/movable/parent_movable, list/overlays)
SIGNAL_HANDLER
if(tile_covered)
return
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_layer = PLUMBING_PIPE_VISIBILE_LAYER + ducting_layer * 0.0003
for(var/direction in GLOB.cardinals)
var/color
var/duct_x = offset - parent_movable.pixel_x - parent_movable.pixel_w
var/duct_y = offset - parent_movable.pixel_y - parent_movable.pixel_z
if(direction & initial(demand_connects))
color = demand_color
else if(direction & initial(supply_connects))
color = supply_color
else
continue
var/image/overlay = image('icons/obj/pipes_n_cables/hydrochem/connects.dmi', "[dir2text(direction)]-[ducting_layer]", layer = duct_layer)
overlay.color = color
overlay.pixel_w = duct_x
overlay.pixel_z = duct_y
overlays += overlay
/datum/component/plumbing/proc/on_parent_dir_change(atom/movable/parent_obj, old_dir, new_dir)
SIGNAL_HANDLER
demand_connects = initial(demand_connects)
supply_connects = initial(supply_connects)
if(new_dir != SOUTH)
var/angle = 180 - dir2angle(new_dir)
var/new_demand_connects = NONE
var/new_supply_connects = NONE
for(var/direction in GLOB.cardinals)
if(direction & demand_connects)
new_demand_connects |= turn(direction, angle)
if(direction & supply_connects)
new_supply_connects |= turn(direction, angle)
demand_connects = new_demand_connects
supply_connects = new_supply_connects
parent_obj.update_appearance(UPDATE_OVERLAYS)
if(length(ducts))
disable()
enable()
/datum/component/plumbing/proc/change_ducting_layer(obj/source, obj/changer, new_layer = DUCT_LAYER_DEFAULT)
SIGNAL_HANDLER
ducting_layer = new_layer
source.update_appearance(UPDATE_OVERLAYS)
if(changer)
playsound(changer, 'sound/items/tools/ratchet.ogg', 10, TRUE) //sound
if(length(ducts))
disable()
enable()
/datum/component/plumbing/process()
if(!demand_connects)
return PROCESS_KILL
var/datum/reagents/receiver = recipient_reagents_holder()
if(QDELETED(receiver))
return PROCESS_KILL
if(!receiver.holder_full())
for(var/dir in GLOB.cardinals)
if(dir & demand_connects)
send_request(dir)
/datum/component/plumbing/proc/on_examine(atom/movable/source, mob/user, list/examine_list)
SIGNAL_HANDLER
if(distinct_reagent_cap != INFINITY)
examine_list += span_notice("This plumbing component will only accept up to [distinct_reagent_cap] distinct reagents at once.")
///called from in process(). only calls process_request(), but can be overwritten for children with special behaviour
/datum/component/plumbing/proc/send_request(dir)
var/amount_to_give = MACHINE_REAGENT_TRANSFER
// infinite cap means we need to special handling, process_request will just grab as much as it wants.
if(distinct_reagent_cap == INFINITY)
process_request(amount_to_give, null, dir) // null for no specific reagent, we're not picky.
return
// we have a cap, so we need to figure out what reagents we want
var/list/all_allowed_reagents = get_all_network_reagents(ducts["[dir]"])
if(length(all_allowed_reagents) > distinct_reagent_cap)
all_allowed_reagents.Cut(distinct_reagent_cap + 1)
else if(!length(all_allowed_reagents))
return
// request an even amount of each allowed reagent
var/amount_per_reagent = round(amount_to_give / length(all_allowed_reagents), CHEMICAL_VOLUME_ROUNDING)
for(var/allowed_reagent in all_allowed_reagents)
process_request(amount_per_reagent, allowed_reagent, dir)
/// Returns a list of all distinct reagent types available in the passed duct network.
/// The passed net can be null, it is handled.
/datum/component/plumbing/proc/get_all_network_reagents(datum/ductnet/net)
var/list/distinct_reagents = list()
for(var/datum/reagent/existing_regent as anything in reagents.reagent_list)
distinct_reagents |= existing_regent.type
for(var/datum/component/plumbing/supplier as anything in net?.suppliers)
for(var/datum/reagent/chemical as anything in supplier.reagents.reagent_list)
distinct_reagents |= chemical.type
return distinct_reagents
///check who can give us what we want, and how many each of them will give us
/datum/component/plumbing/proc/process_request(amount = MACHINE_REAGENT_TRANSFER, reagent, dir, round_robin = TRUE)
//find the duct to take from
var/dirtext = num2text(dir)
var/datum/ductnet/net = ducts[dirtext]
if(QDELETED(net))
if(net)
ducts -= dirtext
return FALSE
//find all valid suppliers in the duct
var/list/valid_suppliers = list()
for(var/datum/component/plumbing/supplier as anything in net?.suppliers)
if(supplier.can_give(amount, reagent, net))
valid_suppliers += supplier
var/suppliersLeft = length(valid_suppliers)
if(!suppliersLeft)
return FALSE
//take an equal amount from each supplier
var/currentRequest
var/target_volume = reagents.total_volume + amount
for(var/datum/component/plumbing/give as anything in valid_suppliers)
currentRequest = (target_volume - reagents.total_volume) / suppliersLeft
give.transfer_to(src, currentRequest, reagent, net, round_robin)
suppliersLeft--
return TRUE
///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)
SHOULD_BE_PURE(TRUE)
if(amount <= 0)
return FALSE
if(reagent) //only asked for one type of reagent
return reagents.has_reagent(reagent)
else if(reagents.total_volume) //take whatever
return TRUE
return FALSE
///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, round_robin = TRUE)
reagents.trans_to(target.recipient_reagents_holder(), amount, target_id = reagent, methods = round_robin ? LINEAR : NONE)