diff --git a/baystation12.dme b/baystation12.dme index 42300df1d7a..a5f86634c93 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -1300,17 +1300,17 @@ #include "code\WorkInProgress\ZomgPonies\mobs\pony.dm" #include "code\WorkInProgress\ZomgPonies\mobs\metroid\powers.dm" #include "code\WorkInProgress\ZomgPonies\objects\doccart.dm" -#include "code\ZAS\Airflow.dm" -#include "code\ZAS\Connection.dm" -#include "code\ZAS\Debug.dm" -#include "code\ZAS\FEA_gas_mixture.dm" -#include "code\ZAS\FEA_system.dm" -#include "code\ZAS\Fire.dm" -#include "code\ZAS\Functions.dm" -#include "code\ZAS\Plasma.dm" -#include "code\ZAS\Variable Settings.dm" -#include "code\ZAS\ZAS_Turfs.dm" -#include "code\ZAS\ZAS_Zones.dm" +#include "code\ZAS - vgstation\Airflow.dm" +#include "code\ZAS - vgstation\Connection.dm" +#include "code\ZAS - vgstation\Debug.dm" +#include "code\ZAS - vgstation\FEA_gas_mixture.dm" +#include "code\ZAS - vgstation\FEA_system.dm" +#include "code\ZAS - vgstation\Fire.dm" +#include "code\ZAS - vgstation\Functions.dm" +#include "code\ZAS - vgstation\NewSettings.dm" +#include "code\ZAS - vgstation\Plasma.dm" +#include "code\ZAS - vgstation\ZAS_Turfs.dm" +#include "code\ZAS - vgstation\ZAS_Zones.dm" #include "interface\interface.dm" #include "interface\skin.dmf" #include "maps\tgstation-redux.dmm" diff --git a/code/ZAS - vgstation/Airflow.dm b/code/ZAS - vgstation/Airflow.dm new file mode 100644 index 00000000000..28bdb8420e0 --- /dev/null +++ b/code/ZAS - vgstation/Airflow.dm @@ -0,0 +1,434 @@ +/* + +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 - zas_settings.Get(/datum/ZAS_Setting/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(zas_settings.Get(/datum/ZAS_Setting/airflow_push)) + if(weakened <= 0) src << "\red The sudden rush of air knocks you over!" + weakened = max(weakened,5) + last_airflow_stun = world.time + return + src << "\blue You stay upright as the air rushes past you." + 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 - zas_settings.Get(/datum/ZAS_Setting/airflow_stun_cooldown)) return 0 + if(buckled) return 0 + if(shoes) + 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 + + if(zas_settings.Get(/datum/ZAS_Setting/airflow_push)) + if(weakened <= 0) src << "\red The sudden rush of air knocks you over!" + weakened = max(weakened,rand(1,5)) + last_airflow_stun = world.time + return + src << "\blue You stay upright as the air rushes past you." + last_airflow_stun = world.time + +atom/movable/proc/check_airflow_movable(n) + if(!zas_settings.Get(/datum/ZAS_Setting/airflow_push)) + return 0 + if(anchored && !ismob(src)) + return 0 + if(!istype(src,/obj/item) && n < zas_settings.Get(/datum/ZAS_Setting/airflow_dense_pressure)) + return 0 + + return 1 + +mob/check_airflow_movable(n) + if(n < zas_settings.Get(/datum/ZAS_Setting/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 < zas_settings.Get(/datum/ZAS_Setting/airflow_lightest_pressure)) return 0 + if(3) + if(n < zas_settings.Get(/datum/ZAS_Setting/airflow_light_pressure)) return 0 + if(4,5) + if(n < zas_settings.Get(/datum/ZAS_Setting/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) < zas_settings.Get(/datum/ZAS_Setting/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 + if(zas_settings.Get(/datum/ZAS_Setting/airflow_push)) // If enabled + for(var/atom/movable/M in air_sucked) + if(M.last_airflow > world.time - zas_settings.Get(/datum/ZAS_Setting/airflow_delay)) continue + + //Check for knocking people over + if(ismob(M) && n > zas_settings.Get(/datum/ZAS_Setting/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 - zas_settings.Get(/datum/ZAS_Setting/airflow_delay)) continue + + if(ismob(M) && abs(n) > zas_settings.Get(/datum/ZAS_Setting/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 < zas_settings.Get(/datum/ZAS_Setting/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. + + if(zas_settings.Get(/datum/ZAS_Setting/airflow_push)) // If enabled + for(var/atom/movable/M in pplz) + if(M.last_airflow > world.time - zas_settings.Get(/datum/ZAS_Setting/airflow_delay)) continue + + if(ismob(M) && n > zas_settings.Get(/datum/ZAS_Setting/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 + var/tmp/airflow_speed = 0 + var/tmp/airflow_time = 0 + var/tmp/last_airflow = 0 + + proc/GotoAirflowDest(n) + if(!zas_settings.Get(/datum/ZAS_Setting/airflow_push)) return // If not enabled, fuck it. + if(!airflow_dest) return + if(airflow_speed < 0) return + if(last_airflow > world.time - zas_settings.Get(/datum/ZAS_Setting/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 -= zas_settings.Get(/datum/ZAS_Setting/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 + zas_settings.Get(/datum/ZAS_Setting/airflow_mob_slowdown) + airflow_dest = null + airflow_speed = 0 + airflow_time = 0 + if(od) + density = 0 + + + proc/RepelAirflowDest(n) + if(!zas_settings.Get(/datum/ZAS_Setting/airflow_push)) return // If not enabled, fuck it. + if(!airflow_dest) return + if(airflow_speed < 0) return + if(last_airflow > world.time - zas_settings.Get(/datum/ZAS_Setting/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 -= zas_settings.Get(/datum/ZAS_Setting/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 + zas_settings.Get(/datum/ZAS_Setting/airflow_mob_slowdown) + airflow_dest = null + airflow_speed = 0 + airflow_time = 0 + if(od) + density = 0 + + 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 * zas_settings.Get(/datum/ZAS_Setting/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(zas_settings.Get(/datum/ZAS_Setting/airflow_push)) + if(airflow_speed > 10) + paralysis += round(airflow_speed * zas_settings.Get(/datum/ZAS_Setting/airflow_stun)) + stunned = max(stunned,paralysis + 3) + else + stunned += round(airflow_speed * zas_settings.Get(/datum/ZAS_Setting/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, /obj/machinery/camera)) + continue + . += A diff --git a/code/ZAS - vgstation/Connection.dm b/code/ZAS - vgstation/Connection.dm new file mode 100644 index 00000000000..8a919d50435 --- /dev/null +++ b/code/ZAS - vgstation/Connection.dm @@ -0,0 +1,403 @@ +/* +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/ref_A + var/ref_B + + var/indirect = CONNECTION_DIRECT //If the connection is purely indirect, the zones should not join. + + var/last_updated //The tick at which this was last updated. + + var/no_zone_count = 0 + + + +/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 + ref_A = "\ref[A]" + + if(!B.zone.connections) B.zone.connections = list() + B.zone.connections += src + zone_B = B.zone + ref_B = "\ref[B]" + + if(ref_A in air_master.turfs_with_connections) + var/list/connections = air_master.turfs_with_connections[ref_A] + connections.Add(src) + else + air_master.turfs_with_connections[ref_A] = list(src) + + if(ref_B in air_master.turfs_with_connections) + var/list/connections = air_master.turfs_with_connections[ref_B] + connections.Add(src) + else + air_master.turfs_with_connections[ref_B] = list(src) + + if(A.CanPass(null, B, 0, 0)) + + ConnectZones(A.zone, B.zone, 1) + + if(A.HasDoor(B) || B.HasDoor(A)) + indirect = CONNECTION_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])" + del(src) + + +/connection/Del() + //remove connections from master lists. + if(ref_B in air_master.turfs_with_connections) + var/list/connections = air_master.turfs_with_connections[ref_B] + connections.Remove(src) + + if(ref_A in air_master.turfs_with_connections) + var/list/connections = air_master.turfs_with_connections[ref_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 + if(A && A.zone && B && B.zone) + DisconnectZones(A.zone, B.zone) + + //Finally, preform actual deletion. + . = ..() + + +/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 + + //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 + + +/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 + + 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) + del src + + //Check sanity: zones are different + if(A.zone == B.zone) + del src + + //Check sanity: same turfs as before. + if(ref_A != "\ref[A]" || ref_B != "\ref[B]") + del src + + //Handle zones changing on a turf. + if((A.zone && A.zone != zone_A) || (B.zone && B.zone != zone_B)) + Sanitize() + + //Manage sudden loss of a turfs zone. (e.g. a wall being built) + if(!A.zone || !B.zone) + no_zone_count++ + if(no_zone_count >= 5) + //world.log << "Connection removed: [A] or [B] missing a zone." + del src + return 0 + + return 1 + + +/connection/proc/CheckPassSanity() + //Sanity check, first. + Cleanup() + + 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, 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 + del src + +/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 + var/temp_ref = ref_A + ref_A = ref_B + ref_B = temp_ref + 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 \ No newline at end of file diff --git a/code/ZAS - vgstation/Debug.dm b/code/ZAS - vgstation/Debug.dm new file mode 100644 index 00000000000..807208a5a3e --- /dev/null +++ b/code/ZAS - vgstation/Debug.dm @@ -0,0 +1,122 @@ + +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('debug_group.dmi', T, null, TURF_LAYER) + + for(var/turf/space/S in unsimulated_tiles) + current_zone_images += image('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('debug_connect.dmi', C.A, null, TURF_LAYER) + current_zone_images += image('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) + + for(var/zone/Z in zones) + if(Z.air == air && Z != src) + var/turf/zloc = pick(Z.contents) + client << "\red Illegal air datum shared by: [zloc.loc.name]" + diff --git a/code/ZAS - vgstation/FEA_gas_mixture.dm b/code/ZAS - vgstation/FEA_gas_mixture.dm new file mode 100644 index 00000000000..a4104ab01c7 --- /dev/null +++ b/code/ZAS - vgstation/FEA_gas_mixture.dm @@ -0,0 +1,1038 @@ +/* +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) \ + (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 + +datum + gas //These are used for the "Trace Gases" stuff, but is buggy. + sleeping_agent + specific_heat = 40 + + oxygen_agent_b + specific_heat = 300 + + volatile_fuel + specific_heat = 30 + + var + moles = 0 + specific_heat = 0 + + moles_archived = 0 + + gas_mixture + var //Holds the "moles" of each of the four gases. + oxygen = 0 + carbon_dioxide = 0 + nitrogen = 0 + toxins = 0 + total_moles = 0 //Updated when a reaction occurs. + + volume = CELL_VOLUME + + temperature = 0 //in Kelvin, use calculate_temperature() to modify + + group_multiplier = 1 + //Size of the group this gas_mixture is representing. + //=1 for singletons + + graphic + + list/datum/gas/trace_gases = list() //Seemed to be a good idea that was abandoned + + tmp //These are variables for use with the archived data + oxygen_archived + carbon_dioxide_archived + nitrogen_archived + toxins_archived + + temperature_archived + + graphic_archived = 0 + fuel_burnt = 0 + +//FOR THE LOVE OF GOD PLEASE USE THIS PROC +//Call it with negative numbers to remove gases. + + 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 + proc/return_temperature() + return temperature + + + proc/return_volume() + return max(0, volume) + + + proc/thermal_energy() + return temperature*heat_capacity() + +/////////////////////////////// +//PV=nRT - related procedures// +/////////////////////////////// + + + 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) + + 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) + + 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*/ + + 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() + + 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// +//////////////////////////////////////////// + + + 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 + + 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 + + var/reacting = 0 //set to 1 if a notable reaction occured (used by pipe_network) + + if(temperature > FIRE_MINIMUM_TEMPERATURE_TO_EXIST) + if(zburn(null) > 0) + reacting = 1 + + return reacting + + 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.// +////////////////////////////////////////////// + + + 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 + + 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) + + 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 + + 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 + + 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 + + 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) + + 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 + + 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 + + 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 + + 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 + + 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 + + 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 + + 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 + + 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 + + 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 + + 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) + + 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) + + 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 + + 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) || (oxygen > (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 + + if(sample.trace_gases.len) + for(var/datum/gas/trace_gas in sample.trace_gases) + if(trace_gas.moles_archived > MINIMUM_AIR_TO_SUSPEND) + var/datum/gas/corresponding = locate(trace_gas.type) in trace_gases + if(corresponding) + if((abs(trace_gas.moles - corresponding.moles) > MINIMUM_AIR_TO_SUSPEND) && \ + ((corresponding.moles < (1-MINIMUM_AIR_RATIO_TO_SUSPEND)*trace_gas.moles) || (corresponding.moles > (1+MINIMUM_AIR_RATIO_TO_SUSPEND)*trace_gas.moles))) + return 0 + else + return 0 + + if(trace_gases.len) + for(var/datum/gas/trace_gas in trace_gases) + if(trace_gas.moles > MINIMUM_AIR_TO_SUSPEND) + var/datum/gas/corresponding = locate(trace_gas.type) in sample.trace_gases + if(corresponding) + if((abs(trace_gas.moles - corresponding.moles) > MINIMUM_AIR_TO_SUSPEND) && \ + ((trace_gas.moles < (1-MINIMUM_AIR_RATIO_TO_SUSPEND)*corresponding.moles) || (trace_gas.moles > (1+MINIMUM_AIR_RATIO_TO_SUSPEND)*corresponding.moles))) + return 0 + else + return 0 + return 1 + + + 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 -= right_side.oxygen + carbon_dioxide -= right_side.carbon_dioxide + nitrogen -= right_side.nitrogen + toxins -= right_side.toxins + + if((trace_gases.len > 0)||(right_side.trace_gases.len > 0)) + 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 \ No newline at end of file diff --git a/code/ZAS - vgstation/FEA_system.dm b/code/ZAS - vgstation/FEA_system.dm new file mode 100644 index 00000000000..c2a33ad99db --- /dev/null +++ b/code/ZAS - vgstation/FEA_system.dm @@ -0,0 +1,221 @@ +/* +Overview: + The air_master global variable is the workhorse for the system. + +Why are you archiving data before modifying it? + The general concept with archiving data and having each tile keep track of when they were last updated is to keep everything symmetric + and totally independent of the order they are read in an update cycle. + This prevents abnormalities like air/fire spreading rapidly in one direction and super slowly in the other. + +Why not just archive everything and then calculate? + Efficiency. While a for-loop that goes through all tils and groups to archive their information before doing any calculations seems simple, it is + slightly less efficient than the archive-before-modify/read method. + +Why is there a cycle check for calculating data as well? + This ensures that every connection between group-tile, tile-tile, and group-group is only evaluated once per loop. + + + + +Important variables: + air_master.groups_to_rebuild (list) + A list of air groups that have had their geometry occluded and thus may need to be split in half. + A set of adjacent groups put in here will join together if validly connected. + This is done before air system calculations for a cycle. + air_master.tiles_to_update (list) + Turfs that are in this list have their border data updated before the next air calculations for a cycle. + Place turfs in this list rather than call the proc directly to prevent race conditions + + turf/simulated.archive() and datum/air_group.archive() + This stores all data for. + If you modify, make sure to update the archived_cycle to prevent race conditions and maintain symmetry + + atom/CanPass(atom/movable/mover, turf/target, height, air_group) + returns 1 for allow pass and 0 for deny pass + Turfs automatically call this for all objects/mobs in its turf. + This is called both as source.CanPass(target, height, air_group) + and target.CanPass(source, height, air_group) + + Cases for the parameters + 1. This is called with args (mover, location, height>0, air_group=0) for normal objects. + 2. This is called with args (null, location, height=0, air_group=0) for flowing air. + 3. This is called with args (null, location, height=?, air_group=1) for determining group boundaries. + + Cases 2 and 3 would be different for doors or other objects open and close fairly often. + (Case 3 would return 0 always while Case 2 would return 0 only when the door is open) + This prevents the necessity of re-evaluating group geometry every time a door opens/closes. + + +Important Procedures + air_master.process() + This first processes the air_master update/rebuild lists then processes all groups and tiles for air calculations + + +*/ + +var/kill_air = 0 +var/tick_multiplier = 2 + +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 + + +var/datum/controller/air_system/air_master + +/datum/controller/air_system/ + //Geoemetry lists + var/list/turfs_with_connections = list() + var/list/active_hotspots = list() + + //Special functions lists + var/list/tiles_to_reconsider_zones = list() + + //Geometry updates lists + var/list/tiles_to_update = list() + var/list/connections_to_check = list() + + var/current_cycle = 0 + var/update_delay = 5 //How long between check should it try to process atmos again. + var/failed_ticks = 0 //How many ticks have runtimed? + + var/tick_progress = 0 + + +/* process() + //Call this to process air movements for a cycle + + process_rebuild_select_groups() + //Used by process() + //Warning: Do not call this + + rebuild_group(datum/air_group) + //Used by process_rebuild_select_groups() + //Warning: Do not call this, add the group to air_master.groups_to_rebuild instead + */ + + +/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. + + set background = 1 + 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++ + if(!S.zone && !S.blocks_air) + if(S.CanPass(null, S, 0, 0)) + new/zone(S) + + for(var/turf/simulated/S in world) + 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. + + + set background = 1 + + while(1) + if(!kill_air) + current_cycle++ + var/success = tick() //Changed so that a runtime does not crash the ticker. + if(!success) //Runtimed. + failed_ticks++ + if(failed_ticks > 20) + world << "ERROR IN ATMOS TICKER. Killing air simulation!" + kill_air = 1 + sleep(max(5,update_delay*tick_multiplier)) + */ + +/datum/controller/air_system/proc/tick() + . = 1 //Set the default return value, for runtime detection. + + tick_progress = "update_air_properties" + if(tiles_to_update.len) //If there are tiles to update, do so. + for(var/turf/simulated/T in tiles_to_update) + if(. && T && !T.update_air_properties()) + . = 0 //If a runtime occured, make sure we can sense it. + //message_admins("ZASALERT: Unable run turf/simualted/update_air_properties()") + if(.) + tiles_to_update = list() + + //Check sanity on connection objects. + if(.) + tick_progress = "connections_to_check" + if(connections_to_check.len) + for(var/connection/C in connections_to_check) + C.CheckPassSanity() + connections_to_check = list() + + //Ensure tiles still have zones. + if(.) + tick_progress = "tiles_to_reconsider_zones" + if(tiles_to_reconsider_zones.len) + for(var/turf/simulated/T in tiles_to_reconsider_zones) + if(!T.zone) + new /zone(T) + tiles_to_reconsider_zones = list() + + //Process zones. + if(.) + tick_progress = "zone/process()" + for(var/zone/Z in zones) + if(Z.last_update < current_cycle) + var/output = Z.process() + if(Z) + Z.last_update = current_cycle + if(. && Z && !output) + . = 0 + //Process fires. + if(.) + tick_progress = "active_hotspots (fire)" + for(var/obj/fire/F in active_hotspots) + if(. && F && !F.process()) + . = 0 + + if(.) + tick_progress = "success" \ No newline at end of file diff --git a/code/ZAS - vgstation/Fire.dm b/code/ZAS - vgstation/Fire.dm new file mode 100644 index 00000000000..4907b04c75e --- /dev/null +++ b/code/ZAS - vgstation/Fire.dm @@ -0,0 +1,365 @@ +/* + +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/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) + + //active_hotspot.just_spawned = (current_cycle < air_master.current_cycle) + //remove just_spawned protection if no longer processing this cell + + return igniting + +obj + fire + //Icon for fire on turfs. + + anchored = 1 + mouse_opacity = 0 + + //luminosity = 3 + + icon = 'fire.dmi' + icon_state = "1" + + layer = TURF_LAYER + + var + firelevel = 10000 //Calculated by gas_mixture.calculate_firelevel() + + 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/zas_settings.Get(/datum/ZAS_Setting/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(zas_settings.Get(/datum/ZAS_Setting/fire_consumption_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 ///////////////////////////////////////////////////////////////// + + + New(newLoc,fl) + ..() + + if(!istype(loc, /turf)) + del src + + dir = pick(cardinal) + SetLuminosity(3) + firelevel = fl + air_master.active_hotspots.Add(src) + + + Del() + if (istype(loc, /turf/simulated)) + SetLuminosity(0) + + loc = null + air_master.active_hotspots.Remove(src) + + ..() + + 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 / zas_settings.Get(/datum/ZAS_Setting/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 + zas_settings.Get(/datum/ZAS_Setting/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 + var/value = 0 + + if(oxygen && (toxins || fuel || liquid)) + if(liquid) + value = 1 + else if (toxins && !value) + value = 1 + else if(fuel && !value) + value = 1 + + return value + +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 + var/value = 0 + + if(oxygen && (toxins || fuel || liquid)) + if(liquid) + value = 1 + else if (toxins >= 0.7 && !value) + value = 1 + else if(fuel && !value) + if(fuel.moles >= 1.4) + value = 1 + + return value + +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 = zas_settings.Get(/datum/ZAS_Setting/fire_firelevel_multiplier) * mix_multiplier * dampening_multiplier + + return max( 0, firelevel) + + +/mob/living/carbon/human/proc/FireBurn(var/firelevel, var/last_temperature, var/pressure) +// mostly using the old proc from Sky until I can think of something better + //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 + chest_exposure = 1 + groin_exposure = 1 + legs_exposure = 1 + arms_exposure = 1 + + //determine the multiplier + //minimize this for low-pressure enviroments + var/mx = 5 * firelevel/zas_settings.Get(/datum/ZAS_Setting/fire_firelevel_multiplier) * min(pressure / ONE_ATMOSPHERE, 1) + + //Get heat transfer coefficients for clothing. + //skytodo: kill anyone who breaks things then orders me to fix them + 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 + + //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") + + //flash_pain() diff --git a/code/ZAS - vgstation/Functions.dm b/code/ZAS - vgstation/Functions.dm new file mode 100644 index 00000000000..dc8f2c2a591 --- /dev/null +++ b/code/ZAS - vgstation/Functions.dm @@ -0,0 +1,171 @@ +//Global Functions +//Contents: FloodFill, ZMerge, ZConnect + +//Floods outward from an initial turf to fill everywhere it's zone would reach. +proc/FloodFill(turf/simulated/start) + + if(!istype(start)) + return list() + + //The list of tiles waiting to be evaulated. + var/list/open = list(start) + //The list of tiles which have been evaulated. + var/list/closed = list() + + //Loop through the turfs in the open list in order to find which adjacent turfs should be added to the zone. + while(open.len) + var/turf/simulated/T = pick(open) + + //sanity! + if(!istype(T)) + open -= T + continue + + //Check all cardinal directions + for(var/d in cardinal) + var/turf/simulated/O = get_step(T,d) + + //Ensure the turf is of proper type, that it is not in either list, and that air can reach it. + if(istype(O) && !(O in open) && !(O in closed) && O.ZCanPass(T)) + + //Handle connections from a tile with a door. + if(T.HasDoor()) + //If they both have doors, then they are not able to connect period. + if(O.HasDoor()) + continue + + //Connect first to north and west + if(d == NORTH || d == WEST) + open += O + + //If that fails, and north/west cannot be connected to, see if west or south can be connected instead. + else + var/turf/simulated/W = get_step(O, WEST) + var/turf/simulated/N = get_step(O, NORTH) + + if( !O.ZCanPass(N) && !O.ZCanPass(W) ) + //If it cannot connect either to the north or west, connect it! + open += O + + //If no doors are involved, add it immediately. + else if(!O.HasDoor()) + open += O + + //Handle connecting to a tile with a door. + else + if(d == SOUTH || d == EAST) + //doors prefer connecting to zones to the north or west + closed += O + + else + //see if we need to force an attempted connection + //(there are no potentially viable zones to the north/west of the door) + var/turf/simulated/W = get_step(O, WEST) + var/turf/simulated/N = get_step(O, NORTH) + + if( !O.ZCanPass(N) && !O.ZCanPass(W) ) + //If it cannot connect either to the north or west, connect it! + closed += O + + //This tile is now evaluated, and can be moved to the list of evaluated tiles. + open -= T + closed += T + + return closed + + +//Procedure to merge two zones together. +proc/ZMerge(zone/A,zone/B) + + //Sanity~ + if(!istype(A) || !istype(B) || !istype(B.air) || !istype(A.air)) + return + + var/new_contents = A.contents + B.contents + + //Set all the zone vars. + for(var/turf/simulated/T in B.contents) + T.zone = A + + if(istype(A.air) && istype(B.air)) + //Merges two zones so that they are one. + var/a_size = A.air.group_multiplier + var/b_size = B.air.group_multiplier + var/c_size = a_size + b_size + + //Set air multipliers to one so air represents gas per tile. + A.air.group_multiplier = 1 + B.air.group_multiplier = 1 + + //Remove some air proportional to the size of this zone. + A.air.remove_ratio(a_size/c_size) + B.air.remove_ratio(b_size/c_size) + + //Merge the gases and set the multiplier to the sum of the old ones. + A.air.merge(B.air) + A.air.group_multiplier = c_size + + //I hate when the air datum somehow disappears. + // Try to make it sorta work anyways. Fakit + else if(istype(B.air)) + A.air = B.air + A.air.group_multiplier = A.contents.len + + else if(istype(A.air)) + A.air.group_multiplier = A.contents.len + + //Doublefakit. + else + A.air = new + + //Check for connections to merge into the new zone. + for(var/connection/C in B.connections) + //The Cleanup proc will delete the connection if the zones are the same. + // It will also set the zone variables correctly. + C.Cleanup() + + //Add space tiles. + if(A.unsimulated_tiles && B.unsimulated_tiles) + A.unsimulated_tiles |= B.unsimulated_tiles + else if (B.unsimulated_tiles) + A.unsimulated_tiles = B.unsimulated_tiles + + //Add contents. + A.contents = new_contents + + //Remove the "B" zone, finally. + B.SoftDelete() + + +//Connects two zones by forming a connection object representing turfs A and B. +proc/ZConnect(turf/simulated/A,turf/simulated/B) + + //Make sure that if it's space, it gets added to unsimulated_tiles instead. + if(!istype(B)) + if(A.zone) + A.zone.AddTurf(B) + return + if(!istype(A)) + if(B.zone) + B.zone.AddTurf(A) + return + + if(!istype(A) || !istype(B)) + return + + //Make some preliminary checks to see if the connection is valid. + if(!A.zone || !B.zone) return + if(A.zone == B.zone) return + + if(A.CanPass(null,B,0,1)) + return ZMerge(A.zone,B.zone) + + //Ensure the connection isn't already made. + if("\ref[A]" in air_master.turfs_with_connections) + for(var/connection/C in air_master.turfs_with_connections["\ref[A]"]) + C.Cleanup() + if(C && (C.B == B || C.A == B)) + return + + //Make the connection. + new /connection(A,B) diff --git a/code/ZAS - vgstation/NewSettings.dm b/code/ZAS - vgstation/NewSettings.dm new file mode 100644 index 00000000000..a65fd6c8bec --- /dev/null +++ b/code/ZAS - vgstation/NewSettings.dm @@ -0,0 +1,500 @@ +/* +ZAS Settings System 2.0 + +Okay, so VariableSettings is a mess of spaghetticode and + is about as flexible as a grandmother covered in + starch. + +This is an attempt to fix that by using getters and + setters instead of stupidity. It's a little more difficult + to code with, but dammit, it's better than hackery. + +NOTE: plc was merged into the main settings. We can set up + visual groups later. + +HOW2GET: + zas_setting.Get(/datum/ZAS_Setting/herp) + +HOW2SET: + zas_setting.Set(/datum/ZAS_Setting/herp, "dsfargeg") +*/ + +var/global/ZAS_Settings/zas_settings = new + +#define ZAS_TYPE_UNDEFINED -1 +#define ZAS_TYPE_BOOLEAN 0 +#define ZAS_TYPE_NUMERIC 1 + +/** +* ZAS Setting Datum +* +* Stores a single setting. +* @author N3X15 +* @package SS13 +* @subpackage ZAS +*/ +/datum/ZAS_Setting/ + var/name="Clown" // Friendly name. + var/desc="Honk" + var/value=null + var/valtype=ZAS_TYPE_UNDEFINED + +/datum/ZAS_Setting/fire_consumption_rate + name = "Fire - Air Consumption Ratio" + desc = "Ratio of air removed and combusted per tick." + valtype=ZAS_TYPE_NUMERIC + value = 0.75 + +/datum/ZAS_Setting/fire_firelevel_multiplier + value = 25 + name = "Fire - Firelevel Constant" + desc = "Multiplied by the equation for firelevel, affects mainly the extingiushing of fires." + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/fire_fuel_energy_release + value = 550000 + name = "Fire - Fuel energy release" + desc = "The energy in joule released when burning one mol of a burnable substance" + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/airflow_lightest_pressure + value = 20 + name = "Airflow - Small Movement Threshold %" + desc = "Percent of 1 Atm. at which items with the small weight classes will move." + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/airflow_light_pressure + value = 35 + name = "Airflow - Medium Movement Threshold %" + desc = "Percent of 1 Atm. at which items with the medium weight classes will move." + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/airflow_medium_pressure + value = 50 + name = "Airflow - Heavy Movement Threshold %" + desc = "Percent of 1 Atm. at which items with the largest weight classes will move." + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/airflow_heavy_pressure + value = 65 + name = "Airflow - Mob Movement Threshold %" + desc = "Percent of 1 Atm. at which mobs will move." + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/airflow_dense_pressure + value = 85 + name = "Airflow - Dense Movement Threshold %" + desc = "Percent of 1 Atm. at which items with canisters and closets will move." + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/airflow_stun_pressure + value = 60 + name = "Airflow - Mob Stunning Threshold %" + desc = "Percent of 1 Atm. at which mobs will be stunned by airflow." + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/airflow_stun_cooldown + value = 60 + name = "Aiflow Stunning - Cooldown" + desc = "How long, in tenths of a second, to wait before stunning them again." + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/airflow_stun + value = 1 + name = "Airflow Impact - Stunning" + desc = "How much a mob is stunned when hit by an object." + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/airflow_damage + value = 2 + name = "Airflow Impact - Damage" + desc = "Damage from airflow impacts." + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/airflow_speed_decay + value = 1.5 + name = "Airflow Speed Decay" + desc = "How rapidly the speed gained from airflow decays." + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/airflow_delay + value = 30 + name = "Airflow Retrigger Delay" + desc = "Time in deciseconds before things can be moved by airflow again." + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/airflow_mob_slowdown + value = 1 + name = "Airflow 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." + valtype=ZAS_TYPE_NUMERIC + +// N3X15 - Added back in so we can tweak performance. +/datum/ZAS_Setting/airflow_push + name="Airflow - Push" + value = 0 + desc="1=yes please rape my server, 0=no" + valtype=ZAS_TYPE_BOOLEAN + +/datum/ZAS_Setting/connection_insulation + value = 0.4 + name = "Connections - Insulation" + desc = "How insulative a connection is, in terms of heat transfer. 1 is perfectly insulative, and 0 is perfectly conductive." + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/connection_temperature_delta + value = 10 + name = "Connections - Temperature Difference" + desc = "The smallest temperature difference which will cause heat to travel through doors." + valtype=ZAS_TYPE_NUMERIC + + +/////////////////////////////////////// +// PLASMA SHIT +/////////////////////////////////////// +// ALL CAPS BECAUSE PLASMA IS HARDCORE YO +// And I'm too lazy to fix the refs. + +/datum/ZAS_Setting/PLASMA_DMG + name = "Plasma Damage Amount" + desc = "Self Descriptive" + value = 3 + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/CLOTH_CONTAMINATION + name = "Cloth Contamination" + desc = "If this is on, plasma does damage by getting into cloth." + value = 1 + valtype=ZAS_TYPE_BOOLEAN + +/datum/ZAS_Setting/PLASMAGUARD_ONLY + name = "PlasmaGuard Only" + desc = "If this is on, only biosuits and spacesuits protect against contamination and ill effects." + value = 0 + valtype=ZAS_TYPE_BOOLEAN + +/datum/ZAS_Setting/GENETIC_CORRUPTION + name = "Genetic Corruption Chance" + desc = "Chance of genetic corruption as well as toxic damage, X in 10,000." + value = 0 + valtype=ZAS_TYPE_BOOLEAN + +/datum/ZAS_Setting/SKIN_BURNS + name = "Skin Burns" + desc = "Plasma has an effect similar to mustard gas on the un-suited." + value = 0 + valtype=ZAS_TYPE_BOOLEAN + +/datum/ZAS_Setting/EYE_BURNS + name = "Eye Burns" + desc = "Plasma burns the eyes of anyone not wearing eye protection." + value = 1 + valtype=ZAS_TYPE_BOOLEAN + +/datum/ZAS_Setting/CONTAMINATION_LOSS + name = "Contamination Loss" + desc = "How much toxin damage is dealt from contaminated clothing" + value = 0.02 //Per tick? ASK ARYN + valtype=ZAS_TYPE_NUMERIC + +/datum/ZAS_Setting/PLASMA_HALLUCINATION + name = "Plasma Hallucination" + desc = "Does being in plasma cause you to hallucinate?" + value = 0 + valtype=ZAS_TYPE_BOOLEAN + +/datum/ZAS_Setting/N2O_HALLUCINATION + name = "N2O Hallucination" + desc = "Does being in sleeping gas cause you to hallucinate?" + value = 1 + valtype=ZAS_TYPE_BOOLEAN + +/** +* ZAS Settings +* +* Stores our settings for ZAS in an editable form. +* @author N3X15 +* @package SS13 +* @subpackage ZAS +*/ +/ZAS_Settings + // INTERNAL USE ONLY + var/list/datum/ZAS_Setting/settings = list() + +/ZAS_Settings/New() + .=..() + for(var/S in typesof(/datum/ZAS_Setting) - /datum/ZAS_Setting) + var/id=idfrompath("[S]") + //testing("Creating zas_settings\[[id]\] = new [S]") + src.settings[id]=new S + + + if(fexists("config/ZAS.txt") == 0) + Save() + Load() + +/ZAS_Settings/proc/Save() + var/F = file("config/ZAS.txt") + fdel(F) + for(var/id in src.settings) + var/datum/ZAS_Setting/setting = src.settings[id] + F << "# [setting.name]" + F << "# [setting.desc]" + F << "[id] [setting.value]" + F << "" + +/ZAS_Settings/proc/Load() + for(var/t in file2list("config/ZAS.txt")) + if(!t) continue + + t = trim(t) + if (length(t) == 0) + continue + else if (copytext(t, 1, 2) == "#") + continue + + var/pos = findtext(t, " ") + var/name = null + var/value = null + + if (pos) + name = copytext(t, 1, pos) + value = copytext(t, pos + 1) + else + name = t + + if (!name) + continue + + src.SetFromConfig(name,value) + +// INTERNAL USE ONLY +/ZAS_Settings/proc/idfrompath(var/str) + return replacetext(str,"/datum/ZAS_Setting/","") + +// INTERNAL USE ONLY +/ZAS_Settings/proc/ChangeSetting(var/user,var/id) + var/datum/ZAS_Setting/setting = src.settings[id] + var/displayedValue="" + switch(setting.valtype) + if(ZAS_TYPE_NUMERIC) + setting.value = input(user,"Enter a number:","Settings",setting.value) as num + displayedValue="\"[setting.value]\"" + /* + if(ZAS_TYPE_BITFLAG) + var/flag = input(user,"Toggle which bit?","Settings") in bitflags + flag = text2num(flag) + if(newvar & flag) + newvar &= ~flag + else + newvar |= flag + */ + if(ZAS_TYPE_BOOLEAN) + setting.value = !setting.value + displayedValue = (setting.value) ? "ON" : "OFF" + /* + if(ZAS_TYPE_STRING) + setting.value = input(user,"Enter text:","Settings",newvar) as message + */ + else + error("[id] has an invalid typeval.") + return + world << "\blue [key_name(user)] changed ZAS setting [setting.name] to [displayedValue]." + + ChangeSettingsDialog(user) + +/** +* Set the value of a setting. +* +* Recommended to use the actual type of the setting rather than the ID, since +* this will allow for the compiler to check the validity of id. Kinda. +* +* @param id Either the typepath of the desired setting, or the string ID of the setting. +* @param value The value that the setting should be set to. +*/ +/ZAS_Settings/proc/Set(var/id, var/value) + var/datum/ZAS_Setting/setting = src.settings[idfrompath(id)] + setting.value=value + +// INTERNAL USE ONLY +/ZAS_Settings/proc/SetFromConfig(var/id, var/value) + var/datum/ZAS_Setting/setting = src.settings[id] + switch(setting.valtype) + if(ZAS_TYPE_NUMERIC) + setting.value = text2num(value) + /* + if(ZAS_TYPE_BITFLAG) + var/flag = input(user,"Toggle which bit?","Settings") in bitflags + flag = text2num(flag) + if(newvar & flag) + newvar &= ~flag + else + newvar |= flag + */ + if(ZAS_TYPE_BOOLEAN) + setting.value = (value == "1") + /* + if(ZAS_TYPE_STRING) + setting.value = input(user,"Enter text:","Settings",newvar) as message + */ + +/** +* Get a setting. +* +* Recommended to use the actual type of the setting rather than the ID, since +* this will allow for the compiler to check the validity of id. Kinda. +* +* @param id Either the typepath of the desired setting, or the string ID of the setting. +* @returns Value of the desired setting +*/ +/ZAS_Settings/proc/Get(var/id) + if(ispath(id)) + id="[id]" + var/datum/ZAS_Setting/setting = src.settings[idfrompath(id)] + if(!setting || !istype(setting)) + world.log << "ZAS_SETTING DEBUG: [id] | [idfrompath(id)]" + return setting.value + +/ZAS_Settings/proc/ChangeSettingsDialog(mob/user) + var/dat = {" + + + ZAS Settings 2.0 + + + +

