diff --git a/byond-extools.dll b/byond-extools.dll
new file mode 100644
index 00000000..a83ea1e5
Binary files /dev/null and b/byond-extools.dll differ
diff --git a/code/__DEFINES/_tick.dm b/code/__DEFINES/_tick.dm
index 10b815e5..fc6147c4 100644
--- a/code/__DEFINES/_tick.dm
+++ b/code/__DEFINES/_tick.dm
@@ -1,13 +1,28 @@
-#define TICK_LIMIT_RUNNING 80
+/// Percentage of tick to leave for master controller to run
+#define MAPTICK_MC_MIN_RESERVE 70
+/// internal_tick_usage is updated every tick by extools
+#define MAPTICK_LAST_INTERNAL_TICK_USAGE ((GLOB.internal_tick_usage / world.tick_lag) * 100)
+/// Tick limit while running normally
+#define TICK_BYOND_RESERVE 2
+#define TICK_LIMIT_RUNNING (max(100 - TICK_BYOND_RESERVE - MAPTICK_LAST_INTERNAL_TICK_USAGE, MAPTICK_MC_MIN_RESERVE))
+/// Tick limit used to resume things in stoplag
#define TICK_LIMIT_TO_RUN 70
+/// Tick limit for MC while running
#define TICK_LIMIT_MC 70
-#define TICK_LIMIT_MC_INIT_DEFAULT 98
+/// Tick limit while initializing
+#define TICK_LIMIT_MC_INIT_DEFAULT (100 - TICK_BYOND_RESERVE)
-#define TICK_USAGE world.tick_usage //for general usage
-#define TICK_USAGE_REAL world.tick_usage //to be used where the result isn't checked
+/// for general usage of tick_usage
+#define TICK_USAGE world.tick_usage
+/// to be used where the result isn't checked
+#define TICK_USAGE_REAL world.tick_usage
+/// Returns true if tick_usage is above the limit
#define TICK_CHECK ( TICK_USAGE > Master.current_ticklimit )
+/// runs stoplag if tick_usage is above the limit
#define CHECK_TICK ( TICK_CHECK ? stoplag() : 0 )
+/// Returns true if tick usage is above 95, for high priority usage
#define TICK_CHECK_HIGH_PRIORITY ( TICK_USAGE > 95 )
+/// runs stoplag if tick_usage is above 95, for high priority usage
#define CHECK_TICK_HIGH_PRIORITY ( TICK_CHECK_HIGH_PRIORITY? stoplag() : 0 )
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index f2ef5638..a45cf49b 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -131,8 +131,15 @@
#define RUNLEVELS_DEFAULT (RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME)
-
-
+// SSair run section
+#define SSAIR_PIPENETS 1
+#define SSAIR_ATMOSMACHINERY 2
+#define SSAIR_REACTQUEUE 3
+#define SSAIR_EXCITEDGROUPS 4
+#define SSAIR_HIGHPRESSURE 5
+#define SSAIR_HOTSPOTS 6
+#define SSAIR_SUPERCONDUCTIVITY 7
+#define SSAIR_REBUILD_PIPENETS 8
#define COMPILE_OVERLAYS(A)\
if (TRUE) {\
diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm
index 38147262..3d4dbf01 100644
--- a/code/_globalvars/misc.dm
+++ b/code/_globalvars/misc.dm
@@ -27,3 +27,5 @@ GLOBAL_VAR(bible_icon_state)
GLOBAL_VAR(bible_item_state)
GLOBAL_VAR(holy_weapon_type)
GLOBAL_VAR(holy_armor_type)
+
+GLOBAL_VAR_INIT(internal_tick_usage, 0.2 * world.tick_lag)
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index 76a3b1eb..70acff1c 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -35,6 +35,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/sleep_delta = 1
+ ///Only run ticker subsystems for the next n ticks.
+ var/skip_ticks = 0
+
var/make_runtime = 0
var/initializations_finished_with_no_players_logged_in //I wonder what this could be?
@@ -334,7 +337,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
new/datum/controller/failsafe() // (re)Start the failsafe.
//now do the actual stuff
- if (!queue_head || !(iteration % 3))
+ if (!skip_ticks)
var/checking_runlevel = current_runlevel
if(cached_runlevel != checking_runlevel)
//resechedule subsystems
@@ -380,6 +383,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
iteration++
last_run = world.time
+ if (skip_ticks)
+ skip_ticks--
src.sleep_delta = MC_AVERAGE_FAST(src.sleep_delta, sleep_delta)
current_ticklimit = TICK_LIMIT_RUNNING
if (processing * sleep_delta <= world.tick_lag)
@@ -443,10 +448,12 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
while (queue_node)
if (ran && TICK_USAGE > TICK_LIMIT_RUNNING)
break
-
queue_node_flags = queue_node.flags
queue_node_priority = queue_node.queued_priority
+ if (!(queue_node_flags & SS_TICKER) && skip_ticks)
+ queue_node = queue_node.queue_next
+ continue
//super special case, subsystems where we can't make them pause mid way through
//if we can't run them this tick (without going over a tick)
//we bump up their priority and attempt to run them next tick
@@ -454,14 +461,15 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
// in those cases, so we just let them run)
if (queue_node_flags & SS_NO_TICK_CHECK)
if (queue_node.tick_usage > TICK_LIMIT_RUNNING - TICK_USAGE && ran_non_ticker)
- queue_node.queued_priority += queue_priority_count * 0.1
- queue_priority_count -= queue_node_priority
- queue_priority_count += queue_node.queued_priority
- current_tick_budget -= queue_node_priority
- queue_node = queue_node.queue_next
+ if (!(queue_node_flags & SS_BACKGROUND))
+ queue_node.queued_priority += queue_priority_count * 0.1
+ queue_priority_count -= queue_node_priority
+ queue_priority_count += queue_node.queued_priority
+ current_tick_budget -= queue_node_priority
+ queue_node = queue_node.queue_next
continue
- if ((queue_node_flags & SS_BACKGROUND) && !bg_calc)
+ if (!bg_calc && (queue_node_flags & SS_BACKGROUND))
current_tick_budget = queue_priority_count_bg
bg_calc = TRUE
@@ -514,7 +522,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
queue_node.paused_ticks = 0
queue_node.paused_tick_usage = 0
- if (queue_node_flags & SS_BACKGROUND) //update our running total
+ if (bg_calc) //update our running total
queue_priority_count_bg -= queue_node_priority
else
queue_priority_count -= queue_node_priority
@@ -582,6 +590,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
log_world("MC: SoftReset: Finished.")
. = 1
+/// Warns us that the end of tick byond map_update will be laggier then normal, so that we can just skip running subsystems this tick.
+/datum/controller/master/proc/laggy_byond_map_update_incoming()
+ if (!skip_ticks)
+ skip_ticks = 1
/datum/controller/master/stat_entry(msg)
diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm
index 853dae5e..6100601e 100644
--- a/code/controllers/subsystem/air.dm
+++ b/code/controllers/subsystem/air.dm
@@ -1,11 +1,3 @@
-#define SSAIR_PIPENETS 1
-#define SSAIR_ATMOSMACHINERY 2
-#define SSAIR_REACTQUEUE 3
-#define SSAIR_EXCITEDGROUPS 4
-#define SSAIR_HIGHPRESSURE 5
-#define SSAIR_HOTSPOTS 6
-#define SSAIR_SUPERCONDUCTIVITY 7
-
SUBSYSTEM_DEF(air)
name = "Atmospherics"
init_order = INIT_ORDER_AIR
@@ -20,6 +12,7 @@ SUBSYSTEM_DEF(air)
var/cost_hotspots = 0
var/cost_superconductivity = 0
var/cost_pipenets = 0
+ var/cost_rebuilds = 0
var/cost_atmos_machinery = 0
var/list/excited_groups = list()
@@ -27,6 +20,7 @@ SUBSYSTEM_DEF(air)
var/list/turf_react_queue = list()
var/list/hotspots = list()
var/list/networks = list()
+ var/list/pipenets_needing_rebuilt = list()
var/list/obj/machinery/atmos_machinery = list()
var/list/pipe_init_dirs_cache = list()
@@ -39,7 +33,7 @@ SUBSYSTEM_DEF(air)
var/list/currentrun = list()
- var/currentpart = SSAIR_PIPENETS
+ var/currentpart = SSAIR_REBUILD_PIPENETS
var/map_loading = TRUE
var/list/queued_for_activation
@@ -52,6 +46,7 @@ SUBSYSTEM_DEF(air)
msg += "HS:[round(cost_hotspots,1)]|"
msg += "SC:[round(cost_superconductivity,1)]|"
msg += "PN:[round(cost_pipenets,1)]|"
+ msg += "RB:[round(cost_rebuilds,1)]|"
msg += "AM:[round(cost_atmos_machinery,1)]"
msg += "} "
msg += "AT:[active_turfs.len]|"
@@ -77,6 +72,18 @@ SUBSYSTEM_DEF(air)
/datum/controller/subsystem/air/fire(resumed = 0)
var/timer = TICK_USAGE_REAL
+ if(currentpart == SSAIR_REBUILD_PIPENETS)
+ var/list/pipenet_rebuilds = pipenets_needing_rebuilt
+ for(var/thing in pipenet_rebuilds)
+ var/obj/machinery/atmospherics/AT = thing
+ AT.build_network()
+ cost_rebuilds = MC_AVERAGE(cost_rebuilds, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+ pipenets_needing_rebuilt.Cut()
+ if(state != SS_RUNNING)
+ return
+ resumed = FALSE
+ currentpart = SSAIR_PIPENETS
+
if(currentpart == SSAIR_PIPENETS || !resumed)
process_pipenets(resumed)
cost_pipenets = MC_AVERAGE(cost_pipenets, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
@@ -137,9 +144,7 @@ SUBSYSTEM_DEF(air)
if(state != SS_RUNNING)
return
resumed = 0
- currentpart = SSAIR_PIPENETS
-
-
+ currentpart = SSAIR_REBUILD_PIPENETS
/datum/controller/subsystem/air/proc/process_pipenets(resumed = 0)
if (!resumed)
@@ -156,6 +161,9 @@ SUBSYSTEM_DEF(air)
if(MC_TICK_CHECK)
return
+/datum/controller/subsystem/air/proc/add_to_rebuild_queue(atmos_machine)
+ if(istype(atmos_machine, /obj/machinery/atmospherics))
+ pipenets_needing_rebuilt += atmos_machine
/datum/controller/subsystem/air/proc/process_atmos_machinery(resumed = 0)
var/seconds = wait * 0.1
diff --git a/code/game/world.dm b/code/game/world.dm
index 5bd39779..f3c6611c 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -7,9 +7,8 @@ GLOBAL_VAR(restart_counter)
//This happens after the Master subsystem new(s) (it's a global datum)
//So subsystems globals exist, but are not initialised
/world/New()
- var/extools = world.GetConfig("env", "EXTOOLS_DLL") || (world.system_type == MS_WINDOWS ? "./byond-extools.dll" : "./libbyond-extools.so")
- if (fexists(extools))
- call(extools, "maptick_initialize")()
+ if(fexists("byond-extools.dll"))
+ call("byond-extools.dll", "maptick_initialize")()
enable_debugger()
log_world("World loaded at [TIME_STAMP("hh:mm:ss", FALSE)]!")
diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm
index f6bbe7b3..aa74e6dc 100644
--- a/code/modules/atmospherics/machinery/atmosmachinery.dm
+++ b/code/modules/atmospherics/machinery/atmosmachinery.dm
@@ -1,341 +1,342 @@
-// 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
- move_resist = INFINITY //Moving a connected machine without actually doing the normal (dis)connection things will probably cause a LOT of issues.
- 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
- var/on = FALSE
-
-/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)
- . += "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()
- switch(dir)
- if(SOUTH)
- setDir(NORTH)
- if(WEST)
- setDir(EAST)
-
-//this is called just after the air controller sets up turfs
-/obj/machinery/atmospherics/proc/atmosinit(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)
- piping_layer = (pipe_flags & PIPING_DEFAULT_LAYER_ONLY) ? PIPING_LAYER_DEFAULT : new_layer
- update_icon()
-
-/obj/machinery/atmospherics/proc/can_be_node(obj/machinery/atmospherics/target, iteration)
- 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/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
- else
- return ..()
-
-/obj/machinery/atmospherics/wrench_act(mob/living/user, obj/item/I)
- if(!can_unwrench(user))
- return ..()
-
- 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()
-
- 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(I.use_tool(src, user, 20, volume=50))
- 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)
- return TRUE
-
-/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()
-
- user.visible_message("[user] is sent flying by pressure!","The pressure sends you flying!")
-
- // if get_dir(src, user) is not 0, target is the edge_target_turf on that dir
- // otherwise, edge_target_turf uses a random cardinal direction
- // range is pressures / 250
- // speed is pressures / 1250
- user.throw_at(get_edge_target_turf(user, get_dir(src, user) || pick(GLOB.cardinals)), pressures / 250, pressures / 1250)
-
-/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), piping_layer=2)
-
- //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]_[piping_layer]"
-
- if((!(. = pipeimages[identifier])))
- var/image/pipe_overlay
- pipe_overlay = . = pipeimages[identifier] = image(iconset, iconstate, dir = direction)
- pipe_overlay.color = col
- PIPING_LAYER_SHIFT(pipe_overlay, piping_layer)
-
-/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_typecache(src, GLOB.ventcrawl_machinery))
- return 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)
- 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
-
-/obj/machinery/atmospherics/proc/update_layer()
+// 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
+ move_resist = INFINITY //Moving a connected machine without actually doing the normal (dis)connection things will probably cause a LOT of issues.
+ 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
+ var/on = FALSE
+
+/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)
+ . += "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
+ SSair.pipenets_needing_rebuilt -= 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()
+ switch(dir)
+ if(SOUTH)
+ setDir(NORTH)
+ if(WEST)
+ setDir(EAST)
+
+//this is called just after the air controller sets up turfs
+/obj/machinery/atmospherics/proc/atmosinit(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)
+ piping_layer = (pipe_flags & PIPING_DEFAULT_LAYER_ONLY) ? PIPING_LAYER_DEFAULT : new_layer
+ update_icon()
+
+/obj/machinery/atmospherics/proc/can_be_node(obj/machinery/atmospherics/target, iteration)
+ 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/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
+ else
+ return ..()
+
+/obj/machinery/atmospherics/wrench_act(mob/living/user, obj/item/I)
+ if(!can_unwrench(user))
+ return ..()
+
+ 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()
+
+ 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(I.use_tool(src, user, 20, volume=50))
+ 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)
+ return TRUE
+
+/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()
+
+ user.visible_message("[user] is sent flying by pressure!","The pressure sends you flying!")
+
+ // if get_dir(src, user) is not 0, target is the edge_target_turf on that dir
+ // otherwise, edge_target_turf uses a random cardinal direction
+ // range is pressures / 250
+ // speed is pressures / 1250
+ user.throw_at(get_edge_target_turf(user, get_dir(src, user) || pick(GLOB.cardinals)), pressures / 250, pressures / 1250)
+
+/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), piping_layer=2)
+
+ //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]_[piping_layer]"
+
+ if((!(. = pipeimages[identifier])))
+ var/image/pipe_overlay
+ pipe_overlay = . = pipeimages[identifier] = image(iconset, iconstate, dir = direction)
+ pipe_overlay.color = col
+ PIPING_LAYER_SHIFT(pipe_overlay, piping_layer)
+
+/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_typecache(src, GLOB.ventcrawl_machinery))
+ return 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)
+ 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
+
+/obj/machinery/atmospherics/proc/update_layer()
layer = initial(layer) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE
\ No newline at end of file
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
index 9fd0850f..bf0f221b 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
@@ -119,7 +119,7 @@
if(node2)
node2.atmosinit()
node2.addMember(src)
- build_network()
+ SSair.add_to_rebuild_queue(src)
return TRUE
diff --git a/code/modules/atmospherics/machinery/components/components_base.dm b/code/modules/atmospherics/machinery/components/components_base.dm
index ff2a655a..3defa76a 100644
--- a/code/modules/atmospherics/machinery/components/components_base.dm
+++ b/code/modules/atmospherics/machinery/components/components_base.dm
@@ -144,8 +144,8 @@
for(var/i in 1 to device_type)
var/datum/pipeline/parent = parents[i]
if(!parent)
- throw EXCEPTION("Component is missing a pipenet! Rebuilding...")
- build_network()
+ stack_trace("Component is missing a pipenet! Rebuilding...")
+ SSair.add_to_rebuild_queue(src)
parent.update = 1
/obj/machinery/atmospherics/components/returnPipenets()
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
index 0a54503b..766b7a24 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
@@ -1,446 +1,446 @@
-/obj/machinery/atmospherics/components/unary/cryo_cell
- name = "cryo cell"
- icon = 'icons/obj/cryogenics.dmi'
- icon_state = "pod-off"
- density = TRUE
- max_integrity = 350
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 30, "acid" = 30)
- layer = ABOVE_WINDOW_LAYER
- state_open = FALSE
- circuit = /obj/item/circuitboard/machine/cryo_tube
- pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY
- occupant_typecache = list(/mob/living/carbon, /mob/living/simple_animal)
-
- var/autoeject = FALSE
- var/volume = 100
-
- var/efficiency = 1
- var/sleep_factor = 0.00125
- var/unconscious_factor = 0.001
- var/heat_capacity = 20000
- var/conduction_coefficient = 0.3
-
- var/obj/item/reagent_containers/glass/beaker = null
- var/reagent_transfer = 0
-
- var/obj/item/radio/radio
- var/radio_key = /obj/item/encryptionkey/headset_med
- var/radio_channel = RADIO_CHANNEL_MEDICAL
-
- var/running_anim = FALSE
-
- var/escape_in_progress = FALSE
- var/message_cooldown
- var/breakout_time = 300
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/Initialize()
- . = ..()
- initialize_directions = dir
-
- radio = new(src)
- radio.keyslot = new radio_key
- radio.subspace_transmission = TRUE
- radio.canhear_range = 0
- radio.recalculateChannels()
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/on_construction()
- ..(dir, dir)
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/RefreshParts()
- var/C
- for(var/obj/item/stock_parts/matter_bin/M in component_parts)
- C += M.rating
-
- efficiency = initial(efficiency) * C
- sleep_factor = initial(sleep_factor) * C
- unconscious_factor = initial(unconscious_factor) * C
- heat_capacity = initial(heat_capacity) / C
- conduction_coefficient = initial(conduction_coefficient) * C
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/Destroy()
- QDEL_NULL(radio)
- QDEL_NULL(beaker)
- return ..()
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/contents_explosion(severity, target)
- ..()
- if(beaker)
- beaker.ex_act(severity, target)
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/handle_atom_del(atom/A)
- ..()
- if(A == beaker)
- beaker = null
- updateUsrDialog()
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/on_deconstruction()
- if(beaker)
- beaker.forceMove(drop_location())
- beaker = null
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/update_icon()
- cut_overlays()
-
- if(panel_open)
- add_overlay("pod-panel")
-
- if(state_open)
- icon_state = "pod-open"
- return
-
- if(occupant)
- var/image/occupant_overlay
-
- if(ismonkey(occupant)) // Monkey
- occupant_overlay = image(CRYOMOBS, "monkey")
- else if(isalienadult(occupant))
- if(isalienroyal(occupant)) // Queen and prae
- occupant_overlay = image(CRYOMOBS, "alienq")
- else if(isalienhunter(occupant)) // Hunter
- occupant_overlay = image(CRYOMOBS, "alienh")
- else if(isaliensentinel(occupant)) // Sentinel
- occupant_overlay = image(CRYOMOBS, "aliens")
- else // Drone or other
- occupant_overlay = image(CRYOMOBS, "aliend")
-
- else if(ishuman(occupant) || islarva(occupant) || (isanimal(occupant) && !ismegafauna(occupant))) // Mobs that are smaller than cryotube
- occupant_overlay = image(occupant.icon, occupant.icon_state)
- occupant_overlay.copy_overlays(occupant)
-
- else
- occupant_overlay = image(CRYOMOBS, "generic")
-
- occupant_overlay.dir = SOUTH
- occupant_overlay.pixel_y = 22
-
- if(on && !running_anim && is_operational())
- icon_state = "pod-on"
- running_anim = TRUE
- run_anim(TRUE, occupant_overlay)
- else
- icon_state = "pod-off"
- add_overlay(occupant_overlay)
- add_overlay("cover-off")
-
- else if(on && is_operational())
- icon_state = "pod-on"
- add_overlay("cover-on")
- else
- icon_state = "pod-off"
- add_overlay("cover-off")
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/proc/run_anim(anim_up, image/occupant_overlay)
- if(!on || !occupant || !is_operational())
- running_anim = FALSE
- return
- cut_overlays()
- if(occupant_overlay.pixel_y != 23) // Same effect as occupant_overlay.pixel_y == 22 || occupant_overlay.pixel_y == 24
- anim_up = occupant_overlay.pixel_y == 22 // Same effect as if(occupant_overlay.pixel_y == 22) anim_up = TRUE ; if(occupant_overlay.pixel_y == 24) anim_up = FALSE
- if(anim_up)
- occupant_overlay.pixel_y++
- else
- occupant_overlay.pixel_y--
- add_overlay(occupant_overlay)
- add_overlay("cover-on")
- addtimer(CALLBACK(src, .proc/run_anim, anim_up, occupant_overlay), 7, TIMER_UNIQUE)
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/process()
- ..()
-
- if(!on)
- return
- if(!is_operational())
- on = FALSE
- update_icon()
- return
- if(!occupant)
- return
-
- var/mob/living/mob_occupant = occupant
-
- if(mob_occupant.stat == DEAD) // We don't bother with dead people.
- return
-
- if(mob_occupant.health >= mob_occupant.getMaxHealth()) // Don't bother with fully healed people.
- on = FALSE
- update_icon()
- playsound(src, 'sound/machines/cryo_warning.ogg', volume) // Bug the doctors.
- var/msg = "Patient fully restored."
- if(autoeject) // Eject if configured.
- msg += " Auto ejecting patient now."
- open_machine()
- radio.talk_into(src, msg, radio_channel)
- return
-
- var/datum/gas_mixture/air1 = airs[1]
-
- if(air1.gases.len)
- if(mob_occupant.bodytemperature < T0C) // Sleepytime. Why? More cryo magic.
- mob_occupant.Sleeping((mob_occupant.bodytemperature * sleep_factor) * 2000)
- mob_occupant.Unconscious((mob_occupant.bodytemperature * unconscious_factor) * 2000)
- if(beaker)
- if(reagent_transfer == 0) // Magically transfer reagents. Because cryo magic.
- beaker.reagents.trans_to(occupant, 1, efficiency * 0.25) // Transfer reagents.
- beaker.reagents.reaction(occupant, VAPOR)
- air1.gases[/datum/gas/oxygen] -= max(0,air1.gases[/datum/gas/oxygen] - 2 / efficiency) //Let's use gas for this
- GAS_GARBAGE_COLLECT(air1.gases)
- if(++reagent_transfer >= 10 * efficiency) // Throttle reagent transfer (higher efficiency will transfer the same amount but consume less from the beaker).
- reagent_transfer = 0
-
- return 1
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/process_atmos()
- ..()
-
- if(!on)
- return
-
- var/datum/gas_mixture/air1 = airs[1]
-
- if(!nodes[1] || !airs[1] || !air1.gases.len || air1.gases[/datum/gas/oxygen] < 5) // Turn off if the machine won't work.
- on = FALSE
- update_icon()
- return
-
- if(occupant)
- var/mob/living/mob_occupant = occupant
- var/cold_protection = 0
- var/temperature_delta = air1.temperature - mob_occupant.bodytemperature // The only semi-realistic thing here: share temperature between the cell and the occupant.
-
- if(ishuman(occupant))
- var/mob/living/carbon/human/H = occupant
- cold_protection = H.get_thermal_protection(air1.temperature, TRUE)
-
- if(abs(temperature_delta) > 1)
- var/air_heat_capacity = air1.heat_capacity()
-
- var/heat = ((1 - cold_protection) * 0.1 + conduction_coefficient) * temperature_delta * (air_heat_capacity * heat_capacity / (air_heat_capacity + heat_capacity))
-
- air1.temperature = max(air1.temperature - heat / air_heat_capacity, TCMB)
- mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB)
-
- air1.gases[/datum/gas/oxygen] = max(0,air1.gases[/datum/gas/oxygen] - 0.5 / efficiency) // Magically consume gas? Why not, we run on cryo magic.
- GAS_GARBAGE_COLLECT(air1.gases)
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/power_change()
- ..()
- update_icon()
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/relaymove(mob/user)
- if(message_cooldown <= world.time)
- message_cooldown = world.time + 50
- to_chat(user, "[src]'s door won't budge!")
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/open_machine(drop = 0)
- if(!state_open && !panel_open)
- on = FALSE
- ..()
- for(var/mob/M in contents) //only drop mobs
- M.forceMove(get_turf(src))
- if(isliving(M))
- var/mob/living/L = M
- L.update_canmove()
- occupant = null
- update_icon()
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/close_machine(mob/living/carbon/user)
- if((isnull(user) || istype(user)) && state_open && !panel_open)
- ..(user)
- return occupant
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/container_resist(mob/living/user)
- user.changeNext_move(CLICK_CD_BREAKOUT)
- user.last_special = world.time + CLICK_CD_BREAKOUT
- user.visible_message("You see [user] kicking against the glass of [src]!", \
- "You struggle inside [src], kicking the release with your foot... (this will take about [DisplayTimeText(breakout_time)].)", \
- "You hear a thump from [src].")
- if(do_after(user, breakout_time, target = src))
- if(!user || user.stat != CONSCIOUS || user.loc != src )
- return
- user.visible_message("[user] successfully broke out of [src]!", \
- "You successfully break out of [src]!")
- open_machine()
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user)
- . = ..()
- if(occupant)
- if(on)
- . += "Someone's inside [src]!"
- else
- . += "You can barely make out a form floating in [src]."
- else
- . += "[src] seems empty."
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/MouseDrop_T(mob/target, mob/user)
- if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
- return
- if (target.IsKnockdown() || target.IsStun() || target.IsSleeping() || target.IsUnconscious())
- close_machine(target)
- else
- user.visible_message("[user] starts shoving [target] inside [src].", "You start shoving [target] inside [src].")
- if (do_after(user, 25, target=target))
- close_machine(target)
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/reagent_containers/glass))
- . = 1 //no afterattack
- if(beaker)
- to_chat(user, "A beaker is already loaded into [src]!")
- return
- if(!user.transferItemToLoc(I, src))
- return
- beaker = I
- user.visible_message("[user] places [I] in [src].", \
- "You place [I] in [src].")
- var/reagentlist = pretty_string_from_reagent_list(I.reagents.reagent_list)
- log_game("[key_name(user)] added an [I] to cryo containing [reagentlist]")
- return
- if(!on && !occupant && !state_open && (default_deconstruction_screwdriver(user, "pod-off", "pod-off", I)) \
- || default_change_direction_wrench(user, I) \
- || default_pry_open(I) \
- || default_deconstruction_crowbar(I))
- update_icon()
- return
- else if(istype(I, /obj/item/screwdriver))
- to_chat(user, "You can't access the maintenance panel while the pod is " \
- + (on ? "active" : (occupant ? "full" : "open")) + ".")
- return
- return ..()
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "cryo", name, 400, 550, master_ui, state)
- ui.open()
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/ui_data()
- var/list/data = list()
- data["isOperating"] = on
- data["hasOccupant"] = occupant ? TRUE : FALSE
- data["isOpen"] = state_open
- data["autoEject"] = autoeject
-
- data["occupant"] = list()
- if(occupant)
- var/mob/living/mob_occupant = occupant
- data["occupant"]["name"] = mob_occupant.name
- switch(mob_occupant.stat)
- if(CONSCIOUS)
- data["occupant"]["stat"] = "Conscious"
- data["occupant"]["statstate"] = "good"
- if(SOFT_CRIT)
- data["occupant"]["stat"] = "Conscious"
- data["occupant"]["statstate"] = "average"
- if(UNCONSCIOUS)
- data["occupant"]["stat"] = "Unconscious"
- data["occupant"]["statstate"] = "average"
- if(DEAD)
- data["occupant"]["stat"] = "Dead"
- data["occupant"]["statstate"] = "bad"
- data["occupant"]["health"] = round(mob_occupant.health, 1)
- data["occupant"]["maxHealth"] = mob_occupant.maxHealth
- data["occupant"]["minHealth"] = HEALTH_THRESHOLD_DEAD
- data["occupant"]["bruteLoss"] = round(mob_occupant.getBruteLoss(), 1)
- data["occupant"]["oxyLoss"] = round(mob_occupant.getOxyLoss(), 1)
- data["occupant"]["toxLoss"] = round(mob_occupant.getToxLoss(), 1)
- data["occupant"]["fireLoss"] = round(mob_occupant.getFireLoss(), 1)
- data["occupant"]["bodyTemperature"] = round(mob_occupant.bodytemperature, 1)
- if(mob_occupant.bodytemperature < TCRYO)
- data["occupant"]["temperaturestatus"] = "good"
- else if(mob_occupant.bodytemperature < T0C)
- data["occupant"]["temperaturestatus"] = "average"
- else
- data["occupant"]["temperaturestatus"] = "bad"
-
- var/datum/gas_mixture/air1 = airs[1]
- data["cellTemperature"] = round(air1.temperature, 1)
-
- data["isBeakerLoaded"] = beaker ? TRUE : FALSE
- var/beakerContents = list()
- if(beaker && beaker.reagents && beaker.reagents.reagent_list.len)
- for(var/datum/reagent/R in beaker.reagents.reagent_list)
- beakerContents += list(list("name" = R.name, "volume" = R.volume))
- data["beakerContents"] = beakerContents
- return data
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/ui_act(action, params)
- if(..())
- return
- switch(action)
- if("power")
- if(on)
- on = FALSE
- else if(!state_open)
- on = TRUE
- . = TRUE
- if("door")
- if(state_open)
- close_machine()
- else
- open_machine()
- . = TRUE
- if("autoeject")
- autoeject = !autoeject
- . = TRUE
- if("ejectbeaker")
- if(beaker)
- beaker.forceMove(drop_location())
- if(Adjacent(usr) && !issilicon(usr))
- usr.put_in_hands(beaker)
- beaker = null
- . = TRUE
- update_icon()
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user)
- if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !state_open)
- on = !on
- update_icon()
- return ..()
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/AltClick(mob/user)
- . = ..()
- if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
- if(state_open)
- close_machine()
- else
- open_machine()
- update_icon()
- return TRUE
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/update_remote_sight(mob/living/user)
- return // we don't see the pipe network while inside cryo.
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/get_remote_view_fullscreens(mob/user)
- user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1)
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/can_crawl_through()
- return // can't ventcrawl in or out of cryo.
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/can_see_pipes()
- return 0 // you can't see the pipe network when inside a cryo cell.
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/return_temperature()
- var/datum/gas_mixture/G = airs[1]
-
- if(G.total_moles() > 10)
- return G.temperature
- return ..()
-
-/obj/machinery/atmospherics/components/unary/cryo_cell/default_change_direction_wrench(mob/user, obj/item/wrench/W)
- . = ..()
- if(.)
- SetInitDirections()
- var/obj/machinery/atmospherics/node = nodes[1]
- if(node)
- node.disconnect(src)
- nodes[1] = null
- nullifyPipenet(parents[1])
- atmosinit()
- node = nodes[1]
- if(node)
- node.atmosinit()
- node.addMember(src)
- build_network()
-
-#undef CRYOMOBS
+/obj/machinery/atmospherics/components/unary/cryo_cell
+ name = "cryo cell"
+ icon = 'icons/obj/cryogenics.dmi'
+ icon_state = "pod-off"
+ density = TRUE
+ max_integrity = 350
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 30, "acid" = 30)
+ layer = ABOVE_WINDOW_LAYER
+ state_open = FALSE
+ circuit = /obj/item/circuitboard/machine/cryo_tube
+ pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY
+ occupant_typecache = list(/mob/living/carbon, /mob/living/simple_animal)
+
+ var/autoeject = FALSE
+ var/volume = 100
+
+ var/efficiency = 1
+ var/sleep_factor = 0.00125
+ var/unconscious_factor = 0.001
+ var/heat_capacity = 20000
+ var/conduction_coefficient = 0.3
+
+ var/obj/item/reagent_containers/glass/beaker = null
+ var/reagent_transfer = 0
+
+ var/obj/item/radio/radio
+ var/radio_key = /obj/item/encryptionkey/headset_med
+ var/radio_channel = RADIO_CHANNEL_MEDICAL
+
+ var/running_anim = FALSE
+
+ var/escape_in_progress = FALSE
+ var/message_cooldown
+ var/breakout_time = 300
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/Initialize()
+ . = ..()
+ initialize_directions = dir
+
+ radio = new(src)
+ radio.keyslot = new radio_key
+ radio.subspace_transmission = TRUE
+ radio.canhear_range = 0
+ radio.recalculateChannels()
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/on_construction()
+ ..(dir, dir)
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/RefreshParts()
+ var/C
+ for(var/obj/item/stock_parts/matter_bin/M in component_parts)
+ C += M.rating
+
+ efficiency = initial(efficiency) * C
+ sleep_factor = initial(sleep_factor) * C
+ unconscious_factor = initial(unconscious_factor) * C
+ heat_capacity = initial(heat_capacity) / C
+ conduction_coefficient = initial(conduction_coefficient) * C
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/Destroy()
+ QDEL_NULL(radio)
+ QDEL_NULL(beaker)
+ return ..()
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/contents_explosion(severity, target)
+ ..()
+ if(beaker)
+ beaker.ex_act(severity, target)
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/handle_atom_del(atom/A)
+ ..()
+ if(A == beaker)
+ beaker = null
+ updateUsrDialog()
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/on_deconstruction()
+ if(beaker)
+ beaker.forceMove(drop_location())
+ beaker = null
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/update_icon()
+ cut_overlays()
+
+ if(panel_open)
+ add_overlay("pod-panel")
+
+ if(state_open)
+ icon_state = "pod-open"
+ return
+
+ if(occupant)
+ var/image/occupant_overlay
+
+ if(ismonkey(occupant)) // Monkey
+ occupant_overlay = image(CRYOMOBS, "monkey")
+ else if(isalienadult(occupant))
+ if(isalienroyal(occupant)) // Queen and prae
+ occupant_overlay = image(CRYOMOBS, "alienq")
+ else if(isalienhunter(occupant)) // Hunter
+ occupant_overlay = image(CRYOMOBS, "alienh")
+ else if(isaliensentinel(occupant)) // Sentinel
+ occupant_overlay = image(CRYOMOBS, "aliens")
+ else // Drone or other
+ occupant_overlay = image(CRYOMOBS, "aliend")
+
+ else if(ishuman(occupant) || islarva(occupant) || (isanimal(occupant) && !ismegafauna(occupant))) // Mobs that are smaller than cryotube
+ occupant_overlay = image(occupant.icon, occupant.icon_state)
+ occupant_overlay.copy_overlays(occupant)
+
+ else
+ occupant_overlay = image(CRYOMOBS, "generic")
+
+ occupant_overlay.dir = SOUTH
+ occupant_overlay.pixel_y = 22
+
+ if(on && !running_anim && is_operational())
+ icon_state = "pod-on"
+ running_anim = TRUE
+ run_anim(TRUE, occupant_overlay)
+ else
+ icon_state = "pod-off"
+ add_overlay(occupant_overlay)
+ add_overlay("cover-off")
+
+ else if(on && is_operational())
+ icon_state = "pod-on"
+ add_overlay("cover-on")
+ else
+ icon_state = "pod-off"
+ add_overlay("cover-off")
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/proc/run_anim(anim_up, image/occupant_overlay)
+ if(!on || !occupant || !is_operational())
+ running_anim = FALSE
+ return
+ cut_overlays()
+ if(occupant_overlay.pixel_y != 23) // Same effect as occupant_overlay.pixel_y == 22 || occupant_overlay.pixel_y == 24
+ anim_up = occupant_overlay.pixel_y == 22 // Same effect as if(occupant_overlay.pixel_y == 22) anim_up = TRUE ; if(occupant_overlay.pixel_y == 24) anim_up = FALSE
+ if(anim_up)
+ occupant_overlay.pixel_y++
+ else
+ occupant_overlay.pixel_y--
+ add_overlay(occupant_overlay)
+ add_overlay("cover-on")
+ addtimer(CALLBACK(src, .proc/run_anim, anim_up, occupant_overlay), 7, TIMER_UNIQUE)
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/process()
+ ..()
+
+ if(!on)
+ return
+ if(!is_operational())
+ on = FALSE
+ update_icon()
+ return
+ if(!occupant)
+ return
+
+ var/mob/living/mob_occupant = occupant
+
+ if(mob_occupant.stat == DEAD) // We don't bother with dead people.
+ return
+
+ if(mob_occupant.health >= mob_occupant.getMaxHealth()) // Don't bother with fully healed people.
+ on = FALSE
+ update_icon()
+ playsound(src, 'sound/machines/cryo_warning.ogg', volume) // Bug the doctors.
+ var/msg = "Patient fully restored."
+ if(autoeject) // Eject if configured.
+ msg += " Auto ejecting patient now."
+ open_machine()
+ radio.talk_into(src, msg, radio_channel)
+ return
+
+ var/datum/gas_mixture/air1 = airs[1]
+
+ if(air1.gases.len)
+ if(mob_occupant.bodytemperature < T0C) // Sleepytime. Why? More cryo magic.
+ mob_occupant.Sleeping((mob_occupant.bodytemperature * sleep_factor) * 2000)
+ mob_occupant.Unconscious((mob_occupant.bodytemperature * unconscious_factor) * 2000)
+ if(beaker)
+ if(reagent_transfer == 0) // Magically transfer reagents. Because cryo magic.
+ beaker.reagents.trans_to(occupant, 1, efficiency * 0.25) // Transfer reagents.
+ beaker.reagents.reaction(occupant, VAPOR)
+ air1.gases[/datum/gas/oxygen] -= max(0,air1.gases[/datum/gas/oxygen] - 2 / efficiency) //Let's use gas for this
+ GAS_GARBAGE_COLLECT(air1.gases)
+ if(++reagent_transfer >= 10 * efficiency) // Throttle reagent transfer (higher efficiency will transfer the same amount but consume less from the beaker).
+ reagent_transfer = 0
+
+ return 1
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/process_atmos()
+ ..()
+
+ if(!on)
+ return
+
+ var/datum/gas_mixture/air1 = airs[1]
+
+ if(!nodes[1] || !airs[1] || !air1.gases.len || air1.gases[/datum/gas/oxygen] < 5) // Turn off if the machine won't work.
+ on = FALSE
+ update_icon()
+ return
+
+ if(occupant)
+ var/mob/living/mob_occupant = occupant
+ var/cold_protection = 0
+ var/temperature_delta = air1.temperature - mob_occupant.bodytemperature // The only semi-realistic thing here: share temperature between the cell and the occupant.
+
+ if(ishuman(occupant))
+ var/mob/living/carbon/human/H = occupant
+ cold_protection = H.get_thermal_protection(air1.temperature, TRUE)
+
+ if(abs(temperature_delta) > 1)
+ var/air_heat_capacity = air1.heat_capacity()
+
+ var/heat = ((1 - cold_protection) * 0.1 + conduction_coefficient) * temperature_delta * (air_heat_capacity * heat_capacity / (air_heat_capacity + heat_capacity))
+
+ air1.temperature = max(air1.temperature - heat / air_heat_capacity, TCMB)
+ mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB)
+
+ air1.gases[/datum/gas/oxygen] = max(0,air1.gases[/datum/gas/oxygen] - 0.5 / efficiency) // Magically consume gas? Why not, we run on cryo magic.
+ GAS_GARBAGE_COLLECT(air1.gases)
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/power_change()
+ ..()
+ update_icon()
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/relaymove(mob/user)
+ if(message_cooldown <= world.time)
+ message_cooldown = world.time + 50
+ to_chat(user, "[src]'s door won't budge!")
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/open_machine(drop = 0)
+ if(!state_open && !panel_open)
+ on = FALSE
+ ..()
+ for(var/mob/M in contents) //only drop mobs
+ M.forceMove(get_turf(src))
+ if(isliving(M))
+ var/mob/living/L = M
+ L.update_canmove()
+ occupant = null
+ update_icon()
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/close_machine(mob/living/carbon/user)
+ if((isnull(user) || istype(user)) && state_open && !panel_open)
+ ..(user)
+ return occupant
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/container_resist(mob/living/user)
+ user.changeNext_move(CLICK_CD_BREAKOUT)
+ user.last_special = world.time + CLICK_CD_BREAKOUT
+ user.visible_message("You see [user] kicking against the glass of [src]!", \
+ "You struggle inside [src], kicking the release with your foot... (this will take about [DisplayTimeText(breakout_time)].)", \
+ "You hear a thump from [src].")
+ if(do_after(user, breakout_time, target = src))
+ if(!user || user.stat != CONSCIOUS || user.loc != src )
+ return
+ user.visible_message("[user] successfully broke out of [src]!", \
+ "You successfully break out of [src]!")
+ open_machine()
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user)
+ . = ..()
+ if(occupant)
+ if(on)
+ . += "Someone's inside [src]!"
+ else
+ . += "You can barely make out a form floating in [src]."
+ else
+ . += "[src] seems empty."
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/MouseDrop_T(mob/target, mob/user)
+ if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
+ return
+ if (target.IsKnockdown() || target.IsStun() || target.IsSleeping() || target.IsUnconscious())
+ close_machine(target)
+ else
+ user.visible_message("[user] starts shoving [target] inside [src].", "You start shoving [target] inside [src].")
+ if (do_after(user, 25, target=target))
+ close_machine(target)
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/attackby(obj/item/I, mob/user, params)
+ if(istype(I, /obj/item/reagent_containers/glass))
+ . = 1 //no afterattack
+ if(beaker)
+ to_chat(user, "A beaker is already loaded into [src]!")
+ return
+ if(!user.transferItemToLoc(I, src))
+ return
+ beaker = I
+ user.visible_message("[user] places [I] in [src].", \
+ "You place [I] in [src].")
+ var/reagentlist = pretty_string_from_reagent_list(I.reagents.reagent_list)
+ log_game("[key_name(user)] added an [I] to cryo containing [reagentlist]")
+ return
+ if(!on && !occupant && !state_open && (default_deconstruction_screwdriver(user, "pod-off", "pod-off", I)) \
+ || default_change_direction_wrench(user, I) \
+ || default_pry_open(I) \
+ || default_deconstruction_crowbar(I))
+ update_icon()
+ return
+ else if(istype(I, /obj/item/screwdriver))
+ to_chat(user, "You can't access the maintenance panel while the pod is " \
+ + (on ? "active" : (occupant ? "full" : "open")) + ".")
+ return
+ return ..()
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
+ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "cryo", name, 400, 550, master_ui, state)
+ ui.open()
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/ui_data()
+ var/list/data = list()
+ data["isOperating"] = on
+ data["hasOccupant"] = occupant ? TRUE : FALSE
+ data["isOpen"] = state_open
+ data["autoEject"] = autoeject
+
+ data["occupant"] = list()
+ if(occupant)
+ var/mob/living/mob_occupant = occupant
+ data["occupant"]["name"] = mob_occupant.name
+ switch(mob_occupant.stat)
+ if(CONSCIOUS)
+ data["occupant"]["stat"] = "Conscious"
+ data["occupant"]["statstate"] = "good"
+ if(SOFT_CRIT)
+ data["occupant"]["stat"] = "Conscious"
+ data["occupant"]["statstate"] = "average"
+ if(UNCONSCIOUS)
+ data["occupant"]["stat"] = "Unconscious"
+ data["occupant"]["statstate"] = "average"
+ if(DEAD)
+ data["occupant"]["stat"] = "Dead"
+ data["occupant"]["statstate"] = "bad"
+ data["occupant"]["health"] = round(mob_occupant.health, 1)
+ data["occupant"]["maxHealth"] = mob_occupant.maxHealth
+ data["occupant"]["minHealth"] = HEALTH_THRESHOLD_DEAD
+ data["occupant"]["bruteLoss"] = round(mob_occupant.getBruteLoss(), 1)
+ data["occupant"]["oxyLoss"] = round(mob_occupant.getOxyLoss(), 1)
+ data["occupant"]["toxLoss"] = round(mob_occupant.getToxLoss(), 1)
+ data["occupant"]["fireLoss"] = round(mob_occupant.getFireLoss(), 1)
+ data["occupant"]["bodyTemperature"] = round(mob_occupant.bodytemperature, 1)
+ if(mob_occupant.bodytemperature < TCRYO)
+ data["occupant"]["temperaturestatus"] = "good"
+ else if(mob_occupant.bodytemperature < T0C)
+ data["occupant"]["temperaturestatus"] = "average"
+ else
+ data["occupant"]["temperaturestatus"] = "bad"
+
+ var/datum/gas_mixture/air1 = airs[1]
+ data["cellTemperature"] = round(air1.temperature, 1)
+
+ data["isBeakerLoaded"] = beaker ? TRUE : FALSE
+ var/beakerContents = list()
+ if(beaker && beaker.reagents && beaker.reagents.reagent_list.len)
+ for(var/datum/reagent/R in beaker.reagents.reagent_list)
+ beakerContents += list(list("name" = R.name, "volume" = R.volume))
+ data["beakerContents"] = beakerContents
+ return data
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/ui_act(action, params)
+ if(..())
+ return
+ switch(action)
+ if("power")
+ if(on)
+ on = FALSE
+ else if(!state_open)
+ on = TRUE
+ . = TRUE
+ if("door")
+ if(state_open)
+ close_machine()
+ else
+ open_machine()
+ . = TRUE
+ if("autoeject")
+ autoeject = !autoeject
+ . = TRUE
+ if("ejectbeaker")
+ if(beaker)
+ beaker.forceMove(drop_location())
+ if(Adjacent(usr) && !issilicon(usr))
+ usr.put_in_hands(beaker)
+ beaker = null
+ . = TRUE
+ update_icon()
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user)
+ if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !state_open)
+ on = !on
+ update_icon()
+ return ..()
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/AltClick(mob/user)
+ . = ..()
+ if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ if(state_open)
+ close_machine()
+ else
+ open_machine()
+ update_icon()
+ return TRUE
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/update_remote_sight(mob/living/user)
+ return // we don't see the pipe network while inside cryo.
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/get_remote_view_fullscreens(mob/user)
+ user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1)
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/can_crawl_through()
+ return // can't ventcrawl in or out of cryo.
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/can_see_pipes()
+ return 0 // you can't see the pipe network when inside a cryo cell.
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/return_temperature()
+ var/datum/gas_mixture/G = airs[1]
+
+ if(G.total_moles() > 10)
+ return G.temperature
+ return ..()
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/default_change_direction_wrench(mob/user, obj/item/wrench/W)
+ . = ..()
+ if(.)
+ SetInitDirections()
+ var/obj/machinery/atmospherics/node = nodes[1]
+ if(node)
+ node.disconnect(src)
+ nodes[1] = null
+ nullifyPipenet(parents[1])
+ atmosinit()
+ node = nodes[1]
+ if(node)
+ node.atmosinit()
+ node.addMember(src)
+ SSair.add_to_rebuild_queue(src)
+
+#undef CRYOMOBS
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
index 877826c1..237e1f17 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
@@ -116,7 +116,7 @@
if(node)
node.atmosinit()
node.addMember(src)
- build_network()
+ SSair.add_to_rebuild_queue(src)
return TRUE
/obj/machinery/atmospherics/components/unary/thermomachine/ui_status(mob/user)
diff --git a/code/modules/atmospherics/machinery/pipes/layermanifold.dm b/code/modules/atmospherics/machinery/pipes/layermanifold.dm
index 33092f53..bc8fd677 100644
--- a/code/modules/atmospherics/machinery/pipes/layermanifold.dm
+++ b/code/modules/atmospherics/machinery/pipes/layermanifold.dm
@@ -31,7 +31,7 @@
nodes = list()
for(var/obj/machinery/atmospherics/A in needs_nullifying)
A.disconnect(src)
- A.build_network()
+ SSair.add_to_rebuild_queue(A)
/obj/machinery/atmospherics/pipe/layer_manifold/proc/get_all_connected_nodes()
return front_nodes + back_nodes + nodes
diff --git a/code/modules/atmospherics/machinery/pipes/pipes.dm b/code/modules/atmospherics/machinery/pipes/pipes.dm
index 15615b8e..23fd2292 100644
--- a/code/modules/atmospherics/machinery/pipes/pipes.dm
+++ b/code/modules/atmospherics/machinery/pipes/pipes.dm
@@ -22,7 +22,7 @@
var/obj/machinery/atmospherics/oldN = nodes[i]
..()
if(oldN)
- oldN.build_network()
+ SSair.add_to_rebuild_queue(oldN)
/obj/machinery/atmospherics/pipe/destroy_network()
QDEL_NULL(parent)
diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm
index afd1e918..f307b59a 100644
--- a/code/modules/atmospherics/machinery/portable/canister.dm
+++ b/code/modules/atmospherics/machinery/portable/canister.dm
@@ -184,7 +184,6 @@
/obj/machinery/portable_atmospherics/canister/proto
name = "prototype canister"
-
/obj/machinery/portable_atmospherics/canister/proto/default
name = "prototype canister"
desc = "The best way to fix an atmospheric emergency... or the best way to introduce one."
@@ -197,7 +196,6 @@
can_min_release_pressure = (ONE_ATMOSPHERE / 30)
prototype = TRUE
-
/obj/machinery/portable_atmospherics/canister/proto/default/oxygen
name = "prototype canister"
desc = "A prototype canister for a prototype bike, what could go wrong?"
@@ -206,8 +204,6 @@
filled = 1
release_pressure = ONE_ATMOSPHERE*2
-
-
/obj/machinery/portable_atmospherics/canister/New(loc, datum/gas_mixture/existing_mixture)
..()
if(existing_mixture)
@@ -217,7 +213,7 @@
pump = new(src, FALSE)
pump.on = TRUE
pump.stat = 0
- pump.build_network()
+ SSair.add_to_rebuild_queue(pump)
update_icon()
@@ -233,6 +229,7 @@
air_contents.gases[gas_type] = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
if(starter_temp)
air_contents.temperature = starter_temp
+
/obj/machinery/portable_atmospherics/canister/air/create_gas()
air_contents.gases[/datum/gas/oxygen] = (O2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
air_contents.gases[/datum/gas/nitrogen] = (N2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
diff --git a/code/modules/atmospherics/machinery/portable/pump.dm b/code/modules/atmospherics/machinery/portable/pump.dm
index ddb907a2..fd2888fb 100644
--- a/code/modules/atmospherics/machinery/portable/pump.dm
+++ b/code/modules/atmospherics/machinery/portable/pump.dm
@@ -20,7 +20,7 @@
pump = new(src, FALSE)
pump.on = TRUE
pump.stat = 0
- pump.build_network()
+ SSair.add_to_rebuild_queue(pump)
/obj/machinery/portable_atmospherics/pump/Destroy()
var/turf/T = get_turf(src)
diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/on_move.dm
index beaaa51a..7780522d 100644
--- a/code/modules/shuttle/on_move.dm
+++ b/code/modules/shuttle/on_move.dm
@@ -249,7 +249,7 @@ All ShuttleMove procs go here
A.atmosinit()
if(A.returnPipenet())
A.addMember(src)
- build_network()
+ SSair.add_to_rebuild_queue(src)
else
// atmosinit() calls update_icon(), so we don't need to call it
update_icon()