/* Quick overview: Pipes combine to form pipelines Pipelines and other atmospheric objects combine to form pipe_networks Note: A single pipe_network represents a completely open space Pipes -> Pipelines Pipelines + Other Objects -> Pipe network */ #define PIPE_VISIBLE_LEVEL 2 #define PIPE_HIDDEN_LEVEL 1 /obj/machinery/atmospherics anchored = TRUE idle_power_usage = 0 active_power_usage = 0 power_channel = ENVIRON layer = GAS_PIPE_HIDDEN_LAYER //under wires resistance_flags = FIRE_PROOF max_integrity = 200 obj_flags = CAN_BE_HIT | ON_BLUEPRINTS var/nodealert = 0 var/can_unwrench = 0 var/initialize_directions = 0 var/pipe_color var/piping_layer = PIPING_LAYER_DEFAULT var/pipe_flags = NONE var/global/list/iconsetids = list() var/global/list/pipeimages = list() var/image/pipe_vision_img = null var/device_type = 0 var/list/obj/machinery/atmospherics/nodes var/construction_type var/pipe_state //icon_state as a pipe item /obj/machinery/atmospherics/examine(mob/user) ..() if(is_type_in_list(src, GLOB.ventcrawl_machinery) && isliving(user)) var/mob/living/L = user if(L.ventcrawler) to_chat(L, "Alt-click to crawl through it.") /obj/machinery/atmospherics/New(loc, process = TRUE, setdir) if(!isnull(setdir)) setDir(setdir) if(pipe_flags & PIPING_CARDINAL_AUTONORMALIZE) normalize_cardinal_directions() nodes = new(device_type) if (!armor) armor = list(melee = 25, bullet = 10, laser = 10, energy = 100, bomb = 0, bio = 100, rad = 100, fire = 100, acid = 70) ..() if(process) SSair.atmos_machinery += src SetInitDirections() /obj/machinery/atmospherics/Destroy() for(var/i in 1 to device_type) nullifyNode(i) SSair.atmos_machinery -= src dropContents() if(pipe_vision_img) qdel(pipe_vision_img) return ..() //return QDEL_HINT_FINDREFERENCE /obj/machinery/atmospherics/proc/destroy_network() return /obj/machinery/atmospherics/proc/build_network() // Called to build a network from this node return /obj/machinery/atmospherics/proc/nullifyNode(i) if(nodes[i]) var/obj/machinery/atmospherics/N = nodes[i] N.disconnect(src) nodes[i] = null /obj/machinery/atmospherics/proc/getNodeConnects() var/list/node_connects = list() node_connects.len = device_type for(var/i in 1 to device_type) for(var/D in GLOB.cardinals) if(D & GetInitDirections()) if(D in node_connects) continue node_connects[i] = D break return node_connects /obj/machinery/atmospherics/proc/normalize_cardinal_directions() if(dir==SOUTH) setDir(NORTH) else if(dir==WEST) setDir(EAST) //this is called just after the air controller sets up turfs /obj/machinery/atmospherics/proc/atmosinit(var/list/node_connects) if(!node_connects) //for pipes where order of nodes doesn't matter node_connects = getNodeConnects() for(var/i in 1 to device_type) for(var/obj/machinery/atmospherics/target in get_step(src,node_connects[i])) if(can_be_node(target, i)) nodes[i] = target break update_icon() /obj/machinery/atmospherics/proc/setPipingLayer(new_layer) if(pipe_flags & PIPING_DEFAULT_LAYER_ONLY) new_layer = PIPING_LAYER_DEFAULT piping_layer = new_layer pixel_x = (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X pixel_y = (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y layer = initial(layer) + ((piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE) /obj/machinery/atmospherics/proc/can_be_node(obj/machinery/atmospherics/target) return connection_check(target, piping_layer) //Find a connecting /obj/machinery/atmospherics in specified direction /obj/machinery/atmospherics/proc/findConnecting(direction, prompted_layer) for(var/obj/machinery/atmospherics/target in get_step(src, direction)) if(target.initialize_directions & get_dir(target,src)) if(connection_check(target, prompted_layer)) return target /obj/machinery/atmospherics/proc/connection_check(obj/machinery/atmospherics/target, given_layer) if(isConnectable(target, given_layer) && target.isConnectable(src, given_layer) && (target.initialize_directions & get_dir(target,src))) return TRUE return FALSE /obj/machinery/atmospherics/proc/isConnectable(obj/machinery/atmospherics/target, given_layer) if(isnull(given_layer)) given_layer = piping_layer if((target.piping_layer == given_layer) || (target.pipe_flags & PIPING_ALL_LAYER)) return TRUE return FALSE /obj/machinery/atmospherics/proc/pipeline_expansion() return nodes /obj/machinery/atmospherics/proc/SetInitDirections() return /obj/machinery/atmospherics/proc/GetInitDirections() return initialize_directions /obj/machinery/atmospherics/proc/returnPipenet() return /obj/machinery/atmospherics/proc/returnPipenetAir() return /obj/machinery/atmospherics/proc/setPipenet() return /obj/machinery/atmospherics/proc/replacePipenet() return /obj/machinery/atmospherics/proc/disconnect(obj/machinery/atmospherics/reference) if(istype(reference, /obj/machinery/atmospherics/pipe)) var/obj/machinery/atmospherics/pipe/P = reference P.destroy_network() nodes[nodes.Find(reference)] = null update_icon() /obj/machinery/atmospherics/update_icon() return /obj/machinery/atmospherics/attackby(obj/item/W, mob/user, params) if(istype(W, /obj/item/pipe)) //lets you autodrop var/obj/item/pipe/pipe = W if(user.dropItemToGround(pipe)) pipe.setPipingLayer(piping_layer) //align it with us return TRUE if(istype(W, /obj/item/wrench)) if(can_unwrench(user)) var/turf/T = get_turf(src) if (level==1 && isturf(T) && T.intact) to_chat(user, "You must remove the plating first!") return TRUE var/datum/gas_mixture/int_air = return_air() var/datum/gas_mixture/env_air = loc.return_air() add_fingerprint(user) var/unsafe_wrenching = FALSE var/internal_pressure = int_air.return_pressure()-env_air.return_pressure() playsound(src, W.usesound, 50, 1) to_chat(user, "You begin to unfasten \the [src]...") if (internal_pressure > 2*ONE_ATMOSPHERE) to_chat(user, "As you begin unwrenching \the [src] a gush of air blows in your face... maybe you should reconsider?") unsafe_wrenching = TRUE //Oh dear oh dear if (do_after(user, 20*W.toolspeed, target = src) && !QDELETED(src)) user.visible_message( \ "[user] unfastens \the [src].", \ "You unfasten \the [src].", \ "You hear ratchet.") investigate_log("was REMOVED by [key_name(usr)]", INVESTIGATE_ATMOS) //You unwrenched a pipe full of pressure? Let's splat you into the wall, silly. if(unsafe_wrenching) unsafe_pressure_release(user, internal_pressure) deconstruct(TRUE) else return ..() /obj/machinery/atmospherics/proc/can_unwrench(mob/user) return can_unwrench // Throws the user when they unwrench a pipe with a major difference between the internal and environmental pressure. /obj/machinery/atmospherics/proc/unsafe_pressure_release(mob/user, pressures = null) if(!user) return if(!pressures) var/datum/gas_mixture/int_air = return_air() var/datum/gas_mixture/env_air = loc.return_air() pressures = int_air.return_pressure() - env_air.return_pressure() var/fuck_you_dir = get_dir(src, user) // Because fuck you... if(!fuck_you_dir) fuck_you_dir = pick(GLOB.cardinals) var/turf/target = get_edge_target_turf(user, fuck_you_dir) var/range = pressures/250 var/speed = range/5 user.visible_message("[user] is sent flying by pressure!","The pressure sends you flying!") user.throw_at(target, range, speed) /obj/machinery/atmospherics/deconstruct(disassembled = TRUE) if(!(flags_1 & NODECONSTRUCT_1)) if(can_unwrench) var/obj/item/pipe/stored = new construction_type(loc, null, dir, src) stored.setPipingLayer(piping_layer) if(!disassembled) stored.obj_integrity = stored.max_integrity * 0.5 transfer_fingerprints_to(stored) ..() /obj/machinery/atmospherics/proc/getpipeimage(iconset, iconstate, direction, col=rgb(255,255,255)) //Add identifiers for the iconset if(iconsetids[iconset] == null) iconsetids[iconset] = num2text(iconsetids.len + 1) //Generate a unique identifier for this image combination var/identifier = iconsetids[iconset] + "_[iconstate]_[direction]_[col]" if((!(. = pipeimages[identifier]))) var/image/pipe_overlay pipe_overlay = . = pipeimages[identifier] = image(iconset, iconstate, dir = direction) pipe_overlay.color = col /obj/machinery/atmospherics/proc/icon_addintact(var/obj/machinery/atmospherics/node) var/image/img = getpipeimage('icons/obj/atmospherics/components/binary_devices.dmi', "pipe_intact", get_dir(src,node), node.pipe_color) underlays += img return img.dir /obj/machinery/atmospherics/proc/icon_addbroken(var/connected = FALSE) var/unconnected = (~connected) & initialize_directions for(var/direction in GLOB.cardinals) if(unconnected & direction) underlays += getpipeimage('icons/obj/atmospherics/components/binary_devices.dmi', "pipe_exposed", direction) /obj/machinery/atmospherics/on_construction(obj_color, set_layer) if(can_unwrench) add_atom_colour(obj_color, FIXED_COLOUR_PRIORITY) pipe_color = obj_color setPipingLayer(set_layer) var/turf/T = get_turf(src) level = T.intact ? 2 : 1 atmosinit() var/list/nodes = pipeline_expansion() for(var/obj/machinery/atmospherics/A in nodes) A.atmosinit() A.addMember(src) build_network() /obj/machinery/atmospherics/Entered(atom/movable/AM) if(istype(AM, /mob/living)) var/mob/living/L = AM L.ventcrawl_layer = piping_layer return ..() /obj/machinery/atmospherics/singularity_pull(S, current_size) if(current_size >= STAGE_FIVE) deconstruct(FALSE) return ..() #define VENT_SOUND_DELAY 30 /obj/machinery/atmospherics/relaymove(mob/living/user, direction) direction &= initialize_directions if(!direction || !(direction in GLOB.cardinals)) //cant go this way. return if(user in buckled_mobs)// fixes buckle ventcrawl edgecase fuck bug return var/obj/machinery/atmospherics/target_move = findConnecting(direction, user.ventcrawl_layer) if(target_move) if(target_move.can_crawl_through()) if(is_type_in_typecache(target_move, GLOB.ventcrawl_machinery)) user.forceMove(target_move.loc) //handle entering and so on. user.visible_message("You hear something squeezing through the ducts...","You climb out the ventilation system.") else var/list/pipenetdiff = returnPipenets() ^ target_move.returnPipenets() if(pipenetdiff.len) user.update_pipe_vision(target_move) user.forceMove(target_move) user.client.eye = target_move //Byond only updates the eye every tick, This smooths out the movement if(world.time - user.last_played_vent > VENT_SOUND_DELAY) user.last_played_vent = world.time playsound(src, 'sound/machines/ventcrawl.ogg', 50, 1, -3) else if(is_type_in_typecache(src, GLOB.ventcrawl_machinery) && can_crawl_through()) //if we move in a way the pipe can connect, but doesn't - or we're in a vent user.forceMove(loc) user.visible_message("You hear something squeezing through the ducts...","You climb out the ventilation system.") user.canmove = FALSE addtimer(VARSET_CALLBACK(user, canmove, TRUE), 1) /obj/machinery/atmospherics/AltClick(mob/living/L) if(is_type_in_list(src, GLOB.ventcrawl_machinery)) L.handle_ventcrawl(src) return ..() /obj/machinery/atmospherics/proc/can_crawl_through() return TRUE /obj/machinery/atmospherics/proc/returnPipenets() return list() /obj/machinery/atmospherics/update_remote_sight(mob/user) // if(isborer(user)) // user.sight |= (SEE_PIXELS) user.sight |= (SEE_TURFS|BLIND) //Used for certain children of obj/machinery/atmospherics to not show pipe vision when mob is inside it. /obj/machinery/atmospherics/proc/can_see_pipes() return TRUE