Implements reaction-over-time and reaction yield

This commit is contained in:
mwerezak
2015-05-23 01:36:22 -04:00
parent b72abc8b64
commit 33487c76fa
5 changed files with 136 additions and 28 deletions

View File

@@ -99,6 +99,7 @@
#include "code\controllers\voting.dm" #include "code\controllers\voting.dm"
#include "code\controllers\Processes\air.dm" #include "code\controllers\Processes\air.dm"
#include "code\controllers\Processes\alarm.dm" #include "code\controllers\Processes\alarm.dm"
#include "code\controllers\Processes\chemistry.dm"
#include "code\controllers\Processes\disease.dm" #include "code\controllers\Processes\disease.dm"
#include "code\controllers\Processes\emergencyShuttle.dm" #include "code\controllers\Processes\emergencyShuttle.dm"
#include "code\controllers\Processes\event.dm" #include "code\controllers\Processes\event.dm"

View File

@@ -0,0 +1,33 @@
var/datum/controller/process/chemistry/chemistryProcess
/datum/controller/process/chemistry
var/tmp/datum/updateQueue/updateQueueInstance
var/list/active_holders
var/list/chemical_reactions
var/list/chemical_reagents
/datum/controller/process/chemistry/setup()
name = "chemistry"
schedule_interval = 20 // every 2 seconds
updateQueueInstance = new
chemistryProcess = src
active_holders = list()
chemical_reactions = chemical_reactions_list
chemical_reagents = chemical_reagents_list
/datum/controller/process/chemistry/getStatName()
return ..()+"([active_holders.len])"
/datum/controller/process/chemistry/doWork()
for(var/datum/reagents/holder in active_holders)
if(!holder.process_reactions())
active_holders -= holder
scheck()
/datum/controller/process/chemistry/proc/mark_for_update(var/datum/reagents/holder)
if(holder in active_holders)
return
//Process once, right away. If we still need to continue then add to the active_holders list and continue later
if(holder.process_reactions())
active_holders += holder

View File

@@ -55,7 +55,7 @@
usr.client.debug_variables(antag) usr.client.debug_variables(antag)
message_admins("Admin [key_name_admin(usr)] is debugging the [antag.role_text] template.") message_admins("Admin [key_name_admin(usr)] is debugging the [antag.role_text] template.")
/client/proc/debug_controller(controller in list("Master","Ticker","Ticker Process","Air","Jobs","Sun","Radio","Supply","Shuttles","Emergency Shuttle","Configuration","pAI", "Cameras", "Transfer Controller", "Gas Data","Event","Plants","Alarm","Nano")) /client/proc/debug_controller(controller in list("Master","Ticker","Ticker Process","Air","Jobs","Sun","Radio","Supply","Shuttles","Emergency Shuttle","Configuration","pAI", "Cameras", "Transfer Controller", "Gas Data","Event","Plants","Alarm","Nano","Chemistry"))
set category = "Debug" set category = "Debug"
set name = "Debug Controller" set name = "Debug Controller"
set desc = "Debug the various periodic loop controllers for the game (be careful!)" set desc = "Debug the various periodic loop controllers for the game (be careful!)"
@@ -119,5 +119,8 @@
if("Nano") if("Nano")
debug_variables(nanomanager) debug_variables(nanomanager)
feedback_add_details("admin_verb", "DNano") feedback_add_details("admin_verb", "DNano")
if("Chemistry")
debug_variables(chemistryProcess)
feedback_add_details("admin_verb", "DChem")
message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.") message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.")
return return

View File

