diff --git a/code/__DEFINES/atmospherics/Connections.dm b/code/__DEFINES/atmospherics/Connections.dm index 65ef7db1ee05..d3e1ff818e2c 100644 --- a/code/__DEFINES/atmospherics/Connections.dm +++ b/code/__DEFINES/atmospherics/Connections.dm @@ -1,6 +1,6 @@ #define WOOSH \ if(connecting_turfs.len && (REALTIMEOFDAY > last_woosh + 2 SECONDS)){ \ - playsound(pick(connecting_turfs),abs(differential) > zas_settings.airflow_heavy_pressure ? 'modular_pariah/master_files/sound/effects/space_wind_big.ogg' : 'modular_pariah/master_files/sound/effects/space_wind.ogg',100,TRUE,null,pressure_affected = FALSE); \ + playsound(pick(connecting_turfs),abs(differential) > zas_settings.airflow_mob_pressure ? 'modular_pariah/master_files/sound/effects/space_wind_big.ogg' : 'modular_pariah/master_files/sound/effects/space_wind.ogg',100,TRUE,null,pressure_affected = FALSE); \ last_woosh = REALTIMEOFDAY;\ } \ diff --git a/code/__DEFINES/obj_flags.dm b/code/__DEFINES/obj_flags.dm index 2743fb91c961..efa6799c8960 100644 --- a/code/__DEFINES/obj_flags.dm +++ b/code/__DEFINES/obj_flags.dm @@ -13,7 +13,6 @@ #define BLOCK_Z_IN_UP (1<<9) // Should this object block z uprise from below? #define BLOCK_Z_FALL (1<<10) // Should this object block falling? #define NO_BUILD (1<<11) // Can we build on this object? -#define PLASMAGUARD (1<<12) //Immune to plasma contamination // If you add new ones, be sure to add them to /obj/Initialize as well for complete mapping support diff --git a/code/controllers/subsystem/airflow.dm b/code/controllers/subsystem/airflow.dm index aa28c57fc72e..c710a30cf403 100644 --- a/code/controllers/subsystem/airflow.dm +++ b/code/controllers/subsystem/airflow.dm @@ -48,10 +48,7 @@ SUBSYSTEM_DEF(airflow) target.airflow_process_delay -= 1 continue - else if (target.airflow_process_delay) - target.airflow_process_delay = 0 - - target.airflow_speed = min(target.airflow_speed, 15) + target.airflow_speed = clamp(0, target.airflow_speed, 15) target.airflow_speed -= zas_settings.airflow_speed_decay if (target.airflow_skip_speedcheck) @@ -119,43 +116,3 @@ SUBSYSTEM_DEF(airflow) /datum/controller/subsystem/airflow/proc/HandleDel(datum/source) SIGNAL_HANDLER processing -= source - -/atom/movable/proc/prepare_airflow(strength) - if (!airflow_dest || airflow_speed < 0 || last_airflow > world.time - zas_settings.airflow_delay) - return FALSE - if (airflow_speed) - airflow_speed = strength / max(get_dist(src, airflow_dest), 1) - return FALSE - if(!check_airflow_movable(strength*10)) //Repel/Gotoairflowdest() divide the differential by a max of 10, so we're undoing that here - return FALSE - if (airflow_dest == loc) - step_away(src, loc) - if (ismob(src)) - to_chat(src, span_warning("You are pushed away by a rush of air!")) - - last_airflow = world.time - - var/airflow_falloff = 9 - sqrt((x - airflow_dest.x) ** 2 + (y - airflow_dest.y) ** 2) - if (airflow_falloff < 1) - airflow_dest = null - return FALSE - - airflow_speed = min(max(strength * (9 / airflow_falloff), 1), 9) - return TRUE - - -/atom/movable/proc/GotoAirflowDest(strength) - if (!prepare_airflow(strength)) - return - airflow_xo = airflow_dest.x - x - airflow_yo = airflow_dest.y - y - airflow_dest = null - SSairflow.Enqueue(src) - -/atom/movable/proc/RepelAirflowDest(strength) - if (!prepare_airflow(strength)) - return - airflow_xo = -(airflow_dest.x - x) - airflow_yo = -(airflow_dest.y - y) - airflow_dest = null - SSairflow.Enqueue(src) diff --git a/code/controllers/subsystem/airmachines.dm b/code/controllers/subsystem/airmachines.dm index 5b1fd6462c1d..466c10652b60 100644 --- a/code/controllers/subsystem/airmachines.dm +++ b/code/controllers/subsystem/airmachines.dm @@ -2,9 +2,11 @@ SUBSYSTEM_DEF(airmachines) name = "Air (Machines)" priority = FIRE_PRIORITY_AIRMACHINES init_order = INIT_ORDER_AIRMACHINES - flags = SS_KEEP_TIMING + flags = SS_POST_FIRE_TIMING runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + wait = 0.5 SECONDS + var/cached_cost var/list/pipe_init_dirs_cache = list() diff --git a/code/controllers/subsystem/zas.dm b/code/controllers/subsystem/zas.dm index 58050380629e..644c20c54729 100644 --- a/code/controllers/subsystem/zas.dm +++ b/code/controllers/subsystem/zas.dm @@ -63,7 +63,7 @@ SUBSYSTEM_DEF(zas) name = "Air Core" priority = FIRE_PRIORITY_AIR init_order = INIT_ORDER_AIR - flags = SS_KEEP_TIMING + flags = SS_POST_FIRE_TIMING runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME wait = 2 SECONDS @@ -466,9 +466,9 @@ SUBSYSTEM_DEF(zas) if(!B.connections) B.connections = new - if(A.connections.get(a_to_b)) + if(A.connections.get_connection_for_dir(a_to_b)) return - if(B.connections.get(b_to_a)) + if(B.connections.get_connection_for_dir(b_to_a)) return if(!space && (A.zone == B.zone)) return diff --git a/code/game/atom/atoms.dm b/code/game/atom/atoms.dm index 487459b87419..e873f88c5fa9 100644 --- a/code/game/atom/atoms.dm +++ b/code/game/atom/atoms.dm @@ -549,16 +549,12 @@ ///Return the current air environment in this atom /atom/proc/return_air() - if(loc) - return loc.return_air() - else - return null + return loc?.return_air() ///Return the current air environment in this atom. If this atom is a turf, it will not automatically update the zone. /atom/proc/unsafe_return_air() return return_air() - ///Return the air if we can analyze it /atom/proc/return_analyzable_air() return null diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index c07b088985a6..c17211228f60 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -629,14 +629,23 @@ if(!bumped_atom) CRASH("Bump was called with no argument.") SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, bumped_atom) + . = ..() + if(!QDELETED(throwing)) throwing.finalize(hit = TRUE, target = bumped_atom) . = TRUE if(QDELETED(bumped_atom)) return + bumped_atom.BumpedBy(src) + if(moving_by_airflow && !QDELING(src)) + if(airflow_speed >= 1 && airflow_dest) + AirflowBump(bumped_atom) + else + SSairflow.Dequeue(src) + /atom/movable/Exited(atom/movable/gone, direction) . = ..() diff --git a/code/game/machinery/computer/atmos_computers/_air_sensor.dm b/code/game/machinery/computer/atmos_computers/_air_sensor.dm index 67d64aed9156..a3124dc39f0d 100644 --- a/code/game/machinery/computer/atmos_computers/_air_sensor.dm +++ b/code/game/machinery/computer/atmos_computers/_air_sensor.dm @@ -43,7 +43,7 @@ if(!on) return - var/datum/gas_mixture/air_sample = unsafe_return_air() + var/datum/gas_mixture/air_sample = loc.unsafe_return_air() var/datum/signal/signal = new(src, list( "sigtype" = "status", "tag" = id_tag, diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 66156c57dcb7..7293c4c9e6d8 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -211,6 +211,7 @@ GLOBAL_PROTECT(admin_verbs_debug) /client/proc/debug_spell_requirements, /client/proc/analyze_openturf, /client/proc/debug_health, + /client/proc/change_zas_settings, ) GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, GLOBAL_PROC_REF(release))) @@ -1036,3 +1037,12 @@ GLOBAL_PROTECT(admin_verbs_hideable) to_chat(world, span_boldannounce("The gamemode is now: [fake_name ? SSticker.mode_display_name : SSticker.mode.name].")) message_admins("[key_name_admin(usr)] has set the gamemode to [SSticker.mode.type].") + +/client/proc/change_zas_settings() + set name = "ZAS Settings" + set category = "Debug" + + if(!check_rights(R_DEBUG)) + return + + zas_settings.ui_interact(mob) diff --git a/code/modules/atmospherics/ZAS/Airflow.dm b/code/modules/atmospherics/ZAS/Airflow.dm index b97dbf3a3798..20e06d88934f 100644 --- a/code/modules/atmospherics/ZAS/Airflow.dm +++ b/code/modules/atmospherics/ZAS/Airflow.dm @@ -14,8 +14,8 @@ This entire system is an absolute mess. var/tmp/airflow_speed = 0 ///Time (ticks) spent in airflow var/tmp/airflow_time = 0 - ///Time (ticks) since last airflow movement - var/tmp/last_airflow = 0 + /// Cooldown for airflow push. + COOLDOWN_DECLARE(airflow_push_cooldown) var/tmp/airborne_acceleration = 0 var/tmp/airflow_xo @@ -34,24 +34,26 @@ This entire system is an absolute mess. return /mob/living/airflow_stun(delta_p) - if(stat == 2) + if(stat == DEAD) return FALSE + if(last_airflow_stun > world.time - zas_settings.airflow_stun_cooldown) return FALSE - if(!(status_flags & CANSTUN) && !(status_flags & CANKNOCKDOWN)) - return FALSE - if(buckled) + + if(!(status_flags & CANSTUN) || !(status_flags & CANKNOCKDOWN)) return FALSE + if(HAS_TRAIT(src, TRAIT_NEGATES_GRAVITY)) //Magboots return FALSE - if(IsKnockdown()) //Uhhh maybe? + + if(body_position == LYING_DOWN) return FALSE Knockdown(zas_settings.airflow_stun * clamp((delta_p / zas_settings.airflow_stun_pressure), 1, 3)) + visible_message( - span_danger("[src] is thrown to the floor by a gust of air!"), - span_danger("A sudden rush of air knocks you over!"), - span_hear("You hear a gust of air, followed by a soft thud.") + span_danger("[src] is thrown to the floor!"), + blind_message = span_hear("You hear a gust of air, followed by a soft thud.") ) last_airflow_stun = world.time @@ -62,42 +64,46 @@ This entire system is an absolute mess. /mob/living/simple_animal/slime/airflow_stun(delta_p) return -///Checks to see if airflow can move this movable. -/atom/movable/proc/check_airflow_movable(n) +/// Checks to see if airflow can move this movable. +/atom/movable/proc/can_airflow_move(delta_p) //We're just hoping nothing goes wrong return TRUE -/mob/check_airflow_movable(n) +/mob/can_airflow_move(delta_p) if(status_flags & GODMODE) return FALSE - if(n < zas_settings.airflow_heavy_pressure) + if(buckled) + return FALSE + if(delta_p < zas_settings.airflow_mob_pressure) return FALSE if(HAS_TRAIT(src, TRAIT_NEGATES_GRAVITY)) //Magboots return FALSE return ..() -/mob/living/silicon/check_airflow_movable() - return 0 +/mob/living/silicon/can_airflow_move(delta_p) + return FALSE -/obj/check_airflow_movable(n) - if(anchored) +/obj/can_airflow_move(delta_p) + if(anchored) // zone/proc/movables() checks this already for real spacewind, but other things may use this proc. return FALSE - if(density && n < zas_settings.airflow_dense_pressure) + if(density && (delta_p < zas_settings.airflow_dense_pressure)) return FALSE return ..() -/obj/item/check_airflow_movable(n) +/obj/item/can_airflow_move(delta_p) switch(w_class) - if(0,1,2) - if(n < zas_settings.airflow_lightest_pressure) return 0 - if(3) - if(n < zas_settings.airflow_light_pressure) return 0 - if(4,5) - if(n < zas_settings.airflow_medium_pressure) return 0 - if(6) - if(n < zas_settings.airflow_heavy_pressure) return 0 - if(7 to INFINITY) - if(n < zas_settings.airflow_dense_pressure) return 0 + if(0,WEIGHT_CLASS_TINY,WEIGHT_CLASS_SMALL) + if(delta_p < zas_settings.airflow_lightest_pressure) + return FALSE + if(WEIGHT_CLASS_NORMAL) + if(delta_p < zas_settings.airflow_light_pressure) + return FALSE + if(WEIGHT_CLASS_BULKY, WEIGHT_CLASS_HUGE) + if(delta_p < zas_settings.airflow_medium_pressure) + return FALSE + if(WEIGHT_CLASS_GIGANTIC) + if(delta_p < zas_settings.airflow_mob_pressure) + return FALSE return ..() ///The typecache of objects airflow can't push objects into the same tile of @@ -106,49 +112,41 @@ GLOBAL_LIST_INIT(airflow_step_blacklist, typecacheof(list( /obj/machinery/door ))) -/atom/movable/Bump(atom/A) - . = ..() - if(!moving_by_airflow) +/// Called when a movable Bump()s another atom due to forced airflow movement. +/atom/movable/proc/AirflowBump(atom/A) + var/turf/T = get_turf(A) + if(airborne_acceleration > 1) + airflow_hit(A) + A.airflow_hit_act(src) + + else if(istype(src, /mob/living/carbon/human) && A.density) + var/mob/living/carbon/human/human_src = src + to_chat(human_src, span_warning("You are pinned against \the [A] by airflow.")) + human_src.Stun(2 SECONDS) // :) + SSairflow.Dequeue(src) return - - if(airflow_speed > 0 && airflow_dest) - var/turf/T = get_turf(A) - if(airborne_acceleration > 1) - airflow_hit(A) - A.airflow_hit_act(src) - else if(istype(src, /mob/living/carbon/human)) - if((A:density)) - to_chat(src, "You are pinned against \the [A] by airflow!") - src:Stun(1 SECONDS) // :) - airflow_speed = 0 - airflow_time = 0 - airborne_acceleration = 0 - return - /* - If the turf of the atom we bumped is NOT dense, then we check if the flying object is dense. - We check the special var because flying objects gain density so they can Bump() objects. - If the object is NOT normally dense, we remove our density and the target's density, - enabling us to step into their turf. Then, we set the density back to the way its supposed to be for airflow. - */ - if(!T.density) - if(ismovable(A) && !(GLOB.airflow_step_blacklist[A.type]) && !A:airflow_old_density) - set_density(FALSE) - A.set_density(FALSE) - step_towards(src, airflow_dest) - set_density(TRUE) - A.set_density(TRUE) - else - airflow_speed = 0 - airflow_time = 0 - airborne_acceleration = 0 + /* + If the turf of the atom we bumped is NOT dense, then we check if the flying object is dense. + We check the special var because flying objects gain density so they can Bump() objects. + If the object is NOT normally dense, we remove our density and the target's density, + enabling us to step into their turf. Then, we set the density back to the way its supposed to be for airflow. + */ + if(!T.density && ismovable(A) && !(GLOB.airflow_step_blacklist[A.type])) + var/atom/movable/bumped_movable = A + if(bumped_movable.airflow_old_density) + return + + set_density(FALSE) + bumped_movable.set_density(FALSE) + step_towards(src, airflow_dest) + set_density(TRUE) + bumped_movable.set_density(TRUE) ///Called when src collides with A during airflow /atom/movable/proc/airflow_hit(atom/A) SHOULD_CALL_PARENT(TRUE) - airflow_speed = 0 - airflow_dest = null - airborne_acceleration = 0 + SSairflow.Dequeue(src) /mob/living/airflow_hit(atom/A) var/b_loss = AIRBORNE_DAMAGE(src) @@ -156,24 +154,26 @@ GLOBAL_LIST_INIT(airflow_step_blacklist, typecacheof(list( return ..() /mob/living/carbon/airflow_hit(atom/A) - if(istype(A, /obj/structure) || iswallturf(A)) - if(airflow_speed > 10) - Paralyze(round(airflow_speed * zas_settings.airflow_stun)) - Stun(round(airflow_speed * zas_settings.airflow_stun) + 3) - loc.add_blood_DNA(return_blood_DNA()) - visible_message( - span_danger("[src] splats against \the [A]!"), - span_userdanger("You slam into \the [A] with tremendous force!"), - span_hear("You hear a loud thud.") - ) - INVOKE_ASYNC(emote("scream")) - else - Stun(round(airflow_speed * zas_settings.airflow_stun/2)) - visible_message( - span_danger("[src] slams into \the [A]!"), - span_userdanger("You're thrown against \the [A] by pressure!"), - span_hear("You hear a loud thud.") - ) + if(!(istype(A, /obj/structure) || iswallturf(A))) + return ..() + + if(airflow_speed > 10) + Paralyze(round(airflow_speed * zas_settings.airflow_stun)) + Stun(round(airflow_speed * zas_settings.airflow_stun) + 3) + loc.add_blood_DNA(return_blood_DNA()) + visible_message( + span_danger("[src] splats against \the [A]!"), + span_userdanger("You slam into \the [A] with tremendous force!"), + span_hear("You hear a loud thud.") + ) + INVOKE_ASYNC(emote("scream")) + else + Stun(round(airflow_speed * zas_settings.airflow_stun/2)) + visible_message( + span_danger("[src] slams into \the [A]!"), + span_userdanger("You're thrown against \the [A] by pressure!"), + span_hear("You hear a loud thud.") + ) return ..() @@ -183,13 +183,12 @@ GLOBAL_LIST_INIT(airflow_step_blacklist, typecacheof(list( /mob/living/airflow_hit_act(atom/movable/flying) . = ..() - src.visible_message( + visible_message( span_danger("A flying [flying.name] slams into \the [src]!"), - span_danger("You're hit by a flying [flying]!"), - span_danger("You hear a soft thud.") + blind_message = span_danger("You hear a soft thud.") ) - playsound(src.loc, "punch", 25, 1, -1) + playsound(loc, "punch", 25, 1, -1) var/weak_amt if(istype(flying,/obj/item)) weak_amt = flying:w_class*2 ///Heheheh @@ -198,18 +197,18 @@ GLOBAL_LIST_INIT(airflow_step_blacklist, typecacheof(list( else weak_amt = rand(1, 3) - src.Knockdown(weak_amt SECONDS) + Knockdown(weak_amt SECONDS) /obj/airflow_hit_act(atom/movable/flying) . = ..() if(flying.airflow_old_density) - src.visible_message( + visible_message( span_danger("A flying [flying.name] slams into \the [src]!"), null, span_danger("You hear a loud slam!") ) - playsound(src.loc, "smash.ogg", 25, 1, -1) + playsound(loc, "smash.ogg", 25, 1, -1) if(!uses_integrity) return @@ -244,4 +243,42 @@ GLOBAL_LIST_INIT(airflow_step_blacklist, typecacheof(list( continue . += A +/atom/movable/proc/prepare_airflow(strength) + if (!airflow_dest || airflow_dest == loc) // This should no longer happen, but just in case, ignore it. + return FALSE + + COOLDOWN_START(src, airflow_push_cooldown, zas_settings.airflow_retrigger_delay) + + var/airflow_falloff = 9 - get_dist_euclidean(loc, airflow_dest) + if (airflow_falloff < 1) + return FALSE + + airflow_speed = clamp(strength * (9 / airflow_falloff), 1, 9) + return TRUE + +/mob/prepare_airflow(strength) + . = ..() + if(!.) + return + + to_chat(src, span_warning("A strong air current drags you away.")) + +/atom/movable/proc/GotoAirflowDest(strength) + if (!prepare_airflow(strength)) + airflow_dest = null + return + airflow_xo = airflow_dest.x - x + airflow_yo = airflow_dest.y - y + airflow_dest = null + SSairflow.Enqueue(src) + +/atom/movable/proc/RepelAirflowDest(strength) + if (!prepare_airflow(strength)) + airflow_dest = null + return + airflow_xo = -(airflow_dest.x - x) + airflow_yo = -(airflow_dest.y - y) + airflow_dest = null + SSairflow.Enqueue(src) + #undef AIRBORNE_DAMAGE diff --git a/code/modules/atmospherics/ZAS/Connection.dm b/code/modules/atmospherics/ZAS/Connection.dm index 8ea204dbfab7..5bf97dac4806 100644 --- a/code/modules/atmospherics/ZAS/Connection.dm +++ b/code/modules/atmospherics/ZAS/Connection.dm @@ -236,4 +236,5 @@ Class Procs: #endif /connection_edge/proc/queue_spacewind() + set waitfor = FALSE return diff --git a/code/modules/atmospherics/ZAS/ConnectionGroup.dm b/code/modules/atmospherics/ZAS/ConnectionGroup.dm index 2af7cea9a2a2..a93100af2bf7 100644 --- a/code/modules/atmospherics/ZAS/ConnectionGroup.dm +++ b/code/modules/atmospherics/ZAS/ConnectionGroup.dm @@ -68,6 +68,8 @@ Class Procs: ///The last time the "woosh" airflow sound played, world.time var/last_woosh + /// Prevents spacewind from happening until the last one is done. + var/flowing = FALSE #ifdef ZASDBG ///Set this to TRUE during testing to get verbose debug information. @@ -126,44 +128,80 @@ Class Procs: ///Airflow proc causing all objects in movable to be checked against a pressure differential. See file header for more info. /connection_edge/proc/flow(list/movable, differential, repelled) - set waitfor = FALSE for(var/atom/movable/M as anything in movable) //Non simulated objects dont get tossed if(!M.simulated) continue //If they're already being tossed, don't do it again. - if(M.last_airflow > world.time - zas_settings.airflow_delay) + if(!COOLDOWN_FINISHED(M, airflow_push_cooldown)) continue if(M.airflow_speed) continue //Check for knocking people over + var/send_message = FALSE if(ismob(M) && differential > zas_settings.airflow_stun_pressure) - if(M:status_flags & GODMODE) + var/mob/living/living_mob = M + if(living_mob.status_flags & GODMODE) continue - if(!M:airflow_stun()) - to_chat(M, span_notice("A gust of air rushes past you.")) - if(M.check_airflow_movable(differential)) - //Check for things that are in range of the midpoint turfs. - var/list/close_turfs = list() - for(var/turf/T as anything in connecting_turfs) - if(get_dist(M, T) < world.view) - close_turfs += T + if(!living_mob.airflow_stun()) + send_message = TRUE - if(!length(close_turfs)) - continue + if(!M.can_airflow_move(differential)) + if(send_message) + to_chat(M, span_warning("A gust of air rushes past you.")) + continue + + //Check for things that are in range of the midpoint turfs. + var/list/close_turfs = list() + for(var/turf/T as anything in connecting_turfs) + if(get_dist(M, T) < world.view) + close_turfs += T + + if(!length(close_turfs)) + continue + + if(HAS_TRAIT(M, TRAIT_EXPERIENCING_AIRFLOW)) + SSairflow.Dequeue(M) + + M.airflow_dest = pick(close_turfs) //Pick a random midpoint to fly towards. + if(M.airflow_dest == M.loc) + M.airflow_dest = null - if(HAS_TRAIT(M, TRAIT_EXPERIENCING_AIRFLOW)) - SSairflow.Dequeue(M) + var/list/nearby_turfs = RANGE_TURFS(1, M) - M.loc + shuffle_inplace(nearby_turfs) + for(var/turf/open/open_turf as anything in nearby_turfs) + // A < B (Move away from B) + if(repelled) + if(open_turf.zone != A) + M.airflow_dest = open_turf + break + continue - M.airflow_dest = pick(close_turfs) //Pick a random midpoint to fly towards. + // A > B (Move towards B) + if(istype(src, /connection_edge/zone)) + var/connection_edge/zone/zone_edge = src - if(repelled) - M.RepelAirflowDest(differential/5) - else - M.GotoAirflowDest(differential/10) + if(open_turf.zone == zone_edge.B) + M.airflow_dest = open_turf + break + continue + + // A > B (Move towards B) + if(!open_turf.zone) + M.airflow_dest = open_turf + break + + if(!M.airflow_dest) + continue + + + if(repelled) + M.RepelAirflowDest(differential/5) + else + M.GotoAirflowDest(differential/10) CHECK_TICK @@ -213,8 +251,9 @@ Class Procs: return var/equiv = A.air.shareRatio(B.air, coefficient) - + #ifndef UNIT_TESTS queue_spacewind() + #endif if(equiv) if(direct) @@ -234,21 +273,28 @@ Class Procs: SSzas.excite_edge(src) /connection_edge/zone/queue_spacewind() + if(flowing) + return + var/differential = A.air.returnPressure() - B.air.returnPressure() - if(abs(differential) >= zas_settings.airflow_lightest_pressure) - var/list/attracted - var/list/repelled - if(differential > 0) - attracted = A.movables() - repelled = B.movables() - else - attracted = B.movables() - repelled = A.movables() + if(abs(differential) < zas_settings.airflow_lightest_pressure) + return + + flowing = TRUE + var/list/attracted + var/list/repelled + if(differential > 0) + attracted = A.movables() + repelled = B.movables() + else + attracted = B.movables() + repelled = A.movables() - WOOSH + WOOSH - flow(attracted, abs(differential), 0) - flow(repelled, abs(differential), 1) + flow(attracted, abs(differential), 0) + flow(repelled, abs(differential), 1) + flowing = FALSE /connection_edge/unsimulated var/turf/B @@ -299,7 +345,9 @@ Class Procs: var/equiv = A.air.shareSpace(air) + #ifndef UNIT_TESTS queue_spacewind() + #endif if(equiv) A.air.copyFrom(air) @@ -315,17 +363,19 @@ Class Procs: SSzas.excite_edge(src) /connection_edge/unsimulated/queue_spacewind() - #ifdef UNIT_TESTS - return - #endif - + if(flowing) + return var/differential = A.air.returnPressure() - air.returnPressure() - if(abs(differential) >= zas_settings.airflow_lightest_pressure) - var/list/attracted = A.movables() + if(abs(differential) < zas_settings.airflow_lightest_pressure) + return + + flowing = TRUE + var/list/attracted = A.movables() - WOOSH + WOOSH - flow(attracted, abs(differential), differential < 0) + flow(attracted, abs(differential), differential < 0) + flowing = FALSE /proc/ShareHeat(datum/gas_mixture/A, datum/gas_mixture/B, connecting_tiles) //This implements a simplistic version of the Stefan-Boltzmann law. diff --git a/code/modules/atmospherics/ZAS/ConnectionManager.dm b/code/modules/atmospherics/ZAS/ConnectionManager.dm index c54099da70d8..ea8ca0fab01f 100644 --- a/code/modules/atmospherics/ZAS/ConnectionManager.dm +++ b/code/modules/atmospherics/ZAS/ConnectionManager.dm @@ -31,9 +31,6 @@ Macros: */ -// macro-ized to cut down on proc calls -#define check(c) (c && c.valid()) - /turf var/tmp/connection_manager/connections @@ -48,28 +45,30 @@ Macros: var/connection/D #endif -/connection_manager/proc/get(d) +/// Return a valid connection for a given direction. +/connection_manager/proc/get_connection_for_dir(d) switch(d) if(NORTH) - if(check(N)) return N - else return null + if(N?.valid()) + return N + if(SOUTH) - if(check(S)) return S - else return null + if(S?.valid()) + return S if(EAST) - if(check(E)) return E - else return null + if(E?.valid()) + return E if(WEST) - if(check(W)) return W - else return null + if(W?.valid()) + return W #ifdef MULTIZAS if(UP) - if(check(U)) return U - else return null + if(U?.valid()) + return U if(DOWN) - if(check(D)) return D - else return null + if(D?.valid()) + return D #endif /connection_manager/proc/place(connection/c, d) @@ -85,23 +84,22 @@ Macros: #endif /connection_manager/proc/update_all() - if(check(N)) N.update() - if(check(S)) S.update() - if(check(E)) E.update() - if(check(W)) W.update() + if(N?.valid()) N.update() + if(S?.valid()) S.update() + if(E?.valid()) E.update() + if(W?.valid()) W.update() #ifdef MULTIZAS - if(check(U)) U.update() - if(check(D)) D.update() + if(U?.valid()) U.update() + if(D?.valid()) D.update() #endif /connection_manager/proc/erase_all() - if(check(N)) N.erase() - if(check(S)) S.erase() - if(check(E)) E.erase() - if(check(W)) W.erase() + if(N?.valid()) N.erase() + if(S?.valid()) S.erase() + if(E?.valid()) E.erase() + if(W?.valid()) W.erase() #ifdef MULTIZAS - if(check(U)) U.erase() - if(check(D)) D.erase() + if(U?.valid()) U.erase() + if(D?.valid()) D.erase() #endif -#undef check diff --git a/code/modules/atmospherics/ZAS/Plasma.dm b/code/modules/atmospherics/ZAS/Plasma.dm index 693e989b7b84..b48775de1407 100644 --- a/code/modules/atmospherics/ZAS/Plasma.dm +++ b/code/modules/atmospherics/ZAS/Plasma.dm @@ -1,14 +1,6 @@ GLOBAL_DATUM_INIT(contamination_overlay, /image, image('modular_pariah/master_files/icons/effects/contamination.dmi')) /datum/pl_control - var/plasma_dmg = 3 - var/plasma_dmg_name = "Plasma Damage Amount" - var/plasma_dmg_desc = "Self Descriptive" - - var/plasmaguard_only = FALSE - var/plamaguard_only_name = "\"PlasmaGuard Only\"" - var/plasmaguard_only_desc = "If this is on, only biosuits and spacesuits protect against contamination and ill effects." - var/genetic_corruption = 0 var/genetic_corruption_name = "Genetic Corruption Chance" var/genetic_corruption_desc = "Chance of genetic corruption, X in 1,000." @@ -28,7 +20,7 @@ GLOBAL_DATUM_INIT(contamination_overlay, /image, image('modular_pariah/master_fi ///Handles all the bad things phoron can do. /mob/living/carbon/human/expose_plasma(exposed_amount) //Anything else requires them to not be dead. - if(stat >= 2) + if(stat == DEAD) return //Burn eyes if exposed. @@ -49,34 +41,3 @@ GLOBAL_DATUM_INIT(contamination_overlay, /image, image('modular_pariah/master_fi if(rand(1, 1000) < zas_settings.plc.genetic_corruption * exposed_amount) easy_random_mutate(NEGATIVE) to_chat(src, "High levels of toxins cause you to spontaneously mutate!") - -/mob/living/carbon/human/proc/pl_head_protected() - //Checks if the head is adequately sealed. - if(head) - if(zas_settings.plc.plasmaguard_only) - if(head.obj_flags & PLASMAGUARD) - return TRUE - else if(is_eyes_covered()) - return TRUE - return FALSE - -/mob/living/carbon/human/proc/pl_suit_protected() - //Checks if the suit is adequately sealed. - var/coverage = NONE - for(var/obj/item/protection in list(wear_suit, gloves, shoes)) - if(!protection) - continue - if(istype(protection, /obj/item/clothing)) - var/obj/item/clothing/clothing_item = protection - if(zas_settings.plc.plasmaguard_only && !((clothing_item.clothing_flags & THICKMATERIAL) || (clothing_item.clothing_flags & GAS_FILTERING) || (clothing_item.obj_flags & PLASMAGUARD))) - return FALSE - - else if(zas_settings.plc.plasmaguard_only && !(protection.obj_flags & PLASMAGUARD)) - return FALSE - - coverage |= protection.body_parts_covered - - if(zas_settings.plc.plasmaguard_only) - return TRUE - - return (~(coverage) & (CHEST|LEGS|FEET|ARMS|HANDS) == 0) diff --git a/code/modules/atmospherics/ZAS/ZAS_Settings.dm b/code/modules/atmospherics/ZAS/ZAS_Settings.dm index 183d1f2b3326..f019576595cd 100644 --- a/code/modules/atmospherics/ZAS/ZAS_Settings.dm +++ b/code/modules/atmospherics/ZAS/ZAS_Settings.dm @@ -1,102 +1,52 @@ +#define SETTING_DEF(var_name, default_value, name, description, category) \ + var/##var_name = default_value;\ + var/name_##var_name = name;\ + var/desc_##var_name = description;\ + var/cat_##var_name = category;\ + /datum/zas_controller var/datum/pl_control/plc = new - var/fire_consumption_rate = 0.25 - var/fire_consumption_rate_NAME = "Fire - Air Consumption Ratio" - var/fire_consumption_rate_DESC = "Ratio of air removed and combusted per tick." - - var/fire_firelevel_multiplier = 25 - var/fire_firelevel_multiplier_NAME = "Fire - Firelevel Constant" - var/fire_firelevel_multiplier_DESC = "Multiplied by the equation for firelevel, affects mainly the extingiushing of fires." - - //Note that this parameter and the phoron heat capacity have a significant impact on TTV yield. - var/fire_fuel_energy_release = 866000 //J/mol. Adjusted to compensate for fire energy release being fixed, was 397000 - var/fire_fuel_energy_release_NAME = "Fire - Fuel energy release" - var/fire_fuel_energy_release_DESC = "The energy in joule released when burning one mol of a burnable substance" - - - var/IgnitionLevel = 0.5 - var/IgnitionLevel_DESC = "Determines point at which fire can ignite" - - var/airflow_lightest_pressure = 20 - var/airflow_lightest_pressure_NAME = "Airflow - Small Movement Threshold %" - var/airflow_lightest_pressure_DESC = "Percent of 1 Atm. at which items with the small weight classes will move." - - var/airflow_light_pressure = 35 - var/airflow_light_pressure_NAME = "Airflow - Medium Movement Threshold %" - var/airflow_light_pressure_DESC = "Percent of 1 Atm. at which items with the medium weight classes will move." - - var/airflow_medium_pressure = 50 - var/airflow_medium_pressure_NAME = "Airflow - Heavy Movement Threshold %" - var/airflow_medium_pressure_DESC = "Percent of 1 Atm. at which items with the largest weight classes will move." - - var/airflow_heavy_pressure = 65 - var/airflow_heavy_pressure_NAME = "Airflow - Mob Movement Threshold %" - var/airflow_heavy_pressure_DESC = "Percent of 1 Atm. at which mobs will move." - - var/airflow_dense_pressure = 85 - var/airflow_dense_pressure_NAME = "Airflow - Dense Movement Threshold %" - var/airflow_dense_pressure_DESC = "Percent of 1 Atm. at which items with canisters and closets will move." - - var/airflow_stun_pressure = 60 - var/airflow_stun_pressure_NAME = "Airflow - Mob Stunning Threshold %" - var/airflow_stun_pressure_DESC = "Percent of 1 Atm. at which mobs will be stunned by airflow." - - var/airflow_stun_cooldown = 60 - var/airflow_stun_cooldown_NAME = "Aiflow Stunning - Cooldown" - var/airflow_stun_cooldown_DESC = "How long, in tenths of a second, to wait before stunning them again." - - var/airflow_stun = 1 SECONDS - var/airflow_stun_NAME = "Airflow Impact - Stunning" - var/airflow_stun_DESC = "How much a mob is stunned when hit by an object." - - var/airflow_damage = 3 - var/airflow_damage_NAME = "Airflow Impact - Damage" - var/airflow_damage_DESC = "Damage from airflow impacts." - - var/airflow_speed_decay = 1.5 - var/airflow_speed_decay_NAME = "Airflow Speed Decay" - var/airflow_speed_decay_DESC = "How rapidly the speed gained from airflow decays." - - var/airflow_delay = 30 - var/airflow_delay_NAME = "Airflow Retrigger Delay" - var/airflow_delay_DESC = "Time in deciseconds before things can be moved by airflow again." - - var/airflow_mob_slowdown = 1 - var/airflow_mob_slowdown_NAME = "Airflow Slowdown" - var/airflow_mob_slowdown_DESC = "Time in tenths of a second to add as a delay to each movement by a mob if they are fighting the pull of the airflow." - - var/connection_insulation = 1 - var/connection_insulation_NAME = "Connections - Insulation" - var/connection_insulation_DESC = "Boolean, should doors forbid heat transfer?" - - var/connection_temperature_delta = 10 - var/connection_temperature_delta_NAME = "Connections - Temperature Difference" - var/connection_temperature_delta_DESC = "The smallest temperature difference which will cause heat to travel through doors." - - var/maxex_devastation_range = 4 - var/max_explosion_range_NAME = "Explosion Devastation Range" - var/max_explosion_range_DESC = "By default, 1/4th of fire range." - - var/maxex_heavy_range = 8 - var/max_heavy_range_NAME = "Explosion Heavy Range" - var/max_heavy_range_DESC = "By default, 1/2 of light range" - - var/maxex_light_range = 16 - var/max_light_range_NAME = "Explosion Light Range" - var/max_light_range_DESC = "By default, this is the baseline for other explosion values." - - var/maxex_fire_range = 16 - var/max_fire_range_NAME = "Explosion Fire Range" - var/max_fire_range_DESC = "By default, equal to light range." - - var/maxex_flash_range = 20 - var/max_flash_range_NAME = "Explosion Flash Range" - var/max_flash_range_DESC = "By default, 5/4ths of light range." - - var/airflow_speed_for_density = 5 - var/airflow_speed_for_density_NAME = "Airflow Speed For Density" - var/airflow_speed_for_density_DESC = "The speed an object must be moving at to become dense to hit other objects" + //* Fire Settings *// + SETTING_DEF(fire_consumption_rate, 0.25, "Air Consumption Ratio", "Ratio of air removed and combusted per tick.", "Fire") + SETTING_DEF(fire_firelevel_multiplier, 25, "Firelevel Constant", "Multiplied by the equation for firelevel, affects mainly the extingiushing of fires.", "Fire") + /// Note that this parameter and the phoron heat capacity have a significant impact on TTV yield. + /// Value is in J/mol. + SETTING_DEF(fire_fuel_energy_release, 866000, "Fuel energy release", "The energy in joule released when burning one mol of a burnable substance", "Fire") + + //* Airflow Settings *// + SETTING_DEF(airflow_lightest_pressure, 20, "Small Movement Threshold %", "Percent of 1 Atm. at which items with the small weight classes will move.", "Airflow") + SETTING_DEF(airflow_light_pressure, 35, "Medium Movement Threshold %", "Percent of 1 Atm. at which items with the medium weight classes will move.", "Airflow") + SETTING_DEF(airflow_medium_pressure, 50, "Heavy Movement Threshold %", "Percent of 1 Atm. at which items with the largest weight classes will move.", "Airflow") + SETTING_DEF(airflow_mob_pressure, 65, "Mob Movement Threshold %", "Percent of 1 Atm. at which mobs will move.", "Airflow") + SETTING_DEF(airflow_dense_pressure, 85, "Dense Movement Threshold %", "Percent of 1 Atm. at which dense objs can move.", "Airflow") + SETTING_DEF(airflow_speed_decay, 1.5, "Speed Decay Rate", "Speed removed from an airborne object per tick.", "Airflow") + SETTING_DEF(airflow_speed_for_density, 5, "Force Density Speed", "The speed value required to be able to impact objects during airflow, if not normally dense.", "Airflow") + SETTING_DEF(airflow_retrigger_delay, 0 SECONDS, "Airflow Retrigger Grace", "After being affected by spacewind, wait this long before affecting again.", "Airflow") + SETTING_DEF(airflow_mob_slowdown, 1, "Mob Slowdown", "Additive slowdown applied to mobs affected by spacewind.", "Airflow") + + SETTING_DEF(airflow_stun_pressure, 60, "Mob Stunning Threshold %", "Percent of 1 Atm. at which mobs will be stunned by airflow.", "Airflow Impact") + SETTING_DEF(airflow_stun, 1 SECOND, "Stun Duration", "How long a mob is stunned when hit by an airborne object.", "Airflow Impact") + SETTING_DEF(airflow_stun_cooldown, 1 SECOND, "Stun Cooldown", "How long, in tenths of a second, to wait before stunning them again.", "Airflow Impact") + SETTING_DEF(airflow_damage, 3, "Damage", "Damage from airflow impacts.", "Airflow Impact") + + + //* Bomb Settings *// + SETTING_DEF(maxex_devastation_range, 4, "Devastation Cap", "By default, 1/4th of fire range.", "Bomb") + SETTING_DEF(maxex_heavy_range, 8, "Heavy Cap", "By default, 1/2 of light range.", "Bomb") + SETTING_DEF(maxex_light_range, 18, "Light Cap", "By default, this is the baseline for other explosion values.", "Bomb") + SETTING_DEF(maxex_fire_range, 16, "Fire Cap", "By default, equal to light range.", "Bomb") + SETTING_DEF(maxex_flash_range, 20, "Flash Cap", "By default, 5/4ths of light range.", "Bomb") + + /// See /proc/compile_vars() + var/list/edittable_vars = list() + +/datum/zas_controller/New() + compile_vars() + +// Reject all VV edits, use the panel. +/datum/zas_controller/vv_edit_var(var_name, var_value) + return FALSE /datum/zas_controller/proc/set_bomb_cap(val) if(!isnum(val)) @@ -107,3 +57,80 @@ maxex_light_range = val maxex_fire_range = val maxex_flash_range = val*1.2 + +/datum/zas_controller/proc/compile_vars() + edittable_vars += nameof(fire_consumption_rate) + edittable_vars += nameof(fire_firelevel_multiplier) + edittable_vars += nameof(fire_fuel_energy_release) + + edittable_vars += nameof(airflow_lightest_pressure) + edittable_vars += nameof(airflow_light_pressure) + edittable_vars += nameof(airflow_medium_pressure) + edittable_vars += nameof(airflow_mob_pressure) + edittable_vars += nameof(airflow_dense_pressure) + edittable_vars += nameof(airflow_speed_decay) + edittable_vars += nameof(airflow_speed_for_density) + edittable_vars += nameof(airflow_retrigger_delay) + edittable_vars += nameof(airflow_mob_slowdown) + + edittable_vars += nameof(airflow_stun_pressure) + edittable_vars += nameof(airflow_stun) + edittable_vars += nameof(airflow_stun_cooldown) + edittable_vars += nameof(airflow_damage) + + edittable_vars += nameof(maxex_devastation_range) + edittable_vars += nameof(maxex_heavy_range) + edittable_vars += nameof(maxex_light_range) + edittable_vars += nameof(maxex_fire_range) + edittable_vars += nameof(maxex_flash_range) + +/datum/zas_controller/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ZasSettings") + ui.open() + +/datum/zas_controller/ui_state(mob/user) + return GLOB.admin_state + +/datum/zas_controller/ui_data(mob/user) + var/list/data = list() + var/list/settings = list() + var/list/categories = list() + data["settings"] = settings + data["categories"] = categories + + for(var/var_name in edittable_vars) + var/var_category = vars["cat_[var_name]"] + settings[var_name] = list( + "value" = vars[var_name], + "name" = vars["name_[var_name]"], + "desc" = vars["desc_[var_name]"], + "category" = var_category, + ) + + categories |= var_category + + return data + + +/datum/zas_controller/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + + switch(action) + if("change_var") + var/var_name = params["var_name"] + var/var_human_name = params["var_human_name"] + if(!(var_name in edittable_vars)) + return TRUE + + var/new_val = tgui_input_number(ui.user, "Change [var_human_name]", "ZAS Settings", vars[var_name], round_value = FALSE) + if(isnull(new_val)) + return + + log_admin("[key_name_admin(ui.user)] updated ZAS setting [var_human_name]: [vars[var_name]] -> [new_val]") + message_admins("[key_name_admin(ui.user)] updated ZAS setting [var_human_name]: [vars[var_name]] -> [new_val]") + vars[var_name] = new_val + return TRUE diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index 072a901ad199..eeefaccfbe40 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -393,7 +393,7 @@ GLOBAL_REAL_VAR(atmos_machinery_default_armor) = list(BLUNT = 25, PUNCTURE = 10, to_chat(user, span_warning("As you begin unwrenching \the [src] a gush of air blows in your face... maybe you should reconsider?")) unsafe_wrenching = TRUE //Oh dear oh dear - if(I.use_tool(src, user, 20, volume=50)) + if(I.use_tool(src, user, 2 SECONDS, volume=50)) user.visible_message( \ "[user] unfastens \the [src].", \ span_notice("You unfasten \the [src]."), \ @@ -428,12 +428,22 @@ GLOBAL_REAL_VAR(atmos_machinery_default_armor) = list(BLUNT = 25, PUNCTURE = 10, /obj/machinery/atmospherics/proc/unsafe_pressure_release(mob/user, pressures = null) if(!user) return + if(!pressures) var/datum/gas_mixture/int_air = return_air() var/datum/gas_mixture/env_air = loc.return_air() pressures = int_air.returnPressure() - env_air.returnPressure() - user.visible_message(span_danger("[user] is sent flying by pressure!"),span_userdanger("The pressure sends you flying!")) + if(pressures < 0) + return + + if(!user.can_airflow_move(pressures)) + return + + user.visible_message( + span_danger("[user] is sent flying by an explosion of pressure!"), + span_hear("You hear a gaseous hiss, followed by a thud.") + ) // if get_dir(src, user) is not 0, target is the edge_target_turf on that dir // otherwise, edge_target_turf uses a random cardinal direction diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index f534b01a772e..a331d482f2ea 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -1237,7 +1237,7 @@ GLOBAL_LIST_EMPTY(features_by_species) humi.adjust_coretemperature(skin_core_change) // get the enviroment details of where the mob is standing - var/datum/gas_mixture/environment = humi.loc?.return_air() + var/datum/gas_mixture/environment = humi.loc?.unsafe_return_air() if(!environment) // if there is no environment (nullspace) drop out here. return diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index 1845ed251b69..3ac30a8728ec 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -64,7 +64,7 @@ handle_random_events(delta_time, times_fired) //Handle temperature/pressure differences between body and environment - var/datum/gas_mixture/environment = loc.return_air() + var/datum/gas_mixture/environment = loc.unsafe_return_air() if(environment) if(handle_environment(environment, delta_time, times_fired)) updatehealth() @@ -98,7 +98,7 @@ /mob/living/proc/handle_chemicals() return -// Base mob environment handler for body temperature +// Base mob environment handler for body temperature. This proc assumes you're not changing the air mix, so don't do it! /mob/living/proc/handle_environment(datum/gas_mixture/environment, delta_time, times_fired) var/loc_temp = get_temperature(environment) var/temp_delta = loc_temp - bodytemperature diff --git a/tgui/packages/tgui/interfaces/ZasSettings.tsx b/tgui/packages/tgui/interfaces/ZasSettings.tsx new file mode 100644 index 000000000000..240fbc4acbb0 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ZasSettings.tsx @@ -0,0 +1,91 @@ +import { toTitleCase } from 'common/string'; + +import { useBackend, useLocalState } from '../backend'; +import { Button, Flex, Section, Table, Tabs, Tooltip } from '../components'; +import { Window } from '../layouts'; + +type ZasSetting = { + category: string; + desc: string; + name: string; + value: number; +}; + +type ZasData = { + categories: string[]; + settings: ZasSetting[]; +}; + +export const ZasSettings = (props) => { + const { data } = useBackend(); + + const [tab, setTab] = useLocalState('tab', 0); + const settings = data.settings; + const categories = data.categories; + return ( + + + + {categories.map((category_name, i) => ( + setTab(i)}> + {category_name} + + ))} + + + + + ); +}; + +export const ZasTab = (props) => { + const { act, data } = useBackend(); + const [tab, setTab] = useLocalState('tab', 0); + const settings = data.settings; + const selected_category = data.categories[tab]; + + return ( +
+ + {Object.values(settings).map( + (setting, i) => + setting.category === selected_category && ( + + + + + + {setting.name} + + + + {setting.value.toString()} + + + + + +
+
+ ), + )} +
+
+ ); +}; + +const getKeyByValue = (obj, value) => + Object.keys(obj).find((key) => obj[key] === value);