From d4afce8b4bbad89326277e0be7bb9f063b4e3bc6 Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Sat, 7 Sep 2024 21:01:08 -0400 Subject: [PATCH] Fixes a race condition in reagent metabolism (#86509) ## About The Pull Request Fixes a race condition that was causing organ damage to occur when it shouldn't be. The species `handle_chemical()` procs as well as the liver version of that same proc get called in `metabolize_reagent()`, and in those procs some toxins like carpotoxin get neutered. Problem was, `metabolize_reagent()` was not being called until after the organ damage calculation was already made using the un-neutered `toxpwr` value. Due to the way things are set up, the only option was to move the liver damage code into `metabolize()`. The code itself is functionally unchanged, it just has been relocated. This was a straightforward process and actually eliminated a for loop (as well as some duplicated code), so things may run a bit faster too. I did my best to keep it legible as possible. Fixes https://github.com/NovaSector/NovaSector/issues/2326 - this is what got me to investigate, the issue doesn't come up as often on TG but it becomes very obvious that something is not right when you have a species' own exotic blood causing organ damage when it's just supposed to be getting removed and added to blood_volume. This fixes that issue and any other similar ones. ## Why It's Good For The Game Species-specific damage immunities and such will now be respected. ## Changelog :cl: fix: fixed a race condition that was causing carpotoxin to cause liver damage to felinids despite being immune /:cl: --------- Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> --- .../reagents/chemistry/holder/mob_life.dm | 44 +++++++++++++++++-- .../surgery/organs/internal/liver/_liver.dm | 35 --------------- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/code/modules/reagents/chemistry/holder/mob_life.dm b/code/modules/reagents/chemistry/holder/mob_life.dm index 03421f15770..f2c0f685dd7 100644 --- a/code/modules/reagents/chemistry/holder/mob_life.dm +++ b/code/modules/reagents/chemistry/holder/mob_life.dm @@ -1,3 +1,8 @@ +#define HAS_SILENT_TOXIN 0 //don't provide a feedback message if this is the only toxin present +#define HAS_NO_TOXIN 1 +#define HAS_PAINFUL_TOXIN 2 +#define MAX_TOXIN_LIVER_DAMAGE 2 //the max damage the liver can receive per second (~1 min at max damage will destroy liver) + /** * Triggers metabolizing for all the reagents in this holder * @@ -16,16 +21,22 @@ var/need_mob_update = FALSE var/obj/item/organ/internal/stomach/belly = owner.get_organ_slot(ORGAN_SLOT_STOMACH) var/obj/item/organ/internal/liver/liver = owner.get_organ_slot(ORGAN_SLOT_LIVER) - var/liver_tolerance + var/liver_tolerance = 0 + var/liver_damage = 0 + var/provide_pain_message + var/amount if(liver) var/liver_health_percent = (liver.maxHealth - liver.damage) / liver.maxHealth liver_tolerance = liver.toxTolerance * liver_health_percent + provide_pain_message = HAS_NO_TOXIN for(var/datum/reagent/reagent as anything in cached_reagents) + var/datum/reagent/toxin/toxin + if(istype(reagent, /datum/reagent/toxin)) + toxin = reagent // skip metabolizing effects for small units of toxins - if(istype(reagent, /datum/reagent/toxin) && liver && !dead) - var/datum/reagent/toxin/toxin = reagent - var/amount = toxin.volume + if(toxin && liver && !dead) + amount = toxin.volume if(belly) amount += belly.reagents.get_reagent_amount(toxin.type) @@ -35,10 +46,35 @@ need_mob_update += metabolize_reagent(owner, reagent, seconds_per_tick, times_fired, can_overdose, liverless, dead) + // If applicable, calculate any toxin-related liver damage + // Note: we have to do this AFTER metabolize_reagent, because we want handle_reagent to run before we make the determination. + // The order is really important unfortunately. + if(toxin && liver && liver.filterToxins && !HAS_TRAIT(owner, TRAIT_TOXINLOVER)) + if(toxin.affected_organ_flags && !(liver.organ_flags & toxin.affected_organ_flags)) //this particular toxin does not affect this type of organ + continue + + // a 15u syringe is a nice baseline to scale lethality by + liver_damage += ((amount/15) * toxin.toxpwr * toxin.liver_damage_multiplier) / liver.liver_resistance + + if(provide_pain_message != HAS_PAINFUL_TOXIN) + provide_pain_message = toxin.silent_toxin ? HAS_SILENT_TOXIN : HAS_PAINFUL_TOXIN + + // if applicable, apply our liver damage and display the accompanying pain message + if(liver_damage) + liver.apply_organ_damage(min(liver_damage * seconds_per_tick , MAX_TOXIN_LIVER_DAMAGE * seconds_per_tick)) + + if(provide_pain_message && liver.damage > 10 && SPT_PROB(liver.damage/6, seconds_per_tick)) //the higher the damage the higher the probability + to_chat(owner, span_warning("You feel a dull pain in your abdomen.")) + if(owner && need_mob_update) //some of the metabolized reagents had effects on the mob that requires some updates. owner.updatehealth() update_total() +#undef HAS_SILENT_TOXIN +#undef HAS_NO_TOXIN +#undef HAS_PAINFUL_TOXIN +#undef MAX_TOXIN_LIVER_DAMAGE + /* * Metabolises a single reagent for a target owner carbon mob. See above. * diff --git a/code/modules/surgery/organs/internal/liver/_liver.dm b/code/modules/surgery/organs/internal/liver/_liver.dm index 3933a9efa59..d71e368bdd0 100755 --- a/code/modules/surgery/organs/internal/liver/_liver.dm +++ b/code/modules/surgery/organs/internal/liver/_liver.dm @@ -1,7 +1,6 @@ #define LIVER_DEFAULT_TOX_TOLERANCE 3 //amount of toxins the liver can filter out #define LIVER_DEFAULT_TOX_RESISTANCE 1 //lower values lower how harmful toxins are to the liver #define LIVER_FAILURE_STAGE_SECONDS 60 //amount of seconds before liver failure reaches a new stage -#define MAX_TOXIN_LIVER_DAMAGE 2 //the max damage the liver can receive per second (~1 min at max damage will destroy liver) /obj/item/organ/internal/liver name = "liver" @@ -124,10 +123,6 @@ continue ADD_TRAIT(replacement, readded_trait, JOB_TRAIT) -#define HAS_SILENT_TOXIN 0 //don't provide a feedback message if this is the only toxin present -#define HAS_NO_TOXIN 1 -#define HAS_PAINFUL_TOXIN 2 - /obj/item/organ/internal/liver/on_life(seconds_per_tick, times_fired) . = ..() //If your liver is failing, then we use the liverless version of metabolize @@ -136,34 +131,8 @@ owner.reagents.metabolize(owner, seconds_per_tick, times_fired, can_overdose = TRUE, liverless = TRUE) return - var/obj/belly = owner.get_organ_slot(ORGAN_SLOT_STOMACH) - var/list/cached_reagents = owner.reagents?.reagent_list - var/liver_damage = 0 - var/provide_pain_message = HAS_NO_TOXIN - - if(filterToxins && !HAS_TRAIT(owner, TRAIT_TOXINLOVER)) - for(var/datum/reagent/toxin/toxin in cached_reagents) - if(toxin.affected_organ_flags && !(organ_flags & toxin.affected_organ_flags)) //this particular toxin does not affect this type of organ - continue - var/amount = toxin.volume - if(belly) - amount += belly.reagents.get_reagent_amount(toxin.type) - - // a 15u syringe is a nice baseline to scale lethality by - liver_damage += ((amount/15) * toxin.toxpwr * toxin.liver_damage_multiplier) / liver_resistance - - if(provide_pain_message != HAS_PAINFUL_TOXIN) - provide_pain_message = toxin.silent_toxin ? HAS_SILENT_TOXIN : HAS_PAINFUL_TOXIN - owner.reagents?.metabolize(owner, seconds_per_tick, times_fired, can_overdose = TRUE) - if(liver_damage) - apply_organ_damage(min(liver_damage * seconds_per_tick , MAX_TOXIN_LIVER_DAMAGE * seconds_per_tick)) - - if(provide_pain_message && damage > 10 && SPT_PROB(damage/6, seconds_per_tick)) //the higher the damage the higher the probability - to_chat(owner, span_warning("You feel a dull pain in your abdomen.")) - - /obj/item/organ/internal/liver/handle_failing_organs(seconds_per_tick) if(HAS_TRAIT(owner, TRAIT_STABLELIVER) || HAS_TRAIT(owner, TRAIT_LIVERLESS_METABOLISM)) return @@ -303,10 +272,6 @@ . = ..() AddElement(/datum/element/dangerous_surgical_removal) -#undef HAS_SILENT_TOXIN -#undef HAS_NO_TOXIN -#undef HAS_PAINFUL_TOXIN #undef LIVER_DEFAULT_TOX_TOLERANCE #undef LIVER_DEFAULT_TOX_RESISTANCE #undef LIVER_FAILURE_STAGE_SECONDS -#undef MAX_TOXIN_LIVER_DAMAGE