@@ -1,3 +1,5 @@
#define PROCESS_REACTION_ITER 5 //when processing a reaction, iterate this many times
/datum/reagents /datum/reagents
var/list/datum/reagent/reagent_list = list() var/list/datum/reagent/reagent_list = list()
var/total_volume = 0 var/total_volume = 0
@@ -11,6 +13,9 @@
/datum/reagents/Destroy() /datum/reagents/Destroy()
..() ..()
if(chemistryProcess)
chemistryProcess.active_holders -= src
for(var/datum/reagent/R in reagent_list) for(var/datum/reagent/R in reagent_list)
qdel(R) qdel(R)
reagent_list.Cut() reagent_list.Cut()
@@ -70,17 +75,22 @@
my_atom.reagents = null my_atom.reagents = null
/datum/reagents/proc/handle_reactions() /datum/reagents/proc/handle_reactions()
if(chemistryProcess)
chemistryProcess.mark_for_update(src)
//returns 1 if the holder should continue reactiong, 0 otherwise.
/datum/reagents/proc/process_reactions()
if(!my_atom) // No reactions in temporary holders if(!my_atom) // No reactions in temporary holders
return return 0
if(!my_atom.loc) //No reactions inside GC'd containers if(!my_atom.loc) //No reactions inside GC'd containers
return return 0
if(my_atom.flags & NOREACT) // No reactions here if(my_atom.flags & NOREACT) // No reactions here
return return 0
var/reaction_occured
var/list/effect_reactions = list()
var/list/eligible_reactions = list() var/list/eligible_reactions = list()
for(var/i in 1 to PROCESS_REACTION_ITER)
var/reaction_occured = 0
do
reaction_occured = 0 reaction_occured = 0
//need to rebuild this to account for chain reactions //need to rebuild this to account for chain reactions
@@ -88,17 +98,20 @@
eligible_reactions |= chemical_reactions_list[R.id] eligible_reactions |= chemical_reactions_list[R.id]
for(var/datum/chemical_reaction/C in eligible_reactions) for(var/datum/chemical_reaction/C in eligible_reactions)
if(!C.can_happen(src)) if(C.can_happen(src) && C.process(src))
continue effect_reactions |= C
if(C.process(src))
reaction_occured = 1 reaction_occured = 1
eligible_reactions.Cut() eligible_reactions.Cut()
while(reaction_occured) if(!reaction_occured)
break
for(var/datum/chemical_reaction/C in effect_reactions)
C.post_reaction(src)
update_total() update_total()
return return reaction_occured
/* Holder-to-chemical */ /* Holder-to-chemical */

View File

