diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm index a959544e49..ec8de8380f 100644 --- a/code/modules/reagents/chemistry/holder.dm +++ b/code/modules/reagents/chemistry/holder.dm @@ -338,6 +338,13 @@ update_total() /datum/reagents/proc/handle_reactions()//HERE EDIT HERE THE MAIN REACTION FERMICHEMS ASSEMBLE! I hope rp is similar + //FermiChem + var/purity = 1 + var/ammoReacted = 0 + var/deltaT = 0 + var/deltapH = 0 + var/stepChemAmmount = 0 + var/list/cached_reagents = reagent_list //a list of the reagents? var/list/cached_reactions = GLOB.chemical_reactions_list //a list of the whole reactions? var/datum/cached_my_atom = my_atom //It says my atom, but I didn't bring one with me!! @@ -369,6 +376,7 @@ var/has_special_react = C.special_react var/can_special_react = 0 //FermiChem WHY ARE VARIBLES SO ESTRANGED it makes me sad + /* var/OptimalTempMin = C.OptimalTempMin // Lower area of bell curve for determining heat based rate reactions var/OptimalTempMax = C.OptimalTempMax var/ExplodeTemp = C.ExplodeTemp @@ -384,14 +392,16 @@ var/FermiChem = C.FermiChem var/FermiExplode = C.FermiExplode var/ImpureChem = C.ImpureChem - + */ //FermiChem + /* var/purity = 1 var/ammoReacted = 0 var/deltaT = 0 var/deltapH = 0 var/stepChemAmmount = 0 + */ for(var/B in cached_required_reagents) if(!has_reagent(B, cached_required_reagents[B])) @@ -425,12 +435,6 @@ matching_other = 1 //FermiChem - if (chem_temp > ExplodeTemp)//Check to see if reaction is too hot! - if (FermiExplode == TRUE) - //To be added! - else - FermiExplode() - //explode function!! if(required_temp == 0 || (is_cold_recipe && chem_temp <= required_temp) || (!is_cold_recipe && chem_temp >= required_temp))//Temperature check!! meets_temp_requirement = 1//binary pass @@ -454,6 +458,7 @@ selected_reaction = competitor var/list/cached_required_reagents = selected_reaction.required_reagents//update reagents list var/list/cached_results = selected_reaction.results//resultant chemical list + var/special_react_result = selected_reaction.check_special_react(src) var/list/multiplier = INFINITY //Wat for(var/B in cached_required_reagents) // @@ -462,84 +467,86 @@ //Splits reactions into two types; FermiChem is advanced reaction mechanics, Other is default reaction. //FermiChem relies on two additional properties; pH and impurity //Temperature plays into a larger role too. - if (C.FermiChem == TRUE) - message_admins("Hee!!!! Someone is doing a Fermi reaction!!! I'm so excited!!") - //FermiReact(C) - //B is Beaker - //P is product - multiplier = min(multiplier, round(get_reagent_amount(B) / cached_required_reagents[B]))//a simple one over the other? (Is this for multiplying end product? Useful for toxinsludge buildup) + if(selected_reaction) + var/datum/chemical_reaction/C = selected_reaction + if (C.FermiChem == TRUE) + message_admins("Hee!!!! Someone is doing a Fermi reaction!!! I'm so excited!!") + //FermiReact(C) + //B is Beaker + //P is product + multiplier = min(multiplier, round(get_reagent_amount(B) / cached_required_reagents[B]))//a simple one over the other? (Is this for multiplying end product? Useful for toxinsludge buildup) - while (ammoReacted < multiplier) - //Begin Parse + while (ammoReacted < multiplier) + //Begin Parse - //Check extremes first - if (chem_temp > ExplodeTemp) - //go to explode proc - FermiExplode() + //Check extremes first + if (chem_temp > C.ExplodeTemp) + //go to explode proc + FermiExplode() - if (pH > 14 || pH < 0) - //Create chemical sludge eventually(for now just destroy the beaker I guess?) - //TODO Strong acids eat glass, make it so you NEED plastic beakers for superacids(for some reactions) - FermiExplode() + if (pH > 14 || pH < 0) + //Create chemical sludge eventually(for now just destroy the beaker I guess?) + //TODO Strong acids eat glass, make it so you NEED plastic beakers for superacids(for some reactions) + FermiExplode() - //For now, purity is handled elsewhere + //For now, purity is handled elsewhere - //Calculate DeltaT (Deviation of T from optimal) - if (chem_temp < OptimalTempMax) - deltaT = (((OptimalTempMin - chem_temp)**CurveSharpT)/((OptimalTempMax - OptimalTempMax)**CurveSharpT)) - else if (chem_temp >= C.OptimalTempMax) - deltaT = 1 - else - deltaT = 0 - - //Calculate DeltapH (Deviation of pH from optimal) - //Lower range - if (pH < C.OptimalpHMin) - if (pH < (C.OptimalpHMin - C.ReactpHLim)) - deltapH = 0 + //Calculate DeltaT (Deviation of T from optimal) + if (chem_temp < C.OptimalTempMax) + deltaT = (((C.OptimalTempMin - chem_temp)**C.CurveSharpT)/((C.OptimalTempMax - C.OptimalTempMax)**C.CurveSharpT)) + else if (chem_temp >= C.OptimalTempMax) + deltaT = 1 else - deltapH = (((pH - (C.OptimalpHMin - C.ReactpHLim))**C.CurveSharp)/(C.ReactpHLim**C.CurveSharppH)) - //Upper range - else if (pH > C.OptimalpHMin) - if (pH > (C.OptimalpHMin + C.ReactpHLim)) - deltapH = 0 + deltaT = 0 + + //Calculate DeltapH (Deviation of pH from optimal) + //Lower range + if (pH < C.OptimalpHMin) + if (pH < (C.OptimalpHMin - C.ReactpHLim)) + deltapH = 0 + else + deltapH = (((pH - (C.OptimalpHMin - C.ReactpHLim))**C.CurveSharppH)/(C.ReactpHLim**C.CurveSharppH)) + //Upper range + else if (pH > C.OptimalpHMin) + if (pH > (C.OptimalpHMin + C.ReactpHLim)) + deltapH = 0 + else + deltapH = ((C.ReactpHLim -(pH - (C.OptimalpHMax + C.ReactpHLim))+C.ReactpHLim)/(C.ReactpHLim**C.CurveSharppH)) + //Within mid range + else if (pH >= C.OptimalpHMin && pH <= C.OptimalpHMax) + deltapH = 1 + //This should never proc: else - deltapH = ((C.ReactpHLim -(pH - (C.OptimalpHMax + C.ReactpHLim))+C.ReactpHLim)/(C.ReactpHLim**C.CurveSharppH)) - //Within mid range - else if (pH >= C.OptimalpHMin && pH <= C.OptimalpHMax) - deltapH = 1 - //This should never proc: - else - message_admins("Fermichem's pH broke!! Please let Fermis know!!") - WARNING("[my_atom] attempted to determine FermiChem pH for '[reagent]' which broke for some reason! ([usr])") - //TODO Add CatalystFact + message_admins("Fermichem's pH broke!! Please let Fermis know!!") + WARNING("[my_atom] attempted to determine FermiChem pH for '[C]' which broke for some reason! ([usr])") + //TODO Add CatalystFact - stepChemAmmount = multiplier * deltaT - if (ammoReacted > 0) - P.purity = ((P.purity * ammoReacted) + (deltapH * stepChemAmmount)) /(2 * (ammoReacted + stepChemAmmount)) //This should add the purity to the product - else - P.purity = deltapH - //Apply pH changes and thermal output of reaction to beaker - chem_temp += (C.ThermicConstant * stepChemAmmount) - pH += (C.HIonRelease * stepChemAmmount) + stepChemAmmount = multiplier * deltaT + if (ammoReacted > 0) + P.purity = ((P.purity * ammoReacted) + (deltapH * stepChemAmmount)) /(2 * (ammoReacted + stepChemAmmount)) //This should add the purity to the product + else + P.purity = deltapH + //Apply pH changes and thermal output of reaction to beaker + chem_temp += (C.ThermicConstant * stepChemAmmount) + pH += (C.HIonRelease * stepChemAmmount) - // End. + // End. - selected_reaction.on_reaction(src, multiplier, special_react_result) + selected_reaction.on_reaction(src, multiplier, special_react_result) - for(var/B in cached_required_reagents) - remove_reagent(B, (stepChemAmmount * cached_required_reagents[B]), safety = 1)//safety? removes reagents from beaker using remove function. + for(var/B in cached_required_reagents) + remove_reagent(B, (stepChemAmmount * cached_required_reagents[B]), safety = 1)//safety? removes reagents from beaker using remove function. - for(var/P in selected_reaction.results)//Not sure how this works, what is selected_reaction.results? - multiplier = max(multiplier, 1) //this shouldnt happen ... - SSblackbox.record_feedback("tally", "chemical_reaction", cached_results[P]*stepChemAmmount, P)//log - add_reagent(P, cached_results[P]*stepChemAmmount, null, chem_temp)//add reagent function!! I THINK I can do this: + for(var/P in selected_reaction.results)//Not sure how this works, what is selected_reaction.results? + multiplier = max(multiplier, 1) //this shouldnt happen ... + SSblackbox.record_feedback("tally", "chemical_reaction", cached_results[P]*stepChemAmmount, P)//log + add_reagent(P, cached_results[P]*stepChemAmmount, null, chem_temp)//add reagent function!! I THINK I can do this: - ammoReacted = ammoReacted + stepChemAmmount + ammoReacted = ammoReacted + stepChemAmmount - reaction_occurred = 1 - SSblackbox.record_feedback("tally", "Fermi_chemical_reaction", cached_results[P]*ammoReacted, P)//log + reaction_occurred = 1 + SSblackbox.record_feedback("tally", "Fermi_chemical_reaction", cached_results[P]*ammoReacted, P)//log //Standard reaction mechanics: else: diff --git a/code/modules/reagents/chemistry/holderBackup.dm b/code/modules/reagents/chemistry/holderBackup.dm new file mode 100644 index 0000000000..b434b348f0 --- /dev/null +++ b/code/modules/reagents/chemistry/holderBackup.dm @@ -0,0 +1,952 @@ + +/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 + var/list/datum/reagent/reagent_list = new/list() + var/total_volume = 0 + var/maximum_volume = 100 + var/atom/my_atom = null + var/chem_temp = 150 + var/pH = REAGENT_NORMAL_PH + var/last_tick = 1 + var/addiction_tick = 1 + var/list/datum/reagent/addiction_list = new/list() + var/reagents_holder_flags + +/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) + build_chemical_reagent_list() + if(!GLOB.chemical_reactions_list) + build_chemical_reactions_list() + +/datum/reagents/Destroy() + . = ..() + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + qdel(R) + cached_reagents.Cut() + cached_reagents = null + if(my_atom && my_atom.reagents == src) + my_atom.reagents = null + my_atom = null + +// Used in attack logs for reagents in pills and such +/datum/reagents/proc/log_list() + if(!length(reagent_list)) + return "no reagents" + + var/list/data = list() + for(var/r in reagent_list) //no reagents will be left behind + var/datum/reagent/R = r + data += "[R.id] ([round(R.volume, 0.1)]u)" + //Using IDs because SOME chemicals (I'm looking at you, chlorhydrate-beer) have the same names as other chemicals. + return english_list(data) + +/datum/reagents/proc/remove_any(amount = 1) + var/list/cached_reagents = reagent_list + var/total_transfered = 0 + var/current_list_element = 1 + + current_list_element = rand(1, cached_reagents.len) + + while(total_transfered != amount) + if(total_transfered >= amount) + break + if(total_volume <= 0 || !cached_reagents.len) + break + + if(current_list_element > cached_reagents.len) + current_list_element = 1 + + var/datum/reagent/R = cached_reagents[current_list_element] + remove_reagent(R.id, 1) + + current_list_element++ + total_transfered++ + update_total() + + handle_reactions() + return total_transfered + +/datum/reagents/proc/remove_all(amount = 1) + var/list/cached_reagents = reagent_list + if(total_volume > 0) + var/part = amount / total_volume + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + remove_reagent(R.id, R.volume * part) + + update_total() + handle_reactions() + return amount + +/datum/reagents/proc/get_master_reagent_name() + var/list/cached_reagents = reagent_list + var/name + var/max_volume = 0 + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.volume > max_volume) + max_volume = R.volume + name = R.name + + return name + +/datum/reagents/proc/get_master_reagent_id() + var/list/cached_reagents = reagent_list + var/id + var/max_volume = 0 + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.volume > max_volume) + max_volume = R.volume + id = R.id + + return id + +/datum/reagents/proc/get_master_reagent() + var/list/cached_reagents = reagent_list + var/datum/reagent/master + var/max_volume = 0 + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.volume > max_volume) + max_volume = R.volume + master = R + + return master + +/datum/reagents/proc/trans_to(obj/target, amount=1, multiplier=1, preserve_data=1, no_react = 0)//if preserve_data=0, the reagents data will be lost. Usefull if you use data for some strange stuff and don't want it to be transferred. + var/list/cached_reagents = reagent_list + if(!target || !total_volume) + return + if(amount < 0) + return + + var/datum/reagents/R + if(istype(target, /datum/reagents)) + R = target + else + if(!target.reagents) + return + R = target.reagents + amount = min(min(amount, src.total_volume), R.maximum_volume-R.total_volume) + var/part = amount / src.total_volume + var/trans_data = null + for(var/reagent in cached_reagents) + var/datum/reagent/T = reagent + var/transfer_amount = T.volume * part + if(preserve_data) + trans_data = copy_data(T) + + //fermichem Added ph TODO: add T.purity + R.add_reagent(T.id, transfer_amount * multiplier, trans_data, chem_temp, pH, no_react = TRUE) //we only handle reaction after every reagent has been transfered. + //R.add_reagent(T.id, transfer_amount * multiplier, trans_data, chem_temp, pH, T.purity, no_react = TRUE) //we only handle reaction after every reagent has been transfered. + remove_reagent(T.id, transfer_amount) + + update_total() + R.update_total() + if(!no_react) + R.handle_reactions() + src.handle_reactions() + return amount + +/datum/reagents/proc/copy_to(obj/target, amount=1, multiplier=1, preserve_data=1) + var/list/cached_reagents = reagent_list + if(!target || !total_volume) + return + + var/datum/reagents/R + if(istype(target, /datum/reagents)) + R = target + else + if(!target.reagents) + return + R = target.reagents + + if(amount < 0) + return + amount = min(min(amount, total_volume), R.maximum_volume-R.total_volume) + var/part = amount / total_volume + var/trans_data = null + for(var/reagent in cached_reagents) + var/datum/reagent/T = reagent + var/copy_amount = T.volume * part + if(preserve_data) + trans_data = T.data + R.add_reagent(T.id, copy_amount * multiplier, trans_data) + + src.update_total() + R.update_total() + R.handle_reactions() + src.handle_reactions() + return amount + +/datum/reagents/proc/trans_id_to(obj/target, reagent, amount=1, preserve_data=1)//Not sure why this proc didn't exist before. It does now! /N + var/list/cached_reagents = reagent_list + if (!target) + return + if (!target.reagents || src.total_volume<=0 || !src.get_reagent_amount(reagent)) + return + if(amount < 0) + return + + var/datum/reagents/R = target.reagents + if(src.get_reagent_amount(reagent)= R.overdose_threshold && !R.overdosed) + R.overdosed = 1 + need_mob_update += R.overdose_start(C) + if(R.addiction_threshold) + if(R.volume >= R.addiction_threshold && !is_type_in_list(R, cached_addictions)) + var/datum/reagent/new_reagent = new R.type() + cached_addictions.Add(new_reagent) + if(R.overdosed) + need_mob_update += R.overdose_process(C) + if(is_type_in_list(R,cached_addictions)) + for(var/addiction in cached_addictions) + var/datum/reagent/A = addiction + if(istype(R, A)) + A.addiction_stage = -15 // you're satisfied for a good while. + need_mob_update += R.on_mob_life(C) + + if(can_overdose) + if(addiction_tick == 6) + addiction_tick = 1 + for(var/addiction in cached_addictions) + var/datum/reagent/R = addiction + if(C && R) + R.addiction_stage++ + if(1 <= R.addiction_stage && R.addiction_stage <= R.addiction_stage1_end) + need_mob_update += R.addiction_act_stage1(C) + else if(R.addiction_stage1_end <= R.addiction_stage && R.addiction_stage <= R.addiction_stage2_end) + need_mob_update += R.addiction_act_stage2(C) + else if(R.addiction_stage2_end <= R.addiction_stage && R.addiction_stage <= R.addiction_stage3_end) + need_mob_update += R.addiction_act_stage3(C) + else if(R.addiction_stage3_end <= R.addiction_stage && R.addiction_stage <= R.addiction_stage4_end) + need_mob_update += R.addiction_act_stage4(C) + else if(R.addiction_stage4_end <= R.addiction_stage) + to_chat(C, "You feel like you've gotten over your need for [R.name].") + SEND_SIGNAL(C, COMSIG_CLEAR_MOOD_EVENT, "[R.id]_addiction") + cached_addictions.Remove(R) + addiction_tick++ + if(C && need_mob_update) //some of the metabolized reagents had effects on the mob that requires some updates. + C.updatehealth() + C.update_canmove() + C.update_stamina() + update_total() + + +/datum/reagents/proc/set_reacting(react = TRUE) + if(react) + reagents_holder_flags &= ~(REAGENT_NOREACT) + else + reagents_holder_flags |= REAGENT_NOREACT + +/datum/reagents/proc/conditional_update_move(atom/A, Running = 0) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + R.on_move (A, Running) + update_total() + +/datum/reagents/proc/conditional_update(atom/A) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + R.on_update (A) + update_total() + +/datum/reagents/proc/handle_reactions()//HERE EDIT HERE THE MAIN REACTION FERMICHEMS ASSEMBLE! I hope rp is similar + //FermiChem + var/purity = 1 + var/ammoReacted = 0 + var/deltaT = 0 + var/deltapH = 0 + var/stepChemAmmount = 0 + + var/list/cached_reagents = reagent_list //a list of the reagents? + var/list/cached_reactions = GLOB.chemical_reactions_list //a list of the whole reactions? + var/datum/cached_my_atom = my_atom //It says my atom, but I didn't bring one with me!! + if(reagents_holder_flags & REAGENT_NOREACT) //Not sure on reagents_holder_flags, but I think it checks to see if theres a reaction with current stuff. + return //Yup, no reactions here. No siree. + + var/reaction_occurred = 0 // checks if reaction, binary variable + do //What does do do in byond? It sounds very redundant? is it a while loop? + var/list/possible_reactions = list() //init list + reaction_occurred = 0 // sets it back to 0? + for(var/reagent in cached_reagents) //for reagent in beaker/holder + var/datum/reagent/R = reagent //check to make sure that reagent is there for the reaction list + for(var/reaction in cached_reactions[R.id]) // Was a big list but now it should be smaller since we filtered it with our reagent id + if(!reaction) + continue + + var/datum/chemical_reaction/C = reaction + var/list/cached_required_reagents = C.required_reagents + var/total_required_reagents = cached_required_reagents.len + var/total_matching_reagents = 0 + var/list/cached_required_catalysts = C.required_catalysts + var/total_required_catalysts = cached_required_catalysts.len + var/total_matching_catalysts= 0 + var/matching_container = 0 + var/matching_other = 0 + var/required_temp = C.required_temp + var/is_cold_recipe = C.is_cold_recipe + var/meets_temp_requirement = 0 + var/has_special_react = C.special_react + var/can_special_react = 0 + //FermiChem WHY ARE VARIBLES SO ESTRANGED it makes me sad + var/OptimalTempMin = C.OptimalTempMin // Lower area of bell curve for determining heat based rate reactions + var/OptimalTempMax = C.OptimalTempMax + var/ExplodeTemp = C.ExplodeTemp + var/OptimalpHMin = C.OptimalpHMin + var/OptimalpHMax = C.OptimalpHMax + var/ReactpHLim = C.ReactpHLim + //var/CatalystFact = C.CatalystFact + var/CurveSharpT = C.CurveSharpT + var/CurveSharppH = C.CurveSharppH + var/ThermicConstant = C.ThermicConstant + var/HIonRelease = C.HIonRelease + var/RateUpLim = C.RateUpLim + var/FermiChem = C.FermiChem + var/FermiExplode = C.FermiExplode + var/ImpureChem = C.ImpureChem + + //FermiChem + /* + var/purity = 1 + var/ammoReacted = 0 + + var/deltaT = 0 + var/deltapH = 0 + var/stepChemAmmount = 0 + */ + + for(var/B in cached_required_reagents) + if(!has_reagent(B, cached_required_reagents[B])) + break + total_matching_reagents++ + for(var/B in cached_required_catalysts) + if(!has_reagent(B, cached_required_catalysts[B])) + break + total_matching_catalysts++ + if(cached_my_atom) + if(!C.required_container) + matching_container = 1 + + else + if(cached_my_atom.type == C.required_container)//if the suspected container is a container + matching_container = 1 + if (isliving(cached_my_atom) && !C.mob_react) //Makes it so certain chemical reactions don't occur in mobs + return + if(!C.required_other)//Checks for other things required + matching_other = 1//binary check passes + + else if(istype(cached_my_atom, /obj/item/slime_extract))//if the object is a slime_extract. This might be complicated as to not break them via fermichem + var/obj/item/slime_extract/M = cached_my_atom + + if(M.Uses > 0) // added a limit to slime cores -- Muskets requested this + matching_other = 1 + else + if(!C.required_container)//I'm not sure why this is here twice, I think if it's not a beaker? Oh, cyro. + matching_container = 1 + if(!C.required_other) + matching_other = 1 + + //FermiChem + + if(required_temp == 0 || (is_cold_recipe && chem_temp <= required_temp) || (!is_cold_recipe && chem_temp >= required_temp))//Temperature check!! + meets_temp_requirement = 1//binary pass + + if(!has_special_react || C.check_special_react(src)) + can_special_react = 1 + + if(total_matching_reagents == total_required_reagents && total_matching_catalysts == total_required_catalysts && matching_container && matching_other && meets_temp_requirement && can_special_react) + possible_reactions += C + + if(possible_reactions.len)//does list exist? + var/datum/chemical_reaction/selected_reaction = possible_reactions[1] + //select the reaction with the most extreme temperature requirements + for(var/V in possible_reactions)//why V, surely that would indicate volume? V is the reaction potential. + var/datum/chemical_reaction/competitor = V //competitor? I think this is theres two of them. Troubling..! + 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)//only returns with lower if reaction "is cold" var. + selected_reaction = competitor + else + if(competitor.required_temp >= selected_reaction.required_temp) //will return with the hotter reacting first. + selected_reaction = competitor + var/list/cached_required_reagents = selected_reaction.required_reagents//update reagents list + var/list/cached_results = selected_reaction.results//resultant chemical list + var/special_react_result = selected_reaction.check_special_react(src) + var/list/multiplier = INFINITY //Wat + + for(var/B in cached_required_reagents) // + multiplier = min(multiplier, round(get_reagent_amount(B) / cached_required_reagents[B]))//a simple one over the other? (Is this for multiplying end product? Useful for toxinsludge buildup) + + //Splits reactions into two types; FermiChem is advanced reaction mechanics, Other is default reaction. + //FermiChem relies on two additional properties; pH and impurity + //Temperature plays into a larger role too. + if (C.FermiChem == TRUE) + message_admins("Hee!!!! Someone is doing a Fermi reaction!!! I'm so excited!!") + //FermiReact(C) + //B is Beaker + //P is product + multiplier = min(multiplier, round(get_reagent_amount(B) / cached_required_reagents[B]))//a simple one over the other? (Is this for multiplying end product? Useful for toxinsludge buildup) + + while (ammoReacted < multiplier) + //Begin Parse + + //Check extremes first + if (chem_temp > ExplodeTemp) + //go to explode proc + FermiExplode() + + if (pH > 14 || pH < 0) + //Create chemical sludge eventually(for now just destroy the beaker I guess?) + //TODO Strong acids eat glass, make it so you NEED plastic beakers for superacids(for some reactions) + FermiExplode() + + //For now, purity is handled elsewhere + + //Calculate DeltaT (Deviation of T from optimal) + if (chem_temp < C.OptimalTempMax) + deltaT = (((C.OptimalTempMin - chem_temp)**C.CurveSharpT)/((C.OptimalTempMax - C.OptimalTempMax)**C.CurveSharpT)) + else if (chem_temp >= C.OptimalTempMax) + deltaT = 1 + else + deltaT = 0 + + //Calculate DeltapH (Deviation of pH from optimal) + //Lower range + if (pH < C.OptimalpHMin) + if (pH < (C.OptimalpHMin - C.ReactpHLim)) + deltapH = 0 + else + deltapH = (((pH - (C.OptimalpHMin - C.ReactpHLim))**C.CurveSharp)/(C.ReactpHLim**C.CurveSharppH)) + //Upper range + else if (pH > C.OptimalpHMin) + if (pH > (C.OptimalpHMin + C.ReactpHLim)) + deltapH = 0 + else + deltapH = ((C.ReactpHLim -(pH - (C.OptimalpHMax + C.ReactpHLim))+C.ReactpHLim)/(C.ReactpHLim**C.CurveSharppH)) + //Within mid range + else if (pH >= C.OptimalpHMin && pH <= C.OptimalpHMax) + deltapH = 1 + //This should never proc: + else + message_admins("Fermichem's pH broke!! Please let Fermis know!!") + WARNING("[my_atom] attempted to determine FermiChem pH for '[reagent]' which broke for some reason! ([usr])") + //TODO Add CatalystFact + + stepChemAmmount = multiplier * deltaT + if (ammoReacted > 0) + P.purity = ((P.purity * ammoReacted) + (deltapH * stepChemAmmount)) /(2 * (ammoReacted + stepChemAmmount)) //This should add the purity to the product + else + P.purity = deltapH + //Apply pH changes and thermal output of reaction to beaker + chem_temp += (C.ThermicConstant * stepChemAmmount) + pH += (C.HIonRelease * stepChemAmmount) + + // End. + + selected_reaction.on_reaction(src, multiplier, special_react_result) + + for(var/B in cached_required_reagents) + remove_reagent(B, (stepChemAmmount * cached_required_reagents[B]), safety = 1)//safety? removes reagents from beaker using remove function. + + for(var/P in selected_reaction.results)//Not sure how this works, what is selected_reaction.results? + multiplier = max(multiplier, 1) //this shouldnt happen ... + SSblackbox.record_feedback("tally", "chemical_reaction", cached_results[P]*stepChemAmmount, P)//log + add_reagent(P, cached_results[P]*stepChemAmmount, null, chem_temp)//add reagent function!! I THINK I can do this: + + + ammoReacted = ammoReacted + stepChemAmmount + + reaction_occurred = 1 + SSblackbox.record_feedback("tally", "Fermi_chemical_reaction", cached_results[P]*ammoReacted, P)//log + //Standard reaction mechanics: + else: + + for(var/B in cached_required_reagents) // + multiplier = min(multiplier, round(get_reagent_amount(B) / cached_required_reagents[B]))//a simple one over the other? (Is this for multiplying end product? Useful for toxinsludge buildup) + + for(var/B in cached_required_reagents) + remove_reagent(B, (multiplier * cached_required_reagents[B]), safety = 1)//safety? removes reagents from beaker using remove function. + + for(var/P in selected_reaction.results)//Not sure how this works, what is selected_reaction.results? + multiplier = max(multiplier, 1) //this shouldnt happen ... + SSblackbox.record_feedback("tally", "chemical_reaction", cached_results[P]*multiplier, P)//log + add_reagent(P, cached_results[P]*multiplier, null, chem_temp)//add reagent function!! I THINK I can do this: + + + var/list/seen = viewers(4, get_turf(my_atom))//Sound and sight checkers + 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) + + for(var/mob/M in seen) + to_chat(M, "[iconhtml] [selected_reaction.mix_message]") + + if(istype(cached_my_atom, /obj/item/slime_extract))//if there's an extract and it's used up. + 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, special_react_result) + reaction_occurred = 1 + + while(reaction_occurred)//while do nothing? + update_total()//Don't know waht this does. + return 0//end! + +/datum/reagents/proc/FermiReact() + return + +/datum/reagents/proc/FermiExplode() + return + +/datum/reagents/proc/isolate_reagent(reagent) + var/list/cached_reagents = reagent_list + for(var/_reagent in cached_reagents) + var/datum/reagent/R = _reagent + if(R.id != reagent) + del_reagent(R.id) + update_total() + +/datum/reagents/proc/del_reagent(reagent) + var/list/cached_reagents = reagent_list + for(var/_reagent in cached_reagents) + var/datum/reagent/R = _reagent + if(R.id == reagent) + if(my_atom && isliving(my_atom)) + var/mob/living/M = my_atom + R.on_mob_delete(M) + qdel(R) + reagent_list -= R + update_total() + if(my_atom) + my_atom.on_reagent_change(DEL_REAGENT) + return 1 + +/datum/reagents/proc/update_total() + var/list/cached_reagents = reagent_list + total_volume = 0 + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.volume < 0.1) + del_reagent(R.id) + else + total_volume += R.volume + + return 0 + +/datum/reagents/proc/clear_reagents() + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + del_reagent(R.id) + return 0 + +/datum/reagents/proc/reaction(atom/A, method = TOUCH, volume_modifier = 1, show_message = 1) + var/react_type + if(isliving(A)) + react_type = "LIVING" + if(method == INGEST) + var/mob/living/L = A + L.taste(src) + else if(isturf(A)) + react_type = "TURF" + else if(isobj(A)) + react_type = "OBJ" + else + return + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + switch(react_type) + if("LIVING") + var/touch_protection = 0 + if(method == VAPOR) + var/mob/living/L = A + touch_protection = L.get_permeability_protection() + R.reaction_mob(A, method, R.volume * volume_modifier, show_message, touch_protection) + if("TURF") + R.reaction_turf(A, R.volume * volume_modifier, show_message) + if("OBJ") + R.reaction_obj(A, R.volume * volume_modifier, show_message) + +/datum/reagents/proc/holder_full() + if(total_volume >= maximum_volume) + return TRUE + return FALSE + +//Returns the average specific heat for all reagents currently in this holder. +/datum/reagents/proc/specific_heat() + . = 0 + var/cached_amount = total_volume //cache amount + var/list/cached_reagents = reagent_list //cache reagents + for(var/I in cached_reagents) + var/datum/reagent/R = I + . += R.specific_heat * (R.volume / cached_amount) + +/datum/reagents/proc/adjust_thermal_energy(J, min_temp = 2.7, max_temp = 1000) + var/S = specific_heat() + chem_temp = CLAMP(chem_temp + (J / (S * total_volume)), 2.7, 1000) + +/datum/reagents/proc/add_reagent(reagent, amount, list/data=null, reagtemp = 300, pH = 7, no_react = 0)//EDIT HERE TOO ~FERMICHEM~ + if(!isnum(amount) || !amount) + return FALSE + + if(amount <= 0) + return FALSE + + var/datum/reagent/D = GLOB.chemical_reagents_list[reagent] + if(!D) + WARNING("[my_atom] attempted to add a reagent called '[reagent]' which doesn't exist. ([usr])") + return FALSE + + update_total() + var/cached_total = total_volume + if(cached_total + amount > maximum_volume) + amount = (maximum_volume - cached_total) //Doesnt fit in. Make it disappear. Shouldnt happen. Will happen. + if(amount <= 0) + return FALSE + var/new_total = cached_total + amount + var/cached_temp = chem_temp + var/list/cached_reagents = reagent_list + + //Equalize temperature - Not using specific_heat() because the new chemical isn't in yet. + var/specific_heat = 0 + var/thermal_energy = 0 + for(var/i in cached_reagents) + var/datum/reagent/R = i + specific_heat += R.specific_heat * (R.volume / new_total) + thermal_energy += R.specific_heat * R.volume * cached_temp + specific_heat += D.specific_heat * (amount / new_total) + thermal_energy += D.specific_heat * amount * reagtemp + chem_temp = thermal_energy / (specific_heat * new_total) + //// + + //add the reagent to the existing if it exists + for(var/A in cached_reagents) + var/datum/reagent/R = A + if (R.id == reagent) + R.volume += amount + update_total() + if(my_atom) + my_atom.on_reagent_change(ADD_REAGENT) + R.on_merge(data, amount) + if(!no_react) + handle_reactions() + return TRUE + + //otherwise make a new one + var/datum/reagent/R = new D.type(data) + cached_reagents += R + R.holder = src + R.volume = amount + if(data) + R.data = data + R.on_new(data) + + if(isliving(my_atom)) + R.on_mob_add(my_atom) //Must occur befor it could posibly run on_mob_delete + update_total() + if(my_atom) + my_atom.on_reagent_change(ADD_REAGENT) + if(!no_react) + handle_reactions() + return TRUE + +/datum/reagents/proc/add_reagent_list(list/list_reagents, list/data=null) // Like add_reagent but you can enter a list. Format it like this: list("toxin" = 10, "beer" = 15) + for(var/r_id in list_reagents) + var/amt = list_reagents[r_id] + add_reagent(r_id, amt, data) + +/datum/reagents/proc/remove_reagent(reagent, amount, safety)//Added a safety check for the trans_id_to + + if(isnull(amount)) + amount = 0 + CRASH("null amount passed to reagent code") + return FALSE + + if(!isnum(amount)) + return FALSE + + if(amount < 0) + return FALSE + + var/list/cached_reagents = reagent_list + + for(var/A in cached_reagents) + var/datum/reagent/R = A + if (R.id == reagent) + //clamp the removal amount to be between current reagent amount + //and zero, to prevent removing more than the holder has stored + amount = CLAMP(amount, 0, R.volume) + R.volume -= amount + update_total() + if(!safety)//So it does not handle reactions when it need not to + handle_reactions() + if(my_atom) + my_atom.on_reagent_change(REM_REAGENT) + return TRUE + + return FALSE + +/datum/reagents/proc/has_reagent(reagent, amount = -1) + var/list/cached_reagents = reagent_list + for(var/_reagent in cached_reagents) + var/datum/reagent/R = _reagent + if (R.id == reagent) + if(!amount) + return R + else + if(R.volume >= amount) + return R + else + return 0 + + return 0 + +/datum/reagents/proc/get_reagent_amount(reagent) + var/list/cached_reagents = reagent_list + for(var/_reagent in cached_reagents) + var/datum/reagent/R = _reagent + if (R.id == reagent) + return R.volume + + return 0 + +/datum/reagents/proc/get_reagents() + var/list/names = list() + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + names += R.name + + return jointext(names, ",") + +/datum/reagents/proc/remove_all_type(reagent_type, amount, strict = 0, safety = 1) // Removes all reagent of X type. @strict set to 1 determines whether the childs of the type are included. + if(!isnum(amount)) + return 1 + var/list/cached_reagents = reagent_list + var/has_removed_reagent = 0 + + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + var/matches = 0 + // Switch between how we check the reagent type + if(strict) + if(R.type == reagent_type) + matches = 1 + else + if(istype(R, reagent_type)) + matches = 1 + // We found a match, proceed to remove the reagent. Keep looping, we might find other reagents of the same type. + if(matches) + // Have our other proc handle removement + has_removed_reagent = remove_reagent(R.id, amount, safety) + + return has_removed_reagent + +//two helper functions to preserve data across reactions (needed for xenoarch) +/datum/reagents/proc/get_data(reagent_id) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.id == reagent_id) + return R.data + +/datum/reagents/proc/set_data(reagent_id, new_data) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.id == reagent_id) + R.data = new_data + +/datum/reagents/proc/copy_data(datum/reagent/current_reagent) + if(!current_reagent || !current_reagent.data) + return null + if(!istype(current_reagent.data, /list)) + return current_reagent.data + + var/list/trans_data = current_reagent.data.Copy() + + // We do this so that introducing a virus to a blood sample + // doesn't automagically infect all other blood samples from + // the same donor. + // + // Technically we should probably copy all data lists, but + // that could possibly eat up a lot of memory needlessly + // if most data lists are read-only. + if(trans_data["viruses"]) + var/list/v = trans_data["viruses"] + trans_data["viruses"] = v.Copy() + + return trans_data + +/datum/reagents/proc/get_reagent(type) + var/list/cached_reagents = reagent_list + . = locate(type) in cached_reagents + +/datum/reagents/proc/generate_taste_message(minimum_percent=15) + // the lower the minimum percent, the more sensitive the message is. + var/list/out = list() + var/list/tastes = list() //descriptor = strength + if(minimum_percent <= 100) + for(var/datum/reagent/R in reagent_list) + if(!R.taste_mult) + continue + + if(istype(R, /datum/reagent/consumable/nutriment)) + var/list/taste_data = R.data + for(var/taste in taste_data) + var/ratio = taste_data[taste] + var/amount = ratio * R.taste_mult * R.volume + if(taste in tastes) + tastes[taste] += amount + else + tastes[taste] = amount + else + var/taste_desc = R.taste_description + var/taste_amount = R.volume * R.taste_mult + if(taste_desc in tastes) + tastes[taste_desc] += taste_amount + else + tastes[taste_desc] = taste_amount + //deal with percentages + // TODO it would be great if we could sort these from strong to weak + var/total_taste = counterlist_sum(tastes) + if(total_taste > 0) + for(var/taste_desc in tastes) + var/percent = tastes[taste_desc]/total_taste * 100 + if(percent < minimum_percent) + continue + var/intensity_desc = "a hint of" + if(percent > minimum_percent * 2 || percent == 100) + intensity_desc = "" + else if(percent > minimum_percent * 3) + intensity_desc = "the strong flavor of" + if(intensity_desc != "") + out += "[intensity_desc] [taste_desc]" + else + out += "[taste_desc]" + + return english_list(out, "something indescribable") + +/datum/reagents/proc/expose_temperature(var/temperature, var/coeff=0.02) + var/temp_delta = (temperature - chem_temp) * coeff + if(temp_delta > 0) + chem_temp = min(chem_temp + max(temp_delta, 1), temperature) + else + chem_temp = max(chem_temp + min(temp_delta, -1), temperature) + chem_temp = round(chem_temp) + handle_reactions() + +/////////////////////////////////////////////////////////////////////////////////// + + +// Convenience proc to create a reagents holder for an atom +// Max vol is maximum volume of holder +/atom/proc/create_reagents(max_vol) + if(reagents) + qdel(reagents) + reagents = new/datum/reagents(max_vol) + reagents.my_atom = src + +/proc/get_random_reagent_id() // Returns a random reagent ID minus blacklisted reagents + var/static/list/random_reagents = list() + if(!random_reagents.len) + for(var/thing in subtypesof(/datum/reagent)) + var/datum/reagent/R = thing + if(initial(R.can_synth)) + random_reagents += initial(R.id) + var/picked_reagent = pick(random_reagents) + return picked_reagent