diff --git a/code/__DEFINES/reagents.dm b/code/__DEFINES/reagents.dm index f8f59a367f..ccfbd1a627 100644 --- a/code/__DEFINES/reagents.dm +++ b/code/__DEFINES/reagents.dm @@ -2,6 +2,11 @@ #define LIQUID 2 #define GAS 3 +#define REAGENT_NORMAL_PH 7.000 +#define REAGENT_PH_ACCURACY 0.001 +#define REAGENT_PURITY_ACCURACY 0.001 +#define DEFAULT_SPECIFIC_HEAT 200 + // container_type defines #define INJECTABLE (1<<0) // Makes it possible to add reagents through droppers and syringes. #define DRAWABLE (1<<1) // Makes it possible to remove reagents through syringes. diff --git a/code/modules/reagents/chemistry/Fermi_Chemistry_notes.txt b/code/modules/reagents/chemistry/Fermi_Chemistry_notes.txt new file mode 100644 index 0000000000..efcd9b748f --- /dev/null +++ b/code/modules/reagents/chemistry/Fermi_Chemistry_notes.txt @@ -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. diff --git a/code/modules/reagents/chemistry/holder(nonProc.old.functional.).dm b/code/modules/reagents/chemistry/holder(nonProc.old.functional.).dm new file mode 100644 index 0000000000..c38f065383 --- /dev/null +++ b/code/modules/reagents/chemistry/holder(nonProc.old.functional.).dm @@ -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)= R.overdose_threshold && !R.overdosed) + R.overdosed = 1 + need_mob_update += R.overdose_start(C) + if(R.addiction_threshold) + if(R.volume >= R.addiction_threshold && !is_type_in_list(R, cached_addictions)) + var/datum/reagent/new_reagent = new R.type() + cached_addictions.Add(new_reagent) + if(R.overdosed) + need_mob_update += R.overdose_process(C) + if(is_type_in_list(R,cached_addictions)) + for(var/addiction in cached_addictions) + var/datum/reagent/A = addiction + if(istype(R, A)) + A.addiction_stage = -15 // you're satisfied for a good while. + need_mob_update += R.on_mob_life(C) + + if(can_overdose) + if(addiction_tick == 6) + addiction_tick = 1 + for(var/addiction in cached_addictions) + var/datum/reagent/R = addiction + if(C && R) + R.addiction_stage++ + if(1 <= R.addiction_stage && R.addiction_stage <= R.addiction_stage1_end) + need_mob_update += R.addiction_act_stage1(C) + else if(R.addiction_stage1_end <= R.addiction_stage && R.addiction_stage <= R.addiction_stage2_end) + need_mob_update += R.addiction_act_stage2(C) + else if(R.addiction_stage2_end <= R.addiction_stage && R.addiction_stage <= R.addiction_stage3_end) + need_mob_update += R.addiction_act_stage3(C) + else if(R.addiction_stage3_end <= R.addiction_stage && R.addiction_stage <= R.addiction_stage4_end) + need_mob_update += R.addiction_act_stage4(C) + else if(R.addiction_stage4_end <= R.addiction_stage) + to_chat(C, "You feel like you've gotten over your need for [R.name].") + SEND_SIGNAL(C, COMSIG_CLEAR_MOOD_EVENT, "[R.id]_addiction") + cached_addictions.Remove(R) + addiction_tick++ + if(C && need_mob_update) //some of the metabolized reagents had effects on the mob that requires some updates. + C.updatehealth() + C.update_canmove() + C.update_stamina() + update_total() + + +/datum/reagents/proc/set_reacting(react = TRUE) + if(react) + reagents_holder_flags &= ~(REAGENT_NOREACT) + else + reagents_holder_flags |= REAGENT_NOREACT + +/datum/reagents/proc/conditional_update_move(atom/A, Running = 0) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + R.on_move (A, Running) + update_total() + +/datum/reagents/proc/conditional_update(atom/A) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + R.on_update (A) + update_total() + +/datum/reagents/proc/handle_reactions()//HERE EDIT HERE THE MAIN REACTION FERMICHEMS ASSEMBLE! I hope rp is similar + 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, "[iconhtml] [selected_reaction.mix_message]") + + if(istype(cached_my_atom, /obj/item/slime_extract))//if there's an extract and it's used up. + var/obj/item/slime_extract/ME2 = my_atom + ME2.Uses-- + if(ME2.Uses <= 0) // give the notification that the slime core is dead + for(var/mob/M in seen) + to_chat(M, "[iconhtml] \The [my_atom]'s power is consumed in the reaction.") + ME2.name = "used slime extract" + ME2.desc = "This extract has been used up." + + selected_reaction.on_reaction(src, multiplier, special_react_result) + reaction_occurred = 1 + + while(reaction_occurred)//while do nothing? + update_total()//Don't know waht this does. + return 0//end! + +/datum/reagents/proc/FermiReact() + return + +/datum/reagents/proc/FermiExplode() + return + +/datum/reagents/proc/isolate_reagent(reagent) + var/list/cached_reagents = reagent_list + for(var/_reagent in cached_reagents) + var/datum/reagent/R = _reagent + if(R.id != reagent) + del_reagent(R.id) + update_total() + +/datum/reagents/proc/del_reagent(reagent) + var/list/cached_reagents = reagent_list + for(var/_reagent in cached_reagents) + var/datum/reagent/R = _reagent + if(R.id == reagent) + if(my_atom && isliving(my_atom)) + var/mob/living/M = my_atom + R.on_mob_delete(M) + qdel(R) + reagent_list -= R + update_total() + if(my_atom) + my_atom.on_reagent_change(DEL_REAGENT) + return 1 + +/datum/reagents/proc/update_total() + var/list/cached_reagents = reagent_list + total_volume = 0 + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.volume < 0.1) + del_reagent(R.id) + else + total_volume += R.volume + + return 0 + +/datum/reagents/proc/clear_reagents() + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + del_reagent(R.id) + return 0 + +/datum/reagents/proc/reaction(atom/A, method = TOUCH, volume_modifier = 1, show_message = 1) + var/react_type + if(isliving(A)) + react_type = "LIVING" + if(method == INGEST) + var/mob/living/L = A + L.taste(src) + else if(isturf(A)) + react_type = "TURF" + else if(isobj(A)) + react_type = "OBJ" + else + return + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + switch(react_type) + if("LIVING") + var/touch_protection = 0 + if(method == VAPOR) + var/mob/living/L = A + touch_protection = L.get_permeability_protection() + R.reaction_mob(A, method, R.volume * volume_modifier, show_message, touch_protection) + if("TURF") + R.reaction_turf(A, R.volume * volume_modifier, show_message) + if("OBJ") + R.reaction_obj(A, R.volume * volume_modifier, show_message) + +/datum/reagents/proc/holder_full() + if(total_volume >= maximum_volume) + return TRUE + return FALSE + +//Returns the average specific heat for all reagents currently in this holder. +/datum/reagents/proc/specific_heat() + . = 0 + var/cached_amount = total_volume //cache amount + var/list/cached_reagents = reagent_list //cache reagents + for(var/I in cached_reagents) + var/datum/reagent/R = I + . += R.specific_heat * (R.volume / cached_amount) + +/datum/reagents/proc/adjust_thermal_energy(J, min_temp = 2.7, max_temp = 1000) + var/S = specific_heat() + chem_temp = CLAMP(chem_temp + (J / (S * total_volume)), 2.7, 1000) + +/datum/reagents/proc/add_reagent(reagent, amount, list/data=null, reagtemp = 300, pH = 7, no_react = 0)//EDIT HERE TOO ~FERMICHEM~ + if(!isnum(amount) || !amount) + return FALSE + + if(amount <= 0) + return FALSE + + var/datum/reagent/D = GLOB.chemical_reagents_list[reagent] + if(!D) + WARNING("[my_atom] attempted to add a reagent called '[reagent]' which doesn't exist. ([usr])") + return FALSE + + update_total() + var/cached_total = total_volume + if(cached_total + amount > maximum_volume) + amount = (maximum_volume - cached_total) //Doesnt fit in. Make it disappear. Shouldnt happen. Will happen. + if(amount <= 0) + return FALSE + var/new_total = cached_total + amount + var/cached_temp = chem_temp + var/list/cached_reagents = reagent_list + + //Equalize temperature - Not using specific_heat() because the new chemical isn't in yet. + var/specific_heat = 0 + var/thermal_energy = 0 + for(var/i in cached_reagents) + var/datum/reagent/R = i + specific_heat += R.specific_heat * (R.volume / new_total) + thermal_energy += R.specific_heat * R.volume * cached_temp + specific_heat += D.specific_heat * (amount / new_total) + thermal_energy += D.specific_heat * amount * reagtemp + chem_temp = thermal_energy / (specific_heat * new_total) + //// + + //add the reagent to the existing if it exists + for(var/A in cached_reagents) + var/datum/reagent/R = A + if (R.id == reagent) + R.volume += amount + update_total() + if(my_atom) + my_atom.on_reagent_change(ADD_REAGENT) + R.on_merge(data, amount) + if(!no_react) + handle_reactions() + return TRUE + + //otherwise make a new one + var/datum/reagent/R = new D.type(data) + cached_reagents += R + R.holder = src + R.volume = amount + if(data) + R.data = data + R.on_new(data) + + if(isliving(my_atom)) + R.on_mob_add(my_atom) //Must occur befor it could posibly run on_mob_delete + update_total() + if(my_atom) + my_atom.on_reagent_change(ADD_REAGENT) + if(!no_react) + handle_reactions() + return TRUE + +/datum/reagents/proc/add_reagent_list(list/list_reagents, list/data=null) // Like add_reagent but you can enter a list. Format it like this: list("toxin" = 10, "beer" = 15) + for(var/r_id in list_reagents) + var/amt = list_reagents[r_id] + add_reagent(r_id, amt, data) + +/datum/reagents/proc/remove_reagent(reagent, amount, safety)//Added a safety check for the trans_id_to + + if(isnull(amount)) + amount = 0 + CRASH("null amount passed to reagent code") + return FALSE + + if(!isnum(amount)) + return FALSE + + if(amount < 0) + return FALSE + + var/list/cached_reagents = reagent_list + + for(var/A in cached_reagents) + var/datum/reagent/R = A + if (R.id == reagent) + //clamp the removal amount to be between current reagent amount + //and zero, to prevent removing more than the holder has stored + amount = CLAMP(amount, 0, R.volume) + R.volume -= amount + update_total() + if(!safety)//So it does not handle reactions when it need not to + handle_reactions() + if(my_atom) + my_atom.on_reagent_change(REM_REAGENT) + return TRUE + + return FALSE + +/datum/reagents/proc/has_reagent(reagent, amount = -1) + var/list/cached_reagents = reagent_list + for(var/_reagent in cached_reagents) + var/datum/reagent/R = _reagent + if (R.id == reagent) + if(!amount) + return R + else + if(R.volume >= amount) + return R + else + return 0 + + return 0 + +/datum/reagents/proc/get_reagent_amount(reagent) + var/list/cached_reagents = reagent_list + for(var/_reagent in cached_reagents) + var/datum/reagent/R = _reagent + if (R.id == reagent) + return R.volume + + return 0 + +/datum/reagents/proc/get_reagents() + var/list/names = list() + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + names += R.name + + return jointext(names, ",") + +/datum/reagents/proc/remove_all_type(reagent_type, amount, strict = 0, safety = 1) // Removes all reagent of X type. @strict set to 1 determines whether the childs of the type are included. + if(!isnum(amount)) + return 1 + var/list/cached_reagents = reagent_list + var/has_removed_reagent = 0 + + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + var/matches = 0 + // Switch between how we check the reagent type + if(strict) + if(R.type == reagent_type) + matches = 1 + else + if(istype(R, reagent_type)) + matches = 1 + // We found a match, proceed to remove the reagent. Keep looping, we might find other reagents of the same type. + if(matches) + // Have our other proc handle removement + has_removed_reagent = remove_reagent(R.id, amount, safety) + + return has_removed_reagent + +//two helper functions to preserve data across reactions (needed for xenoarch) +/datum/reagents/proc/get_data(reagent_id) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.id == reagent_id) + return R.data + +/datum/reagents/proc/set_data(reagent_id, new_data) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.id == reagent_id) + R.data = new_data + +/datum/reagents/proc/copy_data(datum/reagent/current_reagent) + if(!current_reagent || !current_reagent.data) + return null + if(!istype(current_reagent.data, /list)) + return current_reagent.data + + var/list/trans_data = current_reagent.data.Copy() + + // We do this so that introducing a virus to a blood sample + // doesn't automagically infect all other blood samples from + // the same donor. + // + // Technically we should probably copy all data lists, but + // that could possibly eat up a lot of memory needlessly + // if most data lists are read-only. + if(trans_data["viruses"]) + var/list/v = trans_data["viruses"] + trans_data["viruses"] = v.Copy() + + return trans_data + +/datum/reagents/proc/get_reagent(type) + var/list/cached_reagents = reagent_list + . = locate(type) in cached_reagents + +/datum/reagents/proc/generate_taste_message(minimum_percent=15) + // the lower the minimum percent, the more sensitive the message is. + var/list/out = list() + var/list/tastes = list() //descriptor = strength + if(minimum_percent <= 100) + for(var/datum/reagent/R in reagent_list) + if(!R.taste_mult) + continue + + if(istype(R, /datum/reagent/consumable/nutriment)) + var/list/taste_data = R.data + for(var/taste in taste_data) + var/ratio = taste_data[taste] + var/amount = ratio * R.taste_mult * R.volume + if(taste in tastes) + tastes[taste] += amount + else + tastes[taste] = amount + else + var/taste_desc = R.taste_description + var/taste_amount = R.volume * R.taste_mult + if(taste_desc in tastes) + tastes[taste_desc] += taste_amount + else + tastes[taste_desc] = taste_amount + //deal with percentages + // TODO it would be great if we could sort these from strong to weak + var/total_taste = counterlist_sum(tastes) + if(total_taste > 0) + for(var/taste_desc in tastes) + var/percent = tastes[taste_desc]/total_taste * 100 + if(percent < minimum_percent) + continue + var/intensity_desc = "a hint of" + if(percent > minimum_percent * 2 || percent == 100) + intensity_desc = "" + else if(percent > minimum_percent * 3) + intensity_desc = "the strong flavor of" + if(intensity_desc != "") + out += "[intensity_desc] [taste_desc]" + else + out += "[taste_desc]" + + return english_list(out, "something indescribable") + +/datum/reagents/proc/expose_temperature(var/temperature, var/coeff=0.02) + var/temp_delta = (temperature - chem_temp) * coeff + if(temp_delta > 0) + chem_temp = min(chem_temp + max(temp_delta, 1), temperature) + else + chem_temp = max(chem_temp + min(temp_delta, -1), temperature) + chem_temp = round(chem_temp) + handle_reactions() + +/////////////////////////////////////////////////////////////////////////////////// + + +// Convenience proc to create a reagents holder for an atom +// Max vol is maximum volume of holder +/atom/proc/create_reagents(max_vol) + if(reagents) + qdel(reagents) + reagents = new/datum/reagents(max_vol) + reagents.my_atom = src + +/proc/get_random_reagent_id() // Returns a random reagent ID minus blacklisted reagents + var/static/list/random_reagents = list() + if(!random_reagents.len) + for(var/thing in subtypesof(/datum/reagent)) + var/datum/reagent/R = thing + if(initial(R.can_synth)) + random_reagents += initial(R.id) + var/picked_reagent = pick(random_reagents) + return picked_reagent \ No newline at end of file diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm index 252d0d0dc3..fb14cab18f 100644 --- a/code/modules/reagents/chemistry/holder.dm +++ b/code/modules/reagents/chemistry/holder.dm @@ -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, "[iconhtml] [selected_reaction.mix_message]") + 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, "[iconhtml] \The [my_atom]'s power is consumed in the reaction.") - ME2.name = "used slime extract" - ME2.desc = "This extract has been used up." + to_chat(M, "[iconhtml] [selected_reaction.mix_message]") - 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, "[iconhtml] \The [my_atom]'s power is consumed in the reaction.") + 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) diff --git a/code/modules/reagents/chemistry/holderBackup.dm b/code/modules/reagents/chemistry/holderBackup.dm new file mode 100644 index 0000000000..b434b348f0 --- /dev/null +++ b/code/modules/reagents/chemistry/holderBackup.dm @@ -0,0 +1,952 @@ + +/proc/build_chemical_reagent_list() + //Chemical Reagents - Initialises all /datum/reagent into a list indexed by reagent id + + if(GLOB.chemical_reagents_list) + return + + var/paths = subtypesof(/datum/reagent) + GLOB.chemical_reagents_list = list() + + for(var/path in paths) + var/datum/reagent/D = new path() + GLOB.chemical_reagents_list[D.id] = D + +/proc/build_chemical_reactions_list() + //Chemical Reactions - Initialises all /datum/chemical_reaction into a list + // It is filtered into multiple lists within a list. + // For example: + // chemical_reaction_list["plasma"] is a list of all reactions relating to plasma + + if(GLOB.chemical_reactions_list) + return + + var/paths = subtypesof(/datum/chemical_reaction) + GLOB.chemical_reactions_list = list() + + for(var/path in paths) + + var/datum/chemical_reaction/D = new path() + var/list/reaction_ids = list() + + if(D.required_reagents && D.required_reagents.len) + for(var/reaction in D.required_reagents) + reaction_ids += reaction + + // Create filters based on each reagent id in the required reagents list + for(var/id in reaction_ids) + if(!GLOB.chemical_reactions_list[id]) + GLOB.chemical_reactions_list[id] = list() + GLOB.chemical_reactions_list[id] += D + break // Don't bother adding ourselves to other reagent ids, it is redundant + +/////////////////////////////////////////////////////////////////////////////////// + +/datum/reagents + var/list/datum/reagent/reagent_list = new/list() + var/total_volume = 0 + var/maximum_volume = 100 + var/atom/my_atom = null + var/chem_temp = 150 + var/pH = REAGENT_NORMAL_PH + var/last_tick = 1 + var/addiction_tick = 1 + var/list/datum/reagent/addiction_list = new/list() + var/reagents_holder_flags + +/datum/reagents/New(maximum=100) + maximum_volume = maximum + + //I dislike having these here but map-objects are initialised before world/New() is called. >_> + if(!GLOB.chemical_reagents_list) + build_chemical_reagent_list() + if(!GLOB.chemical_reactions_list) + build_chemical_reactions_list() + +/datum/reagents/Destroy() + . = ..() + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + qdel(R) + cached_reagents.Cut() + cached_reagents = null + if(my_atom && my_atom.reagents == src) + my_atom.reagents = null + my_atom = null + +// Used in attack logs for reagents in pills and such +/datum/reagents/proc/log_list() + if(!length(reagent_list)) + return "no reagents" + + var/list/data = list() + for(var/r in reagent_list) //no reagents will be left behind + var/datum/reagent/R = r + data += "[R.id] ([round(R.volume, 0.1)]u)" + //Using IDs because SOME chemicals (I'm looking at you, chlorhydrate-beer) have the same names as other chemicals. + return english_list(data) + +/datum/reagents/proc/remove_any(amount = 1) + var/list/cached_reagents = reagent_list + var/total_transfered = 0 + var/current_list_element = 1 + + current_list_element = rand(1, cached_reagents.len) + + while(total_transfered != amount) + if(total_transfered >= amount) + break + if(total_volume <= 0 || !cached_reagents.len) + break + + if(current_list_element > cached_reagents.len) + current_list_element = 1 + + var/datum/reagent/R = cached_reagents[current_list_element] + remove_reagent(R.id, 1) + + current_list_element++ + total_transfered++ + update_total() + + handle_reactions() + return total_transfered + +/datum/reagents/proc/remove_all(amount = 1) + var/list/cached_reagents = reagent_list + if(total_volume > 0) + var/part = amount / total_volume + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + remove_reagent(R.id, R.volume * part) + + update_total() + handle_reactions() + return amount + +/datum/reagents/proc/get_master_reagent_name() + var/list/cached_reagents = reagent_list + var/name + var/max_volume = 0 + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.volume > max_volume) + max_volume = R.volume + name = R.name + + return name + +/datum/reagents/proc/get_master_reagent_id() + var/list/cached_reagents = reagent_list + var/id + var/max_volume = 0 + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.volume > max_volume) + max_volume = R.volume + id = R.id + + return id + +/datum/reagents/proc/get_master_reagent() + var/list/cached_reagents = reagent_list + var/datum/reagent/master + var/max_volume = 0 + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.volume > max_volume) + max_volume = R.volume + master = R + + return master + +/datum/reagents/proc/trans_to(obj/target, amount=1, multiplier=1, preserve_data=1, no_react = 0)//if preserve_data=0, the reagents data will be lost. Usefull if you use data for some strange stuff and don't want it to be transferred. + var/list/cached_reagents = reagent_list + if(!target || !total_volume) + return + if(amount < 0) + return + + var/datum/reagents/R + if(istype(target, /datum/reagents)) + R = target + else + if(!target.reagents) + return + R = target.reagents + amount = min(min(amount, src.total_volume), R.maximum_volume-R.total_volume) + var/part = amount / src.total_volume + var/trans_data = null + for(var/reagent in cached_reagents) + var/datum/reagent/T = reagent + var/transfer_amount = T.volume * part + if(preserve_data) + trans_data = copy_data(T) + + //fermichem Added ph TODO: add T.purity + R.add_reagent(T.id, transfer_amount * multiplier, trans_data, chem_temp, pH, no_react = TRUE) //we only handle reaction after every reagent has been transfered. + //R.add_reagent(T.id, transfer_amount * multiplier, trans_data, chem_temp, pH, T.purity, no_react = TRUE) //we only handle reaction after every reagent has been transfered. + remove_reagent(T.id, transfer_amount) + + update_total() + R.update_total() + if(!no_react) + R.handle_reactions() + src.handle_reactions() + return amount + +/datum/reagents/proc/copy_to(obj/target, amount=1, multiplier=1, preserve_data=1) + var/list/cached_reagents = reagent_list + if(!target || !total_volume) + return + + var/datum/reagents/R + if(istype(target, /datum/reagents)) + R = target + else + if(!target.reagents) + return + R = target.reagents + + if(amount < 0) + return + amount = min(min(amount, total_volume), R.maximum_volume-R.total_volume) + var/part = amount / total_volume + var/trans_data = null + for(var/reagent in cached_reagents) + var/datum/reagent/T = reagent + var/copy_amount = T.volume * part + if(preserve_data) + trans_data = T.data + R.add_reagent(T.id, copy_amount * multiplier, trans_data) + + src.update_total() + R.update_total() + R.handle_reactions() + src.handle_reactions() + return amount + +/datum/reagents/proc/trans_id_to(obj/target, reagent, amount=1, preserve_data=1)//Not sure why this proc didn't exist before. It does now! /N + var/list/cached_reagents = reagent_list + if (!target) + return + if (!target.reagents || src.total_volume<=0 || !src.get_reagent_amount(reagent)) + return + if(amount < 0) + return + + var/datum/reagents/R = target.reagents + if(src.get_reagent_amount(reagent)= R.overdose_threshold && !R.overdosed) + R.overdosed = 1 + need_mob_update += R.overdose_start(C) + if(R.addiction_threshold) + if(R.volume >= R.addiction_threshold && !is_type_in_list(R, cached_addictions)) + var/datum/reagent/new_reagent = new R.type() + cached_addictions.Add(new_reagent) + if(R.overdosed) + need_mob_update += R.overdose_process(C) + if(is_type_in_list(R,cached_addictions)) + for(var/addiction in cached_addictions) + var/datum/reagent/A = addiction + if(istype(R, A)) + A.addiction_stage = -15 // you're satisfied for a good while. + need_mob_update += R.on_mob_life(C) + + if(can_overdose) + if(addiction_tick == 6) + addiction_tick = 1 + for(var/addiction in cached_addictions) + var/datum/reagent/R = addiction + if(C && R) + R.addiction_stage++ + if(1 <= R.addiction_stage && R.addiction_stage <= R.addiction_stage1_end) + need_mob_update += R.addiction_act_stage1(C) + else if(R.addiction_stage1_end <= R.addiction_stage && R.addiction_stage <= R.addiction_stage2_end) + need_mob_update += R.addiction_act_stage2(C) + else if(R.addiction_stage2_end <= R.addiction_stage && R.addiction_stage <= R.addiction_stage3_end) + need_mob_update += R.addiction_act_stage3(C) + else if(R.addiction_stage3_end <= R.addiction_stage && R.addiction_stage <= R.addiction_stage4_end) + need_mob_update += R.addiction_act_stage4(C) + else if(R.addiction_stage4_end <= R.addiction_stage) + to_chat(C, "You feel like you've gotten over your need for [R.name].") + SEND_SIGNAL(C, COMSIG_CLEAR_MOOD_EVENT, "[R.id]_addiction") + cached_addictions.Remove(R) + addiction_tick++ + if(C && need_mob_update) //some of the metabolized reagents had effects on the mob that requires some updates. + C.updatehealth() + C.update_canmove() + C.update_stamina() + update_total() + + +/datum/reagents/proc/set_reacting(react = TRUE) + if(react) + reagents_holder_flags &= ~(REAGENT_NOREACT) + else + reagents_holder_flags |= REAGENT_NOREACT + +/datum/reagents/proc/conditional_update_move(atom/A, Running = 0) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + R.on_move (A, Running) + update_total() + +/datum/reagents/proc/conditional_update(atom/A) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + R.on_update (A) + update_total() + +/datum/reagents/proc/handle_reactions()//HERE EDIT HERE THE MAIN REACTION FERMICHEMS ASSEMBLE! I hope rp is similar + //FermiChem + var/purity = 1 + var/ammoReacted = 0 + var/deltaT = 0 + var/deltapH = 0 + var/stepChemAmmount = 0 + + var/list/cached_reagents = reagent_list //a list of the reagents? + var/list/cached_reactions = GLOB.chemical_reactions_list //a list of the whole reactions? + var/datum/cached_my_atom = my_atom //It says my atom, but I didn't bring one with me!! + if(reagents_holder_flags & REAGENT_NOREACT) //Not sure on reagents_holder_flags, but I think it checks to see if theres a reaction with current stuff. + return //Yup, no reactions here. No siree. + + var/reaction_occurred = 0 // checks if reaction, binary variable + do //What does do do in byond? It sounds very redundant? is it a while loop? + var/list/possible_reactions = list() //init list + reaction_occurred = 0 // sets it back to 0? + for(var/reagent in cached_reagents) //for reagent in beaker/holder + var/datum/reagent/R = reagent //check to make sure that reagent is there for the reaction list + for(var/reaction in cached_reactions[R.id]) // Was a big list but now it should be smaller since we filtered it with our reagent id + if(!reaction) + continue + + var/datum/chemical_reaction/C = reaction + var/list/cached_required_reagents = C.required_reagents + var/total_required_reagents = cached_required_reagents.len + var/total_matching_reagents = 0 + var/list/cached_required_catalysts = C.required_catalysts + var/total_required_catalysts = cached_required_catalysts.len + var/total_matching_catalysts= 0 + var/matching_container = 0 + var/matching_other = 0 + var/required_temp = C.required_temp + var/is_cold_recipe = C.is_cold_recipe + var/meets_temp_requirement = 0 + var/has_special_react = C.special_react + var/can_special_react = 0 + //FermiChem WHY ARE VARIBLES SO ESTRANGED it makes me sad + var/OptimalTempMin = C.OptimalTempMin // Lower area of bell curve for determining heat based rate reactions + var/OptimalTempMax = C.OptimalTempMax + var/ExplodeTemp = C.ExplodeTemp + var/OptimalpHMin = C.OptimalpHMin + var/OptimalpHMax = C.OptimalpHMax + var/ReactpHLim = C.ReactpHLim + //var/CatalystFact = C.CatalystFact + var/CurveSharpT = C.CurveSharpT + var/CurveSharppH = C.CurveSharppH + var/ThermicConstant = C.ThermicConstant + var/HIonRelease = C.HIonRelease + var/RateUpLim = C.RateUpLim + var/FermiChem = C.FermiChem + var/FermiExplode = C.FermiExplode + var/ImpureChem = C.ImpureChem + + //FermiChem + /* + var/purity = 1 + var/ammoReacted = 0 + + var/deltaT = 0 + var/deltapH = 0 + var/stepChemAmmount = 0 + */ + + for(var/B in cached_required_reagents) + if(!has_reagent(B, cached_required_reagents[B])) + break + total_matching_reagents++ + for(var/B in cached_required_catalysts) + if(!has_reagent(B, cached_required_catalysts[B])) + break + total_matching_catalysts++ + if(cached_my_atom) + if(!C.required_container) + matching_container = 1 + + else + if(cached_my_atom.type == C.required_container)//if the suspected container is a container + matching_container = 1 + if (isliving(cached_my_atom) && !C.mob_react) //Makes it so certain chemical reactions don't occur in mobs + return + if(!C.required_other)//Checks for other things required + matching_other = 1//binary check passes + + else if(istype(cached_my_atom, /obj/item/slime_extract))//if the object is a slime_extract. This might be complicated as to not break them via fermichem + var/obj/item/slime_extract/M = cached_my_atom + + if(M.Uses > 0) // added a limit to slime cores -- Muskets requested this + matching_other = 1 + else + if(!C.required_container)//I'm not sure why this is here twice, I think if it's not a beaker? Oh, cyro. + matching_container = 1 + if(!C.required_other) + matching_other = 1 + + //FermiChem + + if(required_temp == 0 || (is_cold_recipe && chem_temp <= required_temp) || (!is_cold_recipe && chem_temp >= required_temp))//Temperature check!! + meets_temp_requirement = 1//binary pass + + if(!has_special_react || C.check_special_react(src)) + can_special_react = 1 + + if(total_matching_reagents == total_required_reagents && total_matching_catalysts == total_required_catalysts && matching_container && matching_other && meets_temp_requirement && can_special_react) + possible_reactions += C + + if(possible_reactions.len)//does list exist? + var/datum/chemical_reaction/selected_reaction = possible_reactions[1] + //select the reaction with the most extreme temperature requirements + for(var/V in possible_reactions)//why V, surely that would indicate volume? V is the reaction potential. + var/datum/chemical_reaction/competitor = V //competitor? I think this is theres two of them. Troubling..! + if(selected_reaction.is_cold_recipe) //if there are no recipe conflicts, everything in possible_reactions will have this same value for is_cold_reaction. warranty void if assumption not met. + if(competitor.required_temp <= selected_reaction.required_temp)//only returns with lower if reaction "is cold" var. + selected_reaction = competitor + else + if(competitor.required_temp >= selected_reaction.required_temp) //will return with the hotter reacting first. + selected_reaction = competitor + var/list/cached_required_reagents = selected_reaction.required_reagents//update reagents list + var/list/cached_results = selected_reaction.results//resultant chemical list + var/special_react_result = selected_reaction.check_special_react(src) + var/list/multiplier = INFINITY //Wat + + for(var/B in cached_required_reagents) // + multiplier = min(multiplier, round(get_reagent_amount(B) / cached_required_reagents[B]))//a simple one over the other? (Is this for multiplying end product? Useful for toxinsludge buildup) + + //Splits reactions into two types; FermiChem is advanced reaction mechanics, Other is default reaction. + //FermiChem relies on two additional properties; pH and impurity + //Temperature plays into a larger role too. + if (C.FermiChem == TRUE) + message_admins("Hee!!!! Someone is doing a Fermi reaction!!! I'm so excited!!") + //FermiReact(C) + //B is Beaker + //P is product + multiplier = min(multiplier, round(get_reagent_amount(B) / cached_required_reagents[B]))//a simple one over the other? (Is this for multiplying end product? Useful for toxinsludge buildup) + + while (ammoReacted < multiplier) + //Begin Parse + + //Check extremes first + if (chem_temp > ExplodeTemp) + //go to explode proc + FermiExplode() + + if (pH > 14 || pH < 0) + //Create chemical sludge eventually(for now just destroy the beaker I guess?) + //TODO Strong acids eat glass, make it so you NEED plastic beakers for superacids(for some reactions) + FermiExplode() + + //For now, purity is handled elsewhere + + //Calculate DeltaT (Deviation of T from optimal) + if (chem_temp < C.OptimalTempMax) + deltaT = (((C.OptimalTempMin - chem_temp)**C.CurveSharpT)/((C.OptimalTempMax - C.OptimalTempMax)**C.CurveSharpT)) + else if (chem_temp >= C.OptimalTempMax) + deltaT = 1 + else + deltaT = 0 + + //Calculate DeltapH (Deviation of pH from optimal) + //Lower range + if (pH < C.OptimalpHMin) + if (pH < (C.OptimalpHMin - C.ReactpHLim)) + deltapH = 0 + else + deltapH = (((pH - (C.OptimalpHMin - C.ReactpHLim))**C.CurveSharp)/(C.ReactpHLim**C.CurveSharppH)) + //Upper range + else if (pH > C.OptimalpHMin) + if (pH > (C.OptimalpHMin + C.ReactpHLim)) + deltapH = 0 + else + deltapH = ((C.ReactpHLim -(pH - (C.OptimalpHMax + C.ReactpHLim))+C.ReactpHLim)/(C.ReactpHLim**C.CurveSharppH)) + //Within mid range + else if (pH >= C.OptimalpHMin && pH <= C.OptimalpHMax) + deltapH = 1 + //This should never proc: + else + message_admins("Fermichem's pH broke!! Please let Fermis know!!") + WARNING("[my_atom] attempted to determine FermiChem pH for '[reagent]' which broke for some reason! ([usr])") + //TODO Add CatalystFact + + stepChemAmmount = multiplier * deltaT + if (ammoReacted > 0) + P.purity = ((P.purity * ammoReacted) + (deltapH * stepChemAmmount)) /(2 * (ammoReacted + stepChemAmmount)) //This should add the purity to the product + else + P.purity = deltapH + //Apply pH changes and thermal output of reaction to beaker + chem_temp += (C.ThermicConstant * stepChemAmmount) + pH += (C.HIonRelease * stepChemAmmount) + + // End. + + selected_reaction.on_reaction(src, multiplier, special_react_result) + + for(var/B in cached_required_reagents) + remove_reagent(B, (stepChemAmmount * cached_required_reagents[B]), safety = 1)//safety? removes reagents from beaker using remove function. + + for(var/P in selected_reaction.results)//Not sure how this works, what is selected_reaction.results? + multiplier = max(multiplier, 1) //this shouldnt happen ... + SSblackbox.record_feedback("tally", "chemical_reaction", cached_results[P]*stepChemAmmount, P)//log + add_reagent(P, cached_results[P]*stepChemAmmount, null, chem_temp)//add reagent function!! I THINK I can do this: + + + ammoReacted = ammoReacted + stepChemAmmount + + reaction_occurred = 1 + SSblackbox.record_feedback("tally", "Fermi_chemical_reaction", cached_results[P]*ammoReacted, P)//log + //Standard reaction mechanics: + else: + + for(var/B in cached_required_reagents) // + multiplier = min(multiplier, round(get_reagent_amount(B) / cached_required_reagents[B]))//a simple one over the other? (Is this for multiplying end product? Useful for toxinsludge buildup) + + for(var/B in cached_required_reagents) + remove_reagent(B, (multiplier * cached_required_reagents[B]), safety = 1)//safety? removes reagents from beaker using remove function. + + for(var/P in selected_reaction.results)//Not sure how this works, what is selected_reaction.results? + multiplier = max(multiplier, 1) //this shouldnt happen ... + SSblackbox.record_feedback("tally", "chemical_reaction", cached_results[P]*multiplier, P)//log + add_reagent(P, cached_results[P]*multiplier, null, chem_temp)//add reagent function!! I THINK I can do this: + + + var/list/seen = viewers(4, get_turf(my_atom))//Sound and sight checkers + var/iconhtml = icon2html(cached_my_atom, seen) + if(cached_my_atom) + if(!ismob(cached_my_atom)) // No bubbling mobs + if(selected_reaction.mix_sound) + playsound(get_turf(cached_my_atom), selected_reaction.mix_sound, 80, 1) + + for(var/mob/M in seen) + to_chat(M, "[iconhtml] [selected_reaction.mix_message]") + + if(istype(cached_my_atom, /obj/item/slime_extract))//if there's an extract and it's used up. + var/obj/item/slime_extract/ME2 = my_atom + ME2.Uses-- + if(ME2.Uses <= 0) // give the notification that the slime core is dead + for(var/mob/M in seen) + to_chat(M, "[iconhtml] \The [my_atom]'s power is consumed in the reaction.") + ME2.name = "used slime extract" + ME2.desc = "This extract has been used up." + + selected_reaction.on_reaction(src, multiplier, special_react_result) + reaction_occurred = 1 + + while(reaction_occurred)//while do nothing? + update_total()//Don't know waht this does. + return 0//end! + +/datum/reagents/proc/FermiReact() + return + +/datum/reagents/proc/FermiExplode() + return + +/datum/reagents/proc/isolate_reagent(reagent) + var/list/cached_reagents = reagent_list + for(var/_reagent in cached_reagents) + var/datum/reagent/R = _reagent + if(R.id != reagent) + del_reagent(R.id) + update_total() + +/datum/reagents/proc/del_reagent(reagent) + var/list/cached_reagents = reagent_list + for(var/_reagent in cached_reagents) + var/datum/reagent/R = _reagent + if(R.id == reagent) + if(my_atom && isliving(my_atom)) + var/mob/living/M = my_atom + R.on_mob_delete(M) + qdel(R) + reagent_list -= R + update_total() + if(my_atom) + my_atom.on_reagent_change(DEL_REAGENT) + return 1 + +/datum/reagents/proc/update_total() + var/list/cached_reagents = reagent_list + total_volume = 0 + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.volume < 0.1) + del_reagent(R.id) + else + total_volume += R.volume + + return 0 + +/datum/reagents/proc/clear_reagents() + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + del_reagent(R.id) + return 0 + +/datum/reagents/proc/reaction(atom/A, method = TOUCH, volume_modifier = 1, show_message = 1) + var/react_type + if(isliving(A)) + react_type = "LIVING" + if(method == INGEST) + var/mob/living/L = A + L.taste(src) + else if(isturf(A)) + react_type = "TURF" + else if(isobj(A)) + react_type = "OBJ" + else + return + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + switch(react_type) + if("LIVING") + var/touch_protection = 0 + if(method == VAPOR) + var/mob/living/L = A + touch_protection = L.get_permeability_protection() + R.reaction_mob(A, method, R.volume * volume_modifier, show_message, touch_protection) + if("TURF") + R.reaction_turf(A, R.volume * volume_modifier, show_message) + if("OBJ") + R.reaction_obj(A, R.volume * volume_modifier, show_message) + +/datum/reagents/proc/holder_full() + if(total_volume >= maximum_volume) + return TRUE + return FALSE + +//Returns the average specific heat for all reagents currently in this holder. +/datum/reagents/proc/specific_heat() + . = 0 + var/cached_amount = total_volume //cache amount + var/list/cached_reagents = reagent_list //cache reagents + for(var/I in cached_reagents) + var/datum/reagent/R = I + . += R.specific_heat * (R.volume / cached_amount) + +/datum/reagents/proc/adjust_thermal_energy(J, min_temp = 2.7, max_temp = 1000) + var/S = specific_heat() + chem_temp = CLAMP(chem_temp + (J / (S * total_volume)), 2.7, 1000) + +/datum/reagents/proc/add_reagent(reagent, amount, list/data=null, reagtemp = 300, pH = 7, no_react = 0)//EDIT HERE TOO ~FERMICHEM~ + if(!isnum(amount) || !amount) + return FALSE + + if(amount <= 0) + return FALSE + + var/datum/reagent/D = GLOB.chemical_reagents_list[reagent] + if(!D) + WARNING("[my_atom] attempted to add a reagent called '[reagent]' which doesn't exist. ([usr])") + return FALSE + + update_total() + var/cached_total = total_volume + if(cached_total + amount > maximum_volume) + amount = (maximum_volume - cached_total) //Doesnt fit in. Make it disappear. Shouldnt happen. Will happen. + if(amount <= 0) + return FALSE + var/new_total = cached_total + amount + var/cached_temp = chem_temp + var/list/cached_reagents = reagent_list + + //Equalize temperature - Not using specific_heat() because the new chemical isn't in yet. + var/specific_heat = 0 + var/thermal_energy = 0 + for(var/i in cached_reagents) + var/datum/reagent/R = i + specific_heat += R.specific_heat * (R.volume / new_total) + thermal_energy += R.specific_heat * R.volume * cached_temp + specific_heat += D.specific_heat * (amount / new_total) + thermal_energy += D.specific_heat * amount * reagtemp + chem_temp = thermal_energy / (specific_heat * new_total) + //// + + //add the reagent to the existing if it exists + for(var/A in cached_reagents) + var/datum/reagent/R = A + if (R.id == reagent) + R.volume += amount + update_total() + if(my_atom) + my_atom.on_reagent_change(ADD_REAGENT) + R.on_merge(data, amount) + if(!no_react) + handle_reactions() + return TRUE + + //otherwise make a new one + var/datum/reagent/R = new D.type(data) + cached_reagents += R + R.holder = src + R.volume = amount + if(data) + R.data = data + R.on_new(data) + + if(isliving(my_atom)) + R.on_mob_add(my_atom) //Must occur befor it could posibly run on_mob_delete + update_total() + if(my_atom) + my_atom.on_reagent_change(ADD_REAGENT) + if(!no_react) + handle_reactions() + return TRUE + +/datum/reagents/proc/add_reagent_list(list/list_reagents, list/data=null) // Like add_reagent but you can enter a list. Format it like this: list("toxin" = 10, "beer" = 15) + for(var/r_id in list_reagents) + var/amt = list_reagents[r_id] + add_reagent(r_id, amt, data) + +/datum/reagents/proc/remove_reagent(reagent, amount, safety)//Added a safety check for the trans_id_to + + if(isnull(amount)) + amount = 0 + CRASH("null amount passed to reagent code") + return FALSE + + if(!isnum(amount)) + return FALSE + + if(amount < 0) + return FALSE + + var/list/cached_reagents = reagent_list + + for(var/A in cached_reagents) + var/datum/reagent/R = A + if (R.id == reagent) + //clamp the removal amount to be between current reagent amount + //and zero, to prevent removing more than the holder has stored + amount = CLAMP(amount, 0, R.volume) + R.volume -= amount + update_total() + if(!safety)//So it does not handle reactions when it need not to + handle_reactions() + if(my_atom) + my_atom.on_reagent_change(REM_REAGENT) + return TRUE + + return FALSE + +/datum/reagents/proc/has_reagent(reagent, amount = -1) + var/list/cached_reagents = reagent_list + for(var/_reagent in cached_reagents) + var/datum/reagent/R = _reagent + if (R.id == reagent) + if(!amount) + return R + else + if(R.volume >= amount) + return R + else + return 0 + + return 0 + +/datum/reagents/proc/get_reagent_amount(reagent) + var/list/cached_reagents = reagent_list + for(var/_reagent in cached_reagents) + var/datum/reagent/R = _reagent + if (R.id == reagent) + return R.volume + + return 0 + +/datum/reagents/proc/get_reagents() + var/list/names = list() + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + names += R.name + + return jointext(names, ",") + +/datum/reagents/proc/remove_all_type(reagent_type, amount, strict = 0, safety = 1) // Removes all reagent of X type. @strict set to 1 determines whether the childs of the type are included. + if(!isnum(amount)) + return 1 + var/list/cached_reagents = reagent_list + var/has_removed_reagent = 0 + + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + var/matches = 0 + // Switch between how we check the reagent type + if(strict) + if(R.type == reagent_type) + matches = 1 + else + if(istype(R, reagent_type)) + matches = 1 + // We found a match, proceed to remove the reagent. Keep looping, we might find other reagents of the same type. + if(matches) + // Have our other proc handle removement + has_removed_reagent = remove_reagent(R.id, amount, safety) + + return has_removed_reagent + +//two helper functions to preserve data across reactions (needed for xenoarch) +/datum/reagents/proc/get_data(reagent_id) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.id == reagent_id) + return R.data + +/datum/reagents/proc/set_data(reagent_id, new_data) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.id == reagent_id) + R.data = new_data + +/datum/reagents/proc/copy_data(datum/reagent/current_reagent) + if(!current_reagent || !current_reagent.data) + return null + if(!istype(current_reagent.data, /list)) + return current_reagent.data + + var/list/trans_data = current_reagent.data.Copy() + + // We do this so that introducing a virus to a blood sample + // doesn't automagically infect all other blood samples from + // the same donor. + // + // Technically we should probably copy all data lists, but + // that could possibly eat up a lot of memory needlessly + // if most data lists are read-only. + if(trans_data["viruses"]) + var/list/v = trans_data["viruses"] + trans_data["viruses"] = v.Copy() + + return trans_data + +/datum/reagents/proc/get_reagent(type) + var/list/cached_reagents = reagent_list + . = locate(type) in cached_reagents + +/datum/reagents/proc/generate_taste_message(minimum_percent=15) + // the lower the minimum percent, the more sensitive the message is. + var/list/out = list() + var/list/tastes = list() //descriptor = strength + if(minimum_percent <= 100) + for(var/datum/reagent/R in reagent_list) + if(!R.taste_mult) + continue + + if(istype(R, /datum/reagent/consumable/nutriment)) + var/list/taste_data = R.data + for(var/taste in taste_data) + var/ratio = taste_data[taste] + var/amount = ratio * R.taste_mult * R.volume + if(taste in tastes) + tastes[taste] += amount + else + tastes[taste] = amount + else + var/taste_desc = R.taste_description + var/taste_amount = R.volume * R.taste_mult + if(taste_desc in tastes) + tastes[taste_desc] += taste_amount + else + tastes[taste_desc] = taste_amount + //deal with percentages + // TODO it would be great if we could sort these from strong to weak + var/total_taste = counterlist_sum(tastes) + if(total_taste > 0) + for(var/taste_desc in tastes) + var/percent = tastes[taste_desc]/total_taste * 100 + if(percent < minimum_percent) + continue + var/intensity_desc = "a hint of" + if(percent > minimum_percent * 2 || percent == 100) + intensity_desc = "" + else if(percent > minimum_percent * 3) + intensity_desc = "the strong flavor of" + if(intensity_desc != "") + out += "[intensity_desc] [taste_desc]" + else + out += "[taste_desc]" + + return english_list(out, "something indescribable") + +/datum/reagents/proc/expose_temperature(var/temperature, var/coeff=0.02) + var/temp_delta = (temperature - chem_temp) * coeff + if(temp_delta > 0) + chem_temp = min(chem_temp + max(temp_delta, 1), temperature) + else + chem_temp = max(chem_temp + min(temp_delta, -1), temperature) + chem_temp = round(chem_temp) + handle_reactions() + +/////////////////////////////////////////////////////////////////////////////////// + + +// Convenience proc to create a reagents holder for an atom +// Max vol is maximum volume of holder +/atom/proc/create_reagents(max_vol) + if(reagents) + qdel(reagents) + reagents = new/datum/reagents(max_vol) + reagents.my_atom = src + +/proc/get_random_reagent_id() // Returns a random reagent ID minus blacklisted reagents + var/static/list/random_reagents = list() + if(!random_reagents.len) + for(var/thing in subtypesof(/datum/reagent)) + var/datum/reagent/R = thing + if(initial(R.can_synth)) + random_reagents += initial(R.id) + var/picked_reagent = pick(random_reagents) + return picked_reagent diff --git a/code/modules/reagents/chemistry/holderProcBackup.dm b/code/modules/reagents/chemistry/holderProcBackup.dm new file mode 100644 index 0000000000..02ebbc5096 --- /dev/null +++ b/code/modules/reagents/chemistry/holderProcBackup.dm @@ -0,0 +1,1003 @@ + +/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 + var/targetVol = 0 + var/reactedVol = 0 + var/fermiIsReacting = FALSE + var/fermiReactID = null + +/datum/reagents/New(maximum=100) + maximum_volume = maximum + + //I dislike having these here but map-objects are initialised before world/New() is called. >_> + if(!GLOB.chemical_reagents_list) + build_chemical_reagent_list() + if(!GLOB.chemical_reactions_list) + build_chemical_reactions_list() + +/datum/reagents/Destroy() + . = ..() + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + qdel(R) + cached_reagents.Cut() + cached_reagents = null + if(my_atom && my_atom.reagents == src) + my_atom.reagents = null + my_atom = null + +// Used in attack logs for reagents in pills and such +/datum/reagents/proc/log_list() + if(!length(reagent_list)) + return "no reagents" + + var/list/data = list() + for(var/r in reagent_list) //no reagents will be left behind + var/datum/reagent/R = r + data += "[R.id] ([round(R.volume, 0.1)]u)" + //Using IDs because SOME chemicals (I'm looking at you, chlorhydrate-beer) have the same names as other chemicals. + return english_list(data) + +/datum/reagents/proc/remove_any(amount = 1) + var/list/cached_reagents = reagent_list + var/total_transfered = 0 + var/current_list_element = 1 + + current_list_element = rand(1, cached_reagents.len) + + while(total_transfered != amount) + if(total_transfered >= amount) + break + if(total_volume <= 0 || !cached_reagents.len) + break + + if(current_list_element > cached_reagents.len) + current_list_element = 1 + + var/datum/reagent/R = cached_reagents[current_list_element] + remove_reagent(R.id, 1) + + current_list_element++ + total_transfered++ + update_total() + + handle_reactions() + return total_transfered + +/datum/reagents/proc/remove_all(amount = 1) + var/list/cached_reagents = reagent_list + if(total_volume > 0) + var/part = amount / total_volume + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + remove_reagent(R.id, R.volume * part) + + update_total() + handle_reactions() + return amount + +/datum/reagents/proc/get_master_reagent_name() + var/list/cached_reagents = reagent_list + var/name + var/max_volume = 0 + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.volume > max_volume) + max_volume = R.volume + name = R.name + + return name + +/datum/reagents/proc/get_master_reagent_id() + var/list/cached_reagents = reagent_list + var/id + var/max_volume = 0 + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.volume > max_volume) + max_volume = R.volume + id = R.id + + return id + +/datum/reagents/proc/get_master_reagent() + var/list/cached_reagents = reagent_list + var/datum/reagent/master + var/max_volume = 0 + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + if(R.volume > max_volume) + max_volume = R.volume + master = R + + return master + +/datum/reagents/proc/trans_to(obj/target, amount=1, multiplier=1, preserve_data=1, no_react = 0)//if preserve_data=0, the reagents data will be lost. Usefull if you use data for some strange stuff and don't want it to be transferred. + var/list/cached_reagents = reagent_list + if(!target || !total_volume) + return + if(amount < 0) + return + + var/datum/reagents/R + if(istype(target, /datum/reagents)) + R = target + else + if(!target.reagents) + return + R = target.reagents + amount = min(min(amount, src.total_volume), R.maximum_volume-R.total_volume) + var/part = amount / src.total_volume + var/trans_data = null + for(var/reagent in cached_reagents) + var/datum/reagent/T = reagent + var/transfer_amount = T.volume * part + if(preserve_data) + trans_data = copy_data(T) + + //fermichem Added ph TODO: add T.purity + R.add_reagent(T.id, transfer_amount * multiplier, trans_data, chem_temp, pH, no_react = TRUE) //we only handle reaction after every reagent has been transfered. + //R.add_reagent(T.id, transfer_amount * multiplier, trans_data, chem_temp, pH, T.purity, no_react = TRUE) //we only handle reaction after every reagent has been transfered. + remove_reagent(T.id, transfer_amount) + + update_total() + R.update_total() + if(!no_react) + R.handle_reactions() + src.handle_reactions() + return amount + +/datum/reagents/proc/copy_to(obj/target, amount=1, multiplier=1, preserve_data=1) + var/list/cached_reagents = reagent_list + if(!target || !total_volume) + return + + var/datum/reagents/R + if(istype(target, /datum/reagents)) + R = target + else + if(!target.reagents) + return + R = target.reagents + + if(amount < 0) + return + amount = min(min(amount, total_volume), R.maximum_volume-R.total_volume) + var/part = amount / total_volume + var/trans_data = null + for(var/reagent in cached_reagents) + var/datum/reagent/T = reagent + var/copy_amount = T.volume * part + if(preserve_data) + trans_data = T.data + R.add_reagent(T.id, copy_amount * multiplier, trans_data) + + src.update_total() + R.update_total() + R.handle_reactions() + src.handle_reactions() + return amount + +/datum/reagents/proc/trans_id_to(obj/target, reagent, amount=1, preserve_data=1)//Not sure why this proc didn't exist before. It does now! /N + var/list/cached_reagents = reagent_list + if (!target) + return + if (!target.reagents || src.total_volume<=0 || !src.get_reagent_amount(reagent)) + return + if(amount < 0) + return + + var/datum/reagents/R = target.reagents + if(src.get_reagent_amount(reagent)= R.overdose_threshold && !R.overdosed) + R.overdosed = 1 + need_mob_update += R.overdose_start(C) + if(R.addiction_threshold) + if(R.volume >= R.addiction_threshold && !is_type_in_list(R, cached_addictions)) + var/datum/reagent/new_reagent = new R.type() + cached_addictions.Add(new_reagent) + if(R.overdosed) + need_mob_update += R.overdose_process(C) + if(is_type_in_list(R,cached_addictions)) + for(var/addiction in cached_addictions) + var/datum/reagent/A = addiction + if(istype(R, A)) + A.addiction_stage = -15 // you're satisfied for a good while. + need_mob_update += R.on_mob_life(C) + + if(can_overdose) + if(addiction_tick == 6) + addiction_tick = 1 + for(var/addiction in cached_addictions) + var/datum/reagent/R = addiction + if(C && R) + R.addiction_stage++ + if(1 <= R.addiction_stage && R.addiction_stage <= R.addiction_stage1_end) + need_mob_update += R.addiction_act_stage1(C) + else if(R.addiction_stage1_end <= R.addiction_stage && R.addiction_stage <= R.addiction_stage2_end) + need_mob_update += R.addiction_act_stage2(C) + else if(R.addiction_stage2_end <= R.addiction_stage && R.addiction_stage <= R.addiction_stage3_end) + need_mob_update += R.addiction_act_stage3(C) + else if(R.addiction_stage3_end <= R.addiction_stage && R.addiction_stage <= R.addiction_stage4_end) + need_mob_update += R.addiction_act_stage4(C) + else if(R.addiction_stage4_end <= R.addiction_stage) + to_chat(C, "You feel like you've gotten over your need for [R.name].") + SEND_SIGNAL(C, COMSIG_CLEAR_MOOD_EVENT, "[R.id]_addiction") + cached_addictions.Remove(R) + addiction_tick++ + if(C && need_mob_update) //some of the metabolized reagents had effects on the mob that requires some updates. + C.updatehealth() + C.update_canmove() + C.update_stamina() + update_total() + + +/datum/reagents/proc/set_reacting(react = TRUE) + if(react) + reagents_holder_flags &= ~(REAGENT_NOREACT) + else + reagents_holder_flags |= REAGENT_NOREACT + +/datum/reagents/proc/conditional_update_move(atom/A, Running = 0) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + R.on_move (A, Running) + update_total() + +/datum/reagents/proc/conditional_update(atom/A) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + R.on_update (A) + update_total() + +/datum/reagents/proc/handle_reactions()//HERE EDIT HERE THE MAIN REACTION FERMICHEMS ASSEMBLE! I hope rp is similar + 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 + 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(SSfastprocess, src) + fermiIsReacting = FALSE + message_admins("FermiChem processing stopped") + reaction_occurred = 1 + return + + + + do //What does do do in byond? It sounds very redundant? is it a while loop? + var/list/possible_reactions = list() //init list + reaction_occurred = 0 // sets it back to 0? + for(var/reagent in cached_reagents) //for reagent in beaker/holder + var/datum/reagent/R = reagent //check to make sure that reagent is there for the reaction list + for(var/reaction in cached_reactions[R.id]) // Was a big list but now it should be smaller since we filtered it with our reagent id + if(!reaction) + continue + + var/datum/chemical_reaction/C = reaction + var/list/cached_required_reagents = C.required_reagents + var/total_required_reagents = cached_required_reagents.len + var/total_matching_reagents = 0 + var/list/cached_required_catalysts = C.required_catalysts + var/total_required_catalysts = cached_required_catalysts.len + var/total_matching_catalysts= 0 + var/matching_container = 0 + var/matching_other = 0 + var/required_temp = C.required_temp + var/is_cold_recipe = C.is_cold_recipe + var/meets_temp_requirement = 0 + var/has_special_react = C.special_react + var/can_special_react = 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 && !continue_reacting) + message_admins("FermiChem Proc'd") + reaction_occurred = 1 + + for(var/P in selected_reaction.results) + targetVol = cached_results[P]*multiplier + + if (chem_temp > C.OptimalTempMin)//To prevent pointless reactions + if (reactedVol < targetVol) + //reactedVol = FermiReact(selected_reaction, chem_temp, pH, multiplier, reactedVol, targetVol, cached_required_reagents, cached_results) + START_PROCESSING(SSfastprocess, src) + message_admins("FermiChem processing started") + fermiIsReacting = TRUE + fermiReactID = selected_reaction + else + fermiIsReacting = FALSE + STOP_PROCESSING(SSfastprocess, src) + else + return + + + 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) + + for(var/mob/M in seen) + to_chat(M, "[iconhtml] [selected_reaction.mix_message]") + + if(istype(cached_my_atom, /obj/item/slime_extract))//if there's an extract and it's used up. + var/obj/item/slime_extract/ME2 = my_atom + ME2.Uses-- + if(ME2.Uses <= 0) // give the notification that the slime core is dead + for(var/mob/M in seen) + to_chat(M, "[iconhtml] \The [my_atom]'s power is consumed in the reaction.") + ME2.name = "used slime extract" + ME2.desc = "This extract has been used up." + + selected_reaction.on_reaction(src, multiplier, special_react_result) + reaction_occurred = 1 + continue_reacting = TRUE + + while(reaction_occurred)//while do nothing? + update_total()//Don't know waht this does. + return 0//end! + +/datum/reagents/process() + if (chem_temp > C.OptimalTempMin)//To prevent pointless reactions + if (reactedVol < targetVol) + reactedVol = FermiReact(fermiReactID, chem_temp, pH, multiplier, reactedVol, targetVol, cached_required_reagents, cached_results) + else + STOP_PROCESSING(SSfastprocess, src) + //handle_reactions() + +/datum/reagents/proc/FermiReact(selected_reaction, chem_temp, pH, multiplier, 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 + //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 = targetVol * deltaT + if (stepChemAmmount > C.RateUpLim) + stepChemAmmount = C.RateUpLim + else if (stepChemAmmount < 0.01) + stepChemAmmount = 0.01 + + if ((reactedVol + stepChemAmmount) > targetVol) + stepChemAmmount = targetVol - reactedVol + message_admins("target volume reached. Reaction should stop after this loop") + + if (reactedVol > 0) + purity = ((purity * reactedVol) + (deltapH * stepChemAmmount)) /((reactedVol+ 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. + /* + 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: + + reactedVol = reactedVol + stepChemAmmount + + + return //(reactedVol) + +/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 diff --git a/code/modules/reagents/chemistry/holderbk3.dm b/code/modules/reagents/chemistry/holderbk3.dm new file mode 100644 index 0000000000..6c2f5a9876 --- /dev/null +++ b/code/modules/reagents/chemistry/holderbk3.dm @@ -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)= R.overdose_threshold && !R.overdosed) + R.overdosed = 1 + need_mob_update += R.overdose_start(C) + if(R.addiction_threshold) + if(R.volume >= R.addiction_threshold && !is_type_in_list(R, cached_addictions)) + var/datum/reagent/new_reagent = new R.type() + cached_addictions.Add(new_reagent) + if(R.overdosed) + need_mob_update += R.overdose_process(C) + if(is_type_in_list(R,cached_addictions)) + for(var/addiction in cached_addictions) + var/datum/reagent/A = addiction + if(istype(R, A)) + A.addiction_stage = -15 // you're satisfied for a good while. + need_mob_update += R.on_mob_life(C) + + if(can_overdose) + if(addiction_tick == 6) + addiction_tick = 1 + for(var/addiction in cached_addictions) + var/datum/reagent/R = addiction + if(C && R) + R.addiction_stage++ + if(1 <= R.addiction_stage && R.addiction_stage <= R.addiction_stage1_end) + need_mob_update += R.addiction_act_stage1(C) + else if(R.addiction_stage1_end <= R.addiction_stage && R.addiction_stage <= R.addiction_stage2_end) + need_mob_update += R.addiction_act_stage2(C) + else if(R.addiction_stage2_end <= R.addiction_stage && R.addiction_stage <= R.addiction_stage3_end) + need_mob_update += R.addiction_act_stage3(C) + else if(R.addiction_stage3_end <= R.addiction_stage && R.addiction_stage <= R.addiction_stage4_end) + need_mob_update += R.addiction_act_stage4(C) + else if(R.addiction_stage4_end <= R.addiction_stage) + to_chat(C, "You feel like you've gotten over your need for [R.name].") + SEND_SIGNAL(C, COMSIG_CLEAR_MOOD_EVENT, "[R.id]_addiction") + cached_addictions.Remove(R) + addiction_tick++ + if(C && need_mob_update) //some of the metabolized reagents had effects on the mob that requires some updates. + C.updatehealth() + C.update_canmove() + C.update_stamina() + update_total() + + +/datum/reagents/proc/set_reacting(react = TRUE) + if(react) + reagents_holder_flags &= ~(REAGENT_NOREACT) + else + reagents_holder_flags |= REAGENT_NOREACT + +/datum/reagents/proc/conditional_update_move(atom/A, Running = 0) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + R.on_move (A, Running) + update_total() + +/datum/reagents/proc/conditional_update(atom/A) + var/list/cached_reagents = reagent_list + for(var/reagent in cached_reagents) + var/datum/reagent/R = reagent + R.on_update (A) + update_total() + +/datum/reagents/proc/handle_reactions()//HERE EDIT HERE THE MAIN REACTION FERMICHEMS ASSEMBLE! I hope rp is similar + 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, "[iconhtml] [selected_reaction.mix_message]") + + if(istype(cached_my_atom, /obj/item/slime_extract))//if there's an extract and it's used up. + var/obj/item/slime_extract/ME2 = my_atom + ME2.Uses-- + if(ME2.Uses <= 0) // give the notification that the slime core is dead + for(var/mob/M in seen) + to_chat(M, "[iconhtml] \The [my_atom]'s power is consumed in the reaction.") + ME2.name = "used slime extract" + ME2.desc = "This extract has been used up." + + selected_reaction.on_reaction(src, multiplier, special_react_result) + reaction_occurred = 1 + + while(reaction_occurred)//while do nothing? + update_total()//Don't know waht this does. + return 0//end! + +/datum/reagents/proc/FermiReact(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 diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm index a52bb1ecfe..1bf862bd5e 100644 --- a/code/modules/reagents/chemistry/reagents.dm +++ b/code/modules/reagents/chemistry/reagents.dm @@ -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 . = ..() diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 5d1593d1e7..adfc1a507e 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -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() - ..() \ No newline at end of file + ..() + diff --git a/code/modules/reagents/chemistry/recipes.dm b/code/modules/reagents/chemistry/recipes.dm index 50006eef62..f5f5eda104 100644 --- a/code/modules/reagents/chemistry/recipes.dm +++ b/code/modules/reagents/chemistry/recipes.dm @@ -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. diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm index 05f30a7092..1aba4c5a95 100644 --- a/code/modules/reagents/reagent_containers/glass.dm +++ b/code/modules/reagents/reagent_containers/glass.dm @@ -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" diff --git a/config/admins.txt b/config/admins.txt deleted file mode 100644 index 27a2178e2c..0000000000 --- a/config/admins.txt +++ /dev/null @@ -1,11 +0,0 @@ -############################################################################################### -# Basically, ckey goes first. Rank goes after the "=" # -# Case is not important for ckey. # -# Case IS important for the rank. # -# All punctuation (spaces etc) EXCEPT '-', '_' and '@' will be stripped from rank names. # -# Ranks can be anything defined in admin_ranks.txt # -# NOTE: if the rank-name cannot be found in admin_ranks.txt, they will not be adminned! ~Carn # -# NOTE: syntax was changed to allow hyphenation of ranknames, since spaces are stripped. # -############################################################################################### - -yourckeygoeshere = Host diff --git a/modular_citadel/code/datums/mood_events/chem_events.dm b/modular_citadel/code/datums/mood_events/chem_events.dm new file mode 100644 index 0000000000..b1280c3559 --- /dev/null +++ b/modular_citadel/code/datums/mood_events/chem_events.dm @@ -0,0 +1,3 @@ +/datum/mood_event/eigenstate + mood_change = -1 + description = "Where the hell am I? Is this an alternative dimension ?\n" diff --git a/modular_citadel/code/datums/status_effects/chems.dm b/modular_citadel/code/datums/status_effects/chems.dm new file mode 100644 index 0000000000..6f3d4c99ae --- /dev/null +++ b/modular_citadel/code/datums/status_effects/chems.dm @@ -0,0 +1,44 @@ +/datum/status_effect/chem/SGDF + id = "SGDF" + var/mob/living/fermi_Clone + + +/datum/status_effect/chem/SGDF/on_apply() + message_admins("SGDF status appied") + var/typepath = owner.type + fermi_Clone = new typepath(owner.loc) + var/mob/living/carbon/M = owner + var/mob/living/carbon/C = fermi_Clone + + //fermi_Clone = new typepath(get_turf(M)) + //var/mob/living/carbon/C = fermi_Clone + //var/mob/living/carbon/SM = fermi_Gclone + + if(istype(C) && istype(M)) + C.real_name = M.real_name + M.dna.transfer_identity(C, transfer_SE=1) + C.updateappearance(mutcolor_update=1) + return ..() + +/datum/status_effect/chem/SGDF/tick() + message_admins("SDGF ticking") + if(owner.stat == DEAD) + message_admins("SGDF status swapping") + if(fermi_Clone && fermi_Clone.stat != DEAD) + if(owner.mind) + owner.mind.transfer_to(fermi_Clone) + owner.visible_message("Lucidity shoots to your previously blank mind as your mind suddenly finishes the cloning process. You marvel for a moment at yourself, as your mind subconciously recollects all your memories up until the point when you cloned yourself. curiously, you find that you memories are blank after you ingested the sythetic serum, leaving you to wonder where the other you is.") + fermi_Clone.visible_message("Lucidity shoots to your previously blank mind as your mind suddenly finishes the cloning process. You marvel for a moment at yourself, as your mind subconciously recollects all your memories up until the point when you cloned yourself. curiously, you find that you memories are blank after you ingested the sythetic serum, leaving you to wonder where the other you is.") + fermi_Clone = null + owner.remove_status_effect(src) + // to_chat(owner, "[linked_extract] desperately tries to move your soul to a living body, but can't find one!") + ..() + +/datum/status_effect/chem/SDGF/candidates + id = "SGDFCandi" + var/mob/living/fermi_Clone + var/list/candies = list() + +/datum/status_effect/chem/SDGF/candidates/on_apply() + candies = pollGhostCandidates("Do you want to play as a clone and do you agree to respect their character and act in a similar manner to them? I swear to god if you diddle them I will be very disapointed in you. ", "FermiClone", null, ROLE_SENTIENCE, 300) // see poll_ignore.dm, should allow admins to ban greifers or bullies + return ..() diff --git a/modular_citadel/code/modules/reagents/chemistry/reagents/fermi_reagents.dm b/modular_citadel/code/modules/reagents/chemistry/reagents/fermi_reagents.dm new file mode 100644 index 0000000000..9f13ac4d4c --- /dev/null +++ b/modular_citadel/code/modules/reagents/chemistry/reagents/fermi_reagents.dm @@ -0,0 +1,448 @@ +//Fermichem!! +//Fun chems for all the family + +//MCchem +//BE PE chemical +//Angel/astral chemical +//And tips their hat +//Naninte chem + +/datum/reagent/fermi + name = "Fermi" + id = "fermi" + taste_description = "If affection had a taste, this would be it." + +/datum/reagent/fermi/on_mob_life(mob/living/carbon/M) + //current_cycle++ + holder.remove_reagent(src.id, metabolization_rate / M.metabolism_efficiency, FALSE) //fermi reagents stay longer if you have a better metabolism + return ..() + +/datum/reagent/fermi/overdose_start(mob/living/carbon/M) + current_cycle++ + +//eigenstate Chem +//Teleports you to chemistry and back +//OD teleports you randomly around the Station +//Addiction send you on a wild ride and replaces you with an alternative reality version of yourself. + +/datum/reagent/fermi/eigenstate + name = "Eigenstasium" + id = "eigenstate" + description = "A strange mixture formed from a controlled reaction of bluespace with plasma, that causes localised eigenstate fluxuations within the patient" + taste_description = "." + color = "#60A584" // rgb: 96, 0, 255 + overdose_threshold = 15 + addiction_threshold = 20 + metabolization_rate = 0.5 * REAGENTS_METABOLISM + addiction_stage2_end = 30 + addiction_stage3_end = 40 + addiction_stage4_end = 43 //Incase it's too long + var/turf/open/location_created = null + var/turf/open/location_return = null + var/addictCyc1 = 1 + var/addictCyc2 = 1 + var/addictCyc3 = 1 + var/addictCyc4 = 1 + var/mob/living/fermi_Tclone = null + var/teleBool = FALSE + mob/living/carbon/purgeBody + + +/* +/datum/reagent/fermi/eigenstate/oew() + . = ..() //Needed! + location_created = get_turf(src) //Sets up coordinate of where it was created + message_admins("Attempting to get creation location from on_new() [location_created]") + //..()s +*/ +/datum/reagent/fermi/eigenstate/on_new() +//obj/item/reagent/fermi/eigenstate/Initialize() +//datum/reagent/fermi/eigenstate/Initialize() + . = ..() //Needed! + //if(holder && holder.my_atom) + location_created = get_turf(loc) //Sets up coordinate of where it was created + message_admins("Attempting to get creation location from init() [location_created]") + //..() + + +/datum/reagent/fermi/eigenstate/on_mob_life(mob/living/M) //Teleports to chemistry! + switch(current_cycle) + if(0) + location_return = get_turf(M) //sets up return point + to_chat(M, "You feel your wavefunction split!") + do_sparks(5,FALSE,M) + do_teleport(M, location_created, 0, asoundin = 'sound/effects/phasein.ogg') + //M.forceMove(location_created) //Teleports to creation location + do_sparks(5,FALSE,M) + if(prob(20)) + do_sparks(5,FALSE,M) + message_admins("eigenstate state: [current_cycle]") + ..() + +/datum/reagent/fermi/eigenstate/on_mob_delete(mob/living/M) //returns back to original location + do_sparks(5,FALSE,src) + to_chat(M, "You feel your wavefunction collapse!") + do_teleport(M, location_return, 0, asoundin = 'sound/effects/phasein.ogg') //Teleports home + do_sparks(5,FALSE,src) + ..() + +/datum/reagent/fermi/eigenstate/overdose_start(mob/living/M) //Overdose, makes you teleport randomly + . = ..() + to_chat(M, "Oh god, you feel like your wavefunction is about to tear.") + M.Jitter(10) + +/datum/reagent/fermi/eigenstate/overdose_process(mob/living/M) //Overdose, makes you teleport randomly + do_sparks(5,FALSE,src) + do_teleport(M, get_turf(M), 10, asoundin = 'sound/effects/phasein.ogg') + do_sparks(5,FALSE,src) + holder.remove_reagent("eigenstate",0.5)//So you're not stuck for 10 minutes teleporting + ..() //loop function + + +/datum/reagent/fermi/eigenstate/addiction_act_stage1(mob/living/M) //Welcome to Fermis' wild ride. + switch(src.addictCyc1) + if(1) + to_chat(M, "Your wavefunction feels like it's been ripped in half. You feel empty inside.") + M.Jitter(10) + M.nutrition = M.nutrition - (M.nutrition/15) + src.addictCyc1++ + ..() + +/datum/reagent/fermi/eigenstate/addiction_act_stage2(mob/living/M) + switch(src.addictCyc2) + if(1) + to_chat(M, "You start to convlse violently as you feel your consciousness split and merge across realities as your possessions fly wildy off your body.") + M.Jitter(50) + M.Knockdown(100) + M.Stun(40) + + var/items = M.get_contents() + var/obj/item/I = pick(items) + M.dropItemToGround(I, TRUE) + do_sparks(5,FALSE,I) + do_teleport(I, get_turf(I), 5, no_effects=TRUE); + do_sparks(5,FALSE,I) + src.addictCyc2++ + ..() + +/datum/reagent/fermi/eigenstate/addiction_act_stage3(mob/living/M)//Pulls multiple copies of the character from alternative realities while teleporting them around! + + //Clone function - spawns a clone then deletes it - simulates multiple copies of the player teleporting in + switch(src.addictCyc3) + if(0) + M.Jitter(100) + to_chat(M, "Your eigenstate starts to rip apart, causing a localised collapsed field as you're ripped from alternative universes, trapped around the densisty of the eigenstate event horizon.") + if(1) + var/typepath = M.type + fermi_Tclone = new typepath(M.loc) + var/mob/living/carbon/C = fermi_Tclone + fermi_Tclone.appearance = M.appearance + C.real_name = M.real_name + M.visible_message("[M] collapses in from an alternative reality!") + message_admins("Fermi T Clone: [fermi_Tclone]") + do_teleport(C, get_turf(C), 2, no_effects=TRUE) //teleports clone so it's hard to find the real one! + do_sparks(5,FALSE,C) + C.emote("spin") + M.emote("spin") + M.emote("me",1,"flashes into reality suddenly, gasping as they gaze around in a bewildered and highly confused fashion!",TRUE) + C.emote("me",1,"[pick("says", "cries", "mewls", "giggles", "shouts", "screams", "gasps", "moans", "whispers", "announces")], \"[pick("Bugger me, whats all this then?", "Hot damn, where is this?", "sacre bleu! O� suis-je?!", "Yee haw!", "WHAT IS HAPPENING?!", "Picnic!", "Das ist nicht deutschland. Das ist nicht akzeptabel!!!", "Ciekawe co na obiad?", "You fool! You took too much eigenstasium! You've doomed us all!", "Watashi no nihon'noanime no yona monodesu!", "What...what's with these teleports? It's like one of my Japanese animes...!", "Ik stond op het punt om mehki op tafel te zetten, en nu, waar ben ik?", "This must be the will of Stein's gate.", "Detta �r sista g�ngen jag dricker beepsky smash.", "Now neither of us will be virgins!")]\"") + message_admins("Fermi T Clone: [fermi_Tclone] teleport attempt") + if(2) + var/mob/living/carbon/C = fermi_Tclone + do_sparks(5,FALSE,C) + qdel(C) //Deletes CLONE, or at least I hope it is. + message_admins("Fermi T Clone: [fermi_Tclone] deletion attempt") + M.visible_message("[M] is snapped across to a different alternative reality!") + src.addictCyc3 = 0 //counter + fermi_Tclone = null + src.addictCyc3++ + message_admins("[src.addictCyc3]") + do_teleport(M, get_turf(M), 2, no_effects=TRUE) //Teleports player randomly + do_sparks(5,FALSE,M) + ..() //loop function + +/datum/reagent/fermi/eigenstate/addiction_act_stage4(mob/living/M) //Thanks for riding Fermis' wild ride. Mild jitter and player buggery. + switch(src.addictCyc4) + if(1) + do_sparks(5,FALSE,M) + do_teleport(M, get_turf(M), 2, no_effects=TRUE) //teleports clone so it's hard to find the real one! + do_sparks(5,FALSE,M) + M.Sleeping(50, 0) + M.Jitter(50) + M.Knockdown(0) + to_chat(M, "You feel your eigenstate settle, snapping an alternative version of yourself into reality. All your previous memories are lost and replaced with the alternative version of yourself. This version of you feels more [pick("affectionate", "happy", "lusty", "radical", "shy", "ambitious", "frank", "voracious", "sensible", "witty")] than your previous self, sent to god knows what universe.") + M.emote("me",1,"flashes into reality suddenly, gasping as they gaze around in a bewildered and highly confused fashion!",TRUE) + M.reagents.remove_all_type(/datum/reagent/toxin, 5*REM, 0, 1) + for(var/i in M.mood_events) + SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, i.id) + SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "Alternative dimension", /datum/mood_event/eigenstate) + + + if(prob(20)) + do_sparks(5,FALSE,M) + src.addictCyc4++ + + ..() + . = 1 + +///datum/reagent/fermi/eigenstate/overheat_explode(mob/living/M) +// return + +//eigenstate END + +//Clone serum #chemClone +/datum/reagent/fermi/SDGF //vars, mostly only care about keeping track if there's a player in the clone or not. + name = "synthetic-derived growth factor" + id = "SDGF" + description = "A rapidly diving mass of Embryonic stem cells. These cells are missing a nucleus and quickly replicate a host’s DNA before growing to form an almost perfect clone of the host. In some cases neural replication takes longer, though the underlying reason underneath has yet to be determined." + color = "#60A584" // rgb: 96, 0, 255 + var/playerClone = FALSE + var/unitCheck = FALSE + metabolization_rate = 0.25 * REAGENTS_METABOLISM + //var/datum/status_effect/chem/SDGF/candidates/candies + var/list/candies = list() + //var/polling = FALSE + var/list/result = list() + var/list/group = null + + //var/fClone_current_controller = OWNER + //var/mob/living/split_personality/clone//there's two so they can swap without overwriting + //var/mob/living/split_personality/owner + //var/mob/living/carbon/SM +/* +/datum/reagent/fermi/SDGF/New() + candidates = pollGhostCandidates("Do you want to play as a clone and do you agree to respect their character and act in a similar manner to them? I swear to god if you diddle them I will be very disapointed in you. ", "FermiClone", null, ROLE_SENTIENCE, 300) // see poll_ignore.dm, should allow admins to ban greifers or bullies + message_admins("Attempting to poll") +^^^breaks everything +*/ +/datum/reagent/fermi/proc/sepPoll() + var/list/procCandies = list() + procCandies = pollGhostCandidates("Do you want to play as a clone and do you agree to respect their character and act in a similar manner to them? I swear to god if you diddle them I will be very disapointed in you. ", "FermiClone", null, ROLE_SENTIENCE, 20) // see poll_ignore.dm, should allow admins to ban greifers or bullies + return procCandies + + /*if(1) //I'm not sure how pollCanditdates works, so I did this. Gives a chance for people to say yes. + M.apply_status_effect(/datum/status_effect/chem/SDGF/candidates) + /datum/status_effect/chem/SDGF/candidates/candies = new /datum/status_effect/chem/SDGF/candidates + ///datum/status_effect/chem/SDGF/candidates/candies = M.apply_status_effect(/datum/status_effect/chem/SDGF/candidates) + //candies = pollGhostCandidates("Do you want to play as a clone of [M.name] and do you agree to respect their character and act in a similar manner to them? I swear to god if you diddle them I will be very disapointed in you. ", "FermiClone", null, ROLE_SENTIENCE, 300) // see poll_ignore.dm, should allow admins to ban greifers or bullies + message_admins("Attempting to poll")*/ + +/datum/reagent/fermi/SDGF/on_mob_life(mob/living/carbon/M) //Clones user, then puts a ghost in them! If that fails, makes a braindead clone. + //Setup clone + switch(current_cycle) + if(1) + for(var/mob/dead/observer/G in GLOB.player_list) + group += G + for(var/m in group) + var/mob/W = m + message_admins("Attempting to poll") + showCandidatePollWindow(M, 190, "Do you want to play as a clone of [M.name] and do you agree to respect their character and act in a similar manner to them? I swear to god if you diddle them I will be very disapointed in you.", result, null, current_cycle, TRUE) + if(19) + for(var/mob/W in result) + if(!W.key || !W.client) + result -= W + candies = result + if(20 to INFINITY) + message_admins("Number of candidates [LAZYLEN(candies)]") + if(LAZYLEN(candies) && src.playerClone == FALSE) //If there's candidates, clone the person and put them in there! + message_admins("Candidate found!") + //var/typepath = owner.type + //clone = new typepath(owner.loc) + var/typepath = M.type + var/mob/living/carbon/fermi_Gclone = new typepath(M.loc) + //var/mob/living/carbon/SM = owner + //var/mob/living/carbon/M = M + var/mob/living/carbon/SM = fermi_Gclone + if(istype(SM) && istype(M)) + SM.real_name = M.real_name + M.dna.transfer_identity(SM) + SM.updateappearance(mutcolor_update=1) + var/mob/dead/observer/C = pick(candies) + SM.key = C.key + SM.mind.enslave_mind_to_creator(M) + + if(M.getorganslot(ORGAN_SLOT_ZOMBIE))//sure, it "treats" it, but "you've" still got it. Doesn't always work as well; needs a ghost. + var/obj/item/organ/zombie_infection/ZI = M.getorganslot(ORGAN_SLOT_ZOMBIE) + ZI.Remove(M) + ZI.Insert(SM) + + //SM.sentience_act() + to_chat(SM, "You feel a strange sensation building in your mind as you realise there's two of you, before you get a chance to think about it, you suddenly split from your old body, and find yourself face to face with yourself, or rather, your original self.") + to_chat(SM, "While you find your newfound existence strange, you share the same memories as [M.real_name]. [pick("However, You find yourself indifferent to the goals you previously had, and take more interest in your newfound independence, but still have an indescribable care for the safety of your original", "Your mind has not deviated from the tasks you set out to do, and now that there's two of you the tasks should be much easier.")]") + to_chat(M, "You feel a strange sensation building in your mind as you realise there's two of you, before you get a chance to think about it, you suddenly split from your old body, and find yourself face to face with yourself.") + M.visible_message("[M] suddenly shudders, and splits into two identical twins!") + SM.copy_known_languages_from(M, FALSE) + playerClone = TRUE + + M.next_move_modifier = 1 + M.nutrition = 150 + //BALANCE: should I make them a pacifist, or give them some cellular damage or weaknesses? + + //after_success(user, SM) + //qdel(src) + + else //No candidates leads to two outcomes; if there's already a braincless clone, it heals the user, as well as being a rare souce of clone healing (thematic!). + message_admins("Failed to find clone Candidate") + src.unitCheck = TRUE + if(M.has_status_effect(/datum/status_effect/chem/SGDF)) // Heal the user if they went to all this trouble to make it and can't get a clone, the poor fellow. + to_chat(M, "The cells fail to catalyse around a nucleation event, instead merging with your cells.") //This stuff is hard enough to make to rob a user of some benefit. Shouldn't replace Rezadone as it requires the user to not only risk making a player controlled clone, but also requires them to have split in two (which also requires 30u of SGDF). + M.adjustCloneLoss(-2, 0) + M.adjustBruteLoss(-2, 0) + M.adjustFireLoss(-2, 0) + M.heal_bodypart_damage(1,1) + M.remove_trait(TRAIT_DISFIGURED, TRAIT_GENERIC) + else //If there's no ghosts, but they've made a large amount, then proceed to make flavourful clone, where you become fat and useless until you split. + switch(current_cycle) + if(10) + to_chat(M, "You feel the synethic cells rest uncomfortably within your body as they start to pulse and grow rapidly.") + if(11 to 19) + M.nutrition = M.nutrition + (M.nutrition/10) + if(20) + to_chat(M, "You feel the synethic cells grow and expand within yourself, bloating your body outwards.") + if(21 to 39) + M.nutrition = M.nutrition + (M.nutrition/5) + if(40) + to_chat(M, "The synthetic cells begin to merge with your body, it feels like your body is made of a viscous water, making your movements difficult.") + M.next_move_modifier = 4//If this makes you fast then please fix it, it should make you slow!! + //candidates = pollGhostCandidates("Do you want to play as a clone of [M.name] and do you agree to respect their character and act in a similar manner to them? I swear to god if you diddle them I will be very disapointed in you. ", "FermiClone", null, ROLE_SENTIENCE, 300) // see poll_ignore.dm, should allow admins to ban greifers or bullies + if(41 to 69) + M.nutrition = M.nutrition + (M.nutrition/2) + if(70) + to_chat(M, "The cells begin to precipitate outwards of your body, you feel like you'll split soon...") + if (M.nutrition < 20000) + M.nutrition = 20000 //https://www.youtube.com/watch?v=Bj_YLenOlZI + if(76)//Upon splitting, you get really hungry and are capable again. Deletes the chem after you're done. + M.nutrition = 15//YOU BEST BE EATTING AFTER THIS YOU CUTIE + M.next_move_modifier = 1 + to_chat(M, "Your body splits away from the cell clone of yourself, leaving you with a drained and hollow feeling inside.") + M.apply_status_effect(/datum/status_effect/chem/SGDF) + if(77 to INFINITY) + holder.remove_reagent("SGDF", 1)//removes SGDF on completion. + message_admins("Purging SGDF [volume]") + message_admins("Growth nucleation occuring (SDGF), step [current_cycle] of 77") + + + ..() + +/datum/reagent/fermi/SDGF/on_mob_delete(mob/living/M) //When the chem is removed, a few things can happen. + if (playerClone == TRUE)//If the player made a clone with it, then thats all they get. + playerClone = FALSE + return + if (M.next_move_modifier == 4 && !M.has_status_effect(/datum/status_effect/chem/SGDF))//checks if they're ingested over 20u of the stuff, but fell short of the required 30u to make a clone. + to_chat(M, "You feel the cells begin to merge with your body, unable to reach nucleation, they instead merge with your body, healing any wounds.") + M.adjustCloneLoss(-10, 0) //I don't want to make Rezadone obsolete. + M.adjustBruteLoss(-25, 0)// Note that this takes a long time to apply and makes you fat and useless when it's in you, I don't think this small burst of healing will be useful considering how long it takes to get there. + M.adjustFireLoss(-25, 0) + M.blood_volume += 250 + M.heal_bodypart_damage(1,1) + M.next_move_modifier = 1 + if (M.nutrition < 1500) + M.nutrition += 250 + else if (src.unitCheck == TRUE && !M.has_status_effect(/datum/status_effect/chem/SGDF))// If they're ingested a little bit (10u minimum), then give them a little healing. + src.unitCheck = FALSE + to_chat(M, "the cells fail to hold enough mass to generate a clone, instead diffusing into your system instead. you can fee") + M.adjustBruteLoss(-10, 0) + M.adjustFireLoss(-10, 0) + M.blood_volume += 100 + if (M.nutrition < 1500) + M.nutrition += 500 + +/datum/reagent/fermi/SDZF + name = "synthetic-derived zombie factor" + id = "SDZF" + description = "A horribly peverse mass of Embryonic stem cells made real by the hands of a failed chemist. This message should never appear, how did you manage to get a hold of this?" + color = "#60A584" // rgb: 96, 0, 255 + metabolization_rate = 0.25 * REAGENTS_METABOLISM + var/fermi_Zombie + +/datum/reagent/fermi/SDZF/on_mob_life(mob/living/carbon/M) //If you're bad at fermichem, turns your clone into a zombie instead. + message_admins("SGZF ingested") + switch(current_cycle)//Pretends to be normal + if(10) + to_chat(M, "You feel the synethic cells rest uncomfortably within your body as they start to pulse and grow rapidly.") + if(11 to 19) + M.nutrition = M.nutrition + (M.nutrition/10) + if(20) + to_chat(M, "You feel the synethic cells grow and expand within yourself, bloating your body outwards.") + if(21 to 39) + M.nutrition = M.nutrition + (M.nutrition/5) + if(40) + to_chat(M, "The synethic cells begin to merge with your body, it feels like your body is made of a viscous water, making your movements difficult.") + M.next_move_modifier = 4//If this makes you fast then please fix it, it should make you slow!! + if(41 to 63) + M.nutrition = M.nutrition + (M.nutrition/2) + if(64) + to_chat(M, "The cells begin to precipitate outwards of your body, but... something is wrong, the sythetic cells are beginnning to rot...") + if (M.nutrition < 20000) + M.nutrition = 20000 //https://www.youtube.com/watch?v=Bj_YLenOlZI + if(65 to 75) + M.adjustToxLoss(1, 0)// the warning! + if(76) + if (!holder.has_reagent("pen_acid"))//Counterplay is pent.) + message_admins("Zombie spawned at [M.loc]") + M.nutrition -= 18500//YOU BEST BE RUNNING AWAY AFTER THIS YOU BADDIE + M.next_move_modifier = 1 + to_chat(M, "Your body splits away from the cell clone of yourself, your attempted clone birthing itself violently from you as it begins to shamble around, a terrifying abomination of science.") + M.visible_message("[M] suddenly shudders, and splits into a funky smelling copy of themselves!") + M.emote("scream") + M.adjustToxLoss(30, 0) + //fermi_Zombie = new typepath(M.loc) + var/mob/living/simple_animal/hostile/zombie/ZI = new(get_turf(M.loc)) + ZI.damage_coeff = list(BRUTE = ((1 / volume)**0.25) , BURN = ((1 / volume)**0.1), TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) + ZI.real_name = M.real_name//Give your offspring a big old kiss. + ZI.name = M.real_name + //ZI.updateappearance(mutcolor_update=1) + holder.remove_reagent("SGZF", 50) + else + to_chat(M, "The pentetic acid seems to have stopped the decay for now, clumping up the cells into a horrifying tumour.") + if(77 to INFINITY)//purges chemical fast, producing a "slime" for each one. Said slime is weak to fire. + M.nutrition -= 100 + var/mob/living/simple_animal/slime/S = new(get_turf(M.loc),"grey") + S.damage_coeff = list(BRUTE = ((1 / volume)**0.1) , BURN = 2, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) + S.name = "Living teratoma" + S.real_name = "Living teratoma" + //S.updateappearance(mutcolor_update=1) + holder.remove_reagent("SGZF", 50) + M.adjustToxLoss(10, 0) + to_chat(M, "A large glob of the tumour suddenly splits itself from your body. You feel grossed out and slimey...") + message_admins("Growth nucleation occuring (SDGF), step [current_cycle] of 20") + ..() + + +//Breast englargement +/datum/reagent/fermi/BElarger + name = "Sucubus Draft" + id = "BEenlager" + description = "A volatile collodial mixture derived from milk that encourages mammary production via a potent estrogen mix." + color = "#60A584" // rgb: 96, 0, 255 + overdose_threshold = 12 + +/datum/reagent/fermi/BElarger/overdose_start(mob/living/M) //Turns you into a female if male and ODing + if(M.gender == MALE) + M.gender = FEMALE + M.visible_message("[M] suddenly looks more feminine!", "You suddenly feel more feminine!") + + if(M.gender == FEMALE) + M.gender = MALE + M.visible_message("[M] suddenly looks more masculine!", "You suddenly feel more masculine!") +/* +//Nanite removal +/datum/reagent/fermi/naninte_b_gone + name = "Naninte bain" + id = "naninte_b_gone" + description = "A rather simple toxin to small nano machines that will kill them off at a rapid rate well in system." + color = "#5a7267" // rgb: 90, 114, 103 + overdose_threshold = 25 + +/datum/reagent/fermi/naninte_b_gone/on_mob_life(mob/living/carbon/C) + if(C./datum/component/nanites) + regen_rate = -5.0 + else + return + +/datum/reagent/fermi/naninte_b_gone/overdose_start(mob/living/carbon/C) + if(C./datum/component/nanites) + regen_rate = -7.5 + else + return +*/ diff --git a/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm b/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm new file mode 100644 index 0000000000..d4cbf6847a --- /dev/null +++ b/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm @@ -0,0 +1,92 @@ +//TO TWEAK: + +/datum/chemical_reaction/eigenstate + name = "Eigenstasium" + id = "eigenstate" + results = list("eigenstate" = 1) + required_reagents = list("bluespace" = 1, "stable_plasma" = 1, "sugar" = 1) + mix_message = "zaps brightly into existance, diffusing the energy from the localised gravity well as light" + //FermiChem vars: + OptimalTempMin = 350 // Lower area of bell curve for determining heat based rate reactions + 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 reactions (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) + CurveSharpT = 4 // How sharp the temperature exponential curve is (to the power of value) + CurveSharppH = 2 // How sharp the pH 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 = 5 //Optimal/max rate possible if all conditions are perfect + FermiChem = TRUE//If the chemical uses the Fermichem reaction mechanics + FermiExplode = FALSE //If the chemical explodes in a special way + ImpureChem = "toxin" //What chemical is produced with an inpure reaction + +//serum +/datum/chemical_reaction/SDGF + name = "synthetic-derived growth factor" + id = "SDGF" + results = list("SDGF" = 3) + required_reagents = list("plasma" = 1, "stable_plasma" = 1, "sugar" = 1) + //required_reagents = list("stable_plasma" = 5, "slimejelly" = 5, "synthflesh" = 10, "blood" = 10) + //FermiChem vars: + OptimalTempMin = 350 // Lower area of bell curve for determining heat based rate reactions + 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 reactions (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) + CurveSharpT = 4 // How sharp the temperature exponential curve is (to the power of value) + CurveSharppH = 2 // How sharp the pH exponential curve is (to the power of value) + ThermicConstant = 20 //Temperature change per 1u produced + HIonRelease = 0.01 //pH change per 1u reaction + RateUpLim = 5 //Optimal/max rate possible if all conditions are perfect + FermiChem = TRUE//If the chemical uses the Fermichem reaction mechanics + FermiExplode = FALSE //If the chemical explodes in a special way + ImpureChem = "toxin" //What chemical is produced with an inpure reaction + + +/datum/chemical_reaction/BElarger + name = "" + id = "e" + results = list("Eigenstasium" = 6) + required_reagents = list("salglu_solution" = 1, "milk" = 5, "synthflesh" = 2, "silicon" = 2, "crocin" = 2) + //FermiChem vars: + OptimalTempMin = 350 // Lower area of bell curve for determining heat based rate reactions + 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 reactions (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) + CurveSharpT = 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 + FermiChem = TRUE + +//Nano-b-gone +/datum/chemical_reaction/naninte_b_gone + name = "Naninte bain" + id = "naninte_b_gone" + results = list("naninte_b_gone" = 5) + required_reagents = list("synthflesh" = 5, "blood" = 3, "uranium" = 1, "salglu_solution" = 3) + mix_message = "the blood and sugar mixes catalysting a hard coating around the synth flesh" + //FermiChem vars: + OptimalTempMin = 450 + OptimalTempMax = 600 + ExplodeTemp = 700 + OptimalpHMin = 6 + OptimalpHMax = 8 + ReactpHLim = 1 + CatalystFact = 0 //To do 1 + CurveSharpT = 4 + CurveSharppH = 2 + ThermicConstant = -2.5 + HIonRelease = 0.01 + RateUpLim = 5 + FermiChem = TRUE + FermiExplode = FALSE + ImpureChem = "carpotoxin" diff --git a/tgstation.dme b/tgstation.dme index 9e9c6fe593..937f38fb3c 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -2798,10 +2798,12 @@ #include "modular_citadel\code\datums\components\material_container.dm" #include "modular_citadel\code\datums\components\phantomthief.dm" #include "modular_citadel\code\datums\components\souldeath.dm" +#include "modular_citadel\code\datums\mood_events\chem_events.dm" #include "modular_citadel\code\datums\mood_events\generic_negative_events.dm" #include "modular_citadel\code\datums\mood_events\generic_positive_events.dm" #include "modular_citadel\code\datums\mood_events\moodular.dm" #include "modular_citadel\code\datums\mutations\hulk.dm" +#include "modular_citadel\code\datums\status_effects\chems.dm" #include "modular_citadel\code\datums\status_effects\debuffs.dm" #include "modular_citadel\code\datums\traits\neutral.dm" #include "modular_citadel\code\datums\wires\airlock.dm" @@ -2980,7 +2982,9 @@ #include "modular_citadel\code\modules\projectiles\guns\energy\laser.dm" #include "modular_citadel\code\modules\projectiles\projectile\energy.dm" #include "modular_citadel\code\modules\projectiles\projectiles\reusable.dm" +#include "modular_citadel\code\modules\reagents\chemistry\reagents\fermi_reagents.dm" #include "modular_citadel\code\modules\reagents\chemistry\reagents\other_reagents.dm" +#include "modular_citadel\code\modules\reagents\chemistry\recipes\fermi.dm" #include "modular_citadel\code\modules\reagents\reagent container\cit_kegs.dm" #include "modular_citadel\code\modules\reagents\reagent container\hypospraymkii.dm" #include "modular_citadel\code\modules\reagents\reagent container\hypovial.dm"