Skip to content

Commit

Permalink
[MIRROR] Reactions now use volume averaged purity (#803)
Browse files Browse the repository at this point in the history
* Reactions now use volume averaged purity (#81246)

## About The Pull Request
Cause it only makes sense

Currently the purity of reagents created in a reaction is computed as
follows

`total sum of purity of all reagents present / total number of reagents`

This is incorrect because regardless of how much "volume" of an
impure/pure reagent is present the purity of the final solution is
unaffected. Logically if we have more amount of an impure reagent the
more impure the final solution should be & same for opposite case. This
is the case for ph, where if we have a large volume of say "acidic"
reagent then changes in other reagents have a small effect to the
overall "acidity" of the solution. The same concept now applies for
purity as well

`get_average_purity()`accounts for volume thus yielding more realistic
results. The effect becomes more significant with larger volumes of
reagents.

:cl:
fix: reactions now compute purity of reagents based on their volume,
meaning larger amounts of reagents created will have more significant
effects on the final purity of the solution
/:cl:

* Reactions now use volume averaged purity

* Merge #818

---------

Co-authored-by: SyncIt21 <[email protected]>
Co-authored-by: SomeRandomOwl <[email protected]>
  • Loading branch information
3 people authored and FFMirrorBot committed Feb 8, 2024
1 parent 107a5d8 commit 99a2db7
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 47 deletions.
39 changes: 9 additions & 30 deletions code/modules/reagents/chemistry/equilibrium.dm
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
return FALSE

//To prevent reactions outside of the pH window from starting.
if(!((holder.ph >= (reaction.optimal_ph_min - reaction.determin_ph_range)) && (holder.ph <= (reaction.optimal_ph_max + reaction.determin_ph_range))))
if(holder.ph < (reaction.optimal_ph_min - reaction.determin_ph_range) || holder.ph > (reaction.optimal_ph_max + reaction.determin_ph_range))
return FALSE

//All checks pass. cache the product ratio
Expand Down Expand Up @@ -265,24 +265,23 @@
//Begin checks
//Calculate DeltapH (Deviation of pH from optimal)
//Within mid range
var/acceptable_ph
if (cached_ph >= reaction.optimal_ph_min && cached_ph <= reaction.optimal_ph_max)
delta_ph = 1 //100% purity for this step
//Lower range
else if (cached_ph < reaction.optimal_ph_min) //If we're outside of the optimal lower bound
if (cached_ph < (reaction.optimal_ph_min - reaction.determin_ph_range)) //If we're outside of the deterministic bound
acceptable_ph = reaction.optimal_ph_min - reaction.determin_ph_range
if (cached_ph < acceptable_ph) //If we're outside of the deterministic bound
delta_ph = 0 //0% purity
else //We're in the deterministic phase
delta_ph = (((cached_ph - (reaction.optimal_ph_min - reaction.determin_ph_range)) ** reaction.ph_exponent_factor) / ((reaction.determin_ph_range ** reaction.ph_exponent_factor))) //main pH calculation
delta_ph = ((cached_ph - acceptable_ph) / reaction.determin_ph_range) ** reaction.ph_exponent_factor
//Upper range
else if (cached_ph > reaction.optimal_ph_max) //If we're above of the optimal lower bound
if (cached_ph > (reaction.optimal_ph_max + reaction.determin_ph_range)) //If we're outside of the deterministic bound
acceptable_ph = reaction.optimal_ph_max + reaction.determin_ph_range
if (cached_ph > acceptable_ph) //If we're outside of the deterministic bound
delta_ph = 0 //0% purity
else //We're in the deterministic phase
delta_ph = (((- cached_ph + (reaction.optimal_ph_max + reaction.determin_ph_range)) ** reaction.ph_exponent_factor) / (reaction.determin_ph_range ** reaction.ph_exponent_factor))//Reverse - to + to prevent math operation failures.

//This should never proc, but it's a catch incase someone puts in incorrect values
else
stack_trace("[holder.my_atom] attempted to determine FermiChem pH for '[reaction.type]' which had an invalid pH of [cached_ph] for set recipie pH vars. It's likely the recipe vars are wrong.")
delta_ph = ((acceptable_ph - cached_ph) / reaction.determin_ph_range) ** reaction.ph_exponent_factor

//Calculate DeltaT (Deviation of T from optimal)
if(!reaction.is_cold_recipe)
Expand Down Expand Up @@ -316,7 +315,7 @@
purity = delta_ph

//Then adjust purity of result with beaker reagent purity.
purity *= average_purity()
purity *= holder.get_average_purity()

//Then adjust it from the input modifier
purity *= purity_modifier
Expand Down Expand Up @@ -399,23 +398,3 @@
//i.e. we have created all the reagents needed for this reaction
if(total_step_added >= step_target_vol)
to_delete = TRUE

/*
* Calculates the total sum normalised purity of ALL reagents in a holder
* Currently calculates it irrespective of required reagents at the start, but this should be changed if this is powergamed to required reagents
* It's not currently because overly_impure affects all reagents
*/
/datum/equilibrium/proc/average_purity()
PRIVATE_PROC(TRUE)

var/list/cached_reagents = holder.reagent_list

var/num_of_reagents = cached_reagents.len
if(!num_of_reagents)//I've never seen it get here with 0, but in case - it gets here when it blows up from overheat
stack_trace("No reactants found mid reaction for [reaction.type]. Beaker: [holder.my_atom]")
return 0 //we exploded and cleared reagents - but lets not kill the process

var/cached_purity
for(var/datum/reagent/reagent as anything in cached_reagents)
cached_purity += reagent.purity
return cached_purity / num_of_reagents
38 changes: 21 additions & 17 deletions code/modules/reagents/chemistry/holder/reactions.dm
Original file line number Diff line number Diff line change
Expand Up @@ -308,36 +308,39 @@
var/list/cached_required_reagents = selected_reaction.required_reagents
var/list/cached_results = selected_reaction.results
var/datum/cached_my_atom = my_atom
var/multiplier = INFINITY
for(var/reagent in cached_required_reagents)
multiplier = round(min(multiplier, get_reagent_amount(reagent) / cached_required_reagents[reagent]))

if(!multiplier)//Incase we're missing reagents - usually from on_reaction being called in an equlibrium when the results.len == 0 handlier catches a misflagged reaction
//find how much ration of products to create
var/multiplier = INFINITY
for(var/datum/reagent/requirement as anything in cached_required_reagents)
multiplier = min(multiplier, get_reagent_amount(requirement) / cached_required_reagents[requirement])
multiplier = round(multiplier, CHEMICAL_QUANTISATION_LEVEL)
if(!multiplier)//Incase we're missing reagents - usually from on_reaction being called in an equlibrium when the results.len == 0 handler catches a misflagged reaction
return FALSE
var/sum_purity = 0
for(var/_reagent in cached_required_reagents)//this is not an object
var/datum/reagent/reagent = has_reagent(_reagent)
if (!reagent)
continue
sum_purity += reagent.purity
remove_reagent(_reagent, (multiplier * cached_required_reagents[_reagent]))
sum_purity /= cached_required_reagents.len

for(var/product in selected_reaction.results)
multiplier = max(multiplier, 1) //this shouldn't happen ...
var/yield = (cached_results[product]*multiplier)*sum_purity
//average purity to be used in scaling the yield of products formed
var/average_purity = get_average_purity()

//remove the required reagents
for(var/datum/reagent/requirement as anything in cached_required_reagents)//this is not an object
remove_reagent(requirement, cached_required_reagents[requirement] * multiplier)

//add the result reagents whose yield depend on the average purity
var/yield
for(var/datum/reagent/product as anything in cached_results)
yield = cached_results[product] * multiplier * average_purity
SSblackbox.record_feedback("tally", "chemical_reaction", yield, product)
add_reagent(product, yield, null, chem_temp, sum_purity)
add_reagent(product, yield, null, chem_temp, average_purity)

//play sounds on the target atom
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, TRUE)

my_atom.audible_message(span_notice("[iconhtml] [selected_reaction.mix_message]"))

//use slime extract
if(istype(cached_my_atom, /obj/item/slime_extract))
var/obj/item/slime_extract/extract = my_atom
extract.extract_uses--
Expand All @@ -353,4 +356,5 @@
my_turf.pollute_turf(selected_reaction.pollutant_type, selected_reaction.pollutant_amount * multiplier)
//NOVA EDIT END

//finish the reaction
selected_reaction.on_reaction(src, null, multiplier)

0 comments on commit 99a2db7

Please sign in to comment.