ZAS Configuration

+

Save Settings | Load Settings

+

Please note that changing these settings can and probably will result in death, destruction and mayhem. Change at your own risk.

+
"} + for(var/id in src.settings) + var/datum/ZAS_Setting/s = src.settings[id] + dat += "
[s.name] = [s.value] \[Change\]
" + dat += "
[s.desc]
" + dat += "
" + user << browse(dat,"window=settings") + +/ZAS_Settings/Topic(href,href_list) + if("changevar" in href_list) + ChangeSetting(usr,href_list["changevar"]) + if("save" in href_list) + var/sure = input(usr,"Are you sure? This will overwrite your ZAS configuration!","Overwrite ZAS.txt?", "No") in list("Yes","No") + if(sure=="Yes") + Save() + message_admins("[key_name(usr)] saved ZAS settings to disk.") + if("load" in href_list) + var/sure = input(usr,"Are you sure?","Reload ZAS.txt?", "No") in list("Yes","No") + if(sure=="Yes") + Load() + message_admins("[key_name(usr)] reloaded ZAS settings from disk.") + +/ZAS_Settings/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") + Set("CLOTH_CONTAMINATION", 1) //If this is on, plasma does damage by getting into cloth. + Set("PLASMAGUARD_ONLY", 0) + Set("GENETIC_CORRUPTION", 0) //Chance of genetic corruption as well as toxic damage, X in 1000. + Set("SKIN_BURNS", 0) //Plasma has an effect similar to mustard gas on the un-suited. + Set("EYE_BURNS", 1) //Plasma burns the eyes of anyone not wearing eye protection. + Set("PLASMA_HALLUCINATION", 0) + Set("CONTAMINATION_LOSS", 0.02) + + if("Plasma - Low Hazard") + Set("CLOTH_CONTAMINATION", 0) //If this is on, plasma does damage by getting into cloth. + Set("PLASMAGUARD_ONLY", 0) + Set("GENETIC_CORRUPTION", 0) //Chance of genetic corruption as well as toxic damage, X in 1000 + Set("SKIN_BURNS", 0) //Plasma has an effect similar to mustard gas on the un-suited. + Set("EYE_BURNS", 1) //Plasma burns the eyes of anyone not wearing eye protection. + Set("PLASMA_HALLUCINATION", 0) + Set("CONTAMINATION_LOSS", 0.01) + + if("Plasma - High Hazard") + Set("CLOTH_CONTAMINATION", 1) //If this is on, plasma does damage by getting into cloth. + Set("PLASMAGUARD_ONLY", 0) + Set("GENETIC_CORRUPTION", 0) //Chance of genetic corruption as well as toxic damage, X in 1000. + Set("SKIN_BURNS", 1) //Plasma has an effect similar to mustard gas on the un-suited. + Set("EYE_BURNS", 1) //Plasma burns the eyes of anyone not wearing eye protection. + Set("PLASMA_HALLUCINATION", 1) + Set("CONTAMINATION_LOSS", 0.05) + + if("Plasma - Oh Shit!") + Set("CLOTH_CONTAMINATION", 1) //If this is on, plasma does damage by getting into cloth. + Set("PLASMAGUARD_ONLY", 1) + Set("GENETIC_CORRUPTION", 5) //Chance of genetic corruption as well as toxic damage, X in 1000. + Set("SKIN_BURNS", 1) //Plasma has an effect similar to mustard gas on the un-suited. + Set("EYE_BURNS", 1) //Plasma burns the eyes of anyone not wearing eye protection. + Set("PLASMA_HALLUCINATION", 1) + Set("CONTAMINATION_LOSS", 0.075) + + if("ZAS - Normal") + Set("airflow_push", 0) + Set("airflow_lightest_pressure", 20) + Set("airflow_light_pressure", 35) + Set("airflow_medium_pressure", 50) + Set("airflow_heavy_pressure", 65) + Set("airflow_dense_pressure", 85) + Set("airflow_stun_pressure", 60) + Set("airflow_stun_cooldown", 60) + Set("airflow_stun", 1) + Set("airflow_damage", 2) + Set("airflow_speed_decay", 1.5) + Set("airflow_delay", 30) + Set("airflow_mob_slowdown", 1) + + if("ZAS - Forgiving") + Set("airflow_push", 0) + Set("airflow_lightest_pressure", 45) + Set("airflow_light_pressure", 60) + Set("airflow_medium_pressure", 120) + Set("airflow_heavy_pressure", 110) + Set("airflow_dense_pressure", 200) + Set("airflow_stun_pressure", 150) + Set("airflow_stun_cooldown", 90) + Set("airflow_stun", 0.15) + Set("airflow_damage", 0.15) + Set("airflow_speed_decay", 1.5) + Set("airflow_delay", 50) + Set("airflow_mob_slowdown", 0) + + if("ZAS - Dangerous") + Set("airflow_push", 1) + Set("airflow_lightest_pressure", 15) + Set("airflow_light_pressure", 30) + Set("airflow_medium_pressure", 45) + Set("airflow_heavy_pressure", 55) + Set("airflow_dense_pressure", 70) + Set("airflow_stun_pressure", 50) + Set("airflow_stun_cooldown", 50) + Set("airflow_stun", 2) + Set("airflow_damage", 3) + Set("airflow_speed_decay", 1.2) + Set("airflow_delay", 25) + Set("airflow_mob_slowdown", 2) + + if("ZAS - Hellish") + Set("airflow_push", 1) + Set("airflow_lightest_pressure", 20) + Set("airflow_light_pressure", 30) + Set("airflow_medium_pressure", 40) + Set("airflow_heavy_pressure", 50) + Set("airflow_dense_pressure", 60) + Set("airflow_stun_pressure", 40) + Set("airflow_stun_cooldown", 40) + Set("airflow_stun", 3) + Set("airflow_damage", 4) + Set("airflow_speed_decay", 1) + Set("airflow_delay", 20) + Set("airflow_mob_slowdown", 3) + world << "\blue [key_name(usr)] loaded ZAS preset [def]" \ No newline at end of file diff --git a/code/ZAS - vgstation/Plasma.dm b/code/ZAS - vgstation/Plasma.dm new file mode 100644 index 00000000000..a7bd605c86b --- /dev/null +++ b/code/ZAS - vgstation/Plasma.dm @@ -0,0 +1,125 @@ +var/image/contamination_overlay = image('icons/effects/contamination.dmi') + +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 + + contaminate() + //Do a contamination overlay? Temporary measure to keep contamination less deadly than it was. + if(!contaminated) + contaminated = 1 + overlays += contamination_overlay + + 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(zas_settings.Get(/datum/ZAS_Setting/CLOTH_CONTAMINATION)) contaminate() + + //Anything else requires them to not be dead. + if(stat >= 2) + return + + //Burn skin if exposed. + if(zas_settings.Get(/datum/ZAS_Setting/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(zas_settings.Get(/datum/ZAS_Setting/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(zas_settings.Get(/datum/ZAS_Setting/GENETIC_CORRUPTION)) + if(rand(1,10000) < zas_settings.Get(/datum/ZAS_Setting/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!" + eye_stat += 2.5 + eye_blurry = min(eye_blurry+1.5,50) + if (prob(max(0,eye_stat - 20) + 1) &&!eye_blind) + src << "\red You are blinded!" + eye_blind += 20 + eye_stat = 0 + +/mob/living/carbon/human/proc/pl_head_protected() + //Checks if the head is adequately sealed. + if(head) + if(zas_settings.Get(/datum/ZAS_Setting/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(zas_settings.Get(/datum/ZAS_Setting/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) && zas_settings.Get(/datum/ZAS_Setting/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 - vgstation/Variable Settings.dm b/code/ZAS - vgstation/Variable Settings.dm new file mode 100644 index 00000000000..09501ffc78a --- /dev/null +++ b/code/ZAS - vgstation/Variable Settings.dm @@ -0,0 +1,340 @@ +var/global/vs_control/vsc = new + +// Whoever made this fucking thing: I hate you so much. +vs_control/var + // N3X15 - Added back in so we can tweak performance. + airflow_push = 0 + airflow_push_NAME="Airflow - Push shit around" + airflow_push_DESC="1=yes please rape my server, 0=no" + airflow_push_METHOD="Toggle" // See ChangeSettings(). I'd rather not let people break this. + + fire_consuption_rate = 0.75 + fire_consuption_rate_NAME = "Fire - Air Consumption Ratio" + fire_consuption_rate_DESC = "Ratio of air removed and combusted per tick." + + fire_firelevel_multiplier = 25 + fire_firelevel_multiplier_NAME = "Fire - Firelevel Constant" + fire_firelevel_multiplier_DESC = "Multiplied by the equation for firelevel, affects mainly the extingiushing of fires." + + fire_fuel_energy_release = 550000 + fire_fuel_energy_release_NAME = "Fire - Fuel energy release" + fire_fuel_energy_release_DESC = "The energy in joule released when burning one mol of a burnable substance" + + + airflow_lightest_pressure = 20 + airflow_lightest_pressure_NAME = "Airflow - Small Movement Threshold %" + airflow_lightest_pressure_DESC = "Percent of 1 Atm. at which items with the small weight classes will move." + + airflow_light_pressure = 35 + airflow_light_pressure_NAME = "Airflow - Medium Movement Threshold %" + airflow_light_pressure_DESC = "Percent of 1 Atm. at which items with the medium weight classes will move." + + airflow_medium_pressure = 50 + airflow_medium_pressure_NAME = "Airflow - Heavy Movement Threshold %" + airflow_medium_pressure_DESC = "Percent of 1 Atm. at which items with the largest weight classes will move." + + airflow_heavy_pressure = 65 + airflow_heavy_pressure_NAME = "Airflow - Mob Movement Threshold %" + airflow_heavy_pressure_DESC = "Percent of 1 Atm. at which mobs will move." + + airflow_dense_pressure = 85 + airflow_dense_pressure_NAME = "Airflow - Dense Movement Threshold %" + airflow_dense_pressure_DESC = "Percent of 1 Atm. at which items with canisters and closets will move." + + airflow_stun_pressure = 60 + airflow_stun_pressure_NAME = "Airflow - Mob Stunning Threshold %" + airflow_stun_pressure_DESC = "Percent of 1 Atm. at which mobs will be stunned by airflow." + + airflow_stun_cooldown = 60 + airflow_stun_cooldown_NAME = "Aiflow Stunning - Cooldown" + airflow_stun_cooldown_DESC = "How long, in tenths of a second, to wait before stunning them again." + + airflow_stun = 1 + airflow_stun_NAME = "Airflow Impact - Stunning" + airflow_stun_DESC = "How much a mob is stunned when hit by an object." + + airflow_damage = 2 + airflow_damage_NAME = "Airflow Impact - Damage" + airflow_damage_DESC = "Damage from airflow impacts." + + airflow_speed_decay = 1.5 + airflow_speed_decay_NAME = "Airflow Speed Decay" + airflow_speed_decay_DESC = "How rapidly the speed gained from airflow decays." + + airflow_delay = 30 + airflow_delay_NAME = "Airflow Retrigger Delay" + airflow_delay_DESC = "Time in deciseconds before things can be moved by airflow again." + + airflow_mob_slowdown = 1 + airflow_mob_slowdown_NAME = "Airflow Slowdown" + 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 = 0.4 + var/connection_insulation_NAME = "Connections - Insulation" + var/connection_insulation_DESC = "How insulative a connection is, in terms of heat transfer. 1 is perfectly insulative, and 0 is perfectly conductive." + + 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() + list/bitflags = list("1","2","4","8","16","32","64","128","256","512","1024") // Oh jesus why. Learn to shift bits, you idiots. + pl_control/plc = new() + + 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" + + 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") + Topic(href,href_list) + if("changevar" in href_list) + ChangeSetting(usr,href_list["changevar"]) + 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) + 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 + + proc/ChangePlasma() + for(var/V in plc.settings) + plc.Randomize(V) + + 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_push=0 + 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_push=0 + 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_push=1 + 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_push=1 + 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 + + + world << "\blue [key_name(user)] changed the global plasma/ZAS settings to \"[def]\"" + +pl_control + var/list/settings = list() + 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" + 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 - vgstation/ZAS_Turfs.dm b/code/ZAS - vgstation/ZAS_Turfs.dm new file mode 100644 index 00000000000..148a5703a2f --- /dev/null +++ b/code/ZAS - vgstation/ZAS_Turfs.dm @@ -0,0 +1,356 @@ +atom/var/pressure_resistance = ONE_ATMOSPHERE +turf + + var/zone/zone + + assume_air(datum/gas_mixture/giver) //use this for machines to adjust air + del(giver) + return 0 + + 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 + + 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 + + var/current_graphic = null + + var/tmp + datum/gas_mixture/air + + processing = 1 + + air_check_directions = 0 //Do not modify this, just add turf to air_master.tiles_to_update + + obj/fire/active_hotspot + + + proc/update_visuals() + overlays = null + + var/siding_icon_state = return_siding_icon_state() + if(siding_icon_state) + overlays += image('floors.dmi',siding_icon_state) + var/datum/gas_mixture/model = return_air() + switch(model.graphic) + if(1) + overlays.Add(plmaster) //TODO: Make invisible plasma an option + if(2) + overlays.Add(slmaster) + + + + New() + ..() + + if(!blocks_air) + air = new + + air.oxygen = oxygen + air.carbon_dioxide = carbon_dioxide + air.nitrogen = nitrogen + air.toxins = toxins + + air.temperature = temperature + air.update_values() + + if(air_master) + air_master.tiles_to_update.Add(src) + + else + if(air_master) + for(var/direction in cardinal) + var/turf/simulated/floor/target = get_step(src,direction) + if(istype(target)) + air_master.tiles_to_update |= target + + Del() + if(active_hotspot) + del(active_hotspot) + if(blocks_air) + for(var/direction in list(NORTH, SOUTH, EAST, WEST)) + var/turf/simulated/tile = get_step(src,direction) + if(istype(tile) && !tile.blocks_air) + air_master.tiles_to_update.Add(tile) + ..() + + assume_air(datum/gas_mixture/giver) + if(!giver) return 0 + if(zone) + zone.air.merge(giver) + return 1 + else + return ..() + + return_air() + if(zone) + return zone.air + else if(air) + return air + + else + return ..() + + remove_air(amount as num) + if(zone) + var/datum/gas_mixture/removed = null + removed = zone.air.remove(amount) + return removed + else if(air) + var/datum/gas_mixture/removed = null + removed = air.remove(amount) + + if(air.check_tile_graphic()) + update_visuals(air) + return removed + + else + return ..() + + proc/update_air_properties() + . = 1 + var/air_directions_archived = air_check_directions + air_check_directions = 0 + + for(var/direction in cardinal) + if(ZAirPass(get_step(src,direction))) + air_check_directions |= direction + + if(!zone && !blocks_air) //No zone, but not a wall. + for(var/direction in DoorDirections) //Check door directions first. + if(air_check_directions&direction) + var/turf/simulated/T = get_step(src,direction) + if(!istype(T)) + continue + if(T.zone) + T.zone.AddTurf(src) + break + if(!zone) //Still no zone + for(var/direction in CounterDoorDirections) //Check the others second. + if(air_check_directions&direction) + var/turf/simulated/T = get_step(src,direction) + if(!istype(T)) + continue + if(T.zone) + T.zone.AddTurf(src) + break + if(!zone) //No zone found, new zone! + new/zone(src) + if(!zone) //Still no zone, the floodfill determined it is not part of a larger zone. Force a zone on it. + new/zone(list(src)) + + //Check pass sanity of the connections. + if("\ref[src]" in air_master.turfs_with_connections) + for(var/connection/C in air_master.turfs_with_connections["\ref[src]"]) + air_master.connections_to_check |= C + + if(zone && !zone.rebuild) + for(var/direction in cardinal) + var/turf/T = get_step(src,direction) + if(!istype(T)) + continue + + //I can connect to air in this direction + if(air_check_directions&direction) + + //If either block air, we must look to see if the adjacent turfs need rebuilt. + if(!CanPass(null, T, 0, 0)) + + //Target blocks air + if(!T.CanPass(null, T, 0, 0)) + var/turf/NT = get_step(T, direction) + + //If that turf is in my zone still, rebuild. + if(istype(NT,/turf/simulated) && NT in zone.contents) + zone.rebuild = 1 + + //If that is an unsimulated tile in my zone, see if we need to rebuild or just remove. + else if(istype(NT) && NT in zone.unsimulated_tiles) + var/consider_rebuild = 0 + for(var/d in cardinal) + var/turf/UT = get_step(NT,d) + if(istype(UT, /turf/simulated) && UT.zone == zone && UT.CanPass(null, NT, 0, 0)) //If we find a neighboring tile that is in the same zone, check if we need to rebuild + consider_rebuild = 1 + break + if(consider_rebuild) + zone.rebuild = 1 //Gotta check if we need to rebuild, dammit + else + zone.RemoveTurf(NT) //Not adjacent to anything, and unsimulated. Goodbye~ + + //To make a closed connection through closed door. + ZConnect(T, src) + + //If I block air. + else if(T.zone && !T.zone.rebuild) + var/turf/NT = get_step(src, reverse_direction(direction)) + + //If I am splitting a zone, rebuild. + if(istype(NT,/turf/simulated) && (NT in T.zone.contents || (NT.zone && T in NT.zone.contents))) + T.zone.rebuild = 1 + + //If NT is unsimulated, parse if I should remove it or rebuild. + else if(istype(NT) && NT in T.zone.unsimulated_tiles) + var/consider_rebuild = 0 + for(var/d in cardinal) + var/turf/UT = get_step(NT,d) + if(istype(UT, /turf/simulated) && UT.zone == T.zone && UT.CanPass(null, NT, 0, 0)) //If we find a neighboring tile that is in the same zone, check if we need to rebuild + consider_rebuild = 1 + break + + //Needs rebuilt. + if(consider_rebuild) + T.zone.rebuild = 1 + + //Not adjacent to anything, and unsimulated. Goodbye~ + else + T.zone.RemoveTurf(NT) + + else + //Produce connection through open door. + ZConnect(src,T) + + //Something like a wall was built, changing the geometry. + else if(air_directions_archived&direction) + var/turf/NT = get_step(T, direction) + + //If the tile is in our own zone, and we cannot connect to it, better rebuild. + if(istype(NT,/turf/simulated) && NT in zone.contents) + zone.rebuild = 1 + + //Parse if we need to remove the tile, or rebuild the zone. + else if(istype(NT) && NT in zone.unsimulated_tiles) + var/consider_rebuild = 0 + + //Loop through all neighboring turfs to see if we should remove the turf or just rebuild. + for(var/d in cardinal) + var/turf/UT = get_step(NT,d) + + //If we find a neighboring tile that is in the same zone, rebuild + if(istype(UT, /turf/simulated) && UT.zone == zone && UT.CanPass(null, NT, 0, 0)) + consider_rebuild = 1 + break + + //The unsimulated turf is adjacent to another one of our zone's turfs, + // better rebuild to be sure we didn't get cut in twain + if(consider_rebuild && NT.zone) + NT.zone.rebuild = 1 + + //Not adjacent to anything, and unsimulated. Goodbye~ + else + zone.RemoveTurf(NT) + + if(air_check_directions) + processing = 1 + else + processing = 0 + + + + + +/turf/proc/HasDoor(turf/O) + //Checks for the presence of doors, used for zone spreading and connection. + //A positive numerical argument checks only for closed doors. + //Another turf as an argument checks for windoors between here and there. + for(var/obj/machinery/door/D in src) + if(isnum(O) && O) + if(!D.density) continue + if(istype(D,/obj/machinery/door/window)) + if(!istype(O)) + continue + if(D.dir == get_dir(D,O)) + return 1 + else + return 1 + +turf/proc/ZCanPass(turf/simulated/T, var/include_space = 0) + //Fairly standard pass checks for turfs, objects and directional windows. Also stops at the edge of space. + if(!istype(T)) + return 0 + + if(!istype(T) && !include_space) + return 0 + else + if(T.blocks_air||blocks_air) + return 0 + + for(var/obj/obstacle in src) + if(istype(obstacle, /obj/machinery/door) && !obstacle:air_properties_vary_with_direction) + continue + if(!obstacle.CanPass(null, T, 1.5, 1)) + return 0 + + for(var/obj/obstacle in T) + if(istype(obstacle, /obj/machinery/door) && !obstacle:air_properties_vary_with_direction) + continue + if(!obstacle.CanPass(null, src, 1.5, 1)) + return 0 + + return 1 + +turf/proc/ZAirPass(turf/T) + //Fairly standard pass checks for turfs, objects and directional windows. + if(!istype(T)) + return 0 + + if(T.blocks_air||blocks_air) + return 0 + + for(var/obj/obstacle in src) + if(istype(obstacle, /obj/machinery/door) && !obstacle:air_properties_vary_with_direction) + continue + if(!obstacle.CanPass(null, T, 0, 0)) + return 0 + + for(var/obj/obstacle in T) + if(istype(obstacle, /obj/machinery/door) && !obstacle:air_properties_vary_with_direction) + continue + if(!obstacle.CanPass(null, src, 0, 0)) + return 0 + + return 1 + + +/*UNUSED +/turf/proc/check_connections() + //Checks for new connections that can be made. + for(var/d in cardinal) + var/turf/simulated/T = get_step(src,d) + if(istype(T) && ( !T.zone || !T.CanPass(0,src,0,0) ) ) + continue + if(T.zone != zone) + ZConnect(src,T) + +/turf/proc/check_for_space() + //Checks for space around the turf. + for(var/d in cardinal) + var/turf/T = get_step(src,d) + if(istype(T,/turf/space) && T.CanPass(0,src,0,0)) + zone.AddSpace(T) + */ \ No newline at end of file diff --git a/code/ZAS - vgstation/ZAS_Zones.dm b/code/ZAS - vgstation/ZAS_Zones.dm new file mode 100644 index 00000000000..a90a699afca --- /dev/null +++ b/code/ZAS - vgstation/ZAS_Zones.dm @@ -0,0 +1,547 @@ +var/list/zones = list() +var/list/DoorDirections = list(NORTH,WEST) //Which directions doors turfs can connect to zones +var/list/CounterDoorDirections = list(SOUTH,EAST) //Which directions doors turfs can connect to zones + +/zone + var/dbg_output = 0 //Enables debug output. + var/rebuild = 0 //If 1, zone will be rebuilt on next process. Not sure if used. + var/datum/gas_mixture/air //The air contents of the zone. + var/list/contents //All the tiles that are contained in this zone. + var/list/connections // /connection objects which refer to connections with other zones, e.g. through a door. + var/list/connected_zones //Parallels connections, but lists zones to which this one is connected and the number + //of points they're connected at. + var/list/closed_connection_zones //Same as connected_zones, but for zones where the door or whatever is closed. + var/list/unsimulated_tiles // Any space tiles in this list will cause air to flow out. + var/last_update = 0 + var/progress = "nothing" + + +//CREATION AND DELETION +/zone/New(turf/start) + . = ..() + //Get the turfs that are part of the zone using a floodfill method + if(istype(start,/list)) + contents = start + else + contents = FloodFill(start) + + //Change all the zone vars of the turfs, check for space to be added to unsimulated_tiles. + for(var/turf/T in contents) + if(T.zone && T.zone != src) + T.zone.RemoveTurf(T) + T.zone = src + if(!istype(T,/turf/simulated)) + AddTurf(T) + + //Generate the gas_mixture for use in txhis zone by using the average of the gases + //defined at startup. + air = new + air.group_multiplier = contents.len + for(var/turf/simulated/T in contents) + air.oxygen += T.oxygen / air.group_multiplier + air.nitrogen += T.nitrogen / air.group_multiplier + air.carbon_dioxide += T.carbon_dioxide / air.group_multiplier + air.toxins += T.toxins / air.group_multiplier + air.temperature += T.temperature / air.group_multiplier + air.update_values() + + //Add this zone to the global list. + zones.Add(src) + + + //LEGACY, DO NOT USE. Use the SoftDelete proc. +/zone/Del() + //Ensuring the zone list doesn't get clogged with null values. + for(var/turf/simulated/T in contents) + RemoveTurf(T) + air_master.tiles_to_reconsider_zones += T + for(var/zone/Z in connected_zones) + if(src in Z.connected_zones) + Z.connected_zones.Remove(src) + for(var/connection/C in connections) + air_master.connections_to_check += C + zones.Remove(src) + air = null + . = ..() + + + //Handles deletion via garbage collection. +/zone/proc/SoftDelete() + zones.Remove(src) + air = null + + //Ensuring the zone list doesn't get clogged with null values. + for(var/turf/simulated/T in contents) + RemoveTurf(T) + air_master.tiles_to_reconsider_zones += T + + //Removing zone connections and scheduling connection cleanup + for(var/zone/Z in connected_zones) + if(src in Z.connected_zones) + Z.connected_zones.Remove(src) + connected_zones = null + + for(var/connection/C in connections) + air_master.connections_to_check += C + connections = null + + return 1 + + +//ZONE MANAGEMENT FUNCTIONS +/zone/proc/AddTurf(turf/T) + //Adds the turf to contents, increases the size of the zone, and sets the zone var. + if(istype(T, /turf/simulated)) + if(T in contents) + return + if(T.zone) + T.zone.RemoveTurf(T) + contents += T + if(air) + air.group_multiplier++ + T.zone = src + else + if(!unsimulated_tiles) + unsimulated_tiles = list() + else if(T in unsimulated_tiles) + return + unsimulated_tiles += T + contents -= T + +/zone/proc/RemoveTurf(turf/T) + //Same, but in reverse. + if(istype(T, /turf/simulated)) + if(!(T in contents)) + return + contents -= T + if(air) + air.group_multiplier-- + if(T.zone == src) + T.zone = null + else if(unsimulated_tiles) + unsimulated_tiles -= T + if(!unsimulated_tiles.len) + unsimulated_tiles = null + + ////////////// + //PROCESSING// +////////////// + +#define QUANTIZE(variable) (round(variable,0.0001)) + +/zone/proc/process() + . = 1 + + progress = "problem with: SoftDelete()" + + //Deletes zone if empty. + if(!contents.len) + return SoftDelete() + + progress = "problem with: Rebuild()" + + //Does rebuilding stuff. + if(rebuild) + rebuild = 0 + Rebuild() //Shoving this into a proc. + + if(!contents.len) //If we got soft deleted. + return + + progress = "problem with: air regeneration" + + //Sometimes explosions will cause the air to be deleted for some reason. + if(!air) + air = new() + air.oxygen = MOLES_O2STANDARD + air.nitrogen = MOLES_N2STANDARD + air.temperature = T0C + air.total_moles() + world.log << "Air object lost in zone. Regenerating." + + + progress = "problem with: ShareSpace()" + + if(unsimulated_tiles) + if(locate(/turf/simulated) in unsimulated_tiles) + for(var/turf/simulated/T in unsimulated_tiles) + unsimulated_tiles -= T + + if(unsimulated_tiles.len) + var/moved_air = ShareSpace(air,unsimulated_tiles) + + if(moved_air > zas_settings.Get(/datum/ZAS_Setting/airflow_lightest_pressure)) + AirflowSpace(src) + else + unsimulated_tiles = null + + //Check the graphic. + progress = "problem with: modifying turf graphics" + + air.graphic = 0 + if(air.toxins > MOLES_PLASMA_VISIBLE) + air.graphic = 1 + else if(air.trace_gases.len) + var/datum/gas/sleeping_agent = locate(/datum/gas/sleeping_agent) in air.trace_gases + if(sleeping_agent && (sleeping_agent.moles > 1)) + air.graphic = 2 + + progress = "problem with an inbuilt byond function: some conditional checks" + + //Only run through the individual turfs if there's reason to. + if(air.graphic != air.graphic_archived || air.temperature > PLASMA_FLASHPOINT) + + progress = "problem with: turf/simulated/update_visuals()" + + for(var/turf/simulated/S in contents) + //Update overlays. + if(air.graphic != air.graphic_archived) + if(S.HasDoor(1)) + S.update_visuals() + else + S.update_visuals(air) + + progress = "problem with: item or turf temperature_expose()" + + //Expose stuff to extreme heat. + if(air.temperature > PLASMA_FLASHPOINT) + for(var/atom/movable/item in S) + item.temperature_expose(air, air.temperature, CELL_VOLUME) + S.hotspot_expose(air.temperature, CELL_VOLUME) + + progress = "problem with: calculating air graphic" + + //Archive graphic so we can know if it's different. + air.graphic_archived = air.graphic + + progress = "problem with: calculating air temp" + + //Ensure temperature does not reach absolute zero. + air.temperature = max(TCMB,air.temperature) + + progress = "problem with an inbuilt byond function: length(connections)" + + //Handle connections to other zones. + if(length(connections)) + + progress = "problem with: ZMerge(), a couple of misc procs" + + for(var/connection/C in connections) + //Check if the connection is valid first. + if(!C.Cleanup()) + continue + + //Do merging if conditions are met. Specifically, if there's a non-door connection + //to somewhere with space, the zones are merged regardless of equilibrium, to speed + //up spacing in areas with double-plated windows. + if(C && C.A.zone && C.B.zone) + //indirect = 2 is a direct connection. + if( C.indirect == 2 ) + if(C.A.zone.air.compare(C.B.zone.air) || unsimulated_tiles) + ZMerge(C.A.zone,C.B.zone) + + progress = "problem with: ShareRatio(), Airflow(), a couple of misc procs" + + //Share some + for(var/zone/Z in connected_zones) + //If that zone has already processed, skip it. + if(Z.last_update > last_update) + continue + + if(air && Z.air) + //Ensure we're not doing pointless calculations on equilibrium zones. + var/moles_delta = abs(air.total_moles() - Z.air.total_moles()) + if(moles_delta > 0.1 || abs(air.temperature - Z.air.temperature) > 0.1) + if(abs(Z.air.return_pressure() - air.return_pressure()) > zas_settings.Get(/datum/ZAS_Setting/airflow_lightest_pressure)) + Airflow(src,Z) + var/unsimulated_boost = 0 + if(unsimulated_tiles) + unsimulated_boost += unsimulated_tiles.len + if(Z.unsimulated_tiles) + unsimulated_boost += Z.unsimulated_tiles.len + unsimulated_boost = max(0, min(3, unsimulated_boost)) + ShareRatio( air , Z.air , connected_zones[Z] + unsimulated_boost) + + for(var/zone/Z in closed_connection_zones) + //If that zone has already processed, skip it. + if(Z.last_update > last_update) + continue + if(air && Z.air) + if( abs(air.temperature - Z.air.temperature) > zas_settings.Get(/datum/ZAS_Setting/connection_temperature_delta) ) + ShareHeat(air, Z.air, closed_connection_zones[Z]) + + progress = "all components completed successfully, the problem is not here" + + //////////////// + //Air Movement// +//////////////// + +var/list/sharing_lookup_table = list(0.03, 0.07, 0.11, 0.15, 0.18, 0.20) + +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 + + 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 || !unsimulated_tiles.len) + 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) + + // 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) + 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 + 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 = (A.temperature * full_heat_capacity + unsim_temperature * unsim_heat_capacity) / (full_heat_capacity + unsim_heat_capacity) + + if(sharing_lookup_table.len >= unsimulated_tiles.len) //6 or more interconnecting tiles will max at 42% of air moved per tick. + ratio = sharing_lookup_table[unsimulated_tiles.len] + + //We need to adjust it to account for the insulation settings. + ratio *= 1 - zas_settings.Get(/datum/ZAS_Setting/connection_insulation) + + 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) + //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 + + full_heat_capacity = A.heat_capacity() + + s_full_heat_capacity = B.heat_capacity() + + 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.temperature = max(0, (A.temperature - temp_avg) * (1- (ratio / max(1,A.group_multiplier)) ) + temp_avg ) + B.temperature = max(0, (B.temperature - temp_avg) * (1- (ratio / max(1,B.group_multiplier)) ) + temp_avg ) + + + /////////////////// + //Zone Rebuilding// +/////////////////// + +zone/proc/Rebuild() + //Choose a random turf and regenerate the zone from it. + var + turf/simulated/sample = locate() in contents + list/new_contents + problem = 0 + + // + var/list/turfs_to_consider = contents.Copy() + + while(!sample || !sample.CanPass(null, sample, 1.5, 1)) + if(sample) + turfs_to_consider.Remove(sample) + sample = locate() in turfs_to_consider + if(!sample) + break + + if(!istype(sample) || !sample.CanPass(null, sample, 1.5, 1)) //Not a single valid turf. + for(var/turf/simulated/T in contents) + air_master.tiles_to_update |= T + return SoftDelete() + + new_contents = FloodFill(sample) + + var/list/new_unsimulated = ( unsimulated_tiles ? unsimulated_tiles : list() ) + + for(var/turf/S in new_contents) + if(!istype(S, /turf/simulated)) + new_unsimulated |= S + new_contents.Remove(S) + + if(contents.len != new_contents.len) + problem = 1 + + //If something isn't carried over, there was a complication. + for(var/turf/T in contents) + if(!(T in new_contents)) + T.zone = null + problem = 1 + + if(problem) + //Build some new zones for stuff that wasn't included. + var/list/turf/simulated/rebuild_turfs = contents - new_contents + var/list/turf/simulated/reconsider_turfs = list() + contents = new_contents + for(var/turf/simulated/T in rebuild_turfs) + if(!T.zone && T.CanPass(null, T, 1.5, 1)) + var/zone/Z = new /zone(T) + Z.air.copy_from(air) + else + reconsider_turfs |= T + for(var/turf/simulated/T in reconsider_turfs) + if(!T.zone && T.CanPass(null, T, 1.5, 1)) + var/zone/Z = new /zone(T) + Z.air.copy_from(air) + else if(!T in air_master.tiles_to_update) + air_master.tiles_to_update.Add(T) + + for(var/turf/simulated/T in contents) + if(T.zone && T.zone != src) + T.zone.RemoveTurf(T) + T.zone = src + else if(!T.zone) + T.zone = src + air.group_multiplier = contents.len + unsimulated_tiles = null + + if(new_unsimulated.len) + for(var/turf/S in new_unsimulated) + if(istype(S, /turf/simulated)) + continue + for(var/direction in cardinal) + var/turf/simulated/T = get_step(S,direction) + if(istype(T) && T.zone && S.CanPass(null, T, 0, 0)) + T.zone.AddTurf(S) + +//UNUSED +/* +zone/proc/connected_zones() + //A legacy proc for getting connected zones. + . = list() + for(var/connection/C in connections) + var/zone/Z + if(C.A.zone == src) + Z = C.B.zone + else + Z = C.A.zone + + if(Z in .) + .[Z]++ + else + . += Z + .[Z] = 1*/ diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 6aec44f7f17..8948fde47cb 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -93,7 +93,8 @@ /obj/machinery/door/proc/bumpopen(mob/user as mob) if(operating) return - if(user.last_airflow > world.time - vsc.airflow_delay) //Fakkit + //if(user.last_airflow > world.time - vsc.airflow_delay) //Fakkit + if(user.last_airflow > world.time - zas_settings.Get("airflow_delay")) //Fakkit return src.add_fingerprint(user) if(!src.requiresID()) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index db71270618f..38660b2d319 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -2436,12 +2436,13 @@ else if(href_list["vsc"]) if(check_rights(R_ADMIN|R_SERVER)) if(href_list["vsc"] == "airflow") - vsc.ChangeSettingsDialog(usr,vsc.settings) +/* vsc.ChangeSettingsDialog(usr,vsc.settings) if(href_list["vsc"] == "plasma") - vsc.ChangeSettingsDialog(usr,vsc.plc.settings) + vsc.ChangeSettingsDialog(usr,vsc.plc.settings) */ + zas_settings.ChangeSettingsDialog(usr,zas_settings.settings) if(href_list["vsc"] == "default") - vsc.SetDefault(usr) - + //vsc.SetDefault(usr) + zas_settings.SetDefault(usr) // player info stuff if(href_list["add_player_info"]) diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index f5314954069..a3ca2aade32 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -836,7 +836,8 @@ var/total_plasmaloss = 0 for(var/obj/item/I in src) if(I.contaminated) - total_plasmaloss += vsc.plc.CONTAMINATION_LOSS + //total_plasmaloss += vsc.plc.CONTAMINATION_LOSS + total_plasmaloss += zas_settings.Get("CONTAMINATION_LOSS") if(status_flags & GODMODE) return 0 //godmode adjustToxLoss(total_plasmaloss)