Fixes reactions ending prematurely when nearing min conditions (#89109)

## About The Pull Request
So when a reaction is approaching its minimum conditions (temps are
approaching `required_temp` or ph is appraching `optimal_ph_min`) It
would end some good distance from these min values & not very close to
them.

For e.g. Flurosulphuric acid has a minimum required temp of 380K but the
reaction would stop/won't occur if the temps go below 400K itself i.e.
20K from its minimum which is not ideal when we are trying to maximise
results at all times.

Now it goes all the way down to these min values ensuring the reaction
rate never goes below 0.005(which in players terms means the rate of
reagents poduced per tick will never go below 0.01u for any given
reaction)

This also improves the code of debug chem master which caught this bug. 

## Changelog
🆑
fix: chemical reactions can now go all the way to their min required
temp/ph
code: improved code for debug chem master. 
/🆑
This commit is contained in:
SyncIt21
2025-01-23 19:38:36 +01:00
committed by GitHub
parent 76dce99c30
commit 121462a6fb
3 changed files with 53 additions and 35 deletions
@@ -34,6 +34,8 @@
var/delta_t
///How pure our step is
var/delta_ph
///Min reaction rate possible below which rounding errors occur
VAR_PRIVATE/min_rate
///Modifiers from catalysts, do not use negative numbers.
///I should write a better handiler for modifying these
///Speed mod
@@ -67,7 +69,6 @@
LAZYADD(holder.reaction_list, src)
SSblackbox.record_feedback("tally", "chemical_reaction", 1, "[reaction.type] attempts")
/datum/equilibrium/Destroy()
if(reacted_vol < target_vol) //We did NOT finish from reagents - so we can restart this reaction given property changes in the beaker. (i.e. if it stops due to low temp, this will allow it to fast restart when heated up again)
LAZYADD(holder.failed_but_capable_reactions, reaction) //Consider replacing check with calculate_yield()
@@ -114,6 +115,8 @@
product_ratio += reaction.results[product]
else
product_ratio = 1
min_rate = product_ratio * (CHEMICAL_VOLUME_ROUNDING / 2)
return TRUE
/**
@@ -321,10 +324,13 @@
purity *= purity_modifier
//Now we calculate how much to add - this is normalised to the rate up limiter
var/delta_chem_factor = reaction.rate_up_lim * delta_t * seconds_per_tick//add/remove factor
var/delta_chem_factor = reaction.rate_up_lim * delta_t * seconds_per_tick
//keep limited
if(delta_chem_factor > step_target_vol)
delta_chem_factor = step_target_vol
//ensure its above minimum rate below which rounding errors occur
else if(delta_chem_factor < min_rate)
delta_chem_factor = min_rate
//Normalise to multiproducts
delta_chem_factor = round(delta_chem_factor / product_ratio, CHEMICAL_VOLUME_ROUNDING)
if(delta_chem_factor <= 0)
@@ -266,7 +266,7 @@
for(var/datum/reagent/removed_reagent as anything in removed_reagents)
SEND_SIGNAL(src, COMSIG_REAGENTS_REM_REAGENT, removed_reagent, removed_reagents[removed_reagent])
return round(total_removed_amount, CHEMICAL_VOLUME_ROUNDING)
return total_removed_amount
/**
* Removes all reagents either proportionally(amount is the direct volume to remove)
@@ -29,7 +29,7 @@
icon = 'icons/obj/medical/chemical.dmi'
icon_state = "HPLC_debug"
density = TRUE
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.4
use_power = NO_POWER_USE
resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE
///Temperature to be imposed on the reaction
@@ -63,7 +63,7 @@
///The target reagents to we are working with. can vary if an reaction requires a specific container
var/datum/reagents/target_reagents
///The beaker inside this machine, if null will create a new one
var/obj/item/reagent_containers/cup/beaker/bluespace/beaker
var/obj/item/reagent_containers/container
///The default reagent container required for the selected test reaction if any
var/obj/item/reagent_containers/required_container
@@ -85,47 +85,47 @@
reactions_to_test.Cut()
target_reagents = null
edit_reaction = null
QDEL_NULL(beaker)
QDEL_NULL(container)
QDEL_NULL(required_container)
UnregisterSignal(reagents, COMSIG_REAGENTS_REACTION_STEP)
. = ..()
return ..()
/obj/machinery/chem_recipe_debug/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = ..()
if(isnull(held_item) || (held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1))
return NONE
if(!QDELETED(beaker))
if(!QDELETED(container))
if(is_reagent_container(held_item) && held_item.is_open_container())
context[SCREENTIP_CONTEXT_LMB] = "Replace beaker"
context[SCREENTIP_CONTEXT_LMB] = "Replace container"
return CONTEXTUAL_SCREENTIP_SET
else if(is_reagent_container(held_item) && held_item.is_open_container())
context[SCREENTIP_CONTEXT_LMB] = "Insert beaker"
context[SCREENTIP_CONTEXT_LMB] = "Insert container"
return CONTEXTUAL_SCREENTIP_SET
/obj/machinery/chem_recipe_debug/examine(mob/user)
. = ..()
if(!QDELETED(beaker))
. += span_notice("A beaker of [beaker.reagents.maximum_volume]u capacity is inside.")
if(!QDELETED(container))
. += span_notice("A container of [container.reagents.maximum_volume]u capacity is inside.")
else
. += span_notice("No beaker is present. A new will be created when ejecting.")
. += span_notice("No container is present. A new will be created when ejecting.")
/obj/machinery/chem_recipe_debug/Exited(atom/movable/gone, direction)
. = ..()
if(gone == beaker)
beaker = null
if(gone == container)
container = null
/obj/machinery/chem_recipe_debug/attackby(obj/item/held_item, mob/user, params)
/obj/machinery/chem_recipe_debug/item_interaction(mob/living/user, obj/item/held_item, list/modifiers)
. = NONE
if((held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1))
return ..()
return
if(is_reagent_container(held_item) && held_item.is_open_container())
. = TRUE
if(!QDELETED(beaker))
try_put_in_hand(beaker, user)
if(!QDELETED(container))
try_put_in_hand(container, user)
if(!user.transferItemToLoc(held_item, src))
return
beaker = held_item
return ITEM_INTERACT_FAILURE
container = held_item
return ITEM_INTERACT_SUCCESS
/**
* Extracts a human readable name for this chemical reaction
@@ -155,13 +155,12 @@
var/datum/chemical_reaction/test_reaction = reactions_to_test[current_reaction_index || 1]
switch(temp_mode)
if(USE_MINIMUM_TEMPERATURE)
return test_reaction.required_temp + (test_reaction.is_cold_recipe ? - 20 : 20) //20k is good enough offset to account for reaction rate rounding
return test_reaction.required_temp
if(USE_OPTIMAL_TEMPERATURE)
return test_reaction.optimal_temp
if(USE_OVERHEAT_TEMPERATURE)
return test_reaction.overheat_temp
/**
* Adjusts the temperature, ph & purity of the holder
* Arguments
@@ -173,7 +172,7 @@
var/target_temperature = decode_target_temperature()
if(!isnull(target_temperature))
target_reagents.adjust_thermal_energy((target_temperature - target_reagents.chem_temp) * 0.4 * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * target_reagents.total_volume)
target_reagents.adjust_thermal_energy((target_temperature - target_reagents.chem_temp) * 0.45 * seconds_per_tick * target_reagents.heat_capacity())
if(use_forced_purity)
target_reagents.set_all_reagents_purity(forced_purity)
@@ -187,7 +186,7 @@
/obj/machinery/chem_recipe_debug/process(seconds_per_tick)
if(!target_reagents.is_reacting)
adjust_environment(seconds_per_tick)
target_reagents.handle_reactions()
target_reagents.handle_reactions()
//send updates to ui. faster than SStgui.update_uis
for(var/datum/tgui/ui in src.open_uis)
@@ -334,11 +333,10 @@
.["editReaction"] = reaction_data
var/list/beaker_data = null
if(target_reagents.reagent_list.len)
if(!QDELETED(container) || target_reagents.total_volume)
beaker_data = list()
beaker_data["maxVolume"] = target_reagents.maximum_volume
beaker_data["pH"] = round(target_reagents.ph, 0.01)
beaker_data["purity"] = round(target_reagents.get_average_purity(), 0.01)
beaker_data["currentVolume"] = round(target_reagents.total_volume, CHEMICAL_VOLUME_ROUNDING)
beaker_data["currentTemp"] = round(target_reagents.chem_temp, 1)
beaker_data["purity"] = round(target_reagents.get_average_purity(), 0.001)
@@ -666,12 +664,26 @@
tgui_alert(ui.user, "Saved to [dest]")
if("eject")
if(!target_reagents.total_volume)
return
if(QDELETED(beaker))
beaker = new /obj/item/reagent_containers/cup/beaker/bluespace(src)
target_reagents.trans_to(beaker, target_reagents.total_volume)
try_put_in_hand(beaker, ui.user)
//initialize a new container for us
if(QDELETED(container))
if(QDELETED(required_container))
container = new /obj/item/reagent_containers/cup/beaker/bluespace(src)
else
container = new required_container.type(src)
//transfer all reagents & ingredients if we are using a soup pot
container.reagents.clear_reagents()
target_reagents.trans_to(container, target_reagents.total_volume)
if(istype(container, /obj/item/reagent_containers/cup/soup_pot) && istype(required_container, /obj/item/reagent_containers/cup/soup_pot))
var/obj/item/reagent_containers/cup/soup_pot/pot = container
for(var/obj/item as anything in pot.added_ingredients)
qdel(item)
var/obj/item/reagent_containers/cup/soup_pot/holder = required_container
for(var/obj/item as anything in holder.added_ingredients)
item.forceMove(pot)
LAZYADD(pot.added_ingredients, item)
try_put_in_hand(container, ui.user)
return TRUE
#undef USE_REACTION_TEMPERATURE