From 1358b11e468080355fd48b64ac71d548a17a4ea2 Mon Sep 17 00:00:00 2001 From: ZomgPonies Date: Sat, 22 Feb 2014 23:10:37 -0500 Subject: [PATCH] ZAS overhaul --- baystation12.dme | 14 +- code/ZAS - oldbs12/Airflow.dm | 420 +++++++ code/ZAS - oldbs12/Connection.dm | 448 +++++++ code/ZAS - oldbs12/Debug.dm | 219 ++++ .../{ZAS => ZAS - oldbs12}/FEA_gas_mixture.dm | 0 code/{ZAS => ZAS - oldbs12}/FEA_system.dm | 0 code/ZAS - oldbs12/Fire.dm | 362 ++++++ code/{ZAS => ZAS - oldbs12}/Functions.dm | 0 code/ZAS - oldbs12/Plasma.dm | 163 +++ code/ZAS - oldbs12/Variable Settings.dm | 336 +++++ code/{ZAS => ZAS - oldbs12}/ZAS_Turfs.dm | 0 code/{ZAS => ZAS - oldbs12}/ZAS_Zones.dm | 0 code/ZAS/Airflow.dm | 172 +-- code/ZAS/Atom.dm | 66 + code/ZAS/Connection.dm | 565 +++------ code/ZAS/ConnectionGroup.dm | 418 +++++++ code/ZAS/ConnectionManager.dm | 102 ++ code/ZAS/Controller.dm | 321 +++++ code/ZAS/Debug.dm | 233 +--- code/ZAS/Diagnostic.dm | 233 ++++ code/ZAS/Fire.dm | 26 +- code/ZAS/Turf.dm | 241 ++++ code/ZAS/Zone.dm | 153 +++ code/ZAS/_docs.dm | 35 + code/ZAS/_gas_mixture.dm | 1090 +++++++++++++++++ code/game/machinery/doors/door.dm | 35 +- code/game/machinery/doors/windowdoor.dm | 6 +- code/game/machinery/shieldgen.dm | 12 +- code/game/objects/effects/effect_system.dm | 15 +- code/game/objects/structures/false_walls.dm | 4 +- code/game/objects/structures/mineral_doors.dm | 12 +- .../objects/structures/windoor_assembly.dm | 6 +- code/game/objects/structures/window.dm | 15 +- code/game/turfs/turf.dm | 48 +- code/modules/admin/verbs/mapping.dm | 2 + icons/Testing/Zone.dmi | Bin 0 -> 955 bytes 36 files changed, 4834 insertions(+), 938 deletions(-) create mode 100644 code/ZAS - oldbs12/Airflow.dm create mode 100644 code/ZAS - oldbs12/Connection.dm create mode 100644 code/ZAS - oldbs12/Debug.dm rename code/{ZAS => ZAS - oldbs12}/FEA_gas_mixture.dm (100%) rename code/{ZAS => ZAS - oldbs12}/FEA_system.dm (100%) create mode 100644 code/ZAS - oldbs12/Fire.dm rename code/{ZAS => ZAS - oldbs12}/Functions.dm (100%) create mode 100644 code/ZAS - oldbs12/Plasma.dm create mode 100644 code/ZAS - oldbs12/Variable Settings.dm rename code/{ZAS => ZAS - oldbs12}/ZAS_Turfs.dm (100%) rename code/{ZAS => ZAS - oldbs12}/ZAS_Zones.dm (100%) create mode 100644 code/ZAS/Atom.dm create mode 100644 code/ZAS/ConnectionGroup.dm create mode 100644 code/ZAS/ConnectionManager.dm create mode 100644 code/ZAS/Controller.dm create mode 100644 code/ZAS/Diagnostic.dm create mode 100644 code/ZAS/Turf.dm create mode 100644 code/ZAS/Zone.dm create mode 100644 code/ZAS/_docs.dm create mode 100644 code/ZAS/_gas_mixture.dm create mode 100644 icons/Testing/Zone.dmi 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 0000000000000000000000000000000000000000..1d8ab8516693cc287bfae96341b7294a28458047 GIT binary patch literal 955 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSi>g8*N?cNllZ!G7N;32F7#J$% z^qxJ-b;v+~+1>va!kk{Xzw;rk!UG9QWaUzAc#f zMBGQw4S z+x~~oi#{e4BV5k(kcZ(M!vivjOYeEC_6Hg=|2?-Lnc?2*<5%1&rt3vsKKk-s#jm0t zZ|ZXF!+zfVw&mb%VUBc$f|v7|Hpxyqs`LDGAzL_u#UHK-ds)+pS;+_r@BLac>SF86 z!Wnja5pCE9HDyQI_Db8j;@AH^E|rxN4E}YKrM+-A$Cu~;Q*DQn>8z>t(^sgyWSo=# zQ&sI*4bS|JAV&tlr%DB%)`to@9Qh<@@pF5qu)~oeAme+esKb#zK*s#ldJP>vfogt- zN;({Q3=}yWBJFVG3&)&44_EOiB-OC^{j<2rE?{WGq+Y)#lttj8eB)&Mvsp|XJ3x%P ztALEY11IaxyaF<8FZ%s6SGmp)1y?j