diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm
index 71c7f9fa0d..ff16d62609 100644
--- a/code/modules/reagents/chemistry/holder.dm
+++ b/code/modules/reagents/chemistry/holder.dm
@@ -1,4 +1,45 @@
+/proc/build_chemical_reagent_list()
+ //Chemical Reagents - Initialises all /datum/reagent into a list indexed by reagent id
+
+ if(GLOB.chemical_reagents_list)
+ return
+
+ var/paths = subtypesof(/datum/reagent)
+ GLOB.chemical_reagents_list = list()
+
+ for(var/path in paths)
+ var/datum/reagent/D = new path()
+ GLOB.chemical_reagents_list[D.id] = D
+
+/proc/build_chemical_reactions_list()
+ //Chemical Reactions - Initialises all /datum/chemical_reaction into a list
+ // It is filtered into multiple lists within a list.
+ // For example:
+ // chemical_reaction_list["plasma"] is a list of all reactions relating to plasma
+
+ if(GLOB.chemical_reactions_list)
+ return
+
+ var/paths = subtypesof(/datum/chemical_reaction)
+ GLOB.chemical_reactions_list = list()
+
+ for(var/path in paths)
+
+ var/datum/chemical_reaction/D = new path()
+ var/list/reaction_ids = list()
+
+ if(D.required_reagents && D.required_reagents.len)
+ for(var/reaction in D.required_reagents)
+ reaction_ids += reaction
+
+ // Create filters based on each reagent id in the required reagents list
+ for(var/id in reaction_ids)
+ if(!GLOB.chemical_reactions_list[id])
+ GLOB.chemical_reactions_list[id] = list()
+ GLOB.chemical_reactions_list[id] += D
+ break // Don't bother adding ourselves to other reagent ids, it is redundant
+.
///////////////////////////////////////////////////////////////////////////////////
/datum/reagents
@@ -15,39 +56,11 @@
/datum/reagents/New(maximum=100)
maximum_volume = maximum
-
//I dislike having these here but map-objects are initialised before world/New() is called. >_>
if(!GLOB.chemical_reagents_list)
- //Chemical Reagents - Initialises all /datum/reagent into a list indexed by reagent id
- var/paths = subtypesof(/datum/reagent)
- GLOB.chemical_reagents_list = list()
- for(var/path in paths)
- var/datum/reagent/D = new path()
- GLOB.chemical_reagents_list[D.id] = D
+ build_chemical_reagent_list()
if(!GLOB.chemical_reactions_list)
- //Chemical Reactions - Initialises all /datum/chemical_reaction into a list
- // It is filtered into multiple lists within a list.
- // For example:
- // chemical_reaction_list["plasma"] is a list of all reactions relating to plasma
-
- var/paths = subtypesof(/datum/chemical_reaction)
- GLOB.chemical_reactions_list = list()
-
- for(var/path in paths)
-
- var/datum/chemical_reaction/D = new path()
- var/list/reaction_ids = list()
-
- if(D.required_reagents && D.required_reagents.len)
- for(var/reaction in D.required_reagents)
- reaction_ids += reaction
-
- // Create filters based on each reagent id in the required reagents list
- for(var/id in reaction_ids)
- if(!GLOB.chemical_reactions_list[id])
- GLOB.chemical_reactions_list[id] = list()
- GLOB.chemical_reactions_list[id] += D
- break // Don't bother adding ourselves to other reagent ids, it is redundant.
+ build_chemical_reactions_list()
/datum/reagents/Destroy()
. = ..()
@@ -326,6 +339,7 @@
var/reaction_occurred = 0
do
+ var/list/possible_reactions = list()
reaction_occurred = 0
for(var/reagent in cached_reagents)
var/datum/reagent/R = reagent
@@ -342,18 +356,14 @@
var/total_matching_catalysts= 0
var/matching_container = 0
var/matching_other = 0
- var/list/multipliers = new/list()
var/required_temp = C.required_temp
var/is_cold_recipe = C.is_cold_recipe
var/meets_temp_requirement = 0
- var/list/cached_results = C.results
-
for(var/B in cached_required_reagents)
if(!has_reagent(B, cached_required_reagents[B]))
break
total_matching_reagents++
- multipliers += round(get_reagent_amount(B) / cached_required_reagents[B])
for(var/B in cached_required_catalysts)
if(!has_reagent(B, cached_required_catalysts[B]))
break
@@ -385,37 +395,54 @@
meets_temp_requirement = 1
if(total_matching_reagents == total_required_reagents && total_matching_catalysts == total_required_catalysts && matching_container && matching_other && meets_temp_requirement)
- var/multiplier = min(multipliers)
- for(var/B in cached_required_reagents)
- remove_reagent(B, (multiplier * cached_required_reagents[B]), safety = 1)
+ possible_reactions += C
- for(var/P in C.results)
- multiplier = max(multiplier, 1) //this shouldnt happen ...
- SSblackbox.record_feedback("tally", "chemical_reaction", cached_results[P]*multiplier, P)
- add_reagent(P, cached_results[P]*multiplier, null, chem_temp)
+ if(possible_reactions.len)
+ var/datum/chemical_reaction/selected_reaction = possible_reactions[1]
+ //select the reaction with the most extreme temperature requirements
+ for(var/V in possible_reactions)
+ var/datum/chemical_reaction/competitor = V
+ if(selected_reaction.is_cold_recipe) //if there are no recipe conflicts, everything in possible_reactions will have this same value for is_cold_reaction. warranty void if assumption not met.
+ if(competitor.required_temp < selected_reaction.required_temp)
+ selected_reaction = competitor
+ else
+ if(competitor.required_temp > selected_reaction.required_temp)
+ selected_reaction = competitor
+ var/list/cached_required_reagents = selected_reaction.required_reagents
+ var/list/cached_results = selected_reaction.results
+ var/list/multiplier = INFINITY
+ for(var/B in cached_required_reagents)
+ multiplier = min(multiplier, round(get_reagent_amount(B) / cached_required_reagents[B]))
- var/list/seen = viewers(4, get_turf(my_atom))
- var/iconhtml = icon2html(cached_my_atom, seen)
- if(cached_my_atom)
- if(!ismob(cached_my_atom)) // No bubbling mobs
- if(C.mix_sound)
- playsound(get_turf(cached_my_atom), C.mix_sound, 80, 1)
+ for(var/B in cached_required_reagents)
+ remove_reagent(B, (multiplier * cached_required_reagents[B]), safety = 1)
- for(var/mob/M in seen)
- to_chat(M, "[iconhtml] [C.mix_message]")
+ for(var/P in selected_reaction.results)
+ multiplier = max(multiplier, 1) //this shouldnt happen ...
+ SSblackbox.record_feedback("tally", "chemical_reaction", cached_results[P]*multiplier, P)
+ add_reagent(P, cached_results[P]*multiplier, null, chem_temp)
- if(istype(cached_my_atom, /obj/item/slime_extract))
- var/obj/item/slime_extract/ME2 = my_atom
- ME2.Uses--
- if(ME2.Uses <= 0) // give the notification that the slime core is dead
- for(var/mob/M in seen)
- to_chat(M, "[iconhtml] \The [my_atom]'s power is consumed in the reaction.")
- ME2.name = "used slime extract"
- ME2.desc = "This extract has been used up."
+ var/list/seen = viewers(4, get_turf(my_atom))
+ var/iconhtml = icon2html(cached_my_atom, seen)
+ if(cached_my_atom)
+ if(!ismob(cached_my_atom)) // No bubbling mobs
+ if(selected_reaction.mix_sound)
+ playsound(get_turf(cached_my_atom), selected_reaction.mix_sound, 80, 1)
- C.on_reaction(src, multiplier)
- reaction_occurred = 1
- break
+ for(var/mob/M in seen)
+ to_chat(M, "[iconhtml] [selected_reaction.mix_message]")
+
+ if(istype(cached_my_atom, /obj/item/slime_extract))
+ var/obj/item/slime_extract/ME2 = my_atom
+ ME2.Uses--
+ if(ME2.Uses <= 0) // give the notification that the slime core is dead
+ for(var/mob/M in seen)
+ to_chat(M, "[iconhtml] \The [my_atom]'s power is consumed in the reaction.")
+ ME2.name = "used slime extract"
+ ME2.desc = "This extract has been used up."
+
+ selected_reaction.on_reaction(src, multiplier)
+ reaction_occurred = 1
while(reaction_occurred)
update_total()
diff --git a/code/modules/reagents/chemistry/recipes.dm b/code/modules/reagents/chemistry/recipes.dm
index a4167a19df..2493ec39e5 100644
--- a/code/modules/reagents/chemistry/recipes.dm
+++ b/code/modules/reagents/chemistry/recipes.dm
@@ -6,7 +6,7 @@
var/list/required_catalysts = new/list()
// Both of these variables are mostly going to be used with slime cores - but if you want to, you can use them for other things
- var/atom/required_container = null // the container required for the reaction to happen
+ var/required_container = null // the exact container path required for the reaction to happen
var/required_other = 0 // an integer required for the reaction to happen
var/mob_react = TRUE //Determines if a chemical reaction can occur inside a mob
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index 001952ea65..47d35108a1 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -1,5 +1,7 @@
-//include unit test files in this module in this ifdef
-
-#ifdef UNIT_TESTS
-#include "unit_test.dm"
-#endif
+//include unit test files in this module in this ifdef
+
+#ifdef UNIT_TESTS
+#include "unit_test.dm"
+#include "reagent_recipe_collisions.dm"
+#include "reagent_id_typos.dm"
+#endif
diff --git a/code/modules/unit_tests/reagent_id_typos.dm b/code/modules/unit_tests/reagent_id_typos.dm
new file mode 100644
index 0000000000..d6548852fa
--- /dev/null
+++ b/code/modules/unit_tests/reagent_id_typos.dm
@@ -0,0 +1,14 @@
+
+
+/datum/unit_test/reagent_id_typos
+
+/datum/unit_test/reagent_id_typos/Run()
+ build_chemical_reactions_list()
+ build_chemical_reagent_list()
+
+ for(var/I in GLOB.chemical_reactions_list)
+ for(var/V in GLOB.chemical_reactions_list[I])
+ var/datum/chemical_reaction/R = V
+ for(var/id in (R.required_reagents + R.required_catalysts))
+ if(!GLOB.chemical_reagents_list[id])
+ Fail("Unknown chemical id \"[id]\" in recipe [R.type]")
diff --git a/code/modules/unit_tests/reagent_recipe_collisions.dm b/code/modules/unit_tests/reagent_recipe_collisions.dm
new file mode 100644
index 0000000000..31027c2cd3
--- /dev/null
+++ b/code/modules/unit_tests/reagent_recipe_collisions.dm
@@ -0,0 +1,67 @@
+
+
+/datum/unit_test/reagent_recipe_collisions
+
+/datum/unit_test/reagent_recipe_collisions/Run()
+ build_chemical_reactions_list()
+ var/list/reactions = list()
+ for(var/V in GLOB.chemical_reactions_list)
+ reactions += GLOB.chemical_reactions_list[V]
+ for(var/i in 1 to (reactions.len-1))
+ for(var/i2 in (i+1) to reactions.len)
+ var/datum/chemical_reaction/r1 = reactions[i]
+ var/datum/chemical_reaction/r2 = reactions[i2]
+ if(recipes_do_conflict(r1, r2))
+ Fail("Chemical recipe conflict between [r1.type] and [r2.type]")
+
+/datum/unit_test/reagent_recipe_collisions/proc/recipes_do_conflict(datum/chemical_reaction/r1, datum/chemical_reaction/r2)
+ //do the non-list tests first, because they are cheaper
+ if(r1.required_container != r2.required_container)
+ return FALSE
+ if(r1.is_cold_recipe == r2.is_cold_recipe)
+ if(r1.required_temp != r2.required_temp)
+ //one reaction requires a more extreme temperature than the other, so there is no conflict
+ return FALSE
+ else
+ var/datum/chemical_reaction/cold_one = r1.is_cold_recipe ? r1 : r2
+ var/datum/chemical_reaction/warm_one = r1.is_cold_recipe ? r2 : r1
+ if(cold_one.required_temp < warm_one.required_temp)
+ //the range of temperatures does not overlap, so there is no conflict
+ return FALSE
+
+ //find the reactions with the shorter and longer required_reagents list
+ var/datum/chemical_reaction/long_req
+ var/datum/chemical_reaction/short_req
+ if(r1.required_reagents.len > r2.required_reagents.len)
+ long_req = r1
+ short_req = r2
+ else if(r1.required_reagents.len < r2.required_reagents.len)
+ long_req = r2
+ short_req = r1
+ else
+ //if they are the same length, sort instead by the length of the catalyst list
+ //this is important if the required_reagents lists are the same
+ if(r1.required_catalysts.len > r2.required_catalysts.len)
+ long_req = r1
+ short_req = r2
+ else
+ long_req = r2
+ short_req = r1
+
+
+ //check if the shorter reaction list is a subset of the longer one
+ var/list/overlap = r1.required_reagents & r2.required_reagents
+ if(overlap.len != short_req.required_reagents.len)
+ //there is at least one reagent in the short list that is not in the long list, so there is no conflict
+ return FALSE
+
+ //check to see if the shorter reaction's catalyst list is also a subset of the longer reaction's catalyst list
+ //if the longer reaction's catalyst list is a subset of the shorter ones, that is fine
+ //if the reaction lists are the same, the short reaction will have the shorter required_catalysts list, so it will register as a conflict
+ var/list/short_minus_long_catalysts = short_req.required_catalysts - long_req.required_catalysts
+ if(short_minus_long_catalysts.len)
+ //there is at least one unique catalyst for the short reaction, so there is no conflict
+ return FALSE
+
+ //if we got this far, the longer reaction will be impossible to create if the shorter one is earlier in GLOB.chemical_reactions_list, and will require the reagents to be added in a particular order otherwise
+ return TRUE
\ No newline at end of file
diff --git a/code/modules/vore/eating/voreitems.dm b/code/modules/vore/eating/voreitems.dm
index a1d52d109a..5d157c39fe 100644
--- a/code/modules/vore/eating/voreitems.dm
+++ b/code/modules/vore/eating/voreitems.dm
@@ -33,7 +33,7 @@
////////////////////////// Anti-Noms Drugs //////////////////////////
-
+/*
/datum/reagent/medicine/ickypak
name = "Ickypak"
id = "ickypak"
@@ -60,4 +60,4 @@
name = "Ickypak"
id = "ickypak"
results = list("ickypak" = 2)
- required_reagents = list("chlorine" = 2 , "oil" = 1)
\ No newline at end of file
+ required_reagents = list("chlorine" = 2 , "oil" = 1) */
\ No newline at end of file