From 582d213b8d6d79becdf09220edc3661f931b3f87 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Sun, 3 May 2026 13:07:44 -0400 Subject: [PATCH] Some Misc Bugfixes (#22225) This PR fixes a few bugs that were recently reported both by players and by our new bug monitoring system. An AI agent (specifically Gemini 3) was used to do some of the code diving process, while the fixes were tested and verified myself manually. Yes I have actually verified the fixes in this PR in testing. The swap to Calculus methods for organ EMP was done by me, no AI. I love Calculus to much to let a bot do it for me. In particular my fix to organ EMP was to make it so that EMP damage is handled directly as "number of fractional seconds the EMP will last", and that they tick down cleanly per second. I did this because after fixing mechanical organs not doing ANY EMP effects at all, I tested them and discovered that the EMP effects were extremely underwhelming. They were like, "Your screen only briefly filling with static for like 2 ticks", and "your heart skips a single beat", which was lame and nonthreatening. Significantly more fun is making the static last for N number of seconds, and for the Heart to fail to pump for N number of seconds (very life threatening!) The new organ EMP handling vars are per-organ rather than a define, so if we want there to be different EMP times for hearts vs eyes, that's totally doable. Just make the heart have a larger recovery rate, or the eyes have a smaller recovery rate. --------- Signed-off-by: VMSolidus Co-authored-by: Arrow768 <1331699+Arrow768@users.noreply.github.com> --- code/datums/beam.dm | 16 +++-- code/modules/organs/internal/appendix.dm | 5 +- code/modules/organs/organ.dm | 61 ++++++++++--------- code/modules/organs/pain.dm | 3 +- code/modules/projectiles/gun.dm | 6 +- code/modules/projectiles/projectile.dm | 5 +- .../hellfirejag-misc-guard-fixes.yml | 7 +++ 7 files changed, 63 insertions(+), 40 deletions(-) create mode 100644 html/changelogs/hellfirejag-misc-guard-fixes.yml diff --git a/code/datums/beam.dm b/code/datums/beam.dm index 7d57dd38e10..fdeedb47804 100644 --- a/code/datums/beam.dm +++ b/code/datums/beam.dm @@ -38,12 +38,15 @@ /datum/beam/proc/Start() recalculate() - recalculate_in(sleep_time) /datum/beam/proc/recalculate() + if(QDELETED(src)) + return + if(recalculating) recalculate_in(sleep_time) return + recalculating = TRUE timing_id = null var/turf/origin_turf = get_turf(origin) @@ -67,10 +70,12 @@ return /datum/beam/proc/recalculate_in(time) + if(QDELETED(src)) + return timing_id = addtimer(CALLBACK(src, PROC_REF(recalculate)), time, TIMER_STOPPABLE | TIMER_UNIQUE | TIMER_NO_HASH_WAIT | TIMER_OVERRIDE) /datum/beam/proc/after_calculate() - if((sleep_time == null) || finished) //Does not automatically recalculate. + if(QDELETED(src) || (sleep_time == null) || finished) //Does not automatically recalculate. return timing_id = addtimer(CALLBACK(src, PROC_REF(recalculate)), sleep_time, TIMER_STOPPABLE | TIMER_UNIQUE | TIMER_NO_HASH_WAIT) @@ -82,16 +87,17 @@ qdel(src) /datum/beam/proc/Reset() - for(var/obj/effect/ebeam/B in elements) - qdel(B) - elements.Cut() + QDEL_LIST(elements) /datum/beam/Destroy() if(timing_id) deltimer(timing_id) + timing_id = null Reset() target = null origin = null + target_oldloc = null + origin_oldloc = null return ..() /datum/beam/proc/Draw() diff --git a/code/modules/organs/internal/appendix.dm b/code/modules/organs/internal/appendix.dm index 62999b8f36e..b2114eaa2ee 100644 --- a/code/modules/organs/internal/appendix.dm +++ b/code/modules/organs/internal/appendix.dm @@ -65,8 +65,9 @@ owner.Weaken(10) var/obj/item/organ/external/E = owner.get_organ(parent_organ) - E.sever_artery() - E.germ_level = max(INFECTION_LEVEL_TWO, E.germ_level) + if(E) + E.sever_artery() + E.germ_level = max(INFECTION_LEVEL_TWO, E.germ_level) owner.adjustToxLoss(25) removed() qdel(src) diff --git a/code/modules/organs/organ.dm b/code/modules/organs/organ.dm index ffafa6d7c93..f50b7f27083 100644 --- a/code/modules/organs/organ.dm +++ b/code/modules/organs/organ.dm @@ -17,8 +17,14 @@ //Organ damage stats. var/damage = 0 // amount of damage to the organ - var/surge_damage = 0 // EMP damage counter. - var/surge_time = 0 + /// Total amount of EMP damage a mechanical organ has taken. Effectively equal to "number of seconds the organ EMP effect will last". + var/surge_damage = 0.0 + /** + * The amount of EMP damage a mechanical organ will recover per second. + * Fractional and floating points are allowed, but it shouldn't ever be negative. + */ + var/surge_recovery_per_second = 1.0 + var/min_broken_damage = 30 var/min_bruised_damage = 10 // Damage before considered bruised var/max_damage = 30 @@ -65,7 +71,7 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) LOG_DEBUG("[src] at [loc] spawned without a proper DNA.") var/mob/living/carbon/human/H = holder if(istype(H)) - if(internal) + if(internal && parent_organ) var/obj/item/organ/external/E = H.get_organ(parent_organ) if(E) if(E.internal_organs == null) @@ -158,52 +164,52 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) if(istype(loc,/obj/structure/closet/body_bag/cryobag) || istype(loc,/obj/structure/closet/crate/freezer) || istype(loc,/obj/item/storage/box/unique/freezer)) return //Process infections - if ((status & ORGAN_ROBOT) || (owner && owner.species && (owner.species.flags & IS_PLANT))) + var/is_immune = ((status & ORGAN_ROBOT) || robotic >= ROBOTIC_MECHANICAL || (owner && owner.species && (owner.species.flags & IS_PLANT))) + if (is_immune) germ_level = 0 - return - if(BP_IS_ROBOTIC(src) && surge_damage) - tick_surge_damage() + if((BP_IS_ROBOTIC(src) || robotic >= ROBOTIC_MECHANICAL) && surge_damage) + tick_surge_damage(seconds_per_tick) if(!owner) if (QDELETED(reagents)) LOG_DEBUG("Organ [DEBUG_REF(src)] had QDELETED reagents! Regenerating.") create_reagents(5) - if(REAGENT_VOLUME(reagents, /singleton/reagent/blood) && !(status & ORGAN_ROBOT) && prob(40)) + if(REAGENT_VOLUME(reagents, /singleton/reagent/blood) && !is_immune && prob(40)) reagents.remove_reagent(/singleton/reagent/blood,0.1) if (isturf(loc)) blood_splatter(src,src,TRUE) if(GLOB.config.organs_decay) damage += rand(1,3) if(damage >= max_damage) damage = max_damage - germ_level += rand(2,6) - if(germ_level >= INFECTION_LEVEL_TWO) + if(!is_immune) germ_level += rand(2,6) - if(germ_level >= INFECTION_LEVEL_THREE) - die() + if(germ_level >= INFECTION_LEVEL_TWO) + germ_level += rand(2,6) + if(germ_level >= INFECTION_LEVEL_THREE) + die() else if(owner.bodytemperature >= 170) //cryo stops germs from moving and doing their bad stuffs //** Handle antibiotics and curing infections handle_antibiotics() - handle_immunosuppressants() - handle_rejection() - handle_germ_effects() + if(!is_immune) + handle_immunosuppressants() + handle_rejection() + handle_germ_effects() //check if we've hit max_damage if(damage >= max_damage) die() -/obj/item/organ/proc/tick_surge_damage() - if(surge_damage) - do_surge_effects() - if(surge_time + 1 SECOND < world.time) - surge_damage = max(0, surge_damage - 10) - surge_time = world.time - if(!surge_damage) - surge_time = 0 - clear_surge_effects() +/obj/item/organ/proc/tick_surge_damage(seconds_per_tick) + if(!surge_damage) + clear_surge_effects() + return + + do_surge_effects() + surge_damage = max(0, surge_damage - (surge_recovery_per_second * seconds_per_tick)) /obj/item/organ/proc/do_surge_effects() return @@ -232,10 +238,10 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) if(antibiotics < 5 && prob(round(germ_level/7))) germ_level++ - if (germ_level >= INFECTION_LEVEL_TWO) + if (germ_level >= INFECTION_LEVEL_TWO && parent_organ) var/obj/item/organ/external/parent = owner.get_organ(parent_organ) //spread germs - if (antibiotics < 5 && parent.germ_level < germ_level && ( parent.germ_level < INFECTION_LEVEL_ONE*2 || prob(30) )) + if (parent && antibiotics < 5 && parent.germ_level < germ_level && ( parent.germ_level < INFECTION_LEVEL_ONE*2 || prob(30) )) parent.germ_level++ if (prob(3)) //about once every 30 seconds @@ -423,7 +429,6 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) return //We check earlier, but just to make sure. surge_damage = clamp(0, surge + surge_damage, MAXIMUM_SURGE_DAMAGE) //We want X seconds at most of hampered movement or what have you. - surge_time = world.time /** * Remove an organ @@ -443,7 +448,7 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) owner.internal_organs_by_name -= null owner.internal_organs -= src - if(detach) + if(detach && parent_organ) var/obj/item/organ/external/affected = owner.get_organ(parent_organ) if(affected) affected.internal_organs -= src diff --git a/code/modules/organs/pain.dm b/code/modules/organs/pain.dm index 84425782a51..e9171af43c5 100644 --- a/code/modules/organs/pain.dm +++ b/code/modules/organs/pain.dm @@ -86,8 +86,9 @@ // Damage to internal organs hurts a lot. for(var/obj/item/organ/internal/I in internal_organs) - if(prob(1) && !((I.status & ORGAN_DEAD) || BP_IS_ROBOTIC(I)) && I.damage > 5) + if(prob(1) && !((I.status & ORGAN_DEAD) || BP_IS_ROBOTIC(I)) && I.damage > 5 && I.parent_organ) var/obj/item/organ/external/parent = get_organ(I.parent_organ) + if (!parent) continue var/pain = 10 var/message = I.unknown_pain_location ? "You feel a dull pain in your [parent.name]..." : "You feel a dull pain radiating from your [I.name]..." if(I.is_bruised()) diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 3928dcc9014..c72e142781d 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -519,7 +519,8 @@ ABSTRACT_TYPE(/obj/item/gun) P.suppressed = suppressed - P.preparePixelProjectile(target, get_turf(src)) + if(!P.preparePixelProjectile(target, get_turf(src))) + return FALSE P.fired_from = src P.fire() @@ -680,7 +681,8 @@ ABSTRACT_TYPE(/obj/item/gun) else if(mob.shock_stage > 70) added_spread = 15 - P.preparePixelProjectile(target, src, deviation = added_spread) + if(!P.preparePixelProjectile(target, src, params, added_spread)) + return FALSE P.firer = user P.fired_from = src P.def_zone = target_zone diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 62c7bbab89f..07fe18585a0 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -764,6 +764,8 @@ pixel_move(pixel_speed_multiplier, FALSE) /obj/projectile/proc/fire(angle, atom/direct_target) + if(QDELETED(src)) + return LAZYINITLIST(impacted) if(fired_from) SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original) @@ -984,8 +986,7 @@ */ /obj/projectile/proc/preparePixelProjectile(atom/target, atom/source, list/modifiers = null, deviation = 0) if(!(isnull(modifiers) || islist(modifiers))) - stack_trace("WARNING: Projectile [type] fired with non-list modifiers, likely was passed click params.") - modifiers = null + modifiers = params2list(modifiers) var/turf/source_loc = get_turf(source) var/turf/target_loc = get_turf(target) diff --git a/html/changelogs/hellfirejag-misc-guard-fixes.yml b/html/changelogs/hellfirejag-misc-guard-fixes.yml new file mode 100644 index 00000000000..46a7d7fb9a6 --- /dev/null +++ b/html/changelogs/hellfirejag-misc-guard-fixes.yml @@ -0,0 +1,7 @@ +author: Hellfirejag +delete-after: True +changes: + - bugfix: "Fixed mechanical organs and augments getting infections and never recovering correctly from EMP damage." + - bugfix: "Fixed some runtime errors with projectiles and firearms." + - bugfix: "Fixed some runtime errors with Beams." + - bugfix: "Fixed Mechanical Organs EMP effects being extremely underwhelming and short lived. An EMP grenade will disable mechanical organs for 30 seconds."