mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-16 12:43:09 +00:00
* Compact Airlock Pump (#84365) ## About The Pull Request Adds a craftable airlock pump that is an atmos device with distro & waste nodes on a single side. When wrenched down, looks for airlocks and sets up a cycling airlock chamber. A more compact and flexible alternative to the current binary DP Vent Pumps. https://github.com/tgstation/tgstation/assets/3625094/a799b3e8-3425-43fc-9c17-2bdde63ec194 Can also skip cycling when a shuttle is docked with one of the external airlocks: https://github.com/tgstation/tgstation/assets/3625094/37054bc0-b8c5-4556-aa2c-0fa5bc3c7c5b **Set up from scratch**: https://youtu.be/22PDfmJKC7g ## Why It's Good For The Game Provides an easy to map and setup alternative to DP Vents that doesn't require any map helpers, var editing and can even be crafted in the game.  You just need to extend the disto/waste pipes to the place where you want your airlock to be. When built, it automatically considers the first found airlock in the direction of incoming pipes as an internal one, and the opposite - external. Cycling is controlled by the airlocks. When you click/bump an internal airlock, it starts pressurizing. When external one - depressurizing. When unable to complete the cycle in 10 seconds, unbolts the door automatically to avoid stucking due to issues with pipes or slow atmos. TODO: - [x] Hook up to the power and figure out the logic when unpowered - [x] ~Make it work with a directly connected layer manifold~ - [x] Make unwrenching require unpowered state and forbid mapspawn pump unwrenching - [x] Support airlock arrays and double airlocks - [x] An option to open airlocks instead of just bolts - [x] Check for the presence of airlocks and crash if not set up properly on map ## Changelog 🆑 add: Added airlock pump atmos device to create cycling airlocks /🆑 --------- Co-authored-by: Ghom <42542238+Ghommie@ users.noreply.github.com> * Compact Airlock Pump --------- Co-authored-by: Andrew <mt.forspam@gmail.com> Co-authored-by: Ghom <42542238+Ghommie@ users.noreply.github.com>
352 lines
11 KiB
Plaintext
352 lines
11 KiB
Plaintext
// So much of atmospherics.dm was used solely by components, so separating this makes things all a lot cleaner.
|
|
// On top of that, now people can add component-speciic procs/vars if they want!
|
|
|
|
/obj/machinery/atmospherics/components
|
|
hide = FALSE
|
|
layer = GAS_PUMP_LAYER
|
|
///Is the component welded?
|
|
var/welded = FALSE
|
|
///Should the component should show the pipe underneath it?
|
|
var/showpipe = TRUE
|
|
///When the component is on a non default layer should we shift everything? Or just the underlay pipe
|
|
var/shift_underlay_only = TRUE
|
|
///Stores the parent pipeline, used in components
|
|
var/list/datum/pipeline/parents
|
|
///If this is queued for a rebuild this var signifies whether parents should be updated after it's done
|
|
var/update_parents_after_rebuild = FALSE
|
|
///Stores the gasmix for each node, used in components
|
|
var/list/datum/gas_mixture/airs
|
|
///Handles whether the custom reconcilation handling should be used
|
|
var/custom_reconcilation = FALSE
|
|
|
|
/obj/machinery/atmospherics/components/New()
|
|
parents = new(device_type)
|
|
airs = new(device_type)
|
|
|
|
..()
|
|
|
|
for(var/i in 1 to device_type)
|
|
if(airs[i])
|
|
continue
|
|
var/datum/gas_mixture/component_mixture = new
|
|
component_mixture.volume = 200
|
|
airs[i] = component_mixture
|
|
|
|
// Iconnery
|
|
|
|
/**
|
|
* Called by update_icon(), used individually by each component to determine the icon state without the pipe in consideration
|
|
*/
|
|
/obj/machinery/atmospherics/components/proc/update_icon_nopipes()
|
|
return
|
|
|
|
/obj/machinery/atmospherics/components/on_hide(datum/source, underfloor_accessibility)
|
|
hide_pipe(underfloor_accessibility)
|
|
return ..()
|
|
|
|
/**
|
|
* Called in on_hide(), set the showpipe var to true or false depending on the situation, calls update_icon()
|
|
*/
|
|
/obj/machinery/atmospherics/components/proc/hide_pipe(underfloor_accessibility)
|
|
showpipe = !!underfloor_accessibility
|
|
if(showpipe)
|
|
REMOVE_TRAIT(src, TRAIT_UNDERFLOOR, REF(src))
|
|
else
|
|
ADD_TRAIT(src, TRAIT_UNDERFLOOR, REF(src))
|
|
update_appearance()
|
|
|
|
/obj/machinery/atmospherics/components/update_icon()
|
|
update_icon_nopipes()
|
|
|
|
underlays.Cut()
|
|
|
|
color = null
|
|
SET_PLANE_IMPLICIT(src, showpipe ? GAME_PLANE : FLOOR_PLANE)
|
|
// Layer is handled in update_layer()
|
|
|
|
if(!showpipe)
|
|
return ..()
|
|
if(pipe_flags & PIPING_DISTRO_AND_WASTE_LAYERS)
|
|
return ..()
|
|
|
|
var/connected = 0 //Direction bitset
|
|
|
|
var/underlay_pipe_layer = shift_underlay_only ? piping_layer : 3
|
|
|
|
for(var/i in 1 to device_type) //adds intact pieces
|
|
if(!nodes[i])
|
|
continue
|
|
var/obj/machinery/atmospherics/node = nodes[i]
|
|
var/node_dir = get_dir(src, node)
|
|
var/mutable_appearance/pipe_appearance = mutable_appearance('icons/obj/pipes_n_cables/pipe_underlays.dmi', "intact_[node_dir]_[underlay_pipe_layer]")
|
|
pipe_appearance.color = node.pipe_color
|
|
underlays += pipe_appearance
|
|
connected |= node_dir
|
|
|
|
for(var/direction in GLOB.cardinals)
|
|
if((initialize_directions & direction) && !(connected & direction))
|
|
var/mutable_appearance/pipe_appearance = mutable_appearance('icons/obj/pipes_n_cables/pipe_underlays.dmi', "exposed_[direction]_[underlay_pipe_layer]")
|
|
pipe_appearance.color = pipe_color
|
|
underlays += pipe_appearance
|
|
|
|
if(!shift_underlay_only)
|
|
PIPING_LAYER_SHIFT(src, piping_layer)
|
|
return ..()
|
|
|
|
// Pipenet stuff; housekeeping
|
|
|
|
/obj/machinery/atmospherics/components/nullify_node(i)
|
|
if(parents[i])
|
|
nullify_pipenet(parents[i])
|
|
airs[i] = null
|
|
return ..()
|
|
|
|
/obj/machinery/atmospherics/components/on_construction(mob/user)
|
|
. = ..()
|
|
update_parents()
|
|
|
|
/obj/machinery/atmospherics/components/on_deconstruction(disassembled)
|
|
relocate_airs()
|
|
return ..()
|
|
|
|
/obj/machinery/atmospherics/components/rebuild_pipes()
|
|
. = ..()
|
|
if(update_parents_after_rebuild)
|
|
update_parents()
|
|
|
|
/obj/machinery/atmospherics/components/get_rebuild_targets()
|
|
var/list/to_return = list()
|
|
for(var/i in 1 to device_type)
|
|
if(parents[i])
|
|
continue
|
|
parents[i] = new /datum/pipeline()
|
|
to_return += parents[i]
|
|
return to_return
|
|
|
|
/**
|
|
* Called by nullify_node(), used to remove the pipeline the component is attached to
|
|
* Arguments:
|
|
* * -reference: the pipeline the component is attached to
|
|
*/
|
|
/obj/machinery/atmospherics/components/proc/nullify_pipenet(datum/pipeline/reference)
|
|
if(!reference)
|
|
CRASH("nullify_pipenet(null) called by [type] on [COORD(src)]")
|
|
|
|
for (var/i in 1 to parents.len)
|
|
if (parents[i] == reference)
|
|
reference.other_airs -= airs[i] // Disconnects from the pipeline side
|
|
parents[i] = null // Disconnects from the machinery side.
|
|
|
|
reference.other_atmos_machines -= src
|
|
if(custom_reconcilation)
|
|
reference.require_custom_reconcilation -= src
|
|
|
|
/**
|
|
* We explicitly qdel pipeline when this particular pipeline
|
|
* is projected to have no member and cause GC problems.
|
|
* We have to do this because components don't qdel pipelines
|
|
* while pipes must and will happily wreck and rebuild everything
|
|
* again every time they are qdeleted.
|
|
*/
|
|
|
|
if(!length(reference.other_atmos_machines) && !length(reference.members))
|
|
if(QDESTROYING(reference))
|
|
CRASH("nullify_pipenet() called on qdeleting [reference]")
|
|
qdel(reference)
|
|
|
|
/obj/machinery/atmospherics/components/return_pipenet_airs(datum/pipeline/reference)
|
|
var/list/returned_air = list()
|
|
|
|
for (var/i in 1 to parents.len)
|
|
if (parents[i] == reference)
|
|
returned_air += airs[i]
|
|
return returned_air
|
|
|
|
/obj/machinery/atmospherics/components/pipeline_expansion(datum/pipeline/reference)
|
|
if(reference)
|
|
return list(nodes[parents.Find(reference)])
|
|
return ..()
|
|
|
|
/obj/machinery/atmospherics/components/set_pipenet(datum/pipeline/reference, obj/machinery/atmospherics/target_component)
|
|
parents[nodes.Find(target_component)] = reference
|
|
|
|
/obj/machinery/atmospherics/components/return_pipenet(obj/machinery/atmospherics/target_component = nodes[1]) //returns parents[1] if called without argument
|
|
return parents[nodes.Find(target_component)]
|
|
|
|
/obj/machinery/atmospherics/components/replace_pipenet(datum/pipeline/Old, datum/pipeline/New)
|
|
parents[parents.Find(Old)] = New
|
|
|
|
// Helpers
|
|
|
|
/**
|
|
* Called in most atmos processes and gas handling situations, update the parents pipelines of the devices connected to the source component
|
|
* This way gases won't get stuck
|
|
*/
|
|
/obj/machinery/atmospherics/components/proc/update_parents()
|
|
if(!SSair.initialized)
|
|
return
|
|
if(rebuilding)
|
|
update_parents_after_rebuild = TRUE
|
|
return
|
|
for(var/i in 1 to device_type)
|
|
var/datum/pipeline/parent = parents[i]
|
|
if(!parent)
|
|
WARNING("Component is missing a pipenet! Rebuilding...")
|
|
SSair.add_to_rebuild_queue(src)
|
|
else
|
|
parent.update = TRUE
|
|
|
|
/obj/machinery/atmospherics/components/return_pipenets()
|
|
. = list()
|
|
for(var/i in 1 to device_type)
|
|
. += return_pipenet(nodes[i])
|
|
|
|
/// When this machine is in a pipenet that is reconciling airs, this proc can add pipelines to the calculation.
|
|
/// Can be either a list of pipenets or a single pipenet.
|
|
/obj/machinery/atmospherics/components/proc/return_pipenets_for_reconcilation(datum/pipeline/requester)
|
|
return list()
|
|
|
|
/// When this machine is in a pipenet that is reconciling airs, this proc can add airs to the calculation.
|
|
/// Can be either a list of airs or a single air mix.
|
|
/obj/machinery/atmospherics/components/proc/return_airs_for_reconcilation(datum/pipeline/requester)
|
|
return list()
|
|
|
|
// UI Stuff
|
|
|
|
/obj/machinery/atmospherics/components/ui_status(mob/user, datum/ui_state/state)
|
|
if(allowed(user))
|
|
return ..()
|
|
to_chat(user, span_danger("Access denied."))
|
|
return UI_CLOSE
|
|
|
|
// Tool acts
|
|
|
|
/obj/machinery/atmospherics/components/return_analyzable_air()
|
|
return airs
|
|
|
|
/**
|
|
* Handles machinery deconstruction and unsafe pressure release
|
|
*/
|
|
/obj/machinery/atmospherics/components/proc/crowbar_deconstruction_act(mob/living/user, obj/item/tool, internal_pressure = 0)
|
|
if(!panel_open)
|
|
balloon_alert(user, "open panel!")
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
var/unsafe_wrenching = FALSE
|
|
var/filled_pipe = FALSE
|
|
var/datum/gas_mixture/environment_air = loc.return_air()
|
|
|
|
for(var/i in 1 to device_type)
|
|
var/datum/gas_mixture/inside_air = airs[i]
|
|
if(inside_air.total_moles() > 0 || internal_pressure)
|
|
filled_pipe = TRUE
|
|
if(!nodes[i] || (istype(nodes[i], /obj/machinery/atmospherics/components/unary/portables_connector) && !portable_device_connected(i)))
|
|
internal_pressure = internal_pressure > airs[i].return_pressure() ? internal_pressure : airs[i].return_pressure()
|
|
|
|
if(!filled_pipe)
|
|
default_deconstruction_crowbar(tool)
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
to_chat(user, span_notice("You begin to unfasten \the [src]..."))
|
|
|
|
internal_pressure -= environment_air.return_pressure()
|
|
|
|
if(internal_pressure > 2 * ONE_ATMOSPHERE)
|
|
to_chat(user, span_warning("As you begin deconstructing \the [src] a gush of air blows in your face... maybe you should reconsider?"))
|
|
unsafe_wrenching = TRUE
|
|
|
|
if(!do_after(user, 2 SECONDS, src))
|
|
return
|
|
if(unsafe_wrenching)
|
|
unsafe_pressure_release(user, internal_pressure)
|
|
tool.play_tool_sound(src, 50)
|
|
deconstruct(TRUE)
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
/obj/machinery/atmospherics/components/default_change_direction_wrench(mob/user, obj/item/I)
|
|
. = ..()
|
|
if(!.)
|
|
return FALSE
|
|
set_init_directions()
|
|
reconnect_nodes()
|
|
return TRUE
|
|
|
|
/obj/machinery/atmospherics/components/proc/reconnect_nodes()
|
|
for(var/i in 1 to device_type)
|
|
var/obj/machinery/atmospherics/node = nodes[i]
|
|
if(node)
|
|
if(src in node.nodes)
|
|
node.disconnect(src)
|
|
nodes[i] = null
|
|
if(parents[i])
|
|
nullify_pipenet(parents[i])
|
|
for(var/i in 1 to device_type)
|
|
var/obj/machinery/atmospherics/node = nodes[i]
|
|
atmos_init()
|
|
node = nodes[i]
|
|
if(node)
|
|
node.atmos_init()
|
|
node.add_member(src)
|
|
update_parents()
|
|
SSair.add_to_rebuild_queue(src)
|
|
|
|
/**
|
|
* Disconnects all nodes from ourselves, remove us from the node's nodes.
|
|
* Nullify our parent pipenet
|
|
*/
|
|
/obj/machinery/atmospherics/components/proc/disconnect_nodes()
|
|
for(var/i in 1 to device_type)
|
|
var/obj/machinery/atmospherics/node = nodes[i]
|
|
if(node)
|
|
if(src in node.nodes) //Only if it's actually connected. On-pipe version would is one-sided.
|
|
node.disconnect(src)
|
|
nodes[i] = null
|
|
if(parents[i])
|
|
nullify_pipenet(parents[i])
|
|
|
|
/**
|
|
* Connects all nodes to ourselves, add us to the node's nodes.
|
|
* Calls atmos_init() on the node and on us.
|
|
*/
|
|
/obj/machinery/atmospherics/components/proc/connect_nodes()
|
|
atmos_init()
|
|
for(var/i in 1 to device_type)
|
|
var/obj/machinery/atmospherics/node = nodes[i]
|
|
if(node)
|
|
node.atmos_init()
|
|
node.add_member(src)
|
|
SSair.add_to_rebuild_queue(src)
|
|
|
|
/**
|
|
* Easy way to toggle nodes connection and disconnection.
|
|
*
|
|
* Arguments:
|
|
* * disconnect - if TRUE, disconnects all nodes. If FALSE, connects all nodes.
|
|
*/
|
|
/obj/machinery/atmospherics/components/proc/change_nodes_connection(disconnect)
|
|
if(disconnect)
|
|
disconnect_nodes()
|
|
return
|
|
connect_nodes()
|
|
|
|
/obj/machinery/atmospherics/components/update_layer()
|
|
layer = (showpipe ? initial(layer) : ABOVE_OPEN_TURF_LAYER) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE + (GLOB.pipe_colors_ordered[pipe_color] * 0.001)
|
|
|
|
/**
|
|
* Handles air relocation to the pipenet/environment
|
|
*/
|
|
/obj/machinery/atmospherics/components/proc/relocate_airs(datum/gas_mixture/to_release)
|
|
var/turf/local_turf = get_turf(src)
|
|
for(var/i in 1 to device_type)
|
|
var/datum/gas_mixture/air = airs[i]
|
|
if(!nodes[i] || (istype(nodes[i], /obj/machinery/atmospherics/components/unary/portables_connector) && !portable_device_connected(i)))
|
|
if(!to_release)
|
|
to_release = air
|
|
continue
|
|
to_release.merge(air)
|
|
continue
|
|
var/datum/gas_mixture/parents_air = parents[i].air
|
|
parents_air.merge(air)
|
|
if(to_release)
|
|
local_turf.assume_air(to_release)
|