From d0d685d09328b89cfa9df0b2dd7e824d73e2390c Mon Sep 17 00:00:00 2001 From: Drulikar Date: Fri, 12 Jun 2026 20:33:15 -0500 Subject: [PATCH 01/11] Fix the incorrect assumption of directionless explosions --- code/datums/autocells/explosion.dm | 3 ++- code/game/objects/explosion_recursive.dm | 16 ++++++++-------- code/game/objects/shrapnel.dm | 2 ++ .../lighting/lighting_mask/lighting_mask.dm | 2 +- code/modules/mob/living/carbon/carbon.dm | 4 ++-- .../mob/living/carbon/xenomorph/damage_procs.dm | 2 +- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/code/datums/autocells/explosion.dm b/code/datums/autocells/explosion.dm index 706e75623b71..dd78750da32e 100644 --- a/code/datums/autocells/explosion.dm +++ b/code/datums/autocells/explosion.dm @@ -240,10 +240,11 @@ as having entered the turf. // Note that we don't want to make it a directed ex_act because // it could toss them back and make them get hit by the explosion again + // so we indicate this with a direction < 0 if(A.gc_destroyed) return - INVOKE_ASYNC(A, TYPE_PROC_REF(/atom, ex_act), power, null, explosion_cause_data, 0, enviro) + INVOKE_ASYNC(A, TYPE_PROC_REF(/atom, ex_act), power, -1, explosion_cause_data, 0, enviro) log_explosion(A, src) // I'll admit most of the code from here on out is basically just copypasta from DOREC diff --git a/code/game/objects/explosion_recursive.dm b/code/game/objects/explosion_recursive.dm index f8836f5dad81..06e6552b592a 100644 --- a/code/game/objects/explosion_recursive.dm +++ b/code/game/objects/explosion_recursive.dm @@ -325,19 +325,21 @@ explosion resistance exactly as much as their health if(anchored) return - if(!istype(src.loc, /turf)) + if(!isturf(loc)) return + if(direction < 0) + return // Don't do anything if explicitly directionless + + var/range = min(round(severity/w_class * 0.2, 1), 14) + if(!direction) direction = pick(GLOB.alldirs) - var/range = min(round(severity/src.w_class * 0.2, 1), 14) - if(!direction) - range = round( range/2 ,1) + range = round(range/2, 1) if(range < 1) return - var/speed = max(range*2.5, SPEED_SLOW) var/atom/target = get_ranged_target_turf(src, direction, range) @@ -345,13 +347,11 @@ explosion resistance exactly as much as their health var/scatter = range/4 * scatter_multiplier var/scatter_x = rand(-scatter,scatter) var/scatter_y = rand(-scatter,scatter) - target = locate(target.x + round( scatter_x , 1),target.y + round( scatter_y , 1),target.z) //Locate an adjacent turf. + target = locate(target.x + round(scatter_x , 1), target.y + round(scatter_y , 1), target.z) //Locate an adjacent turf. //time for the explosion to destroy windows, walls, etc which might be in the way INVOKE_ASYNC(src, TYPE_PROC_REF(/atom/movable, throw_atom), target, range, speed, null, TRUE) - return - /mob/proc/explosion_throw(severity, direction) if(anchored) return diff --git a/code/game/objects/shrapnel.dm b/code/game/objects/shrapnel.dm index 6b39bcb05fb7..65c210c3a97c 100644 --- a/code/game/objects/shrapnel.dm +++ b/code/game/objects/shrapnel.dm @@ -1,5 +1,7 @@ /proc/create_shrapnel(turf/epicenter, shrapnel_number = 10, shrapnel_direction, shrapnel_spread = 45, datum/ammo/shrapnel_type = /datum/ammo/bullet/shrapnel, datum/cause_data/cause_data, ignore_source_mob = FALSE, on_hit_coefficient = 0.15, use_shrapnel_angle = FALSE) + if(shrapnel_direction < 0) + return // Don't do anything if explicitly directionless epicenter = get_turf(epicenter) diff --git a/code/modules/lighting/lighting_mask/lighting_mask.dm b/code/modules/lighting/lighting_mask/lighting_mask.dm index e76590d254bb..dd6f2e2a871d 100644 --- a/code/modules/lighting/lighting_mask/lighting_mask.dm +++ b/code/modules/lighting/lighting_mask/lighting_mask.dm @@ -165,7 +165,7 @@ /atom/movable/lighting_mask/rotating_conical icon_state = "light_conical_rotating" -/atom/movable/lighting_mask/ex_act(severity, target) +/atom/movable/lighting_mask/ex_act(severity) return /atom/movable/lighting_mask/fire_act(exposed_temperature, exposed_volume) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 7ff77d825c5c..e297b41a9117 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -50,7 +50,7 @@ if(severity >= health && severity >= EXPLOSION_THRESHOLD_GIB) gibbing = TRUE - if(body_position == LYING_DOWN && direction) + if(body_position == LYING_DOWN && direction > 0) severity *= EXPLOSION_PRONE_MULTIPLIER if(HAS_TRAIT(src, TRAIT_HAULED) && !gibbing) // We still probably wanna gib them as well if they were supposed to be gibbed by the explosion in the first place @@ -69,7 +69,7 @@ apply_damage(severity, BRUTE, enviro=enviro) updatehealth() - var/knock_value = min( round( severity*0.1 ,1) ,10) + var/knock_value = min(round(severity*0.1, 1), 10) if(knock_value > 0) apply_effect(knock_value, PARALYZE) explosion_throw(severity, direction) diff --git a/code/modules/mob/living/carbon/xenomorph/damage_procs.dm b/code/modules/mob/living/carbon/xenomorph/damage_procs.dm index d59ad11f8eb0..97c25f0430a4 100644 --- a/code/modules/mob/living/carbon/xenomorph/damage_procs.dm +++ b/code/modules/mob/living/carbon/xenomorph/damage_procs.dm @@ -46,7 +46,7 @@ /mob/living/carbon/xenomorph/ex_act(severity, direction, datum/cause_data/cause_data, pierce=0, enviro=FALSE) - if(body_position == LYING_DOWN && direction) + if(body_position == LYING_DOWN && direction > 0) severity *= EXPLOSION_PRONE_MULTIPLIER if(severity >= 30) From 75bcb487e2a7ac139dec9449db947917dd2535c0 Mon Sep 17 00:00:00 2001 From: Drulikar Date: Fri, 12 Jun 2026 20:33:36 -0500 Subject: [PATCH 02/11] explosions that merge going the same direction share exploded_atoms --- code/datums/autocells/explosion.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/datums/autocells/explosion.dm b/code/datums/autocells/explosion.dm index dd78750da32e..cd92d425fe7d 100644 --- a/code/datums/autocells/explosion.dm +++ b/code/datums/autocells/explosion.dm @@ -109,6 +109,7 @@ // Two waves traveling the same direction amplifies the explosion if(survivor.direction == dying.direction) survivor.power += dying.power + survivor.exploded_atoms |= dying.exploded_atoms // Two waves travling towards each other weakens the explosion if(survivor.direction == GLOB.reverse_dir[dying.direction]) From b1287f9539d000a643ecb36a38689f7c7d1d2c81 Mon Sep 17 00:00:00 2001 From: Drulikar Date: Fri, 12 Jun 2026 20:33:57 -0500 Subject: [PATCH 03/11] explosions that propagate share exploded_atoms --- code/datums/autocells/explosion.dm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/code/datums/autocells/explosion.dm b/code/datums/autocells/explosion.dm index cd92d425fe7d..5cf78935cda7 100644 --- a/code/datums/autocells/explosion.dm +++ b/code/datums/autocells/explosion.dm @@ -80,6 +80,11 @@ if(shockwave) qdel(shockwave) +/datum/automata_cell/explosion/propagate(dir) + var/datum/automata_cell/explosion/new_cell = ..() + new_cell?.exploded_atoms |= exploded_atoms + return new_cell + // Compare directions. If the other explosion is traveling in the same direction, // the explosion is amplified. If not, it's weakened /datum/automata_cell/explosion/merge(datum/automata_cell/explosion/E) From e1100d115b6cf0a62ed310d322c48ddddfcad3d6 Mon Sep 17 00:00:00 2001 From: Drulikar Date: Sat, 13 Jun 2026 17:16:11 -0500 Subject: [PATCH 04/11] tweaks --- code/datums/autocells/explosion.dm | 33 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/code/datums/autocells/explosion.dm b/code/datums/autocells/explosion.dm index 5cf78935cda7..63d3cca21189 100644 --- a/code/datums/autocells/explosion.dm +++ b/code/datums/autocells/explosion.dm @@ -82,7 +82,7 @@ /datum/automata_cell/explosion/propagate(dir) var/datum/automata_cell/explosion/new_cell = ..() - new_cell?.exploded_atoms |= exploded_atoms + new_cell?.exploded_atoms += exploded_atoms return new_cell // Compare directions. If the other explosion is traveling in the same direction, @@ -151,19 +151,19 @@ return // The resistance here will affect the damage taken and the falloff in the propagated explosion var/resistance = max(0, in_turf.get_explosion_resistance(direction)) - for(var/atom/A in in_turf) - resistance += max(0, A.get_explosion_resistance()) + for(var/atom/thing in in_turf) + resistance += max(0, thing.get_explosion_resistance()) // Blow stuff up INVOKE_ASYNC(in_turf, TYPE_PROC_REF(/atom, ex_act), power, direction, explosion_cause_data, 0, enviro) - for(var/atom/A in in_turf) - if(A in exploded_atoms) + for(var/atom/thing in in_turf) + if(thing.gc_destroyed) continue - if(A.gc_destroyed) + if(thing in exploded_atoms) continue - INVOKE_ASYNC(A, TYPE_PROC_REF(/atom, ex_act), power, direction, explosion_cause_data, 0, enviro) - exploded_atoms += A - log_explosion(A, src) + exploded_atoms += thing + INVOKE_ASYNC(thing, TYPE_PROC_REF(/atom, ex_act), power, direction, explosion_cause_data, 0, enviro) + log_explosion(thing, src) var/reflected = FALSE @@ -237,21 +237,20 @@ When the cell processes, we simply don't blow up atoms that were tracked as having entered the turf. */ -/datum/automata_cell/explosion/proc/on_turf_entered(atom/movable/A) +/datum/automata_cell/explosion/proc/on_turf_entered(atom/movable/thing) // Once is enough - if(A in exploded_atoms) + if(thing.gc_destroyed) + return + if(thing in exploded_atoms) return - exploded_atoms += A + exploded_atoms += thing // Note that we don't want to make it a directed ex_act because // it could toss them back and make them get hit by the explosion again // so we indicate this with a direction < 0 - if(A.gc_destroyed) - return - - INVOKE_ASYNC(A, TYPE_PROC_REF(/atom, ex_act), power, -1, explosion_cause_data, 0, enviro) - log_explosion(A, src) + INVOKE_ASYNC(thing, TYPE_PROC_REF(/atom, ex_act), power, -1, explosion_cause_data, 0, enviro) + log_explosion(thing, src) // I'll admit most of the code from here on out is basically just copypasta from DOREC From 0ec0e771277d0ec255e12120f5d92239e4def929 Mon Sep 17 00:00:00 2001 From: Drulikar Date: Sat, 13 Jun 2026 20:40:41 -0500 Subject: [PATCH 05/11] clear exploded atoms on death --- code/datums/autocells/explosion.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/datums/autocells/explosion.dm b/code/datums/autocells/explosion.dm index 63d3cca21189..aa1e9c2c8a21 100644 --- a/code/datums/autocells/explosion.dm +++ b/code/datums/autocells/explosion.dm @@ -79,6 +79,7 @@ /datum/automata_cell/explosion/death() if(shockwave) qdel(shockwave) + exploded_atoms.Cut() /datum/automata_cell/explosion/propagate(dir) var/datum/automata_cell/explosion/new_cell = ..() From 7389db88436357425168bb936eb94cec211252a6 Mon Sep 17 00:00:00 2001 From: Drulikar Date: Sat, 13 Jun 2026 23:06:24 -0500 Subject: [PATCH 06/11] Don't throw something qdeleted --- code/modules/movement/launching/launching.dm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/modules/movement/launching/launching.dm b/code/modules/movement/launching/launching.dm index b5c177371b9e..2e0e68684ae2 100644 --- a/code/modules/movement/launching/launching.dm +++ b/code/modules/movement/launching/launching.dm @@ -131,6 +131,9 @@ // Proc for throwing items (should only really be used for throw) /atom/movable/proc/throw_atom(atom/target, range, speed = 0, atom/thrower, spin, launch_type = NORMAL_LAUNCH, pass_flags = NO_FLAGS, list/end_throw_callbacks, list/collision_callbacks, tracking = FALSE) + if(QDELETED(src)) + return // Why throw something deleting? + var/temp_pass_flags = pass_flags switch (launch_type) if (NORMAL_LAUNCH) From 791b993d74fc9f2c80e4cd005ed5168594247fd0 Mon Sep 17 00:00:00 2001 From: Drulikar Date: Wed, 24 Jun 2026 20:37:15 -0500 Subject: [PATCH 07/11] cell merge changes and cleanup --- code/datums/autocells/auto_cell.dm | 32 +++++++++++++----------------- code/datums/autocells/explosion.dm | 21 +++++++++++++------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/code/datums/autocells/auto_cell.dm b/code/datums/autocells/auto_cell.dm index fb679c56676e..a08ceba2f6fb 100644 --- a/code/datums/autocells/auto_cell.dm +++ b/code/datums/autocells/auto_cell.dm @@ -21,20 +21,19 @@ // This affects what neighbors you'll get passed in update_state() var/neighbor_type = NEIGHBORS_CARDINAL -/datum/automata_cell/New(turf/T) +/datum/automata_cell/New(turf/location) ..() - if(!istype(T)) + if(!isturf(location)) qdel(src) return - // Attempt to merge the two cells if they end up in the same turf - var/datum/automata_cell/C = T.get_cell(type) - if(C && merge(C)) - qdel(src) - return + // Attempt to merge two cells if they end up in the same turf + var/datum/automata_cell/existing_cell = location.get_cell(type) + if(!merge(existing_cell)) + return // We didn't survive - in_turf = T + in_turf = location LAZYADD(in_turf.autocells, src) GLOB.cellauto_cells += src @@ -44,9 +43,8 @@ /datum/automata_cell/Destroy() . = ..() - if(!QDELETED(in_turf)) - LAZYREMOVE(in_turf.autocells, src) - in_turf = null + LAZYREMOVE(in_turf?.autocells, src) + in_turf = null GLOB.cellauto_cells -= src @@ -65,17 +63,15 @@ if(QDELETED(new_turf)) return - if(!QDELETED(in_turf)) - LAZYREMOVE(in_turf.autocells, src) - in_turf = null + LAZYREMOVE(in_turf?.autocells, src) in_turf = new_turf LAZYADD(in_turf.autocells, src) -// Use this proc to merge this cell with another one if the other cell enters the same turf -// Return TRUE if this cell should survive the merge (the other one will die/be qdeleted) -// Return FALSE if this cell should die and be replaced by the other cell -/datum/automata_cell/proc/merge(datum/automata_cell/other_cell) +/// Use this proc to merge this cell with another one if the other cell enters the same turf +/// Returns TRUE if this cell should survive the merge (the other one will die/be qdeleted) +/// Returns FALSE if this cell died and is replaced by the other cell +/datum/automata_cell/proc/merge(datum/automata_cell/other) return TRUE // Returns a list of neighboring cells diff --git a/code/datums/autocells/explosion.dm b/code/datums/autocells/explosion.dm index aa1e9c2c8a21..37fc8deaffa9 100644 --- a/code/datums/autocells/explosion.dm +++ b/code/datums/autocells/explosion.dm @@ -86,19 +86,24 @@ new_cell?.exploded_atoms += exploded_atoms return new_cell -// Compare directions. If the other explosion is traveling in the same direction, -// the explosion is amplified. If not, it's weakened -/datum/automata_cell/explosion/merge(datum/automata_cell/explosion/E) +// Attempts to merge explosions. Will compare directions to determine effects on power. +// If the other explosion is traveling in the same direction, the explosion is amplified. +// If not, it's weakened +// Returns TRUE if this explosion survived. +/datum/automata_cell/explosion/merge(datum/automata_cell/explosion/other) + if(QDELETED(other)) + return TRUE + // Non-merging explosions take priority - if(!should_merge) + if(!should_merge || !other.should_merge) return TRUE // The strongest of the two explosions should survive the merge // This prevents a weaker explosion merging with a strong one, // the strong one removing all the weaker one's power and just killing the explosion - var/is_stronger = (power >= E.power) - var/datum/automata_cell/explosion/survivor = is_stronger ? src : E - var/datum/automata_cell/explosion/dying = is_stronger ? E : src + var/is_stronger = (power >= other.power) + var/datum/automata_cell/explosion/survivor = is_stronger ? src : other + var/datum/automata_cell/explosion/dying = is_stronger ? other : src // Two epicenters merging, or a new epicenter merging with a traveling wave if((!survivor.direction && !dying.direction) || (survivor.direction && !dying.direction)) @@ -121,6 +126,8 @@ if(survivor.direction == GLOB.reverse_dir[dying.direction]) survivor.power -= dying.power + qdel(dying) + return is_stronger // Get a list of all directions the explosion should propagate to before dying From 840d52e3867cf96ea1e758006692bac5a55e88ba Mon Sep 17 00:00:00 2001 From: Drulikar Date: Wed, 24 Jun 2026 20:39:12 -0500 Subject: [PATCH 08/11] more -1 dir handling --- code/game/objects/explosion_recursive.dm | 5 ++++- code/modules/mob/living/carbon/human/human.dm | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/code/game/objects/explosion_recursive.dm b/code/game/objects/explosion_recursive.dm index 06e6552b592a..fc86e15e0595 100644 --- a/code/game/objects/explosion_recursive.dm +++ b/code/game/objects/explosion_recursive.dm @@ -356,9 +356,12 @@ explosion resistance exactly as much as their health if(anchored) return - if(!istype(src.loc, /turf)) + if(!isturf(loc)) return + if(direction < 0) + return // Don't do anything if explicitly directionless + var/weight = 1 switch(mob_size) if(MOB_SIZE_SMALL) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 78c4e9b08082..36469a51068b 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -145,7 +145,7 @@ . += "Self Destruct Status: [SShijack.get_sd_eta()]" /mob/living/carbon/human/ex_act(severity, direction, datum/cause_data/cause_data, pierce=0, enviro=FALSE) - if(body_position == LYING_DOWN && direction) + if(body_position == LYING_DOWN && direction > 0) severity *= EXPLOSION_PRONE_MULTIPLIER var/b_loss = 0 From 6f1829ca94a0e701e7a40940305f687cd102bed3 Mon Sep 17 00:00:00 2001 From: Drulikar Date: Wed, 24 Jun 2026 20:40:15 -0500 Subject: [PATCH 09/11] Attempts to delay turf sideeffects & misc refactoring --- .../game/machinery/medical_pod/bodyscanner.dm | 4 +- .../machinery/vending/vendor_types/medical.dm | 2 +- code/game/turfs/closed.dm | 2 +- code/game/turfs/open.dm | 39 ++++++++++--------- code/game/turfs/open_space.dm | 7 +++- code/game/turfs/transit.dm | 6 ++- code/game/turfs/turf.dm | 25 ++++++------ 7 files changed, 47 insertions(+), 38 deletions(-) diff --git a/code/game/machinery/medical_pod/bodyscanner.dm b/code/game/machinery/medical_pod/bodyscanner.dm index cede827c9dfa..d0ac201afb86 100644 --- a/code/game/machinery/medical_pod/bodyscanner.dm +++ b/code/game/machinery/medical_pod/bodyscanner.dm @@ -46,10 +46,10 @@ return go_out() -/obj/structure/machinery/medical_pod/bodyscanner/ex_act(severity, datum/cause_data/cause_data) +/obj/structure/machinery/medical_pod/bodyscanner/ex_act(severity, direction, datum/cause_data/cause_data) for(var/atom/movable/A as mob|obj in src) A.forceMove(loc) - A.ex_act(severity, , cause_data) + A.ex_act(severity, cause_data=cause_data) switch(severity) if(0 to EXPLOSION_THRESHOLD_LOW) if (prob(25)) diff --git a/code/game/machinery/vending/vendor_types/medical.dm b/code/game/machinery/vending/vendor_types/medical.dm index c502bbf875c5..9ac095133559 100644 --- a/code/game/machinery/vending/vendor_types/medical.dm +++ b/code/game/machinery/vending/vendor_types/medical.dm @@ -15,7 +15,7 @@ unacidable = TRUE /obj/structure/medical_supply_link/ex_act(severity, direction) - return FALSE + return /obj/structure/medical_supply_link/Initialize() . = ..() diff --git a/code/game/turfs/closed.dm b/code/game/turfs/closed.dm index 5aca4ac781ab..99a8ea9dd997 100644 --- a/code/game/turfs/closed.dm +++ b/code/game/turfs/closed.dm @@ -88,7 +88,7 @@ thing.forceMove(above_current) return -/turf/closed/Enter(atom/movable/mover, atom/forget) +/turf/closed/Enter(atom/movable/mover, atom/old_loc) . = ..() if(!mover.move_intentionally || !istype(mover,/mob/living)) return diff --git a/code/game/turfs/open.dm b/code/game/turfs/open.dm index 9cbfae93835e..f66560e3fd82 100644 --- a/code/game/turfs/open.dm +++ b/code/game/turfs/open.dm @@ -165,12 +165,12 @@ icon_state = "grass1" is_weedable = NOT_WEEDABLE -/turf/open/slippery/Enter(atom/movable/mover, atom/forget) +/turf/open/slippery/Enter(atom/movable/mover, atom/old_loc) . = ..() if(isliving(mover)) return FALSE -/turf/open/slippery/Entered(atom/movable/crosser) +/turf/open/slippery/Entered(atom/movable/crosser, atom/old_loc) . = ..() if(isobserver(crosser) || crosser.anchored) return @@ -178,7 +178,10 @@ if(!(isitem(crosser) || isliving(crosser))) return - INVOKE_ASYNC(crosser, TYPE_PROC_REF(/atom/movable, throw_atom), (get_step(src, dir)), 50, SPEED_FAST, null, TRUE) + if(old_loc != src) + INVOKE_ASYNC(crosser, TYPE_PROC_REF(/atom/movable, throw_atom), (get_step(src, dir)), 50, SPEED_FAST, null, TRUE) + else + INVOKE_NEXT_TICK(crosser, TYPE_PROC_REF(/atom/movable, throw_atom), (get_step(src, dir)), 50, SPEED_FAST, null, TRUE) /turf/open/slippery/hull name = "sloped roof" @@ -853,28 +856,28 @@ default_name = "deep ocean" allow_construction = FALSE -/turf/open/gm/river/ocean/Entered(atom/movable/AM) +/turf/open/gm/river/ocean/Entered(atom/movable/entered_movable, atom/old_loc) . = ..() - if(prob(20)) // fuck you - if(!ismob(AM)) + if(old_loc != src && prob(20)) // fuck you + if(!ismob(entered_movable)) return - var/mob/unlucky_mob = AM - var/turf/target_turf = get_random_turf_in_range(AM, 3, 0) - var/datum/launch_metadata/LM = new() - LM.target = target_turf - LM.range = get_dist(AM.loc, target_turf) - LM.speed = SPEED_FAST - LM.thrower = unlucky_mob - LM.spin = TRUE - LM.pass_flags = NO_FLAGS + var/mob/unlucky_mob = entered_movable + var/turf/target_turf = get_random_turf_in_range(entered_movable, 3, 0) + var/datum/launch_metadata/launch = new() + launch.target = target_turf + launch.range = get_dist(entered_movable.loc, target_turf) + launch.speed = SPEED_FAST + launch.thrower = unlucky_mob + launch.spin = TRUE + launch.pass_flags = NO_FLAGS to_chat(unlucky_mob, SPAN_WARNING("The ocean currents sweep you off your feet and throw you away!")) // Entered can occur during Initialize so we need to not sleep - INVOKE_ASYNC(unlucky_mob, TYPE_PROC_REF(/atom/movable, launch_towards), LM) + INVOKE_ASYNC(unlucky_mob, TYPE_PROC_REF(/atom/movable, launch_towards), launch) return if(world.time % 5) - if(ismob(AM)) - var/mob/rivermob = AM + if(ismob(entered_movable)) + var/mob/rivermob = entered_movable if(!HAS_TRAIT(rivermob, TRAIT_HAULED)) to_chat(rivermob, SPAN_WARNING("Moving through the incredibly deep ocean slows you down a lot!")) diff --git a/code/game/turfs/open_space.dm b/code/game/turfs/open_space.dm index dfbffbc77c2d..3b7c3991514f 100644 --- a/code/game/turfs/open_space.dm +++ b/code/game/turfs/open_space.dm @@ -29,7 +29,7 @@ GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdr ADD_TRAIT(src, TURF_Z_TRANSPARENT_TRAIT, TRAIT_SOURCE_INHERENT) return INITIALIZE_HINT_LATELOAD -/turf/open_space/Enter(atom/movable/mover, atom/forget) +/turf/open_space/Enter(atom/movable/mover, atom/old_loc) . = ..() if(. && !mover.throwing && isliving(mover) && check_blocked()) to_chat(mover, SPAN_WARNING("It would be too dangerous to go that way.")) @@ -37,7 +37,10 @@ GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdr /turf/open_space/Entered(atom/movable/entered_movable, atom/old_loc) . = ..() - check_fall(entered_movable) + if(old_loc != src) + check_fall(entered_movable) + else + INVOKE_NEXT_TICK(src, PROC_REF(check_fall), entered_movable) /turf/open_space/on_throw_end(atom/movable/thrown_atom) check_fall(thrown_atom) diff --git a/code/game/turfs/transit.dm b/code/game/turfs/transit.dm index 95a2e390b201..270d67ebc525 100644 --- a/code/game/turfs/transit.dm +++ b/code/game/turfs/transit.dm @@ -17,8 +17,10 @@ if(!istype(old_loc, /turf/open/space)) var/turf/projected = get_ranged_target_turf(crosser, dir, 10) - INVOKE_ASYNC(crosser, TYPE_PROC_REF(/atom/movable, throw_atom), projected, 50, SPEED_FAST, null, TRUE) - + if(old_loc != src) + INVOKE_ASYNC(crosser, TYPE_PROC_REF(/atom/movable, throw_atom), projected, 50, SPEED_FAST, null, TRUE) + else + INVOKE_NEXT_TICK(crosser, TYPE_PROC_REF(/atom/movable, throw_atom), projected, 50, SPEED_FAST, null, TRUE) addtimer(CALLBACK(src, PROC_REF(handle_crosser), crosser), 0.5 SECONDS) /turf/open/space/transit/proc/handle_crosser(atom/movable/crosser) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index f9ab30019ca1..3864d368c8ac 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -246,8 +246,8 @@ return // Handles whether an atom is able to enter the src turf -/turf/Enter(atom/movable/mover, atom/forget) - if (!mover || !isturf(mover.loc)) +/turf/Enter(atom/movable/mover, atom/old_loc) + if(QDELETED(mover) || !isturf(mover.loc)) return FALSE var/override = SEND_SIGNAL(mover, COMSIG_MOVABLE_TURF_ENTER, src) @@ -277,7 +277,7 @@ mover.Collide(T) return FALSE for (obstacle in T) //First, check objects to block exit - if (mover == obstacle || forget == obstacle) + if (mover == obstacle || old_loc == obstacle) continue A = obstacle if (!istype(A) || !A.can_block_movement) @@ -298,7 +298,7 @@ mover.Collide(T) return FALSE for(obstacle in T) - if(forget == obstacle) + if(old_loc == obstacle) continue A = obstacle if (!istype(A) || !A.can_block_movement) @@ -318,7 +318,7 @@ mover.Collide(T) return FALSE for(obstacle in T) - if(forget == obstacle) + if(old_loc == obstacle) continue A = obstacle if (!istype(A) || !A.can_block_movement) @@ -336,7 +336,7 @@ mover.Collide(src) return FALSE for(obstacle in src) //Then, check atoms in the target turf - if(forget == obstacle) + if(old_loc == obstacle) continue A = obstacle if (!istype(A) || !A.can_block_movement) @@ -357,16 +357,17 @@ return TRUE //Nothing found to block so return success! -/turf/Entered(atom/movable/A) - if(!istype(A)) +/turf/Entered(atom/movable/entered_movable, atom/old_loc) + if(QDELETED(entered_movable)) return - SEND_SIGNAL(src, COMSIG_TURF_ENTERED, A) - SEND_SIGNAL(A, COMSIG_MOVABLE_TURF_ENTERED, src) + SEND_SIGNAL(src, COMSIG_TURF_ENTERED, entered_movable) + SEND_SIGNAL(entered_movable, COMSIG_MOVABLE_TURF_ENTERED, src) // Let explosions know that the atom entered - for(var/datum/automata_cell/explosion/E in autocells) - E.on_turf_entered(A) + if(old_loc != src) + for(var/datum/automata_cell/explosion/cell in autocells) + cell.on_turf_entered(entered_movable) /turf/proc/is_plating() return 0 From f0a704a0ef5ac6a8d9dfc6ce0de73fa865820a34 Mon Sep 17 00:00:00 2001 From: Drulikar Date: Wed, 24 Jun 2026 20:41:02 -0500 Subject: [PATCH 10/11] Signal simplification/fixes (I really don't think we should try to retain signals for turfs) --- code/datums/components/_component.dm | 6 +++--- code/game/turfs/turf.dm | 21 +++++++++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm index 185a540e7ad3..2be44825bc09 100644 --- a/code/datums/components/_component.dm +++ b/code/datums/components/_component.dm @@ -197,10 +197,10 @@ else if(lookup[sig_type] == src) // We already registered here continue else if(!length(lookup[sig_type])) // One other thing registered here - lookup[sig_type] = list(lookup[sig_type]=TRUE) - lookup[sig_type][src] = TRUE + lookup[sig_type] = list(lookup[sig_type]) + lookup[sig_type] += src else // Many other things have registered here - lookup[sig_type][src] = TRUE + lookup[sig_type] += src signal_enabled = TRUE diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 3864d368c8ac..5191b4f2c4ee 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -500,17 +500,30 @@ changing_turf = TRUE qdel(src) //Just get the side effects and call Destroy // Get signal registrations post-Destroy so stuff that's unregistered on Destroy won't be readded - var/list/old_comp_lookup = comp_lookup?.Copy() - var/list/old_signal_procs = signal_procs?.Copy() + var/list/old_comp_lookup = comp_lookup + var/list/old_signal_procs = signal_procs var/turf/W = new path(src) // WARNING WARNING // Turfs DO NOT lose their signals when they get replaced, REMEMBER THIS // It's possible because turfs are fucked, and if you have one in a list and it's replaced with another one, the list ref points to the new turf + // Enjoy some dumb code to copy signals that you hope didn't change from then to the new turf being New'd if(old_comp_lookup) - LAZYOR(W.comp_lookup, old_comp_lookup) + for(var/key in old_comp_lookup) + var/old_lookup = old_comp_lookup[key] + var/list/old_items = islist(old_lookup) ? old_lookup : list(old_lookup) + var/new_lookup = LAZYACCESS(W.comp_lookup, key) + var/list/new_items = islist(new_lookup) ? new_lookup : !isnull(new_lookup) ? list(new_lookup) : list() + var/list/combined_list = old_items + new_items + LAZYSET(W.comp_lookup, key, length(combined_list) == 1 ? combined_list[1] : combined_list) if(old_signal_procs) - LAZYOR(W.signal_procs, old_signal_procs) + for(var/key in old_signal_procs) + var/old_lookup = old_signal_procs[key] + var/list/old_items = islist(old_lookup) ? old_lookup : list() + var/new_lookup = LAZYACCESS(W.signal_procs, key) + var/list/new_items = islist(new_lookup) ? new_lookup : list() + var/list/combined_list = old_items + new_items + LAZYSET(W.signal_procs, key, combined_list) W.weak_reference = old_ref From 7e23ca7308dee861240465d5de8fd49e1443972f Mon Sep 17 00:00:00 2001 From: Drulikar Date: Wed, 24 Jun 2026 20:44:29 -0500 Subject: [PATCH 11/11] Debug testing (Revert this) --- code/__HELPERS/unsorted.dm | 1 + code/datums/components/weed_food.dm | 4 ++ code/game/objects/explosion_recursive.dm | 48 ++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index f562f6b913c4..364e06aecb1c 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1588,6 +1588,7 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new) //gives us the stack trace from CRASH() without ending the current proc. /proc/stack_trace(msg) + message_admins(msg) CRASH(msg) // \ref behaviour got changed in 512 so this is necesary to replicate old behaviour. diff --git a/code/datums/components/weed_food.dm b/code/datums/components/weed_food.dm index e2f0d0b46a63..cb5aa6527640 100644 --- a/code/datums/components/weed_food.dm +++ b/code/datums/components/weed_food.dm @@ -93,6 +93,7 @@ RegisterSignal(parent_mob, COMSIG_LIVING_PREIGNITION, PROC_REF(on_preignition)) RegisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING, PROC_REF(on_forsaken)) if(parent_turf) + message_admins("[\ref(src)] at [COORD(parent_mob)] RegisterWithParent: Registered [parent_turf] [\ref(parent_turf)] at [COORD(parent_turf)]") RegisterSignal(parent_turf, COMSIG_WEEDNODE_GROWTH, PROC_REF(on_update)) /datum/component/weed_food/UnregisterFromParent() @@ -108,6 +109,7 @@ if(absorbing_weeds) UnregisterSignal(absorbing_weeds, COMSIG_PARENT_QDELETING) if(parent_turf) + message_admins("[\ref(src)] at [COORD(parent_mob)] UnregisterFromParent: Unregistered [parent_turf] [\ref(parent_turf)] at [COORD(parent_turf)]") UnregisterSignal(parent_turf, COMSIG_WEEDNODE_GROWTH) if(parent_buckle) UnregisterSignal(parent_buckle, COMSIG_OBJ_AFTER_BUCKLE) @@ -124,11 +126,13 @@ absorbing_weeds = null if(parent_turf) + message_admins("[\ref(src)] at [COORD(parent_mob)] on_move: Unregistered [parent_turf] [\ref(parent_turf)] at [COORD(parent_turf)]") UnregisterSignal(parent_turf, COMSIG_WEEDNODE_GROWTH) parent_turf = get_turf(parent_mob) if(parent_turf != parent_mob.loc) parent_turf = null // if our location is actually a container, we want to be safe from weeds else + message_admins("[\ref(src)] at [COORD(parent_mob)] on_move: Registered [parent_turf] [\ref(parent_turf)] at [COORD(parent_turf)]") RegisterSignal(parent_turf, COMSIG_WEEDNODE_GROWTH, PROC_REF(on_update)) // We moved, restart or start the proccess diff --git a/code/game/objects/explosion_recursive.dm b/code/game/objects/explosion_recursive.dm index fc86e15e0595..a3265b17d9dd 100644 --- a/code/game/objects/explosion_recursive.dm +++ b/code/game/objects/explosion_recursive.dm @@ -26,6 +26,54 @@ For explosion resistance, an explosion should never go through a wall or window explosion resistance exactly as much as their health */ +GLOBAL_VAR_INIT(create_and_destroy_ignore_paths2, generate_ignore_paths2()) +/proc/generate_ignore_paths2() + . = list( + //Never meant to be created, errors out the ass for mobcode reasons + /mob/living/carbon, + /obj/effect/node, + /obj/item/seeds/cutting, + //lighting singleton + /mob/dview, + // These use walk_away() after initialization, which causes false positives + /obj/item/explosive/grenade/flashbang/cluster/segment, + /obj/item/explosive/grenade/flashbang/cluster_piece, + /mob/living/simple_animal/hostile/retaliate/giant_lizard, + /obj/effect/landmark/lizard_spawn, + /obj/effect/fake_attacker, + /atom/movable/lighting_mask, //leave it alone + //This is meant to fail extremely loud every single time it occurs in any environment in any context, and it falsely alarms when this unit test iterates it. Let's not spawn it in. + /obj/merge_conflict_marker, + /obj/effect/projector_anchor, // Needs a link ID set to work as intended + /obj/effect/projector/linked, // Needs a link ID set to work as intended + ) + //This turf existing is an error in and of itself + . += typesof(/turf/baseturf_skipover) + . += typesof(/turf/baseturf_bottom) + //Our system doesn't support it without warning spam from unregister calls on things that never registered + . += typesof(/obj/docking_port) + . += typesof(/obj/item/storage/internal) + // fuck interiors + . += typesof(/obj/vehicle) + . += typesof(/obj/effect/vehicle_spawner) + // Always ought to have an associated escape menu. Any references it could possibly hold would need one regardless. + . += subtypesof(/atom/movable/screen/escape_menu) + . += typesof(/obj/effect/timed_event) + // Need a defined ID, mapping-only, will and should fail loudly if spawned without one + . += typesof(/obj/effect/landmark/dispersal_initiator) + +/mob/verb/explosion_test() + set name = "Explosion Test" + set category = "Debug" + + var/turf/location = get_turf(usr) + var/mob/living/carbon/human/body = new(location) + body.death() + cell_explosion(location, 60, 20, EXPLOSION_FALLOFF_SHAPE_LINEAR, SOUTH, create_cause_data("testing")) + cell_explosion(location, 60, 20, EXPLOSION_FALLOFF_SHAPE_LINEAR, SOUTH, create_cause_data("testing")) + for(var/turf/turf_path as anything in subtypesof(/turf) - GLOB.create_and_destroy_ignore_paths2) + location = location.ChangeTurf(turf_path) + /proc/explosion_rec(turf/epicenter, power, falloff = 20, datum/cause_data/explosion_cause_data) var/obj/effect/explosion/Controller = new /obj/effect/explosion(epicenter) Controller.initiate_explosion(epicenter, power, falloff, explosion_cause_data)