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

🆑
fix: fixed a race condition that was causing carpotoxin to cause liver
damage to felinids despite being immune
/🆑

---------

Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
This commit is contained in:
Bloop
2024-09-08 03:01:08 +02:00
committed by GitHub
co-authored by SyncIt21
parent 3633341836
commit d4afce8b4b
2 changed files with 40 additions and 39 deletions
@@ -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.
*
@@ -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