@@ -11,11 +11,24 @@
for(var/path in paths) for(var/path in paths)
var/datum/chemical_reaction/D = new path() var/datum/chemical_reaction/D = new path()
if(D.required_reagents && D.required_reagents.len) if(D.required_reagents && D.required_reagents.len)
var/reagent_id = D.required_reagents[1] var/reagent_id = D.required_reagents[1]
if(!chemical_reactions_list[reagent_id])
chemical_reactions_list[reagent_id] = list()
chemical_reactions_list[reagent_id] += D chemical_reactions_list[reagent_id] += D
//helper that ensures the reaction rate holds after iterating
//Ex. REACTION_RATE(0.3) means that 30% of the reagents will react each chemistry tick (~2 seconds by default).
#define REACTION_RATE(rate) (1.0 - (1.0-rate)**(1.0/PROCESS_REACTION_ITER))
//helper to define reaction rate in terms of half-life
//Ex.
//HALF_LIFE(0) -> Reaction completes immediately (default chems)
//HALF_LIFE(1) -> Half of the reagents react immediately, the rest after 1 chemistry ticks
//HALF_LIFE(2) -> Half of the reagents are consumed after 2 chemistry ticks.
//HALF_LIFE(3) -> Half of the reagents are consumed after 3 chemistry ticks.
#define HALF_LIFE(ticks) (ticks? 1.0 - (0.5)**(1.0/(ticks*PROCESS_REACTION_ITER)) : 1.0)
/datum/chemical_reaction /datum/chemical_reaction
var/name = null var/name = null
var/id = null var/id = null
@@ -23,8 +36,19 @@
var/list/required_reagents = list() var/list/required_reagents = list()
var/list/catalysts = list() var/list/catalysts = list()
var/list/inhibitors = list() var/list/inhibitors = list()
var/result_amount = 0 var/result_amount = 0
//how far the reaction proceeds each time it is processed. Used with either REACTION_RATE or HALF_LIFE macros.
var/reaction_rate = HALF_LIFE(0)
//if less than 1, the reaction will be inhibited if the ratio of products/reagents is too high.
//0.5 = 50% yield -> reaction will only proceed halfway until products are removed.
var/yield = 1.0
//If limits on reaction rate would leave less than this amount of any reagent (adjusted by the reaction ratios),
//the reaction goes to completion. This is to prevent reactions from going on forever with tiny reagent amounts.
var/min_reaction = 2
var/mix_message = "The solution begins to bubble." var/mix_message = "The solution begins to bubble."
var/reaction_sound = 'sound/effects/bubbles.ogg' var/reaction_sound = 'sound/effects/bubbles.ogg'
@@ -48,27 +72,69 @@
return 1 return 1
/datum/chemical_reaction/proc/calc_reaction_progress(var/datum/reagents/holder, var/reaction_limit)
var/progress = reaction_limit * reaction_rate //simple exponential progression
//calculate yield
if(1-yield > 0.001) //if yield ratio is big enough just assume it goes to completion
/*
Determine the max amount of product by applying the yield condition:
(max_product/result_amount) / reaction_limit == yield/(1-yield)
We make use of the fact that:
reaction_limit = (holder.get_reagent_amount(reactant) / required_reagents[reactant]) of the limiting reagent.
*/
var/yield_ratio = yield/(1-yield)
var/max_product = yield_ratio * reaction_limit * result_amount //rearrange to obtain max_product
var/yield_limit = max(0, max_product - holder.get_reagent_amount(result))/result_amount
progress = min(progress, yield_limit) //apply yield limit
//apply min reaction progress - wasn't sure if this should go before or after applying yield
//I guess people can just have their miniscule reactions go to completion regardless of yield.
for(var/reactant in required_reagents)
var/remainder = holder.get_reagent_amount(reactant) - progress*required_reagents[reactant]
if(remainder <= min_reaction*required_reagents[reactant])
progress = reaction_limit
break
return progress
/datum/chemical_reaction/proc/process(var/datum/reagents/holder) /datum/chemical_reaction/proc/process(var/datum/reagents/holder)
//determine how far the reaction can proceed //determine how far the reaction can proceed
var/list/reaction_limits = list() var/list/reaction_limits = list()
for(var/reactant in required_reagents) for(var/reactant in required_reagents)
reaction_limits += holder.get_reagent_amount(reactant) / required_reagents[reactant] reaction_limits += holder.get_reagent_amount(reactant) / required_reagents[reactant]
//determine how far the reaction proceeds
var/reaction_limit = min(reaction_limits) var/reaction_limit = min(reaction_limits)
var/progress_limit = calc_reaction_progress(holder, reaction_limit)
var/reaction_progress = min(reaction_limit, progress_limit) //no matter what, the reaction progress cannot exceed the stoichiometric limit.
//need to obtain the new reagent's data before anything is altered //need to obtain the new reagent's data before anything is altered
var/data = send_data(holder, reaction_limit) var/data = send_data(holder, reaction_progress)
//remove the reactants //remove the reactants
for(var/reactant in required_reagents) for(var/reactant in required_reagents)
var/amt_used = required_reagents[reactant] * reaction_limit var/amt_used = required_reagents[reactant] * reaction_progress
holder.remove_reagent(reactant, amt_used, safety = 1) holder.remove_reagent(reactant, amt_used, safety = 1)
//add the product //add the product
var/amt_produced = result_amount * reaction_limit var/amt_produced = result_amount * reaction_progress
if(result) if(result)
holder.add_reagent(result, amt_produced, data) holder.add_reagent(result, amt_produced, data, safety = 1)
//produces messages and sounds on_reaction(holder, amt_produced)
return reaction_progress
//called when a reaction processes
/datum/chemical_reaction/proc/on_reaction(var/datum/reagents/holder, var/created_volume)
return
//called after processing reactions, if they occurred
/datum/chemical_reaction/proc/post_reaction(var/datum/reagents/holder)
var/atom/container = holder.my_atom var/atom/container = holder.my_atom
if(mix_message && container && !ismob(container)) if(mix_message && container && !ismob(container))
var/turf/T = get_turf(container) var/turf/T = get_turf(container)
@@ -76,14 +142,6 @@
for(var/mob/M in seen) for(var/mob/M in seen)
M.show_message("<span class='notice'>\icon[container] [mix_message]</span>", 1) M.show_message("<span class='notice'>\icon[container] [mix_message]</span>", 1)
playsound(T, reaction_sound, 80, 1) playsound(T, reaction_sound, 80, 1)
on_reaction(holder, amt_produced)
return reaction_limit
//called after a reaction occurs
/datum/chemical_reaction/proc/on_reaction(var/datum/reagents/holder, var/created_volume)
return
//obtains any special data that will be provided to the reaction products //obtains any special data that will be provided to the reaction products
//this is called just before reactants are removed. //this is called just before reactants are removed.