diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm
index c51c318e3e..9eec115250 100644
--- a/code/modules/reagents/chemistry/holder.dm
+++ b/code/modules/reagents/chemistry/holder.dm
@@ -53,6 +53,10 @@
var/addiction_tick = 1
var/list/datum/reagent/addiction_list = new/list()
var/reagents_holder_flags
+ var/targetVol = 0
+ var/reactedVol = 0
+ var/fermiIsReacting = FALSE
+ var/fermiReactID = null
/datum/reagents/New(maximum=100)
maximum_volume = maximum
@@ -337,205 +341,362 @@
R.on_update (A)
update_total()
-/datum/reagents/process()
- var/occured = handle_reactions(TRUE)
- if(!occured)
- return PROCESS_KILL
+/datum/reagents/proc/handle_reactions()//HERE EDIT HERE THE MAIN REACTION FERMICHEMS ASSEMBLE! I hope rp is similar
+ if(fermiIsReacting == TRUE)
+ //reagents_holder_flags |= REAGENT_NOREACT unsure if this is needed
+ return
+ 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.
-/datum/reagents/proc/handle_reactions(fermi_chem_react = FALSE)//HERE EDIT HERE THE MAIN REACTION FERMICHEMS ASSEMBLE! I hope rp is similar
- return
- if(reagents_holder_flags & REAGENT_NOREACT)
- return //don't react
- //cache things for performance
- var/list/cached_reagents = reagent_list
- var/list/cached_reactions = GLOB.chemical_reactions_list
- var/datum/cached_my_atom = my_atom
- var/reaction_occurred //if a reaction happened
- var/list/fermichem_reacted //keeps track of fermichem reactions that already reacted to prevent it from being instant
- do
- var/list/possible_reactions
- reaction_occurred = FALSE
- for(var/reagent in cached_reagents)
- var/datum/reagent/R = reagent
- for(var/reaction in cached_reactions[R.id])
+ var/reaction_occurred = 0 // checks if reaction, binary variable
+ var/continue_reacting = FALSE //Helps keep track what kind of reaction is occuring; standard or fermi.
+
+ //if(fermiIsReacting == TRUE)
+ /* if (reactedVol >= targetVol && targetVol != 0)
+ STOP_PROCESSING(SSprocessing, src)
+ fermiIsReacting = FALSE
+ message_admins("FermiChem processing stopped in reaction handler")
+ reaction_occurred = 1
+ return
+ else
+ message_admins("FermiChem processing passed in reaction handler")
+ return
+ */
+
+
+ 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/fail = FALSE
+ var/can_special_react = 0
+
+
for(var/B in cached_required_reagents)
if(!has_reagent(B, cached_required_reagents[B]))
- fail = TRUE
break
- if(fail)
- continue
+ total_matching_reagents++
for(var/B in cached_required_catalysts)
if(!has_reagent(B, cached_required_catalysts[B]))
- fail = TRUE
break
- if(fail)
- continue
+ total_matching_catalysts++
if(cached_my_atom)
- if(C.required_container && C.required_container != cached_my_atom.type)
- continue
+ 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
- continue
- if(C.required_other) //Checks for other things required
- 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
- continue
+ 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 || C.required_other)
- continue
- if(required_temp && (is_cold_recipe? (chem_temp > required_temp) : (chem_temp < required_temp)))
- continue
- if(has_special_react && !C.check_special_react(src))
- continue
- if(C.FermiChem) //fermichem checks
- if(chem_temp < C.OptimalTempMin) //too low temperature
- continue
- if(LAZYACCESS(fermichem_reacted, C)) //fermichems don't keep reacting instantly
- continue
- START_PROCESSING(SSchemistry, src) //start processing
- if(!fermi_chem_react) //only react fermichems on process icks
- continue
- LAZYADD(possible_reactions, C)
- if(length(possible_reactions)) //does list exist?
+ 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 (chem_temp > C.ExplodeTemp)//Check to see if reaction is too hot!
+ if (C.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
+
+ 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]
- for(var/I in possible_reactions)
- var/datum/chemical_reaction/competitor = I
- if(selected_reaction.is_cold_recipe? competitor.required_temp < selected_reaction.required_temp : competitor.required_temp > selected_reaction.required_temp)
- selected_reaction = competitor
+ //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.special_react
+ 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.
//BRANCH HERE
- var/multiplier = INFINITY //the multiplier of the stnadard "1 reaction" we managed to do this cycle
- if(!selected_reaction.FermiChem) //it's a normal ss13 chem reaction, instant reactions.
- for(var/B in cached_required_reagents)
- multiplier = min(multiplier, round(get_reagent_amount(B) / cached_required_reagents[B])) //cap by the multiplier of the required you can get out of this
- //remove reactants
- for(var/B in cached_required_reagents)
- remove_reagent(B, (multiplier * cached_required_reagents[B]), safety = TRUE)
- //add products
- for(var/P in cached_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)
- else //FermiiiCheeeem!
- //FERMICHEM BEGIN
- //--CHECKS
+ //if(selected_reaction)
+ var/datum/chemical_reaction/C = selected_reaction
+
+ if (C.FermiChem == TRUE && !continue_reacting)
message_admins("FermiChem Proc'd")
- var/target_volume = 0
- for(var/result_id in cached_results)
- target_volume += multiplier * cached_results[result_id]
- message_admins("FermiChem target volume: [target_volume]")
- //--PROCESS
- message_admins("FermiChem processing started")
- //--REACTION
- //set up fermichem variables
- var/deltaT = 0
- var/deltapH = 0
- var/stepChemAmount = 0
- var/purity = 1
- //For now, purity is handled elsewhere
- //check extremes first
- if(chem_temp > selected_reaction.ExplodeTemp)
- FermiExplode(selected_reaction)
- else if(!ISINRANGE(pH, 0, 14))
- //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()
- //Calculate DeltaT (Deviation of T from optimal)
- if (chem_temp < selected_reaction.OptimalTempMax && chem_temp >= selected_reaction.OptimalTempMin)
- deltaT = (((selected_reaction.OptimalTempMin - chem_temp)**selected_reaction.CurveSharpT)/((selected_reaction.OptimalTempMax - selected_reaction.OptimalTempMin)**selected_reaction.CurveSharpT))
- else if (chem_temp >= selected_reaction.OptimalTempMax)
- deltaT = 1
- else
- deltaT = 0
- message_admins("calculating temperature factor, min: [selected_reaction.OptimalTempMin], max: [selected_reaction.OptimalTempMax], Exponential: [selected_reaction.CurveSharpT], deltaT: [deltaT]")
- //Calculate DeltapH (Deviation of pH from optimal)
- //Lower range
- if (pH < selected_reaction.OptimalpHMin)
- if (pH < (selected_reaction.OptimalpHMin - selected_reaction.ReactpHLim))
- deltapH = 0
+
+ for(var/P in selected_reaction.results)
+ targetVol = cached_results[P]*multiplier
+ message_admins("FermiChem target volume: [targetVol]")
+
+ if (chem_temp > C.OptimalTempMin)//To prevent pointless reactions
+ //if (reactedVol < targetVol)
+ if (fermiIsReacting == TRUE)
+ return 0
else
- deltapH = (((pH - (selected_reaction.OptimalpHMin - selected_reaction.ReactpHLim))**selected_reaction.CurveSharppH)/(selected_reaction.ReactpHLim**selected_reaction.CurveSharppH))
- //Upper range
- else if (pH > selected_reaction.OptimalpHMin)
- if (pH > (selected_reaction.OptimalpHMin + selected_reaction.ReactpHLim))
- deltapH = 0
- else
- deltapH = ((selected_reaction.ReactpHLim -(pH - (selected_reaction.OptimalpHMax + selected_reaction.ReactpHLim))+selected_reaction.ReactpHLim)/(selected_reaction.ReactpHLim**selected_reaction.CurveSharppH))
- //Within mid range
- else if (pH >= selected_reaction.OptimalpHMin && pH <= selected_reaction.OptimalpHMax)
- deltapH = 1
- //This should never proc:
+ //reactedVol = FermiReact(selected_reaction, chem_temp, pH, multiplier, reactedVol, targetVol, cached_required_reagents, cached_results)
+ START_PROCESSING(SSprocessing, src)
+ message_admins("FermiChem processing started")
+ fermiIsReacting = TRUE
+ fermiReactID = selected_reaction
+ reaction_occurred = 1
+ //else
+ // fermiIsReacting = FALSE
+ // STOP_PROCESSING(SSfastprocess, src)
else
- message_admins("Fermichem's pH broke!! Please let Fermis know!!")
- WARNING("[my_atom] attempted to determine FermiChem pH for '[selected_reaction.id]' which broke for some reason! ([usr])")
- //TODO Add CatalystFact
- message_admins("calculating pH factor(purity), pH: [pH], min: [selected_reaction.OptimalpHMin]-[selected_reaction.ReactpHLim], max: [selected_reaction.OptimalpHMax]+[selected_reaction.ReactpHLim], deltapH: [deltapH]")
- stepChemAmount = target_volume * deltaT
- if (stepChemAmount > selected_reaction.RateUpLim)
- stepChemAmount = selected_reaction.RateUpLim
- else if (stepChemAmount <= 0.01)
- message_admins("stepChem underflow [stepChemAmount]")
- stepChemAmount = 0.02
- purity = deltapH
- message_admins("cached_results: [cached_results], stepChemAmount [stepChemAmount]")
- if(stepChemAmount > multiplier) //too much
- message_admins("stepChemAmount was over multiplier for some reason..?")
+ return 0
+
+
+ SSblackbox.record_feedback("tally", "Fermi_chemical_reaction", reactedVol, C.id)//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)
- message_admins("cached_required_reagents(B): [cached_required_reagents[B]], base stepChemAmount [stepChemAmount]")
- remove_reagent(B, (stepChemAmount * cached_required_reagents[B]), safety = TRUE)//safety? removes reagents from beaker using remove function.
+ remove_reagent(B, (multiplier * cached_required_reagents[B]), safety = 1)//safety? removes reagents from beaker using remove function.
- for(var/P in cached_results)//Not sure how this works, what is selected_reaction.results?
- //reactedVol = max(reactedVol, 1) //this shouldnt happen ...
- SSblackbox.record_feedback("tally", "chemical_reaction", cached_results[P]*stepChemAmount, P)//log
- add_reagent(P, cached_results[P] * stepChemAmount, null, chem_temp)//add reagent function!! I THINK I can do this:
- message_admins("purity: [purity], purity of beaker")
- message_admins("Temp before change: [chem_temp], pH after change: [pH]")
- //Apply pH changes and thermal output of reaction to beaker
- chem_temp += (selected_reaction.ThermicConstant * stepChemAmount)
- pH += (selected_reaction.HIonRelease * stepChemAmount)
- message_admins("Temp after change: [chem_temp], pH after change: [pH]")
- SSblackbox.record_feedback("tally", "Fermi_chemical_reaction", multiplier, selected_reaction.id)//log
- //ensure the same thing doesn't happen in the same tick
- LAZYSET(fermichem_reacted, selected_reaction, TRUE)
- //FERMICHEM END
- //reaction is done, do the effects.
- 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/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:
- 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
+ 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] \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 = TRUE
- while(reaction_occurred)
- update_total()
- return reaction_occurred
+ 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
+ continue_reacting = TRUE
+
+ while(reaction_occurred)//while do nothing?
+ update_total()//Don't know waht this does.
+ return 0//end!
+
+/datum/reagents/process()
+ var/datum/chemical_reaction/C = fermiReactID
+ var/list/cached_required_reagents = C.required_reagents//update reagents list
+ var/list/cached_results = C.results//resultant chemical list
+ var/multiplier = INFINITY
+
+ message_admins("updating targetVol from [targetVol]")
+ for(var/B in cached_required_reagents) //
+ multiplier = min(multiplier, round(get_reagent_amount(B) / cached_required_reagents[B]))
+ if (multiplier == 0)
+ STOP_PROCESSING(SSprocessing, src)
+ fermiIsReacting = FALSE
+ message_admins("FermiChem STOPPED due to reactant removal! Reacted vol: [reactedVol] of [targetVol]")
+ reactedVol = 0
+ targetVol = 0
+ handle_reactions()
+ update_total()
+ return
+ for(var/P in cached_results)
+ targetVol = cached_results[P]*multiplier
+
+ message_admins("to [targetVol]")
+
+ if (fermiIsReacting == FALSE)
+ message_admins("THIS SHOULD NEVER APPEAR!")
+
+ if (chem_temp > C.OptimalTempMin && fermiIsReacting == TRUE)//To prevent pointless reactions
+ if (reactedVol < targetVol)
+ reactedVol = FermiReact(fermiReactID, chem_temp, pH, reactedVol, targetVol, cached_required_reagents, cached_results)
+ message_admins("FermiChem tick activated started, Reacted vol: [reactedVol] of [targetVol]")
+ else
+ STOP_PROCESSING(SSprocessing, src)
+ fermiIsReacting = FALSE
+ message_admins("FermiChem STOPPED due to volume reached! Reacted vol: [reactedVol] of [targetVol]")
+ reactedVol = 0
+ targetVol = 0
+ handle_reactions()
+ update_total()
+ return
+ else
+ STOP_PROCESSING(SSprocessing, src)
+ message_admins("FermiChem STOPPED due to temperature! Reacted vol: [reactedVol] of [targetVol]")
+ fermiIsReacting = FALSE
+ reactedVol = 0
+ targetVol = 0
+ handle_reactions()
+ update_total()
+ return
+
+ //handle_reactions()
+
+/datum/reagents/proc/FermiReact(selected_reaction, chem_temp, pH, reactedVol, targetVol, cached_required_reagents, cached_results)
+ var/datum/chemical_reaction/C = selected_reaction
+ var/deltaT = 0
+ var/deltapH = 0
+ var/stepChemAmmount = 0
+ //var/ammoReacted = 0
+ //get purity from combined beaker reactant purities HERE.
+ var/purity = 1
+ //var/tempVol = totalVol
+
+ message_admins("Loop beginning")
+ //Begin Parse
+
+ //Check extremes first
+ if (chem_temp > C.ExplodeTemp)
+ //go to explode proc
+ message_admins("temperature is over limit: [C.ExplodeTemp] Current temperature: [chem_temp]")
+ 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)
+ message_admins("pH is lover limit, cur pH: [pH]")
+ FermiExplode()
+
+ //For now, purity is handled elsewhere
+
+ //Calculate DeltaT (Deviation of T from optimal)
+ if (chem_temp < C.OptimalTempMax && chem_temp >= C.OptimalTempMin)
+ deltaT = (((C.OptimalTempMin - chem_temp)**C.CurveSharpT)/((C.OptimalTempMax - C.OptimalTempMin)**C.CurveSharpT))
+ else if (chem_temp >= C.OptimalTempMax)
+ deltaT = 1
+ else
+ deltaT = 0
+ message_admins("calculating temperature factor, min: [C.OptimalTempMin], max: [C.OptimalTempMax], Exponential: [C.CurveSharpT], deltaT: [deltaT]")
+
+ //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
+ message_admins("Fermichem's pH broke!! Please let Fermis know!!")
+ WARNING("[my_atom] attempted to determine FermiChem pH for '[C.id]' which broke for some reason! ([usr])")
+ //TODO Add CatalystFact
+ message_admins("calculating pH factor(purity), pH: [pH], min: [C.OptimalpHMin]-[C.ReactpHLim], max: [C.OptimalpHMax]+[C.ReactpHLim], deltapH: [deltapH]")
+
+ stepChemAmmount = targetVol * deltaT
+ if (stepChemAmmount > C.RateUpLim)
+ stepChemAmmount = C.RateUpLim
+ else if (stepChemAmmount <= 0.01)
+ message_admins("stepChem underflow [stepChemAmmount]")
+ stepChemAmmount = 0.02
+
+ if ((reactedVol + stepChemAmmount) > targetVol)
+ stepChemAmmount = targetVol - reactedVol
+ message_admins("target volume reached. Reaction should stop after this loop. stepChemAmmount: [stepChemAmmount] + reactedVol: [reactedVol] = targetVol [targetVol]")
+
+ if (reactedVol > 0)
+ purity = ((purity * reactedVol) + (deltapH * stepChemAmmount)) /((reactedVol+ stepChemAmmount)) //This should add the purity to the product
+ else
+ purity = deltapH
+
+ // End.
+ /*
+ for(var/B in cached_required_reagents) //
+ tempVol = min(reactedVol, 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)
+ */
+ message_admins("cached_results: [cached_results], reactedVol: [reactedVol], stepChemAmmount [stepChemAmmount]")
+
+
+
+ for(var/B in cached_required_reagents)
+ message_admins("cached_required_reagents(B): [cached_required_reagents[B]], reactedVol: [reactedVol], base stepChemAmmount [stepChemAmmount]")
+ remove_reagent(B, (stepChemAmmount * cached_required_reagents[B]), safety = 1)//safety? removes reagents from beaker using remove function.
+
+ for(var/P in cached_results)//Not sure how this works, what is selected_reaction.results?
+ //reactedVol = max(reactedVol, 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:
+
+ message_admins("purity: [purity], purity of beaker")
+ message_admins("Temp before change: [chem_temp], pH after change: [pH]")
+ //Apply pH changes and thermal output of reaction to beaker
+ chem_temp += (C.ThermicConstant * stepChemAmmount)
+ pH += (C.HIonRelease * stepChemAmmount)
+ message_admins("Temp after change: [chem_temp], pH after change: [pH]")
+
+
+ reactedVol = reactedVol + stepChemAmmount
+
+
+ return (reactedVol)
+/* MOVED TO REAGENTS.DM
+/datum/reagents/proc/FermiExplode()
+ return
+*/
/datum/reagents/proc/isolate_reagent(reagent)
var/list/cached_reagents = reagent_list
diff --git a/code/modules/reagents/chemistry/holderKev.dm b/code/modules/reagents/chemistry/holderKev.dm
new file mode 100644
index 0000000000..c51c318e3e
--- /dev/null
+++ b/code/modules/reagents/chemistry/holderKev.dm
@@ -0,0 +1,900 @@
+
+/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/process()
+ var/occured = handle_reactions(TRUE)
+ if(!occured)
+ return PROCESS_KILL
+
+/datum/reagents/proc/handle_reactions(fermi_chem_react = FALSE)//HERE EDIT HERE THE MAIN REACTION FERMICHEMS ASSEMBLE! I hope rp is similar
+ return
+ if(reagents_holder_flags & REAGENT_NOREACT)
+ return //don't react
+ //cache things for performance
+ var/list/cached_reagents = reagent_list
+ var/list/cached_reactions = GLOB.chemical_reactions_list
+ var/datum/cached_my_atom = my_atom
+ var/reaction_occurred //if a reaction happened
+ var/list/fermichem_reacted //keeps track of fermichem reactions that already reacted to prevent it from being instant
+ do
+ var/list/possible_reactions
+ reaction_occurred = FALSE
+ for(var/reagent in cached_reagents)
+ var/datum/reagent/R = reagent
+ for(var/reaction in cached_reactions[R.id])
+ if(!reaction)
+ continue
+ var/datum/chemical_reaction/C = reaction
+ var/list/cached_required_reagents = C.required_reagents
+ var/list/cached_required_catalysts = C.required_catalysts
+ var/required_temp = C.required_temp
+ var/is_cold_recipe = C.is_cold_recipe
+ var/has_special_react = C.special_react
+ var/fail = FALSE
+ for(var/B in cached_required_reagents)
+ if(!has_reagent(B, cached_required_reagents[B]))
+ fail = TRUE
+ break
+ if(fail)
+ continue
+ for(var/B in cached_required_catalysts)
+ if(!has_reagent(B, cached_required_catalysts[B]))
+ fail = TRUE
+ break
+ if(fail)
+ continue
+ if(cached_my_atom)
+ if(C.required_container && C.required_container != cached_my_atom.type)
+ continue
+ if (isliving(cached_my_atom) && !C.mob_react) //Makes it so certain chemical reactions don't occur in mobs
+ continue
+ if(C.required_other) //Checks for other things required
+ 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
+ continue
+ else
+ if(C.required_container || C.required_other)
+ continue
+ if(required_temp && (is_cold_recipe? (chem_temp > required_temp) : (chem_temp < required_temp)))
+ continue
+ if(has_special_react && !C.check_special_react(src))
+ continue
+ if(C.FermiChem) //fermichem checks
+ if(chem_temp < C.OptimalTempMin) //too low temperature
+ continue
+ if(LAZYACCESS(fermichem_reacted, C)) //fermichems don't keep reacting instantly
+ continue
+ START_PROCESSING(SSchemistry, src) //start processing
+ if(!fermi_chem_react) //only react fermichems on process icks
+ continue
+ LAZYADD(possible_reactions, C)
+ if(length(possible_reactions)) //does list exist?
+ var/datum/chemical_reaction/selected_reaction = possible_reactions[1]
+ for(var/I in possible_reactions)
+ var/datum/chemical_reaction/competitor = I
+ if(selected_reaction.is_cold_recipe? competitor.required_temp < selected_reaction.required_temp : competitor.required_temp > selected_reaction.required_temp)
+ 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.special_react
+ //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.
+ //BRANCH HERE
+ var/multiplier = INFINITY //the multiplier of the stnadard "1 reaction" we managed to do this cycle
+ if(!selected_reaction.FermiChem) //it's a normal ss13 chem reaction, instant reactions.
+ for(var/B in cached_required_reagents)
+ multiplier = min(multiplier, round(get_reagent_amount(B) / cached_required_reagents[B])) //cap by the multiplier of the required you can get out of this
+ //remove reactants
+ for(var/B in cached_required_reagents)
+ remove_reagent(B, (multiplier * cached_required_reagents[B]), safety = TRUE)
+ //add products
+ for(var/P in cached_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)
+ else //FermiiiCheeeem!
+ //FERMICHEM BEGIN
+ //--CHECKS
+ message_admins("FermiChem Proc'd")
+ var/target_volume = 0
+ for(var/result_id in cached_results)
+ target_volume += multiplier * cached_results[result_id]
+ message_admins("FermiChem target volume: [target_volume]")
+ //--PROCESS
+ message_admins("FermiChem processing started")
+ //--REACTION
+ //set up fermichem variables
+ var/deltaT = 0
+ var/deltapH = 0
+ var/stepChemAmount = 0
+ var/purity = 1
+ //For now, purity is handled elsewhere
+ //check extremes first
+ if(chem_temp > selected_reaction.ExplodeTemp)
+ FermiExplode(selected_reaction)
+ else if(!ISINRANGE(pH, 0, 14))
+ //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()
+ //Calculate DeltaT (Deviation of T from optimal)
+ if (chem_temp < selected_reaction.OptimalTempMax && chem_temp >= selected_reaction.OptimalTempMin)
+ deltaT = (((selected_reaction.OptimalTempMin - chem_temp)**selected_reaction.CurveSharpT)/((selected_reaction.OptimalTempMax - selected_reaction.OptimalTempMin)**selected_reaction.CurveSharpT))
+ else if (chem_temp >= selected_reaction.OptimalTempMax)
+ deltaT = 1
+ else
+ deltaT = 0
+ message_admins("calculating temperature factor, min: [selected_reaction.OptimalTempMin], max: [selected_reaction.OptimalTempMax], Exponential: [selected_reaction.CurveSharpT], deltaT: [deltaT]")
+ //Calculate DeltapH (Deviation of pH from optimal)
+ //Lower range
+ if (pH < selected_reaction.OptimalpHMin)
+ if (pH < (selected_reaction.OptimalpHMin - selected_reaction.ReactpHLim))
+ deltapH = 0
+ else
+ deltapH = (((pH - (selected_reaction.OptimalpHMin - selected_reaction.ReactpHLim))**selected_reaction.CurveSharppH)/(selected_reaction.ReactpHLim**selected_reaction.CurveSharppH))
+ //Upper range
+ else if (pH > selected_reaction.OptimalpHMin)
+ if (pH > (selected_reaction.OptimalpHMin + selected_reaction.ReactpHLim))
+ deltapH = 0
+ else
+ deltapH = ((selected_reaction.ReactpHLim -(pH - (selected_reaction.OptimalpHMax + selected_reaction.ReactpHLim))+selected_reaction.ReactpHLim)/(selected_reaction.ReactpHLim**selected_reaction.CurveSharppH))
+ //Within mid range
+ else if (pH >= selected_reaction.OptimalpHMin && pH <= selected_reaction.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 '[selected_reaction.id]' which broke for some reason! ([usr])")
+ //TODO Add CatalystFact
+ message_admins("calculating pH factor(purity), pH: [pH], min: [selected_reaction.OptimalpHMin]-[selected_reaction.ReactpHLim], max: [selected_reaction.OptimalpHMax]+[selected_reaction.ReactpHLim], deltapH: [deltapH]")
+ stepChemAmount = target_volume * deltaT
+ if (stepChemAmount > selected_reaction.RateUpLim)
+ stepChemAmount = selected_reaction.RateUpLim
+ else if (stepChemAmount <= 0.01)
+ message_admins("stepChem underflow [stepChemAmount]")
+ stepChemAmount = 0.02
+ purity = deltapH
+ message_admins("cached_results: [cached_results], stepChemAmount [stepChemAmount]")
+ if(stepChemAmount > multiplier) //too much
+ message_admins("stepChemAmount was over multiplier for some reason..?")
+ for(var/B in cached_required_reagents)
+ message_admins("cached_required_reagents(B): [cached_required_reagents[B]], base stepChemAmount [stepChemAmount]")
+ remove_reagent(B, (stepChemAmount * cached_required_reagents[B]), safety = TRUE)//safety? removes reagents from beaker using remove function.
+
+ for(var/P in cached_results)//Not sure how this works, what is selected_reaction.results?
+ //reactedVol = max(reactedVol, 1) //this shouldnt happen ...
+ SSblackbox.record_feedback("tally", "chemical_reaction", cached_results[P]*stepChemAmount, P)//log
+ add_reagent(P, cached_results[P] * stepChemAmount, null, chem_temp)//add reagent function!! I THINK I can do this:
+ message_admins("purity: [purity], purity of beaker")
+ message_admins("Temp before change: [chem_temp], pH after change: [pH]")
+ //Apply pH changes and thermal output of reaction to beaker
+ chem_temp += (selected_reaction.ThermicConstant * stepChemAmount)
+ pH += (selected_reaction.HIonRelease * stepChemAmount)
+ message_admins("Temp after change: [chem_temp], pH after change: [pH]")
+ SSblackbox.record_feedback("tally", "Fermi_chemical_reaction", multiplier, selected_reaction.id)//log
+ //ensure the same thing doesn't happen in the same tick
+ LAZYSET(fermichem_reacted, selected_reaction, TRUE)
+ //FERMICHEM END
+ //reaction is done, do the effects.
+ 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 = TRUE
+ while(reaction_occurred)
+ update_total()
+ return reaction_occurred
+
+/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
+ R.loc = get_turf(my_atom)
+ 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
diff --git a/modular_citadel/code/modules/arousal/organs/breasts.dm b/modular_citadel/code/modules/arousal/organs/breasts.dm
index b2b565b603..87d57f6452 100644
--- a/modular_citadel/code/modules/arousal/organs/breasts.dm
+++ b/modular_citadel/code/modules/arousal/organs/breasts.dm
@@ -15,7 +15,7 @@
fluid_id = "milk"
var/amount = 2
producing = TRUE
- shape = "pair"
+ shape = "Pair"
can_masturbate_with = TRUE
masturbation_verb = "massage"
can_climax = TRUE
diff --git a/modular_citadel/code/modules/arousal/organs/genitals.dm b/modular_citadel/code/modules/arousal/organs/genitals.dm
index aad06f8cdb..8923d196c2 100644
--- a/modular_citadel/code/modules/arousal/organs/genitals.dm
+++ b/modular_citadel/code/modules/arousal/organs/genitals.dm
@@ -263,10 +263,9 @@
return
update_genitals()
-/mob/living/carbon/human/proc/update_genitals(mob/living/carbon/human/H)
+/mob/living/carbon/human/proc/update_genitals()
if(src && !QDELETED(src))
dna.species.handle_genitals(src)
- H.update_body()
/mob/living/carbon/human/proc/Force_update_genitals(mob/living/carbon/human/H)
dna.species.handle_genitals(src)
@@ -369,7 +368,7 @@
//if(!S || S.icon_state == "none")
//return
-
+
for(var/layer in relevant_layers)
var/layertext = genitals_layertext(layer)
S = GLOB.breasts_shapes_list[B.shape]