diff --git a/code/_globalvars/lists/reagents.dm b/code/_globalvars/lists/reagents.dm index 225b12c8353..8aeacb6f583 100644 --- a/code/_globalvars/lists/reagents.dm +++ b/code/_globalvars/lists/reagents.dm @@ -58,6 +58,8 @@ GLOBAL_LIST_INIT(blacklisted_metalgen_types, typecacheof(list( GLOBAL_LIST_INIT(name2reagent, build_name2reagentlist()) /// list of all plan traits GLOBAL_LIST_INIT(plant_traits, init_plant_traits()) +/// List of all reagent side effects +GLOBAL_LIST_INIT(stacked_metabolization_effect, init_chemical_side_effects()) /// Initialises all /datum/reagent into a list indexed by reagent id /proc/init_chemical_reagent_list() @@ -198,3 +200,9 @@ GLOBAL_LIST_INIT(plant_traits, init_plant_traits()) //build map with sorted keys for(var/name in only_names) .[name] = name_to_reagent[name] + +/proc/init_chemical_side_effects() + . = list() + + for(var/datum/stacked_metabolization_effect/effect as anything in valid_subtypesof(/datum/stacked_metabolization_effect)) + . += new effect() diff --git a/code/modules/reagents/chemistry/holder/mob_life.dm b/code/modules/reagents/chemistry/holder/mob_life.dm index 978f1fb0d99..693dc8bb885 100644 --- a/code/modules/reagents/chemistry/holder/mob_life.dm +++ b/code/modules/reagents/chemistry/holder/mob_life.dm @@ -30,6 +30,7 @@ liver_tolerance = liver.toxTolerance * liver_health_percent provide_pain_message = HAS_NO_TOXIN + var/list/reagents_metabolized = list() for(var/datum/reagent/reagent as anything in cached_reagents) var/datum/reagent/toxin/toxin if(istype(reagent, /datum/reagent/toxin)) @@ -44,7 +45,7 @@ owner.reagents.remove_reagent(toxin.type, toxin.metabolization_rate * owner.metabolism_efficiency * seconds_per_tick) continue - need_mob_update += metabolize_reagent(owner, reagent, seconds_per_tick, can_overdose, liverless, dead) + need_mob_update += metabolize_reagent(owner, reagent, seconds_per_tick, can_overdose, liverless, dead, reagents_metabolized) // 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. @@ -66,8 +67,15 @@ 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() + if(owner) + //apply side effects based on reagents metabolized + if(reagents_metabolized.len > 1) + for(var/datum/stacked_metabolization_effect/effect as anything in GLOB.stacked_metabolization_effect) + need_mob_update += effect.check_and_apply(reagents_metabolized, owner, seconds_per_tick) + + //some of the metabolized reagents had effects on the mob that requires some updates. + if(need_mob_update) + owner.updatehealth() #undef HAS_SILENT_TOXIN #undef HAS_NO_TOXIN @@ -80,11 +88,11 @@ * Arguments: * * mob/living/carbon/owner - The mob to metabolize in, if null it uses [/datum/reagents/var/my_atom] * * seconds_per_tick - the time in server seconds between proc calls (when performing normally it will be 2) - * * times_fired - the number of times the owner's life() tick has been called aka The number of times SSmobs has fired * * can_overdose - Allows overdosing * * liverless - Stops reagents that aren't set as [/datum/reagent/var/self_consuming] from metabolizing + * * list/reagents_metabolized - a list to hold the reagent metabolized with the metabolization ratio */ -/datum/reagents/proc/metabolize_reagent(mob/living/carbon/owner, datum/reagent/reagent, seconds_per_tick, can_overdose = FALSE, liverless = FALSE, dead = FALSE) +/datum/reagents/proc/metabolize_reagent(mob/living/carbon/owner, datum/reagent/reagent, seconds_per_tick, can_overdose = FALSE, liverless = FALSE, dead = FALSE, list/reagents_metabolized) if(QDELETED(reagent.holder)) return FALSE @@ -104,6 +112,8 @@ var/need_mob_update = FALSE var/metabolized_volume = reagent.compute_metabolization(owner, seconds_per_tick) var/metabolization_ratio = REM * metabolized_volume + if(reagents_metabolized) + reagents_metabolized[reagent.type] = metabolization_ratio if(can_overdose && !HAS_TRAIT(owner, TRAIT_OVERDOSEIMMUNE)) if(reagent.overdose_threshold && reagent.volume >= reagent.overdose_threshold && !reagent.overdosed) reagent.overdosed = TRUE diff --git a/code/modules/reagents/stacked_effects/stacked_effects.dm b/code/modules/reagents/stacked_effects/stacked_effects.dm new file mode 100644 index 00000000000..c1e76e1857d --- /dev/null +++ b/code/modules/reagents/stacked_effects/stacked_effects.dm @@ -0,0 +1,61 @@ +///Side effects from metabolizing an reagent or a combination of them +/datum/stacked_metabolization_effect + abstract_type = /datum/stacked_metabolization_effect + ///List of reagents that need to be metabolized for this side effect to kick in. For subtypes values greater than requirement list will also trigger this effect + var/list/datum/reagent/requirements + +/** + * Returns the average metabolzation ratio from an list + * Arguments + * + * * list/reagents_metabolized - the list of reagents metabolized +*/ +/datum/stacked_metabolization_effect/proc/average(list/reagents_metabolized) + SHOULD_BE_PURE(TRUE) + SHOULD_NOT_OVERRIDE(TRUE) + + var/average = 0 + for(var/datum/reagent/test as anything in reagents_metabolized) + average += reagents_metabolized[test] + return average / reagents_metabolized.len + +/** + * Checks if this side effect can be applied on the mob + * Arguments + * + * * list/reagents_metabolized - a map of reagent type path -> metabolization_ratio of all reagents + * * mob/living/carbon/owner - the mob to apply the side effects to + * * seconds_per_tick - passed from /datum/reagents/proc/metabolize_reagent() +*/ +/datum/stacked_metabolization_effect/proc/check_and_apply(list/reagents_metabolized, mob/living/carbon/owner, seconds_per_tick) + SHOULD_NOT_OVERRIDE(TRUE) + . = 0 + + var/list/datum/reagent/requirements_needed = requirements + for(var/datum/reagent/test as anything in reagents_metabolized) + for(var/datum/reagent/requirement as anything in requirements_needed) + if(ispath(test, requirement)) + if(requirements_needed == requirements) + requirements_needed = requirements.Copy() + requirements_needed[requirement] -= 1 + if(!requirements_needed[requirement]) + requirements_needed -= requirement + break + + if(!requirements_needed.len) + return apply(reagents_metabolized, owner, seconds_per_tick) + +/** + * Apply a list of side effects to an mob once they have metabolized the requirments + * Arguments + * + * * list/reagents_metabolized - a map of reagent -> metabolization_ratio of all reagents + * * mob/living/carbon/owner - the mob to apply the side effects to + * * seconds_per_tick - passed from /datum/reagents/proc/metabolize_reagent() + * Returns a positive value if the mobs health needs to be updated +*/ +/datum/stacked_metabolization_effect/proc/apply(list/reagents_metabolized, mob/living/carbon/owner, seconds_per_tick) + PROTECTED_PROC(TRUE) + SHOULD_NOT_SLEEP(TRUE) + + return 0 diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index b37ce733209..5894dc7feec 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -313,6 +313,7 @@ #include "spraycan.dm" #include "spritesheets.dm" #include "stack_singular_name.dm" +#include "stacked_metab.dm" #include "station_trait_tests.dm" #include "status_effect_validity.dm" #include "stomach.dm" diff --git a/code/modules/unit_tests/stacked_metab.dm b/code/modules/unit_tests/stacked_metab.dm new file mode 100644 index 00000000000..94158acb8f5 --- /dev/null +++ b/code/modules/unit_tests/stacked_metab.dm @@ -0,0 +1,22 @@ +/datum/unit_test/stacked_metabolization_effect_verify + +/datum/unit_test/stacked_metabolization_effect_verify/Run() + for(var/datum/stacked_metabolization_effect/effect as anything in valid_subtypesof(/datum/stacked_metabolization_effect)) + effect = new effect() + + if(!length(effect.requirements)) + TEST_FAIL("Effect [effect] does not have any requirments!") + qdel(effect) + continue + + for(var/datum/reagent/key as anything in effect.requirements) + if(!ispath(key, /datum/reagent)) + TEST_FAIL("Effect [effect] has an non reagent key [key] as requirment") + break + + var/count = effect.requirements[key] + if(count < 1) + TEST_FAIL("Effect [effect] has an invalid requirement count [count] for key [key] ") + break + + qdel(effect) diff --git a/tgstation.dme b/tgstation.dme index 303cf1ab8ce..492cfa5ca93 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -6127,6 +6127,7 @@ #include "code\modules\reagents\reagent_containers\cups\mauna_mug.dm" #include "code\modules\reagents\reagent_containers\cups\organ_jar.dm" #include "code\modules\reagents\reagent_containers\cups\soda.dm" +#include "code\modules\reagents\stacked_effects\stacked_effects.dm" #include "code\modules\reagents\withdrawal\_addiction.dm" #include "code\modules\reagents\withdrawal\generic_addictions.dm" #include "code\modules\recycling\conveyor.dm"