diff --git a/baystation12.dme b/baystation12.dme
index f750622adee..d487d7325ac 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -1469,17 +1469,21 @@
#include "code\WorkInProgress\ZomgPonies\oldcode\turntable.dm"
#include "code\WorkInProgress\ZomgPonies\powerarmor\powerarmor.dm"
#include "code\WorkInProgress\ZomgPonies\powerarmor\powerarmorcomponents.dm"
+#include "code\ZAS\_docs.dm"
+#include "code\ZAS\_gas_mixture.dm"
#include "code\ZAS\Airflow.dm"
+#include "code\ZAS\Atom.dm"
#include "code\ZAS\Connection.dm"
+#include "code\ZAS\ConnectionGroup.dm"
+#include "code\ZAS\ConnectionManager.dm"
+#include "code\ZAS\Controller.dm"
#include "code\ZAS\Debug.dm"
-#include "code\ZAS\FEA_gas_mixture.dm"
-#include "code\ZAS\FEA_system.dm"
+#include "code\ZAS\Diagnostic.dm"
#include "code\ZAS\Fire.dm"
-#include "code\ZAS\Functions.dm"
#include "code\ZAS\Plasma.dm"
+#include "code\ZAS\Turf.dm"
#include "code\ZAS\Variable Settings.dm"
-#include "code\ZAS\ZAS_Turfs.dm"
-#include "code\ZAS\ZAS_Zones.dm"
+#include "code\ZAS\Zone.dm"
#include "interface\interface.dm"
#include "interface\skin.dmf"
#include "maps\tgstation-redux-WIP.dmm"
diff --git a/code/ZAS - oldbs12/Airflow.dm b/code/ZAS - oldbs12/Airflow.dm
new file mode 100644
index 00000000000..efa45700f4e
--- /dev/null
+++ b/code/ZAS - oldbs12/Airflow.dm
@@ -0,0 +1,420 @@
+/*
+
+CONTAINS:
+All AirflowX() procs, all Variable Setting Controls for airflow, save/load variable tweaks for airflow.
+
+VARIABLES:
+
+atom/movable/airflow_dest
+ The destination turf of a flying object.
+
+atom/movable/airflow_speed
+ The speed (1-15) at which a flying object is traveling to airflow_dest. Decays over time.
+
+
+OVERLOADABLE PROCS:
+
+mob/airflow_stun()
+ Contains checks for and results of being stunned by airflow.
+ Called when airflow quantities exceed airflow_medium_pressure.
+ RETURNS: Null
+
+atom/movable/check_airflow_movable(n)
+ Contains checks for moving any object due to airflow.
+ n is the pressure that is flowing.
+ RETURNS: 1 if the object moves under the air conditions, 0 if it stays put.
+
+atom/movable/airflow_hit(atom/A)
+ Contains results of hitting a solid object (A) due to airflow.
+ A is the dense object hit.
+ Use airflow_speed to determine how fast the projectile was going.
+
+
+AUTOMATIC PROCS:
+
+Airflow(zone/A, zone/B)
+ Causes objects to fly along a pressure gradient.
+ Called by zone updates. A and B are two connected zones.
+
+AirflowSpace(zone/A)
+ Causes objects to fly into space.
+ Called by zone updates. A is a zone connected to space.
+
+atom/movable/GotoAirflowDest(n)
+atom/movable/RepelAirflowDest(n)
+ Called by main airflow procs to cause the object to fly to or away from destination at speed n.
+ Probably shouldn't call this directly unless you know what you're
+ doing and have set airflow_dest. airflow_hit() will be called if the object collides with an obstacle.
+
+*/
+
+mob/var/tmp/last_airflow_stun = 0
+mob/proc/airflow_stun()
+ if(stat == 2)
+ return 0
+ if(last_airflow_stun > world.time - vsc.airflow_stun_cooldown) return 0
+ if(!(status_flags & CANSTUN) && !(status_flags & CANWEAKEN))
+ src << "\blue You stay upright as the air rushes past you."
+ return 0
+ if(weakened <= 0) src << "\red The sudden rush of air knocks you over!"
+ weakened = max(weakened,5)
+ last_airflow_stun = world.time
+
+mob/living/silicon/airflow_stun()
+ return
+
+mob/living/carbon/metroid/airflow_stun()
+ return
+
+mob/living/carbon/human/airflow_stun()
+ if(last_airflow_stun > world.time - vsc.airflow_stun_cooldown) return 0
+ if(buckled) return 0
+ if(shoes)
+ if((shoes.flags & NOSLIP) || (species.bodyflags & FEET_NOSLIP)) return 0
+ if(!(status_flags & CANSTUN) && !(status_flags & CANWEAKEN))
+ src << "\blue You stay upright as the air rushes past you."
+ return 0
+ if(weakened <= 0) src << "\red The sudden rush of air knocks you over!"
+ weakened = max(weakened,rand(1,5))
+ last_airflow_stun = world.time
+
+atom/movable/proc/check_airflow_movable(n)
+
+ if(anchored && !ismob(src)) return 0
+
+ if(!istype(src,/obj/item) && n < vsc.airflow_dense_pressure) return 0
+
+ return 1
+
+mob/check_airflow_movable(n)
+ if(n < vsc.airflow_heavy_pressure)
+ return 0
+ return 1
+
+mob/dead/observer/check_airflow_movable()
+ return 0
+
+mob/living/silicon/check_airflow_movable()
+ return 0
+
+
+obj/item/check_airflow_movable(n)
+ . = ..()
+ switch(w_class)
+ if(2)
+ if(n < vsc.airflow_lightest_pressure) return 0
+ if(3)
+ if(n < vsc.airflow_light_pressure) return 0
+ if(4,5)
+ if(n < vsc.airflow_medium_pressure) return 0
+
+//The main airflow code. Called by zone updates.
+//Zones A and B are air zones. n represents the amount of air moved.
+
+proc/Airflow(zone/A, zone/B)
+
+ var/n = B.air.return_pressure() - A.air.return_pressure()
+
+ //Don't go any further if n is lower than the lowest value needed for airflow.
+ if(abs(n) < vsc.airflow_lightest_pressure) return
+
+ //These turfs are the midway point between A and B, and will be the destination point for thrown objects.
+ var/list/connection/connections_A = A.connections
+ var/list/turf/connected_turfs = list()
+ for(var/connection/C in connections_A) //Grab the turf that is in the zone we are flowing to (determined by n)
+ if( ( A == C.A.zone || A == C.zone_A ) && ( B == C.B.zone || B == C.zone_B ) )
+ if(n < 0)
+ connected_turfs |= C.B
+ else
+ connected_turfs |= C.A
+ else if( ( A == C.B.zone || A == C.zone_B ) && ( B == C.A.zone || B == C.zone_A ) )
+ if(n < 0)
+ connected_turfs |= C.A
+ else
+ connected_turfs |= C.B
+
+ //Get lists of things that can be thrown across the room for each zone (assumes air is moving from zone B to zone A)
+ var/list/air_sucked = B.movables()
+ var/list/air_repelled = A.movables()
+ if(n < 0)
+ //air is moving from zone A to zone B
+ var/list/temporary_pplz = air_sucked
+ air_sucked = air_repelled
+ air_repelled = temporary_pplz
+
+ for(var/atom/movable/M in air_sucked)
+
+ if(M.last_airflow > world.time - vsc.airflow_delay) continue
+
+ //Check for knocking people over
+ if(ismob(M) && n > vsc.airflow_stun_pressure)
+ if(M:status_flags & GODMODE) continue
+ M:airflow_stun()
+
+ if(M.check_airflow_movable(n))
+
+ //Check for things that are in range of the midpoint turfs.
+ var/list/close_turfs = list()
+ for(var/turf/U in connected_turfs)
+ if(M in range(U)) close_turfs += U
+ if(!close_turfs.len) continue
+
+ //If they're already being tossed, don't do it again.
+ if(!M.airflow_speed)
+
+ M.airflow_dest = pick(close_turfs) //Pick a random midpoint to fly towards.
+
+ spawn M.GotoAirflowDest(abs(n)/5)
+
+ //Do it again for the stuff in the other zone, making it fly away.
+ for(var/atom/movable/M in air_repelled)
+
+ if(M.last_airflow > world.time - vsc.airflow_delay) continue
+
+ if(ismob(M) && abs(n) > vsc.airflow_medium_pressure)
+ if(M:status_flags & GODMODE) continue
+ M:airflow_stun()
+
+ if(M.check_airflow_movable(abs(n)))
+
+ var/list/close_turfs = list()
+ for(var/turf/U in connected_turfs)
+ if(M in range(U)) close_turfs += U
+ if(!close_turfs.len) continue
+
+ //If they're already being tossed, don't do it again.
+ if(!M.airflow_speed)
+
+ M.airflow_dest = pick(close_turfs) //Pick a random midpoint to fly towards.
+
+ spawn M.RepelAirflowDest(abs(n)/5)
+
+proc/AirflowSpace(zone/A)
+
+ //The space version of the Airflow(A,B,n) proc.
+
+ var/n = A.air.return_pressure()
+ //Here, n is determined by only the pressure in the room.
+
+ if(n < vsc.airflow_lightest_pressure) return
+
+ var/list/connected_turfs = A.unsimulated_tiles //The midpoints are now all the space connections.
+ var/list/pplz = A.movables() //We only need to worry about things in the zone, not things in space.
+
+ for(var/atom/movable/M in pplz)
+
+ if(M.last_airflow > world.time - vsc.airflow_delay) continue
+
+ if(ismob(M) && n > vsc.airflow_stun_pressure)
+ var/mob/O = M
+ if(O.status_flags & GODMODE) continue
+ O.airflow_stun()
+
+ if(M.check_airflow_movable(n))
+
+ var/list/close_turfs = list()
+ for(var/turf/U in connected_turfs)
+ if(M in range(U)) close_turfs += U
+ if(!close_turfs.len) continue
+
+ //If they're already being tossed, don't do it again.
+ if(!M.airflow_speed)
+
+ M.airflow_dest = pick(close_turfs) //Pick a random midpoint to fly towards.
+ spawn
+ if(M) M.GotoAirflowDest(n/10)
+ //Sometimes shit breaks, and M isn't there after the spawn.
+
+
+/atom/movable/var/tmp/turf/airflow_dest
+/atom/movable/var/tmp/airflow_speed = 0
+/atom/movable/var/tmp/airflow_time = 0
+/atom/movable/var/tmp/last_airflow = 0
+
+/atom/movable/proc/GotoAirflowDest(n)
+ if(!airflow_dest) return
+ if(airflow_speed < 0) return
+ if(last_airflow > world.time - vsc.airflow_delay) return
+ if(airflow_speed)
+ airflow_speed = n/max(get_dist(src,airflow_dest),1)
+ return
+ last_airflow = world.time
+ if(airflow_dest == loc)
+ step_away(src,loc)
+ if(ismob(src))
+ if(src:status_flags & GODMODE)
+ return
+ if(istype(src, /mob/living/carbon/human))
+ if(src:buckled)
+ return
+ if(src:shoes)
+ if(istype(src:shoes, /obj/item/clothing/shoes/magboots))
+ if(src:shoes:magpulse)
+ return
+ src << "\red You are sucked away by airflow!"
+ var/airflow_falloff = 9 - ul_FalloffAmount(airflow_dest) //It's a fast falloff calc. Very useful.
+ if(airflow_falloff < 1)
+ airflow_dest = null
+ return
+ airflow_speed = min(max(n * (9/airflow_falloff),1),9)
+ var
+ xo = airflow_dest.x - src.x
+ yo = airflow_dest.y - src.y
+ od = 0
+ airflow_dest = null
+ if(!density)
+ density = 1
+ od = 1
+ while(airflow_speed > 0)
+ if(airflow_speed <= 0) return
+ airflow_speed = min(airflow_speed,15)
+ airflow_speed -= vsc.airflow_speed_decay
+ if(airflow_speed > 7)
+ if(airflow_time++ >= airflow_speed - 7)
+ if(od)
+ density = 0
+ sleep(1 * tick_multiplier)
+ else
+ if(od)
+ density = 0
+ sleep(max(1,10-(airflow_speed+3)) * tick_multiplier)
+ if(od)
+ density = 1
+ if ((!( src.airflow_dest ) || src.loc == src.airflow_dest))
+ src.airflow_dest = locate(min(max(src.x + xo, 1), world.maxx), min(max(src.y + yo, 1), world.maxy), src.z)
+ if ((src.x == 1 || src.x == world.maxx || src.y == 1 || src.y == world.maxy))
+ return
+ if(!istype(loc, /turf))
+ return
+ step_towards(src, src.airflow_dest)
+ if(ismob(src) && src:client)
+ src:client:move_delay = world.time + vsc.airflow_mob_slowdown
+ airflow_dest = null
+ airflow_speed = 0
+ airflow_time = 0
+ if(od)
+ density = 0
+
+
+/atom/movable/proc/RepelAirflowDest(n)
+ if(!airflow_dest) return
+ if(airflow_speed < 0) return
+ if(last_airflow > world.time - vsc.airflow_delay) return
+ if(airflow_speed)
+ airflow_speed = n/max(get_dist(src,airflow_dest),1)
+ return
+ if(airflow_dest == loc)
+ step_away(src,loc)
+ if(ismob(src))
+ if(src:status_flags & GODMODE)
+ return
+ if(istype(src, /mob/living/carbon/human))
+ if(src:buckled)
+ return
+ if(src:shoes)
+ if(istype(src:shoes, /obj/item/clothing/shoes/magboots))
+ if(src:shoes.flags & NOSLIP)
+ return
+ src << "\red You are pushed away by airflow!"
+ last_airflow = world.time
+ var/airflow_falloff = 9 - ul_FalloffAmount(airflow_dest) //It's a fast falloff calc. Very useful.
+ if(airflow_falloff < 1)
+ airflow_dest = null
+ return
+ airflow_speed = min(max(n * (9/airflow_falloff),1),9)
+ var
+ xo = -(airflow_dest.x - src.x)
+ yo = -(airflow_dest.y - src.y)
+ od = 0
+ airflow_dest = null
+ if(!density)
+ density = 1
+ od = 1
+ while(airflow_speed > 0)
+ if(airflow_speed <= 0) return
+ airflow_speed = min(airflow_speed,15)
+ airflow_speed -= vsc.airflow_speed_decay
+ if(airflow_speed > 7)
+ if(airflow_time++ >= airflow_speed - 7)
+ sleep(1 * tick_multiplier)
+ else
+ sleep(max(1,10-(airflow_speed+3)) * tick_multiplier)
+ if ((!( src.airflow_dest ) || src.loc == src.airflow_dest))
+ src.airflow_dest = locate(min(max(src.x + xo, 1), world.maxx), min(max(src.y + yo, 1), world.maxy), src.z)
+ if ((src.x == 1 || src.x == world.maxx || src.y == 1 || src.y == world.maxy))
+ return
+ if(!istype(loc, /turf))
+ return
+ step_towards(src, src.airflow_dest)
+ if(ismob(src) && src:client)
+ src:client:move_delay = world.time + vsc.airflow_mob_slowdown
+ airflow_dest = null
+ airflow_speed = 0
+ airflow_time = 0
+ if(od)
+ density = 0
+
+/atom/movable/Bump(atom/A)
+ if(airflow_speed > 0 && airflow_dest)
+ airflow_hit(A)
+ else
+ airflow_speed = 0
+ airflow_time = 0
+ . = ..()
+
+atom/movable/proc/airflow_hit(atom/A)
+ airflow_speed = 0
+ airflow_dest = null
+
+mob/airflow_hit(atom/A)
+ for(var/mob/M in hearers(src))
+ M.show_message("\red \The [src] slams into \a [A]!",1,"\red You hear a loud slam!",2)
+ playsound(src.loc, "smash.ogg", 25, 1, -1)
+ weakened = max(weakened, (istype(A,/obj/item) ? A:w_class : rand(1,5))) //Heheheh
+ . = ..()
+
+obj/airflow_hit(atom/A)
+ for(var/mob/M in hearers(src))
+ M.show_message("\red \The [src] slams into \a [A]!",1,"\red You hear a loud slam!",2)
+ playsound(src.loc, "smash.ogg", 25, 1, -1)
+ . = ..()
+
+obj/item/airflow_hit(atom/A)
+ airflow_speed = 0
+ airflow_dest = null
+
+mob/living/carbon/human/airflow_hit(atom/A)
+// for(var/mob/M in hearers(src))
+// M.show_message("\red [src] slams into [A]!",1,"\red You hear a loud slam!",2)
+ playsound(src.loc, "punch", 25, 1, -1)
+ loc:add_blood(src)
+ if (src.wear_suit)
+ src.wear_suit.add_blood(src)
+ if (src.w_uniform)
+ src.w_uniform.add_blood(src)
+ var/b_loss = airflow_speed * vsc.airflow_damage
+
+ var/blocked = run_armor_check("head","melee")
+ apply_damage(b_loss/3, BRUTE, "head", blocked, 0, "Airflow")
+
+ blocked = run_armor_check("chest","melee")
+ apply_damage(b_loss/3, BRUTE, "chest", blocked, 0, "Airflow")
+
+ blocked = run_armor_check("groin","melee")
+ apply_damage(b_loss/3, BRUTE, "groin", blocked, 0, "Airflow")
+
+ if(airflow_speed > 10)
+ paralysis += round(airflow_speed * vsc.airflow_stun)
+ stunned = max(stunned,paralysis + 3)
+ else
+ stunned += round(airflow_speed * vsc.airflow_stun/2)
+ . = ..()
+
+zone/proc/movables()
+ . = list()
+ for(var/turf/T in contents)
+ for(var/atom/A in T)
+// if(istype(A, /obj/effect) || istype(A, /mob/aiEye))
+ if(istype(A, /obj/effect) || istype(A, /mob/camera/aiEye))
+ continue
+ . += A
diff --git a/code/ZAS - oldbs12/Connection.dm b/code/ZAS - oldbs12/Connection.dm
new file mode 100644
index 00000000000..1bb8f9ca54d
--- /dev/null
+++ b/code/ZAS - oldbs12/Connection.dm
@@ -0,0 +1,448 @@
+/*
+This object is contained within zone/var/connections. It's generated whenever two turfs from different zones are linked.
+Indirect connections will not merge the two zones after they reach equilibrium.
+*/
+#define CONNECTION_DIRECT 2
+#define CONNECTION_INDIRECT 1
+#define CONNECTION_CLOSED 0
+
+/connection
+ var/turf/simulated/A
+ var/turf/simulated/B
+
+ var/zone/zone_A
+ var/zone/zone_B
+
+ var/indirect = CONNECTION_DIRECT //If the connection is purely indirect, the zones should not join.
+
+
+/connection/New(turf/T,turf/O)
+ . = ..()
+
+ A = T
+ B = O
+
+ if(A.zone && B.zone)
+ if(!A.zone.connections)
+ A.zone.connections = list()
+ A.zone.connections += src
+ zone_A = A.zone
+
+ if(!B.zone.connections)
+ B.zone.connections = list()
+ B.zone.connections += src
+ zone_B = B.zone
+
+ if(A in air_master.turfs_with_connections)
+ var/list/connections = air_master.turfs_with_connections[A]
+ connections.Add(src)
+ else
+ air_master.turfs_with_connections[A] = list(src)
+
+ if(B in air_master.turfs_with_connections)
+ var/list/connections = air_master.turfs_with_connections[B]
+ connections.Add(src)
+ else
+ air_master.turfs_with_connections[B] = list(src)
+
+ if(A.CanPass(null, B, 0, 0))
+
+ if(!A.CanPass(null, B, 1.5, 1))
+ indirect = CONNECTION_INDIRECT
+
+ ConnectZones(A.zone, B.zone, indirect)
+
+ else
+ ConnectZones(A.zone, B.zone)
+ indirect = CONNECTION_CLOSED
+
+ else
+ world.log << "Attempted to create connection object for non-zone tiles: [T] ([T.x],[T.y],[T.z]) -> [O] ([O.x],[O.y],[O.z])"
+ SoftDelete()
+
+
+/connection/Del()
+ //remove connections from master lists.
+ if(B in air_master.turfs_with_connections)
+ var/list/connections = air_master.turfs_with_connections[B]
+ connections.Remove(src)
+
+ if(A in air_master.turfs_with_connections)
+ var/list/connections = air_master.turfs_with_connections[A]
+ connections.Remove(src)
+
+ //Remove connection from zones.
+ if(A)
+ if(A.zone && A.zone.connections)
+ A.zone.connections.Remove(src)
+ if(!A.zone.connections.len)
+ A.zone.connections = null
+
+ if(istype(zone_A) && (!A || A.zone != zone_A))
+ if(zone_A.connections)
+ zone_A.connections.Remove(src)
+ if(!zone_A.connections.len)
+ zone_A.connections = null
+
+ if(B)
+ if(B.zone && B.zone.connections)
+ B.zone.connections.Remove(src)
+ if(!B.zone.connections.len)
+ B.zone.connections = null
+
+ if(istype(zone_B) && (!B || B.zone != zone_B))
+ if(zone_B.connections)
+ zone_B.connections.Remove(src)
+ if(!zone_B.connections.len)
+ zone_B.connections = null
+
+ //Disconnect zones while handling unusual conditions.
+ // e.g. loss of a zone on a turf
+ DisconnectZones(zone_A, zone_B)
+
+ //Finally, preform actual deletion.
+ . = ..()
+
+
+/connection/proc/SoftDelete()
+ //remove connections from master lists.
+ if(B in air_master.turfs_with_connections)
+ var/list/connections = air_master.turfs_with_connections[B]
+ connections.Remove(src)
+
+ if(A in air_master.turfs_with_connections)
+ var/list/connections = air_master.turfs_with_connections[A]
+ connections.Remove(src)
+
+ //Remove connection from zones.
+ if(A)
+ if(A.zone && A.zone.connections)
+ A.zone.connections.Remove(src)
+ if(!A.zone.connections.len)
+ A.zone.connections = null
+
+ if(istype(zone_A) && (!A || A.zone != zone_A))
+ if(zone_A.connections)
+ zone_A.connections.Remove(src)
+ if(!zone_A.connections.len)
+ zone_A.connections = null
+
+ if(B)
+ if(B.zone && B.zone.connections)
+ B.zone.connections.Remove(src)
+ if(!B.zone.connections.len)
+ B.zone.connections = null
+
+ if(istype(zone_B) && (!B || B.zone != zone_B))
+ if(zone_B.connections)
+ zone_B.connections.Remove(src)
+ if(!zone_B.connections.len)
+ zone_B.connections = null
+
+ //Disconnect zones while handling unusual conditions.
+ // e.g. loss of a zone on a turf
+ DisconnectZones(zone_A, zone_B)
+
+
+/connection/proc/ConnectZones(var/zone/zone_1, var/zone/zone_2, open = 0)
+
+ //Sanity checking
+ if(!istype(zone_1) || !istype(zone_2))
+ return
+
+ //Handle zones connecting indirectly/directly.
+ if(open)
+
+ //Create the lists if necessary.
+ if(!zone_1.connected_zones)
+ zone_1.connected_zones = list()
+
+ if(!zone_2.connected_zones)
+ zone_2.connected_zones = list()
+
+ //Increase the number of connections between zones.
+ if(zone_2 in zone_1.connected_zones)
+ zone_1.connected_zones[zone_2]++
+ else
+ zone_1.connected_zones += zone_2
+ zone_1.connected_zones[zone_2] = 1
+
+ if(zone_1 in zone_2.connected_zones)
+ zone_2.connected_zones[zone_1]++
+ else
+ zone_2.connected_zones += zone_1
+ zone_2.connected_zones[zone_1] = 1
+
+ if(open == CONNECTION_DIRECT)
+ if(!zone_1.direct_connections)
+ zone_1.direct_connections = list(src)
+ else
+ zone_1.direct_connections += src
+
+ if(!zone_2.direct_connections)
+ zone_2.direct_connections = list(src)
+ else
+ zone_2.direct_connections += src
+
+
+ //Handle closed connections.
+ else
+
+ //Create the lists
+ if(!zone_1.closed_connection_zones)
+ zone_1.closed_connection_zones = list()
+
+ if(!zone_2.closed_connection_zones)
+ zone_2.closed_connection_zones = list()
+
+ //Increment the connections.
+ if(zone_2 in zone_1.closed_connection_zones)
+ zone_1.closed_connection_zones[zone_2]++
+ else
+ zone_1.closed_connection_zones += zone_2
+ zone_1.closed_connection_zones[zone_2] = 1
+
+ if(zone_1 in zone_2.closed_connection_zones)
+ zone_2.closed_connection_zones[zone_1]++
+ else
+ zone_2.closed_connection_zones += zone_1
+ zone_2.closed_connection_zones[zone_1] = 1
+
+ if(zone_1.status == ZONE_SLEEPING)
+ zone_1.SetStatus(ZONE_ACTIVE)
+
+ if(zone_2.status == ZONE_SLEEPING)
+ zone_2.SetStatus(ZONE_ACTIVE)
+
+/connection/proc/DisconnectZones(var/zone/zone_1, var/zone/zone_2)
+ //Sanity checking
+ if(!istype(zone_1) || !istype(zone_2))
+ return
+
+ if(indirect != CONNECTION_CLOSED)
+ //Handle disconnection of indirectly or directly connected zones.
+ if( (zone_1 in zone_2.connected_zones) || (zone_2 in zone_1.connected_zones) )
+
+ //If there are more than one connection, decrement the number of connections
+ //Otherwise, remove all connections between the zones.
+ if(zone_2 in zone_1.connected_zones)
+ if(zone_1.connected_zones[zone_2] > 1)
+ zone_1.connected_zones[zone_2]--
+ else
+ zone_1.connected_zones -= zone_2
+ //remove the list if it is empty
+ if(!zone_1.connected_zones.len)
+ zone_1.connected_zones = null
+
+ //Then do the same for the other zone.
+ if(zone_1 in zone_2.connected_zones)
+ if(zone_2.connected_zones[zone_1] > 1)
+ zone_2.connected_zones[zone_1]--
+ else
+ zone_2.connected_zones -= zone_1
+ if(!zone_2.connected_zones.len)
+ zone_2.connected_zones = null
+
+ if(indirect == CONNECTION_DIRECT)
+ zone_1.direct_connections -= src
+ if(!zone_1.direct_connections.len)
+ zone_1.direct_connections = null
+
+ zone_2.direct_connections -= src
+ if(!zone_2.direct_connections.len)
+ zone_2.direct_connections = null
+
+ else
+ //Handle disconnection of closed zones.
+ if( (zone_1 in zone_2.closed_connection_zones) || (zone_2 in zone_1.closed_connection_zones) )
+
+ //If there are more than one connection, decrement the number of connections
+ //Otherwise, remove all connections between the zones.
+ if(zone_2 in zone_1.closed_connection_zones)
+ if(zone_1.closed_connection_zones[zone_2] > 1)
+ zone_1.closed_connection_zones[zone_2]--
+ else
+ zone_1.closed_connection_zones -= zone_2
+ //remove the list if it is empty
+ if(!zone_1.closed_connection_zones.len)
+ zone_1.closed_connection_zones = null
+
+ //Then do the same for the other zone.
+ if(zone_1 in zone_2.closed_connection_zones)
+ if(zone_2.closed_connection_zones[zone_1] > 1)
+ zone_2.closed_connection_zones[zone_1]--
+ else
+ zone_2.closed_connection_zones -= zone_1
+ if(!zone_2.closed_connection_zones.len)
+ zone_2.closed_connection_zones = null
+
+
+/connection/proc/Cleanup()
+
+ //Check sanity: existance of turfs
+ if(!A || !B)
+ SoftDelete()
+ return
+
+ //Check sanity: loss of zone
+ if(!A.zone || !B.zone)
+ SoftDelete()
+ return
+
+ //Check sanity: zones are different
+ if(A.zone == B.zone)
+ SoftDelete()
+ return
+
+ //Handle zones changing on a turf.
+ if((A.zone && A.zone != zone_A) || (B.zone && B.zone != zone_B))
+ Sanitize()
+
+ if(A.zone && B.zone)
+
+ //If no walls are blocking us...
+ if(A.ZAirPass(B))
+ //...we check to see if there is a door in the way...
+ var/door_pass = A.CanPass(null,B,1.5,1)
+ //...and if it is opened.
+ if(door_pass || A.CanPass(null,B,0,0))
+
+ //Make and remove connections to let air pass.
+ if(indirect == CONNECTION_CLOSED)
+ DisconnectZones(A.zone, B.zone)
+ ConnectZones(A.zone, B.zone, door_pass + 1)
+
+ if(door_pass)
+ indirect = CONNECTION_DIRECT
+ else if(!door_pass)
+ indirect = CONNECTION_INDIRECT
+
+ //The door is instead closed.
+ else if(indirect > CONNECTION_CLOSED)
+ DisconnectZones(A.zone, B.zone)
+ indirect = CONNECTION_CLOSED
+ ConnectZones(A.zone, B.zone)
+
+ //If I can no longer pass air, better delete
+ else
+ SoftDelete()
+ return
+
+/connection/proc/Sanitize()
+ //If the zones change on connected turfs, update it.
+
+ //Both zones changed (wat)
+ if(A.zone && A.zone != zone_A && B.zone && B.zone != zone_B)
+
+ //If the zones have gotten swapped
+ // (do not ask me how, I am just being anal retentive about sanity)
+ if(A.zone == zone_B && B.zone == zone_A)
+ var/turf/temp = B
+ B = A
+ A = temp
+ zone_B = B.zone
+ zone_A = A.zone
+ return
+
+ //Handle removal of connections from archived zones.
+ if(zone_A && zone_A.connections)
+ zone_A.connections.Remove(src)
+ if(!zone_A.connections.len)
+ zone_A.connections = null
+
+ if(zone_B && zone_B.connections)
+ zone_B.connections.Remove(src)
+ if(!zone_B.connections.len)
+ zone_B.connections = null
+
+ if(A.zone)
+ if(!A.zone.connections)
+ A.zone.connections = list()
+ A.zone.connections |= src
+
+ if(B.zone)
+ if(!B.zone.connections)
+ B.zone.connections = list()
+ B.zone.connections |= src
+
+ //If either zone is null, we disconnect the archived ones after cleaning up the connections.
+ if(!A.zone || !B.zone)
+ if(zone_A && zone_B)
+ DisconnectZones(zone_B, zone_A)
+
+ if(!A.zone)
+ zone_A = A.zone
+
+ if(!B.zone)
+ zone_B = B.zone
+ return
+
+ //Handle diconnection and reconnection of zones.
+ if(zone_A && zone_B)
+ DisconnectZones(zone_A, zone_B)
+ ConnectZones(A.zone, B.zone, indirect)
+
+ //resetting values of archived values.
+ zone_B = B.zone
+ zone_A = A.zone
+
+ //The "A" zone changed.
+ else if(A.zone && A.zone != zone_A)
+
+ //Handle connection cleanup
+ if(zone_A)
+ if(zone_A.connections)
+ zone_A.connections.Remove(src)
+ if(!zone_A.connections.len)
+ zone_A.connections = null
+
+ if(A.zone)
+ if(!A.zone.connections)
+ A.zone.connections = list()
+ A.zone.connections |= src
+
+ //If the "A" zone is null, we disconnect the archived ones after cleaning up the connections.
+ if(!A.zone)
+ if(zone_A && zone_B)
+ DisconnectZones(zone_A, zone_B)
+ zone_A = A.zone
+ return
+
+ //Handle diconnection and reconnection of zones.
+ if(zone_A && zone_B)
+ DisconnectZones(zone_A, zone_B)
+ ConnectZones(A.zone, B.zone, indirect)
+ zone_A = A.zone
+
+ //The "B" zone changed.
+ else if(B.zone && B.zone != zone_B)
+
+ //Handle connection cleanup
+ if(zone_B)
+ if(zone_B.connections)
+ zone_B.connections.Remove(src)
+ if(!zone_B.connections.len)
+ zone_B.connections = null
+
+ if(B.zone)
+ if(!B.zone.connections)
+ B.zone.connections = list()
+ B.zone.connections |= src
+
+ //If the "B" zone is null, we disconnect the archived ones after cleaning up the connections.
+ if(!B.zone)
+ if(zone_A && zone_B)
+ DisconnectZones(zone_A, zone_B)
+ zone_B = B.zone
+ return
+
+ //Handle diconnection and reconnection of zones.
+ if(zone_A && zone_B)
+ DisconnectZones(zone_A, zone_B)
+ ConnectZones(A.zone, B.zone, indirect)
+ zone_B = B.zone
+
+
+#undef CONNECTION_DIRECT
+#undef CONNECTION_INDIRECT
+#undef CONNECTION_CLOSED
diff --git a/code/ZAS - oldbs12/Debug.dm b/code/ZAS - oldbs12/Debug.dm
new file mode 100644
index 00000000000..39f5a7caabd
--- /dev/null
+++ b/code/ZAS - oldbs12/Debug.dm
@@ -0,0 +1,219 @@
+client/proc/ZoneTick()
+ set category = "Debug"
+ set name = "Process Atmos"
+
+ var/result = air_master.Tick()
+ if(result)
+ src << "Sucessfully Processed."
+
+ else
+ src << "Failed to process! ([air_master.tick_progress])"
+
+
+client/proc/Zone_Info(turf/T as null|turf)
+ set category = "Debug"
+ if(T)
+ if(T.zone)
+ T.zone.DebugDisplay(src)
+ else
+ mob << "No zone here."
+ else
+ if(zone_debug_images)
+ for(var/zone in zone_debug_images)
+ images -= zone_debug_images[zone]
+ zone_debug_images = null
+
+client/var/list/zone_debug_images
+
+client/proc/Test_ZAS_Connection(var/turf/simulated/T as turf)
+ set category = "Debug"
+ if(!istype(T))
+ return
+
+ var/direction_list = list(\
+ "North" = NORTH,\
+ "South" = SOUTH,\
+ "East" = EAST,\
+ "West" = WEST,\
+ "N/A" = null)
+ var/direction = input("What direction do you wish to test?","Set direction") as null|anything in direction_list
+ if(!direction)
+ return
+
+ if(direction == "N/A")
+ if(T.CanPass(null, T, 0,0))
+ mob << "The turf can pass air! :D"
+ else
+ mob << "No air passage :x"
+ return
+
+ var/turf/simulated/other_turf = get_step(T, direction_list[direction])
+ if(!istype(other_turf))
+ return
+
+ var/pass_directions = T.CanPass(null, other_turf, 0, 0) + 2 * other_turf.CanPass(null, T, 0, 0)
+
+ switch(pass_directions)
+ if(0)
+ mob << "Neither turf can connect. :("
+
+ if(1)
+ mob << "The initial turf only can connect. :\\"
+
+ if(2)
+ mob << "The other turf can connect, but not the initial turf. :/"
+
+ if(3)
+ mob << "Both turfs can connect! :)"
+
+
+zone/proc/DebugDisplay(client/client)
+ if(!istype(client))
+ return
+
+ if(!dbg_output)
+ dbg_output = 1 //Don't want to be spammed when someone investigates a zone...
+
+ if(!client.zone_debug_images)
+ client.zone_debug_images = list()
+
+ var/list/current_zone_images = list()
+
+ for(var/turf/T in contents)
+ current_zone_images += image('icons/misc/debug_group.dmi', T, null, TURF_LAYER)
+
+ for(var/turf/space/S in unsimulated_tiles)
+ current_zone_images += image('icons/misc/debug_space.dmi', S, null, TURF_LAYER)
+
+ client << "Zone Air Contents"
+ client << "Oxygen: [air.oxygen]"
+ client << "Nitrogen: [air.nitrogen]"
+ client << "Plasma: [air.toxins]"
+ client << "Carbon Dioxide: [air.carbon_dioxide]"
+ client << "Temperature: [air.temperature] K"
+ client << "Heat Energy: [air.temperature * air.heat_capacity()] J"
+ client << "Pressure: [air.return_pressure()] KPa"
+ client << ""
+ client << "Space Tiles: [length(unsimulated_tiles)]"
+ client << "Movable Objects: [length(movables())]"
+ client << "Connections: [length(connections)]"
+
+ for(var/connection/C in connections)
+ client << "\ref[C] [C.A] --> [C.B] [(C.indirect?"Open":"Closed")]"
+ current_zone_images += image('icons/misc/debug_connect.dmi', C.A, null, TURF_LAYER)
+ current_zone_images += image('icons/misc/debug_connect.dmi', C.B, null, TURF_LAYER)
+
+ client << "Connected Zones:"
+ for(var/zone/zone in connected_zones)
+ client << "\ref[zone] [zone] - [connected_zones[zone]] (Connected)"
+
+ for(var/zone/zone in closed_connection_zones)
+ client << "\ref[zone] [zone] - [closed_connection_zones[zone]] (Unconnected)"
+
+ for(var/C in connections)
+ if(!istype(C,/connection))
+ client << "[C] (Not Connection!)"
+
+ if(!client.zone_debug_images)
+ client.zone_debug_images = list()
+ client.zone_debug_images[src] = current_zone_images
+
+ client.images += client.zone_debug_images[src]
+
+ else
+ dbg_output = 0
+
+ client.images -= client.zone_debug_images[src]
+ client.zone_debug_images.Remove(src)
+
+ if(air_master)
+ for(var/zone/Z in air_master.zones)
+ if(Z.air == air && Z != src)
+ var/turf/zloc = pick(Z.contents)
+ client << "\red Illegal air datum shared by: [zloc.loc.name]"
+
+
+client/proc/TestZASRebuild()
+ set category = "Debug"
+// var/turf/turf = get_turf(mob)
+ var/zone/current_zone = mob.loc:zone
+ if(!current_zone)
+ src << "There is no zone there!"
+ return
+
+ var/list/current_adjacents = list()
+ var/list/overlays = list()
+ var/adjacent_id
+ var/lowest_id
+
+ var/list/identical_ids = list()
+ var/list/turfs = current_zone.contents.Copy()
+ var/current_identifier = 1
+
+ for(var/turf/simulated/current in turfs)
+ lowest_id = null
+ current_adjacents = list()
+
+ for(var/direction in cardinal)
+ var/turf/simulated/adjacent = get_step(current, direction)
+ if(!current.ZCanPass(adjacent))
+ continue
+ if(turfs.Find(adjacent))
+ current_adjacents += adjacent
+ adjacent_id = turfs[adjacent]
+
+ if(adjacent_id && (!lowest_id || adjacent_id < lowest_id))
+ lowest_id = adjacent_id
+
+ if(!lowest_id)
+ lowest_id = current_identifier++
+ identical_ids += lowest_id
+ overlays += image('icons/misc/debug_rebuild.dmi',, "[lowest_id]")
+
+ for(var/turf/simulated/adjacent in current_adjacents)
+ adjacent_id = turfs[adjacent]
+ if(adjacent_id != lowest_id)
+ if(adjacent_id)
+ adjacent.overlays -= overlays[adjacent_id]
+ identical_ids[adjacent_id] = lowest_id
+
+ turfs[adjacent] = lowest_id
+ adjacent.overlays += overlays[lowest_id]
+
+ sleep(5)
+
+ if(turfs[current])
+ current.overlays -= overlays[turfs[current]]
+ turfs[current] = lowest_id
+ current.overlays += overlays[lowest_id]
+ sleep(5)
+
+ var/list/final_arrangement = list()
+
+ for(var/turf/simulated/current in turfs)
+ current_identifier = identical_ids[turfs[current]]
+ current.overlays -= overlays[turfs[current]]
+ current.overlays += overlays[current_identifier]
+ sleep(5)
+
+ if( current_identifier > final_arrangement.len )
+ final_arrangement.len = current_identifier
+ final_arrangement[current_identifier] = list(current)
+
+ else
+ final_arrangement[current_identifier] += current
+
+ //lazy but fast
+ final_arrangement.Remove(null)
+
+ src << "There are [final_arrangement.len] unique segments."
+
+ for(var/turf/current in turfs)
+ current.overlays -= overlays
+
+ return final_arrangement
+
+/client/proc/ZASSettings()
+ set category = "Debug"
+
+ vsc.SetDefault(mob)
\ No newline at end of file
diff --git a/code/ZAS/FEA_gas_mixture.dm b/code/ZAS - oldbs12/FEA_gas_mixture.dm
similarity index 100%
rename from code/ZAS/FEA_gas_mixture.dm
rename to code/ZAS - oldbs12/FEA_gas_mixture.dm
diff --git a/code/ZAS/FEA_system.dm b/code/ZAS - oldbs12/FEA_system.dm
similarity index 100%
rename from code/ZAS/FEA_system.dm
rename to code/ZAS - oldbs12/FEA_system.dm
diff --git a/code/ZAS - oldbs12/Fire.dm b/code/ZAS - oldbs12/Fire.dm
new file mode 100644
index 00000000000..b79d7f8b553
--- /dev/null
+++ b/code/ZAS - oldbs12/Fire.dm
@@ -0,0 +1,362 @@
+/*
+
+Making Bombs with ZAS:
+Make burny fire with lots of burning
+Draw off 5000K gas from burny fire
+Separate gas into oxygen and plasma components
+Obtain plasma and oxygen tanks filled up about 50-75% with normal-temp gas
+Fill rest with super hot gas from separated canisters, they should be about 125C now.
+Attach to transfer valve and open. BOOM.
+
+*/
+
+
+//Some legacy definitions so fires can be started.
+atom/proc/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)
+ return null
+
+
+turf/proc/hotspot_expose(exposed_temperature, exposed_volume, soh = 0)
+
+
+turf/simulated/hotspot_expose(exposed_temperature, exposed_volume, soh)
+ if(fire_protection > world.time-300)
+ return 0
+ if(locate(/obj/fire) in src)
+ return 1
+ var/datum/gas_mixture/air_contents = return_air()
+ if(!air_contents || exposed_temperature < PLASMA_MINIMUM_BURN_TEMPERATURE)
+ return 0
+
+ var/obj/structure/reagent_dispensers/fueltank/FT = locate() in src
+ if(exposed_temperature >= AUTOIGNITION_WELDERFUEL)
+ if (FT)
+ FT.explode()
+ return 1
+
+ var/igniting = 0
+ var/obj/effect/decal/cleanable/liquid_fuel/liquid = locate() in src
+
+ if(air_contents.check_combustability(liquid))
+ igniting = 1
+
+ if(! (locate(/obj/fire) in src))
+
+ new /obj/fire(src,1000)
+
+ return igniting
+
+/obj/fire
+ //Icon for fire on turfs.
+
+ anchored = 1
+ mouse_opacity = 0
+
+ //luminosity = 3
+
+ icon = 'icons/effects/fire.dmi'
+ icon_state = "1"
+
+ layer = TURF_LAYER
+
+ var/firelevel = 10000 //Calculated by gas_mixture.calculate_firelevel()
+
+/obj/fire/process()
+ . = 1
+
+ //get location and check if it is in a proper ZAS zone
+ var/turf/simulated/S = loc
+
+ if(!istype(S))
+ del src
+
+ if(!S.zone)
+ del src
+
+ var/datum/gas_mixture/air_contents = S.return_air()
+ //get liquid fuels on the ground.
+ var/obj/effect/decal/cleanable/liquid_fuel/liquid = locate() in S
+ //and the volatile stuff from the air
+ var/datum/gas/volatile_fuel/fuel = locate() in air_contents.trace_gases
+
+ //since the air is processed in fractions, we need to make sure not to have any minuscle residue or
+ //the amount of moles might get to low for some functions to catch them and thus result in wonky behaviour
+ if(air_contents.oxygen < 0.001)
+ air_contents.oxygen = 0
+ if(air_contents.toxins < 0.001)
+ air_contents.toxins = 0
+ if(fuel)
+ if(fuel.moles < 0.001)
+ air_contents.trace_gases.Remove(fuel)
+
+ //check if there is something to combust
+ if(!air_contents.check_recombustability(liquid))
+ //del src
+ RemoveFire()
+
+ //get a firelevel and set the icon
+ firelevel = air_contents.calculate_firelevel(liquid)
+
+ if(firelevel > 6)
+ icon_state = "3"
+ SetLuminosity(7)
+ else if(firelevel > 2.5)
+ icon_state = "2"
+ SetLuminosity(5)
+ else
+ icon_state = "1"
+ SetLuminosity(3)
+
+ //im not sure how to implement a version that works for every creature so for now monkeys are firesafe
+ for(var/mob/living/carbon/human/M in loc)
+ M.FireBurn(firelevel, air_contents.temperature, air_contents.return_pressure() ) //Burn the humans!
+ for(var/atom/A in loc)
+ A.fire_act(air_contents, air_contents.temperature, air_contents.return_volume())
+ //spread
+ for(var/direction in cardinal)
+ if(S.air_check_directions&direction) //Grab all valid bordering tiles
+
+ var/turf/simulated/enemy_tile = get_step(S, direction)
+
+ if(istype(enemy_tile))
+ var/datum/gas_mixture/acs = enemy_tile.return_air()
+ var/obj/effect/decal/cleanable/liquid_fuel/liq = locate() in enemy_tile
+ if(!acs) continue
+ if(!acs.check_recombustability(liq)) continue
+ //If extinguisher mist passed over the turf it's trying to spread to, don't spread and
+ //reduce firelevel.
+ if(enemy_tile.fire_protection > world.time-30)
+ firelevel -= 1.5
+ continue
+
+ //Spread the fire.
+ if(!(locate(/obj/fire) in enemy_tile))
+ if( prob( 50 + 50 * (firelevel/vsc.fire_firelevel_multiplier) ) && S.CanPass(null, enemy_tile, 0,0) && enemy_tile.CanPass(null, S, 0,0))
+ new/obj/fire(enemy_tile,firelevel)
+
+ //seperate part of the present gas
+ //this is done to prevent the fire burning all gases in a single pass
+ var/datum/gas_mixture/flow = air_contents.remove_ratio(vsc.fire_consuption_rate)
+///////////////////////////////// FLOW HAS BEEN CREATED /// DONT DELETE THE FIRE UNTIL IT IS MERGED BACK OR YOU WILL DELETE AIR ///////////////////////////////////////////////
+
+ if(flow)
+
+ if(flow.check_recombustability(liquid))
+ //Ensure flow temperature is higher than minimum fire temperatures.
+ //this creates some energy ex nihilo but is necessary to get a fire started
+ //lets just pretend this energy comes from the ignition source and dont mention this again
+ //flow.temperature = max(PLASMA_MINIMUM_BURN_TEMPERATURE+0.1,flow.temperature)
+
+ //burn baby burn!
+
+ flow.zburn(liquid,1)
+ //merge the air back
+ S.assume_air(flow)
+
+///////////////////////////////// FLOW HAS BEEN REMERGED /// feel free to delete the fire again from here on //////////////////////////////////////////////////////////////////
+
+
+/obj/fire/New(newLoc,fl)
+ ..()
+
+ if(!istype(loc, /turf))
+ del src
+
+ dir = pick(cardinal)
+ SetLuminosity(3)
+ firelevel = fl
+ air_master.active_hotspots.Add(src)
+
+
+/obj/fire/Destroy()
+ if (istype(loc, /turf/simulated))
+ SetLuminosity(0)
+
+ loc = null
+ air_master.active_hotspots.Remove(src)
+
+ ..()
+
+/obj/fire/proc/RemoveFire()
+ if (istype(loc, /turf/simulated))
+ SetLuminosity(0)
+ loc = null
+ air_master.active_hotspots.Remove(src)
+
+
+
+turf/simulated/var/fire_protection = 0 //Protects newly extinguished tiles from being overrun again.
+turf/proc/apply_fire_protection()
+turf/simulated/apply_fire_protection()
+ fire_protection = world.time
+
+
+datum/gas_mixture/proc/zburn(obj/effect/decal/cleanable/liquid_fuel/liquid, force_burn)
+ var/value = 0
+
+ if((temperature > PLASMA_MINIMUM_BURN_TEMPERATURE || force_burn) && check_recombustability(liquid))
+ var/total_fuel = 0
+ var/datum/gas/volatile_fuel/fuel = locate() in trace_gases
+
+ total_fuel += toxins
+
+ if(fuel)
+ //Volatile Fuel
+ total_fuel += fuel.moles
+
+ if(liquid)
+ //Liquid Fuel
+ if(liquid.amount <= 0)
+ del liquid
+ else
+ total_fuel += liquid.amount
+
+ //Calculate the firelevel.
+ var/firelevel = calculate_firelevel(liquid)
+
+ //get the current inner energy of the gas mix
+ //this must be taken here to prevent the addition or deletion of energy by a changing heat capacity
+ var/starting_energy = temperature * heat_capacity()
+
+ //determine the amount of oxygen used
+ var/total_oxygen = min(oxygen, 2 * total_fuel)
+
+ //determine the amount of fuel actually used
+ var/used_fuel_ratio = min(oxygen / 2 , total_fuel) / total_fuel
+ total_fuel = total_fuel * used_fuel_ratio
+
+ var/total_reactants = total_fuel + total_oxygen
+
+ //determine the amount of reactants actually reacting
+ var/used_reactants_ratio = min( max(total_reactants * firelevel / vsc.fire_firelevel_multiplier, 0.2), total_reactants) / total_reactants
+
+ //remove and add gasses as calculated
+ oxygen -= min(oxygen, total_oxygen * used_reactants_ratio )
+
+ toxins -= min(toxins, (toxins * used_fuel_ratio * used_reactants_ratio ) * 3)
+ if(toxins < 0)
+ toxins = 0
+
+ carbon_dioxide += max(2 * total_fuel, 0)
+
+ if(fuel)
+ fuel.moles -= (fuel.moles * used_fuel_ratio * used_reactants_ratio) * 5 //Fuel burns 5 times as quick
+ if(fuel.moles <= 0) del fuel
+
+ if(liquid)
+ liquid.amount -= (liquid.amount * used_fuel_ratio * used_reactants_ratio) * 5 // liquid fuel burns 5 times as quick
+
+ if(liquid.amount <= 0) del liquid
+
+ //calculate the energy produced by the reaction and then set the new temperature of the mix
+ temperature = (starting_energy + vsc.fire_fuel_energy_release * total_fuel) / heat_capacity()
+
+ update_values()
+ value = total_reactants * used_reactants_ratio
+ return value
+
+datum/gas_mixture/proc/check_recombustability(obj/effect/decal/cleanable/liquid_fuel/liquid)
+ //this is a copy proc to continue a fire after its been started.
+
+ var/datum/gas/volatile_fuel/fuel = locate() in trace_gases
+
+ if(oxygen && (toxins || fuel || liquid))
+ if(liquid)
+ return 1
+ if (toxins)
+ return 1
+ if(fuel)
+ return 1
+
+ return 0
+
+datum/gas_mixture/proc/check_combustability(obj/effect/decal/cleanable/liquid_fuel/liquid)
+ //this check comes up very often and is thus centralized here to ease adding stuff
+
+ var/datum/gas/volatile_fuel/fuel = locate() in trace_gases
+
+ if(oxygen && (toxins || fuel || liquid))
+ if(liquid)
+ return 1
+ if (toxins >= 0.7)
+ return 1
+ if(fuel && fuel.moles >= 1.4)
+ return 1
+
+ return 0
+
+datum/gas_mixture/proc/calculate_firelevel(obj/effect/decal/cleanable/liquid_fuel/liquid)
+ //Calculates the firelevel based on one equation instead of having to do this multiple times in different areas.
+
+ var/datum/gas/volatile_fuel/fuel = locate() in trace_gases
+ var/total_fuel = 0
+ var/firelevel = 0
+
+ if(check_recombustability(liquid))
+
+ total_fuel += toxins
+
+ if(liquid)
+ total_fuel += liquid.amount
+
+ if(fuel)
+ total_fuel += fuel.moles
+
+ var/total_combustables = (total_fuel + oxygen)
+
+ if(total_fuel > 0 && oxygen > 0)
+
+ //slows down the burning when the concentration of the reactants is low
+ var/dampening_multiplier = total_combustables / (total_combustables + nitrogen + carbon_dioxide)
+ //calculates how close the mixture of the reactants is to the optimum
+ var/mix_multiplier = 1 / (1 + (5 * ((oxygen / total_combustables) ** 2)))
+ //toss everything together
+ firelevel = vsc.fire_firelevel_multiplier * mix_multiplier * dampening_multiplier
+
+ return max( 0, firelevel)
+
+
+/mob/living/proc/FireBurn(var/firelevel, var/last_temperature, var/pressure)
+ var/mx = 5 * firelevel/vsc.fire_firelevel_multiplier * min(pressure / ONE_ATMOSPHERE, 1)
+ apply_damage(2.5*mx, BURN)
+
+
+/mob/living/carbon/human/FireBurn(var/firelevel, var/last_temperature, var/pressure)
+ //Burns mobs due to fire. Respects heat transfer coefficients on various body parts.
+ //Due to TG reworking how fireprotection works, this is kinda less meaningful.
+
+ var/head_exposure = 1
+ var/chest_exposure = 1
+ var/groin_exposure = 1
+ var/legs_exposure = 1
+ var/arms_exposure = 1
+
+ //Get heat transfer coefficients for clothing.
+
+ for(var/obj/item/clothing/C in src)
+ if(l_hand == C || r_hand == C)
+ continue
+
+ if( C.max_heat_protection_temperature >= last_temperature )
+ if(C.body_parts_covered & HEAD)
+ head_exposure = 0
+ if(C.body_parts_covered & UPPER_TORSO)
+ chest_exposure = 0
+ if(C.body_parts_covered & LOWER_TORSO)
+ groin_exposure = 0
+ if(C.body_parts_covered & LEGS)
+ legs_exposure = 0
+ if(C.body_parts_covered & ARMS)
+ arms_exposure = 0
+ //minimize this for low-pressure enviroments
+ var/mx = 5 * firelevel/vsc.fire_firelevel_multiplier * min(pressure / ONE_ATMOSPHERE, 1)
+
+ //Always check these damage procs first if fire damage isn't working. They're probably what's wrong.
+
+ apply_damage(2.5*mx*head_exposure, BURN, "head", 0, 0, "Fire")
+ apply_damage(2.5*mx*chest_exposure, BURN, "chest", 0, 0, "Fire")
+ apply_damage(2.0*mx*groin_exposure, BURN, "groin", 0, 0, "Fire")
+ apply_damage(0.6*mx*legs_exposure, BURN, "l_leg", 0, 0, "Fire")
+ apply_damage(0.6*mx*legs_exposure, BURN, "r_leg", 0, 0, "Fire")
+ apply_damage(0.4*mx*arms_exposure, BURN, "l_arm", 0, 0, "Fire")
+ apply_damage(0.4*mx*arms_exposure, BURN, "r_arm", 0, 0, "Fire")
diff --git a/code/ZAS/Functions.dm b/code/ZAS - oldbs12/Functions.dm
similarity index 100%
rename from code/ZAS/Functions.dm
rename to code/ZAS - oldbs12/Functions.dm
diff --git a/code/ZAS - oldbs12/Plasma.dm b/code/ZAS - oldbs12/Plasma.dm
new file mode 100644
index 00000000000..716ea216403
--- /dev/null
+++ b/code/ZAS - oldbs12/Plasma.dm
@@ -0,0 +1,163 @@
+var/image/contamination_overlay = image('icons/effects/contamination.dmi')
+
+/pl_control
+ var/PLASMA_DMG = 3
+ var/PLASMA_DMG_NAME = "Plasma Damage Amount"
+ var/PLASMA_DMG_DESC = "Self Descriptive"
+
+ var/CLOTH_CONTAMINATION = 1
+ var/CLOTH_CONTAMINATION_NAME = "Cloth Contamination"
+ var/CLOTH_CONTAMINATION_DESC = "If this is on, plasma does damage by getting into cloth."
+
+ var/PLASMAGUARD_ONLY = 0
+ var/PLASMAGUARD_ONLY_NAME = "\"PlasmaGuard Only\""
+ var/PLASMAGUARD_ONLY_DESC = "If this is on, only biosuits and spacesuits protect against contamination and ill effects."
+
+ var/GENETIC_CORRUPTION = 0
+ var/GENETIC_CORRUPTION_NAME = "Genetic Corruption Chance"
+ var/GENETIC_CORRUPTION_DESC = "Chance of genetic corruption as well as toxic damage, X in 10,000."
+
+ var/SKIN_BURNS = 0
+ var/SKIN_BURNS_DESC = "Plasma has an effect similar to mustard gas on the un-suited."
+ var/SKIN_BURNS_NAME = "Skin Burns"
+
+ var/EYE_BURNS = 1
+ var/EYE_BURNS_NAME = "Eye Burns"
+ var/EYE_BURNS_DESC = "Plasma burns the eyes of anyone not wearing eye protection."
+
+ var/CONTAMINATION_LOSS = 0.02
+ var/CONTAMINATION_LOSS_NAME = "Contamination Loss"
+ var/CONTAMINATION_LOSS_DESC = "How much toxin damage is dealt from contaminated clothing" //Per tick? ASK ARYN
+
+ var/PLASMA_HALLUCINATION = 0
+ var/PLASMA_HALLUCINATION_NAME = "Plasma Hallucination"
+ var/PLASMA_HALLUCINATION_DESC = "Does being in plasma cause you to hallucinate?"
+
+ var/N2O_HALLUCINATION = 1
+ var/N2O_HALLUCINATION_NAME = "N2O Hallucination"
+ var/N2O_HALLUCINATION_DESC = "Does being in sleeping gas cause you to hallucinate?"
+
+
+obj/var/contaminated = 0
+
+
+/obj/item/proc/can_contaminate()
+ //Clothing and backpacks can be contaminated.
+ if(flags & PLASMAGUARD) return 0
+ else if(istype(src,/obj/item/weapon/storage/backpack)) return 0 //Cannot be washed :(
+ else if(istype(src,/obj/item/clothing)) return 1
+
+/obj/item/proc/contaminate()
+ //Do a contamination overlay? Temporary measure to keep contamination less deadly than it was.
+ if(!contaminated)
+ contaminated = 1
+ overlays += contamination_overlay
+
+/obj/item/proc/decontaminate()
+ contaminated = 0
+ overlays -= contamination_overlay
+
+/mob/proc/contaminate()
+
+/mob/living/carbon/human/contaminate()
+ //See if anything can be contaminated.
+
+ if(!pl_suit_protected())
+ suit_contamination()
+
+ if(!pl_head_protected())
+ if(prob(1)) suit_contamination() //Plasma can sometimes get through such an open suit.
+
+//Cannot wash backpacks currently.
+// if(istype(back,/obj/item/weapon/storage/backpack))
+// back.contaminate()
+
+/mob/proc/pl_effects()
+
+/mob/living/carbon/human/pl_effects()
+ //Handles all the bad things plasma can do.
+
+ //Contamination
+ if(vsc.plc.CLOTH_CONTAMINATION) contaminate()
+
+ //Anything else requires them to not be dead.
+ if(stat >= 2)
+ return
+
+ //Burn skin if exposed.
+ if(vsc.plc.SKIN_BURNS)
+ if(!pl_head_protected() || !pl_suit_protected())
+ burn_skin(0.75)
+ if(prob(20)) src << "\red Your skin burns!"
+ updatehealth()
+
+ //Burn eyes if exposed.
+ if(vsc.plc.EYE_BURNS)
+ if(!head)
+ if(!wear_mask)
+ burn_eyes()
+ else
+ if(!(wear_mask.flags & MASKCOVERSEYES))
+ burn_eyes()
+ else
+ if(!(head.flags & HEADCOVERSEYES))
+ if(!wear_mask)
+ burn_eyes()
+ else
+ if(!(wear_mask.flags & MASKCOVERSEYES))
+ burn_eyes()
+
+ //Genetic Corruption
+ if(vsc.plc.GENETIC_CORRUPTION)
+ if(rand(1,10000) < vsc.plc.GENETIC_CORRUPTION)
+ randmutb(src)
+ src << "\red High levels of toxins cause you to spontaneously mutate."
+ domutcheck(src,null)
+
+
+/mob/living/carbon/human/proc/burn_eyes()
+ //The proc that handles eye burning.
+ if(prob(20)) src << "\red Your eyes burn!"
+ var/datum/organ/internal/eyes/E = internal_organs["eyes"]
+ E.damage += 2.5
+ eye_blurry = min(eye_blurry+1.5,50)
+ if (prob(max(0,E.damage - 15) + 1) &&!eye_blind)
+ src << "\red You are blinded!"
+ eye_blind += 20
+
+/mob/living/carbon/human/proc/pl_head_protected()
+ //Checks if the head is adequately sealed.
+ if(head)
+ if(vsc.plc.PLASMAGUARD_ONLY)
+ if(head.flags & PLASMAGUARD)
+ return 1
+ else if(head.flags & HEADCOVERSEYES)
+ return 1
+ return 0
+
+/mob/living/carbon/human/proc/pl_suit_protected()
+ //Checks if the suit is adequately sealed.
+ if(wear_suit)
+ if(vsc.plc.PLASMAGUARD_ONLY)
+ if(wear_suit.flags & PLASMAGUARD) return 1
+ else
+ if(wear_suit.flags_inv & HIDEJUMPSUIT) return 1
+ return 0
+
+/mob/living/carbon/human/proc/suit_contamination()
+ //Runs over the things that can be contaminated and does so.
+ if(w_uniform) w_uniform.contaminate()
+ if(shoes) shoes.contaminate()
+ if(gloves) gloves.contaminate()
+
+
+turf/Entered(obj/item/I)
+ . = ..()
+ //Items that are in plasma, but not on a mob, can still be contaminated.
+ if(istype(I) && vsc.plc.CLOTH_CONTAMINATION)
+ var/datum/gas_mixture/env = return_air(1)
+ if(!env)
+ return
+ if(env.toxins > MOLES_PLASMA_VISIBLE + 1)
+ if(I.can_contaminate())
+ I.contaminate()
\ No newline at end of file
diff --git a/code/ZAS - oldbs12/Variable Settings.dm b/code/ZAS - oldbs12/Variable Settings.dm
new file mode 100644
index 00000000000..92841a02dcd
--- /dev/null
+++ b/code/ZAS - oldbs12/Variable Settings.dm
@@ -0,0 +1,336 @@
+var/global/vs_control/vsc = new
+
+/vs_control
+ var/fire_consuption_rate = 0.25
+ var/fire_consuption_rate_NAME = "Fire - Air Consumption Ratio"
+ var/fire_consuption_rate_DESC = "Ratio of air removed and combusted per tick."
+
+ var/fire_firelevel_multiplier = 25
+ var/fire_firelevel_multiplier_NAME = "Fire - Firelevel Constant"
+ var/fire_firelevel_multiplier_DESC = "Multiplied by the equation for firelevel, affects mainly the extingiushing of fires."
+
+ var/fire_fuel_energy_release = 397000
+ var/fire_fuel_energy_release_NAME = "Fire - Fuel energy release"
+ var/fire_fuel_energy_release_DESC = "The energy in joule released when burning one mol of a burnable substance"
+
+
+ var/IgnitionLevel = 0.5
+ var/IgnitionLevel_DESC = "Determines point at which fire can ignite"
+
+ var/airflow_lightest_pressure = 20
+ var/airflow_lightest_pressure_NAME = "Airflow - Small Movement Threshold %"
+ var/airflow_lightest_pressure_DESC = "Percent of 1 Atm. at which items with the small weight classes will move."
+
+ var/airflow_light_pressure = 35
+ var/airflow_light_pressure_NAME = "Airflow - Medium Movement Threshold %"
+ var/airflow_light_pressure_DESC = "Percent of 1 Atm. at which items with the medium weight classes will move."
+
+ var/airflow_medium_pressure = 50
+ var/airflow_medium_pressure_NAME = "Airflow - Heavy Movement Threshold %"
+ var/airflow_medium_pressure_DESC = "Percent of 1 Atm. at which items with the largest weight classes will move."
+
+ var/airflow_heavy_pressure = 65
+ var/airflow_heavy_pressure_NAME = "Airflow - Mob Movement Threshold %"
+ var/airflow_heavy_pressure_DESC = "Percent of 1 Atm. at which mobs will move."
+
+ var/airflow_dense_pressure = 85
+ var/airflow_dense_pressure_NAME = "Airflow - Dense Movement Threshold %"
+ var/airflow_dense_pressure_DESC = "Percent of 1 Atm. at which items with canisters and closets will move."
+
+ var/airflow_stun_pressure = 60
+ var/airflow_stun_pressure_NAME = "Airflow - Mob Stunning Threshold %"
+ var/airflow_stun_pressure_DESC = "Percent of 1 Atm. at which mobs will be stunned by airflow."
+
+ var/airflow_stun_cooldown = 60
+ var/airflow_stun_cooldown_NAME = "Aiflow Stunning - Cooldown"
+ var/airflow_stun_cooldown_DESC = "How long, in tenths of a second, to wait before stunning them again."
+
+ var/airflow_stun = 1
+ var/airflow_stun_NAME = "Airflow Impact - Stunning"
+ var/airflow_stun_DESC = "How much a mob is stunned when hit by an object."
+
+ var/airflow_damage = 2
+ var/airflow_damage_NAME = "Airflow Impact - Damage"
+ var/airflow_damage_DESC = "Damage from airflow impacts."
+
+ var/airflow_speed_decay = 1.5
+ var/airflow_speed_decay_NAME = "Airflow Speed Decay"
+ var/airflow_speed_decay_DESC = "How rapidly the speed gained from airflow decays."
+
+ var/airflow_delay = 30
+ var/airflow_delay_NAME = "Airflow Retrigger Delay"
+ var/airflow_delay_DESC = "Time in deciseconds before things can be moved by airflow again."
+
+ var/airflow_mob_slowdown = 1
+ var/airflow_mob_slowdown_NAME = "Airflow Slowdown"
+ var/airflow_mob_slowdown_DESC = "Time in tenths of a second to add as a delay to each movement by a mob if they are fighting the pull of the airflow."
+
+ var/connection_insulation = 1
+ var/connection_insulation_NAME = "Connections - Insulation"
+ var/connection_insulation_DESC = "Boolean, should doors forbid heat transfer?"
+
+ var/connection_temperature_delta = 10
+ var/connection_temperature_delta_NAME = "Connections - Temperature Difference"
+ var/connection_temperature_delta_DESC = "The smallest temperature difference which will cause heat to travel through doors."
+
+
+/vs_control/var/list/settings = list()
+/vs_control/var/list/bitflags = list("1","2","4","8","16","32","64","128","256","512","1024")
+/vs_control/var/pl_control/plc = new()
+
+/vs_control/New()
+ . = ..()
+ settings = vars.Copy()
+
+ var/datum/D = new() //Ensure only unique vars are put through by making a datum and removing all common vars.
+ for(var/V in D.vars)
+ settings -= V
+
+ for(var/V in settings)
+ if(findtextEx(V,"_RANDOM") || findtextEx(V,"_DESC") || findtextEx(V,"_METHOD"))
+ settings -= V
+
+ settings -= "settings"
+ settings -= "bitflags"
+ settings -= "plc"
+
+/vs_control/proc/ChangeSettingsDialog(mob/user,list/L)
+ //var/which = input(user,"Choose a setting:") in L
+ var/dat = ""
+ for(var/ch in L)
+ if(findtextEx(ch,"_RANDOM") || findtextEx(ch,"_DESC") || findtextEx(ch,"_METHOD") || findtextEx(ch,"_NAME")) continue
+ var/vw
+ var/vw_desc = "No Description."
+ var/vw_name = ch
+ if(ch in plc.settings)
+ vw = plc.vars[ch]
+ if("[ch]_DESC" in plc.vars) vw_desc = plc.vars["[ch]_DESC"]
+ if("[ch]_NAME" in plc.vars) vw_name = plc.vars["[ch]_NAME"]
+ else
+ vw = vars[ch]
+ if("[ch]_DESC" in vars) vw_desc = vars["[ch]_DESC"]
+ if("[ch]_NAME" in vars) vw_name = vars["[ch]_NAME"]
+ dat += "[vw_name] = [vw] \[Change\]
"
+ dat += "[vw_desc]
"
+ user << browse(dat,"window=settings")
+
+/vs_control/Topic(href,href_list)
+ if("changevar" in href_list)
+ ChangeSetting(usr,href_list["changevar"])
+
+/vs_control/proc/ChangeSetting(mob/user,ch)
+ var/vw
+ var/how = "Text"
+ var/display_description = ch
+ if(ch in plc.settings)
+ vw = plc.vars[ch]
+ if("[ch]_NAME" in plc.vars)
+ display_description = plc.vars["[ch]_NAME"]
+ if("[ch]_METHOD" in plc.vars)
+ how = plc.vars["[ch]_METHOD"]
+ else
+ if(isnum(vw))
+ how = "Numeric"
+ else
+ how = "Text"
+ else
+ vw = vars[ch]
+ if("[ch]_NAME" in vars)
+ display_description = vars["[ch]_NAME"]
+ if("[ch]_METHOD" in vars)
+ how = vars["[ch]_METHOD"]
+ else
+ if(isnum(vw))
+ how = "Numeric"
+ else
+ how = "Text"
+ var/newvar = vw
+ switch(how)
+ if("Numeric")
+ newvar = input(user,"Enter a number:","Settings",newvar) as num
+ if("Bit Flag")
+ var/flag = input(user,"Toggle which bit?","Settings") in bitflags
+ flag = text2num(flag)
+ if(newvar & flag)
+ newvar &= ~flag
+ else
+ newvar |= flag
+ if("Toggle")
+ newvar = !newvar
+ if("Text")
+ newvar = input(user,"Enter a string:","Settings",newvar) as text
+ if("Long Text")
+ newvar = input(user,"Enter text:","Settings",newvar) as message
+ vw = newvar
+ if(ch in plc.settings)
+ plc.vars[ch] = vw
+ else
+ vars[ch] = vw
+ if(how == "Toggle")
+ newvar = (newvar?"ON":"OFF")
+ world << "\blue [key_name(user)] changed the setting [display_description] to [newvar]."
+ if(ch in plc.settings)
+ ChangeSettingsDialog(user,plc.settings)
+ else
+ ChangeSettingsDialog(user,settings)
+
+/vs_control/proc/RandomizeWithProbability()
+ for(var/V in settings)
+ var/newvalue
+ if("[V]_RANDOM" in vars)
+ if(isnum(vars["[V]_RANDOM"]))
+ newvalue = prob(vars["[V]_RANDOM"])
+ else if(istext(vars["[V]_RANDOM"]))
+ newvalue = roll(vars["[V]_RANDOM"])
+ else
+ newvalue = vars[V]
+ V = newvalue
+
+/vs_control/proc/ChangePlasma()
+ for(var/V in plc.settings)
+ plc.Randomize(V)
+
+/vs_control/proc/SetDefault(var/mob/user)
+ var/list/setting_choices = list("Plasma - Standard", "Plasma - Low Hazard", "Plasma - High Hazard", "Plasma - Oh Shit!",\
+ "ZAS - Normal", "ZAS - Forgiving", "ZAS - Dangerous", "ZAS - Hellish")
+ var/def = input(user, "Which of these presets should be used?") as null|anything in setting_choices
+ if(!def)
+ return
+ switch(def)
+ if("Plasma - Standard")
+ plc.CLOTH_CONTAMINATION = 1 //If this is on, plasma does damage by getting into cloth.
+ plc.PLASMAGUARD_ONLY = 0
+ plc.GENETIC_CORRUPTION = 0 //Chance of genetic corruption as well as toxic damage, X in 1000.
+ plc.SKIN_BURNS = 0 //Plasma has an effect similar to mustard gas on the un-suited.
+ plc.EYE_BURNS = 1 //Plasma burns the eyes of anyone not wearing eye protection.
+ plc.PLASMA_HALLUCINATION = 0
+ plc.CONTAMINATION_LOSS = 0.02
+
+ if("Plasma - Low Hazard")
+ plc.CLOTH_CONTAMINATION = 0 //If this is on, plasma does damage by getting into cloth.
+ plc.PLASMAGUARD_ONLY = 0
+ plc.GENETIC_CORRUPTION = 0 //Chance of genetic corruption as well as toxic damage, X in 1000
+ plc.SKIN_BURNS = 0 //Plasma has an effect similar to mustard gas on the un-suited.
+ plc.EYE_BURNS = 1 //Plasma burns the eyes of anyone not wearing eye protection.
+ plc.PLASMA_HALLUCINATION = 0
+ plc.CONTAMINATION_LOSS = 0.01
+
+ if("Plasma - High Hazard")
+ plc.CLOTH_CONTAMINATION = 1 //If this is on, plasma does damage by getting into cloth.
+ plc.PLASMAGUARD_ONLY = 0
+ plc.GENETIC_CORRUPTION = 0 //Chance of genetic corruption as well as toxic damage, X in 1000.
+ plc.SKIN_BURNS = 1 //Plasma has an effect similar to mustard gas on the un-suited.
+ plc.EYE_BURNS = 1 //Plasma burns the eyes of anyone not wearing eye protection.
+ plc.PLASMA_HALLUCINATION = 1
+ plc.CONTAMINATION_LOSS = 0.05
+
+ if("Plasma - Oh Shit!")
+ plc.CLOTH_CONTAMINATION = 1 //If this is on, plasma does damage by getting into cloth.
+ plc.PLASMAGUARD_ONLY = 1
+ plc.GENETIC_CORRUPTION = 5 //Chance of genetic corruption as well as toxic damage, X in 1000.
+ plc.SKIN_BURNS = 1 //Plasma has an effect similar to mustard gas on the un-suited.
+ plc.EYE_BURNS = 1 //Plasma burns the eyes of anyone not wearing eye protection.
+ plc.PLASMA_HALLUCINATION = 1
+ plc.CONTAMINATION_LOSS = 0.075
+
+ if("ZAS - Normal")
+ airflow_lightest_pressure = 20
+ airflow_light_pressure = 35
+ airflow_medium_pressure = 50
+ airflow_heavy_pressure = 65
+ airflow_dense_pressure = 85
+ airflow_stun_pressure = 60
+ airflow_stun_cooldown = 60
+ airflow_stun = 1
+ airflow_damage = 2
+ airflow_speed_decay = 1.5
+ airflow_delay = 30
+ airflow_mob_slowdown = 1
+
+ if("ZAS - Forgiving")
+ airflow_lightest_pressure = 45
+ airflow_light_pressure = 60
+ airflow_medium_pressure = 120
+ airflow_heavy_pressure = 110
+ airflow_dense_pressure = 200
+ airflow_stun_pressure = 150
+ airflow_stun_cooldown = 90
+ airflow_stun = 0.15
+ airflow_damage = 0.15
+ airflow_speed_decay = 1.5
+ airflow_delay = 50
+ airflow_mob_slowdown = 0
+
+ if("ZAS - Dangerous")
+ airflow_lightest_pressure = 15
+ airflow_light_pressure = 30
+ airflow_medium_pressure = 45
+ airflow_heavy_pressure = 55
+ airflow_dense_pressure = 70
+ airflow_stun_pressure = 50
+ airflow_stun_cooldown = 50
+ airflow_stun = 2
+ airflow_damage = 3
+ airflow_speed_decay = 1.2
+ airflow_delay = 25
+ airflow_mob_slowdown = 2
+
+ if("ZAS - Hellish")
+ airflow_lightest_pressure = 20
+ airflow_light_pressure = 30
+ airflow_medium_pressure = 40
+ airflow_heavy_pressure = 50
+ airflow_dense_pressure = 60
+ airflow_stun_pressure = 40
+ airflow_stun_cooldown = 40
+ airflow_stun = 3
+ airflow_damage = 4
+ airflow_speed_decay = 1
+ airflow_delay = 20
+ airflow_mob_slowdown = 3
+ connection_insulation = 0
+
+
+ world << "\blue [key_name(user)] changed the global plasma/ZAS settings to \"[def]\""
+
+/pl_control/var/list/settings = list()
+
+/pl_control/New()
+ . = ..()
+ settings = vars.Copy()
+
+ var/datum/D = new() //Ensure only unique vars are put through by making a datum and removing all common vars.
+ for(var/V in D.vars)
+ settings -= V
+
+ for(var/V in settings)
+ if(findtextEx(V,"_RANDOM") || findtextEx(V,"_DESC"))
+ settings -= V
+
+ settings -= "settings"
+
+/pl_control/proc/Randomize(V)
+ var/newvalue
+ if("[V]_RANDOM" in vars)
+ if(isnum(vars["[V]_RANDOM"]))
+ newvalue = prob(vars["[V]_RANDOM"])
+ else if(istext(vars["[V]_RANDOM"]))
+ var/txt = vars["[V]_RANDOM"]
+ if(findtextEx(txt,"PROB"))
+ txt = text2list(txt,"/")
+ txt[1] = replacetext(txt[1],"PROB","")
+ var/p = text2num(txt[1])
+ var/r = txt[2]
+ if(prob(p))
+ newvalue = roll(r)
+ else
+ newvalue = vars[V]
+ else if(findtextEx(txt,"PICK"))
+ txt = replacetext(txt,"PICK","")
+ txt = text2list(txt,",")
+ newvalue = pick(txt)
+ else
+ newvalue = roll(txt)
+ else
+ newvalue = vars[V]
+ vars[V] = newvalue
diff --git a/code/ZAS/ZAS_Turfs.dm b/code/ZAS - oldbs12/ZAS_Turfs.dm
similarity index 100%
rename from code/ZAS/ZAS_Turfs.dm
rename to code/ZAS - oldbs12/ZAS_Turfs.dm
diff --git a/code/ZAS/ZAS_Zones.dm b/code/ZAS - oldbs12/ZAS_Zones.dm
similarity index 100%
rename from code/ZAS/ZAS_Zones.dm
rename to code/ZAS - oldbs12/ZAS_Zones.dm
diff --git a/code/ZAS/Airflow.dm b/code/ZAS/Airflow.dm
index efa45700f4e..ea839b87298 100644
--- a/code/ZAS/Airflow.dm
+++ b/code/ZAS/Airflow.dm
@@ -1,53 +1,8 @@
/*
-
-CONTAINS:
-All AirflowX() procs, all Variable Setting Controls for airflow, save/load variable tweaks for airflow.
-
-VARIABLES:
-
-atom/movable/airflow_dest
- The destination turf of a flying object.
-
-atom/movable/airflow_speed
- The speed (1-15) at which a flying object is traveling to airflow_dest. Decays over time.
-
-
-OVERLOADABLE PROCS:
-
-mob/airflow_stun()
- Contains checks for and results of being stunned by airflow.
- Called when airflow quantities exceed airflow_medium_pressure.
- RETURNS: Null
-
-atom/movable/check_airflow_movable(n)
- Contains checks for moving any object due to airflow.
- n is the pressure that is flowing.
- RETURNS: 1 if the object moves under the air conditions, 0 if it stays put.
-
-atom/movable/airflow_hit(atom/A)
- Contains results of hitting a solid object (A) due to airflow.
- A is the dense object hit.
- Use airflow_speed to determine how fast the projectile was going.
-
-
-AUTOMATIC PROCS:
-
-Airflow(zone/A, zone/B)
- Causes objects to fly along a pressure gradient.
- Called by zone updates. A and B are two connected zones.
-
-AirflowSpace(zone/A)
- Causes objects to fly into space.
- Called by zone updates. A is a zone connected to space.
-
-atom/movable/GotoAirflowDest(n)
-atom/movable/RepelAirflowDest(n)
- Called by main airflow procs to cause the object to fly to or away from destination at speed n.
- Probably shouldn't call this directly unless you know what you're
- doing and have set airflow_dest. airflow_hit() will be called if the object collides with an obstacle.
-
+Contains helper procs for airflow, handled in /connection_group.
*/
+
mob/var/tmp/last_airflow_stun = 0
mob/proc/airflow_stun()
if(stat == 2)
@@ -70,7 +25,7 @@ mob/living/carbon/human/airflow_stun()
if(last_airflow_stun > world.time - vsc.airflow_stun_cooldown) return 0
if(buckled) return 0
if(shoes)
- if((shoes.flags & NOSLIP) || (species.bodyflags & FEET_NOSLIP)) return 0
+ if(shoes.flags & NOSLIP) return 0
if(!(status_flags & CANSTUN) && !(status_flags & CANWEAKEN))
src << "\blue You stay upright as the air rushes past you."
return 0
@@ -108,124 +63,6 @@ obj/item/check_airflow_movable(n)
if(4,5)
if(n < vsc.airflow_medium_pressure) return 0
-//The main airflow code. Called by zone updates.
-//Zones A and B are air zones. n represents the amount of air moved.
-
-proc/Airflow(zone/A, zone/B)
-
- var/n = B.air.return_pressure() - A.air.return_pressure()
-
- //Don't go any further if n is lower than the lowest value needed for airflow.
- if(abs(n) < vsc.airflow_lightest_pressure) return
-
- //These turfs are the midway point between A and B, and will be the destination point for thrown objects.
- var/list/connection/connections_A = A.connections
- var/list/turf/connected_turfs = list()
- for(var/connection/C in connections_A) //Grab the turf that is in the zone we are flowing to (determined by n)
- if( ( A == C.A.zone || A == C.zone_A ) && ( B == C.B.zone || B == C.zone_B ) )
- if(n < 0)
- connected_turfs |= C.B
- else
- connected_turfs |= C.A
- else if( ( A == C.B.zone || A == C.zone_B ) && ( B == C.A.zone || B == C.zone_A ) )
- if(n < 0)
- connected_turfs |= C.A
- else
- connected_turfs |= C.B
-
- //Get lists of things that can be thrown across the room for each zone (assumes air is moving from zone B to zone A)
- var/list/air_sucked = B.movables()
- var/list/air_repelled = A.movables()
- if(n < 0)
- //air is moving from zone A to zone B
- var/list/temporary_pplz = air_sucked
- air_sucked = air_repelled
- air_repelled = temporary_pplz
-
- for(var/atom/movable/M in air_sucked)
-
- if(M.last_airflow > world.time - vsc.airflow_delay) continue
-
- //Check for knocking people over
- if(ismob(M) && n > vsc.airflow_stun_pressure)
- if(M:status_flags & GODMODE) continue
- M:airflow_stun()
-
- if(M.check_airflow_movable(n))
-
- //Check for things that are in range of the midpoint turfs.
- var/list/close_turfs = list()
- for(var/turf/U in connected_turfs)
- if(M in range(U)) close_turfs += U
- if(!close_turfs.len) continue
-
- //If they're already being tossed, don't do it again.
- if(!M.airflow_speed)
-
- M.airflow_dest = pick(close_turfs) //Pick a random midpoint to fly towards.
-
- spawn M.GotoAirflowDest(abs(n)/5)
-
- //Do it again for the stuff in the other zone, making it fly away.
- for(var/atom/movable/M in air_repelled)
-
- if(M.last_airflow > world.time - vsc.airflow_delay) continue
-
- if(ismob(M) && abs(n) > vsc.airflow_medium_pressure)
- if(M:status_flags & GODMODE) continue
- M:airflow_stun()
-
- if(M.check_airflow_movable(abs(n)))
-
- var/list/close_turfs = list()
- for(var/turf/U in connected_turfs)
- if(M in range(U)) close_turfs += U
- if(!close_turfs.len) continue
-
- //If they're already being tossed, don't do it again.
- if(!M.airflow_speed)
-
- M.airflow_dest = pick(close_turfs) //Pick a random midpoint to fly towards.
-
- spawn M.RepelAirflowDest(abs(n)/5)
-
-proc/AirflowSpace(zone/A)
-
- //The space version of the Airflow(A,B,n) proc.
-
- var/n = A.air.return_pressure()
- //Here, n is determined by only the pressure in the room.
-
- if(n < vsc.airflow_lightest_pressure) return
-
- var/list/connected_turfs = A.unsimulated_tiles //The midpoints are now all the space connections.
- var/list/pplz = A.movables() //We only need to worry about things in the zone, not things in space.
-
- for(var/atom/movable/M in pplz)
-
- if(M.last_airflow > world.time - vsc.airflow_delay) continue
-
- if(ismob(M) && n > vsc.airflow_stun_pressure)
- var/mob/O = M
- if(O.status_flags & GODMODE) continue
- O.airflow_stun()
-
- if(M.check_airflow_movable(n))
-
- var/list/close_turfs = list()
- for(var/turf/U in connected_turfs)
- if(M in range(U)) close_turfs += U
- if(!close_turfs.len) continue
-
- //If they're already being tossed, don't do it again.
- if(!M.airflow_speed)
-
- M.airflow_dest = pick(close_turfs) //Pick a random midpoint to fly towards.
- spawn
- if(M) M.GotoAirflowDest(n/10)
- //Sometimes shit breaks, and M isn't there after the spawn.
-
-
/atom/movable/var/tmp/turf/airflow_dest
/atom/movable/var/tmp/airflow_speed = 0
/atom/movable/var/tmp/airflow_time = 0
@@ -414,7 +251,6 @@ zone/proc/movables()
. = list()
for(var/turf/T in contents)
for(var/atom/A in T)
-// if(istype(A, /obj/effect) || istype(A, /mob/aiEye))
if(istype(A, /obj/effect) || istype(A, /mob/camera/aiEye))
continue
- . += A
+ . += A
\ No newline at end of file
diff --git a/code/ZAS/Atom.dm b/code/ZAS/Atom.dm
new file mode 100644
index 00000000000..b4d1de20af4
--- /dev/null
+++ b/code/ZAS/Atom.dm
@@ -0,0 +1,66 @@
+
+
+/atom/var/pressure_resistance = ONE_ATMOSPHERE
+
+atom/proc/CanPass(atom/movable/mover, turf/target, height=1.5, air_group = 0)
+ //Purpose: Determines if the object (or airflow) can pass this atom.
+ //Called by: Movement, airflow.
+ //Inputs: The moving atom (optional), target turf, "height" and air group
+ //Outputs: Boolean if can pass.
+
+ return (!density || !height || air_group)
+
+/turf/CanPass(atom/movable/mover, turf/target, height=1.5,air_group=0)
+ if(!target) return 0
+
+ if(istype(mover)) // turf/Enter(...) will perform more advanced checks
+ return !density
+
+ else // Now, doing more detailed checks for air movement and air group formation
+ if(target.blocks_air||blocks_air)
+ return 0
+
+ for(var/obj/obstacle in src)
+ if(!obstacle.CanPass(mover, target, height, air_group))
+ return 0
+ if(target != src)
+ for(var/obj/obstacle in target)
+ if(!obstacle.CanPass(mover, src, height, air_group))
+ return 0
+
+ return 1
+
+//Basically another way of calling CanPass(null, other, 0, 0) and CanPass(null, other, 1.5, 1).
+//Returns:
+// 0 - Not blocked
+// AIR_BLOCKED - Blocked
+// ZONE_BLOCKED - Not blocked, but zone boundaries will not cross.
+// BLOCKED - Blocked, zone boundaries will not cross even if opened.
+atom/proc/c_airblock(turf/other)
+ #ifdef ZASDBG
+ ASSERT(isturf(other))
+ #endif
+ return !CanPass(null, other, 0, 0) + 2*!CanPass(null, other, 1.5, 1)
+
+
+turf/c_airblock(turf/other)
+ #ifdef ZASDBG
+ ASSERT(isturf(other))
+ #endif
+ if(blocks_air)
+ return BLOCKED
+
+ //Z-level handling code. Always block if there isn't an open space.
+ #ifdef ZLEVELS
+ if(other.z != src.z)
+ if(other.z < src.z)
+ if(!istype(src, /turf/simulated/floor/open)) return BLOCKED
+ else
+ if(!istype(other, /turf/simulated/floor/open)) return BLOCKED
+ #endif
+
+ var/result = 0
+ for(var/atom/movable/M in contents)
+ result |= M.c_airblock(other)
+ if(result == BLOCKED) return BLOCKED
+ return result
\ No newline at end of file
diff --git a/code/ZAS/Connection.dm b/code/ZAS/Connection.dm
index 1bb8f9ca54d..b9cf2278a18 100644
--- a/code/ZAS/Connection.dm
+++ b/code/ZAS/Connection.dm
@@ -1,448 +1,165 @@
+#define CONNECTION_DIRECT 2
+#define CONNECTION_SPACE 4
+#define CONNECTION_INVALID 8
+
/*
-This object is contained within zone/var/connections. It's generated whenever two turfs from different zones are linked.
-Indirect connections will not merge the two zones after they reach equilibrium.
+
+Overview:
+ Connections are made between turfs by air_master.connect(). They represent a single point where two zones converge.
+
+Class Vars:
+ A - Always a simulated turf.
+ B - A simulated or unsimulated turf.
+
+ zoneA - The archived zone of A. Used to check that the zone hasn't changed.
+ zoneB - The archived zone of B. May be null in case of unsimulated connections.
+
+ edge - Stores the edge this connection is in. Can reference an edge that is no longer processed
+ after this connection is removed, so make sure to check edge.coefficient > 0 before re-adding it.
+
+Class Procs:
+
+ mark_direct()
+ Marks this connection as direct. Does not update the edge.
+ Called when the connection is made and there are no doors between A and B.
+ Also called by update() as a correction.
+
+ mark_indirect()
+ Unmarks this connection as direct. Does not update the edge.
+ Called by update() as a correction.
+
+ mark_space()
+ Marks this connection as unsimulated. Updating the connection will check the validity of this.
+ Called when the connection is made.
+ This will not be called as a correction, any connections failing a check against this mark are erased and rebuilt.
+
+ direct()
+ Returns 1 if no doors are in between A and B.
+
+ valid()
+ Returns 1 if the connection has not been erased.
+
+ erase()
+ Called by update() and connection_manager/erase_all().
+ Marks the connection as erased and removes it from its edge.
+
+ update()
+ Called by connection_manager/update_all().
+ Makes numerous checks to decide whether the connection is still valid. Erases it automatically if not.
+
*/
-#define CONNECTION_DIRECT 2
-#define CONNECTION_INDIRECT 1
-#define CONNECTION_CLOSED 0
-/connection
- var/turf/simulated/A
- var/turf/simulated/B
+/connection/var/turf/simulated/A
+/connection/var/turf/simulated/B
+/connection/var/zone/zoneA
+/connection/var/zone/zoneB
- var/zone/zone_A
- var/zone/zone_B
+/connection/var/connection_edge/edge
- var/indirect = CONNECTION_DIRECT //If the connection is purely indirect, the zones should not join.
-
-
-/connection/New(turf/T,turf/O)
- . = ..()
-
- A = T
- B = O
-
- if(A.zone && B.zone)
- if(!A.zone.connections)
- A.zone.connections = list()
- A.zone.connections += src
- zone_A = A.zone
-
- if(!B.zone.connections)
- B.zone.connections = list()
- B.zone.connections += src
- zone_B = B.zone
-
- if(A in air_master.turfs_with_connections)
- var/list/connections = air_master.turfs_with_connections[A]
- connections.Add(src)
- else
- air_master.turfs_with_connections[A] = list(src)
-
- if(B in air_master.turfs_with_connections)
- var/list/connections = air_master.turfs_with_connections[B]
- connections.Add(src)
- else
- air_master.turfs_with_connections[B] = list(src)
-
- if(A.CanPass(null, B, 0, 0))
-
- if(!A.CanPass(null, B, 1.5, 1))
- indirect = CONNECTION_INDIRECT
-
- ConnectZones(A.zone, B.zone, indirect)
-
- else
- ConnectZones(A.zone, B.zone)
- indirect = CONNECTION_CLOSED
+/connection/var/state = 0
+/connection/New(turf/simulated/A, turf/simulated/B)
+ #ifdef ZASDBG
+ ASSERT(air_master.has_valid_zone(A))
+ //ASSERT(air_master.has_valid_zone(B))
+ #endif
+ src.A = A
+ src.B = B
+ zoneA = A.zone
+ if(!istype(B))
+ mark_space()
+ edge = air_master.get_edge(A.zone,B)
+ edge.add_connection(src)
else
- world.log << "Attempted to create connection object for non-zone tiles: [T] ([T.x],[T.y],[T.z]) -> [O] ([O.x],[O.y],[O.z])"
- SoftDelete()
+ zoneB = B.zone
+ edge = air_master.get_edge(A.zone,B.zone)
+ edge.add_connection(src)
+/connection/proc/mark_direct()
+ state |= CONNECTION_DIRECT
+ //world << "Marked direct."
-/connection/Del()
- //remove connections from master lists.
- if(B in air_master.turfs_with_connections)
- var/list/connections = air_master.turfs_with_connections[B]
- connections.Remove(src)
+/connection/proc/mark_indirect()
+ state &= ~CONNECTION_DIRECT
+ //world << "Marked indirect."
- if(A in air_master.turfs_with_connections)
- var/list/connections = air_master.turfs_with_connections[A]
- connections.Remove(src)
+/connection/proc/mark_space()
+ state |= CONNECTION_SPACE
- //Remove connection from zones.
- if(A)
- if(A.zone && A.zone.connections)
- A.zone.connections.Remove(src)
- if(!A.zone.connections.len)
- A.zone.connections = null
+/connection/proc/direct()
+ return (state & CONNECTION_DIRECT)
- if(istype(zone_A) && (!A || A.zone != zone_A))
- if(zone_A.connections)
- zone_A.connections.Remove(src)
- if(!zone_A.connections.len)
- zone_A.connections = null
+/connection/proc/valid()
+ return !(state & CONNECTION_INVALID)
- if(B)
- if(B.zone && B.zone.connections)
- B.zone.connections.Remove(src)
- if(!B.zone.connections.len)
- B.zone.connections = null
+/connection/proc/erase()
+ edge.remove_connection(src)
+ state |= CONNECTION_INVALID
+ //world << "Connection Erased: [state]"
- if(istype(zone_B) && (!B || B.zone != zone_B))
- if(zone_B.connections)
- zone_B.connections.Remove(src)
- if(!zone_B.connections.len)
- zone_B.connections = null
-
- //Disconnect zones while handling unusual conditions.
- // e.g. loss of a zone on a turf
- DisconnectZones(zone_A, zone_B)
-
- //Finally, preform actual deletion.
- . = ..()
-
-
-/connection/proc/SoftDelete()
- //remove connections from master lists.
- if(B in air_master.turfs_with_connections)
- var/list/connections = air_master.turfs_with_connections[B]
- connections.Remove(src)
-
- if(A in air_master.turfs_with_connections)
- var/list/connections = air_master.turfs_with_connections[A]
- connections.Remove(src)
-
- //Remove connection from zones.
- if(A)
- if(A.zone && A.zone.connections)
- A.zone.connections.Remove(src)
- if(!A.zone.connections.len)
- A.zone.connections = null
-
- if(istype(zone_A) && (!A || A.zone != zone_A))
- if(zone_A.connections)
- zone_A.connections.Remove(src)
- if(!zone_A.connections.len)
- zone_A.connections = null
-
- if(B)
- if(B.zone && B.zone.connections)
- B.zone.connections.Remove(src)
- if(!B.zone.connections.len)
- B.zone.connections = null
-
- if(istype(zone_B) && (!B || B.zone != zone_B))
- if(zone_B.connections)
- zone_B.connections.Remove(src)
- if(!zone_B.connections.len)
- zone_B.connections = null
-
- //Disconnect zones while handling unusual conditions.
- // e.g. loss of a zone on a turf
- DisconnectZones(zone_A, zone_B)
-
-
-/connection/proc/ConnectZones(var/zone/zone_1, var/zone/zone_2, open = 0)
-
- //Sanity checking
- if(!istype(zone_1) || !istype(zone_2))
+/connection/proc/update()
+ //world << "Updated, \..."
+ if(!istype(A,/turf/simulated))
+ //world << "Invalid A."
+ erase()
return
- //Handle zones connecting indirectly/directly.
- if(open)
-
- //Create the lists if necessary.
- if(!zone_1.connected_zones)
- zone_1.connected_zones = list()
-
- if(!zone_2.connected_zones)
- zone_2.connected_zones = list()
-
- //Increase the number of connections between zones.
- if(zone_2 in zone_1.connected_zones)
- zone_1.connected_zones[zone_2]++
- else
- zone_1.connected_zones += zone_2
- zone_1.connected_zones[zone_2] = 1
-
- if(zone_1 in zone_2.connected_zones)
- zone_2.connected_zones[zone_1]++
- else
- zone_2.connected_zones += zone_1
- zone_2.connected_zones[zone_1] = 1
-
- if(open == CONNECTION_DIRECT)
- if(!zone_1.direct_connections)
- zone_1.direct_connections = list(src)
- else
- zone_1.direct_connections += src
-
- if(!zone_2.direct_connections)
- zone_2.direct_connections = list(src)
- else
- zone_2.direct_connections += src
-
-
- //Handle closed connections.
- else
-
- //Create the lists
- if(!zone_1.closed_connection_zones)
- zone_1.closed_connection_zones = list()
-
- if(!zone_2.closed_connection_zones)
- zone_2.closed_connection_zones = list()
-
- //Increment the connections.
- if(zone_2 in zone_1.closed_connection_zones)
- zone_1.closed_connection_zones[zone_2]++
- else
- zone_1.closed_connection_zones += zone_2
- zone_1.closed_connection_zones[zone_2] = 1
-
- if(zone_1 in zone_2.closed_connection_zones)
- zone_2.closed_connection_zones[zone_1]++
- else
- zone_2.closed_connection_zones += zone_1
- zone_2.closed_connection_zones[zone_1] = 1
-
- if(zone_1.status == ZONE_SLEEPING)
- zone_1.SetStatus(ZONE_ACTIVE)
-
- if(zone_2.status == ZONE_SLEEPING)
- zone_2.SetStatus(ZONE_ACTIVE)
-
-/connection/proc/DisconnectZones(var/zone/zone_1, var/zone/zone_2)
- //Sanity checking
- if(!istype(zone_1) || !istype(zone_2))
+ var/block_status = air_master.air_blocked(A,B)
+ if(block_status & AIR_BLOCKED)
+ //world << "Blocked connection."
+ erase()
return
-
- if(indirect != CONNECTION_CLOSED)
- //Handle disconnection of indirectly or directly connected zones.
- if( (zone_1 in zone_2.connected_zones) || (zone_2 in zone_1.connected_zones) )
-
- //If there are more than one connection, decrement the number of connections
- //Otherwise, remove all connections between the zones.
- if(zone_2 in zone_1.connected_zones)
- if(zone_1.connected_zones[zone_2] > 1)
- zone_1.connected_zones[zone_2]--
- else
- zone_1.connected_zones -= zone_2
- //remove the list if it is empty
- if(!zone_1.connected_zones.len)
- zone_1.connected_zones = null
-
- //Then do the same for the other zone.
- if(zone_1 in zone_2.connected_zones)
- if(zone_2.connected_zones[zone_1] > 1)
- zone_2.connected_zones[zone_1]--
- else
- zone_2.connected_zones -= zone_1
- if(!zone_2.connected_zones.len)
- zone_2.connected_zones = null
-
- if(indirect == CONNECTION_DIRECT)
- zone_1.direct_connections -= src
- if(!zone_1.direct_connections.len)
- zone_1.direct_connections = null
-
- zone_2.direct_connections -= src
- if(!zone_2.direct_connections.len)
- zone_2.direct_connections = null
-
- else
- //Handle disconnection of closed zones.
- if( (zone_1 in zone_2.closed_connection_zones) || (zone_2 in zone_1.closed_connection_zones) )
-
- //If there are more than one connection, decrement the number of connections
- //Otherwise, remove all connections between the zones.
- if(zone_2 in zone_1.closed_connection_zones)
- if(zone_1.closed_connection_zones[zone_2] > 1)
- zone_1.closed_connection_zones[zone_2]--
- else
- zone_1.closed_connection_zones -= zone_2
- //remove the list if it is empty
- if(!zone_1.closed_connection_zones.len)
- zone_1.closed_connection_zones = null
-
- //Then do the same for the other zone.
- if(zone_1 in zone_2.closed_connection_zones)
- if(zone_2.closed_connection_zones[zone_1] > 1)
- zone_2.closed_connection_zones[zone_1]--
- else
- zone_2.closed_connection_zones -= zone_1
- if(!zone_2.closed_connection_zones.len)
- zone_2.closed_connection_zones = null
-
-
-/connection/proc/Cleanup()
-
- //Check sanity: existance of turfs
- if(!A || !B)
- SoftDelete()
- return
-
- //Check sanity: loss of zone
- if(!A.zone || !B.zone)
- SoftDelete()
- return
-
- //Check sanity: zones are different
- if(A.zone == B.zone)
- SoftDelete()
- return
-
- //Handle zones changing on a turf.
- if((A.zone && A.zone != zone_A) || (B.zone && B.zone != zone_B))
- Sanitize()
-
- if(A.zone && B.zone)
-
- //If no walls are blocking us...
- if(A.ZAirPass(B))
- //...we check to see if there is a door in the way...
- var/door_pass = A.CanPass(null,B,1.5,1)
- //...and if it is opened.
- if(door_pass || A.CanPass(null,B,0,0))
-
- //Make and remove connections to let air pass.
- if(indirect == CONNECTION_CLOSED)
- DisconnectZones(A.zone, B.zone)
- ConnectZones(A.zone, B.zone, door_pass + 1)
-
- if(door_pass)
- indirect = CONNECTION_DIRECT
- else if(!door_pass)
- indirect = CONNECTION_INDIRECT
-
- //The door is instead closed.
- else if(indirect > CONNECTION_CLOSED)
- DisconnectZones(A.zone, B.zone)
- indirect = CONNECTION_CLOSED
- ConnectZones(A.zone, B.zone)
-
- //If I can no longer pass air, better delete
+ else if(block_status & ZONE_BLOCKED)
+ if(direct())
+ mark_indirect()
else
- SoftDelete()
+ mark_direct()
+
+ var/b_is_space = !istype(B,/turf/simulated)
+
+ if(state & CONNECTION_SPACE)
+ if(!b_is_space)
+ //world << "Invalid B."
+ erase()
return
-
-/connection/proc/Sanitize()
- //If the zones change on connected turfs, update it.
-
- //Both zones changed (wat)
- if(A.zone && A.zone != zone_A && B.zone && B.zone != zone_B)
-
- //If the zones have gotten swapped
- // (do not ask me how, I am just being anal retentive about sanity)
- if(A.zone == zone_B && B.zone == zone_A)
- var/turf/temp = B
- B = A
- A = temp
- zone_B = B.zone
- zone_A = A.zone
- return
-
- //Handle removal of connections from archived zones.
- if(zone_A && zone_A.connections)
- zone_A.connections.Remove(src)
- if(!zone_A.connections.len)
- zone_A.connections = null
-
- if(zone_B && zone_B.connections)
- zone_B.connections.Remove(src)
- if(!zone_B.connections.len)
- zone_B.connections = null
-
- if(A.zone)
- if(!A.zone.connections)
- A.zone.connections = list()
- A.zone.connections |= src
-
- if(B.zone)
- if(!B.zone.connections)
- B.zone.connections = list()
- B.zone.connections |= src
-
- //If either zone is null, we disconnect the archived ones after cleaning up the connections.
- if(!A.zone || !B.zone)
- if(zone_A && zone_B)
- DisconnectZones(zone_B, zone_A)
-
+ if(A.zone != zoneA)
+ //world << "Zone changed, \..."
if(!A.zone)
- zone_A = A.zone
+ erase()
+ //world << "erased."
+ return
+ else
+ edge.remove_connection(src)
+ edge = air_master.get_edge(A.zone, B)
+ edge.add_connection(src)
+ zoneA = A.zone
- if(!B.zone)
- zone_B = B.zone
+ //world << "valid."
+ return
+
+ else if(b_is_space)
+ //world << "Invalid B."
+ erase()
+ return
+
+ if(A.zone == B.zone)
+ //world << "A == B"
+ erase()
+ return
+
+ if(A.zone != zoneA || (zoneB && (B.zone != zoneB)))
+
+ //world << "Zones changed, \..."
+ if(A.zone && B.zone)
+ edge.remove_connection(src)
+ edge = air_master.get_edge(A.zone, B.zone)
+ edge.add_connection(src)
+ zoneA = A.zone
+ zoneB = B.zone
+ else
+ //world << "erased."
+ erase()
return
- //Handle diconnection and reconnection of zones.
- if(zone_A && zone_B)
- DisconnectZones(zone_A, zone_B)
- ConnectZones(A.zone, B.zone, indirect)
- //resetting values of archived values.
- zone_B = B.zone
- zone_A = A.zone
-
- //The "A" zone changed.
- else if(A.zone && A.zone != zone_A)
-
- //Handle connection cleanup
- if(zone_A)
- if(zone_A.connections)
- zone_A.connections.Remove(src)
- if(!zone_A.connections.len)
- zone_A.connections = null
-
- if(A.zone)
- if(!A.zone.connections)
- A.zone.connections = list()
- A.zone.connections |= src
-
- //If the "A" zone is null, we disconnect the archived ones after cleaning up the connections.
- if(!A.zone)
- if(zone_A && zone_B)
- DisconnectZones(zone_A, zone_B)
- zone_A = A.zone
- return
-
- //Handle diconnection and reconnection of zones.
- if(zone_A && zone_B)
- DisconnectZones(zone_A, zone_B)
- ConnectZones(A.zone, B.zone, indirect)
- zone_A = A.zone
-
- //The "B" zone changed.
- else if(B.zone && B.zone != zone_B)
-
- //Handle connection cleanup
- if(zone_B)
- if(zone_B.connections)
- zone_B.connections.Remove(src)
- if(!zone_B.connections.len)
- zone_B.connections = null
-
- if(B.zone)
- if(!B.zone.connections)
- B.zone.connections = list()
- B.zone.connections |= src
-
- //If the "B" zone is null, we disconnect the archived ones after cleaning up the connections.
- if(!B.zone)
- if(zone_A && zone_B)
- DisconnectZones(zone_A, zone_B)
- zone_B = B.zone
- return
-
- //Handle diconnection and reconnection of zones.
- if(zone_A && zone_B)
- DisconnectZones(zone_A, zone_B)
- ConnectZones(A.zone, B.zone, indirect)
- zone_B = B.zone
-
-
-#undef CONNECTION_DIRECT
-#undef CONNECTION_INDIRECT
-#undef CONNECTION_CLOSED
+ //world << "valid."
\ No newline at end of file
diff --git a/code/ZAS/ConnectionGroup.dm b/code/ZAS/ConnectionGroup.dm
new file mode 100644
index 00000000000..1486f5088b6
--- /dev/null
+++ b/code/ZAS/ConnectionGroup.dm
@@ -0,0 +1,418 @@
+/*
+
+Overview:
+ These are what handle gas transfers between zones and into space.
+ They are found in a zone's edges list and in air_master.edges.
+ Each edge updates every air tick due to their role in gas transfer.
+ They come in two flavors, /connection_edge/zone and /connection_edge/unsimulated.
+ As the type names might suggest, they handle inter-zone and spacelike connections respectively.
+
+Class Vars:
+
+ A - This always holds a zone. In unsimulated edges, it holds the only zone.
+
+ connecting_turfs - This holds a list of connected turfs, mainly for the sake of airflow.
+
+ coefficent - This is a marker for how many connections are on this edge. Used to determine the ratio of flow.
+
+ connection_edge/zone
+
+ B - This holds the second zone with which the first zone equalizes.
+
+ direct - This counts the number of direct (i.e. with no doors) connections on this edge.
+ Any value of this is sufficient to make the zones mergeable.
+
+ connection_edge/unsimulated
+
+ B - This holds an unsimulated turf which has the gas values this edge is mimicing.
+
+ air - Retrieved from B on creation and used as an argument for the legacy ShareSpace() proc.
+
+Class Procs:
+
+ add_connection(connection/c)
+ Adds a connection to this edge. Usually increments the coefficient and adds a turf to connecting_turfs.
+
+ remove_connection(connection/c)
+ Removes a connection from this edge. This works even if c is not in the edge, so be careful.
+ If the coefficient reaches zero as a result, the edge is erased.
+
+ contains_zone(zone/Z)
+ Returns true if either A or B is equal to Z. Unsimulated connections return true only on A.
+
+ erase()
+ Removes this connection from processing and zone edge lists.
+
+ tick()
+ Called every air tick on edges in the processing list. Equalizes gas.
+
+ flow(list/movable, differential, repelled)
+ Airflow proc causing all objects in movable to be checked against a pressure differential.
+ If repelled is true, the objects move away from any turf in connecting_turfs, otherwise they approach.
+ A check against vsc.lightest_airflow_pressure should generally be performed before calling this.
+
+ get_connected_zone(zone/from)
+ Helper proc that allows getting the other zone of an edge given one of them.
+ Only on /connection_edge/zone, otherwise use A.
+
+*/
+
+
+/connection_edge/var/zone/A
+
+/connection_edge/var/list/connecting_turfs = list()
+
+/connection_edge/var/coefficient = 0
+
+/connection_edge/New()
+ CRASH("Cannot make connection edge without specifications.")
+
+/connection_edge/proc/add_connection(connection/c)
+ coefficient++
+ //world << "Connection added: [type] Coefficient: [coefficient]"
+
+/connection_edge/proc/remove_connection(connection/c)
+ //world << "Connection removed: [type] Coefficient: [coefficient-1]"
+ coefficient--
+ if(coefficient <= 0)
+ erase()
+
+/connection_edge/proc/contains_zone(zone/Z)
+
+/connection_edge/proc/erase()
+ air_master.remove_edge(src)
+ //world << "[type] Erased."
+
+/connection_edge/proc/tick()
+
+/connection_edge/proc/flow(list/movable, differential, repelled)
+ for(var/atom/movable/M in movable)
+
+ //If they're already being tossed, don't do it again.
+ if(M.last_airflow > world.time - vsc.airflow_delay) continue
+ if(M.airflow_speed) continue
+
+ //Check for knocking people over
+ if(ismob(M) && differential > vsc.airflow_stun_pressure)
+ if(M:status_flags & GODMODE) continue
+ M:airflow_stun()
+
+ if(M.check_airflow_movable(differential))
+ //Check for things that are in range of the midpoint turfs.
+ var/list/close_turfs = list()
+ for(var/turf/U in connecting_turfs)
+ if(get_dist(M,U) < world.view) close_turfs += U
+ if(!close_turfs.len) continue
+
+ M.airflow_dest = pick(close_turfs) //Pick a random midpoint to fly towards.
+
+ if(repelled) spawn if(M) M.RepelAirflowDest(differential/5)
+ else spawn if(M) M.GotoAirflowDest(differential/10)
+
+
+
+
+/connection_edge/zone/var/zone/B
+/connection_edge/zone/var/direct = 0
+
+/connection_edge/zone/New(zone/A, zone/B)
+
+ src.A = A
+ src.B = B
+ A.edges.Add(src)
+ B.edges.Add(src)
+ //id = edge_id(A,B)
+ //world << "New edge between [A] and [B]"
+
+/connection_edge/zone/add_connection(connection/c)
+ . = ..()
+ connecting_turfs.Add(c.A)
+ if(c.direct()) direct++
+
+/connection_edge/zone/remove_connection(connection/c)
+ connecting_turfs.Remove(c.A)
+ if(c.direct()) direct--
+ . = ..()
+
+/connection_edge/zone/contains_zone(zone/Z)
+ return A == Z || B == Z
+
+/connection_edge/zone/erase()
+ A.edges.Remove(src)
+ B.edges.Remove(src)
+ . = ..()
+
+/connection_edge/zone/tick()
+ if(A.invalid || B.invalid)
+ erase()
+ return
+ //world << "[id]: Tick [air_master.current_cycle]: \..."
+ if(direct)
+ if(air_master.equivalent_pressure(A, B))
+ //world << "merged."
+ erase()
+ air_master.merge(A, B)
+ //world << "zones merged."
+ return
+
+ //air_master.equalize(A, B)
+ ShareRatio(A.air,B.air,coefficient)
+ air_master.mark_zone_update(A)
+ air_master.mark_zone_update(B)
+ //world << "equalized."
+
+ var/differential = A.air.return_pressure() - B.air.return_pressure()
+ if(abs(differential) < vsc.airflow_lightest_pressure) return
+
+ var/list/attracted
+ var/list/repelled
+ if(differential > 0)
+ attracted = A.movables()
+ repelled = B.movables()
+ else
+ attracted = B.movables()
+ repelled = A.movables()
+
+ flow(attracted, abs(differential), 0)
+ flow(repelled, abs(differential), 1)
+
+//Helper proc to get connections for a zone.
+/connection_edge/zone/proc/get_connected_zone(zone/from)
+ if(A == from) return B
+ else return A
+
+/connection_edge/unsimulated/var/turf/B
+/connection_edge/unsimulated/var/datum/gas_mixture/air
+
+/connection_edge/unsimulated/New(zone/A, turf/B)
+ src.A = A
+ src.B = B
+ A.edges.Add(src)
+ air = B.return_air()
+ //id = 52*A.id
+ //world << "New edge from [A.id] to [B]."
+
+/connection_edge/unsimulated/add_connection(connection/c)
+ . = ..()
+ connecting_turfs.Add(c.B)
+ air.group_multiplier = coefficient
+
+/connection_edge/unsimulated/remove_connection(connection/c)
+ connecting_turfs.Remove(c.B)
+ air.group_multiplier = coefficient
+ . = ..()
+
+/connection_edge/unsimulated/erase()
+ A.edges.Remove(src)
+ . = ..()
+
+/connection_edge/unsimulated/contains_zone(zone/Z)
+ return A == Z
+
+/connection_edge/unsimulated/tick()
+ if(A.invalid)
+ erase()
+ return
+ //world << "[id]: Tick [air_master.current_cycle]: To [B]!"
+ //A.air.mimic(B, coefficient)
+ ShareSpace(A.air,air)
+ air_master.mark_zone_update(A)
+
+ var/differential = A.air.return_pressure() - air.return_pressure()
+ if(abs(differential) < vsc.airflow_lightest_pressure) return
+
+ var/list/attracted = A.movables()
+ flow(attracted, abs(differential), differential < 0)
+
+var/list/sharing_lookup_table = list(0.30, 0.40, 0.48, 0.54, 0.60, 0.66)
+
+proc/ShareRatio(datum/gas_mixture/A, datum/gas_mixture/B, connecting_tiles)
+ //Shares a specific ratio of gas between mixtures using simple weighted averages.
+ var
+ //WOOT WOOT TOUCH THIS AND YOU ARE A RETARD
+ ratio = sharing_lookup_table[6]
+ //WOOT WOOT TOUCH THIS AND YOU ARE A RETARD
+
+ size = max(1,A.group_multiplier)
+ share_size = max(1,B.group_multiplier)
+
+ full_oxy = A.oxygen * size
+ full_nitro = A.nitrogen * size
+ full_co2 = A.carbon_dioxide * size
+ full_plasma = A.toxins * size
+
+ full_heat_capacity = A.heat_capacity() * size
+
+ s_full_oxy = B.oxygen * share_size
+ s_full_nitro = B.nitrogen * share_size
+ s_full_co2 = B.carbon_dioxide * share_size
+ s_full_plasma = B.toxins * share_size
+
+ s_full_heat_capacity = B.heat_capacity() * share_size
+
+ oxy_avg = (full_oxy + s_full_oxy) / (size + share_size)
+ nit_avg = (full_nitro + s_full_nitro) / (size + share_size)
+ co2_avg = (full_co2 + s_full_co2) / (size + share_size)
+ plasma_avg = (full_plasma + s_full_plasma) / (size + share_size)
+
+ temp_avg = (A.temperature * full_heat_capacity + B.temperature * s_full_heat_capacity) / (full_heat_capacity + s_full_heat_capacity)
+
+ //WOOT WOOT TOUCH THIS AND YOU ARE A RETARD
+ if(sharing_lookup_table.len >= connecting_tiles) //6 or more interconnecting tiles will max at 42% of air moved per tick.
+ ratio = sharing_lookup_table[connecting_tiles]
+ //WOOT WOOT TOUCH THIS AND YOU ARE A RETARD
+
+ A.oxygen = max(0, (A.oxygen - oxy_avg) * (1-ratio) + oxy_avg )
+ A.nitrogen = max(0, (A.nitrogen - nit_avg) * (1-ratio) + nit_avg )
+ A.carbon_dioxide = max(0, (A.carbon_dioxide - co2_avg) * (1-ratio) + co2_avg )
+ A.toxins = max(0, (A.toxins - plasma_avg) * (1-ratio) + plasma_avg )
+
+ A.temperature = max(0, (A.temperature - temp_avg) * (1-ratio) + temp_avg )
+
+ B.oxygen = max(0, (B.oxygen - oxy_avg) * (1-ratio) + oxy_avg )
+ B.nitrogen = max(0, (B.nitrogen - nit_avg) * (1-ratio) + nit_avg )
+ B.carbon_dioxide = max(0, (B.carbon_dioxide - co2_avg) * (1-ratio) + co2_avg )
+ B.toxins = max(0, (B.toxins - plasma_avg) * (1-ratio) + plasma_avg )
+
+ B.temperature = max(0, (B.temperature - temp_avg) * (1-ratio) + temp_avg )
+
+ for(var/datum/gas/G in A.trace_gases)
+ var/datum/gas/H = locate(G.type) in B.trace_gases
+ if(H)
+ var/G_avg = (G.moles*size + H.moles*share_size) / (size+share_size)
+ G.moles = (G.moles - G_avg) * (1-ratio) + G_avg
+
+ H.moles = (H.moles - G_avg) * (1-ratio) + G_avg
+ else
+ H = new G.type
+ B.trace_gases += H
+ var/G_avg = (G.moles*size) / (size+share_size)
+ G.moles = (G.moles - G_avg) * (1-ratio) + G_avg
+ H.moles = (H.moles - G_avg) * (1-ratio) + G_avg
+
+ for(var/datum/gas/G in B.trace_gases)
+ var/datum/gas/H = locate(G.type) in A.trace_gases
+ if(!H)
+ H = new G.type
+ A.trace_gases += H
+ var/G_avg = (G.moles*size) / (size+share_size)
+ G.moles = (G.moles - G_avg) * (1-ratio) + G_avg
+ H.moles = (H.moles - G_avg) * (1-ratio) + G_avg
+
+ A.update_values()
+ B.update_values()
+
+ if(A.compare(B)) return 1
+ else return 0
+
+proc/ShareSpace(datum/gas_mixture/A, list/unsimulated_tiles, dbg_output)
+ //A modified version of ShareRatio for spacing gas at the same rate as if it were going into a large airless room.
+ if(!unsimulated_tiles)
+ return 0
+
+ var
+ unsim_oxygen = 0
+ unsim_nitrogen = 0
+ unsim_co2 = 0
+ unsim_plasma = 0
+ unsim_heat_capacity = 0
+ unsim_temperature = 0
+
+ size = max(1,A.group_multiplier)
+
+ var/tileslen
+ var/share_size
+
+ if(istype(unsimulated_tiles, /datum/gas_mixture))
+ var/datum/gas_mixture/avg_unsim = unsimulated_tiles
+ unsim_oxygen = avg_unsim.oxygen
+ unsim_co2 = avg_unsim.carbon_dioxide
+ unsim_nitrogen = avg_unsim.nitrogen
+ unsim_plasma = avg_unsim.toxins
+ unsim_temperature = avg_unsim.temperature
+ share_size = max(1, max(size + 3, 1) + avg_unsim.group_multiplier)
+ tileslen = avg_unsim.group_multiplier
+
+ else if(istype(unsimulated_tiles, /list))
+ if(!unsimulated_tiles.len)
+ return 0
+ // We use the same size for the potentially single space tile
+ // as we use for the entire room. Why is this?
+ // Short answer: We do not want larger rooms to depressurize more
+ // slowly than small rooms, preserving our good old "hollywood-style"
+ // oh-shit effect when large rooms get breached, but still having small
+ // rooms remain pressurized for long enough to make escape possible.
+ share_size = max(1, max(size + 3, 1) + unsimulated_tiles.len)
+ var/correction_ratio = share_size / unsimulated_tiles.len
+
+ for(var/turf/T in unsimulated_tiles)
+ unsim_oxygen += T.oxygen
+ unsim_co2 += T.carbon_dioxide
+ unsim_nitrogen += T.nitrogen
+ unsim_plasma += T.toxins
+ unsim_temperature += T.temperature/unsimulated_tiles.len
+
+ //These values require adjustment in order to properly represent a room of the specified size.
+ unsim_oxygen *= correction_ratio
+ unsim_co2 *= correction_ratio
+ unsim_nitrogen *= correction_ratio
+ unsim_plasma *= correction_ratio
+ tileslen = unsimulated_tiles.len
+
+ else //invalid input type
+ return 0
+
+ unsim_heat_capacity = HEAT_CAPACITY_CALCULATION(unsim_oxygen, unsim_co2, unsim_nitrogen, unsim_plasma)
+
+ var
+ ratio = sharing_lookup_table[6]
+
+ old_pressure = A.return_pressure()
+
+ full_oxy = A.oxygen * size
+ full_nitro = A.nitrogen * size
+ full_co2 = A.carbon_dioxide * size
+ full_plasma = A.toxins * size
+
+ full_heat_capacity = A.heat_capacity() * size
+
+ oxy_avg = (full_oxy + unsim_oxygen) / (size + share_size)
+ nit_avg = (full_nitro + unsim_nitrogen) / (size + share_size)
+ co2_avg = (full_co2 + unsim_co2) / (size + share_size)
+ plasma_avg = (full_plasma + unsim_plasma) / (size + share_size)
+
+ temp_avg = 0
+
+ if((full_heat_capacity + unsim_heat_capacity) > 0)
+ temp_avg = (A.temperature * full_heat_capacity + unsim_temperature * unsim_heat_capacity) / (full_heat_capacity + unsim_heat_capacity)
+
+ if(sharing_lookup_table.len >= tileslen) //6 or more interconnecting tiles will max at 42% of air moved per tick.
+ ratio = sharing_lookup_table[tileslen]
+
+ A.oxygen = max(0, (A.oxygen - oxy_avg) * (1 - ratio) + oxy_avg )
+ A.nitrogen = max(0, (A.nitrogen - nit_avg) * (1 - ratio) + nit_avg )
+ A.carbon_dioxide = max(0, (A.carbon_dioxide - co2_avg) * (1 - ratio) + co2_avg )
+ A.toxins = max(0, (A.toxins - plasma_avg) * (1 - ratio) + plasma_avg )
+
+ A.temperature = max(TCMB, (A.temperature - temp_avg) * (1 - ratio) + temp_avg )
+
+ for(var/datum/gas/G in A.trace_gases)
+ var/G_avg = (G.moles * size) / (size + share_size)
+ G.moles = (G.moles - G_avg) * (1 - ratio) + G_avg
+
+ A.update_values()
+
+ return abs(old_pressure - A.return_pressure())
+
+
+proc/ShareHeat(datum/gas_mixture/A, datum/gas_mixture/B, connecting_tiles)
+ //This implements a simplistic version of the Stefan-Boltzmann law.
+ var/energy_delta = ((A.temperature - B.temperature) ** 4) * 5.6704e-8 * connecting_tiles * 2.5
+ var/maximum_energy_delta = max(0, min(A.temperature * A.heat_capacity() * A.group_multiplier, B.temperature * B.heat_capacity() * B.group_multiplier))
+ if(maximum_energy_delta > abs(energy_delta))
+ if(energy_delta < 0)
+ maximum_energy_delta *= -1
+ energy_delta = maximum_energy_delta
+
+ A.temperature -= energy_delta / (A.heat_capacity() * A.group_multiplier)
+ B.temperature += energy_delta / (B.heat_capacity() * B.group_multiplier)
\ No newline at end of file
diff --git a/code/ZAS/ConnectionManager.dm b/code/ZAS/ConnectionManager.dm
new file mode 100644
index 00000000000..1c101f4b45a
--- /dev/null
+++ b/code/ZAS/ConnectionManager.dm
@@ -0,0 +1,102 @@
+/*
+
+Overview:
+ The connection_manager class stores connections in each cardinal direction on a turf.
+ It isn't always present if a turf has no connections, check if(connections) before using.
+ Contains procs for mass manipulation of connection data.
+
+Class Vars:
+
+ NSEWUD - Connections to this turf in each cardinal direction.
+
+Class Procs:
+
+ get(d)
+ Returns the connection (if any) in this direction.
+ Preferable to accessing the connection directly because it checks validity.
+
+ place(connection/c, d)
+ Called by air_master.connect(). Sets the connection in the specified direction to c.
+
+ update_all()
+ Called after turf/update_air_properties(). Updates the validity of all connections on this turf.
+
+ erase_all()
+ Called when the turf is changed with ChangeTurf(). Erases all existing connections.
+
+ check(connection/c)
+ Checks for connection validity. It's possible to have a reference to a connection that has been erased.
+
+
+*/
+
+/turf/var/tmp/connection_manager/connections
+
+/connection_manager/var/connection/N
+/connection_manager/var/connection/S
+/connection_manager/var/connection/E
+/connection_manager/var/connection/W
+
+#ifdef ZLEVELS
+/connection_manager/var/connection/U
+/connection_manager/var/connection/D
+#endif
+
+/connection_manager/proc/get(d)
+ switch(d)
+ if(NORTH)
+ if(check(N)) return N
+ else return null
+ if(SOUTH)
+ if(check(S)) return S
+ else return null
+ if(EAST)
+ if(check(E)) return E
+ else return null
+ if(WEST)
+ if(check(W)) return W
+ else return null
+
+ #ifdef ZLEVELS
+ if(UP)
+ if(check(U)) return U
+ else return null
+ if(DOWN)
+ if(check(D)) return D
+ else return null
+ #endif
+
+/connection_manager/proc/place(connection/c, d)
+ switch(d)
+ if(NORTH) N = c
+ if(SOUTH) S = c
+ if(EAST) E = c
+ if(WEST) W = c
+
+ #ifdef ZLEVELS
+ if(UP) U = c
+ if(DOWN) D = c
+ #endif
+
+/connection_manager/proc/update_all()
+ if(check(N)) N.update()
+ if(check(S)) S.update()
+ if(check(E)) E.update()
+ if(check(W)) W.update()
+ #ifdef ZLEVELS
+ if(check(U)) U.update()
+ if(check(D)) D.update()
+ #endif
+
+/connection_manager/proc/erase_all()
+ if(check(N)) N.erase()
+ if(check(S)) S.erase()
+ if(check(E)) E.erase()
+ if(check(W)) W.erase()
+ #ifdef ZLEVELS
+ if(check(U)) U.erase()
+ if(check(D)) D.erase()
+ #endif
+
+/connection_manager/proc/check(connection/c)
+ return c && c.valid()
\ No newline at end of file
diff --git a/code/ZAS/Controller.dm b/code/ZAS/Controller.dm
new file mode 100644
index 00000000000..83b4bf3f3b4
--- /dev/null
+++ b/code/ZAS/Controller.dm
@@ -0,0 +1,321 @@
+var/datum/controller/air_system/air_master
+
+var/tick_multiplier = 2
+
+/*
+
+Overview:
+ The air controller does everything. There are tons of procs in here.
+
+Class Vars:
+ zones - All zones currently holding one or more turfs.
+ edges - All processing edges.
+
+ tiles_to_update - Tiles scheduled to update next tick.
+ zones_to_update - Zones which have had their air changed and need air archival.
+ active_hotspots - All processing fire objects.
+
+ active_zones - The number of zones which were archived last tick. Used in debug verbs.
+ next_id - The next UID to be applied to a zone. Mostly useful for debugging purposes as zones do not need UIDs to function.
+
+Class Procs:
+
+ mark_for_update(turf/T)
+ Adds the turf to the update list. When updated, update_air_properties() will be called.
+ When stuff changes that might affect airflow, call this. It's basically the only thing you need.
+
+ add_zone(zone/Z) and remove_zone(zone/Z)
+ Adds zones to the zones list. Does not mark them for update.
+
+ air_blocked(turf/A, turf/B)
+ Returns a bitflag consisting of:
+ AIR_BLOCKED - The connection between turfs is physically blocked. No air can pass.
+ ZONE_BLOCKED - There is a door between the turfs, so zones cannot cross. Air may or may not be permeable.
+
+ has_valid_zone(turf/T)
+ Checks the presence and validity of T's zone.
+ May be called on unsimulated turfs, returning 0.
+
+ merge(zone/A, zone/B)
+ Called when zones have a direct connection and equivalent pressure and temperature.
+ Merges the zones to create a single zone.
+
+ connect(turf/simulated/A, turf/B)
+ Called by turf/update_air_properties(). The first argument must be simulated.
+ Creates a connection between A and B.
+
+ mark_zone_update(zone/Z)
+ Adds zone to the update list. Unlike mark_for_update(), this one is called automatically whenever
+ air is returned from a simulated turf.
+
+ equivalent_pressure(zone/A, zone/B)
+ Currently identical to A.air.compare(B.air). Returns 1 when directly connected zones are ready to be merged.
+
+ get_edge(zone/A, zone/B)
+ get_edge(zone/A, turf/B)
+ Gets a valid connection_edge between A and B, creating a new one if necessary.
+
+ has_same_air(turf/A, turf/B)
+ Used to determine if an unsimulated edge represents a specific turf.
+ Simulated edges use connection_edge/contains_zone() for the same purpose.
+ Returns 1 if A has identical gases and temperature to B.
+
+ remove_edge(connection_edge/edge)
+ Called when an edge is erased. Removes it from processing.
+
+*/
+
+
+//Geometry lists
+/datum/controller/air_system/var/list/zones = list()
+/datum/controller/air_system/var/list/edges = list()
+
+//Geometry updates lists
+/datum/controller/air_system/var/list/tiles_to_update = list()
+/datum/controller/air_system/var/list/zones_to_update = list()
+/datum/controller/air_system/var/list/active_hotspots = list()
+
+/datum/controller/air_system/var/active_zones = 0
+
+/datum/controller/air_system/var/current_cycle = 0
+/datum/controller/air_system/var/update_delay = 5 //How long between check should it try to process atmos again.
+/datum/controller/air_system/var/failed_ticks = 0 //How many ticks have runtimed?
+
+/datum/controller/air_system/var/tick_progress = 0
+
+/datum/controller/air_system/var/next_id = 1 //Used to keep track of zone UIDs.
+
+/datum/controller/air_system/proc/Setup()
+ //Purpose: Call this at the start to setup air groups geometry
+ // (Warning: Very processor intensive but only must be done once per round)
+ //Called by: Gameticker/Master controller
+ //Inputs: None.
+ //Outputs: None.
+
+ #ifndef ZASDBG
+ set background = 1
+ #endif
+
+ world << "\red \b Processing Geometry..."
+ sleep(-1)
+
+ var/start_time = world.timeofday
+
+ var/simulated_turf_count = 0
+
+ for(var/turf/simulated/S in world)
+ simulated_turf_count++
+ S.update_air_properties()
+
+ world << {"Geometry initialized in [round(0.1*(world.timeofday-start_time),0.1)] seconds.
+Total Simulated Turfs: [simulated_turf_count]
+Total Zones: [zones.len]
+Total Unsimulated Turfs: [world.maxx*world.maxy*world.maxz - simulated_turf_count]"}
+
+// spawn Start()
+
+
+/datum/controller/air_system/proc/Start()
+ //Purpose: This is kicked off by the master controller, and controls the processing of all atmosphere.
+ //Called by: Master controller
+ //Inputs: None.
+ //Outputs: None.
+
+ #ifndef ZASDBG
+ set background = 1
+ #endif
+
+ while(1)
+ Tick()
+ sleep(max(5,update_delay*tick_multiplier))
+
+
+/datum/controller/air_system/proc/Tick()
+ . = 1 //Set the default return value, for runtime detection.
+
+ current_cycle++
+
+ //If there are tiles to update, do so.
+ tick_progress = "updating turf properties"
+
+ var/list/updating
+
+ if(tiles_to_update.len)
+ updating = tiles_to_update
+ tiles_to_update = list()
+
+ #ifdef ZASDBG
+ var/updated = 0
+ #endif
+ for(var/turf/T in updating)
+ T.update_air_properties()
+ T.post_update_air_properties()
+ T.needs_air_update = 0
+ #ifdef ZASDBG
+ T.overlays -= mark
+ updated++
+ #endif
+ //sleep(1)
+
+ #ifdef ZASDBG
+ if(updated != updating.len)
+ tick_progress = "[updating.len - updated] tiles left unupdated."
+ world << "\red [tick_progress]"
+ . = 0
+ #endif
+
+ //Where gas exchange happens.
+ if(.)
+ tick_progress = "processing edges"
+
+ for(var/connection_edge/edge in edges)
+ edge.tick()
+
+ //Process fires.
+ if(.)
+ tick_progress = "processing fire"
+
+ for(var/obj/fire/fire in active_hotspots)
+ fire.process()
+
+ //Process zones.
+ if(.)
+ tick_progress = "updating zones"
+
+ active_zones = zones_to_update.len
+ if(zones_to_update.len)
+ updating = zones_to_update
+ zones_to_update = list()
+ for(var/zone/zone in updating)
+ zone.tick()
+ zone.needs_update = 0
+
+ if(.)
+ tick_progress = "success"
+
+/datum/controller/air_system/proc/add_zone(zone/z)
+ zones.Add(z)
+ z.name = "Zone [next_id++]"
+ mark_zone_update(z)
+
+/datum/controller/air_system/proc/remove_zone(zone/z)
+ zones.Remove(z)
+
+/datum/controller/air_system/proc/air_blocked(turf/A, turf/B)
+ #ifdef ZASDBG
+ ASSERT(isturf(A))
+ ASSERT(isturf(B))
+ #endif
+ var/ablock = A.c_airblock(B)
+ if(ablock == BLOCKED) return BLOCKED
+ return ablock | B.c_airblock(A)
+
+/datum/controller/air_system/proc/has_valid_zone(turf/simulated/T)
+ #ifdef ZASDBG
+ ASSERT(istype(T))
+ #endif
+ return istype(T) && T.zone && !T.zone.invalid
+
+/datum/controller/air_system/proc/merge(zone/A, zone/B)
+ #ifdef ZASDBG
+ ASSERT(istype(A))
+ ASSERT(istype(B))
+ ASSERT(!A.invalid)
+ ASSERT(!B.invalid)
+ ASSERT(A != B)
+ #endif
+ if(A.contents.len < B.contents.len)
+ A.c_merge(B)
+ mark_zone_update(B)
+ else
+ B.c_merge(A)
+ mark_zone_update(A)
+
+/datum/controller/air_system/proc/connect(turf/simulated/A, turf/simulated/B)
+ #ifdef ZASDBG
+ ASSERT(istype(A))
+ ASSERT(isturf(B))
+ ASSERT(A.zone)
+ ASSERT(!A.zone.invalid)
+ //ASSERT(B.zone)
+ ASSERT(A != B)
+ #endif
+
+ var/block = air_master.air_blocked(A,B)
+ if(block & AIR_BLOCKED) return
+
+ var/direct = !(block & ZONE_BLOCKED)
+ var/space = !istype(B)
+
+ if(direct && !space)
+ if(equivalent_pressure(A.zone,B.zone) || current_cycle == 0)
+ merge(A.zone,B.zone)
+ return
+
+ var
+ a_to_b = get_dir(A,B)
+ b_to_a = get_dir(B,A)
+
+ if(!A.connections) A.connections = new
+ if(!B.connections) B.connections = new
+
+ if(A.connections.get(a_to_b)) return
+ if(B.connections.get(b_to_a)) return
+ if(!space)
+ if(A.zone == B.zone) return
+
+
+ var/connection/c = new /connection(A,B)
+
+ A.connections.place(c, a_to_b)
+ B.connections.place(c, b_to_a)
+
+ if(direct) c.mark_direct()
+
+/datum/controller/air_system/proc/mark_for_update(turf/T)
+ #ifdef ZASDBG
+ ASSERT(isturf(T))
+ #endif
+ if(T.needs_air_update) return
+ tiles_to_update |= T
+ #ifdef ZASDBG
+ T.overlays += mark
+ #endif
+ T.needs_air_update = 1
+
+/datum/controller/air_system/proc/mark_zone_update(zone/Z)
+ #ifdef ZASDBG
+ ASSERT(istype(Z))
+ #endif
+ if(Z.needs_update) return
+ zones_to_update.Add(Z)
+ Z.needs_update = 1
+
+/datum/controller/air_system/proc/equivalent_pressure(zone/A, zone/B)
+ return A.air.compare(B.air)
+
+/datum/controller/air_system/proc/get_edge(zone/A, zone/B)
+
+ if(istype(B))
+ for(var/connection_edge/zone/edge in A.edges)
+ if(edge.contains_zone(B)) return edge
+ var/connection_edge/edge = new/connection_edge/zone(A,B)
+ edges.Add(edge)
+ return edge
+ else
+ for(var/connection_edge/unsimulated/edge in A.edges)
+ if(has_same_air(edge.B,B)) return edge
+ var/connection_edge/edge = new/connection_edge/unsimulated(A,B)
+ edges.Add(edge)
+ return edge
+
+/datum/controller/air_system/proc/has_same_air(turf/A, turf/B)
+ if(A.oxygen != B.oxygen) return 0
+ if(A.nitrogen != B.nitrogen) return 0
+ if(A.toxins != B.toxins) return 0
+ if(A.carbon_dioxide != B.carbon_dioxide) return 0
+ if(A.temperature != B.temperature) return 0
+ return 1
+
+/datum/controller/air_system/proc/remove_edge(connection/c)
+ edges.Remove(c)
\ No newline at end of file
diff --git a/code/ZAS/Debug.dm b/code/ZAS/Debug.dm
index 39f5a7caabd..b27056f2384 100644
--- a/code/ZAS/Debug.dm
+++ b/code/ZAS/Debug.dm
@@ -1,219 +1,18 @@
-client/proc/ZoneTick()
- set category = "Debug"
- set name = "Process Atmos"
+var/image/assigned = image('icons/Testing/Zone.dmi', icon_state = "assigned")
+var/image/created = image('icons/Testing/Zone.dmi', icon_state = "created")
+var/image/merged = image('icons/Testing/Zone.dmi', icon_state = "merged")
+var/image/invalid_zone = image('icons/Testing/Zone.dmi', icon_state = "invalid")
+var/image/air_blocked = image('icons/Testing/Zone.dmi', icon_state = "block")
+var/image/zone_blocked = image('icons/Testing/Zone.dmi', icon_state = "zoneblock")
+var/image/blocked = image('icons/Testing/Zone.dmi', icon_state = "fullblock")
+var/image/mark = image('icons/Testing/Zone.dmi', icon_state = "mark")
- var/result = air_master.Tick()
- if(result)
- src << "Sucessfully Processed."
+/turf/var/tmp/dbg_img
+/turf/proc/dbg(image/img, d = 0)
+ if(d > 0) img.dir = d
+ overlays -= dbg_img
+ overlays += img
+ dbg_img = img
- else
- src << "Failed to process! ([air_master.tick_progress])"
-
-
-client/proc/Zone_Info(turf/T as null|turf)
- set category = "Debug"
- if(T)
- if(T.zone)
- T.zone.DebugDisplay(src)
- else
- mob << "No zone here."
- else
- if(zone_debug_images)
- for(var/zone in zone_debug_images)
- images -= zone_debug_images[zone]
- zone_debug_images = null
-
-client/var/list/zone_debug_images
-
-client/proc/Test_ZAS_Connection(var/turf/simulated/T as turf)
- set category = "Debug"
- if(!istype(T))
- return
-
- var/direction_list = list(\
- "North" = NORTH,\
- "South" = SOUTH,\
- "East" = EAST,\
- "West" = WEST,\
- "N/A" = null)
- var/direction = input("What direction do you wish to test?","Set direction") as null|anything in direction_list
- if(!direction)
- return
-
- if(direction == "N/A")
- if(T.CanPass(null, T, 0,0))
- mob << "The turf can pass air! :D"
- else
- mob << "No air passage :x"
- return
-
- var/turf/simulated/other_turf = get_step(T, direction_list[direction])
- if(!istype(other_turf))
- return
-
- var/pass_directions = T.CanPass(null, other_turf, 0, 0) + 2 * other_turf.CanPass(null, T, 0, 0)
-
- switch(pass_directions)
- if(0)
- mob << "Neither turf can connect. :("
-
- if(1)
- mob << "The initial turf only can connect. :\\"
-
- if(2)
- mob << "The other turf can connect, but not the initial turf. :/"
-
- if(3)
- mob << "Both turfs can connect! :)"
-
-
-zone/proc/DebugDisplay(client/client)
- if(!istype(client))
- return
-
- if(!dbg_output)
- dbg_output = 1 //Don't want to be spammed when someone investigates a zone...
-
- if(!client.zone_debug_images)
- client.zone_debug_images = list()
-
- var/list/current_zone_images = list()
-
- for(var/turf/T in contents)
- current_zone_images += image('icons/misc/debug_group.dmi', T, null, TURF_LAYER)
-
- for(var/turf/space/S in unsimulated_tiles)
- current_zone_images += image('icons/misc/debug_space.dmi', S, null, TURF_LAYER)
-
- client << "Zone Air Contents"
- client << "Oxygen: [air.oxygen]"
- client << "Nitrogen: [air.nitrogen]"
- client << "Plasma: [air.toxins]"
- client << "Carbon Dioxide: [air.carbon_dioxide]"
- client << "Temperature: [air.temperature] K"
- client << "Heat Energy: [air.temperature * air.heat_capacity()] J"
- client << "Pressure: [air.return_pressure()] KPa"
- client << ""
- client << "Space Tiles: [length(unsimulated_tiles)]"
- client << "Movable Objects: [length(movables())]"
- client << "Connections: [length(connections)]"
-
- for(var/connection/C in connections)
- client << "\ref[C] [C.A] --> [C.B] [(C.indirect?"Open":"Closed")]"
- current_zone_images += image('icons/misc/debug_connect.dmi', C.A, null, TURF_LAYER)
- current_zone_images += image('icons/misc/debug_connect.dmi', C.B, null, TURF_LAYER)
-
- client << "Connected Zones:"
- for(var/zone/zone in connected_zones)
- client << "\ref[zone] [zone] - [connected_zones[zone]] (Connected)"
-
- for(var/zone/zone in closed_connection_zones)
- client << "\ref[zone] [zone] - [closed_connection_zones[zone]] (Unconnected)"
-
- for(var/C in connections)
- if(!istype(C,/connection))
- client << "[C] (Not Connection!)"
-
- if(!client.zone_debug_images)
- client.zone_debug_images = list()
- client.zone_debug_images[src] = current_zone_images
-
- client.images += client.zone_debug_images[src]
-
- else
- dbg_output = 0
-
- client.images -= client.zone_debug_images[src]
- client.zone_debug_images.Remove(src)
-
- if(air_master)
- for(var/zone/Z in air_master.zones)
- if(Z.air == air && Z != src)
- var/turf/zloc = pick(Z.contents)
- client << "\red Illegal air datum shared by: [zloc.loc.name]"
-
-
-client/proc/TestZASRebuild()
- set category = "Debug"
-// var/turf/turf = get_turf(mob)
- var/zone/current_zone = mob.loc:zone
- if(!current_zone)
- src << "There is no zone there!"
- return
-
- var/list/current_adjacents = list()
- var/list/overlays = list()
- var/adjacent_id
- var/lowest_id
-
- var/list/identical_ids = list()
- var/list/turfs = current_zone.contents.Copy()
- var/current_identifier = 1
-
- for(var/turf/simulated/current in turfs)
- lowest_id = null
- current_adjacents = list()
-
- for(var/direction in cardinal)
- var/turf/simulated/adjacent = get_step(current, direction)
- if(!current.ZCanPass(adjacent))
- continue
- if(turfs.Find(adjacent))
- current_adjacents += adjacent
- adjacent_id = turfs[adjacent]
-
- if(adjacent_id && (!lowest_id || adjacent_id < lowest_id))
- lowest_id = adjacent_id
-
- if(!lowest_id)
- lowest_id = current_identifier++
- identical_ids += lowest_id
- overlays += image('icons/misc/debug_rebuild.dmi',, "[lowest_id]")
-
- for(var/turf/simulated/adjacent in current_adjacents)
- adjacent_id = turfs[adjacent]
- if(adjacent_id != lowest_id)
- if(adjacent_id)
- adjacent.overlays -= overlays[adjacent_id]
- identical_ids[adjacent_id] = lowest_id
-
- turfs[adjacent] = lowest_id
- adjacent.overlays += overlays[lowest_id]
-
- sleep(5)
-
- if(turfs[current])
- current.overlays -= overlays[turfs[current]]
- turfs[current] = lowest_id
- current.overlays += overlays[lowest_id]
- sleep(5)
-
- var/list/final_arrangement = list()
-
- for(var/turf/simulated/current in turfs)
- current_identifier = identical_ids[turfs[current]]
- current.overlays -= overlays[turfs[current]]
- current.overlays += overlays[current_identifier]
- sleep(5)
-
- if( current_identifier > final_arrangement.len )
- final_arrangement.len = current_identifier
- final_arrangement[current_identifier] = list(current)
-
- else
- final_arrangement[current_identifier] += current
-
- //lazy but fast
- final_arrangement.Remove(null)
-
- src << "There are [final_arrangement.len] unique segments."
-
- for(var/turf/current in turfs)
- current.overlays -= overlays
-
- return final_arrangement
-
-/client/proc/ZASSettings()
- set category = "Debug"
-
- vsc.SetDefault(mob)
\ No newline at end of file
+proc/soft_assert(thing,fail)
+ if(!thing) message_admins(fail)
\ No newline at end of file
diff --git a/code/ZAS/Diagnostic.dm b/code/ZAS/Diagnostic.dm
new file mode 100644
index 00000000000..07f06a5514d
--- /dev/null
+++ b/code/ZAS/Diagnostic.dm
@@ -0,0 +1,233 @@
+client/proc/ZoneTick()
+ set category = "Debug"
+ set name = "Process Atmos"
+
+ var/result = air_master.Tick()
+ if(result)
+ src << "Sucessfully Processed."
+
+ else
+ src << "Failed to process! ([air_master.tick_progress])"
+
+
+client/proc/Zone_Info(turf/T as null|turf)
+ set category = "Debug"
+ if(T)
+ if(istype(T,/turf/simulated) && T:zone)
+ T:zone:dbg_data(src)
+ else
+ mob << "No zone here."
+ else
+ if(zone_debug_images)
+ for(var/zone in zone_debug_images)
+ images -= zone_debug_images[zone]
+ zone_debug_images = null
+
+client/var/list/zone_debug_images
+
+client/proc/Test_ZAS_Connection(var/turf/simulated/T as turf)
+ set category = "Debug"
+ if(!istype(T))
+ return
+
+ var/direction_list = list(\
+ "North" = NORTH,\
+ "South" = SOUTH,\
+ "East" = EAST,\
+ "West" = WEST,\
+ "N/A" = null)
+ var/direction = input("What direction do you wish to test?","Set direction") as null|anything in direction_list
+ if(!direction)
+ return
+
+ if(direction == "N/A")
+ if(!(T.c_airblock(T) & AIR_BLOCKED))
+ mob << "The turf can pass air! :D"
+ else
+ mob << "No air passage :x"
+ return
+
+ var/turf/simulated/other_turf = get_step(T, direction_list[direction])
+ if(!istype(other_turf))
+ return
+
+ var/t_block = T.c_airblock(other_turf)
+ var/o_block = other_turf.c_airblock(T)
+
+ if(o_block & AIR_BLOCKED)
+ if(t_block & AIR_BLOCKED)
+ mob << "Neither turf can connect. :("
+
+ else
+ mob << "The initial turf only can connect. :\\"
+ else
+ if(t_block & AIR_BLOCKED)
+ mob << "The other turf can connect, but not the initial turf. :/"
+
+ else
+ mob << "Both turfs can connect! :)"
+
+ mob << "Additionally, \..."
+
+ if(o_block & ZONE_BLOCKED)
+ if(t_block & ZONE_BLOCKED)
+ mob << "neither turf can merge."
+ else
+ mob << "the other turf cannot merge."
+ else
+ if(t_block & ZONE_BLOCKED)
+ mob << "the initial turf cannot merge."
+ else
+ mob << "both turfs can merge."
+
+
+/*zone/proc/DebugDisplay(client/client)
+ if(!istype(client))
+ return
+
+ if(!dbg_output)
+ dbg_output = 1 //Don't want to be spammed when someone investigates a zone...
+
+ if(!client.zone_debug_images)
+ client.zone_debug_images = list()
+
+ var/list/current_zone_images = list()
+
+ for(var/turf/T in contents)
+ current_zone_images += image('icons/misc/debug_group.dmi', T, null, TURF_LAYER)
+
+ for(var/turf/space/S in unsimulated_tiles)
+ current_zone_images += image('icons/misc/debug_space.dmi', S, null, TURF_LAYER)
+
+ client << "Zone Air Contents"
+ client << "Oxygen: [air.oxygen]"
+ client << "Nitrogen: [air.nitrogen]"
+ client << "Plasma: [air.toxins]"
+ client << "Carbon Dioxide: [air.carbon_dioxide]"
+ client << "Temperature: [air.temperature] K"
+ client << "Heat Energy: [air.temperature * air.heat_capacity()] J"
+ client << "Pressure: [air.return_pressure()] KPa"
+ client << ""
+ client << "Space Tiles: [length(unsimulated_tiles)]"
+ client << "Movable Objects: [length(movables())]"
+ client << "Connections: [length(connections)]"
+
+ for(var/connection/C in connections)
+ client << "\ref[C] [C.A] --> [C.B] [(C.indirect?"Open":"Closed")]"
+ current_zone_images += image('icons/misc/debug_connect.dmi', C.A, null, TURF_LAYER)
+ current_zone_images += image('icons/misc/debug_connect.dmi', C.B, null, TURF_LAYER)
+
+ client << "Connected Zones:"
+ for(var/zone/zone in connected_zones)
+ client << "\ref[zone] [zone] - [connected_zones[zone]] (Connected)"
+
+ for(var/zone/zone in closed_connection_zones)
+ client << "\ref[zone] [zone] - [closed_connection_zones[zone]] (Unconnected)"
+
+ for(var/C in connections)
+ if(!istype(C,/connection))
+ client << "[C] (Not Connection!)"
+
+ if(!client.zone_debug_images)
+ client.zone_debug_images = list()
+ client.zone_debug_images[src] = current_zone_images
+
+ client.images += client.zone_debug_images[src]
+
+ else
+ dbg_output = 0
+
+ client.images -= client.zone_debug_images[src]
+ client.zone_debug_images.Remove(src)
+
+ if(air_master)
+ for(var/zone/Z in air_master.zones)
+ if(Z.air == air && Z != src)
+ var/turf/zloc = pick(Z.contents)
+ client << "\red Illegal air datum shared by: [zloc.loc.name]"*/
+
+
+/*client/proc/TestZASRebuild()
+ set category = "Debug"
+// var/turf/turf = get_turf(mob)
+ var/zone/current_zone = mob.loc:zone
+ if(!current_zone)
+ src << "There is no zone there!"
+ return
+
+ var/list/current_adjacents = list()
+ var/list/overlays = list()
+ var/adjacent_id
+ var/lowest_id
+
+ var/list/identical_ids = list()
+ var/list/turfs = current_zone.contents.Copy()
+ var/current_identifier = 1
+
+ for(var/turf/simulated/current in turfs)
+ lowest_id = null
+ current_adjacents = list()
+
+ for(var/direction in cardinal)
+ var/turf/simulated/adjacent = get_step(current, direction)
+ if(!current.ZCanPass(adjacent))
+ continue
+ if(turfs.Find(adjacent))
+ current_adjacents += adjacent
+ adjacent_id = turfs[adjacent]
+
+ if(adjacent_id && (!lowest_id || adjacent_id < lowest_id))
+ lowest_id = adjacent_id
+
+ if(!lowest_id)
+ lowest_id = current_identifier++
+ identical_ids += lowest_id
+ overlays += image('icons/misc/debug_rebuild.dmi',, "[lowest_id]")
+
+ for(var/turf/simulated/adjacent in current_adjacents)
+ adjacent_id = turfs[adjacent]
+ if(adjacent_id != lowest_id)
+ if(adjacent_id)
+ adjacent.overlays -= overlays[adjacent_id]
+ identical_ids[adjacent_id] = lowest_id
+
+ turfs[adjacent] = lowest_id
+ adjacent.overlays += overlays[lowest_id]
+
+ sleep(5)
+
+ if(turfs[current])
+ current.overlays -= overlays[turfs[current]]
+ turfs[current] = lowest_id
+ current.overlays += overlays[lowest_id]
+ sleep(5)
+
+ var/list/final_arrangement = list()
+
+ for(var/turf/simulated/current in turfs)
+ current_identifier = identical_ids[turfs[current]]
+ current.overlays -= overlays[turfs[current]]
+ current.overlays += overlays[current_identifier]
+ sleep(5)
+
+ if( current_identifier > final_arrangement.len )
+ final_arrangement.len = current_identifier
+ final_arrangement[current_identifier] = list(current)
+
+ else
+ final_arrangement[current_identifier] += current
+
+ //lazy but fast
+ final_arrangement.Remove(null)
+
+ src << "There are [final_arrangement.len] unique segments."
+
+ for(var/turf/current in turfs)
+ current.overlays -= overlays
+
+ return final_arrangement*/
+
+client/proc/ZASSettings()
+ set category = "Debug"
+
+ vsc.SetDefault(mob)
\ No newline at end of file
diff --git a/code/ZAS/Fire.dm b/code/ZAS/Fire.dm
index b79d7f8b553..a564de2836f 100644
--- a/code/ZAS/Fire.dm
+++ b/code/ZAS/Fire.dm
@@ -28,12 +28,6 @@ turf/simulated/hotspot_expose(exposed_temperature, exposed_volume, soh)
if(!air_contents || exposed_temperature < PLASMA_MINIMUM_BURN_TEMPERATURE)
return 0
- var/obj/structure/reagent_dispensers/fueltank/FT = locate() in src
- if(exposed_temperature >= AUTOIGNITION_WELDERFUEL)
- if (FT)
- FT.explode()
- return 1
-
var/igniting = 0
var/obj/effect/decal/cleanable/liquid_fuel/liquid = locate() in src
@@ -81,12 +75,12 @@ turf/simulated/hotspot_expose(exposed_temperature, exposed_volume, soh)
//since the air is processed in fractions, we need to make sure not to have any minuscle residue or
//the amount of moles might get to low for some functions to catch them and thus result in wonky behaviour
- if(air_contents.oxygen < 0.001)
+ if(air_contents.oxygen < 0.1)
air_contents.oxygen = 0
- if(air_contents.toxins < 0.001)
+ if(air_contents.toxins < 0.1)
air_contents.toxins = 0
if(fuel)
- if(fuel.moles < 0.001)
+ if(fuel.moles < 0.1)
air_contents.trace_gases.Remove(fuel)
//check if there is something to combust
@@ -114,7 +108,7 @@ turf/simulated/hotspot_expose(exposed_temperature, exposed_volume, soh)
A.fire_act(air_contents, air_contents.temperature, air_contents.return_volume())
//spread
for(var/direction in cardinal)
- if(S.air_check_directions&direction) //Grab all valid bordering tiles
+ if(S.open_directions & direction) //Grab all valid bordering tiles
var/turf/simulated/enemy_tile = get_step(S, direction)
@@ -168,7 +162,7 @@ turf/simulated/hotspot_expose(exposed_temperature, exposed_volume, soh)
air_master.active_hotspots.Add(src)
-/obj/fire/Destroy()
+/obj/fire/Del()
if (istype(loc, /turf/simulated))
SetLuminosity(0)
@@ -206,7 +200,7 @@ datum/gas_mixture/proc/zburn(obj/effect/decal/cleanable/liquid_fuel/liquid, forc
if(liquid)
//Liquid Fuel
- if(liquid.amount <= 0)
+ if(liquid.amount <= 0.1)
del liquid
else
total_fuel += liquid.amount
@@ -263,9 +257,9 @@ datum/gas_mixture/proc/check_recombustability(obj/effect/decal/cleanable/liquid_
if(oxygen && (toxins || fuel || liquid))
if(liquid)
return 1
- if (toxins)
+ if(toxins >= 0.1)
return 1
- if(fuel)
+ if(fuel && fuel.moles >= 0.1)
return 1
return 0
@@ -278,9 +272,9 @@ datum/gas_mixture/proc/check_combustability(obj/effect/decal/cleanable/liquid_fu
if(oxygen && (toxins || fuel || liquid))
if(liquid)
return 1
- if (toxins >= 0.7)
+ if (toxins >= 0.1)
return 1
- if(fuel && fuel.moles >= 1.4)
+ if(fuel && fuel.moles >= 0.1)
return 1
return 0
diff --git a/code/ZAS/Turf.dm b/code/ZAS/Turf.dm
new file mode 100644
index 00000000000..fece782aa71
--- /dev/null
+++ b/code/ZAS/Turf.dm
@@ -0,0 +1,241 @@
+/turf/simulated/var/zone/zone
+/turf/simulated/var/open_directions
+/turf/simulated/var/gas_graphic
+
+/turf/var/needs_air_update = 0
+/turf/var/datum/gas_mixture/air
+
+/turf/simulated/proc/set_graphic(new_graphic)
+ if(isnum(new_graphic))
+ if(new_graphic == 1) new_graphic = plmaster
+ else if(new_graphic == 2) new_graphic = slmaster
+ if(gas_graphic) overlays -= gas_graphic
+ if(new_graphic) overlays += new_graphic
+ gas_graphic = new_graphic
+
+/turf/proc/update_air_properties()
+ var/block = c_airblock(src)
+ if(block & AIR_BLOCKED)
+ //dbg(blocked)
+ return 1
+
+ #ifdef ZLEVELS
+ for(var/d = 1, d < 64, d *= 2)
+ #else
+ for(var/d = 1, d < 16, d *= 2)
+ #endif
+
+ var/turf/unsim = get_step(src, d)
+ block = unsim.c_airblock(src)
+
+ if(block & AIR_BLOCKED)
+ //unsim.dbg(air_blocked, turn(180,d))
+ continue
+
+ var/r_block = c_airblock(unsim)
+
+ if(r_block & AIR_BLOCKED)
+ continue
+
+ if(istype(unsim, /turf/simulated))
+
+ var/turf/simulated/sim = unsim
+ if(air_master.has_valid_zone(sim))
+
+ air_master.connect(sim, src)
+
+/turf/simulated/update_air_properties()
+ if(zone && zone.invalid)
+ c_copy_air()
+ zone = null //Easier than iterating through the list at the zone.
+
+ var/s_block = c_airblock(src)
+ if(s_block & AIR_BLOCKED)
+ #ifdef ZASDBG
+ if(verbose) world << "Self-blocked."
+ //dbg(blocked)
+ #endif
+ if(zone)
+ var/zone/z = zone
+ if(locate(/obj/machinery/door/airlock) in src) //Hacky, but prevents normal airlocks from rebuilding zones all the time
+ z.remove(src)
+ else
+ z.rebuild()
+
+ return 1
+
+ var/previously_open = open_directions
+ open_directions = 0
+
+ var/list/postponed
+ #ifdef ZLEVELS
+ for(var/d = 1, d < 64, d *= 2)
+ #else
+ for(var/d = 1, d < 16, d *= 2)
+ #endif
+
+ var/turf/unsim = get_step(src, d)
+ var/block = unsim.c_airblock(src)
+ if(block & AIR_BLOCKED)
+
+ #ifdef ZASDBG
+ if(verbose) world << "[d] is blocked."
+ //unsim.dbg(air_blocked, turn(180,d))
+ #endif
+
+ continue
+
+ var/r_block = c_airblock(unsim)
+ if(r_block & AIR_BLOCKED)
+
+ #ifdef ZASDBG
+ if(verbose) world << "[d] is blocked."
+ //dbg(air_blocked, d)
+ #endif
+
+ //Check that our zone hasn't been cut off recently.
+ //This happens when windows move or are constructed. We need to rebuild.
+ if((previously_open & d) && istype(unsim, /turf/simulated))
+ var/turf/simulated/sim = unsim
+ if(sim.zone == zone)
+ zone.rebuild()
+ return
+
+ continue
+
+ open_directions |= d
+
+ if(istype(unsim, /turf/simulated))
+
+ var/turf/simulated/sim = unsim
+ if(air_master.has_valid_zone(sim))
+
+ //Might have assigned a zone, since this happens for each direction.
+ if(!zone)
+
+ //if((block & ZONE_BLOCKED) || (r_block & ZONE_BLOCKED && !(s_block & ZONE_BLOCKED)))
+ if(((block & ZONE_BLOCKED) && !(r_block & ZONE_BLOCKED)) || (r_block & ZONE_BLOCKED && !(s_block & ZONE_BLOCKED)))
+ #ifdef ZASDBG
+ if(verbose) world << "[d] is zone blocked."
+ //dbg(zone_blocked, d)
+ #endif
+
+ //Postpone this tile rather than exit, since a connection can still be made.
+ if(!postponed) postponed = list()
+ postponed.Add(sim)
+
+ else
+
+ sim.zone.add(src)
+
+ #ifdef ZASDBG
+ dbg(assigned)
+ if(verbose) world << "Added to [zone]"
+ #endif
+
+ else if(sim.zone != zone)
+
+ #ifdef ZASDBG
+ if(verbose) world << "Connecting to [sim.zone]"
+ #endif
+
+ air_master.connect(src, sim)
+
+
+ #ifdef ZASDBG
+ else if(verbose) world << "[d] has same zone."
+
+ else if(verbose) world << "[d] has invalid zone."
+ #endif
+
+ else
+
+ //Postponing connections to tiles until a zone is assured.
+ if(!postponed) postponed = list()
+ postponed.Add(unsim)
+
+ if(!air_master.has_valid_zone(src)) //Still no zone, make a new one.
+ var/zone/newzone = new/zone()
+ newzone.add(src)
+
+ #ifdef ZASDBG
+ dbg(created)
+
+ ASSERT(zone)
+ #endif
+
+ //At this point, a zone should have happened. If it hasn't, don't add more checks, fix the bug.
+
+ for(var/turf/T in postponed)
+ air_master.connect(src, T)
+
+/turf/proc/post_update_air_properties()
+ if(connections) connections.update_all()
+
+/turf/assume_air(datum/gas_mixture/giver) //use this for machines to adjust air
+ del(giver)
+ return 0
+
+/turf/return_air()
+ //Create gas mixture to hold data for passing
+ var/datum/gas_mixture/GM = new
+
+ GM.oxygen = oxygen
+ GM.carbon_dioxide = carbon_dioxide
+ GM.nitrogen = nitrogen
+ GM.toxins = toxins
+
+ GM.temperature = temperature
+ GM.update_values()
+
+ return GM
+
+/turf/remove_air(amount as num)
+ var/datum/gas_mixture/GM = new
+
+ var/sum = oxygen + carbon_dioxide + nitrogen + toxins
+ if(sum>0)
+ GM.oxygen = (oxygen/sum)*amount
+ GM.carbon_dioxide = (carbon_dioxide/sum)*amount
+ GM.nitrogen = (nitrogen/sum)*amount
+ GM.toxins = (toxins/sum)*amount
+
+ GM.temperature = temperature
+ GM.update_values()
+
+ return GM
+
+/turf/simulated/assume_air(datum/gas_mixture/giver)
+ var/datum/gas_mixture/my_air = return_air()
+ my_air.merge(giver)
+
+/turf/simulated/remove_air(amount as num)
+ var/datum/gas_mixture/my_air = return_air()
+ return my_air.remove(amount)
+
+/turf/simulated/return_air()
+ if(zone)
+ if(!zone.invalid)
+ air_master.mark_zone_update(zone)
+ return zone.air
+ else
+ if(!air)
+ make_air()
+ c_copy_air()
+ return air
+ else
+ if(!air)
+ make_air()
+ return air
+
+/turf/proc/make_air()
+ air = new/datum/gas_mixture
+ air.temperature = temperature
+ air.adjust(oxygen, carbon_dioxide, nitrogen, toxins)
+ air.group_multiplier = 1
+ air.volume = CELL_VOLUME
+
+/turf/simulated/proc/c_copy_air()
+ if(!air) air = new/datum/gas_mixture
+ air.copy_from(zone.air)
+ air.group_multiplier = 1
\ No newline at end of file
diff --git a/code/ZAS/Zone.dm b/code/ZAS/Zone.dm
new file mode 100644
index 00000000000..17d6097cdaf
--- /dev/null
+++ b/code/ZAS/Zone.dm
@@ -0,0 +1,153 @@
+/*
+
+Overview:
+ Each zone is a self-contained area where gas values would be the same if tile-based equalization were run indefinitely.
+ If you're unfamiliar with ZAS, FEA's air groups would have similar functionality if they didn't break in a stiff breeze.
+
+Class Vars:
+ name - A name of the format "Zone [#]", used for debugging.
+ invalid - True if the zone has been erased and is no longer eligible for processing.
+ needs_update - True if the zone has been added to the update list.
+ edges - A list of edges that connect to this zone.
+ air - The gas mixture that any turfs in this zone will return. Values are per-tile with a group multiplier.
+
+Class Procs:
+ add(turf/simulated/T)
+ Adds a turf to the contents, sets its zone and merges its air.
+
+ remove(turf/simulated/T)
+ Removes a turf, sets its zone to null and erases any gas graphics.
+ Invalidates the zone if it has no more tiles.
+
+ c_merge(zone/into)
+ Invalidates this zone and adds all its former contents to into.
+
+ c_invalidate()
+ Marks this zone as invalid and removes it from processing.
+
+ rebuild()
+ Invalidates the zone and marks all its former tiles for updates.
+
+ add_tile_air(turf/simulated/T)
+ Adds the air contained in T.air to the zone's air supply. Called when adding a turf.
+
+ tick()
+ Called only when the gas content is changed. Archives values and changes gas graphics.
+
+ dbg_data(mob/M)
+ Sends M a printout of important figures for the zone.
+
+*/
+
+
+/zone/var/name
+/zone/var/invalid = 0
+/zone/var/list/contents = list()
+
+/zone/var/needs_update = 0
+
+/zone/var/list/edges = list()
+
+/zone/var/datum/gas_mixture/air = new
+
+/zone/New()
+ air_master.add_zone(src)
+ air.temperature = TCMB
+ air.group_multiplier = 1
+ air.volume = CELL_VOLUME
+
+/zone/proc/add(turf/simulated/T)
+#ifdef ZASDBG
+ ASSERT(!invalid)
+ ASSERT(istype(T))
+ ASSERT(!air_master.has_valid_zone(T))
+#endif
+
+ var/datum/gas_mixture/turf_air = T.return_air()
+ add_tile_air(turf_air)
+ T.zone = src
+ contents.Add(T)
+ T.set_graphic(air.graphic)
+
+/zone/proc/remove(turf/simulated/T)
+#ifdef ZASDBG
+ ASSERT(!invalid)
+ ASSERT(istype(T))
+ ASSERT(T.zone == src)
+ soft_assert(T in contents, "Lists are weird broseph")
+#endif
+ contents.Remove(T)
+ T.zone = null
+ T.set_graphic(0)
+ if(contents.len)
+ air.group_multiplier = contents.len
+ else
+ c_invalidate()
+
+/zone/proc/c_merge(zone/into)
+#ifdef ZASDBG
+ ASSERT(!invalid)
+ ASSERT(istype(into))
+ ASSERT(into != src)
+ ASSERT(!into.invalid)
+#endif
+ c_invalidate()
+ for(var/turf/simulated/T in contents)
+ into.add(T)
+ #ifdef ZASDBG
+ T.dbg(merged)
+ #endif
+
+/zone/proc/c_invalidate()
+ invalid = 1
+ air_master.remove_zone(src)
+ #ifdef ZASDBG
+ for(var/turf/simulated/T in contents)
+ T.dbg(invalid_zone)
+ #endif
+
+/zone/proc/rebuild()
+ if(invalid) return //Short circuit for explosions where rebuild is called many times over.
+ c_invalidate()
+ for(var/turf/simulated/T in contents)
+ //T.dbg(invalid_zone)
+ T.needs_air_update = 0 //Reset the marker so that it will be added to the list.
+ air_master.mark_for_update(T)
+
+/zone/proc/add_tile_air(datum/gas_mixture/tile_air)
+ //air.volume += CELL_VOLUME
+ air.group_multiplier = 1
+ air.multiply(contents.len)
+ air.merge(tile_air)
+ air.divide(contents.len+1)
+ air.group_multiplier = contents.len+1
+
+/zone/proc/tick()
+ air.archive()
+ if(air.check_tile_graphic())
+ for(var/turf/simulated/T in contents)
+ T.set_graphic(air.graphic)
+
+/zone/proc/dbg_data(mob/M)
+ M << name
+ M << "O2: [air.oxygen] N2: [air.nitrogen] CO2: [air.carbon_dioxide] P: [air.toxins]"
+ M << "P: [air.return_pressure()] kPa V: [air.volume]L T: [air.temperature]°K ([air.temperature - T0C]°C)"
+ M << "O2 per N2: [(air.nitrogen ? air.oxygen/air.nitrogen : "N/A")] Moles: [air.total_moles]"
+ M << "Simulated: [contents.len] ([air.group_multiplier])"
+ //M << "Unsimulated: [unsimulated_contents.len]"
+ //M << "Edges: [edges.len]"
+ if(invalid) M << "Invalid!"
+ var/zone_edges = 0
+ var/space_edges = 0
+ var/space_coefficient = 0
+ for(var/connection_edge/E in edges)
+ if(E.type == /connection_edge/zone) zone_edges++
+ else
+ space_edges++
+ space_coefficient += E.coefficient
+
+ M << "Zone Edges: [zone_edges]"
+ M << "Space Edges: [space_edges] ([space_coefficient] connections)"
+
+ //for(var/turf/T in unsimulated_contents)
+ // M << "[T] at ([T.x],[T.y])"
\ No newline at end of file
diff --git a/code/ZAS/_docs.dm b/code/ZAS/_docs.dm
new file mode 100644
index 00000000000..8d084dc4c95
--- /dev/null
+++ b/code/ZAS/_docs.dm
@@ -0,0 +1,35 @@
+/*
+
+Zone Air System:
+
+This air system divides the station into impermeable areas called zones.
+When something happens, i.e. a door opening or a wall being taken down,
+zones equalize and eventually merge. Making an airtight area closes the connection again.
+
+Control Flow:
+Every air tick:
+ Marked turfs are updated with update_air_properties(), followed by post_update_air_properties().
+ Edges, including those generated by connections in the previous step, are processed. This is where gas is exchanged.
+ Fire is processed.
+ Marked zones have their air archived.
+
+Important Functions:
+
+air_master.mark_for_update(turf)
+ When stuff happens, call this. It works on everything. You basically don't need to worry about any other
+ functions besides CanPass().
+
+Notes for people who used ZAS before:
+ There is no connected_zones anymore.
+ To get the zones that are connected to a zone, use this loop:
+ for(var/connection_edge/zone/edge in zone.edges)
+ var/zone/connected_zone = edge.get_connected_zone(zone)
+
+*/
+
+//#define ZASDBG
+//#define ZLEVELS
+
+#define AIR_BLOCKED 1
+#define ZONE_BLOCKED 2
+#define BLOCKED 3
\ No newline at end of file
diff --git a/code/ZAS/_gas_mixture.dm b/code/ZAS/_gas_mixture.dm
new file mode 100644
index 00000000000..9a829633e2b
--- /dev/null
+++ b/code/ZAS/_gas_mixture.dm
@@ -0,0 +1,1090 @@
+/*
+What are the archived variables for?
+ Calculations are done using the archived variables with the results merged into the regular variables.
+ This prevents race conditions that arise based on the order of tile processing.
+*/
+
+#define SPECIFIC_HEAT_TOXIN 200
+#define SPECIFIC_HEAT_AIR 20
+#define SPECIFIC_HEAT_CDO 30
+#define HEAT_CAPACITY_CALCULATION(oxygen,carbon_dioxide,nitrogen,toxins) \
+ max(0, carbon_dioxide * SPECIFIC_HEAT_CDO + (oxygen + nitrogen) * SPECIFIC_HEAT_AIR + toxins * SPECIFIC_HEAT_TOXIN)
+
+#define MINIMUM_HEAT_CAPACITY 0.0003
+#define QUANTIZE(variable) (round(variable,0.0001))
+#define TRANSFER_FRACTION 5 //What fraction (1/#) of the air difference to try and transfer
+
+/hook/startup/proc/createGasOverlays()
+ plmaster = new /obj/effect/overlay()
+ plmaster.icon = 'icons/effects/tile_effects.dmi'
+ plmaster.icon_state = "plasma"
+ plmaster.layer = FLY_LAYER
+ plmaster.mouse_opacity = 0
+
+ slmaster = new /obj/effect/overlay()
+ slmaster.icon = 'icons/effects/tile_effects.dmi'
+ slmaster.icon_state = "sleeping_agent"
+ slmaster.layer = FLY_LAYER
+ slmaster.mouse_opacity = 0
+ return 1
+
+/datum/gas/sleeping_agent/specific_heat = 40 //These are used for the "Trace Gases" stuff, but is buggy.
+
+/datum/gas/oxygen_agent_b/specific_heat = 300
+
+/datum/gas/volatile_fuel/specific_heat = 30
+
+/datum/gas
+ var/moles = 0
+
+ var/specific_heat = 0
+
+ var/moles_archived = 0
+
+/datum/gas_mixture/
+ var/oxygen = 0 //Holds the "moles" of each of the four gases.
+ var/carbon_dioxide = 0
+ var/nitrogen = 0
+ var/toxins = 0
+
+ var/total_moles = 0 //Updated when a reaction occurs.
+
+ var/volume = CELL_VOLUME
+
+ var/temperature = 0 //in Kelvin, use calculate_temperature() to modify
+
+ var/group_multiplier = 1
+ //Size of the group this gas_mixture is representing.
+ //=1 for singletons
+
+ var/graphic
+
+ var/list/datum/gas/trace_gases = list() //Seemed to be a good idea that was abandoned
+
+ var/tmp/oxygen_archived //These are variables for use with the archived data
+ var/tmp/carbon_dioxide_archived
+ var/tmp/nitrogen_archived
+ var/tmp/toxins_archived
+
+ var/tmp/temperature_archived
+
+ var/tmp/graphic_archived = 0
+ var/tmp/fuel_burnt = 0
+
+ var/reacting = 0
+
+//FOR THE LOVE OF GOD PLEASE USE THIS PROC
+//Call it with negative numbers to remove gases.
+
+/datum/gas_mixture/proc/adjust(o2 = 0, co2 = 0, n2 = 0, tx = 0, list/datum/gas/traces = list())
+ //Purpose: Adjusting the gases within a airmix
+ //Called by: Nothing, yet!
+ //Inputs: The values of the gases to adjust
+ //Outputs: null
+
+ oxygen = max(0, oxygen + o2)
+ carbon_dioxide = max(0, carbon_dioxide + co2)
+ nitrogen = max(0, nitrogen + n2)
+ toxins = max(0, toxins + tx)
+
+ //handle trace gasses
+ for(var/datum/gas/G in traces)
+ var/datum/gas/T = locate(G.type) in trace_gases
+ if(T)
+ T.moles = max(G.moles + T.moles, 0)
+ else if(G.moles > 0)
+ trace_gases |= G
+ update_values()
+ return
+
+ //tg seems to like using these a lot
+/datum/gas_mixture/proc/return_temperature()
+ return temperature
+
+
+/datum/gas_mixture/proc/return_volume()
+ return max(0, volume)
+
+
+/datum/gas_mixture/proc/thermal_energy()
+ return temperature*heat_capacity()
+
+///////////////////////////////
+//PV=nRT - related procedures//
+///////////////////////////////
+
+/datum/gas_mixture/proc/heat_capacity()
+ //Purpose: Returning the heat capacity of the gas mix
+ //Called by: UNKNOWN
+ //Inputs: None
+ //Outputs: Heat capacity
+
+ var/heat_capacity = HEAT_CAPACITY_CALCULATION(oxygen,carbon_dioxide,nitrogen,toxins)
+
+ if(trace_gases.len)
+ for(var/datum/gas/trace_gas in trace_gases)
+ heat_capacity += trace_gas.moles*trace_gas.specific_heat
+
+ return max(MINIMUM_HEAT_CAPACITY,heat_capacity)
+
+/datum/gas_mixture/proc/heat_capacity_archived()
+ //Purpose: Returning the archived heat capacity of the gas mix
+ //Called by: UNKNOWN
+ //Inputs: None
+ //Outputs: Archived heat capacity
+
+ var/heat_capacity_archived = HEAT_CAPACITY_CALCULATION(oxygen_archived,carbon_dioxide_archived,nitrogen_archived,toxins_archived)
+
+ if(trace_gases.len)
+ for(var/datum/gas/trace_gas in trace_gases)
+ heat_capacity_archived += trace_gas.moles_archived*trace_gas.specific_heat
+
+ return max(MINIMUM_HEAT_CAPACITY,heat_capacity_archived)
+
+/datum/gas_mixture/proc/total_moles()
+ return total_moles
+ /*var/moles = oxygen + carbon_dioxide + nitrogen + toxins
+
+ if(trace_gases.len)
+ for(var/datum/gas/trace_gas in trace_gases)
+ moles += trace_gas.moles
+ return moles*/
+
+/datum/gas_mixture/proc/return_pressure()
+ //Purpose: Calculating Current Pressure
+ //Called by:
+ //Inputs: None
+ //Outputs: Gas pressure.
+
+ if(volume>0)
+ return total_moles()*R_IDEAL_GAS_EQUATION*temperature/volume
+ return 0
+
+// proc/return_temperature()
+ //Purpose:
+ //Inputs:
+ //Outputs:
+
+// return temperature
+
+// proc/return_volume()
+ //Purpose:
+ //Inputs:
+ //Outputs:
+
+// return max(0, volume)
+
+// proc/thermal_energy()
+ //Purpose:
+ //Inputs:
+ //Outputs:
+
+// return temperature*heat_capacity()
+
+/datum/gas_mixture/proc/update_values()
+ //Purpose: Calculating and storing values which were normally called CONSTANTLY
+ //Called by: Anything that changes values within a gas mix.
+ //Inputs: None
+ //Outputs: None
+
+ total_moles = oxygen + carbon_dioxide + nitrogen + toxins
+
+ if(trace_gases.len)
+ for(var/datum/gas/trace_gas in trace_gases)
+ total_moles += trace_gas.moles
+
+ return
+
+////////////////////////////////////////////
+//Procedures used for very specific events//
+////////////////////////////////////////////
+
+/datum/gas_mixture/proc/check_tile_graphic()
+ //Purpose: Calculating the graphic for a tile
+ //Called by: Turfs updating
+ //Inputs: None
+ //Outputs: 1 if graphic changed, 0 if unchanged
+
+ graphic = 0
+ if(toxins > MOLES_PLASMA_VISIBLE)
+ graphic = 1
+ else if(length(trace_gases))
+ var/datum/gas/sleeping_agent = locate(/datum/gas/sleeping_agent) in trace_gases
+ if(sleeping_agent && (sleeping_agent.moles > 1))
+ graphic = 2
+ else
+ graphic = 0
+
+ return graphic != graphic_archived
+
+/datum/gas_mixture/proc/react(atom/dump_location)
+ //Purpose: Calculating if it is possible for a fire to occur in the airmix
+ //Called by: Air mixes updating?
+ //Inputs: None
+ //Outputs: If a fire occured
+
+ //set to 1 if a notable reaction occured (used by pipe_network)
+
+ zburn(null)
+
+ return reacting
+
+/*
+/datum/gas_mixture/proc/fire()
+ //Purpose: Calculating any fire reactions.
+ //Called by: react() (See above)
+ //Inputs: None
+ //Outputs: How much fuel burned
+
+ return zburn(null)
+
+ var/energy_released = 0
+ var/old_heat_capacity = heat_capacity()
+
+ var/datum/gas/volatile_fuel/fuel_store = locate(/datum/gas/volatile_fuel) in trace_gases
+ if(fuel_store) //General volatile gas burn
+ var/burned_fuel = 0
+
+ if(oxygen < fuel_store.moles)
+ burned_fuel = oxygen
+ fuel_store.moles -= burned_fuel
+ oxygen = 0
+ else
+ burned_fuel = fuel_store.moles
+ oxygen -= fuel_store.moles
+ del(fuel_store)
+
+ energy_released += FIRE_CARBON_ENERGY_RELEASED * burned_fuel
+ carbon_dioxide += burned_fuel
+ fuel_burnt += burned_fuel
+
+ //Handle plasma burning
+ if(toxins > MINIMUM_HEAT_CAPACITY)
+ var/plasma_burn_rate = 0
+ var/oxygen_burn_rate = 0
+ //more plasma released at higher temperatures
+ var/temperature_scale
+ if(temperature > PLASMA_UPPER_TEMPERATURE)
+ temperature_scale = 1
+ else
+ temperature_scale = (temperature-PLASMA_MINIMUM_BURN_TEMPERATURE)/(PLASMA_UPPER_TEMPERATURE-PLASMA_MINIMUM_BURN_TEMPERATURE)
+ if(temperature_scale > 0)
+ oxygen_burn_rate = 1.4 - temperature_scale
+ if(oxygen > toxins*PLASMA_OXYGEN_FULLBURN)
+ plasma_burn_rate = (toxins*temperature_scale)/4
+ else
+ plasma_burn_rate = (temperature_scale*(oxygen/PLASMA_OXYGEN_FULLBURN))/4
+ if(plasma_burn_rate > MINIMUM_HEAT_CAPACITY)
+ toxins -= plasma_burn_rate
+ oxygen -= plasma_burn_rate*oxygen_burn_rate
+ carbon_dioxide += plasma_burn_rate
+
+ energy_released += FIRE_PLASMA_ENERGY_RELEASED * (plasma_burn_rate)
+
+ fuel_burnt += (plasma_burn_rate)*(1+oxygen_burn_rate)
+
+ if(energy_released > 0)
+ var/new_heat_capacity = heat_capacity()
+ if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
+ temperature = (temperature*old_heat_capacity + energy_released)/new_heat_capacity
+ update_values()
+
+ return fuel_burnt*/
+
+//////////////////////////////////////////////
+//Procs for general gas spread calculations.//
+//////////////////////////////////////////////
+
+
+/datum/gas_mixture/proc/archive()
+ //Purpose: Archives the current gas values
+ //Called by: UNKNOWN
+ //Inputs: None
+ //Outputs: 1
+
+ oxygen_archived = oxygen
+ carbon_dioxide_archived = carbon_dioxide
+ nitrogen_archived = nitrogen
+ toxins_archived = toxins
+
+ if(trace_gases.len)
+ for(var/datum/gas/trace_gas in trace_gases)
+ trace_gas.moles_archived = trace_gas.moles
+
+ temperature_archived = temperature
+
+ graphic_archived = graphic
+
+ return 1
+
+/datum/gas_mixture/proc/check_then_merge(datum/gas_mixture/giver)
+ //Purpose: Similar to merge(...) but first checks to see if the amount of air assumed is small enough
+ // that group processing is still accurate for source (aborts if not)
+ //Called by: airgroups/machinery expelling air, ?
+ //Inputs: The gas to try and merge
+ //Outputs: 1 on successful merge. 0 otherwise.
+
+ if(!giver)
+ return 0
+ if(((giver.oxygen > MINIMUM_AIR_TO_SUSPEND) && (giver.oxygen >= oxygen*MINIMUM_AIR_RATIO_TO_SUSPEND)) \
+ || ((giver.carbon_dioxide > MINIMUM_AIR_TO_SUSPEND) && (giver.carbon_dioxide >= carbon_dioxide*MINIMUM_AIR_RATIO_TO_SUSPEND)) \
+ || ((giver.nitrogen > MINIMUM_AIR_TO_SUSPEND) && (giver.nitrogen >= nitrogen*MINIMUM_AIR_RATIO_TO_SUSPEND)) \
+ || ((giver.toxins > MINIMUM_AIR_TO_SUSPEND) && (giver.toxins >= toxins*MINIMUM_AIR_RATIO_TO_SUSPEND)))
+ return 0
+ if(abs(giver.temperature - temperature) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND)
+ return 0
+
+ if(giver.trace_gases.len)
+ for(var/datum/gas/trace_gas in giver.trace_gases)
+ var/datum/gas/corresponding = locate(trace_gas.type) in trace_gases
+ if((trace_gas.moles > MINIMUM_AIR_TO_SUSPEND) && (!corresponding || (trace_gas.moles >= corresponding.moles*MINIMUM_AIR_RATIO_TO_SUSPEND)))
+ return 0
+
+ return merge(giver)
+
+/datum/gas_mixture/proc/merge(datum/gas_mixture/giver)
+ //Purpose: Merges all air from giver into self. Deletes giver.
+ //Called by: Machinery expelling air, check_then_merge, ?
+ //Inputs: The gas to merge.
+ //Outputs: 1
+
+ if(!giver)
+ return 0
+
+ if(abs(temperature-giver.temperature)>MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
+ var/self_heat_capacity = heat_capacity()*group_multiplier
+ var/giver_heat_capacity = giver.heat_capacity()*giver.group_multiplier
+ var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity
+ if(combined_heat_capacity != 0)
+ temperature = (giver.temperature*giver_heat_capacity + temperature*self_heat_capacity)/combined_heat_capacity
+
+ if((group_multiplier>1)||(giver.group_multiplier>1))
+ oxygen += giver.oxygen*giver.group_multiplier/group_multiplier
+ carbon_dioxide += giver.carbon_dioxide*giver.group_multiplier/group_multiplier
+ nitrogen += giver.nitrogen*giver.group_multiplier/group_multiplier
+ toxins += giver.toxins*giver.group_multiplier/group_multiplier
+ else
+ oxygen += giver.oxygen
+ carbon_dioxide += giver.carbon_dioxide
+ nitrogen += giver.nitrogen
+ toxins += giver.toxins
+
+ if(giver.trace_gases.len)
+ for(var/datum/gas/trace_gas in giver.trace_gases)
+ var/datum/gas/corresponding = locate(trace_gas.type) in trace_gases
+ if(!corresponding)
+ corresponding = new trace_gas.type()
+ trace_gases += corresponding
+ corresponding.moles += trace_gas.moles*giver.group_multiplier/group_multiplier
+ update_values()
+
+ // Let the garbage collector handle it, faster according to /tg/ testers
+ //del(giver)
+ return 1
+
+/datum/gas_mixture/proc/remove(amount)
+ //Purpose: Removes a certain number of moles from the air.
+ //Called by: ?
+ //Inputs: How many moles to remove.
+ //Outputs: Removed air.
+
+ var/sum = total_moles()
+ amount = min(amount,sum) //Can not take more air than tile has!
+ if(amount <= 0)
+ return null
+
+ var/datum/gas_mixture/removed = new
+
+
+ removed.oxygen = QUANTIZE((oxygen/sum)*amount)
+ removed.nitrogen = QUANTIZE((nitrogen/sum)*amount)
+ removed.carbon_dioxide = QUANTIZE((carbon_dioxide/sum)*amount)
+ removed.toxins = QUANTIZE(((toxins/sum)*amount))
+
+ oxygen -= removed.oxygen/group_multiplier
+ nitrogen -= removed.nitrogen/group_multiplier
+ carbon_dioxide -= removed.carbon_dioxide/group_multiplier
+ toxins -= removed.toxins/group_multiplier
+
+ if(trace_gases.len)
+ for(var/datum/gas/trace_gas in trace_gases)
+ var/datum/gas/corresponding = new trace_gas.type()
+ removed.trace_gases += corresponding
+
+ corresponding.moles = ((trace_gas.moles/sum)*amount)
+ trace_gas.moles -= (corresponding.moles/group_multiplier)
+
+ removed.temperature = temperature
+ update_values()
+ removed.update_values()
+
+ return removed
+
+/datum/gas_mixture/proc/remove_ratio(ratio)
+ //Purpose: Removes a certain ratio of the air.
+ //Called by: ?
+ //Inputs: Percentage to remove.
+ //Outputs: Removed air.
+
+ if(ratio <= 0)
+ return null
+
+ ratio = min(ratio, 1)
+
+ var/datum/gas_mixture/removed = new
+
+ removed.oxygen = QUANTIZE(oxygen*ratio)
+ removed.nitrogen = QUANTIZE(nitrogen*ratio)
+ removed.carbon_dioxide = QUANTIZE(carbon_dioxide*ratio)
+ removed.toxins = QUANTIZE(toxins*ratio)
+
+ oxygen -= removed.oxygen/group_multiplier
+ nitrogen -= removed.nitrogen/group_multiplier
+ carbon_dioxide -= removed.carbon_dioxide/group_multiplier
+ toxins -= removed.toxins/group_multiplier
+
+ if(trace_gases.len)
+ for(var/datum/gas/trace_gas in trace_gases)
+ var/datum/gas/corresponding = new trace_gas.type()
+ removed.trace_gases += corresponding
+
+ corresponding.moles = trace_gas.moles*ratio
+ trace_gas.moles -= corresponding.moles/group_multiplier
+
+ removed.temperature = temperature
+ update_values()
+ removed.update_values()
+
+ return removed
+
+/datum/gas_mixture/proc/check_then_remove(amount)
+ //Purpose: Similar to remove(...) but first checks to see if the amount of air removed is small enough
+ // that group processing is still accurate for source (aborts if not)
+ //Called by: ?
+ //Inputs: Number of moles to remove
+ //Outputs: Removed air or 0 if it can remove air or not.
+
+ amount = min(amount,total_moles()) //Can not take more air than tile has!
+
+ if((amount > MINIMUM_AIR_RATIO_TO_SUSPEND) && (amount > total_moles()*MINIMUM_AIR_RATIO_TO_SUSPEND))
+ return 0
+
+ return remove(amount)
+
+/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample)
+ //Purpose: Duplicates the sample air mixture.
+ //Called by: airgroups splitting, ?
+ //Inputs: Gas to copy
+ //Outputs: 1
+
+ oxygen = sample.oxygen
+ carbon_dioxide = sample.carbon_dioxide
+ nitrogen = sample.nitrogen
+ toxins = sample.toxins
+ total_moles = sample.total_moles()
+
+ trace_gases.len=null
+ if(sample.trace_gases.len > 0)
+ for(var/datum/gas/trace_gas in sample.trace_gases)
+ var/datum/gas/corresponding = new trace_gas.type()
+ trace_gases += corresponding
+
+ corresponding.moles = trace_gas.moles
+
+ temperature = sample.temperature
+
+ return 1
+
+/datum/gas_mixture/proc/check_gas_mixture(datum/gas_mixture/sharer)
+ //Purpose: Telling if one or both airgroups needs to disable group processing.
+ //Called by: Airgroups sharing air, checking if group processing needs disabled.
+ //Inputs: Gas to compare from other airgroup
+ //Outputs: 0 if the self-check failed (local airgroup breaks?)
+ // then -1 if sharer-check failed (sharing airgroup breaks?)
+ // then 1 if both checks pass (share succesful?)
+ if(!istype(sharer))
+ return
+
+ var/delta_oxygen = QUANTIZE(oxygen_archived - sharer.oxygen_archived)/TRANSFER_FRACTION
+ var/delta_carbon_dioxide = QUANTIZE(carbon_dioxide_archived - sharer.carbon_dioxide_archived)/TRANSFER_FRACTION
+ var/delta_nitrogen = QUANTIZE(nitrogen_archived - sharer.nitrogen_archived)/TRANSFER_FRACTION
+ var/delta_toxins = QUANTIZE(toxins_archived - sharer.toxins_archived)/TRANSFER_FRACTION
+
+ var/delta_temperature = (temperature_archived - sharer.temperature_archived)
+
+ if(((abs(delta_oxygen) > MINIMUM_AIR_TO_SUSPEND) && (abs(delta_oxygen) >= oxygen_archived*MINIMUM_AIR_RATIO_TO_SUSPEND)) \
+ || ((abs(delta_carbon_dioxide) > MINIMUM_AIR_TO_SUSPEND) && (abs(delta_carbon_dioxide) >= carbon_dioxide_archived*MINIMUM_AIR_RATIO_TO_SUSPEND)) \
+ || ((abs(delta_nitrogen) > MINIMUM_AIR_TO_SUSPEND) && (abs(delta_nitrogen) >= nitrogen_archived*MINIMUM_AIR_RATIO_TO_SUSPEND)) \
+ || ((abs(delta_toxins) > MINIMUM_AIR_TO_SUSPEND) && (abs(delta_toxins) >= toxins_archived*MINIMUM_AIR_RATIO_TO_SUSPEND)))
+ return 0
+
+ if(abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND)
+ return 0
+
+ if(sharer.trace_gases.len)
+ for(var/datum/gas/trace_gas in sharer.trace_gases)
+ if(trace_gas.moles_archived > MINIMUM_AIR_TO_SUSPEND*4)
+ var/datum/gas/corresponding = locate(trace_gas.type) in trace_gases
+ if(corresponding)
+ if(trace_gas.moles_archived >= corresponding.moles_archived*MINIMUM_AIR_RATIO_TO_SUSPEND*4)
+ return 0
+ else
+ return 0
+
+ if(trace_gases.len)
+ for(var/datum/gas/trace_gas in trace_gases)
+ if(trace_gas.moles_archived > MINIMUM_AIR_TO_SUSPEND*4)
+ if(!locate(trace_gas.type) in sharer.trace_gases)
+ return 0
+
+ if(((abs(delta_oxygen) > MINIMUM_AIR_TO_SUSPEND) && (abs(delta_oxygen) >= sharer.oxygen_archived*MINIMUM_AIR_RATIO_TO_SUSPEND)) \
+ || ((abs(delta_carbon_dioxide) > MINIMUM_AIR_TO_SUSPEND) && (abs(delta_carbon_dioxide) >= sharer.carbon_dioxide_archived*MINIMUM_AIR_RATIO_TO_SUSPEND)) \
+ || ((abs(delta_nitrogen) > MINIMUM_AIR_TO_SUSPEND) && (abs(delta_nitrogen) >= sharer.nitrogen_archived*MINIMUM_AIR_RATIO_TO_SUSPEND)) \
+ || ((abs(delta_toxins) > MINIMUM_AIR_TO_SUSPEND) && (abs(delta_toxins) >= sharer.toxins_archived*MINIMUM_AIR_RATIO_TO_SUSPEND)))
+ return -1
+
+ if(trace_gases.len)
+ for(var/datum/gas/trace_gas in trace_gases)
+ if(trace_gas.moles_archived > MINIMUM_AIR_TO_SUSPEND*4)
+ var/datum/gas/corresponding = locate(trace_gas.type) in sharer.trace_gases
+ if(corresponding)
+ if(trace_gas.moles_archived >= corresponding.moles_archived*MINIMUM_AIR_RATIO_TO_SUSPEND*4)
+ return -1
+ else
+ return -1
+
+ return 1
+
+/datum/gas_mixture/proc/check_turf(turf/model)
+ //Purpose: Used to compare the gases in an unsimulated turf with the gas in a simulated one.
+ //Called by: Sharing air (mimicing) with adjacent unsimulated turfs
+ //Inputs: Unsimulated turf
+ //Outputs: 1 if safe to mimic, 0 if needs to break airgroup.
+
+ var/delta_oxygen = (oxygen_archived - model.oxygen)/TRANSFER_FRACTION
+ var/delta_carbon_dioxide = (carbon_dioxide_archived - model.carbon_dioxide)/TRANSFER_FRACTION
+ var/delta_nitrogen = (nitrogen_archived - model.nitrogen)/TRANSFER_FRACTION
+ var/delta_toxins = (toxins_archived - model.toxins)/TRANSFER_FRACTION
+
+ var/delta_temperature = (temperature_archived - model.temperature)
+
+ if(((abs(delta_oxygen) > MINIMUM_AIR_TO_SUSPEND) && (abs(delta_oxygen) >= oxygen_archived*MINIMUM_AIR_RATIO_TO_SUSPEND)) \
+ || ((abs(delta_carbon_dioxide) > MINIMUM_AIR_TO_SUSPEND) && (abs(delta_carbon_dioxide) >= carbon_dioxide_archived*MINIMUM_AIR_RATIO_TO_SUSPEND)) \
+ || ((abs(delta_nitrogen) > MINIMUM_AIR_TO_SUSPEND) && (abs(delta_nitrogen) >= nitrogen_archived*MINIMUM_AIR_RATIO_TO_SUSPEND)) \
+ || ((abs(delta_toxins) > MINIMUM_AIR_TO_SUSPEND) && (abs(delta_toxins) >= toxins_archived*MINIMUM_AIR_RATIO_TO_SUSPEND)))
+ return 0
+ if(abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND)
+ return 0
+
+ if(trace_gases.len)
+ for(var/datum/gas/trace_gas in trace_gases)
+ if(trace_gas.moles_archived > MINIMUM_AIR_TO_SUSPEND*4)
+ return 0
+
+ return 1
+
+/datum/gas_mixture/proc/share(datum/gas_mixture/sharer)
+ //Purpose: Used to transfer gas from a more pressurised tile to a less presurised tile
+ // (Two directional, if the other tile is more pressurised, air travels to current tile)
+ //Called by: Sharing air with adjacent simulated turfs
+ //Inputs: Air datum to share with
+ //Outputs: Amount of gas exchanged (Negative if lost air, positive if gained.)
+
+
+ if(!istype(sharer))
+ return
+
+ var/delta_oxygen = QUANTIZE(oxygen_archived - sharer.oxygen_archived)/TRANSFER_FRACTION
+ var/delta_carbon_dioxide = QUANTIZE(carbon_dioxide_archived - sharer.carbon_dioxide_archived)/TRANSFER_FRACTION
+ var/delta_nitrogen = QUANTIZE(nitrogen_archived - sharer.nitrogen_archived)/TRANSFER_FRACTION
+ var/delta_toxins = QUANTIZE(toxins_archived - sharer.toxins_archived)/TRANSFER_FRACTION
+
+ var/delta_temperature = (temperature_archived - sharer.temperature_archived)
+
+ var/old_self_heat_capacity = 0
+ var/old_sharer_heat_capacity = 0
+
+ var/heat_self_to_sharer = 0
+ var/heat_capacity_self_to_sharer = 0
+ var/heat_sharer_to_self = 0
+ var/heat_capacity_sharer_to_self = 0
+
+ if(abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
+
+ var/delta_air = delta_oxygen+delta_nitrogen
+ if(delta_air)
+ var/air_heat_capacity = SPECIFIC_HEAT_AIR*delta_air
+ if(delta_air > 0)
+ heat_self_to_sharer += air_heat_capacity*temperature_archived
+ heat_capacity_self_to_sharer += air_heat_capacity
+ else
+ heat_sharer_to_self -= air_heat_capacity*sharer.temperature_archived
+ heat_capacity_sharer_to_self -= air_heat_capacity
+
+ if(delta_carbon_dioxide)
+ var/carbon_dioxide_heat_capacity = SPECIFIC_HEAT_CDO*delta_carbon_dioxide
+ if(delta_carbon_dioxide > 0)
+ heat_self_to_sharer += carbon_dioxide_heat_capacity*temperature_archived
+ heat_capacity_self_to_sharer += carbon_dioxide_heat_capacity
+ else
+ heat_sharer_to_self -= carbon_dioxide_heat_capacity*sharer.temperature_archived
+ heat_capacity_sharer_to_self -= carbon_dioxide_heat_capacity
+
+ if(delta_toxins)
+ var/toxins_heat_capacity = SPECIFIC_HEAT_TOXIN*delta_toxins
+ if(delta_toxins > 0)
+ heat_self_to_sharer += toxins_heat_capacity*temperature_archived
+ heat_capacity_self_to_sharer += toxins_heat_capacity
+ else
+ heat_sharer_to_self -= toxins_heat_capacity*sharer.temperature_archived
+ heat_capacity_sharer_to_self -= toxins_heat_capacity
+
+ old_self_heat_capacity = heat_capacity()*group_multiplier
+ old_sharer_heat_capacity = sharer.heat_capacity()*sharer.group_multiplier
+
+ oxygen -= delta_oxygen/group_multiplier
+ sharer.oxygen += delta_oxygen/sharer.group_multiplier
+
+ carbon_dioxide -= delta_carbon_dioxide/group_multiplier
+ sharer.carbon_dioxide += delta_carbon_dioxide/sharer.group_multiplier
+
+ nitrogen -= delta_nitrogen/group_multiplier
+ sharer.nitrogen += delta_nitrogen/sharer.group_multiplier
+
+ toxins -= delta_toxins/group_multiplier
+ sharer.toxins += delta_toxins/sharer.group_multiplier
+
+ var/moved_moles = (delta_oxygen + delta_carbon_dioxide + delta_nitrogen + delta_toxins)
+
+ var/list/trace_types_considered = list()
+
+ if(trace_gases.len)
+ for(var/datum/gas/trace_gas in trace_gases)
+
+ var/datum/gas/corresponding = locate(trace_gas.type) in sharer.trace_gases
+ var/delta = 0
+
+ if(corresponding)
+ delta = QUANTIZE(trace_gas.moles_archived - corresponding.moles_archived)/TRANSFER_FRACTION
+ else
+ corresponding = new trace_gas.type()
+ sharer.trace_gases += corresponding
+
+ delta = trace_gas.moles_archived/TRANSFER_FRACTION
+
+ trace_gas.moles -= delta/group_multiplier
+ corresponding.moles += delta/sharer.group_multiplier
+
+ if(delta)
+ var/individual_heat_capacity = trace_gas.specific_heat*delta
+ if(delta > 0)
+ heat_self_to_sharer += individual_heat_capacity*temperature_archived
+ heat_capacity_self_to_sharer += individual_heat_capacity
+ else
+ heat_sharer_to_self -= individual_heat_capacity*sharer.temperature_archived
+ heat_capacity_sharer_to_self -= individual_heat_capacity
+
+ moved_moles += delta
+
+ trace_types_considered += trace_gas.type
+
+
+ if(sharer.trace_gases.len)
+ for(var/datum/gas/trace_gas in sharer.trace_gases)
+ if(trace_gas.type in trace_types_considered) continue
+ else
+ var/datum/gas/corresponding
+ var/delta = 0
+
+ corresponding = new trace_gas.type()
+ trace_gases += corresponding
+
+ delta = trace_gas.moles_archived/TRANSFER_FRACTION
+
+ trace_gas.moles -= delta/sharer.group_multiplier
+ corresponding.moles += delta/group_multiplier
+
+ //Guaranteed transfer from sharer to self
+ var/individual_heat_capacity = trace_gas.specific_heat*delta
+ heat_sharer_to_self += individual_heat_capacity*sharer.temperature_archived
+ heat_capacity_sharer_to_self += individual_heat_capacity
+
+ moved_moles += -delta
+ update_values()
+ sharer.update_values()
+
+ if(abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
+ var/new_self_heat_capacity = old_self_heat_capacity + heat_capacity_sharer_to_self - heat_capacity_self_to_sharer
+ var/new_sharer_heat_capacity = old_sharer_heat_capacity + heat_capacity_self_to_sharer - heat_capacity_sharer_to_self
+
+ if(new_self_heat_capacity > MINIMUM_HEAT_CAPACITY)
+ temperature = (old_self_heat_capacity*temperature - heat_capacity_self_to_sharer*temperature_archived + heat_capacity_sharer_to_self*sharer.temperature_archived)/new_self_heat_capacity
+
+ if(new_sharer_heat_capacity > MINIMUM_HEAT_CAPACITY)
+ sharer.temperature = (old_sharer_heat_capacity*sharer.temperature-heat_capacity_sharer_to_self*sharer.temperature_archived + heat_capacity_self_to_sharer*temperature_archived)/new_sharer_heat_capacity
+
+ if(abs(old_sharer_heat_capacity) > MINIMUM_HEAT_CAPACITY)
+ if(abs(new_sharer_heat_capacity/old_sharer_heat_capacity - 1) < 0.10) // <10% change in sharer heat capacity
+ temperature_share(sharer, OPEN_HEAT_TRANSFER_COEFFICIENT)
+
+ if((delta_temperature > MINIMUM_TEMPERATURE_TO_MOVE) || abs(moved_moles) > MINIMUM_MOLES_DELTA_TO_MOVE)
+ var/delta_pressure = temperature_archived*(total_moles() + moved_moles) - sharer.temperature_archived*(sharer.total_moles() - moved_moles)
+ return delta_pressure*R_IDEAL_GAS_EQUATION/volume
+
+ else
+ return 0
+
+/datum/gas_mixture/proc/mimic(turf/model, border_multiplier)
+ //Purpose: Used transfer gas from a more pressurised tile to a less presurised unsimulated tile.
+ //Called by: "sharing" from unsimulated to simulated turfs.
+ //Inputs: Unsimulated turf, Multiplier for gas transfer (optional)
+ //Outputs: Amount of gas exchanged
+
+ var/delta_oxygen = QUANTIZE(oxygen_archived - model.oxygen)/TRANSFER_FRACTION
+ var/delta_carbon_dioxide = QUANTIZE(carbon_dioxide_archived - model.carbon_dioxide)/TRANSFER_FRACTION
+ var/delta_nitrogen = QUANTIZE(nitrogen_archived - model.nitrogen)/TRANSFER_FRACTION
+ var/delta_toxins = QUANTIZE(toxins_archived - model.toxins)/TRANSFER_FRACTION
+
+ var/delta_temperature = (temperature_archived - model.temperature)
+
+ var/heat_transferred = 0
+ var/old_self_heat_capacity = 0
+ var/heat_capacity_transferred = 0
+
+ if(abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
+
+ var/delta_air = delta_oxygen+delta_nitrogen
+ if(delta_air)
+ var/air_heat_capacity = SPECIFIC_HEAT_AIR*delta_air
+ heat_transferred -= air_heat_capacity*model.temperature
+ heat_capacity_transferred -= air_heat_capacity
+
+ if(delta_carbon_dioxide)
+ var/carbon_dioxide_heat_capacity = SPECIFIC_HEAT_CDO*delta_carbon_dioxide
+ heat_transferred -= carbon_dioxide_heat_capacity*model.temperature
+ heat_capacity_transferred -= carbon_dioxide_heat_capacity
+
+ if(delta_toxins)
+ var/toxins_heat_capacity = SPECIFIC_HEAT_TOXIN*delta_toxins
+ heat_transferred -= toxins_heat_capacity*model.temperature
+ heat_capacity_transferred -= toxins_heat_capacity
+
+ old_self_heat_capacity = heat_capacity()*group_multiplier
+
+ if(border_multiplier)
+ oxygen -= delta_oxygen*border_multiplier/group_multiplier
+ carbon_dioxide -= delta_carbon_dioxide*border_multiplier/group_multiplier
+ nitrogen -= delta_nitrogen*border_multiplier/group_multiplier
+ toxins -= delta_toxins*border_multiplier/group_multiplier
+ else
+ oxygen -= delta_oxygen/group_multiplier
+ carbon_dioxide -= delta_carbon_dioxide/group_multiplier
+ nitrogen -= delta_nitrogen/group_multiplier
+ toxins -= delta_toxins/group_multiplier
+
+ var/moved_moles = (delta_oxygen + delta_carbon_dioxide + delta_nitrogen + delta_toxins)
+
+ if(trace_gases.len)
+ for(var/datum/gas/trace_gas in trace_gases)
+ var/delta = 0
+
+ delta = trace_gas.moles_archived/TRANSFER_FRACTION
+
+ if(border_multiplier)
+ trace_gas.moles -= delta*border_multiplier/group_multiplier
+ else
+ trace_gas.moles -= delta/group_multiplier
+
+ var/heat_cap_transferred = delta*trace_gas.specific_heat
+ heat_transferred += heat_cap_transferred*temperature_archived
+ heat_capacity_transferred += heat_cap_transferred
+ moved_moles += delta
+ update_values()
+
+ if(abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
+ var/new_self_heat_capacity = old_self_heat_capacity - heat_capacity_transferred
+ if(new_self_heat_capacity > MINIMUM_HEAT_CAPACITY)
+ if(border_multiplier)
+ temperature = (old_self_heat_capacity*temperature - heat_capacity_transferred*border_multiplier*temperature_archived)/new_self_heat_capacity
+ else
+ temperature = (old_self_heat_capacity*temperature - heat_capacity_transferred*border_multiplier*temperature_archived)/new_self_heat_capacity
+
+ temperature_mimic(model, model.thermal_conductivity, border_multiplier)
+
+ if((delta_temperature > MINIMUM_TEMPERATURE_TO_MOVE) || abs(moved_moles) > MINIMUM_MOLES_DELTA_TO_MOVE)
+ var/delta_pressure = temperature_archived*(total_moles() + moved_moles) - model.temperature*(model.oxygen+model.carbon_dioxide+model.nitrogen+model.toxins)
+ return delta_pressure*R_IDEAL_GAS_EQUATION/volume
+ else
+ return 0
+
+/datum/gas_mixture/proc/check_both_then_temperature_share(datum/gas_mixture/sharer, conduction_coefficient)
+ var/delta_temperature = (temperature_archived - sharer.temperature_archived)
+
+ var/self_heat_capacity = heat_capacity_archived()
+ var/sharer_heat_capacity = sharer.heat_capacity_archived()
+
+ var/self_temperature_delta = 0
+ var/sharer_temperature_delta = 0
+
+ if((sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY))
+ var/heat = conduction_coefficient*delta_temperature* \
+ (self_heat_capacity*sharer_heat_capacity/(self_heat_capacity+sharer_heat_capacity))
+
+ self_temperature_delta = -heat/(self_heat_capacity*group_multiplier)
+ sharer_temperature_delta = heat/(sharer_heat_capacity*sharer.group_multiplier)
+ else
+ return 1
+
+ if((abs(self_temperature_delta) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) \
+ && (abs(self_temperature_delta) > MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND*temperature_archived))
+ return 0
+
+ if((abs(sharer_temperature_delta) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) \
+ && (abs(sharer_temperature_delta) > MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND*sharer.temperature_archived))
+ return -1
+
+ temperature += self_temperature_delta
+ sharer.temperature += sharer_temperature_delta
+
+ return 1
+ //Logic integrated from: temperature_share(sharer, conduction_coefficient) for efficiency
+
+/datum/gas_mixture/proc/check_me_then_temperature_share(datum/gas_mixture/sharer, conduction_coefficient)
+ var/delta_temperature = (temperature_archived - sharer.temperature_archived)
+
+ var/self_heat_capacity = heat_capacity_archived()
+ var/sharer_heat_capacity = sharer.heat_capacity_archived()
+
+ var/self_temperature_delta = 0
+ var/sharer_temperature_delta = 0
+
+ if((sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY))
+ var/heat = conduction_coefficient*delta_temperature* \
+ (self_heat_capacity*sharer_heat_capacity/(self_heat_capacity+sharer_heat_capacity))
+
+ self_temperature_delta = -heat/(self_heat_capacity*group_multiplier)
+ sharer_temperature_delta = heat/(sharer_heat_capacity*sharer.group_multiplier)
+ else
+ return 1
+
+ if((abs(self_temperature_delta) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) \
+ && (abs(self_temperature_delta) > MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND*temperature_archived))
+ return 0
+
+ temperature += self_temperature_delta
+ sharer.temperature += sharer_temperature_delta
+
+ return 1
+ //Logic integrated from: temperature_share(sharer, conduction_coefficient) for efficiency
+
+/datum/gas_mixture/proc/check_me_then_temperature_turf_share(turf/simulated/sharer, conduction_coefficient)
+ var/delta_temperature = (temperature_archived - sharer.temperature)
+
+ var/self_temperature_delta = 0
+ var/sharer_temperature_delta = 0
+
+ if(abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
+ var/self_heat_capacity = heat_capacity_archived()
+
+ if((sharer.heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY))
+ var/heat = conduction_coefficient*delta_temperature* \
+ (self_heat_capacity*sharer.heat_capacity/(self_heat_capacity+sharer.heat_capacity))
+
+ self_temperature_delta = -heat/(self_heat_capacity*group_multiplier)
+ sharer_temperature_delta = heat/sharer.heat_capacity
+ else
+ return 1
+
+ if((abs(self_temperature_delta) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) \
+ && (abs(self_temperature_delta) > MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND*temperature_archived))
+ return 0
+
+ temperature += self_temperature_delta
+ sharer.temperature += sharer_temperature_delta
+
+ return 1
+ //Logic integrated from: temperature_turf_share(sharer, conduction_coefficient) for efficiency
+
+/datum/gas_mixture/proc/check_me_then_temperature_mimic(turf/model, conduction_coefficient)
+ var/delta_temperature = (temperature_archived - model.temperature)
+ var/self_temperature_delta = 0
+
+ if(abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
+ var/self_heat_capacity = heat_capacity_archived()
+
+ if((model.heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY))
+ var/heat = conduction_coefficient*delta_temperature* \
+ (self_heat_capacity*model.heat_capacity/(self_heat_capacity+model.heat_capacity))
+
+ self_temperature_delta = -heat/(self_heat_capacity*group_multiplier)
+
+ if((abs(self_temperature_delta) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) \
+ && (abs(self_temperature_delta) > MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND*temperature_archived))
+ return 0
+
+ temperature += self_temperature_delta
+
+ return 1
+ //Logic integrated from: temperature_mimic(model, conduction_coefficient) for efficiency
+
+/datum/gas_mixture/proc/temperature_share(datum/gas_mixture/sharer, conduction_coefficient)
+ var/delta_temperature = (temperature_archived - sharer.temperature_archived)
+ if(abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
+ var/self_heat_capacity = heat_capacity_archived()
+ var/sharer_heat_capacity = sharer.heat_capacity_archived()
+ if(!group_multiplier)
+ message_admins("Error! The gas mixture (ref \ref[src]) has no group multiplier!")
+ return
+
+ if((sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY))
+ var/heat = conduction_coefficient*delta_temperature* \
+ (self_heat_capacity*sharer_heat_capacity/(self_heat_capacity+sharer_heat_capacity))
+
+ temperature -= heat/(self_heat_capacity*group_multiplier)
+ sharer.temperature += heat/(sharer_heat_capacity*sharer.group_multiplier)
+
+/datum/gas_mixture/proc/temperature_mimic(turf/model, conduction_coefficient, border_multiplier)
+ var/delta_temperature = (temperature - model.temperature)
+ if(abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
+ var/self_heat_capacity = heat_capacity()//_archived()
+ if(!group_multiplier)
+ message_admins("Error! The gas mixture (ref \ref[src]) has no group multiplier!")
+ return
+
+ if((model.heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY))
+ var/heat = conduction_coefficient*delta_temperature* \
+ (self_heat_capacity*model.heat_capacity/(self_heat_capacity+model.heat_capacity))
+
+ if(border_multiplier)
+ temperature -= heat*border_multiplier/(self_heat_capacity*group_multiplier)
+ else
+ temperature -= heat/(self_heat_capacity*group_multiplier)
+
+/datum/gas_mixture/proc/temperature_turf_share(turf/simulated/sharer, conduction_coefficient)
+ var/delta_temperature = (temperature_archived - sharer.temperature)
+ if(abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
+ var/self_heat_capacity = heat_capacity()
+
+ if((sharer.heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY))
+ var/heat = conduction_coefficient*delta_temperature* \
+ (self_heat_capacity*sharer.heat_capacity/(self_heat_capacity+sharer.heat_capacity))
+
+ temperature -= heat/(self_heat_capacity*group_multiplier)
+ sharer.temperature += heat/sharer.heat_capacity
+
+/datum/gas_mixture/proc/compare(datum/gas_mixture/sample)
+ //Purpose: Compares sample to self to see if within acceptable ranges that group processing may be enabled
+ //Called by: Airgroups trying to rebuild
+ //Inputs: Gas mix to compare
+ //Outputs: 1 if can rebuild, 0 if not.
+ if(!sample) return 0
+
+
+ if((abs(oxygen-sample.oxygen) > MINIMUM_AIR_TO_SUSPEND) && \
+ ((oxygen < (1-MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.oxygen) || (oxygen > (1+MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.oxygen)))
+ return 0
+ if((abs(nitrogen-sample.nitrogen) > MINIMUM_AIR_TO_SUSPEND) && \
+ ((nitrogen < (1-MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.nitrogen) || (nitrogen > (1+MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.nitrogen)))
+ return 0
+ if((abs(carbon_dioxide-sample.carbon_dioxide) > MINIMUM_AIR_TO_SUSPEND) && \
+ ((carbon_dioxide < (1-MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.carbon_dioxide) || (carbon_dioxide > (1+MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.carbon_dioxide)))
+ return 0
+ if((abs(toxins-sample.toxins) > MINIMUM_AIR_TO_SUSPEND) && \
+ ((toxins < (1-MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.toxins) || (toxins > (1+MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.toxins)))
+ return 0
+
+
+ if(total_moles() > MINIMUM_AIR_TO_SUSPEND)
+ if((abs(temperature-sample.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) && \
+ ((temperature < (1-MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND)*sample.temperature) || (temperature > (1+MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND)*sample.temperature)))
+ //world << "temp fail [temperature] & [sample.temperature]"
+ return 0
+ var/check_moles
+ if(sample.trace_gases.len)
+ for(var/datum/gas/trace_gas in sample.trace_gases)
+ var/datum/gas/corresponding = locate(trace_gas.type) in trace_gases
+ if(corresponding)
+ check_moles = corresponding.moles
+ else
+ check_moles = 0
+
+ if((abs(trace_gas.moles - check_moles) > MINIMUM_AIR_TO_SUSPEND) && \
+ ((check_moles < (1-MINIMUM_AIR_RATIO_TO_SUSPEND)*trace_gas.moles) || (check_moles > (1+MINIMUM_AIR_RATIO_TO_SUSPEND)*trace_gas.moles)))
+ return 0
+
+ if(trace_gases.len)
+ for(var/datum/gas/trace_gas in trace_gases)
+ var/datum/gas/corresponding = locate(trace_gas.type) in trace_gases
+ if(corresponding)
+ check_moles = corresponding.moles
+ else
+ check_moles = 0
+
+ if((abs(trace_gas.moles - check_moles) > MINIMUM_AIR_TO_SUSPEND) && \
+ ((trace_gas.moles < (1-MINIMUM_AIR_RATIO_TO_SUSPEND)*check_moles) || (trace_gas.moles > (1+MINIMUM_AIR_RATIO_TO_SUSPEND)*check_moles)))
+ return 0
+
+ return 1
+
+/datum/gas_mixture/proc/add(datum/gas_mixture/right_side)
+ oxygen += right_side.oxygen
+ carbon_dioxide += right_side.carbon_dioxide
+ nitrogen += right_side.nitrogen
+ toxins += right_side.toxins
+
+ if(trace_gases.len || right_side.trace_gases.len)
+ for(var/datum/gas/trace_gas in right_side.trace_gases)
+ var/datum/gas/corresponding = locate(trace_gas.type) in trace_gases
+ if(!corresponding)
+ corresponding = new trace_gas.type()
+ trace_gases += corresponding
+ corresponding.moles += trace_gas.moles
+
+ update_values()
+ return 1
+
+/datum/gas_mixture/proc/subtract(datum/gas_mixture/right_side)
+ //Purpose: Subtracts right_side from air_mixture. Used to help turfs mingle
+ //Called by: Pipelines ending in a break (or something)
+ //Inputs: Gas mix to remove
+ //Outputs: 1
+
+ oxygen = max(oxygen - right_side.oxygen)
+ carbon_dioxide = max(carbon_dioxide - right_side.carbon_dioxide)
+ nitrogen = max(nitrogen - right_side.nitrogen)
+ toxins = max(toxins - right_side.toxins)
+
+ if(trace_gases.len || right_side.trace_gases.len)
+ for(var/datum/gas/trace_gas in right_side.trace_gases)
+ var/datum/gas/corresponding = locate(trace_gas.type) in trace_gases
+ if(corresponding)
+ corresponding.moles = max(0, corresponding.moles - trace_gas.moles)
+
+ update_values()
+ return 1
+
+/datum/gas_mixture/proc/multiply(factor)
+ oxygen *= factor
+ carbon_dioxide *= factor
+ nitrogen *= factor
+ toxins *= factor
+
+ if(trace_gases && trace_gases.len)
+ for(var/datum/gas/trace_gas in trace_gases)
+ trace_gas.moles *= factor
+
+ update_values()
+ return 1
+
+/datum/gas_mixture/proc/divide(factor)
+ oxygen /= factor
+ carbon_dioxide /= factor
+ nitrogen /= factor
+ toxins /= factor
+
+ if(trace_gases && trace_gases.len)
+ for(var/datum/gas/trace_gas in trace_gases)
+ trace_gas.moles /= factor
+
+ update_values()
+ return 1
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 88eea9131b4..766aa7d663e 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -264,39 +264,16 @@
return 1
/obj/machinery/door/proc/update_nearby_tiles(need_rebuild)
- if(!air_master) return 0
+ if(!air_master)
+ return 0
- var/turf/simulated/source = loc
- var/turf/simulated/north = get_step(source,NORTH)
- var/turf/simulated/south = get_step(source,SOUTH)
- var/turf/simulated/east = get_step(source,EAST)
- var/turf/simulated/west = get_step(source,WEST)
-
- update_heat_protection(loc)
-
- if(istype(source)) air_master.tiles_to_update += source
- if(istype(north)) air_master.tiles_to_update += north
- if(istype(south)) air_master.tiles_to_update += south
- if(istype(east)) air_master.tiles_to_update += east
- if(istype(west)) air_master.tiles_to_update += west
-
- if(width > 1)
- var/turf/simulated/next_turf = src
- var/step_dir = turn(dir, 180)
- for(var/current_step = 2, current_step <= width, current_step++)
- next_turf = get_step(src, step_dir)
- north = get_step(next_turf, step_dir)
- east = get_step(next_turf, turn(step_dir, 90))
- south = get_step(next_turf, turn(step_dir, -90))
-
- update_heat_protection(next_turf)
-
- if(istype(north)) air_master.tiles_to_update |= north
- if(istype(south)) air_master.tiles_to_update |= south
- if(istype(east)) air_master.tiles_to_update |= east
+ for(var/turf/simulated/turf in locs)
+ update_heat_protection(turf)
+ air_master.mark_for_update(turf)
return 1
+
/obj/machinery/door/proc/update_heat_protection(var/turf/simulated/source)
if(istype(source))
if(src.density && (src.opacity || src.heat_proof))
diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm
index b2c1b5a3b7c..ba457e2685d 100644
--- a/code/game/machinery/doors/windowdoor.dm
+++ b/code/game/machinery/doors/windowdoor.dm
@@ -17,11 +17,7 @@
/obj/machinery/door/window/update_nearby_tiles(need_rebuild)
if(!air_master) return 0
- var/turf/simulated/source = get_turf(src)
- var/turf/simulated/target = get_step(source,dir)
-
- if(istype(source)) air_master.tiles_to_update |= source
- if(istype(target)) air_master.tiles_to_update |= target
+ air_master.mark_for_update(get_turf(src))
return 1
diff --git a/code/game/machinery/shieldgen.dm b/code/game/machinery/shieldgen.dm
index b903ced749d..e6225f648d7 100644
--- a/code/game/machinery/shieldgen.dm
+++ b/code/game/machinery/shieldgen.dm
@@ -29,17 +29,7 @@
/obj/machinery/shield/proc/update_nearby_tiles(need_rebuild)
if(!air_master) return 0
- var/turf/simulated/source = get_turf(src)
- var/turf/simulated/north = get_step(source,NORTH)
- var/turf/simulated/south = get_step(source,SOUTH)
- var/turf/simulated/east = get_step(source,EAST)
- var/turf/simulated/west = get_step(source,WEST)
-
- if(istype(source)) air_master.tiles_to_update |= source
- if(istype(north)) air_master.tiles_to_update |= north
- if(istype(south)) air_master.tiles_to_update |= south
- if(istype(east)) air_master.tiles_to_update |= east
- if(istype(west)) air_master.tiles_to_update |= west
+ air_master.mark_for_update(get_turf(src))
return 1
diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm
index 0fec6297f64..4f7c73b0d54 100644
--- a/code/game/objects/effects/effect_system.dm
+++ b/code/game/objects/effects/effect_system.dm
@@ -1058,19 +1058,10 @@ steam.start() -- spawns the effect
proc/update_nearby_tiles(need_rebuild)
- if(!air_master) return 0
+ if(!air_master)
+ return 0
- var/turf/simulated/source = get_turf(src)
- var/turf/simulated/north = get_step(source,NORTH)
- var/turf/simulated/south = get_step(source,SOUTH)
- var/turf/simulated/east = get_step(source,EAST)
- var/turf/simulated/west = get_step(source,WEST)
-
- if(istype(source)) air_master.tiles_to_update |= source
- if(istype(north)) air_master.tiles_to_update |= north
- if(istype(south)) air_master.tiles_to_update |= south
- if(istype(east)) air_master.tiles_to_update |= east
- if(istype(west)) air_master.tiles_to_update |= west
+ air_master.mark_for_update(get_turf(src))
return 1
diff --git a/code/game/objects/structures/false_walls.dm b/code/game/objects/structures/false_walls.dm
index 58942ef6342..e483731f1c4 100644
--- a/code/game/objects/structures/false_walls.dm
+++ b/code/game/objects/structures/false_walls.dm
@@ -14,9 +14,9 @@
var/minp=16777216;
var/maxp=0;
for(var/dir in cardinal)
- var/turf/T=get_turf(get_step(loc,dir))
+ var/turf/simulated/T=get_turf(get_step(loc,dir))
var/cp=0
- if(T && istype(T,/turf/simulated) && T.zone)
+ if(T && istype(T) && T.zone)
var/datum/gas_mixture/environment = T.return_air()
cp = environment.return_pressure()
else
diff --git a/code/game/objects/structures/mineral_doors.dm b/code/game/objects/structures/mineral_doors.dm
index 43e3d37ee60..9472b64676e 100644
--- a/code/game/objects/structures/mineral_doors.dm
+++ b/code/game/objects/structures/mineral_doors.dm
@@ -158,17 +158,7 @@
proc/update_nearby_tiles(need_rebuild) //Copypasta from airlock code
if(!air_master) return 0
- var/turf/simulated/source = loc
- var/turf/simulated/north = get_step(source,NORTH)
- var/turf/simulated/south = get_step(source,SOUTH)
- var/turf/simulated/east = get_step(source,EAST)
- var/turf/simulated/west = get_step(source,WEST)
-
- if(istype(source)) air_master.tiles_to_update += source
- if(istype(north)) air_master.tiles_to_update += north
- if(istype(south)) air_master.tiles_to_update += south
- if(istype(east)) air_master.tiles_to_update += east
- if(istype(west)) air_master.tiles_to_update += west
+ air_master.mark_for_update(get_turf(src))
return 1
diff --git a/code/game/objects/structures/windoor_assembly.dm b/code/game/objects/structures/windoor_assembly.dm
index c8b3b93f305..b571a869009 100644
--- a/code/game/objects/structures/windoor_assembly.dm
+++ b/code/game/objects/structures/windoor_assembly.dm
@@ -296,10 +296,6 @@ obj/structure/windoor_assembly/Destroy()
/obj/structure/windoor_assembly/proc/update_nearby_tiles(need_rebuild)
if(!air_master) return 0
- var/turf/simulated/source = loc
- var/turf/simulated/target = get_step(source,dir)
-
- if(istype(source)) air_master.tiles_to_update += source
- if(istype(target)) air_master.tiles_to_update += target
+ air_master.mark_for_update(loc)
return 1
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index d7a606f63e4..4bdd5ac3613 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -366,20 +366,7 @@
//This proc has to do with airgroups and atmos, it has nothing to do with smoothwindows, that's update_nearby_tiles().
/obj/structure/window/proc/update_nearby_tiles(need_rebuild)
if(!air_master) return 0
- if(!dir in cardinal)
- var/turf/simulated/source = get_turf(src)
- if(istype(source))
- air_master.tiles_to_update |= source
- for(var/dir in cardinal)
- var/turf/simulated/target = get_step(source,dir)
- if(istype(target)) air_master.tiles_to_update |= target
- return 1
-
- var/turf/simulated/source = get_turf(src)
- var/turf/simulated/target = get_step(source,dir)
-
- if(istype(source)) air_master.tiles_to_update |= source
- if(istype(target)) air_master.tiles_to_update |= target
+ air_master.mark_for_update(get_turf(src))
return 1
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index cc9f12b51f9..005c8a85ab0 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -206,9 +206,27 @@
var/old_lumcount = lighting_lumcount - initial(lighting_lumcount)
+ //world << "Replacing [src.type] with [N]"
+
+ if(connections) connections.erase_all()
+
+ if(istype(src,/turf/simulated))
+ //Yeah, we're just going to rebuild the whole thing.
+ //Despite this being called a bunch during explosions,
+ //the zone will only really do heavy lifting once.
+ var/turf/simulated/S = src
+ if(S.zone) S.zone.rebuild()
+
if(ispath(N, /turf/simulated/floor))
+ //if the old turf had a zone, connect the new turf to it as well - Cael
+ //Adjusted by SkyMarshal 5/10/13 - The air master will handle the addition of the new turf.
+ //if(zone)
+ // zone.RemoveTurf(src)
+ // if(!zone.CheckStatus())
+ // zone.SetStatus(ZONE_ACTIVE)
+
var/turf/simulated/W = new N( locate(src.x, src.y, src.z) )
- W.Assimilate_Air()
+ //W.Assimilate_Air()
W.lighting_lumcount += old_lumcount
if(old_lumcount != W.lighting_lumcount)
@@ -218,20 +236,17 @@
if (istype(W,/turf/simulated/floor))
W.RemoveLattice()
- //if the old turf had a zone, connect the new turf to it as well - Cael
- if(src.zone)
- src.zone.RemoveTurf(src)
- W.zone = src.zone
- W.zone.AddTurf(W)
-
- for(var/turf/simulated/T in orange(src,1))
- air_master.tiles_to_update.Add(T)
+ if(air_master)
+ air_master.mark_for_update(src)
W.levelupdate()
return W
+
else
- /*if(istype(src, /turf/simulated) && src.zone)
- src.zone.rebuild = 1*/
+ //if(zone)
+ // zone.RemoveTurf(src)
+ // if(!zone.CheckStatus())
+ // zone.SetStatus(ZONE_ACTIVE)
var/turf/W = new N( locate(src.x, src.y, src.z) )
W.lighting_lumcount += old_lumcount
@@ -239,18 +254,13 @@
W.lighting_changed = 1
lighting_controller.changed_turfs += W
- if(src.zone)
- src.zone.RemoveTurf(src)
- W.zone = src.zone
- W.zone.AddTurf(W)
-
if(air_master)
- for(var/turf/simulated/T in orange(src,1))
- air_master.tiles_to_update.Add(T)
+ air_master.mark_for_update(src)
W.levelupdate()
return W
+/*
//////Assimilate Air//////
/turf/simulated/proc/Assimilate_Air()
var/aoxy = 0//Holders to assimilate air from nearby turfs
@@ -295,7 +305,7 @@
S.air.toxins = air.toxins
S.air.temperature = air.temperature
S.air.update_values()
-
+*/
/turf/proc/ReplaceWithLattice()
src.ChangeTurf(/turf/space)
new /obj/structure/lattice( locate(src.x, src.y, src.z) )
diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm
index af7f4f545e2..7b952382a5d 100644
--- a/code/modules/admin/verbs/mapping.dm
+++ b/code/modules/admin/verbs/mapping.dm
@@ -161,6 +161,8 @@ var/intercom_range_display_status = 0
src.verbs += /client/proc/disable_movement
src.verbs += /client/proc/Zone_Info
src.verbs += /client/proc/Test_ZAS_Connection
+ src.verbs += /client/proc/ZoneTick
+ //src.verbs += /client/proc/TestZASRebuild
//src.verbs += /client/proc/cmd_admin_rejuvenate
feedback_add_details("admin_verb","mDV") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/icons/Testing/Zone.dmi b/icons/Testing/Zone.dmi
new file mode 100644
index 00000000000..1d8ab851669
Binary files /dev/null and b/icons/Testing/Zone.dmi differ