/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/last_tick = 1 var/addiction_tick = 1 var/list/datum/reagent/addiction_list = new/list() var/reagents_holder_flags /datum/reagents/New(maximum=100, new_flags) 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() reagents_holder_flags = new_flags /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) 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. 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/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() if(reagents_holder_flags & NO_REACT) return //Yup, no reactions here. No siree. var/list/cached_reagents = reagent_list var/list/cached_reactions = GLOB.chemical_reactions_list var/datum/cached_my_atom = my_atom var/reaction_occurred = 0 do var/list/possible_reactions = list() reaction_occurred = 0 for(var/reagent in cached_reagents) var/datum/reagent/R = reagent 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) 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 else if(istype(cached_my_atom, /obj/item/slime_extract)) 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) 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 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) 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 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) selected_reaction = competitor else if(competitor.required_temp >= selected_reaction.required_temp) selected_reaction = competitor var/list/cached_required_reagents = selected_reaction.required_reagents var/list/cached_results = selected_reaction.results 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])) for(var/B in cached_required_reagents) remove_reagent(B, (multiplier * cached_required_reagents[B]), safety = 1) 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) 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) for(var/mob/M in seen) to_chat(M, "[iconhtml] [selected_reaction.mix_message]") 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." selected_reaction.on_reaction(src, multiplier, special_react_result) reaction_occurred = 1 while(reaction_occurred) update_total() return 0 /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, no_react = 0) 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, flags) if(reagents) qdel(reagents) reagents = new/datum/reagents(max_vol, flags) 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