Merge branch 'master' into pr/7

This commit is contained in:
Thalpy
2019-05-10 02:20:19 +01:00
committed by GitHub
17 changed files with 4877 additions and 78 deletions
@@ -0,0 +1,58 @@
Cit/main/Chemistry notes
SEE README.MD FOR FUNCTIONS
~Main folder: \ss13\Citadel-Station-13\code\modules\reagents\chemistry~
~ADD NEW FILES TO tgstation.dme~
~Recipies: \ss13\Citadel-Station-13\code\modules\reagents\chemistry\recipes~
This contains the reaction recipies and thats it.
name, id, results, required_reagents, required_catalysts, mix_message, required_temp
To add: a lot
eg:{
/datum/chemical_reaction/charcoal
name = "Charcoal"
id = "charcoal"
results = list("charcoal" = 2)
required_reagents = list("ash" = 1, "sodiumchloride" = 1)
mix_message = "The mixture yields a fine black powder."
required_temp = 380
required_catalysts = list("plasma" = 5)
}
How the end state should look:{
/datum/chemical_reaction/bicaridine
name = "Bicaridine"
id = "bicaridine"
results = list("bicaridine" = 3)
required_reagents = list("carbon" = 1, "oxygen" = 1, "sugar" = 1)
OptimalTempMin = 350 // Lower area of bell curve for determining heat based rate reations
OptimalTempMax = 500 // Upper end for above
ExplodeTemp = 550 //Temperature at which reaction explodes
OptimalpHMin = 4 // Lowest value of pH determining pH a 1 value for pH based rate reations (Plateu phase)
OptimalpHMax = 9.5 // Higest value for above
ReactpHLim = 2 // How far out pH wil react, giving impurity place (Exponential phase)
//CatalystFact = 0 // How much the catalyst affects the reaction (0 = no catalyst)
CurveSharp = 4 // How sharp the exponential curve is (to the power of value)
ThermicConstant = -2.5 //Temperature change per 1u produced
HIonRelease = 0.01 //pH change per 1u reaction
RateUpLim = 50 //Optimal/max rate possible if all conditions are perfect
//optional:
mix_message = "The mixture yields a fine black powder."
required_catalysts = list("plasma" = 5)
edit:
required_temp = 380
}
~\ss13\Citadel-Station-13\code/modules/reagents/chemistry/holder.dm~
Where the actual reaction takes place, main edit location.
About half way where I've commented (WHY IS THERE NO LINE NUMBERS?!)
~\ss13\Citadel-Station-13\code/modules/reagents/chemistry/reagents~
Where what the reactions do, the code that sets what they do.
~\ss13\Citadel-Station-13\tgui\src\interfaces~
This contains the 4 scripts for generation of the UI for operation of the various chemical equipments.
@@ -0,0 +1,967 @@
/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)<amount)
amount = src.get_reagent_amount(reagent)
amount = min(amount, R.maximum_volume-R.total_volume)
var/trans_data = null
for (var/CR in cached_reagents)
var/datum/reagent/current_reagent = CR
if(current_reagent.id == reagent)
if(preserve_data)
trans_data = current_reagent.data
R.add_reagent(current_reagent.id, amount, trans_data, src.chem_temp, pH, no_react = TRUE) //Fermichem edit TODO: add purity
//R.add_reagent(current_reagent.id, amount, trans_data, src.chem_temp, pH, current_reagent.purity, no_react = TRUE) //Fermichem edit
remove_reagent(current_reagent.id, amount, 1)
break
src.update_total()
R.update_total()
R.handle_reactions()
return amount
/datum/reagents/proc/metabolize(mob/living/carbon/C, can_overdose = FALSE, liverless = FALSE)
var/list/cached_reagents = reagent_list
var/list/cached_addictions = addiction_list
if(C)
expose_temperature(C.bodytemperature, 0.25)
var/need_mob_update = 0
for(var/reagent in cached_reagents)
var/datum/reagent/R = reagent
if(QDELETED(R.holder))
continue
if(liverless && !R.self_consuming) //need to be metabolized
continue
if(!C)
C = R.holder.my_atom
if(C && R)
if(C.reagent_check(R) != 1)
if(can_overdose)
if(R.overdose_threshold)
if(R.volume >= 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, "<span class='notice'>You feel like you've gotten over your need for [R.name].</span>")
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
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
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 (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]
//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(selected_reaction)
var/datum/chemical_reaction/C = selected_reaction
if (C.FermiChem == TRUE)
//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)
var/deltaT = 0
var/deltapH = 0
var/stepChemAmmount = 0
var/ammoReacted = 0
//get purity from combined beaker reactant purities.
var/purity = 1
while (ammoReacted < multiplier)
message_admins("Loop beginning")
CHECK_TICK
//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))
// 350 300
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 = multiplier * deltaT
if (stepChemAmmount > C.RateUpLim)
stepChemAmmount = C.RateUpLim
else if (stepChemAmmount < 0.01)
stepChemAmmount = 0.1
if (ammoReacted > 0)
purity = ((purity * ammoReacted) + (deltapH * stepChemAmmount)) /((ammoReacted + stepChemAmmount)) //This should add the purity to the product
else
purity = deltapH
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]")
// End.
selected_reaction.on_reaction(src, multiplier, special_react_result)
message_admins("cached_results: [cached_results], multiplier: [multiplier], stepChemAmmount [stepChemAmmount]")
for(var/B in cached_required_reagents)
message_admins("cached_results: [cached_results], multiplier: [multiplier], stepChemAmmount [stepChemAmmount]")
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
CHECK_TICK
reaction_occurred = 1
SSblackbox.record_feedback("tally", "Fermi_chemical_reaction", ammoReacted, 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)
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, "<span class='notice'>[iconhtml] [selected_reaction.mix_message]</span>")
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, "<span class='notice'>[iconhtml] \The [my_atom]'s power is consumed in the reaction.</span>")
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
+303 -60
View File
@@ -48,10 +48,15 @@
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
var/targetVol = 0
var/reactedVol = 0
var/fermiIsReacting = FALSE
var/fermiReactID = null
/datum/reagents/New(maximum=100)
maximum_volume = maximum
@@ -182,7 +187,10 @@
var/transfer_amount = T.volume * part
if(preserve_data)
trans_data = copy_data(T)
R.add_reagent(T.id, transfer_amount * multiplier, trans_data, chem_temp, no_react = 1) //we only handle reaction after every reagent has been transfered.
//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()
@@ -242,7 +250,8 @@
if(current_reagent.id == reagent)
if(preserve_data)
trans_data = current_reagent.data
R.add_reagent(current_reagent.id, amount, trans_data, src.chem_temp)
R.add_reagent(current_reagent.id, amount, trans_data, src.chem_temp, pH, no_react = TRUE) //Fermichem edit TODO: add purity
//R.add_reagent(current_reagent.id, amount, trans_data, src.chem_temp, pH, current_reagent.purity, no_react = TRUE) //Fermichem edit
remove_reagent(current_reagent.id, amount, 1)
break
@@ -332,19 +341,37 @@
R.on_update (A)
update_total()
/datum/reagents/proc/handle_reactions()
var/list/cached_reagents = reagent_list
var/list/cached_reactions = GLOB.chemical_reactions_list
var/datum/cached_my_atom = my_atom
if(reagents_holder_flags & REAGENT_NOREACT)
/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.
var/reaction_occurred = 0
do
var/list/possible_reactions = list()
reaction_occurred = 0
for(var/reagent in cached_reagents)
var/datum/reagent/R = reagent
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
@@ -364,6 +391,7 @@
var/has_special_react = C.special_react
var/can_special_react = 0
for(var/B in cached_required_reagents)
if(!has_reagent(B, cached_required_reagents[B]))
break
@@ -377,26 +405,36 @@
matching_container = 1
else
if(cached_my_atom.type == C.required_container)
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)
matching_other = 1
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))
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)
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
if(required_temp == 0 || (is_cold_recipe && chem_temp <= required_temp) || (!is_cold_recipe && chem_temp >= required_temp))
meets_temp_requirement = 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
@@ -404,57 +442,260 @@
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)
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)
var/datum/chemical_reaction/competitor = V
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)
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)
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
var/list/cached_results = selected_reaction.results
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
for(var/B in cached_required_reagents)
multiplier = min(multiplier, round(get_reagent_amount(B) / cached_required_reagents[B]))
var/list/multiplier = INFINITY //Wat
for(var/B in cached_required_reagents)
remove_reagent(B, (multiplier * cached_required_reagents[B]), safety = 1)
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/P in selected_reaction.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)
//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
//if(selected_reaction)
var/datum/chemical_reaction/C = selected_reaction
var/list/seen = viewers(4, get_turf(my_atom))
var/iconhtml = icon2html(cached_my_atom, seen)
if(cached_my_atom)
if(!ismob(cached_my_atom)) // No bubbling mobs
if(selected_reaction.mix_sound)
playsound(get_turf(cached_my_atom), selected_reaction.mix_sound, 80, 1)
if (C.FermiChem == TRUE && !continue_reacting)
message_admins("FermiChem Proc'd")
for(var/mob/M in seen)
to_chat(M, "<span class='notice'>[iconhtml] [selected_reaction.mix_message]</span>")
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
//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
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)
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)
if(istype(cached_my_atom, /obj/item/slime_extract))
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, "<span class='notice'>[iconhtml] \The [my_atom]'s power is consumed in the reaction.</span>")
ME2.name = "used slime extract"
ME2.desc = "This extract has been used up."
to_chat(M, "<span class='notice'>[iconhtml] [selected_reaction.mix_message]</span>")
selected_reaction.on_reaction(src, multiplier, special_react_result)
reaction_occurred = 1
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, "<span class='notice'>[iconhtml] \The [my_atom]'s power is consumed in the reaction.</span>")
ME2.name = "used slime extract"
ME2.desc = "This extract has been used up."
while(reaction_occurred)
update_total()
return 0
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)
/datum/reagents/proc/FermiExplode()
return
/datum/reagents/proc/isolate_reagent(reagent)
var/list/cached_reagents = reagent_list
@@ -544,7 +785,7 @@
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, no_react = 0)
/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
@@ -555,7 +796,7 @@
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)
@@ -596,12 +837,14 @@
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
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)
@@ -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)<amount)
amount = src.get_reagent_amount(reagent)
amount = min(amount, R.maximum_volume-R.total_volume)
var/trans_data = null
for (var/CR in cached_reagents)
var/datum/reagent/current_reagent = CR
if(current_reagent.id == reagent)
if(preserve_data)
trans_data = current_reagent.data
R.add_reagent(current_reagent.id, amount, trans_data, src.chem_temp, pH, no_react = TRUE) //Fermichem edit TODO: add purity
//R.add_reagent(current_reagent.id, amount, trans_data, src.chem_temp, pH, current_reagent.purity, no_react = TRUE) //Fermichem edit
remove_reagent(current_reagent.id, amount, 1)
break
src.update_total()
R.update_total()
R.handle_reactions()
return amount
/datum/reagents/proc/metabolize(mob/living/carbon/C, can_overdose = FALSE, liverless = FALSE)
var/list/cached_reagents = reagent_list
var/list/cached_addictions = addiction_list
if(C)
expose_temperature(C.bodytemperature, 0.25)
var/need_mob_update = 0
for(var/reagent in cached_reagents)
var/datum/reagent/R = reagent
if(QDELETED(R.holder))
continue
if(liverless && !R.self_consuming) //need to be metabolized
continue
if(!C)
C = R.holder.my_atom
if(C && R)
if(C.reagent_check(R) != 1)
if(can_overdose)
if(R.overdose_threshold)
if(R.volume >= 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, "<span class='notice'>You feel like you've gotten over your need for [R.name].</span>")
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, "<span class='notice'>[iconhtml] [selected_reaction.mix_message]</span>")
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, "<span class='notice'>[iconhtml] \The [my_atom]'s power is consumed in the reaction.</span>")
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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,962 @@
/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)<amount)
amount = src.get_reagent_amount(reagent)
amount = min(amount, R.maximum_volume-R.total_volume)
var/trans_data = null
for (var/CR in cached_reagents)
var/datum/reagent/current_reagent = CR
if(current_reagent.id == reagent)
if(preserve_data)
trans_data = current_reagent.data
R.add_reagent(current_reagent.id, amount, trans_data, src.chem_temp, pH, no_react = TRUE) //Fermichem edit TODO: add purity
//R.add_reagent(current_reagent.id, amount, trans_data, src.chem_temp, pH, current_reagent.purity, no_react = TRUE) //Fermichem edit
remove_reagent(current_reagent.id, amount, 1)
break
src.update_total()
R.update_total()
R.handle_reactions()
return amount
/datum/reagents/proc/metabolize(mob/living/carbon/C, can_overdose = FALSE, liverless = FALSE)
var/list/cached_reagents = reagent_list
var/list/cached_addictions = addiction_list
if(C)
expose_temperature(C.bodytemperature, 0.25)
var/need_mob_update = 0
for(var/reagent in cached_reagents)
var/datum/reagent/R = reagent
if(QDELETED(R.holder))
continue
if(liverless && !R.self_consuming) //need to be metabolized
continue
if(!C)
C = R.holder.my_atom
if(C && R)
if(C.reagent_check(R) != 1)
if(can_overdose)
if(R.overdose_threshold)
if(R.volume >= 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, "<span class='notice'>You feel like you've gotten over your need for [R.name].</span>")
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
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
var/reactedVol = 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 (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]
//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.
//BRANCH HERE
if(selected_reaction)
var/datum/chemical_reaction/C = selected_reaction
if (C.FermiChem == TRUE)
message_admins("FermiChem Proc'd")
while (reaction_occurred < 1)
reaction_occurred, reactedVol = FermiReact(selected_reaction, chem_temp, pH, multiplier)
CHECK_TICK
//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, "<span class='notice'>[iconhtml] [selected_reaction.mix_message]</span>")
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, "<span class='notice'>[iconhtml] \The [my_atom]'s power is consumed in the reaction.</span>")
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(selected_reaction, chem_temp, pH, totalVol)
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
//var/list/multiplier = INFINITY
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 = tempVol * deltaT
if (stepChemAmmount > C.RateUpLim)
stepChemAmmount = C.RateUpLim
else if (stepChemAmmount < 0.01)
stepChemAmmount = 0.01
if (ammoReacted > 0)
purity = ((purity * ammoReacted) + (deltapH * stepChemAmmount)) /((ammoReacted + stepChemAmmount)) //This should add the purity to the product
else
purity = deltapH
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]")
// End.
selected_reaction.on_reaction(src, multiplier, special_react_result)
for(var/B in cached_required_reagents) //
tempVol = min(totalVol, 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], totalVol: [totalVol], stepChemAmmount [stepChemAmmount]")
for(var/B in cached_required_reagents)
message_admins("cached_required_reagents(B): [cached_required_reagents[B]], totalVol: [totalVol], 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 selected_reaction.results)//Not sure how this works, what is selected_reaction.results?
totalVol = max(totalVol, 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
if ammoReacted = totalVol
reaction_occurred = 1
SSblackbox.record_feedback("tally", "Fermi_chemical_reaction", ammoReacted, C.id)//log
CHECK_TICK
return (reaction_occurred, ammoReacted)
/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
@@ -33,6 +33,10 @@
var/addiction_stage4_end = 40
var/overdosed = 0 // You fucked up and this is now triggering its overdose effects, purge that shit quick.
var/self_consuming = FALSE
//Fermichem vars:
var/purity = 1
var/impureChem = "toxin"
var/loc = null
/datum/reagent/Destroy() // This should only be called by the holder, so it's already handled clearing its references
. = ..()
@@ -1863,6 +1863,11 @@
description = "The primary precursor for an ancient feline delicacy known as skooma. While it has no notable effects on it's own, mixing it with morphine in a chilled container may yield interesting results."
color = "#FAEAFF"
taste_description = "synthetic catnip"
/datum/reagent/moonsugar/on_mob_life(mob/living/carbon/M)
if(prob(20))
to_chat(M, "You find yourself unable to supress the desire to meow!")
M.emote("nya")
/datum/reagent/penis_enlargement
name = "Penis Enlargement"
@@ -1879,4 +1884,5 @@
if(added_length >= 0.20) //Only add the length if it's greater than or equal to 0.2. This is to prevent people from smoking the reagents and causing the penis to update constantly.
P.length += added_length
P.update()
..()
..()
@@ -17,6 +17,25 @@
var/mix_message = "The solution begins to bubble." //The message shown to nearby people upon mixing, if applicable
var/mix_sound = 'sound/effects/bubbles.ogg' //The sound played upon mixing, if applicable
//FermiChem!
var/OptimalTempMin = 200 // Lower area of bell curve for determining heat based rate reactions
var/OptimalTempMax = 800
var/ExplodeTemp = 900 //If any reaction is this hot, it explodes!
var/OptimalpHMin = 5
var/OptimalpHMax = 10
var/ReactpHLim = 3
//var/CatalystFact = C.CatalystFact
var/CatalystFact = 0
var/CurveSharpT = 2
var/CurveSharppH = 2
var/ThermicConstant = 1
var/HIonRelease = 0.1
var/RateUpLim = 10
var/FermiChem = FALSE //If the chemical uses the Fermichem reaction mechanics
var/FermiExplode = FALSE //If the chemical explodes in a special way as a result of
var/ImpureChem = "toxin" //What chemical is produced with an inpure reaction
/datum/chemical_reaction/proc/on_reaction(datum/reagents/holder, created_volume, specialreact)
return
//I recommend you set the result amount to the total volume of all components.
@@ -167,12 +167,12 @@
/obj/item/reagent_containers/glass/beaker/plastic
name = "x-large beaker"
desc = "An extra-large beaker. Can hold up to 120 units."
desc = "An extra-large beaker. Can hold up to 150 units."
icon_state = "beakerwhite"
materials = list(MAT_GLASS=2500, MAT_PLASTIC=3000)
volume = 120
volume = 150
amount_per_transfer_from_this = 10
possible_transfer_amounts = list(5,10,15,20,25,30,60,120)
possible_transfer_amounts = list(5,10,15,20,25,30,60,120,150)
/obj/item/reagent_containers/glass/beaker/plastic/update_icon()
icon_state = "beakerlarge" // hack to lets us reuse the large beaker reagent fill states
@@ -181,12 +181,12 @@
/obj/item/reagent_containers/glass/beaker/meta
name = "metamaterial beaker"
desc = "A large beaker. Can hold up to 180 units."
desc = "A large beaker. Can hold up to 200 units."
icon_state = "beakergold"
materials = list(MAT_GLASS=2500, MAT_PLASTIC=3000, MAT_GOLD=1000, MAT_TITANIUM=1000)
volume = 180
volume = 200
amount_per_transfer_from_this = 10
possible_transfer_amounts = list(5,10,15,20,25,30,60,120,180)
possible_transfer_amounts = list(5,10,15,20,25,30,60,120,150,200)
/obj/item/reagent_containers/glass/beaker/noreact
name = "cryostasis beaker"