diff --git a/code/__DEFINES/achievements.dm b/code/__DEFINES/achievements.dm index 7a7bf3489e56bc..da640d795e4d06 100644 --- a/code/__DEFINES/achievements.dm +++ b/code/__DEFINES/achievements.dm @@ -31,6 +31,7 @@ #define MEDAL_RUST_ASCENSION "Rust" #define MEDAL_VOID_ASCENSION "Void" #define MEDAL_TOOLBOX_SOUL "Toolsoul" +#define MEDAL_CHEM_TUT "Beginner Chemist" //Skill medal hub IDs #define MEDAL_LEGENDARY_MINER "Legendary Miner" diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index e7b7d485493a5f..c5dad7e1334725 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -208,6 +208,8 @@ #define COMSIG_REAGENTS_TEMP_CHANGE "reagents_temp_change" ///from base of [/datum/reagents/proc/handle_reactions]: (num_reactions) #define COMSIG_REAGENTS_REACTED "reagents_reacted" +///from base of [/datum/reagents/proc/process]: (num_reactions) +#define COMSIG_REAGENTS_REACTION_STEP "reagents_time_step" ///from base of [/atom/proc/expose_reagents]: (/atom, /list, methods, volume_modifier, show_message) #define COMSIG_REAGENTS_EXPOSE_ATOM "reagents_expose_atom" ///from base of [/obj/proc/expose_reagents]: (/obj, /list, methods, volume_modifier, show_message) diff --git a/code/__DEFINES/reagents.dm b/code/__DEFINES/reagents.dm index c149c503a69254..e4baf7f9d577da 100644 --- a/code/__DEFINES/reagents.dm +++ b/code/__DEFINES/reagents.dm @@ -12,6 +12,7 @@ #define TRANSPARENT (1<<5) // Used on containers which you want to be able to see the reagents off. #define AMOUNT_VISIBLE (1<<6) // For non-transparent containers that still have the general amount of reagents in them visible. #define NO_REACT (1<<7) // Applied to a reagent holder, the contents will not react with each other. +#define REAGENT_HOLDER_INSTANT_REACT (1<<8) // Applied to a reagent holder, all of the reactions in the reagents datum will be instant. Meant to be used for things like smoke effects where reactions aren't meant to occur // Is an open container for all intents and purposes. #define OPENCONTAINER (REFILLABLE | DRAINABLE | TRANSPARENT) @@ -43,3 +44,42 @@ #define CONDIMASTER_STYLE_FALLBACK "_" #define ALLERGIC_REMOVAL_SKIP "Allergy" + +//Used in holder.dm/equlibrium.dm to set values and volume limits +///stops floating point errors causing issues with checking reagent amounts +#define CHEMICAL_QUANTISATION_LEVEL 0.0001 +///The smallest amount of volume allowed - prevents tiny numbers +#define CHEMICAL_VOLUME_MINIMUM 0.001 +///Round to this, to prevent extreme decimal magic and to keep reagent volumes in line with perceived values. +#define CHEMICAL_VOLUME_ROUNDING 0.01 +///Default pH for reagents datum +#define CHEMICAL_NORMAL_PH 7.000 + +//reagent bitflags, used for altering how they works +///allows on_mob_dead() if present in a dead body +#define REAGENT_DEAD_PROCESS (1<<0) +///Do not split the chem at all during processing - ignores all purity effects +#define REAGENT_DONOTSPLIT (1<<1) +///Doesn't appear on handheld health analyzers. +#define REAGENT_INVISIBLE (1<<2) +///When inverted, the inverted chem uses the name of the original chem +#define REAGENT_SNEAKYNAME (1<<3) +///Retains initial volume of chem when splitting for purity effects +#define REAGENT_SPLITRETAINVOL (1<<4) + +//Chemical reaction flags, for determining reaction specialties +///Convert into impure/pure on reaction completion +#define REACTION_CLEAR_IMPURE (1<<0) +///Convert into inverse on reaction completion when purity is low enough +#define REACTION_CLEAR_INVERSE (1<<1) +///Clear converted chems retain their purities/inverted purities. Requires 1 or both of the above. +#define REACTION_CLEAR_RETAIN (1<<2) +///Used to create instant reactions +#define REACTION_INSTANT (1<<3) +///Used to force reactions to create a specific amount of heat per 1u created. So if thermic_constant = 5, for 1u of reagent produced, the heat will be forced up arbitarily by 5 irresepective of other reagents. If you use this, keep in mind standard thermic_constant values are 100x what it should be with this enabled. +#define REACTION_HEAT_ARBITARY (1<<4) +///Used to bypass the chem_master transfer block (This is needed for competitive reactions unless you have an end state programmed). More stuff might be added later. When defining this, please add in the comments the associated reactions that it competes with +#define REACTION_COMPETITIVE (1<<5) + +///Used to force an equlibrium to end a reaction in reaction_step() (i.e. in a reaction_step() proc return END_REACTION to end it) +#define END_REACTION "end_reaction" diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index b6d5cceac545a2..3bdfedbc96b732 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -119,6 +119,7 @@ #define INIT_ORDER_QUIRKS 60 #define INIT_ORDER_TICKER 55 #define INIT_ORDER_TCG 55 +#define INIT_ORDER_REAGENTS 55 //HAS to be before mapping - mapping creates objects, which creates reagents, which relies on lists made in this subsystem #define INIT_ORDER_MAPPING 50 #define INIT_ORDER_TIMETRACK 47 #define INIT_ORDER_NETWORKS 45 @@ -161,6 +162,7 @@ #define FIRE_PRIORITY_NPC 20 #define FIRE_PRIORITY_PROCESS 25 #define FIRE_PRIORITY_THROWING 25 +#define FIRE_PRIORITY_REAGENTS 26 #define FIRE_PRIORITY_SPACEDRIFT 30 #define FIRE_PRIORITY_FIELDS 30 #define FIRE_PRIOTITY_SMOOTHING 35 diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 08ca05396e8caa..a6f64518d82592 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -11,7 +11,8 @@ #define LAZYINITLIST(L) if (!L) L = list() #define UNSETEMPTY(L) if (L && !length(L)) L = null -#define LAZYCOPY(L) (L ? L.Copy() : list() ) +///Like LAZYCOPY - copies an input list if the list has entries, If it doesn't the assigned list is nulled +#define LAZYLISTDUPLICATE(L) (L ? L.Copy() : null ) #define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } } #define LAZYADD(L, I) if(!L) { L = list(); } L += I; #define LAZYOR(L, I) if(!L) { L = list(); } L |= I; @@ -19,12 +20,17 @@ #define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null) #define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V; #define LAZYLEN(L) length(L) -#define LAZYCLEARLIST(L) if(L) L.Cut() -#define SANITIZE_LIST(L) ( islist(L) ? L : list() ) -#define reverseList(L) reverseRange(L.Copy()) +///Sets a list to null +#define LAZYNULL(L) L = null #define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += list(V); #define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; } #define LAZYACCESSASSOC(L, I, K) L ? L[I] ? L[I][K] ? L[I][K] : null : null : null +#define QDEL_LAZYLIST(L) for(var/I in L) qdel(I); L = null; +//These methods don't null the list +#define LAZYCOPY(L) (L ? L.Copy() : list() ) //Use LAZYLISTDUPLICATE instead if you want it to null with no entries +#define LAZYCLEARLIST(L) if(L) L.Cut() // Consider LAZYNULL instead +#define SANITIZE_LIST(L) ( islist(L) ? L : list() ) +#define reverseList(L) reverseRange(L.Copy()) /// Passed into BINARY_INSERT to compare keys #define COMPARE_KEY __BIN_LIST[__BIN_MID] diff --git a/code/__HELPERS/reagents.dm b/code/__HELPERS/reagents.dm index e96a5509f66668..92c7b872bb1db3 100644 --- a/code/__HELPERS/reagents.dm +++ b/code/__HELPERS/reagents.dm @@ -1,4 +1,8 @@ /proc/chem_recipes_do_conflict(datum/chemical_reaction/r1, datum/chemical_reaction/r2) + //We have to check to see if either is competitive so can ignore it (competitive reagents are supposed to conflict) + if((r1.reaction_flags & REACTION_COMPETITIVE) || (r2.reaction_flags & REACTION_COMPETITIVE)) + return FALSE + //do the non-list tests first, because they are cheaper if(r1.required_container != r2.required_container) return FALSE diff --git a/code/_compile_options.dm b/code/_compile_options.dm index 8ff0ac753a446b..e4ac1b2b099917 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -15,6 +15,13 @@ //#define REFERENCE_TRACKING #ifdef REFERENCE_TRACKING +/* +* Enables debug messages for every single reaction step. This is 1 message per 0.5s for a SINGLE reaction. Useful for tracking down bugs/asking me for help in the main reaction handiler (equilibrium.dm). +* +* * Requires TESTING to be defined to work. +*/ +//#define REAGENTS_TESTING + ///Run a lookup on things hard deleting by default. //#define GC_FAILURE_HARD_LOOKUP #ifdef GC_FAILURE_HARD_LOOKUP diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index b9156b8ba1a006..ab3f28a557f965 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -294,3 +294,18 @@ DEFINE_BITFIELD(zap_flags, list( "ZAP_OBJ_DAMAGE" = ZAP_OBJ_DAMAGE, "ZAP_GENERATES_POWER" = ZAP_GENERATES_POWER, )) + +DEFINE_BITFIELD(chemical_flags, list( + "REAGENT_DEAD_PROCESS" = REAGENT_DEAD_PROCESS, + "REAGENT_DONOTSPLIT" = REAGENT_DONOTSPLIT, + "REAGENT_INVISIBLE" = REAGENT_INVISIBLE, + "REAGENT_SNEAKYNAME" = REAGENT_SNEAKYNAME, + "REAGENT_SPLITRETAINVOL" = REAGENT_SPLITRETAINVOL, +)) + +DEFINE_BITFIELD(reaction_flags, list( + "REACTION_CLEAR_IMPURE" = REACTION_CLEAR_IMPURE, + "REACTION_CLEAR_INVERSE" = REACTION_CLEAR_INVERSE, + "REACTION_CLEAR_RETAIN" = REACTION_CLEAR_RETAIN, + "REACTION_INSTANT" = REACTION_INSTANT, +)) diff --git a/code/controllers/subsystem/processing/reagents.dm b/code/controllers/subsystem/processing/reagents.dm new file mode 100644 index 00000000000000..344c066adf2802 --- /dev/null +++ b/code/controllers/subsystem/processing/reagents.dm @@ -0,0 +1,42 @@ +//Used for active reactions in reagents/equilibrium datums + +PROCESSING_SUBSYSTEM_DEF(reagents) + name = "Reagents" + init_order = INIT_ORDER_REAGENTS + priority = FIRE_PRIORITY_REAGENTS + wait = 0.25 SECONDS //You might think that rate_up_lim has to be set to half, but since everything is normalised around delta_time, it automatically adjusts it to be per second. Magic! + flags = SS_KEEP_TIMING + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + ///What time was it when we last ticked + var/previous_world_time = 0 + +/datum/controller/subsystem/processing/reagents/Initialize() + . = ..() + //So our first step isn't insane + previous_world_time = world.time + //Build GLOB lists - see holder.dm + build_chemical_reagent_list() + build_chemical_reactions_list() + return + +/datum/controller/subsystem/processing/reagents/fire(resumed = FALSE) + if (!resumed) + currentrun = processing.Copy() + //cache for sanic speed (lists are references anyways) + var/list/current_run = currentrun + + //Attempt to realtime reactions in a way that doesn't make them overtly dangerous + var/delta_realtime = (world.time - previous_world_time)/10 //normalise to s from ds + previous_world_time = world.time + + while(current_run.len) + var/datum/thing = current_run[current_run.len] + current_run.len-- + if(QDELETED(thing)) + stack_trace("Found qdeleted thing in [type], in the current_run list.") + processing -= thing + else if(thing.process(delta_realtime) == PROCESS_KILL) //we are realtime + // fully stop so that a future START_PROCESSING will work + STOP_PROCESSING(src, thing) + if (MC_TICK_CHECK) + return diff --git a/code/datums/achievements/misc_achievements.dm b/code/datums/achievements/misc_achievements.dm index c08b2d9dbbdd46..0da38df8f306a3 100644 --- a/code/datums/achievements/misc_achievements.dm +++ b/code/datums/achievements/misc_achievements.dm @@ -153,3 +153,9 @@ desc = "My eternal soul was destroyed to make a toolbox look funny and all I got was this achievement..." database_id = MEDAL_TOOLBOX_SOUL icon = "toolbox_soul" + +/datum/award/achievement/misc/chemistry_tut + name = "Perfect chemistry blossom" + desc = "Passed the chemistry tutorial with perfect purity!" + database_id = MEDAL_CHEM_TUT + icon = "chem_tut" diff --git a/code/datums/components/plumbing/reaction_chamber.dm b/code/datums/components/plumbing/reaction_chamber.dm index 88d7c760a7dc09..07ee12b108edd7 100644 --- a/code/datums/components/plumbing/reaction_chamber.dm +++ b/code/datums/components/plumbing/reaction_chamber.dm @@ -36,3 +36,9 @@ RC.emptying = TRUE //If we move this up, it'll instantly get turned off since any reaction always sets the reagent_total to zero. Other option is make the reaction update //everything for every chemical removed, wich isn't a good option either. RC.on_reagent_change(reagents) //We need to check it now, because some reactions leave nothing left. + + +/datum/component/plumbing/reaction_chamber/transfer_to(datum/component/plumbing/target, amount, reagent, datum/ductnet/net) + if(reagents.is_reacting == TRUE) //Let the thing react in peace + return + return ..() diff --git a/code/datums/quirks/negative.dm b/code/datums/quirks/negative.dm index 3421b5f550c5b6..2ec0d00f540d86 100644 --- a/code/datums/quirks/negative.dm +++ b/code/datums/quirks/negative.dm @@ -193,7 +193,7 @@ if("Psychologist") heirloom_type = /obj/item/storage/pill_bottle if("Chemist") - heirloom_type = /obj/item/book/manual/wiki/chemistry + heirloom_type = pick(/obj/item/book/manual/wiki/chemistry, /obj/item/ph_booklet) if("Virologist") heirloom_type = /obj/item/reagent_containers/syringe //Engineering diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 630c68415da44c..4af731ba37cde1 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -611,7 +611,10 @@ if(length(reagents.reagent_list)) if(user.can_see_reagents()) //Show each individual reagent for(var/datum/reagent/R in reagents.reagent_list) - . += "[R.volume] units of [R.name]" + . += "[round(R.volume, 0.01)] units of [R.name]" + if(reagents.is_reacting) + . += "It is currently reacting!" + . += "The solution's pH is [round(reagents.ph, 0.01)] and has a temperature of [reagents.chem_temp]K." else //Otherwise, just show the total volume var/total_volume = 0 for(var/datum/reagent/R in reagents.reagent_list) diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm index 4f36f7ac73efbf..cca2561c4f7305 100644 --- a/code/game/machinery/Sleeper.dm +++ b/code/game/machinery/Sleeper.dm @@ -202,6 +202,8 @@ data["occupant"]["reagents"] = list() if(mob_occupant.reagents && mob_occupant.reagents.reagent_list.len) for(var/datum/reagent/R in mob_occupant.reagents.reagent_list) + if(R.chemical_flags & REAGENT_INVISIBLE) //Don't show hidden chems + continue data["occupant"]["reagents"] += list(list("name" = R.name, "volume" = R.volume)) return data diff --git a/code/game/machinery/medical_kiosk.dm b/code/game/machinery/medical_kiosk.dm index 5f1c2af708b913..3cd0b413d7c735 100644 --- a/code/game/machinery/medical_kiosk.dm +++ b/code/game/machinery/medical_kiosk.dm @@ -235,6 +235,8 @@ if(altPatient.reagents.reagent_list.len) //Chemical Analysis details. for(var/r in altPatient.reagents.reagent_list) var/datum/reagent/reagent = r + if(reagent.chemical_flags & REAGENT_INVISIBLE) //Don't show hidden chems + continue chemical_list += list(list("name" = reagent.name, "volume" = round(reagent.volume, 0.01))) if(reagent.overdosed) overdose_list += list(list("name" = reagent.name)) @@ -242,6 +244,8 @@ if(belly?.reagents.reagent_list.len) //include the stomach contents if it exists for(var/bile in belly.reagents.reagent_list) var/datum/reagent/bit = bile + if(bit.chemical_flags & REAGENT_INVISIBLE) //Don't show hidden chems + continue if(!belly.food_reagents[bit.type]) chemical_list += list(list("name" = bit.name, "volume" = round(bit.volume, 0.01))) else diff --git a/code/game/objects/effects/effect_system/effects_foam.dm b/code/game/objects/effects/effect_system/effects_foam.dm index 0466504ac5cc2d..513fc71153a80f 100644 --- a/code/game/objects/effects/effect_system/effects_foam.dm +++ b/code/game/objects/effects/effect_system/effects_foam.dm @@ -91,7 +91,7 @@ /obj/effect/particle_effect/foam/Initialize() . = ..() - create_reagents(1000) //limited by the size of the reagent holder anyway. + create_reagents(1000, REAGENT_HOLDER_INSTANT_REACT) //limited by the size of the reagent holder anyway. Works without instant possibly edit in future START_PROCESSING(SSfastprocess, src) playsound(src, 'sound/effects/bubbles2.ogg', 80, TRUE, -3) @@ -224,7 +224,7 @@ /datum/effect_system/foam_spread/New() ..() chemholder = new /obj() - var/datum/reagents/R = new/datum/reagents(1000) + var/datum/reagents/R = new/datum/reagents(1000, REAGENT_HOLDER_INSTANT_REACT) //same as above chemholder.reagents = R R.my_atom = chemholder diff --git a/code/game/objects/effects/effect_system/effects_smoke.dm b/code/game/objects/effects/effect_system/effects_smoke.dm index 04a4f901676814..aaf4f52889bc13 100644 --- a/code/game/objects/effects/effect_system/effects_smoke.dm +++ b/code/game/objects/effects/effect_system/effects_smoke.dm @@ -263,8 +263,9 @@ /datum/effect_system/smoke_spread/chem/New() ..() chemholder = new /obj() - var/datum/reagents/R = new/datum/reagents(500) + var/datum/reagents/R = new (500, REAGENT_HOLDER_INSTANT_REACT) //This is a safety for now to prevent smoke generating more smoke as the smoke reagents react in the smoke. This is prevented naturally from happening even if this is off, but I want to be sure that any edge cases are prevented before I get a chance to rework smoke reactions (specifically adding water or reacting away stabilizing agent in the middle of it). chemholder.reagents = R + R.my_atom = chemholder /datum/effect_system/smoke_spread/chem/Destroy() @@ -288,7 +289,7 @@ contained = "\[[contained]\]" var/where = "[AREACOORD(location)]" - if(carry.my_atom.fingerprintslast) + if(carry.my_atom?.fingerprintslast) //Some reagents don't have a my_atom in some cases var/mob/M = get_mob_by_key(carry.my_atom.fingerprintslast) var/more = "" if(M) diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index c217af95f943b2..ea2ea76c45f0a8 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -430,6 +430,8 @@ GENE SCANNER render_list += "Subject contains the following reagents in their blood:\n" for(var/r in M.reagents.reagent_list) var/datum/reagent/reagent = r + if(reagent.chemical_flags & REAGENT_INVISIBLE) //Don't show hidden chems on scanners + continue render_list += "[round(reagent.volume, 0.001)] units of [reagent.name][reagent.overdosed ? " - OVERDOSING" : "."]\n" else render_list += "Subject contains no reagents in their blood.\n" @@ -439,6 +441,8 @@ GENE SCANNER render_list += "Subject contains the following reagents in their stomach:\n" for(var/bile in belly.reagents.reagent_list) var/datum/reagent/bit = bile + if(bit.chemical_flags & REAGENT_INVISIBLE) //Don't show hidden chems on scanners + continue if(!belly.food_reagents[bit.type]) render_list += "[round(bit.volume, 0.001)] units of [bit.name][bit.overdosed ? " - OVERDOSING" : "."]\n" else diff --git a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm index e5705f28015902..8e01f29c65af0a 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm @@ -120,6 +120,9 @@ new /obj/item/storage/box/pillbottles(src) new /obj/item/storage/box/medigels(src) new /obj/item/storage/box/medigels(src) + new /obj/item/ph_booklet(src) + new /obj/item/reagent_containers/dropper(src) + new /obj/item/reagent_containers/glass/bottle/acidic_buffer(src) //hopefully they get the hint /obj/structure/closet/secure_closet/chemical/heisenberg //contains one of each beaker, syringe etc. name = "advanced chemical closet" diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm index 62bcf6e02888af..dd2bc8d46324d9 100644 --- a/code/modules/asset_cache/asset_list_items.dm +++ b/code/modules/asset_cache/asset_list_items.dm @@ -524,3 +524,8 @@ /proc/sanitize_css_class_name(name) var/static/regex/regex = new(@"[^a-zA-Z0-9]","g") return replacetext(name, regex, "") + +/datum/asset/simple/tutorial_advisors + assets = list( + "chem_help_advisor.gif" = 'icons/UI_Icons/Advisors/chem_help_advisor.gif', + ) diff --git a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm index 45978644c63874..d2561fbc86692d 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm @@ -131,7 +131,8 @@ GLOBAL_LIST_INIT(oilfry_blacklisted_items, typecacheof(list( reagents.expose(C, TOUCH) var/permeability = 1 - C.get_permeability_protection(list(HEAD)) C.apply_damage(min(30 * permeability, reagents.total_volume), BURN, BODY_ZONE_HEAD) - reagents.remove_any((reagents.total_volume/2)) + if(reagents.reagent_list) //This can runtime if reagents has nothing in it. + reagents.remove_any((reagents.total_volume/2)) C.Paralyze(60) user.changeNext_move(CLICK_CD_MELEE) return ..() diff --git a/code/modules/food_and_drinks/recipes/drinks_recipes.dm b/code/modules/food_and_drinks/recipes/drinks_recipes.dm index c7fc75b956e2fc..da378521c4a247 100644 --- a/code/modules/food_and_drinks/recipes/drinks_recipes.dm +++ b/code/modules/food_and_drinks/recipes/drinks_recipes.dm @@ -1,594 +1,610 @@ +//////////////////////////////////////// DRINK RECIPE BASE //////////////////////////////// + +/datum/chemical_reaction/drink + optimal_temp = 400 + temp_exponent_factor = 1 + optimal_ph_min = 2 + optimal_ph_max = 10 + thermic_constant = 0 + H_ion_release = 0 + rate_up_lim = 50 + ////////////////////////////////////////// COCKTAILS ////////////////////////////////////// -/datum/chemical_reaction/goldschlager +/datum/chemical_reaction/drink/goldschlager results = list(/datum/reagent/consumable/ethanol/goldschlager = 10) required_reagents = list(/datum/reagent/consumable/ethanol/vodka = 10, /datum/reagent/gold = 1) -/datum/chemical_reaction/patron +/datum/chemical_reaction/drink/patron results = list(/datum/reagent/consumable/ethanol/patron = 10) required_reagents = list(/datum/reagent/consumable/ethanol/tequila = 10, /datum/reagent/silver = 1) -/datum/chemical_reaction/bilk +/datum/chemical_reaction/drink/bilk results = list(/datum/reagent/consumable/ethanol/bilk = 2) required_reagents = list(/datum/reagent/consumable/milk = 1, /datum/reagent/consumable/ethanol/beer = 1) -/datum/chemical_reaction/icetea +/datum/chemical_reaction/drink/icetea results = list(/datum/reagent/consumable/icetea = 4) required_reagents = list(/datum/reagent/consumable/ice = 1, /datum/reagent/consumable/tea = 3) -/datum/chemical_reaction/icecoffee +/datum/chemical_reaction/drink/icecoffee results = list(/datum/reagent/consumable/icecoffee = 4) required_reagents = list(/datum/reagent/consumable/ice = 1, /datum/reagent/consumable/coffee = 3) -/datum/chemical_reaction/hoticecoffee +/datum/chemical_reaction/drink/hoticecoffee results = list(/datum/reagent/consumable/hot_ice_coffee = 3) required_reagents = list(/datum/reagent/toxin/hot_ice = 1, /datum/reagent/consumable/coffee = 2) -/datum/chemical_reaction/nuka_cola +/datum/chemical_reaction/drink/nuka_cola results = list(/datum/reagent/consumable/nuka_cola = 6) required_reagents = list(/datum/reagent/uranium = 1, /datum/reagent/consumable/space_cola = 6) -/datum/chemical_reaction/moonshine +/datum/chemical_reaction/drink/moonshine results = list(/datum/reagent/consumable/ethanol/moonshine = 10) required_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/sugar = 5) required_catalysts = list(/datum/reagent/consumable/enzyme = 5) -/datum/chemical_reaction/wine +/datum/chemical_reaction/drink/wine results = list(/datum/reagent/consumable/ethanol/wine = 10) required_reagents = list(/datum/reagent/consumable/grapejuice = 10) required_catalysts = list(/datum/reagent/consumable/enzyme = 5) -/datum/chemical_reaction/spacebeer +/datum/chemical_reaction/drink/spacebeer results = list(/datum/reagent/consumable/ethanol/beer = 10) required_reagents = list(/datum/reagent/consumable/flour = 10) required_catalysts = list(/datum/reagent/consumable/enzyme = 5) -/datum/chemical_reaction/vodka +/datum/chemical_reaction/drink/vodka results = list(/datum/reagent/consumable/ethanol/vodka = 10) required_reagents = list(/datum/reagent/consumable/potato_juice = 10) required_catalysts = list(/datum/reagent/consumable/enzyme = 5) -/datum/chemical_reaction/kahlua +/datum/chemical_reaction/drink/kahlua results = list(/datum/reagent/consumable/ethanol/kahlua = 5) required_reagents = list(/datum/reagent/consumable/coffee = 5, /datum/reagent/consumable/sugar = 5) required_catalysts = list(/datum/reagent/consumable/enzyme = 5) -/datum/chemical_reaction/gin_tonic +/datum/chemical_reaction/drink/gin_tonic results = list(/datum/reagent/consumable/ethanol/gintonic = 3) required_reagents = list(/datum/reagent/consumable/ethanol/gin = 2, /datum/reagent/consumable/tonic = 1) -/datum/chemical_reaction/rum_coke +/datum/chemical_reaction/drink/rum_coke results = list(/datum/reagent/consumable/ethanol/rum_coke = 3) required_reagents = list(/datum/reagent/consumable/ethanol/rum = 2, /datum/reagent/consumable/space_cola = 1) -/datum/chemical_reaction/cuba_libre +/datum/chemical_reaction/drink/cuba_libre results = list(/datum/reagent/consumable/ethanol/cuba_libre = 4) required_reagents = list(/datum/reagent/consumable/ethanol/rum_coke = 3, /datum/reagent/consumable/limejuice = 1) -/datum/chemical_reaction/martini +/datum/chemical_reaction/drink/martini results = list(/datum/reagent/consumable/ethanol/martini = 3) required_reagents = list(/datum/reagent/consumable/ethanol/gin = 2, /datum/reagent/consumable/ethanol/vermouth = 1) -/datum/chemical_reaction/vodkamartini +/datum/chemical_reaction/drink/vodkamartini results = list(/datum/reagent/consumable/ethanol/vodkamartini = 3) required_reagents = list(/datum/reagent/consumable/ethanol/vodka = 2, /datum/reagent/consumable/ethanol/vermouth = 1) -/datum/chemical_reaction/white_russian +/datum/chemical_reaction/drink/white_russian results = list(/datum/reagent/consumable/ethanol/white_russian = 5) required_reagents = list(/datum/reagent/consumable/ethanol/black_russian = 3, /datum/reagent/consumable/cream = 2) -/datum/chemical_reaction/whiskey_cola +/datum/chemical_reaction/drink/whiskey_cola results = list(/datum/reagent/consumable/ethanol/whiskey_cola = 3) required_reagents = list(/datum/reagent/consumable/ethanol/whiskey = 2, /datum/reagent/consumable/space_cola = 1) -/datum/chemical_reaction/screwdriver +/datum/chemical_reaction/drink/screwdriver results = list(/datum/reagent/consumable/ethanol/screwdrivercocktail = 3) required_reagents = list(/datum/reagent/consumable/ethanol/vodka = 2, /datum/reagent/consumable/orangejuice = 1) -/datum/chemical_reaction/bloody_mary +/datum/chemical_reaction/drink/bloody_mary results = list(/datum/reagent/consumable/ethanol/bloody_mary = 4) required_reagents = list(/datum/reagent/consumable/ethanol/vodka = 1, /datum/reagent/consumable/tomatojuice = 2, /datum/reagent/consumable/limejuice = 1) -/datum/chemical_reaction/gargle_blaster +/datum/chemical_reaction/drink/gargle_blaster results = list(/datum/reagent/consumable/ethanol/gargle_blaster = 5) required_reagents = list(/datum/reagent/consumable/ethanol/vodka = 1, /datum/reagent/consumable/ethanol/gin = 1, /datum/reagent/consumable/ethanol/whiskey = 1, /datum/reagent/consumable/ethanol/cognac = 1, /datum/reagent/consumable/limejuice = 1) -/datum/chemical_reaction/brave_bull +/datum/chemical_reaction/drink/brave_bull results = list(/datum/reagent/consumable/ethanol/brave_bull = 3) required_reagents = list(/datum/reagent/consumable/ethanol/tequila = 2, /datum/reagent/consumable/ethanol/kahlua = 1) -/datum/chemical_reaction/tequila_sunrise +/datum/chemical_reaction/drink/tequila_sunrise results = list(/datum/reagent/consumable/ethanol/tequila_sunrise = 5) required_reagents = list(/datum/reagent/consumable/ethanol/tequila = 2, /datum/reagent/consumable/orangejuice = 2, /datum/reagent/consumable/grenadine = 1) -/datum/chemical_reaction/toxins_special +/datum/chemical_reaction/drink/toxins_special results = list(/datum/reagent/consumable/ethanol/toxins_special = 5) required_reagents = list(/datum/reagent/consumable/ethanol/rum = 2, /datum/reagent/consumable/ethanol/vermouth = 1, /datum/reagent/toxin/plasma = 2) -/datum/chemical_reaction/beepsky_smash +/datum/chemical_reaction/drink/beepsky_smash results = list(/datum/reagent/consumable/ethanol/beepsky_smash = 5) required_reagents = list(/datum/reagent/consumable/limejuice = 2, /datum/reagent/consumable/ethanol/quadruple_sec = 2, /datum/reagent/iron = 1) -/datum/chemical_reaction/doctor_delight +/datum/chemical_reaction/drink/doctor_delight results = list(/datum/reagent/consumable/doctor_delight = 5) required_reagents = list(/datum/reagent/consumable/limejuice = 1, /datum/reagent/consumable/tomatojuice = 1, /datum/reagent/consumable/orangejuice = 1, /datum/reagent/consumable/cream = 1, /datum/reagent/medicine/cryoxadone = 1) -/datum/chemical_reaction/irish_cream +/datum/chemical_reaction/drink/irish_cream results = list(/datum/reagent/consumable/ethanol/irish_cream = 3) required_reagents = list(/datum/reagent/consumable/ethanol/whiskey = 2, /datum/reagent/consumable/cream = 1) -/datum/chemical_reaction/manly_dorf +/datum/chemical_reaction/drink/manly_dorf results = list(/datum/reagent/consumable/ethanol/manly_dorf = 3) required_reagents = list (/datum/reagent/consumable/ethanol/beer = 1, /datum/reagent/consumable/ethanol/ale = 2) -/datum/chemical_reaction/greenbeer +/datum/chemical_reaction/drink/greenbeer results = list(/datum/reagent/consumable/ethanol/beer/green = 10) required_reagents = list(/datum/reagent/colorful_reagent/powder/green = 1, /datum/reagent/consumable/ethanol/beer = 10) -/datum/chemical_reaction/greenbeer2 //apparently there's no other way to do this +/datum/chemical_reaction/drink/greenbeer2 //apparently there's no other way to do this results = list(/datum/reagent/consumable/ethanol/beer/green = 10) required_reagents = list(/datum/reagent/colorful_reagent/powder/green/crayon = 1, /datum/reagent/consumable/ethanol/beer = 10) -/datum/chemical_reaction/hooch +/datum/chemical_reaction/drink/hooch results = list(/datum/reagent/consumable/ethanol/hooch = 3) required_reagents = list (/datum/reagent/consumable/ethanol = 2, /datum/reagent/fuel = 1) required_catalysts = list(/datum/reagent/consumable/enzyme = 1) -/datum/chemical_reaction/irish_coffee +/datum/chemical_reaction/drink/irish_coffee results = list(/datum/reagent/consumable/ethanol/irishcoffee = 2) required_reagents = list(/datum/reagent/consumable/ethanol/irish_cream = 1, /datum/reagent/consumable/coffee = 1) -/datum/chemical_reaction/b52 +/datum/chemical_reaction/drink/b52 results = list(/datum/reagent/consumable/ethanol/b52 = 3) required_reagents = list(/datum/reagent/consumable/ethanol/irish_cream = 1, /datum/reagent/consumable/ethanol/kahlua = 1, /datum/reagent/consumable/ethanol/cognac = 1) -/datum/chemical_reaction/atomicbomb +/datum/chemical_reaction/drink/atomicbomb results = list(/datum/reagent/consumable/ethanol/atomicbomb = 10) required_reagents = list(/datum/reagent/consumable/ethanol/b52 = 10, /datum/reagent/uranium = 1) -/datum/chemical_reaction/margarita +/datum/chemical_reaction/drink/margarita results = list(/datum/reagent/consumable/ethanol/margarita = 4) required_reagents = list(/datum/reagent/consumable/ethanol/tequila = 2, /datum/reagent/consumable/limejuice = 1, /datum/reagent/consumable/ethanol/triple_sec = 1) -/datum/chemical_reaction/longislandicedtea +/datum/chemical_reaction/drink/longislandicedtea results = list(/datum/reagent/consumable/ethanol/longislandicedtea = 4) required_reagents = list(/datum/reagent/consumable/ethanol/vodka = 1, /datum/reagent/consumable/ethanol/gin = 1, /datum/reagent/consumable/ethanol/tequila = 1, /datum/reagent/consumable/ethanol/cuba_libre = 1) -/datum/chemical_reaction/threemileisland +/datum/chemical_reaction/drink/threemileisland results = list(/datum/reagent/consumable/ethanol/threemileisland = 10) required_reagents = list(/datum/reagent/consumable/ethanol/longislandicedtea = 10, /datum/reagent/uranium = 1) -/datum/chemical_reaction/whiskeysoda +/datum/chemical_reaction/drink/whiskeysoda results = list(/datum/reagent/consumable/ethanol/whiskeysoda = 3) required_reagents = list(/datum/reagent/consumable/ethanol/whiskey = 2, /datum/reagent/consumable/sodawater = 1) -/datum/chemical_reaction/black_russian +/datum/chemical_reaction/drink/black_russian results = list(/datum/reagent/consumable/ethanol/black_russian = 5) required_reagents = list(/datum/reagent/consumable/ethanol/vodka = 3, /datum/reagent/consumable/ethanol/kahlua = 2) -/datum/chemical_reaction/hiveminderaser +/datum/chemical_reaction/drink/hiveminderaser results = list(/datum/reagent/consumable/ethanol/hiveminderaser = 4) required_reagents = list(/datum/reagent/consumable/ethanol/black_russian = 2, /datum/reagent/consumable/ethanol/thirteenloko = 1, /datum/reagent/consumable/grenadine = 1) -/datum/chemical_reaction/manhattan +/datum/chemical_reaction/drink/manhattan results = list(/datum/reagent/consumable/ethanol/manhattan = 3) required_reagents = list(/datum/reagent/consumable/ethanol/whiskey = 2, /datum/reagent/consumable/ethanol/vermouth = 1) -/datum/chemical_reaction/manhattan_proj +/datum/chemical_reaction/drink/manhattan_proj results = list(/datum/reagent/consumable/ethanol/manhattan_proj = 10) required_reagents = list(/datum/reagent/consumable/ethanol/manhattan = 10, /datum/reagent/uranium = 1) -/datum/chemical_reaction/vodka_tonic +/datum/chemical_reaction/drink/vodka_tonic results = list(/datum/reagent/consumable/ethanol/vodkatonic = 3) required_reagents = list(/datum/reagent/consumable/ethanol/vodka = 2, /datum/reagent/consumable/tonic = 1) -/datum/chemical_reaction/gin_fizz +/datum/chemical_reaction/drink/gin_fizz results = list(/datum/reagent/consumable/ethanol/ginfizz = 4) required_reagents = list(/datum/reagent/consumable/ethanol/gin = 2, /datum/reagent/consumable/sodawater = 1, /datum/reagent/consumable/limejuice = 1) -/datum/chemical_reaction/bahama_mama +/datum/chemical_reaction/drink/bahama_mama results = list(/datum/reagent/consumable/ethanol/bahama_mama = 5) required_reagents = list(/datum/reagent/consumable/ethanol/creme_de_coconut = 1, /datum/reagent/consumable/ethanol/kahlua = 1, /datum/reagent/consumable/ethanol/rum = 2, /datum/reagent/consumable/pineapplejuice = 1) -/datum/chemical_reaction/singulo +/datum/chemical_reaction/drink/singulo results = list(/datum/reagent/consumable/ethanol/singulo = 10) required_reagents = list(/datum/reagent/consumable/ethanol/vodka = 5, /datum/reagent/uranium/radium = 1, /datum/reagent/consumable/ethanol/wine = 5) -/datum/chemical_reaction/alliescocktail +/datum/chemical_reaction/drink/alliescocktail results = list(/datum/reagent/consumable/ethanol/alliescocktail = 2) required_reagents = list(/datum/reagent/consumable/ethanol/martini = 1, /datum/reagent/consumable/ethanol/vodka = 1) -/datum/chemical_reaction/demonsblood +/datum/chemical_reaction/drink/demonsblood results = list(/datum/reagent/consumable/ethanol/demonsblood = 4) required_reagents = list(/datum/reagent/consumable/ethanol/rum = 1, /datum/reagent/consumable/spacemountainwind = 1, /datum/reagent/blood = 1, /datum/reagent/consumable/dr_gibb = 1) -/datum/chemical_reaction/booger +/datum/chemical_reaction/drink/booger results = list(/datum/reagent/consumable/ethanol/booger = 4) required_reagents = list(/datum/reagent/consumable/cream = 1, /datum/reagent/consumable/banana = 1, /datum/reagent/consumable/ethanol/rum = 1, /datum/reagent/consumable/watermelonjuice = 1) -/datum/chemical_reaction/antifreeze +/datum/chemical_reaction/drink/antifreeze results = list(/datum/reagent/consumable/ethanol/antifreeze = 4) required_reagents = list(/datum/reagent/consumable/ethanol/vodka = 2, /datum/reagent/consumable/cream = 1, /datum/reagent/consumable/ice = 1) -/datum/chemical_reaction/barefoot +/datum/chemical_reaction/drink/barefoot results = list(/datum/reagent/consumable/ethanol/barefoot = 3) required_reagents = list(/datum/reagent/consumable/berryjuice = 1, /datum/reagent/consumable/cream = 1, /datum/reagent/consumable/ethanol/vermouth = 1) -/datum/chemical_reaction/moscow_mule +/datum/chemical_reaction/drink/moscow_mule results = list(/datum/reagent/consumable/ethanol/moscow_mule = 10) required_reagents = list(/datum/reagent/consumable/sol_dry = 5, /datum/reagent/consumable/ethanol/vodka = 5, /datum/reagent/consumable/limejuice = 1, /datum/reagent/consumable/ice = 1) mix_sound = 'sound/effects/bubbles2.ogg' -/datum/chemical_reaction/painkiller +/datum/chemical_reaction/drink/painkiller results = list(/datum/reagent/consumable/ethanol/painkiller = 10) required_reagents = list(/datum/reagent/consumable/ethanol/creme_de_coconut = 5, /datum/reagent/consumable/pineapplejuice = 4, /datum/reagent/consumable/orangejuice = 1) -/datum/chemical_reaction/pina_colada +/datum/chemical_reaction/drink/pina_colada results = list(/datum/reagent/consumable/ethanol/pina_colada = 5) required_reagents = list(/datum/reagent/consumable/ethanol/creme_de_coconut = 1, /datum/reagent/consumable/pineapplejuice = 3, /datum/reagent/consumable/ethanol/rum = 1, /datum/reagent/consumable/limejuice = 1) ////DRINKS THAT REQUIRED IMPROVED SPRITES BELOW:: -Agouri///// -/datum/chemical_reaction/sbiten +/datum/chemical_reaction/drink/sbiten results = list(/datum/reagent/consumable/ethanol/sbiten = 10) required_reagents = list(/datum/reagent/consumable/ethanol/vodka = 10, /datum/reagent/consumable/capsaicin = 1) -/datum/chemical_reaction/red_mead +/datum/chemical_reaction/drink/red_mead results = list(/datum/reagent/consumable/ethanol/red_mead = 2) required_reagents = list(/datum/reagent/blood = 1, /datum/reagent/consumable/ethanol/mead = 1) -/datum/chemical_reaction/mead +/datum/chemical_reaction/drink/mead results = list(/datum/reagent/consumable/ethanol/mead = 2) required_reagents = list(/datum/reagent/consumable/honey = 2) required_catalysts = list(/datum/reagent/consumable/enzyme = 5) -/datum/chemical_reaction/iced_beer +/datum/chemical_reaction/drink/iced_beer results = list(/datum/reagent/consumable/ethanol/iced_beer = 6) required_reagents = list(/datum/reagent/consumable/ethanol/beer = 5, /datum/reagent/consumable/ice = 1) -/datum/chemical_reaction/grog +/datum/chemical_reaction/drink/grog results = list(/datum/reagent/consumable/ethanol/grog = 2) required_reagents = list(/datum/reagent/consumable/ethanol/rum = 1, /datum/reagent/water = 1) -/datum/chemical_reaction/soy_latte +/datum/chemical_reaction/drink/soy_latte results = list(/datum/reagent/consumable/soy_latte = 2) required_reagents = list(/datum/reagent/consumable/coffee = 1, /datum/reagent/consumable/soymilk = 1) -/datum/chemical_reaction/cafe_latte +/datum/chemical_reaction/drink/cafe_latte results = list(/datum/reagent/consumable/cafe_latte = 2) required_reagents = list(/datum/reagent/consumable/coffee = 1, /datum/reagent/consumable/milk = 1) -/datum/chemical_reaction/acidspit +/datum/chemical_reaction/drink/acidspit results = list(/datum/reagent/consumable/ethanol/acid_spit = 6) required_reagents = list(/datum/reagent/toxin/acid = 1, /datum/reagent/consumable/ethanol/wine = 5) + optimal_ph_min = 0 //Our reaction is very acidic, so lets shift our range -/datum/chemical_reaction/amasec +/datum/chemical_reaction/drink/amasec results = list(/datum/reagent/consumable/ethanol/amasec = 10) required_reagents = list(/datum/reagent/iron = 1, /datum/reagent/consumable/ethanol/wine = 5, /datum/reagent/consumable/ethanol/vodka = 5) -/datum/chemical_reaction/changelingsting +/datum/chemical_reaction/drink/changelingsting results = list(/datum/reagent/consumable/ethanol/changelingsting = 5) required_reagents = list(/datum/reagent/consumable/ethanol/screwdrivercocktail = 1, /datum/reagent/consumable/lemon_lime = 2) -/datum/chemical_reaction/aloe +/datum/chemical_reaction/drink/aloe results = list(/datum/reagent/consumable/ethanol/aloe = 2) required_reagents = list(/datum/reagent/consumable/ethanol/irish_cream = 1, /datum/reagent/consumable/watermelonjuice = 1) -/datum/chemical_reaction/andalusia +/datum/chemical_reaction/drink/andalusia results = list(/datum/reagent/consumable/ethanol/andalusia = 3) required_reagents = list(/datum/reagent/consumable/ethanol/rum = 1, /datum/reagent/consumable/ethanol/whiskey = 1, /datum/reagent/consumable/lemonjuice = 1) -/datum/chemical_reaction/neurotoxin +/datum/chemical_reaction/drink/neurotoxin results = list(/datum/reagent/consumable/ethanol/neurotoxin = 2) required_reagents = list(/datum/reagent/consumable/ethanol/gargle_blaster = 1, /datum/reagent/medicine/morphine = 1) -/datum/chemical_reaction/snowwhite +/datum/chemical_reaction/drink/snowwhite results = list(/datum/reagent/consumable/ethanol/snowwhite = 2) required_reagents = list(/datum/reagent/consumable/ethanol/beer = 1, /datum/reagent/consumable/lemon_lime = 1) -/datum/chemical_reaction/irishcarbomb +/datum/chemical_reaction/drink/irishcarbomb results = list(/datum/reagent/consumable/ethanol/irishcarbomb = 2) required_reagents = list(/datum/reagent/consumable/ethanol/ale = 1, /datum/reagent/consumable/ethanol/irish_cream = 1) -/datum/chemical_reaction/syndicatebomb +/datum/chemical_reaction/drink/syndicatebomb results = list(/datum/reagent/consumable/ethanol/syndicatebomb = 2) required_reagents = list(/datum/reagent/consumable/ethanol/beer = 1, /datum/reagent/consumable/ethanol/whiskey_cola = 1) -/datum/chemical_reaction/erikasurprise +/datum/chemical_reaction/drink/erikasurprise results = list(/datum/reagent/consumable/ethanol/erikasurprise = 5) required_reagents = list(/datum/reagent/consumable/ethanol/ale = 1, /datum/reagent/consumable/limejuice = 1, /datum/reagent/consumable/ethanol/whiskey = 1, /datum/reagent/consumable/banana = 1, /datum/reagent/consumable/ice = 1) -/datum/chemical_reaction/devilskiss +/datum/chemical_reaction/drink/devilskiss results = list(/datum/reagent/consumable/ethanol/devilskiss = 3) required_reagents = list(/datum/reagent/blood = 1, /datum/reagent/consumable/ethanol/kahlua = 1, /datum/reagent/consumable/ethanol/rum = 1) -/datum/chemical_reaction/hippiesdelight +/datum/chemical_reaction/drink/hippiesdelight results = list(/datum/reagent/consumable/ethanol/hippies_delight = 2) required_reagents = list(/datum/reagent/drug/mushroomhallucinogen = 1, /datum/reagent/consumable/ethanol/gargle_blaster = 1) -/datum/chemical_reaction/bananahonk +/datum/chemical_reaction/drink/bananahonk results = list(/datum/reagent/consumable/ethanol/bananahonk = 2) required_reagents = list(/datum/reagent/consumable/laughter = 1, /datum/reagent/consumable/cream = 1) -/datum/chemical_reaction/silencer +/datum/chemical_reaction/drink/silencer results = list(/datum/reagent/consumable/ethanol/silencer = 3) required_reagents = list(/datum/reagent/consumable/nothing = 1, /datum/reagent/consumable/cream = 1, /datum/reagent/consumable/sugar = 1) -/datum/chemical_reaction/driestmartini +/datum/chemical_reaction/drink/driestmartini results = list(/datum/reagent/consumable/ethanol/driestmartini = 2) required_reagents = list(/datum/reagent/consumable/nothing = 1, /datum/reagent/consumable/ethanol/gin = 1) -/datum/chemical_reaction/thirteenloko +/datum/chemical_reaction/drink/thirteenloko results = list(/datum/reagent/consumable/ethanol/thirteenloko = 3) required_reagents = list(/datum/reagent/consumable/ethanol/vodka = 1, /datum/reagent/consumable/coffee = 1, /datum/reagent/consumable/limejuice = 1) -/datum/chemical_reaction/cherryshake +/datum/chemical_reaction/drink/cherryshake results = list(/datum/reagent/consumable/cherryshake = 3) required_reagents = list(/datum/reagent/consumable/cherryjelly = 1, /datum/reagent/consumable/ice = 1, /datum/reagent/consumable/cream = 1) -/datum/chemical_reaction/bluecherryshake +/datum/chemical_reaction/drink/bluecherryshake results = list(/datum/reagent/consumable/bluecherryshake = 3) required_reagents = list(/datum/reagent/consumable/bluecherryjelly = 1, /datum/reagent/consumable/ice = 1, /datum/reagent/consumable/cream = 1) -/datum/chemical_reaction/drunkenblumpkin +/datum/chemical_reaction/drink/drunkenblumpkin results = list(/datum/reagent/consumable/ethanol/drunkenblumpkin = 4) required_reagents = list(/datum/reagent/consumable/blumpkinjuice = 1, /datum/reagent/consumable/ethanol/irish_cream = 2, /datum/reagent/consumable/ice = 1) -/datum/chemical_reaction/pumpkin_latte +/datum/chemical_reaction/drink/pumpkin_latte results = list(/datum/reagent/consumable/pumpkin_latte = 15) required_reagents = list(/datum/reagent/consumable/pumpkinjuice = 5, /datum/reagent/consumable/coffee = 5, /datum/reagent/consumable/cream = 5) -/datum/chemical_reaction/gibbfloats +/datum/chemical_reaction/drink/gibbfloats results = list(/datum/reagent/consumable/gibbfloats = 15) required_reagents = list(/datum/reagent/consumable/dr_gibb = 5, /datum/reagent/consumable/ice = 5, /datum/reagent/consumable/cream = 5) -/datum/chemical_reaction/triple_citrus +/datum/chemical_reaction/drink/triple_citrus results = list(/datum/reagent/consumable/triple_citrus = 5) required_reagents = list(/datum/reagent/consumable/lemonjuice = 1, /datum/reagent/consumable/limejuice = 1, /datum/reagent/consumable/orangejuice = 1) + optimal_ph_min = 0//Our reaction is very acidic, so lets shift our range -/datum/chemical_reaction/grape_soda +/datum/chemical_reaction/drink/grape_soda results = list(/datum/reagent/consumable/grape_soda = 2) required_reagents = list(/datum/reagent/consumable/grapejuice = 1, /datum/reagent/consumable/sodawater = 1) -/datum/chemical_reaction/grappa +/datum/chemical_reaction/drink/grappa results = list(/datum/reagent/consumable/ethanol/grappa = 10) required_reagents = list (/datum/reagent/consumable/ethanol/wine = 10) required_catalysts = list (/datum/reagent/consumable/enzyme = 10) -/datum/chemical_reaction/whiskey_sour +/datum/chemical_reaction/drink/whiskey_sour results = list(/datum/reagent/consumable/ethanol/whiskey_sour = 3) required_reagents = list(/datum/reagent/consumable/ethanol/whiskey = 1, /datum/reagent/consumable/lemonjuice = 1, /datum/reagent/consumable/sugar = 1) mix_message = "The mixture darkens to a rich gold hue." -/datum/chemical_reaction/fetching_fizz +/datum/chemical_reaction/drink/fetching_fizz results = list(/datum/reagent/consumable/ethanol/fetching_fizz = 3) required_reagents = list(/datum/reagent/consumable/nuka_cola = 1, /datum/reagent/iron = 1) //Manufacturable from only the mining station mix_message = "The mixture slightly vibrates before settling." -/datum/chemical_reaction/hearty_punch +/datum/chemical_reaction/drink/hearty_punch results = list(/datum/reagent/consumable/ethanol/hearty_punch = 1) //Very little, for balance reasons required_reagents = list(/datum/reagent/consumable/ethanol/brave_bull = 5, /datum/reagent/consumable/ethanol/syndicatebomb = 5, /datum/reagent/consumable/ethanol/absinthe = 5) mix_message = "The mixture darkens to a healthy crimson." required_temp = 315 //Piping hot! -/datum/chemical_reaction/bacchus_blessing +/datum/chemical_reaction/drink/bacchus_blessing results = list(/datum/reagent/consumable/ethanol/bacchus_blessing = 4) required_reagents = list(/datum/reagent/consumable/ethanol/hooch = 1, /datum/reagent/consumable/ethanol/absinthe = 1, /datum/reagent/consumable/ethanol/manly_dorf = 1, /datum/reagent/consumable/ethanol/syndicatebomb = 1) mix_message = "The mixture turns to a sickening froth." -/datum/chemical_reaction/lemonade +/datum/chemical_reaction/drink/lemonade results = list(/datum/reagent/consumable/lemonade = 5) required_reagents = list(/datum/reagent/consumable/lemonjuice = 2, /datum/reagent/water = 2, /datum/reagent/consumable/sugar = 1, /datum/reagent/consumable/ice = 1) mix_message = "You're suddenly reminded of home." -/datum/chemical_reaction/arnold_palmer +/datum/chemical_reaction/drink/arnold_palmer results = list(/datum/reagent/consumable/tea/arnold_palmer = 2) required_reagents = list(/datum/reagent/consumable/tea = 1, /datum/reagent/consumable/lemonade = 1) mix_message = "The smells of fresh green grass and sand traps waft through the air as the mixture turns a friendly yellow-orange." -/datum/chemical_reaction/chocolate_milk +/datum/chemical_reaction/drink/chocolate_milk results = list(/datum/reagent/consumable/milk/chocolate_milk = 5) required_reagents = list(/datum/reagent/consumable/hot_coco = 3, /datum/reagent/consumable/coco = 2) mix_message = "The color changes as the mixture blends smoothly." required_temp = 300 is_cold_recipe = TRUE + optimal_temp = 280 + overheat_temp = 5 + thermic_constant= -1 -/datum/chemical_reaction/hot_coco +/datum/chemical_reaction/drink/hot_coco results = list(/datum/reagent/consumable/hot_coco = 6) required_reagents = list(/datum/reagent/consumable/milk = 5, /datum/reagent/consumable/coco = 1) required_temp = 320 -/datum/chemical_reaction/hot_coco_from_chocolate_milk +/datum/chemical_reaction/drink/hot_coco_from_chocolate_milk results = list(/datum/reagent/consumable/hot_coco = 3) required_reagents = list(/datum/reagent/consumable/milk/chocolate_milk = 1, /datum/reagent/consumable/milk = 2) required_temp = 320 -/datum/chemical_reaction/coffee +/datum/chemical_reaction/drink/coffee results = list(/datum/reagent/consumable/coffee = 5) required_reagents = list(/datum/reagent/toxin/coffeepowder = 1, /datum/reagent/water = 5) -/datum/chemical_reaction/tea +/datum/chemical_reaction/drink/tea results = list(/datum/reagent/consumable/tea = 5) required_reagents = list(/datum/reagent/toxin/teapowder = 1, /datum/reagent/water = 5) -/datum/chemical_reaction/eggnog +/datum/chemical_reaction/drink/eggnog results = list(/datum/reagent/consumable/ethanol/eggnog = 15) required_reagents = list(/datum/reagent/consumable/ethanol/rum = 5, /datum/reagent/consumable/cream = 5, /datum/reagent/consumable/eggyolk = 5) -/datum/chemical_reaction/narsour +/datum/chemical_reaction/drink/narsour results = list(/datum/reagent/consumable/ethanol/narsour = 1) required_reagents = list(/datum/reagent/blood = 1, /datum/reagent/consumable/lemonjuice = 1, /datum/reagent/consumable/ethanol/demonsblood = 1) mix_message = "The mixture develops a sinister glow." mix_sound = 'sound/effects/singlebeat.ogg' -/datum/chemical_reaction/quadruplesec +/datum/chemical_reaction/drink/quadruplesec results = list(/datum/reagent/consumable/ethanol/quadruple_sec = 15) required_reagents = list(/datum/reagent/consumable/ethanol/triple_sec = 5, /datum/reagent/consumable/triple_citrus = 5, /datum/reagent/consumable/ethanol/creme_de_menthe = 5) mix_message = "The snap of a taser emanates clearly from the mixture as it settles." mix_sound = 'sound/weapons/taser.ogg' -/datum/chemical_reaction/grasshopper +/datum/chemical_reaction/drink/grasshopper results = list(/datum/reagent/consumable/ethanol/grasshopper = 15) required_reagents = list(/datum/reagent/consumable/cream = 5, /datum/reagent/consumable/ethanol/creme_de_menthe = 5, /datum/reagent/consumable/ethanol/creme_de_cacao = 5) mix_message = "A vibrant green bubbles forth as the mixture emulsifies." -/datum/chemical_reaction/stinger +/datum/chemical_reaction/drink/stinger results = list(/datum/reagent/consumable/ethanol/stinger = 15) required_reagents = list(/datum/reagent/consumable/ethanol/cognac = 10, /datum/reagent/consumable/ethanol/creme_de_menthe = 5 ) -/datum/chemical_reaction/quintuplesec +/datum/chemical_reaction/drink/quintuplesec results = list(/datum/reagent/consumable/ethanol/quintuple_sec = 15) required_reagents = list(/datum/reagent/consumable/ethanol/quadruple_sec = 5, /datum/reagent/consumable/clownstears = 5, /datum/reagent/consumable/ethanol/syndicatebomb = 5) mix_message = "Judgement is upon you." mix_sound = 'sound/items/airhorn2.ogg' -/datum/chemical_reaction/bastion_bourbon +/datum/chemical_reaction/drink/bastion_bourbon results = list(/datum/reagent/consumable/ethanol/bastion_bourbon = 2) required_reagents = list(/datum/reagent/consumable/tea = 1, /datum/reagent/consumable/ethanol/creme_de_menthe = 1, /datum/reagent/consumable/triple_citrus = 1, /datum/reagent/consumable/berryjuice = 1) //herbal and minty, with a hint of citrus and berry mix_message = "You catch an aroma of hot tea and fruits as the mix blends into a blue-green color." -/datum/chemical_reaction/squirt_cider +/datum/chemical_reaction/drink/squirt_cider results = list(/datum/reagent/consumable/ethanol/squirt_cider = 1) required_reagents = list(/datum/reagent/water = 1, /datum/reagent/consumable/tomatojuice = 1, /datum/reagent/consumable/nutriment = 1) mix_message = "The mix swirls and turns a bright red that reminds you of an apple's skin." -/datum/chemical_reaction/fringe_weaver +/datum/chemical_reaction/drink/fringe_weaver results = list(/datum/reagent/consumable/ethanol/fringe_weaver = 10) required_reagents = list(/datum/reagent/consumable/ethanol = 9, /datum/reagent/consumable/sugar = 1) //9 karmotrine, 1 adelhyde mix_message = "The mix turns a pleasant cream color and foams up." -/datum/chemical_reaction/sugar_rush +/datum/chemical_reaction/drink/sugar_rush results = list(/datum/reagent/consumable/ethanol/sugar_rush = 4) required_reagents = list(/datum/reagent/consumable/sugar = 2, /datum/reagent/consumable/lemonjuice = 1, /datum/reagent/consumable/ethanol/wine = 1) //2 adelhyde (sweet), 1 powdered delta (sour), 1 karmotrine (alcohol) mix_message = "The mixture bubbles and brightens into a girly pink." -/datum/chemical_reaction/crevice_spike +/datum/chemical_reaction/drink/crevice_spike results = list(/datum/reagent/consumable/ethanol/crevice_spike = 6) required_reagents = list(/datum/reagent/consumable/limejuice = 2, /datum/reagent/consumable/capsaicin = 4) //2 powdered delta (sour), 4 flanergide (spicy) mix_message = "The mixture stings your eyes as it settles." -/datum/chemical_reaction/sake +/datum/chemical_reaction/drink/sake results = list(/datum/reagent/consumable/ethanol/sake = 10) required_reagents = list(/datum/reagent/consumable/rice = 10) required_catalysts = list(/datum/reagent/consumable/enzyme = 5) mix_message = "The rice grains ferment into a clear, sweet-smelling liquid." -/datum/chemical_reaction/peppermint_patty +/datum/chemical_reaction/drink/peppermint_patty results = list(/datum/reagent/consumable/ethanol/peppermint_patty = 10) required_reagents = list(/datum/reagent/consumable/hot_coco = 6, /datum/reagent/consumable/ethanol/creme_de_cacao = 1, /datum/reagent/consumable/ethanol/creme_de_menthe = 1, /datum/reagent/consumable/ethanol/vodka = 1, /datum/reagent/consumable/menthol = 1) mix_message = "The coco turns mint green just as the strong scent hits your nose." -/datum/chemical_reaction/alexander +/datum/chemical_reaction/drink/alexander results = list(/datum/reagent/consumable/ethanol/alexander = 3) required_reagents = list(/datum/reagent/consumable/ethanol/cognac = 1, /datum/reagent/consumable/ethanol/creme_de_cacao = 1, /datum/reagent/consumable/cream = 1) -/datum/chemical_reaction/sidecar +/datum/chemical_reaction/drink/sidecar results = list(/datum/reagent/consumable/ethanol/sidecar = 4) required_reagents = list(/datum/reagent/consumable/ethanol/cognac = 2, /datum/reagent/consumable/ethanol/triple_sec = 1, /datum/reagent/consumable/lemonjuice = 1) -/datum/chemical_reaction/between_the_sheets +/datum/chemical_reaction/drink/between_the_sheets results = list(/datum/reagent/consumable/ethanol/between_the_sheets = 5) required_reagents = list(/datum/reagent/consumable/ethanol/rum = 1, /datum/reagent/consumable/ethanol/sidecar = 4) -/datum/chemical_reaction/kamikaze +/datum/chemical_reaction/drink/kamikaze results = list(/datum/reagent/consumable/ethanol/kamikaze = 3) required_reagents = list(/datum/reagent/consumable/ethanol/vodka = 1, /datum/reagent/consumable/ethanol/triple_sec = 1, /datum/reagent/consumable/limejuice = 1) -/datum/chemical_reaction/mojito +/datum/chemical_reaction/drink/mojito results = list(/datum/reagent/consumable/ethanol/mojito = 5) required_reagents = list(/datum/reagent/consumable/ethanol/rum = 1, /datum/reagent/consumable/sugar = 1, /datum/reagent/consumable/limejuice = 1, /datum/reagent/consumable/sodawater = 1, /datum/reagent/consumable/menthol = 1) -/datum/chemical_reaction/fernet_cola +/datum/chemical_reaction/drink/fernet_cola results = list(/datum/reagent/consumable/ethanol/fernet_cola = 2) required_reagents = list(/datum/reagent/consumable/ethanol/fernet = 1, /datum/reagent/consumable/space_cola = 1) -/datum/chemical_reaction/fanciulli +/datum/chemical_reaction/drink/fanciulli results = list(/datum/reagent/consumable/ethanol/fanciulli = 2) required_reagents = list(/datum/reagent/consumable/ethanol/manhattan = 1, /datum/reagent/consumable/ethanol/fernet = 1) -/datum/chemical_reaction/branca_menta +/datum/chemical_reaction/drink/branca_menta results = list(/datum/reagent/consumable/ethanol/branca_menta = 3) required_reagents = list(/datum/reagent/consumable/ethanol/fernet = 1, /datum/reagent/consumable/ethanol/creme_de_menthe = 1, /datum/reagent/consumable/ice = 1) -/datum/chemical_reaction/blank_paper +/datum/chemical_reaction/drink/blank_paper results = list(/datum/reagent/consumable/ethanol/blank_paper = 3) required_reagents = list(/datum/reagent/consumable/ethanol/silencer = 1, /datum/reagent/consumable/nothing = 1, /datum/reagent/consumable/nuka_cola = 1) -/datum/chemical_reaction/wizz_fizz +/datum/chemical_reaction/drink/wizz_fizz results = list(/datum/reagent/consumable/ethanol/wizz_fizz = 3) required_reagents = list(/datum/reagent/consumable/ethanol/triple_sec = 1, /datum/reagent/consumable/sodawater = 1, /datum/reagent/consumable/ethanol/champagne = 1) mix_message = "The beverage starts to froth with an almost mystical zeal!" mix_sound = 'sound/effects/bubbles2.ogg' -/datum/chemical_reaction/bug_spray +/datum/chemical_reaction/drink/bug_spray results = list(/datum/reagent/consumable/ethanol/bug_spray = 5) required_reagents = list(/datum/reagent/consumable/ethanol/triple_sec = 2, /datum/reagent/consumable/lemon_lime = 1, /datum/reagent/consumable/ethanol/rum = 2, /datum/reagent/consumable/ethanol/vodka = 1) mix_message = "The faint aroma of summer camping trips wafts through the air; but what's that buzzing noise?" mix_sound = 'sound/creatures/bee.ogg' -/datum/chemical_reaction/jack_rose +/datum/chemical_reaction/drink/jack_rose results = list(/datum/reagent/consumable/ethanol/jack_rose = 4) required_reagents = list(/datum/reagent/consumable/grenadine = 1, /datum/reagent/consumable/ethanol/applejack = 2, /datum/reagent/consumable/limejuice = 1) mix_message = "As the grenadine incorporates, the beverage takes on a mellow, red-orange glow." -/datum/chemical_reaction/turbo +/datum/chemical_reaction/drink/turbo results = list(/datum/reagent/consumable/ethanol/turbo = 5) required_reagents = list(/datum/reagent/consumable/ethanol/moonshine = 2, /datum/reagent/nitrous_oxide = 1, /datum/reagent/consumable/ethanol/sugar_rush = 1, /datum/reagent/consumable/pwr_game = 1) -/datum/chemical_reaction/old_timer +/datum/chemical_reaction/drink/old_timer results = list(/datum/reagent/consumable/ethanol/old_timer = 6) required_reagents = list(/datum/reagent/consumable/ethanol/whiskeysoda = 3, /datum/reagent/consumable/parsnipjuice = 2, /datum/reagent/consumable/ethanol/alexander = 1) -/datum/chemical_reaction/rubberneck +/datum/chemical_reaction/drink/rubberneck results = list(/datum/reagent/consumable/ethanol/rubberneck = 10) required_reagents = list(/datum/reagent/consumable/ethanol = 4, /datum/reagent/consumable/grey_bull = 5, /datum/reagent/consumable/astrotame = 1) -/datum/chemical_reaction/duplex +/datum/chemical_reaction/drink/duplex results = list(/datum/reagent/consumable/ethanol/duplex = 4) required_reagents = list(/datum/reagent/consumable/ethanol/hcider = 2, /datum/reagent/consumable/applejuice = 1, /datum/reagent/consumable/berryjuice = 1) -/datum/chemical_reaction/trappist +/datum/chemical_reaction/drink/trappist results = list(/datum/reagent/consumable/ethanol/trappist = 5) required_reagents = list(/datum/reagent/consumable/ethanol/ale = 2, /datum/reagent/water/holywater = 2, /datum/reagent/consumable/sugar = 1) -/datum/chemical_reaction/cream_soda +/datum/chemical_reaction/drink/cream_soda results = list(/datum/reagent/consumable/cream_soda = 4) required_reagents = list(/datum/reagent/consumable/sugar = 2, /datum/reagent/consumable/sodawater = 2, /datum/reagent/consumable/vanilla = 1) -/datum/chemical_reaction/blazaam +/datum/chemical_reaction/drink/blazaam results = list(/datum/reagent/consumable/ethanol/blazaam = 3) required_reagents = list(/datum/reagent/consumable/ethanol/gin = 2, /datum/reagent/consumable/peachjuice = 1, /datum/reagent/bluespace = 1) -/datum/chemical_reaction/planet_cracker +/datum/chemical_reaction/drink/planet_cracker results = list(/datum/reagent/consumable/ethanol/planet_cracker = 4) required_reagents = list(/datum/reagent/consumable/ethanol/champagne = 2, /datum/reagent/consumable/ethanol/lizardwine = 2, /datum/reagent/consumable/eggyolk = 1, /datum/reagent/gold = 1) mix_message = "The liquid's color starts shifting as the nanogold is alternately corroded and redeposited." -/datum/chemical_reaction/red_queen +/datum/chemical_reaction/drink/red_queen results = list(/datum/reagent/consumable/red_queen = 10) required_reagents = list(/datum/reagent/consumable/tea = 6, /datum/reagent/mercury = 2, /datum/reagent/consumable/blackpepper = 1, /datum/reagent/growthserum = 1) -/datum/chemical_reaction/mauna_loa +/datum/chemical_reaction/drink/mauna_loa results = list(/datum/reagent/consumable/ethanol/mauna_loa = 5) required_reagents = list(/datum/reagent/consumable/capsaicin = 2, /datum/reagent/consumable/ethanol/kahlua = 1, /datum/reagent/consumable/ethanol/bahama_mama = 2) -/datum/chemical_reaction/godfather +/datum/chemical_reaction/drink/godfather results = list(/datum/reagent/consumable/ethanol/godfather = 2) required_reagents = list(/datum/reagent/consumable/ethanol/amaretto = 1, /datum/reagent/consumable/ethanol/whiskey = 1) -/datum/chemical_reaction/godmother +/datum/chemical_reaction/drink/godmother results = list(/datum/reagent/consumable/ethanol/godmother = 2) required_reagents = list(/datum/reagent/consumable/ethanol/amaretto = 1, /datum/reagent/consumable/ethanol/vodka = 1) -/datum/chemical_reaction/amaretto_alexander +/datum/chemical_reaction/drink/amaretto_alexander results = list(/datum/reagent/consumable/ethanol/amaretto_alexander = 3) required_reagents = list(/datum/reagent/consumable/ethanol/amaretto = 1, /datum/reagent/consumable/ethanol/creme_de_cacao = 1, /datum/reagent/consumable/cream = 1) -/datum/chemical_reaction/ginger_amaretto +/datum/chemical_reaction/drink/ginger_amaretto results = list(/datum/reagent/consumable/ethanol/ginger_amaretto = 4) required_reagents = list(/datum/reagent/consumable/ethanol/amaretto = 1, /datum/reagent/consumable/sol_dry = 1, /datum/reagent/consumable/ice = 1, /datum/reagent/consumable/lemonjuice = 1) diff --git a/code/modules/food_and_drinks/recipes/food_mixtures.dm b/code/modules/food_and_drinks/recipes/food_mixtures.dm index 2f1cc77cf6b4bb..eed688d1604dd1 100644 --- a/code/modules/food_and_drinks/recipes/food_mixtures.dm +++ b/code/modules/food_and_drinks/recipes/food_mixtures.dm @@ -8,39 +8,50 @@ //////////////////////////////////////////FOOD MIXTURES//////////////////////////////////// -/datum/chemical_reaction/tofu +/datum/chemical_reaction/food + optimal_temp = 400 + temp_exponent_factor = 1 + optimal_ph_min = 2 + optimal_ph_max = 10 + thermic_constant = 0 + H_ion_release = 0 + +/datum/chemical_reaction/food/tofu required_reagents = list(/datum/reagent/consumable/soymilk = 10) required_catalysts = list(/datum/reagent/consumable/enzyme = 5) mob_react = FALSE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/tofu/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/food/tofu/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/food/tofu(location) return -/datum/chemical_reaction/chocolatepudding +/datum/chemical_reaction/food/chocolatepudding results = list(/datum/reagent/consumable/chocolatepudding = 20) required_reagents = list(/datum/reagent/consumable/milk/chocolate_milk = 10, /datum/reagent/consumable/eggyolk = 5) -/datum/chemical_reaction/vanillapudding +/datum/chemical_reaction/food/vanillapudding results = list(/datum/reagent/consumable/vanillapudding = 20) required_reagents = list(/datum/reagent/consumable/vanilla = 5, /datum/reagent/consumable/milk = 5, /datum/reagent/consumable/eggyolk = 5) -/datum/chemical_reaction/chocolate_bar +/datum/chemical_reaction/food/chocolate_bar required_reagents = list(/datum/reagent/consumable/soymilk = 2, /datum/reagent/consumable/coco = 2, /datum/reagent/consumable/sugar = 2) + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/chocolate_bar/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/food/chocolate_bar/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/food/chocolatebar(location) return -/datum/chemical_reaction/chocolate_bar2 +/datum/chemical_reaction/food/chocolate_bar2 required_reagents = list(/datum/reagent/consumable/milk/chocolate_milk = 4, /datum/reagent/consumable/sugar = 2) mob_react = FALSE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/chocolate_bar2/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/food/chocolate_bar2/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/food/chocolatebar(location) @@ -49,7 +60,7 @@ /datum/chemical_reaction/chocolate_bar3 required_reagents = list(/datum/reagent/consumable/milk = 2, /datum/reagent/consumable/coco = 2, /datum/reagent/consumable/sugar = 2) -/datum/chemical_reaction/chocolate_bar3/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/chocolate_bar3/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/food/chocolatebar(location) @@ -59,110 +70,118 @@ results = list(/datum/reagent/consumable/soysauce = 5) required_reagents = list(/datum/reagent/consumable/soymilk = 4, /datum/reagent/toxin/acid = 1) -/datum/chemical_reaction/corn_syrup +/datum/chemical_reaction/food/corn_syrup results = list(/datum/reagent/consumable/corn_syrup = 5) required_reagents = list(/datum/reagent/consumable/corn_starch = 1, /datum/reagent/toxin/acid = 1) required_temp = 374 -/datum/chemical_reaction/caramel +/datum/chemical_reaction/food/caramel results = list(/datum/reagent/consumable/caramel = 1) required_reagents = list(/datum/reagent/consumable/sugar = 1) required_temp = 413.15 + optimal_temp = 600 mob_react = FALSE -/datum/chemical_reaction/caramel_burned +/datum/chemical_reaction/food/caramel_burned results = list(/datum/reagent/carbon = 1) required_reagents = list(/datum/reagent/consumable/caramel = 1) required_temp = 483.15 + optimal_temp = 600 mob_react = FALSE -/datum/chemical_reaction/cheesewheel +/datum/chemical_reaction/food/cheesewheel required_reagents = list(/datum/reagent/consumable/milk = 40) required_catalysts = list(/datum/reagent/consumable/enzyme = 5) + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/cheesewheel/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/food/cheesewheel/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/food/cheese/wheel(location) -/datum/chemical_reaction/synthmeat +/datum/chemical_reaction/food/synthmeat required_reagents = list(/datum/reagent/blood = 5, /datum/reagent/medicine/cryoxadone = 1) mob_react = FALSE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/synthmeat/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/food/synthmeat/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/food/meat/slab/synthmeat(location) -/datum/chemical_reaction/hot_ramen +/datum/chemical_reaction/food/hot_ramen results = list(/datum/reagent/consumable/hot_ramen = 3) required_reagents = list(/datum/reagent/water = 1, /datum/reagent/consumable/dry_ramen = 3) -/datum/chemical_reaction/hell_ramen +/datum/chemical_reaction/food/hell_ramen results = list(/datum/reagent/consumable/hell_ramen = 6) required_reagents = list(/datum/reagent/consumable/capsaicin = 1, /datum/reagent/consumable/hot_ramen = 6) -/datum/chemical_reaction/imitationcarpmeat +/datum/chemical_reaction/food/imitationcarpmeat required_reagents = list(/datum/reagent/toxin/carpotoxin = 5) required_container = /obj/item/food/tofu mix_message = "The mixture becomes similar to carp meat." + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/imitationcarpmeat/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/food/imitationcarpmeat/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) new /obj/item/food/fishmeat/carp/imitation(location) if(holder?.my_atom) qdel(holder.my_atom) -/datum/chemical_reaction/dough +/datum/chemical_reaction/food/dough required_reagents = list(/datum/reagent/water = 10, /datum/reagent/consumable/flour = 15) mix_message = "The ingredients form a dough." + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/dough/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/food/dough/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/food/dough(location) -/datum/chemical_reaction/cakebatter +/datum/chemical_reaction/food/cakebatter required_reagents = list(/datum/reagent/consumable/eggyolk = 15, /datum/reagent/consumable/flour = 15, /datum/reagent/consumable/sugar = 5) mix_message = "The ingredients form a cake batter." + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/cakebatter/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/food/cakebatter/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/food/cakebatter(location) -/datum/chemical_reaction/cakebatter/vegan +/datum/chemical_reaction/food/cakebatter/vegan required_reagents = list(/datum/reagent/consumable/soymilk = 15, /datum/reagent/consumable/flour = 15, /datum/reagent/consumable/sugar = 5) -/datum/chemical_reaction/pancakebatter +/datum/chemical_reaction/food/pancakebatter results = list(/datum/reagent/consumable/pancakebatter = 15) required_reagents = list(/datum/reagent/consumable/eggyolk = 12, /datum/reagent/consumable/milk = 10, /datum/reagent/consumable/flour = 5) -/datum/chemical_reaction/ricebowl +/datum/chemical_reaction/food/ricebowl required_reagents = list(/datum/reagent/consumable/rice = 10, /datum/reagent/water = 10) required_container = /obj/item/reagent_containers/glass/bowl mix_message = "The rice absorbs the water." + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/ricebowl/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/food/ricebowl/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) new /obj/item/food/salad/ricebowl(location) if(holder?.my_atom) qdel(holder.my_atom) -/datum/chemical_reaction/nutriconversion +/datum/chemical_reaction/food/nutriconversion results = list(/datum/reagent/consumable/nutriment/peptides = 0.5) required_reagents = list(/datum/reagent/consumable/nutriment/ = 0.5) required_catalysts = list(/datum/reagent/medicine/metafactor = 0.5) -/datum/chemical_reaction/protein_peptide +/datum/chemical_reaction/food/protein_peptide results = list(/datum/reagent/consumable/nutriment/peptides = 0.5) required_reagents = list(/datum/reagent/consumable/nutriment/protein = 0.5) required_catalysts = list(/datum/reagent/medicine/metafactor = 0.5) -/datum/chemical_reaction/bbqsauce +/datum/chemical_reaction/food/bbqsauce results = list(/datum/reagent/consumable/bbqsauce = 5) required_reagents = list(/datum/reagent/ash = 1, /datum/reagent/consumable/tomatojuice = 1, /datum/reagent/medicine/salglu_solution = 3, /datum/reagent/consumable/blackpepper = 1) -/datum/chemical_reaction/gravy +/datum/chemical_reaction/food/gravy results = list(/datum/reagent/consumable/gravy = 3) required_reagents = list(/datum/reagent/consumable/milk = 1, /datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/flour = 1) diff --git a/code/modules/jobs/job_types/chemist.dm b/code/modules/jobs/job_types/chemist.dm index 0ca731d0b52ebc..e71e93c1cc5da5 100644 --- a/code/modules/jobs/job_types/chemist.dm +++ b/code/modules/jobs/job_types/chemist.dm @@ -27,6 +27,8 @@ glasses = /obj/item/clothing/glasses/science belt = /obj/item/pda/chemist + l_pocket = /obj/item/reagent_containers/glass/bottle/random_buffer + r_pocket = /obj/item/reagent_containers/dropper ears = /obj/item/radio/headset/headset_med uniform = /obj/item/clothing/under/rank/medical/chemist shoes = /obj/item/clothing/shoes/sneakers/white diff --git a/code/modules/plumbing/plumbers/reaction_chamber.dm b/code/modules/plumbing/plumbers/reaction_chamber.dm index 794987e5e044bb..75cfcfca2eb5b5 100644 --- a/code/modules/plumbing/plumbers/reaction_chamber.dm +++ b/code/modules/plumbing/plumbers/reaction_chamber.dm @@ -13,6 +13,11 @@ ///our reagent goal has been reached, so now we lock our inputs and start emptying var/emptying = FALSE + ///towards which temperature do we build (except during draining)? + var/target_temperature = 300 + ///cool/heat power + var/heater_coefficient = 0.05 //same lvl as acclimator + /obj/machinery/plumbing/reaction_chamber/Initialize(mapload, bolt) . = ..() AddComponent(/datum/component/plumbing/reaction_chamber, bolt) @@ -37,6 +42,11 @@ holder.flags |= NO_REACT return NONE +/obj/machinery/plumbing/reaction_chamber/process(delta_time) + if(!emptying) //suspend heating/cooling during emptying phase + reagents.adjust_thermal_energy((target_temperature - reagents.chem_temp) * heater_coefficient * delta_time * SPECIFIC_HEAT_DEFAULT * reagents.total_volume) //keep constant with chem heater + reagents.handle_reactions() + /obj/machinery/plumbing/reaction_chamber/power_change() . = ..() if(use_power != NO_POWER_USE) @@ -59,6 +69,10 @@ data["reagents"] = text_reagents data["emptying"] = emptying + data["temperature"] = round(reagents.chem_temp, 0.1) + data["ph"] = round(reagents.ph, 0.01) + data["targetTemp"] = target_temperature + data["isReacting"] = reagents.is_reacting return data /obj/machinery/plumbing/reaction_chamber/ui_act(action, params) @@ -77,3 +91,10 @@ var/input_amount = text2num(params["amount"]) if(input_amount) required_reagents[input_reagent] = input_amount + if("temperature") + var/target = params["target"] + if(text2num(target) != null) + target = text2num(target) + . = TRUE + if(.) + target_temperature = clamp(target, 0, 1000) diff --git a/code/modules/reagents/chemistry/equilibrium.dm b/code/modules/reagents/chemistry/equilibrium.dm new file mode 100644 index 00000000000000..e44ddbc53f0a77 --- /dev/null +++ b/code/modules/reagents/chemistry/equilibrium.dm @@ -0,0 +1,404 @@ +/* +* #/datum/equilibrium +* +* A dynamic reaction object that processes the reaction that it is set within it. Relies on a reagents holder to call and operate the functions. +* +* An object/datum to contain the vars for each of the reactions currently ongoing in a holder/reagents datum +* This way all information is kept within one accessable object +* equilibrium is a unique name as reaction is already too close to chemical_reaction +* This is set up this way to reduce holder.dm bloat as well as reduce confusing list overhead +* The crux of the fermimechanics are handled here +* Instant reactions AREN'T handled here. See holder.dm +*/ +/datum/equilibrium + ///The chemical reaction that is presently being processed + var/datum/chemical_reaction/reaction + ///The location/reagents datum the processing is taking place + var/datum/reagents/holder + ///How much product we can make multiplied by the input recipe's products/required_reagents numerical values + var/multiplier = INFINITY + ///The sum total of each of the product's numerical's values. This is so the addition/deletion is kept at the right values for multiple product reactions + var/product_ratio = 0 + ///The total possible that this reaction can make presently - used for gui outputs + var/target_vol = 0 + ///The target volume the reaction is headed towards. This is updated every tick, so isn't the total value for the reaction, it's just a way to ensure we can't make more than is possible. + var/step_target_vol = INFINITY + ///How much of the reaction has been made so far. Mostly used for subprocs, but it keeps track across the whole reaction and is added to every step. + var/reacted_vol = 0 + ///What our last delta_ph was + var/reaction_quality = 1 + ///If we're done with this reaction so that holder can clear it. + var/to_delete = FALSE + ///Result vars, private - do not edit unless in reaction_step() + ///How much we're adding + var/delta_t + ///How pure our step is + var/delta_ph + ///Modifiers from catalysts, do not use negative numbers. + ///I should write a better handiler for modifying these + ///Speed mod + var/speed_mod = 1 + ///pH mod + var/h_ion_mod = 1 + ///Temp mod + var/thermic_mod = 1 + ///Allow us to deal with lag by "charging" up our reactions to react faster over a period - this means that the reaction doesn't suddenly mass react - which can cause explosions + var/time_deficit + +/* +* Creates and sets up a new equlibrium object +* +* Arguments: +* * input_reaction - the chemical_reaction datum that will be processed +* * input_holder - the reagents datum that the output will be put into +*/ +/datum/equilibrium/New(datum/chemical_reaction/input_reaction, datum/reagents/input_holder) + reaction = input_reaction + holder = input_holder + if(!holder || !reaction) //sanity check + stack_trace("A new [type] was set up, with incorrect/null input vars!") + to_delete = TRUE + return + if(!check_inital_conditions()) //If we're outside of the scope of the reaction vars + to_delete = TRUE + return + if(!length(reaction.results)) //Come back to and revise the affected reactions in the next PR, this is a placeholder fix. + holder.instant_react(reaction) //Even if this check fails, there's a backup - look inside of calculate_yield() + to_delete = TRUE + return + LAZYADD(holder.reaction_list, src) + SSblackbox.record_feedback("tally", "chemical_reaction", 1, "[reaction.type] attempts") + + +/datum/equilibrium/Destroy() + if(reacted_vol < target_vol) //We did NOT finish from reagents - so we can restart this reaction given property changes in the beaker. (i.e. if it stops due to low temp, this will allow it to fast restart when heated up again) + LAZYADD(holder.failed_but_capable_reactions, reaction) //Consider replacing check with calculate_yield() + LAZYREMOVE(holder.reaction_list, src) + holder = null + reaction = null + return ..() + +/* +* Check to make sure our input vars are sensible - truncated version of check_reagent_properties() +* +* (as the setup in holder.dm checks for that already - this is a way to reduce calculations on New()) +* Don't call this unless you know what you're doing, this is an internal proc +*/ +/datum/equilibrium/proc/check_inital_conditions() + //Make sure we have the right multipler for on_reaction() + for(var/single_reagent in reaction.required_reagents) + multiplier = min(multiplier, round((holder.get_reagent_amount(single_reagent) / reaction.required_reagents[single_reagent]), CHEMICAL_QUANTISATION_LEVEL)) + if(multiplier == INFINITY) + return FALSE + //Consider purity gating too? - probably not, purity is hard to determine + //To prevent reactions outside of the pH window from starting. + if(!((holder.ph >= (reaction.optimal_ph_min - reaction.determin_ph_range)) && (holder.ph <= (reaction.optimal_ph_max + reaction.determin_ph_range)))) + return FALSE + return TRUE + +/* +* Check to make sure our input vars are sensible - is the holder overheated? does it have the required reagents? Does it have the required calalysts? +* +* If you're adding more checks for reactions, this is the proc to edit +* otherwise, generally, don't call this directed except internally +*/ +/datum/equilibrium/proc/check_reagent_properties() + //Have we exploded from on_reaction? + if(!holder.my_atom || holder.reagent_list.len == 0) + return FALSE + if(!holder) + stack_trace("an equilibrium is missing it's holder.") + return FALSE + if(!reaction) + stack_trace("an equilibrium is missing it's reaction.") + return FALSE + + //set up catalyst checks + var/total_matching_catalysts = 0 + //Reagents check should be handled in the calculate_yield() from multiplier + + //If the product/reactants are too impure + for(var/r in holder.reagent_list) + var/datum/reagent/reagent = r + //this is done this way to reduce processing compared to holder.has_reagent(P) + for(var/c in reaction.required_catalysts) + var/datum/reagent/catalyst = c + if(catalyst == reagent.type) + total_matching_catalysts++ + if(istype(reagent, /datum/reagent/catalyst_agent)) + var/datum/reagent/catalyst_agent/catalyst_agent = reagent + if(reagent.volume >= catalyst_agent.min_volume) + catalyst_agent.consider_catalyst(src) + + if(!(total_matching_catalysts == reaction.required_catalysts.len)) + return FALSE + + //All good! + return TRUE + +/* +* Calculates how much we're aiming to create +* +* Specifically calcuates multiplier, product_ratio, step_target_vol +* Also checks to see if these numbers are sane, returns a TRUE/FALSE +* Generally an internal proc +*/ +/datum/equilibrium/proc/calculate_yield() + if(!reaction) + stack_trace("Tried to calculate an equlibrium for reaction [reaction.type], but there was no reaction set for the datum") + return FALSE + + multiplier = INFINITY + for(var/reagent in reaction.required_reagents) + multiplier = min(multiplier, round((holder.get_reagent_amount(reagent) / reaction.required_reagents[reagent]), CHEMICAL_QUANTISATION_LEVEL)) + + if(!length(reaction.results)) //Incase of no reagent product + product_ratio = 1 + step_target_vol = INFINITY + for(var/reagent in reaction.required_reagents) + step_target_vol = min(step_target_vol, multiplier * reaction.required_reagents[reagent]) + if(step_target_vol == 0 || multiplier == 0) + return FALSE + //Sanity Check + if(step_target_vol == INFINITY || multiplier == INFINITY) //I don't see how this can happen, but I'm not bold enough to let infinities roll around for free + to_delete = TRUE + CRASH("Tried to calculate target vol for [reaction.type] with no products, but could not find required reagents for the reaction. If it got here, something is really broken with the recipe.") + return TRUE + + product_ratio = 0 + step_target_vol = 0 + var/true_reacted_vol //Because volumes can be lost mid reactions + for(var/product in reaction.results) + step_target_vol += (reaction.results[product]*multiplier) + product_ratio += reaction.results[product] + true_reacted_vol += holder.get_reagent_amount(product) + if(step_target_vol == 0 || multiplier == INFINITY) + return FALSE + target_vol = step_target_vol + true_reacted_vol + reacted_vol = true_reacted_vol + return TRUE + +/* +* Deals with lag - allows a reaction to speed up to 3x from delta_time +* "Charged" time (time_deficit) discharges by incrementing reactions by doubling them +* If delta_time is greater than 1.5, then we save the extra time for the next ticks +* +* Arguments: +* * delta_time - the time between the last proc in world.time +*/ +/datum/equilibrium/proc/deal_with_time(delta_time) + if(delta_time > 1) + time_deficit += delta_time - 1 + delta_time = 1 //Lets make sure reactions aren't super speedy and blow people up from a big lag spike + else if (time_deficit) + if(time_deficit < 0.25) + delta_time += time_deficit + time_deficit = 0 + else + delta_time += 0.25 + time_deficit = 0.25 + return delta_time + +/* +* Main method of checking for explosive - or failed states +* Checks overheated() and overly_impure() of a reaction +* This was moved from the start, to the end - after a reaction, so post reaction temperature changes aren't ignored. +* overheated() is first - so double explosions can't happen (i.e. explosions that blow up the holder) +*/ +/datum/equilibrium/proc/check_fail_states() + //Are we overheated? + if(reaction.is_cold_recipe) + if(holder.chem_temp < reaction.overheat_temp) //This is before the process - this is here so that overly_impure and overheated() share the same code location (and therefore vars) for calls. + SSblackbox.record_feedback("tally", "chemical_reaction", 1, "[reaction.type] overheated reaction steps") + reaction.overheated(holder, src) + else + if(holder.chem_temp > reaction.overheat_temp) + SSblackbox.record_feedback("tally", "chemical_reaction", 1, "[reaction.type] overheated reaction steps") + reaction.overheated(holder, src) + + //is our product too impure? + for(var/product in reaction.results) + var/datum/reagent/reagent = holder.has_reagent(product) + if(!reagent) //might be missing from overheat exploding + continue + if (reagent.purity < reaction.purity_min)//If purity is below the min, call the proc + SSblackbox.record_feedback("tally", "chemical_reaction", 1, "[reaction.type] overly impure reaction steps") + reaction.overly_impure(holder, src) + + //did we explode? + if(!holder.my_atom || holder.reagent_list.len == 0) + return FALSE + return TRUE + +/* +* Main reaction processor - Increments the reaction by a timestep +* +* First checks the holder to make sure it can continue +* Then calculates the purity and volume produced.TRUE +* Then adds/removes reagents +* Then alters the holder pH and temperature, and calls reaction_step +* Arguments: +* * delta_time - the time displacement between the last call and the current, 1 is a standard step +* * purity_modifier - how much to modify the step's purity by (0 - 1) +*/ +/datum/equilibrium/proc/react_timestep(delta_time, purity_modifier = 1) + if(to_delete) + //This occurs when it explodes + return FALSE + if(!check_reagent_properties()) //this is first because it'll call explosions first + to_delete = TRUE + return + if(!calculate_yield())//So that this can detect if we're missing reagents + to_delete = TRUE + return + delta_time = deal_with_time(delta_time) + + delta_t = 0 //how far off optimal temp we care + delta_ph = 0 //How far off the pH we are + var/cached_ph = holder.ph + var/cached_temp = holder.chem_temp + var/purity = 1 //purity of the current step + + //Begin checks + //Calculate DeltapH (Deviation of pH from optimal) + //Within mid range + if (cached_ph >= reaction.optimal_ph_min && cached_ph <= reaction.optimal_ph_max) + delta_ph = 1 //100% purity for this step + //Lower range + else if (cached_ph < reaction.optimal_ph_min) //If we're outside of the optimal lower bound + if (cached_ph < (reaction.optimal_ph_min - reaction.determin_ph_range)) //If we're outside of the deterministic bound + delta_ph = 0 //0% purity + else //We're in the deterministic phase + delta_ph = (((cached_ph - (reaction.optimal_ph_min - reaction.determin_ph_range))**reaction.ph_exponent_factor)/((reaction.determin_ph_range**reaction.ph_exponent_factor))) //main pH calculation + //Upper range + else if (cached_ph > reaction.optimal_ph_max) //If we're above of the optimal lower bound + if (cached_ph > (reaction.optimal_ph_max + reaction.determin_ph_range)) //If we're outside of the deterministic bound + delta_ph = 0 //0% purity + else //We're in the deterministic phase + delta_ph = (((- cached_ph + (reaction.optimal_ph_max + reaction.determin_ph_range))**reaction.ph_exponent_factor)/(reaction.determin_ph_range**reaction.ph_exponent_factor))//Reverse - to + to prevent math operation failures. + + //This should never proc, but it's a catch incase someone puts in incorrect values + else + stack_trace("[holder.my_atom] attempted to determine FermiChem pH for '[reaction.type]' which had an invalid pH of [cached_ph] for set recipie pH vars. It's likely the recipe vars are wrong.") + + //Calculate DeltaT (Deviation of T from optimal) + if(!reaction.is_cold_recipe) + if (cached_temp < reaction.optimal_temp && cached_temp >= reaction.required_temp) + delta_t = (((cached_temp - reaction.required_temp)**reaction.temp_exponent_factor)/((reaction.optimal_temp - reaction.required_temp)**reaction.temp_exponent_factor)) + else if (cached_temp >= reaction.optimal_temp) + delta_t = 1 + else //too hot + delta_t = 0 + to_delete = TRUE + return + else + if (cached_temp > reaction.optimal_temp && cached_temp <= reaction.required_temp) + delta_t = (((cached_temp - reaction.required_temp)**reaction.temp_exponent_factor)/((reaction.optimal_temp - reaction.required_temp)**reaction.temp_exponent_factor)) + else if (cached_temp <= reaction.optimal_temp) + delta_t = 1 + else //Too cold + delta_t = 0 + to_delete = TRUE + return + + //Call any special reaction steps BEFORE addition + if(reaction.reaction_step(src, holder, delta_t, delta_ph, step_target_vol) == END_REACTION) + to_delete = TRUE + return + + //Catalyst modifier + delta_t *= speed_mod + + purity = delta_ph//set purity equal to pH offset + + //Then adjust purity of result with beaker reagent purity. + purity *= reactant_purity(reaction) + + //Then adjust it from the input modifier + purity *= purity_modifier + + //Now we calculate how much to add - this is normalised to the rate up limiter + var/delta_chem_factor = (reaction.rate_up_lim*delta_t)*delta_time//add/remove factor + var/total_step_added = 0 + //keep limited + if(delta_chem_factor > step_target_vol) + delta_chem_factor = step_target_vol + else if (delta_chem_factor < CHEMICAL_VOLUME_MINIMUM) + delta_chem_factor = CHEMICAL_VOLUME_MINIMUM + //Normalise to multiproducts + delta_chem_factor /= product_ratio + //delta_chem_factor = round(delta_chem_factor, CHEMICAL_QUANTISATION_LEVEL) // Might not be needed - left here incase testmerge shows that it does. Remove before full commit. + + //Calculate how much product to make and how much reactant to remove factors.. + for(var/reagent in reaction.required_reagents) + holder.remove_reagent(reagent, (delta_chem_factor * reaction.required_reagents[reagent]), safety = TRUE) + //Apply pH changes + holder.adjust_specific_reagent_ph(reagent, (delta_chem_factor * reaction.required_reagents[reagent])*(reaction.H_ion_release*h_ion_mod)) + + var/step_add + for(var/product in reaction.results) + //create the products + step_add = delta_chem_factor * reaction.results[product] + holder.add_reagent(product, step_add, null, cached_temp, purity, override_base_ph = TRUE) + //Apply pH changes + holder.adjust_specific_reagent_ph(product, step_add*reaction.H_ion_release) + reacted_vol += step_add + total_step_added += step_add + + #ifdef REAGENTS_TESTING //Kept in so that people who want to write fermireactions can contact me with this log so I can help them + if(GLOB.Debug2) //I want my spans for my sanity + message_admins("Reaction step active for:[reaction.type]") + message_admins("|Reaction conditions| Temp: [holder.chem_temp], pH: [holder.ph], reactions: [length(holder.reaction_list)], awaiting reactions: [length(holder.failed_but_capable_reactions)], no. reagents:[length(holder.reagent_list)], no. prev reagents: [length(holder.previous_reagent_list)]") + message_admins("Reaction vars: PreReacted:[reacted_vol] of [step_target_vol] of total [target_vol]. delta_t [delta_t], multiplier [multiplier], delta_chem_factor [delta_chem_factor] Pfactor [product_ratio], purity of [purity] from a delta_ph of [delta_ph]. DeltaTime: [delta_time]") + #endif + + //Apply thermal output of reaction to beaker + if(reaction.reaction_flags & REACTION_HEAT_ARBITARY) + holder.chem_temp += (reaction.thermic_constant* total_step_added*thermic_mod) //old method - for every bit added, the whole temperature is adjusted + else //Standard mechanics + var/heat_energy = reaction.thermic_constant * total_step_added * thermic_mod * SPECIFIC_HEAT_DEFAULT + holder.adjust_thermal_energy(heat_energy, 0, 10000) //heat is relative to the beaker conditions + + //Give a chance of sounds + if(prob(5)) + holder.my_atom.audible_message("[icon2html(holder.my_atom, viewers(DEFAULT_MESSAGE_RANGE, src))] [reaction.mix_message]") + if(reaction.mix_sound) + playsound(get_turf(holder.my_atom), reaction.mix_sound, 80, TRUE) + + //Used for UI output + reaction_quality = purity + + //post reaction checks + if(!(check_fail_states())) + to_delete = TRUE + + //end reactions faster so plumbing is faster + if((step_add >= step_target_vol) && (length(holder.reaction_list == 1)))//length is so that plumbing is faster - but it doesn't disable competitive reactions. Basically, competitive reactions will likely reach their step target at the start, so this will disable that. We want to avoid that. But equally, we do want to full stop a holder from reacting asap so plumbing isn't waiting an tick to resolve. + to_delete = TRUE + + holder.update_total()//do NOT recalculate reactions + + +/* +* Calculates the total sum normalised purity of ALL reagents in a holder +* +* Currently calculates it irrespective of required reagents at the start, but this should be changed if this is powergamed to required reagents +* It's not currently because overly_impure affects all reagents +*/ +/datum/equilibrium/proc/reactant_purity(datum/chemical_reaction/C) + var/list/cached_reagents = holder.reagent_list + var/i = 0 + var/cached_purity + for(var/datum/reagent/reagent as anything in holder.reagent_list) + if (reagent in cached_reagents) + cached_purity += reagent.purity + i++ + if(!i)//I've never seen it get here with 0, but in case - it gets here when it blows up from overheat + stack_trace("No reactants found mid reaction for [C.type]. Beaker: [holder.my_atom]") + return 0 //we exploded and cleared reagents - but lets not kill the process + return cached_purity/i + +///Panic stop a reaction - cleanup should be handled by the next timestep +/datum/equilibrium/proc/force_clear_reactive_agents() + for(var/reagent in reaction.required_reagents) + holder.remove_reagent(reagent, (multiplier * reaction.required_reagents[reagent]), safety = 1) diff --git a/code/modules/reagents/chemistry/fermi_readme.md b/code/modules/reagents/chemistry/fermi_readme.md new file mode 100644 index 00000000000000..ebb930e486fca0 --- /dev/null +++ b/code/modules/reagents/chemistry/fermi_readme.md @@ -0,0 +1,212 @@ +# How to make fermi reactions from a code perspective + +## How purity works + +Purity by default only splits on a mob’s consumption unless reaction_flags in the recipe is set to one of the CLEAR_CONVERT defines. Here is a bad flowchart for the on mob process. + +I am not good at flowcharts sorry. + +![image](https://user-images.githubusercontent.com/33956696/103941231-78123b80-5126-11eb-9d89-635a6b810454.png) + +Essentially +For purity: +If 1: normal +If above inverse_chem_val: normal + impure +If below: inverse + +# How reactions mechanics work + +For the effects starting/during/at the end of a reaction see below: + +![image](https://user-images.githubusercontent.com/33956696/103941281-88c2b180-5126-11eb-8740-207dc9bb830d.png) + +Maybe this makes no sense. + +In brief: + +Holder.dm now sets up reactions, while equilibrium.dm runs them. Holder itself is processed when there is a list of reactions, but the equilibrium does the calculating. In essence, it holds onto a list of objects to run. Handle_reactions() is used to update the reaction list, with a few checks at the start to prevent any unnecessary updates. + +#### When a reaction is detected: +- If it’s REACTION_INSTANT then it’ll use a method similar to the old mechanics. +- If not then it’ll set up an equilibrium, which checks to see if the reaction is valid on creation. +- If it’s valid, then on_reaction is called. +- If the reaction’s temperature is over the overheat_temp overheated() is called +- When equilibriums detect they’re invalid, they flag for deletion and holder.dm deletes them. +- If there’s a list of reactions, then the holder starts processing. + +#### When holder is processing: +- Each equilibrium is processed, and it handles it’s own reaction. For each step it handles every reaction. +- At the start, the equilibrium checks it’s conditions and calculates how much it can make in this step. +- It checks the temp, reagents and catalyst. +- If it’s overheated, call overheated() +- If it’s too impure call overly_impure() +- The offset of optimal pH and temp is calculated, and these correlate with purity and yield. + +#### How a holder stops reacting: +When one of the checks fails in the equilibrium object, it is flagged for deletion. The holder will detect this and call reaction_finish() and delete the equilibrium object – ending that reaction. + +## Recipe and processing mechanics + +Lets go over the reaction vars below. These can be edited and set on a per chemical_reaction basis +```dm +/datum/chemical_reaction + ... + var/required_temp = 100 + var/optimal_temp = 500 // Upper end for above + var/overheat_temp = 900 // Temperature at which reaction explodes - If any reaction is this hot, it procs overheated() + var/optimal_ph_min = 5 // Lowest value of pH determining pH a 1 value for pH based rate reactions (Plateu phase) + var/optimal_ph_max = 9 // Higest value for above + var/determin_ph_range = 4 // How far out pH wil react, giving impurity place (Exponential phase) + var/temp_exponent_factor = 2 // How sharp the temperature exponential curve is (to the power of value) + var/ph_exponent_factor = 1 // How sharp the pH exponential curve is (to the power of value) + var/thermic_constant = 1 // Temperature change per 1u produced + var/H_ion_release = 0.01 // pH change per 1u reaction + var/rate_up_lim = 20 // Optimal/max rate possible if all conditions are perfect + var/purity_min = 0.15 // If purity is below 0.15, it calls OverlyImpure() too. Set to 0 to disable this. + var/reaction_flags // bitflags for clear conversions; REACTION_CLEAR_IMPURE, REACTION_CLEAR_INVERSE, REACTION_CLEAR_RETAIN, REACTION_INSTANT +``` + +### How temperature ranges are set and how reaction rate is determined + +Reaction rates are determined by the current temperature of the reagents holder. There are a few variables related to this: + +```dm +/datum/chemical_reaction + var/required_temp = 100 + var/optimal_temp = 500 // Upper end for above + var/overheat_temp = 900 // Temperature at which reaction explodes - If any reaction is this hot, it procs overheated() + var/temp_exponent_factor = 2 // How sharp the temperature exponential curve is (to the power of value) + var/rate_up_lim = 20 // Optimal/max rate possible if all conditions are perfect +``` + +The amount added is based off the recipies’ required_temp, optimal_temp, overheat_temp and temp_exponent_factor. See below: +![image](https://user-images.githubusercontent.com/33956696/104081088-5e571e00-5224-11eb-8834-87aa36b3e45f.png) + +the y axis is the normalised value of growth, which is then muliplied by the rate_up_lim. You can see that temperatures below the required_temp produce no result (the reaction doesn't start, or if it is reacting, the reaction will stop). Between the required and optimal is a region that is defined by the temp_exponent_factor, so in this case the value is ^2, so we see exponential growth. Between the optimal_temp and the overheat_temp is the optimal phase - where the rate factor is 1. After that it continues to react, but will call overheated() per timestep. Presently the default for overheated() is to reduce the yield of the product (i.e. it's faster but you get less). The rate_up_lim is the maximum rate the reaction can go at optimal temperatures, so in this case a rate factor of 1 i.e. a temperature between 500+ will produce 10u, or a temperature of 400 will roughly produce 4u per step (independant of product ratio produced, if you put 10, it will only create 10 maximum regardless of how much product is defined in the results list). + +### How pH ranges are set and what pH mechanics do + +Optimal pH ranges are set on a per recipe basis - though at the moment all recipes use a default recipe, so they all have the same window (except for the buffers). Hopefully either as a community effort/or in future PRs we can create unique profiles for the present reactions in the game. + +As for how you define the reaction variables for a reaction, there are a few new variables for the chemical_recipe datum. I'll go over specifically how pH works for the default reaction. +```dm +/datum/chemical_reaction + ... + var/optimal_ph_min = 5 // Lowest value of pH determining pH a 1 value for pH based rate reactions (Plateu phase) + var/optimal_ph_max = 9 // Higest value for above + var/determin_ph_range = 4 // How far out pH wil react, giving impurity place (Exponential phase) + var/ph_exponent_factor = 1 // How sharp the pH exponential curve is (to the power of value) + var/purity_min = 0.15 // If purity is below 0.15, it calls overly_impure(). In addition, if the product's purity is below this value at the end, the product will be 100% converted into the reagent's failed_chem. Set to 0 to disable this. +``` + +For this default reaction, the curve looks like this: +![image](https://user-images.githubusercontent.com/33956696/104081030-0fa98400-5224-11eb-822e-ffc614799ddc.png) + +The y axis is the purity of the product made for that time step. This is recalculated based off the beaker's sum pH for every tick in the reaction. The rate in which your product is made based off the temperature (If you want me to describe that too I can.) So say our reaction has 10u of a purity 1 of product in there, and for our step we're making another 10u with our pH at (roughly) 3, from the curve our purity is (roughly) 0.5. So we will be adding 10u of 0.5 purity to 10u of 1 purity, resulting in 20u of 0.75 purity product. (Though - to note the reactant's purities also modify the purity of volume created on top of this). + +If you're designing a reaction you can define an optimal range between the OptimalpHMin to OptimalpHMax (5 - 7 in this case) and a deterministic region set by the ReactpHLim (5 - 4, 9 + 4 aka between 1 to 5 and 9 to 13). This deterministic region is exponential, so if you set it to 2 then it’ll exponentially grow, but since our CurveSharpph = 1, it’s linear (basically normalise the range in the determinsitic region, then put that to the power of CurveSharppH). Finally values outside of these ranges will prevent reactions from starting, but if a reaction drifts out during a reaction, the purity of volume created for each step will be 0 (It does not stop ongoing reactions). It’s entirely possible to design a reaction without a deterministic or optimal phase if you wanted. + +Though to note; if your purity dips below the PurityMin of a reaction it’ll call the overly_impure() function – which by default reduces the purity of all reagents in the beaker. Additionally, if the purity at the end of a reaction is below the PurityMin, it’ll convert into the failed chem defined by the product’s failed_chem defined in it's reagent datum. For default the PurityMin is 0.15, and is pretty difficult to fail. This is all customisable however, if you wanted to use these hooks to design a even more unique reaction, just don’t call the parent proc when using methods. + + +### Conditional changes in reagents datum per timestep + +```dm +/datum/chemical_reaction + ... + var/thermic_constant = 1 // Temperature change per 1u produced + var/H_ion_release = 0.01 // pH change per 1u reaction +``` + +The thermic_constant is how much the temperature changes per u created, so for 10u created the temperature will increase by 10K. The H_ion_release is how much the pH changes per u created, for 10u created the pH will increase by 0.1. During a reaction this is the only factor in pH changes - presently the addition/removal of reagents tie to the reaction won't affect this, though other reactions ongoing in the beaker will also affect pH, as well as the removal/addition of reagents outside of the reaction. + +### Reaction flags + +Reaction_flags can be used to set these defines: + +```dm +#define REACTION_CLEAR_IMPURE //Convert into impure/pure on reaction completion in the datum/reagents holder instead of on consumption +#define REACTION_CLEAR_INVERSE //Convert into inverse on reaction completion when purity is low enough in the datum/reagents holder instead of on consumption +#define REACTION_CLEAR_RETAIN //Clear converted chems retain their purities/inverted purities. Requires 1 or both of the above. This is so that it can split again after splitting from a reaction (i.e. if your impure_chem or inverse_chem has it's own impure_chem/inverse_chem and you want it to split again on consumption). +#define REACTION_INSTANT //Used to create instant reactions + +/datum/chemical_reaction + var/reaction_flags +``` + +For REACTION_CLEAR – this causes the purity mechanics to resolve in the beaker at the end of the reaction, instead of when added to a mob. + +#### A note on cold recipies + +Is_cold_recipie requires you to set your overheat_temp and optimal_temp descend instead. +Eg: +```dm +/datum/chemical_reaction + ... + var/required_temp = 300 + var/optimal_temp = 200 + var/overheat_temp = 50 +``` + +# Reagents +The new vars that are introduced are below: +```dm +/datum/reagent + /// pH of the reagent + var/ph = 7 + ///Purity of the reagent + var/purity = 1 + ///the purity of the reagent on creation (i.e. when it's added to a mob and it's purity split it into 2 chems; the purity of the resultant chems are kept as 1, this tracks what the purity was before that) + var/creation_purity = 1 + //impure chem values (see fermi_readme.dm for more details): + var/impure_chem = /datum/reagent/impurity // What chemical path is made when metabolised as a function of purity + var/inverse_chem_val = 0.2 // If the impurity is below 0.5, replace ALL of the chem with inverse_chem upon metabolising + var/inverse_chem = /datum/reagent/impurity/toxic // What chem is metabolised when purity is below inverse_chem_val + var/failed_chem = /datum/reagent/consumable/failed_reaction //what chem is made at the end of a reaction IF the purity is below the recipies purity_min + var/chemical_flags +``` + +- `pH` is the innate pH of the reagent and is used to calculate the pH of a reagents datum on addition/removal. This does not change and is a reference value. The reagents datum pH changes. +- `purity` is the INTERNAL value for splitting. This is set to 1 after splitting so that it doesn't infinite split +- `creation_purity` is the purity of the reagent on creation. This won't change. If you want to write code that checks the purity in any of the methods, use this. +- `impure_chem` is the datum type that is created provided that it's `creation_purity` is above the `inverse_chem_val`. When the reagent is consumed it will split into this OR if the associated `datum/chemical_recipe` has a REACTION_CLEAR_IMPURE flag it will split at the end of the reaction in the `datum/reagents` holder +- `inverse_chem_val` if a reagent's purity is below this value it will 100% convert into `inverse_chem`. If above it will split into `impure_chem`. See the note on purity effects above +- `inverse_chem` is the datum type that is created provided that it's `creation_purity` is below the `inverse_chem_val`. When the reagent is consumed it will 100% convert into this OR if the associated `datum/chemical_recipe` has a REACTION_CLEAR_INVERSE flag it will 100% convert at the end of the reaction in the `datum/reagents` holder +- `failed_chem` is the chem that the product is 100% converted into if the purity is below the associated `datum/chemical_recipies`' `PurityMin` AT THE END OF A REACTION. + +When writing any reagent code ALWAYS use creation_purity. Purity is kept for internal mechanics only and won’t reflect the purity on creation. + +See above for purity mechanics, but this is where you set the reagents that are created. If you’re making an impure reagent I recommend looking at impure_reagents.dm to see how they’re set up and consider using the `datum/reagents/impure` as a parent. + +The flags you can set for `var/chemical_flags` are: +```dm +#define REAGENT_DEAD_PROCESS (1<<0) //allows on_mob_dead() if present in a dead body +#define REAGENT_DONOTSPLIT (1<<1) //Do not split the chem at all during processing - ignores all purity effects +#define REAGENT_INVISIBLE (1<<2) //Doesn't appear on handheld health analyzers. +#define REAGENT_SNEAKYNAME (1<<3) //When inverted, the inverted chem uses the name of the original chem +#define REAGENT_SPLITRETAINVOL (1<<4) //Retains initial volume of chem when splitting for purity effects + +/datum/reagent + var/chemical_flags +``` + +While you might think reagent_flags is a more sensible name - it is already used for beakers. Hopefully this doesn't trip anyone up. + +# Relivant vars from the holder.dm / reagents datum + +There are a few variables that are useful to know about +```dm +/datum/reagents + /// Current temp of the holder volume + var/chem_temp = 150 + ///pH of the whole system + var/ph = CHEMICAL_NORMAL_PH //aka 7 + ///cached list of reagents + var/list/datum/reagent/previous_reagent_list = new/list() + ///Hard check to see if the reagents is presently reacting + var/is_reacting = FALSE +``` +- chem_temp is the temperature used in the `datum/chemical_recipe` +- pH is a result of the sum of all reagents, as well as any changes from buffers and reactions. This is the pH used in `datum/chemical_recipe`. +- isReacting is a bool that can be used outside to ensure that you don't touch a reagents that is reacting. +- previous_reagent_list is a list of the previous reagents (just the typepaths, not the objects) that was present on the last handle_reactions() method. This is to prevent pointless method calls. \ No newline at end of file diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm index 63ad0c0658c0d0..1c9958fa1ae79a 100644 --- a/code/modules/reagents/chemistry/holder.dm +++ b/code/modules/reagents/chemistry/holder.dm @@ -1,6 +1,4 @@ -#define CHEMICAL_QUANTISATION_LEVEL 0.0001 //stops floating point errors causing issues with checking reagent amounts - - +/////////////These are used in the reagents subsystem init() and the reagent_id_typos.dm//////// /proc/build_chemical_reagent_list() //Chemical Reagents - Initialises all /datum/reagent into a list indexed by reagent id @@ -44,7 +42,7 @@ GLOB.chemical_reactions_list[id] += D break // Don't bother adding ourselves to other reagent ids, it is redundant -/////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////Main reagents code///////////////////////////////////////////// /// Holder for a bunch of [/datum/reagent] /datum/reagents @@ -58,6 +56,8 @@ var/atom/my_atom = null /// Current temp of the holder volume var/chem_temp = 150 + ///pH of the whole system + var/ph = CHEMICAL_NORMAL_PH /// unused var/last_tick = 1 /// see [/datum/reagents/proc/metabolize] for usage @@ -66,30 +66,34 @@ var/list/datum/reagent/addiction_list /// various flags, see code\__DEFINES\reagents.dm var/flags + ///list of reactions currently on going, this is a lazylist for optimisation + var/list/datum/equilibrium/reaction_list + ///cached list of reagents typepaths (not object references), this is a lazylist for optimisation + var/list/datum/reagent/previous_reagent_list + ///If a reaction fails due to temperature or pH, this tracks the required temperature or pH for it to be enabled. + var/list/failed_but_capable_reactions + ///Hard check to see if the reagents is presently reacting + var/is_reacting = FALSE /datum/reagents/New(maximum=100, new_flags=0) 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() - flags = new_flags /datum/reagents/Destroy() - . = ..() //We're about to delete all reagents, so lets cleanup addiction_list = null for(var/reagent in reagent_list) var/datum/reagent/R = reagent qdel(R) reagent_list = null + if(is_reacting) //If false, reaction list should be cleaned up + force_stop_reacting() + QDEL_LAZYLIST(reaction_list) + previous_reagent_list = null if(my_atom && my_atom.reagents == src) my_atom.reagents = null my_atom = null - + return ..() /** * Adds a reagent to this holder @@ -100,19 +104,29 @@ * * list/data - Any reagent data for this reagent, used for transferring data with reagents * * reagtemp - Temperature of this reagent, will be equalized * * no_react - prevents reactions being triggered by this addition + * * added_purity - override to force a purity when added + * * added_ph - override to force a pH when added + * * override_base_ph - ingore the present pH of the reagent, and instead use the default (i.e. if buffers/reactions alter it) */ -/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, added_purity = null, added_ph, no_react = 0, override_base_ph = FALSE) if(!isnum(amount) || !amount) return FALSE - if(amount <= 0) + if(amount <= CHEMICAL_QUANTISATION_LEVEL)//To prevent small amount problems. return FALSE var/datum/reagent/glob_reagent = GLOB.chemical_reagents_list[reagent] if(!glob_reagent) - WARNING("[my_atom] attempted to add a reagent called '[reagent]' which doesn't exist. ([usr])") + stack_trace("[my_atom] attempted to add a reagent called '[reagent]' which doesn't exist. ([usr])") return FALSE + var/datum/reagent/D = GLOB.chemical_reagents_list[reagent] + if(isnull(added_purity)) //Because purity additions can be 0 + added_purity = D.creation_purity //Usually 1 + + if(!added_ph) + added_ph = D.ph + update_total() var/cached_total = total_volume if(cached_total + amount > maximum_volume) @@ -134,7 +148,12 @@ for(var/r in cached_reagents) var/datum/reagent/iter_reagent = r if (iter_reagent.type == reagent) - iter_reagent.volume += amount + if(override_base_ph) + added_ph = iter_reagent.ph + iter_reagent.purity = ((iter_reagent.creation_purity * iter_reagent.volume) + (added_purity * amount)) /(iter_reagent.volume + amount) //This should add the purity to the product + iter_reagent.creation_purity = iter_reagent.purity + iter_reagent.ph = ((iter_reagent.ph*(iter_reagent.volume))+(added_ph*amount))/(iter_reagent.volume+amount) + iter_reagent.volume += round(amount, CHEMICAL_QUANTISATION_LEVEL) update_total() iter_reagent.on_merge(data, amount) @@ -146,7 +165,7 @@ set_temperature(reagtemp) SEND_SIGNAL(src, COMSIG_REAGENTS_ADD_REAGENT, iter_reagent, amount, reagtemp, data, no_react) - if(!no_react) + if(!no_react && !is_reacting) //To reduce the amount of calculations for a reaction the reaction list is only updated on a reagents addition. handle_reactions() return TRUE @@ -155,12 +174,15 @@ cached_reagents += new_reagent new_reagent.holder = src new_reagent.volume = amount + new_reagent.purity = added_purity + new_reagent.creation_purity = added_purity + new_reagent.ph = added_ph if(data) new_reagent.data = data new_reagent.on_new(data) if(isliving(my_atom)) - new_reagent.on_mob_add(my_atom) //Must occur before it could posibly run on_mob_delete + new_reagent.on_mob_add(my_atom, amount) //Must occur before it could posibly run on_mob_delete update_total() if(reagtemp != cached_temp) @@ -183,7 +205,7 @@ /// Remove a specific reagent -/datum/reagents/proc/remove_reagent(reagent, amount, safety)//Added a safety check for the trans_id_to +/datum/reagents/proc/remove_reagent(reagent, amount, safety = TRUE)//Added a safety check for the trans_id_to if(isnull(amount)) amount = 0 CRASH("null amount passed to reagent code") @@ -203,12 +225,11 @@ amount = clamp(amount, 0, R.volume) R.volume -= amount update_total() - SEND_SIGNAL(src, COMSIG_REAGENTS_REM_REAGENT, QDELING(R) ? reagent : R, amount) if(!safety)//So it does not handle reactions when it need not to handle_reactions() + SEND_SIGNAL(src, COMSIG_REAGENTS_REM_REAGENT, QDELING(R) ? reagent : R, amount) return TRUE - return FALSE /// Remove an amount of reagents without caring about what they are @@ -250,7 +271,7 @@ var/datum/reagent/R = reagent remove_reagent(R.type, R.volume * part) - update_total() + //finish_reacting() //A just in case - update total is in here - should be unneeded, make sure to test this handle_reactions() return amount @@ -293,11 +314,19 @@ //Clear from relevant lists LAZYREMOVE(addiction_list, R) reagent_list -= R + LAZYREMOVE(previous_reagent_list, R.type) qdel(R) update_total() SEND_SIGNAL(src, COMSIG_REAGENTS_DEL_REAGENT, reagent) return TRUE +//Converts the creation_purity to purity +/datum/reagents/proc/uncache_creation_purity(id) + var/datum/reagent/R = has_reagent(id) + if(!R) + return + R.purity = R.creation_purity + /// Remove every reagent except this one /datum/reagents/proc/isolate_reagent(reagent) var/list/cached_reagents = reagent_list @@ -362,6 +391,7 @@ if(amount < 0) return + var/cached_amount = amount var/atom/target_atom var/datum/reagents/R if(istype(target, /datum/reagents)) @@ -382,6 +412,10 @@ R = target.reagents target_atom = target + //Set up new reagents to inherit the old ongoing reactions + if(!no_react) + transfer_reactions(R) + amount = min(min(amount, src.total_volume), R.maximum_volume-R.total_volume) var/trans_data = null var/transfer_log = list() @@ -394,7 +428,9 @@ var/transfer_amount = T.volume * part if(preserve_data) trans_data = copy_data(T) - R.add_reagent(T.type, transfer_amount * multiplier, trans_data, chem_temp, no_react = 1) //we only handle reaction after every reagent has been transfered. + if(T.intercept_reagents_transfer(R, cached_amount))//Use input amount instead. + continue + R.add_reagent(T.type, transfer_amount * multiplier, trans_data, chem_temp, T.purity, T.ph, no_react = TRUE) //we only handle reaction after every reagent has been transfered. if(methods) if(istype(target_atom, /obj/item/organ)) R.expose_single(T, target, methods, part, show_message) @@ -403,6 +439,8 @@ T.on_transfer(target_atom, methods, transfer_amount * multiplier) remove_reagent(T.type, transfer_amount) transfer_log[T.type] = transfer_amount + if(is_type_in_list(target_atom, list(/mob/living/carbon, /obj/item/organ/stomach))) + R.process_mob_reagent_purity(T.type, transfer_amount * multiplier, T.purity) else var/to_transfer = amount for(var/reagent in cached_reagents) @@ -416,7 +454,9 @@ var/transfer_amount = amount if(amount > T.volume) transfer_amount = T.volume - R.add_reagent(T.type, transfer_amount * multiplier, trans_data, chem_temp, no_react = 1) + if(T.intercept_reagents_transfer(R, cached_amount))//Use input amount instead. + continue + R.add_reagent(T.type, transfer_amount * multiplier, trans_data, chem_temp, T.purity, T.ph, no_react = TRUE) //we only handle reaction after every reagent has been transfered. to_transfer = max(to_transfer - transfer_amount , 0) if(methods) if(istype(target_atom, /obj/item/organ)) @@ -426,11 +466,14 @@ T.on_transfer(target_atom, methods, transfer_amount * multiplier) remove_reagent(T.type, transfer_amount) transfer_log[T.type] = transfer_amount + if(is_type_in_list(target_atom, list(/mob/living/carbon, /obj/item/organ/stomach))) + R.process_mob_reagent_purity(T.type, transfer_amount * multiplier, T.purity) if(transfered_by && target_atom) target_atom.add_hiddenprint(transfered_by) //log prints so admins can figure out who touched it last. log_combat(transfered_by, target_atom, "transferred reagents ([log_list(transfer_log)]) from [my_atom] to") + update_total() R.update_total() if(!no_react) @@ -447,18 +490,23 @@ return if(amount < 0) return - + + var/cached_amount = amount var/datum/reagents/R = target.reagents if(src.get_reagent_amount(reagent) 0) - add_reagent(T.type, T.volume * change) + add_reagent(T.type, T.volume * change, added_purity = T.purity) else remove_reagent(T.type, abs(T.volume * change)) //absolute value to prevent a double negative situation (removing -50% would be adding 50%) @@ -684,127 +736,416 @@ update_total() /// Handle any reactions possible in this holder +/// Also UPDATES the reaction list +/// High potential for infinite loopsa if you're editing this. /datum/reagents/proc/handle_reactions() + if(QDELING(src)) + CRASH("[my_atom] is trying to handle reactions while being flagged for deletion. It presently has [length(reagent_list)] number of reactants in it. If that is over 0 then something terrible happened.") + + if(!length(reagent_list))//The liver is calling this method a lot, and is often empty of reagents so it's pointless busywork. It should be an easy fix, but I'm nervous about touching things beyond scope. Also since everything is so handle_reactions() trigger happy it might be a good idea having this check anyways. + return FALSE + if(flags & NO_REACT) - return 0 //Yup, no reactions here. No siree. + if(is_reacting) + force_stop_reacting() //Force anything that is trying to to stop + return FALSE //Yup, no reactions here. No siree. + + if(is_reacting)//Prevent wasteful calculations + if(datum_flags != DF_ISPROCESSING)//If we're reacting - but not processing (i.e. we've transfered) + START_PROCESSING(SSreagents, src) + if(!(has_changed_state())) + return FALSE var/list/cached_reagents = reagent_list var/list/cached_reactions = GLOB.chemical_reactions_list var/datum/cached_my_atom = my_atom + LAZYNULL(failed_but_capable_reactions) . = 0 - var/reaction_occurred - do - var/list/possible_reactions = list() - reaction_occurred = FALSE - for(var/reagent in cached_reagents) - var/datum/reagent/R = reagent - for(var/reaction in cached_reactions[R.type]) // 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 = FALSE - var/matching_other = FALSE - var/required_temp = C.required_temp - var/is_cold_recipe = C.is_cold_recipe - var/meets_temp_requirement = FALSE - - 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 = TRUE - else - if(cached_my_atom.type == C.required_container) - matching_container = TRUE - if (isliving(cached_my_atom) && !C.mob_react) //Makes it so certain chemical reactions don't occur in mobs - matching_container = FALSE - if(!C.required_other) - matching_other = TRUE - - else if(istype(cached_my_atom, /obj/item/slime_extract)) - var/obj/item/slime_extract/M = cached_my_atom + var/list/possible_reactions = list() + for(var/_reagent in cached_reagents) + var/datum/reagent/reagent = _reagent + for(var/_reaction in cached_reactions[reagent.type]) // Was a big list but now it should be smaller since we filtered it with our reagent id + if(!_reaction) + continue - if(M.Uses > 0) // added a limit to slime cores -- Muskets requested this - matching_other = TRUE + var/datum/chemical_reaction/reaction = _reaction + if(!reaction.required_reagents)//Don't bring in empty ones + continue + var/list/cached_required_reagents = reaction.required_reagents + var/total_required_reagents = cached_required_reagents.len + var/total_matching_reagents = 0 + var/list/cached_required_catalysts = reaction.required_catalysts + var/total_required_catalysts = cached_required_catalysts.len + var/total_matching_catalysts= 0 + var/matching_container = FALSE + var/matching_other = FALSE + var/required_temp = reaction.required_temp + var/is_cold_recipe = reaction.is_cold_recipe + var/meets_temp_requirement = FALSE + var/meets_ph_requirement = FALSE + var/granularity = 1 + if(!(reaction.reaction_flags & REACTION_INSTANT)) + granularity = 0.01 + + for(var/req_reagent in cached_required_reagents) + if(!has_reagent(req_reagent, (cached_required_reagents[req_reagent]*granularity))) + break + total_matching_reagents++ + for(var/_catalyst in cached_required_catalysts) + if(!has_reagent(_catalyst, (cached_required_catalysts[_catalyst]*granularity))) + break + total_matching_catalysts++ + if(cached_my_atom) + if(!reaction.required_container) + matching_container = TRUE else - if(!C.required_container) + if(cached_my_atom.type == reaction.required_container) matching_container = TRUE - if(!C.required_other) - matching_other = TRUE - - if(required_temp == 0 || (is_cold_recipe && chem_temp <= required_temp) || (!is_cold_recipe && chem_temp >= required_temp)) - meets_temp_requirement = TRUE + if (isliving(cached_my_atom) && !reaction.mob_react) //Makes it so certain chemical reactions don't occur in mobs + matching_container = FALSE + if(!reaction.required_other) + matching_other = TRUE - if(total_matching_reagents == total_required_reagents && total_matching_catalysts == total_required_catalysts && matching_container && matching_other && meets_temp_requirement) - possible_reactions += C + else if(istype(cached_my_atom, /obj/item/slime_extract)) + var/obj/item/slime_extract/extract = cached_my_atom - 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 + if(extract.Uses > 0) // added a limit to slime cores -- Muskets requested this + matching_other = TRUE + else + if(!reaction.required_container) + matching_container = TRUE + if(!reaction.required_other) + matching_other = TRUE + + if(required_temp == 0 || (is_cold_recipe && chem_temp <= required_temp) || (!is_cold_recipe && chem_temp >= required_temp)) + meets_temp_requirement = TRUE + + if(((ph >= (reaction.optimal_ph_min - reaction.determin_ph_range)) && (ph <= (reaction.optimal_ph_max + reaction.determin_ph_range)))) + meets_ph_requirement = TRUE + + if(total_matching_reagents == total_required_reagents && total_matching_catalysts == total_required_catalysts && matching_container && matching_other) + if(meets_temp_requirement && meets_ph_requirement) + possible_reactions += reaction 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/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 shouldn't 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, TRUE) - - 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) - reaction_occurred = TRUE + LAZYADD(failed_but_capable_reactions, reaction) + + update_previous_reagent_list() + //This is the point where we have all the possible reactions from a reagent/catalyst point of view, so we set up the reaction list + for(var/_possible_reaction in possible_reactions) + var/datum/chemical_reaction/selected_reaction = _possible_reaction + if((selected_reaction.reaction_flags & REACTION_INSTANT) || (flags & REAGENT_HOLDER_INSTANT_REACT)) //If we have instant reactions, we process them here + instant_react(selected_reaction) .++ + update_total() + continue + else + var/exists = FALSE + for(var/_equilibrium in reaction_list) + var/datum/equilibrium/E_exist = _equilibrium + if(ispath(E_exist.reaction.type, selected_reaction.type)) //Don't add duplicates + exists = TRUE + + //Add it if it doesn't exist in the list + if(!exists) + is_reacting = TRUE//Prevent any on_reaction() procs from infinite looping + var/datum/equilibrium/equilibrium = new (selected_reaction, src) //Otherwise we add them to the processing list. + if(equilibrium.to_delete)//failed startup checks + qdel(equilibrium) + else + //Adding is done in new(), deletion is in qdel + equilibrium.reaction.on_reaction(equilibrium, src, equilibrium.multiplier) + equilibrium.react_timestep(1)//Get an initial step going so there's not a delay between setup and start - DO NOT ADD THIS TO equilibrium.NEW() + + if(LAZYLEN(reaction_list)) + is_reacting = TRUE //We've entered the reaction phase - this is set here so any reagent handling called in on_reaction() doesn't cause infinite loops + START_PROCESSING(SSreagents, src) //see process() to see how reactions are handled + else + is_reacting = FALSE - while(reaction_occurred) - update_total() if(.) SEND_SIGNAL(src, COMSIG_REAGENTS_REACTED, .) +/* +* Main Reaction loop handler, Do not call this directly +* +* Checks to see if there's a reaction, then processes over the reaction list, removing them if flagged +* If any are ended, it displays the reaction message and removes it from the reaction list +* If the list is empty at the end it finishes reacting. +* Arguments: +* * delta_time - the time between each time step +*/ +/datum/reagents/process(delta_time) + if(!is_reacting) + force_stop_reacting() + stack_trace("[src] | [my_atom] was forced to stop reacting. This might be unintentional.") + //sum of output messages. + var/list/mix_message = list() + //Process over our reaction list + //See equilibrium.dm for mechanics + var/num_reactions = 0 + for(var/_equilibrium in reaction_list) + var/datum/equilibrium/equilibrium = _equilibrium + //Continue reacting + equilibrium.react_timestep(delta_time) + num_reactions++ + //if it's been flagged to delete + if(equilibrium.to_delete) + var/temp_mix_message = end_reaction(equilibrium) + if(!text_in_list(temp_mix_message, mix_message)) + mix_message += temp_mix_message + continue + SSblackbox.record_feedback("tally", "chemical_reaction", 1, "[equilibrium.reaction.type] total reaction steps") + + if(num_reactions) + SEND_SIGNAL(src, COMSIG_REAGENTS_REACTION_STEP, num_reactions, delta_time) + + if(length(mix_message)) //This is only at the end + my_atom.audible_message("[icon2html(my_atom, viewers(DEFAULT_MESSAGE_RANGE, src))] [mix_message.Join()]") + + if(!LAZYLEN(reaction_list)) + finish_reacting() + else + update_total() + handle_reactions() + +/* +* This ends a single instance of an ongoing reaction +* +* Arguments: +* * E - the equilibrium that will be ended +* Returns: +* * mix_message - the associated mix message of a reaction +*/ +/datum/reagents/proc/end_reaction(datum/equilibrium/equilibrium) + if(!equilibrium.holder || !equilibrium.reaction) //Somehow I'm getting empty equilibrium. This is here to handle them + LAZYREMOVE(reaction_list, equilibrium) + qdel(equilibrium) + stack_trace("The equilibrium datum currently processing in this reagents datum had a nulled holder or nulled reaction. src holder:[my_atom] || src type:[my_atom.type] ") //Shouldn't happen. Does happen + return + if(equilibrium.holder != src) //When called from Destroy() eqs are nulled in smoke. This is very strange. This is probably causing it to spam smoke because of the runtime interupting the removal. + stack_trace("The equilibrium datum currently processing in this reagents datum had a desynced holder to the ending reaction. src holder:[my_atom] | equilibrium holder:[equilibrium.holder.my_atom] || src type:[my_atom.type] | equilibrium holder:[equilibrium.holder.my_atom.type]") + LAZYREMOVE(reaction_list, equilibrium) + equilibrium.reaction.reaction_finish(src, equilibrium.reacted_vol) + var/reaction_message = equilibrium.reaction.mix_message + if(equilibrium.reaction.mix_sound) + playsound(get_turf(my_atom), equilibrium.reaction.mix_sound, 80, TRUE) + qdel(equilibrium) + update_total() + SEND_SIGNAL(src, COMSIG_REAGENTS_REACTED, .) + return reaction_message + +/* +* This stops the holder from processing at the end of a series of reactions (i.e. when all the equilibriums are completed) +* +* Also resets reaction variables to be null/empty/FALSE so that it can restart correctly in the future +*/ +/datum/reagents/proc/finish_reacting() + STOP_PROCESSING(SSreagents, src) + is_reacting = FALSE + //Cap off values + for(var/_reagent in reagent_list) + var/datum/reagent/reagent = _reagent + reagent.volume = round(reagent.volume, CHEMICAL_VOLUME_ROUNDING)//To prevent runaways. + LAZYNULL(previous_reagent_list) //reset it to 0 - because any change will be different now. + update_total() + if(!QDELING(src)) + handle_reactions() //Should be okay without. Each step checks. + +/* +* Force stops the current holder/reagents datum from reacting +* +* Calls end_reaction() for each equlilbrium datum in reaction_list and finish_reacting() +* Usually only called when a datum is transfered into a NO_REACT container +*/ +/datum/reagents/proc/force_stop_reacting() + var/list/mix_message = list() + for(var/_equilibrium in reaction_list) + var/datum/equilibrium/equilibrium = _equilibrium + mix_message += end_reaction(equilibrium) + if(length(mix_message)) + my_atom.audible_message("[icon2html(my_atom, viewers(DEFAULT_MESSAGE_RANGE, src))] [mix_message.Join()]") + finish_reacting() + +/* +* Force stops a specific reagent's associated reaction if it exists +* +* Mostly used if a reagent is being taken out by trans_id_to +* Might have some other applciations +* Returns TRUE if it stopped something, FALSE if it didn't +* Arguments: +* * reagent - the reagent PRODUCT that we're seeking reactions for, any and all found will be shut down +*/ +/datum/reagents/proc/force_stop_reagent_reacting(datum/reagent/reagent) + var/any_stopped = FALSE + var/list/mix_message = list() + for(var/_equilibrium in reaction_list) + var/datum/equilibrium/equilibrium = _equilibrium + for(var/result in equilibrium.reaction.results) + if(result == reagent.type) + mix_message += end_reaction(equilibrium) + any_stopped = TRUE + if(length(mix_message)) + my_atom.audible_message("[icon2html(my_atom, viewers(DEFAULT_MESSAGE_RANGE, src))] [mix_message.Join()]") + return any_stopped + +/* +* Transfers the reaction_list to a new reagents datum +* +* Arguments: +* * target - the datum/reagents that this src is being transfered into +*/ +/datum/reagents/proc/transfer_reactions(datum/reagents/target) + if(QDELETED(target)) + CRASH("transfer_reactions() had a [target] ([target.type]) passed to it when it was set to qdel, or it isn't a reagents datum.") + if(!reaction_list) + return + for(var/reaction in reaction_list) + var/datum/equilibrium/reaction_source = reaction + var/exists = FALSE + for(var/reaction2 in target.reaction_list) //Don't add duplicates + var/datum/equilibrium/reaction_target = reaction2 + if(reaction_source.reaction.type == reaction_target.reaction.type) + exists = TRUE + if(exists) + continue + if(!reaction_source.holder) + CRASH("reaction_source is missing a holder in transfer_reactions()!") + + var/datum/equilibrium/new_E = new (reaction_source.reaction, target)//addition to reaction_list is done in new() + if(new_E.to_delete)//failed startup checks + qdel(new_E) + + target.previous_reagent_list = LAZYLISTDUPLICATE(previous_reagent_list) + target.is_reacting = is_reacting + + +///Checks to see if the reagents has a difference in reagents_list and previous_reagent_list (I.e. if there's a difference between the previous call and the last) +///Also checks to see if the saved reactions in failed_but_capable_reactions can start as a result of temp/pH change +/datum/reagents/proc/has_changed_state() + //Check if reagents are different + var/total_matching_reagents = 0 + for(var/reagent in previous_reagent_list) + if(has_reagent(reagent)) + total_matching_reagents++ + if(total_matching_reagents != reagent_list.len) + return TRUE + + //Check our last reactions + for(var/_reaction in failed_but_capable_reactions) + var/datum/chemical_reaction/reaction = _reaction + if(reaction.is_cold_recipe) + if(reaction.required_temp < chem_temp) + return TRUE + else + if(reaction.required_temp < chem_temp) + return TRUE + if(((ph >= (reaction.optimal_ph_min - reaction.determin_ph_range)) && (ph <= (reaction.optimal_ph_max + reaction.determin_ph_range)))) + return TRUE + return FALSE + +/datum/reagents/proc/update_previous_reagent_list() + LAZYNULL(previous_reagent_list) + for(var/_reagent in reagent_list) + var/datum/reagent/reagent = _reagent + LAZYADD(previous_reagent_list, reagent.type) + +///Old reaction mechanics, edited to work on one only +///This is changed from the old - purity of the reagents will affect yield +/datum/reagents/proc/instant_react(datum/chemical_reaction/selected_reaction) + var/list/cached_required_reagents = selected_reaction.required_reagents + var/list/cached_results = selected_reaction.results + var/datum/cached_my_atom = my_atom + var/multiplier = INFINITY + for(var/reagent in cached_required_reagents) + multiplier = min(multiplier, round(get_reagent_amount(reagent) / cached_required_reagents[reagent])) + + if(multiplier == 0)//Incase we're missing reagents - usually from on_reaction being called in an equlibrium when the results.len == 0 handlier catches a misflagged reaction + return FALSE + var/sum_purity = 0 + for(var/_reagent in cached_required_reagents) + var/datum/reagent/reagent = has_reagent(_reagent) + sum_purity += reagent.purity + remove_reagent(_reagent, (multiplier * cached_required_reagents[_reagent]), safety = 1) + sum_purity /= cached_required_reagents.len + + for(var/product in selected_reaction.results) + multiplier = max(multiplier, 1) //this shouldn't happen ... + var/yield = (cached_results[product]*multiplier)*sum_purity + SSblackbox.record_feedback("tally", "chemical_reaction", yield, product) + add_reagent(product, yield, null, chem_temp, sum_purity) + + 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, TRUE) + + my_atom.audible_message("[iconhtml] [selected_reaction.mix_message]") + + if(istype(cached_my_atom, /obj/item/slime_extract)) + var/obj/item/slime_extract/extract = my_atom + extract.Uses-- + if(extract.Uses <= 0) // give the notification that the slime core is dead + my_atom.visible_message("[iconhtml] \The [my_atom]'s power is consumed in the reaction.") + extract.name = "used slime extract" + extract.desc = "This extract has been used up." + + selected_reaction.on_reaction(null, src, multiplier) + +///Possibly remove - see if multiple instant reactions is okay (Though, this "sorts" reactions by temp decending) +///Presently unused +/datum/reagents/proc/get_priority_instant_reaction(list/possible_reactions) + if(!length(possible_reactions)) + return FALSE + var/datum/chemical_reaction/selected_reaction = possible_reactions[1] + //select the reaction with the most extreme temperature requirements + for(var/_reaction in possible_reactions) + var/datum/chemical_reaction/competitor = _reaction + if(selected_reaction.is_cold_recipe) + if(competitor.required_temp <= selected_reaction.required_temp) + selected_reaction = competitor + else + if(competitor.required_temp >= selected_reaction.required_temp) + selected_reaction = competitor + return selected_reaction + +/*Processes the reagents in the holder and converts them, only called in a mob/living/carbon on addition +* +* Arguments: +* * reagent - the added reagent datum/object +* * added_volume - the volume of the reagent that was added (since it can already exist in a mob) +* * added_purity - the purity of the added volume +*/ +/datum/reagents/proc/process_mob_reagent_purity(_reagent, added_volume, added_purity) + var/datum/reagent/R = has_reagent(_reagent) + if(!R) + stack_trace("Tried to process reagent purity for [_reagent], but 0 volume was found right after it was added!") //This can happen from smoking, where the volume is 0 after adding? + return + if (R.purity == 1) + return + if(R.chemical_flags & REAGENT_DONOTSPLIT) + R.purity = 1 + return + if(R.purity < 0) + stack_trace("Purity below 0 for chem: [type]!") + R.purity = 0 + + if ((R.inverse_chem_val > R.purity) && (R.inverse_chem))//Turns all of a added reagent into the inverse chem + remove_reagent(R.type, added_volume, FALSE) + add_reagent(R.inverse_chem, added_volume, FALSE, added_purity = 1-R.creation_purity) + var/datum/reagent/inverse_reagent = has_reagent(R.inverse_chem) + if(inverse_reagent.chemical_flags & REAGENT_SNEAKYNAME) + inverse_reagent.name = R.name//Negative effects are hidden + if(inverse_reagent.chemical_flags & REAGENT_INVISIBLE) + inverse_reagent.chemical_flags |= (REAGENT_INVISIBLE) + else if (R.impure_chem) + var/impureVol = added_volume * (1 - R.purity) //turns impure ratio into impure chem + if(!(R.chemical_flags & REAGENT_SPLITRETAINVOL)) + remove_reagent(R.type, impureVol, FALSE) + add_reagent(R.impure_chem, impureVol, FALSE, added_purity = 1-R.creation_purity) + R.purity = 1 //prevent this process from repeating (this is why creation_purity exists) /// Updates [/datum/reagents/var/total_volume] /datum/reagents/proc/update_total() @@ -812,10 +1153,14 @@ total_volume = 0 for(var/reagent in cached_reagents) var/datum/reagent/R = reagent - if(R.volume < 0.05) + if((R.volume < 0.05) && !is_reacting) + del_reagent(R.type) + else if(R.volume <= CHEMICAL_VOLUME_MINIMUM)//For clarity del_reagent(R.type) else total_volume += R.volume + recalculate_sum_ph() + /** * Applies the relevant expose_ proc for every reagent in this holder @@ -873,6 +1218,15 @@ return round(R.volume, CHEMICAL_QUANTISATION_LEVEL) return 0 +/// Get the purity of this reagent +/datum/reagents/proc/get_reagent_purity(reagent) + var/list/cached_reagents = reagent_list + for(var/_reagent in cached_reagents) + var/datum/reagent/R = _reagent + if (R.type == reagent) + return round(R.purity, 0.01) + return 0 + /// Get a comma separated string of every reagent name in this holder. UNUSED /datum/reagents/proc/get_reagent_names() var/list/names = list() @@ -1018,6 +1372,65 @@ chem_temp = _temperature SEND_SIGNAL(src, COMSIG_REAGENTS_TEMP_CHANGE, _temperature, .) +/* +* Adjusts the base pH of all of the reagents in a beaker +* +* - moves it towards acidic +* + moves it towards basic +* Arguments: +* * value - How much to adjust the base pH by +*/ +/datum/reagents/proc/adjust_all_reagents_ph(value, lower_limit = 0, upper_limit = 14) + for(var/reagent in reagent_list) + var/datum/reagent/R = reagent + R.ph = clamp(R.ph + value, lower_limit, upper_limit) + +/* +* Adjusts the base pH of all of the listed types +* +* - moves it towards acidic +* + moves it towards basic +* Arguments: +* * input_reagents_list - list of reagents to adjust +* * value - How much to adjust the base pH by +*/ +/datum/reagents/proc/adjust_specific_reagent_list_ph(list/input_reagents_list, value, lower_limit = 0, upper_limit = 14) + for(var/reagent in input_reagents_list) + var/datum/reagent/R = get_reagent(reagent) + if(!R) //We can call this with missing reagents. + continue + R.ph = clamp(R.ph + value, lower_limit, upper_limit) + +/* +* Adjusts the base pH of a specific type +* +* - moves it towards acidic +* + moves it towards basic +* Arguments: +* * input_reagent - type path of the reagent +* * value - How much to adjust the base pH by +* * lower_limit - how low the pH can go +* * upper_limit - how high the pH can go +*/ +/datum/reagents/proc/adjust_specific_reagent_ph(input_reagent, value, lower_limit = 0, upper_limit = 14) + var/datum/reagent/R = get_reagent(input_reagent) + if(!R) //We can call this with missing reagents. + return FALSE + R.ph = clamp(R.ph + value, lower_limit, upper_limit) + +/* +* Updates the reagents datum pH based off the volume weighted sum of the reagent_list's reagent pH +*/ +/datum/reagents/proc/recalculate_sum_ph() + if(!reagent_list || !total_volume) //Ensure that this is true + ph = CHEMICAL_NORMAL_PH + return + var/total_ph = 0 + for(var/reagent in reagent_list) + var/datum/reagent/R = get_reagent(reagent) //we need the specific instance + total_ph += (R.ph * R.volume) + //Keep limited + ph = clamp(total_ph/total_volume, 0, 14) /** * Used in attack logs for reagents in pills and such diff --git a/code/modules/reagents/chemistry/items.dm b/code/modules/reagents/chemistry/items.dm new file mode 100644 index 00000000000000..d4f7bf6fdfd82f --- /dev/null +++ b/code/modules/reagents/chemistry/items.dm @@ -0,0 +1,164 @@ +///if the ph_meter gives a detailed output +#define DETAILED_CHEM_OUTPUT 1 +///if the pH meter gives a shorter output +#define SHORTENED_CHEM_OUTPUT 0 + +/* +* a pH booklet that contains pH paper pages that will change color depending on the pH of the reagents datum it's attacked onto +*/ +/obj/item/ph_booklet + name = "pH indicator booklet" + desc = "A booklet containing paper soaked in universal indicator." + icon_state = "pHbooklet" + icon = 'icons/obj/chemical.dmi' + item_flags = NOBLUDGEON + resistance_flags = FLAMMABLE + w_class = WEIGHT_CLASS_TINY + ///How many pages the booklet holds + var/number_of_pages = 50 + +//A little janky with pockets +/obj/item/ph_booklet/attack_hand(mob/user) + if(user.get_held_index_of_item(src))//Does this check pockets too..? + if(number_of_pages == 50) + icon_state = "pHbooklet_open" + if(!number_of_pages) + to_chat(user, "[src] is empty!") + add_fingerprint(user) + return + var/obj/item/ph_paper/page = new(get_turf(user)) + page.add_fingerprint(user) + user.put_in_active_hand(page) + to_chat(user, "You take [page] out of \the [src].") + number_of_pages-- + playsound(user.loc, 'sound/items/poster_ripped.ogg', 50, TRUE) + add_fingerprint(user) + if(!number_of_pages) + icon_state = "pHbooklet_empty" + return + var/I = user.get_active_held_item() + if(!I) + user.put_in_active_hand(src) + return ..() + +/obj/item/ph_booklet/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params) + var/mob/living/user = usr + if(!isliving(user)) + return + if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) + return + if(!number_of_pages) + to_chat(user, "[src] is empty!") + add_fingerprint(user) + return + if(number_of_pages == 50) + icon_state = "pHbooklet_open" + var/obj/item/ph_paper/P = new(get_turf(user)) + P.add_fingerprint(user) + user.put_in_active_hand(P) + to_chat(user, "You take [P] out of \the [src].") + number_of_pages-- + playsound(user.loc, 'sound/items/poster_ripped.ogg', 50, TRUE) + add_fingerprint(user) + if(!number_of_pages) + icon_state = "pHbookletEmpty" + +/* +* pH paper will change color depending on the pH of the reagents datum it's attacked onto +*/ +/obj/item/ph_paper + name = "pH indicator strip" + desc = "A piece of paper that will change colour depending on the pH of a solution." + icon_state = "pHpaper" + icon = 'icons/obj/chemical.dmi' + item_flags = NOBLUDGEON + color = "#f5c352" + resistance_flags = FLAMMABLE + w_class = WEIGHT_CLASS_TINY + ///If the paper was used, and therefore cannot change color again + var/used = FALSE + +/obj/item/ph_paper/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + var/obj/item/reagent_containers/cont = target + if(!istype(cont)) + return + if(used == TRUE) + to_chat(user, "[src] has already been used!") + return + if(!LAZYLEN(cont.reagents.reagent_list)) + return + switch(round(cont.reagents.ph, 1)) + if(14 to INFINITY) + color = "#462c83" + if(13 to 14) + color = "#63459b" + if(12 to 13) + color = "#5a51a2" + if(11 to 12) + color = "#3853a4" + if(10 to 11) + color = "#3f93cf" + if(9 to 10) + color = "#0bb9b7" + if(8 to 9) + color = "#23b36e" + if(7 to 8) + color = "#3aa651" + if(6 to 7) + color = "#4cb849" + if(5 to 6) + color = "#b5d335" + if(4 to 5) + color = "#f7ec1e" + if(3 to 4) + color = "#fbc314" + if(2 to 3) + color = "#f26724" + if(1 to 2) + color = "#ef1d26" + if(-INFINITY to 1) + color = "#c6040c" + desc += " The paper looks to be around a pH of [round(cont.reagents.ph, 1)]" + name = "used [name]" + used = TRUE + +/* +* pH meter that will give a detailed or truncated analysis of all the reagents in of an object with a reagents datum attached to it. Only way of detecting purity for now. +*/ +/obj/item/ph_meter + name = "Chemistry Analyser" + desc = "An electrode attached to a small circuit box that will display details of a solution. Can be toggled to provide a description of each of the reagents. The screen currently displays nothing." + icon_state = "pHmeter" + icon = 'icons/obj/chemical.dmi' + resistance_flags = FLAMMABLE + w_class = WEIGHT_CLASS_TINY + ///level of detail for output for the meter + var/scanmode = DETAILED_CHEM_OUTPUT + +/obj/item/ph_meter/attack_self(mob/user) + if(scanmode == SHORTENED_CHEM_OUTPUT) + to_chat(user, "You switch the chemical analyzer to provide a detailed description of each reagent.") + scanmode = DETAILED_CHEM_OUTPUT + else + to_chat(user, "You switch the chemical analyzer to not include reagent descriptions in it's report.") + scanmode = SHORTENED_CHEM_OUTPUT + +/obj/item/ph_meter/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + if(!istype(target, /obj/item/reagent_containers)) + return + var/obj/item/reagent_containers/cont = target + if(LAZYLEN(cont.reagents.reagent_list) == null) + return + var/list/out_message = list() + to_chat(user, "The chemistry meter beeps and displays:") + out_message += "Total volume: [round(cont.volume, 0.01)] Current temperature: [round(cont.reagents.chem_temp, 0.1)]K Total pH: [round(cont.reagents.ph, 0.01)]\n" + out_message += "Chemicals found in the beaker:\n" + if(cont.reagents.is_reacting) + out_message += "A reaction appears to be occuring currently.\n" + for(var/datum/reagent/R in cont.reagents.reagent_list) + out_message += "[round(R.volume, 0.01)]u of [R.name], Purity: [round(R.purity, 0.01)], [(scanmode?"[(R.overdose_threshold?"Overdose: [R.overdose_threshold]u, ":"")][(R.addiction_threshold?"Addiction: [R.addiction_threshold]u, ":"")]Base pH: [initial(R.ph)], Current pH: [R.ph].":"Current pH: [R.ph].")]\n" + if(scanmode) + out_message += "Analysis: [R.description]\n" + to_chat(user, "[out_message.Join()]") + desc = "An electrode attached to a small circuit box that will display details of a solution. Can be toggled to provide a description of each of the reagents. The screen currently displays detected vol: [round(cont.volume, 0.01)] detected pH:[round(cont.reagents.ph, 0.1)]." diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm index c87a79cb4c7513..7400ac071d5cfd 100644 --- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm +++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm @@ -29,6 +29,8 @@ var/amount = 30 var/recharge_amount = 10 var/recharge_counter = 0 + ///If the UI has the pH meter shown + var/show_ph = TRUE var/mutable_appearance/beaker_overlay var/working_state = "dispenser_working" var/nopower_state = "dispenser_nopower" @@ -191,23 +193,26 @@ data["energy"] = cell.charge ? cell.charge * powerefficiency : "0" //To prevent NaN in the UI. data["maxEnergy"] = cell.maxcharge * powerefficiency data["isBeakerLoaded"] = beaker ? 1 : 0 + data["showpH"] = show_ph var/beakerContents[0] var/beakerCurrentVolume = 0 if(beaker && beaker.reagents && beaker.reagents.reagent_list.len) for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents.Add(list(list("name" = R.name, "volume" = R.volume))) // list in a list because Byond merges the first list... + beakerContents.Add(list(list("name" = R.name, "volume" = round(R.volume, 0.01), "pH" = R.ph, "purity" = R.purity))) // list in a list because Byond merges the first list... beakerCurrentVolume += R.volume data["beakerContents"] = beakerContents if (beaker) - data["beakerCurrentVolume"] = beakerCurrentVolume + data["beakerCurrentVolume"] = round(beakerCurrentVolume, 0.01) data["beakerMaxVolume"] = beaker.volume data["beakerTransferAmounts"] = beaker.possible_transfer_amounts + data["beakerCurrentpH"] = round(beaker.reagents.ph, 0.01) else data["beakerCurrentVolume"] = null data["beakerMaxVolume"] = null data["beakerTransferAmounts"] = null + data["beakerCurrentpH"] = null var/chemicals[0] var/is_hallucinating = FALSE @@ -219,7 +224,7 @@ var/chemname = temp.name if(is_hallucinating && prob(5)) chemname = "[pick_list_replacements("hallucination.json", "chemicals")]" - chemicals.Add(list(list("title" = chemname, "id" = ckey(temp.name)))) + chemicals.Add(list(list("title" = chemname, "id" = ckey(temp.name), "pH" = temp.ph, "pHCol" = ConvertpHToCol(temp.ph)))) data["chemicals"] = chemicals data["recipes"] = saved_recipes @@ -403,6 +408,32 @@ update_icon() return TRUE +//Converts the pH into a tgui readable color +/obj/machinery/chem_dispenser/proc/ConvertpHToCol(pH) + switch(pH) + if(-INFINITY to 1) + return "red" + if(1 to 2) + return "orange" + if(2 to 3) + return "average" + if(3 to 4) + return "yellow" + if(4 to 5) + return "olive" + if(5 to 6) + return "good" + if(6 to 8) + return "green" + if(8 to 9.5) + return "teal" + if(9.5 to 11) + return "blue" + if(11 to 12.5) + return "violet" + if(12.5 to INFINITY) + return "purple" + /obj/machinery/chem_dispenser/on_deconstruction() cell = null if(beaker) @@ -456,6 +487,7 @@ working_state = null nopower_state = null pass_flags = PASSTABLE + show_ph = FALSE dispensable_reagents = list( /datum/reagent/water, /datum/reagent/consumable/ice, diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm index 17d3a56fed7b51..b0de5e7e9ee5d5 100644 --- a/code/modules/reagents/chemistry/machinery/chem_heater.dm +++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm @@ -1,5 +1,17 @@ +#define ENABLE_FLASHING -1 + +///Tutorial states +#define TUT_NO_BUFFER 50 +#define TUT_START 1 +#define TUT_HAS_REAGENTS 2 +#define TUT_IS_ACTIVE 3 +#define TUT_IS_REACTING 4 +#define TUT_FAIL 4.5 +#define TUT_COMPLETE 5 +#define TUT_MISSING 10 + /obj/machinery/chem_heater - name = "chemical heater" + name = "reaction chamber" //Maybe this name is more accurate? density = TRUE icon = 'icons/obj/chemical.dmi' icon_state = "mixer0b" @@ -12,11 +24,29 @@ var/target_temperature = 300 var/heater_coefficient = 0.05 var/on = FALSE + var/dispense_volume = 1 + + //The list of active clients using this heater, so that we can update the UI on a reaction_step. I assume there are multiple clients possible. + var/list/ui_client_list + ///If the user has the tutorial enabled + var/tutorial_active = FALSE + ///What state we're at in the tutorial + var/tutorial_state = 0 +/obj/machinery/chem_heater/Initialize() + . = ..() + create_reagents(200, NO_REACT)//Lets save some calculations here + reagents.add_reagent(/datum/reagent/reaction_agent/basic_buffer, 20) + reagents.add_reagent(/datum/reagent/reaction_agent/acidic_buffer, 20) + //TODO: comsig reaction_start and reaction_end to enable/disable the UI autoupdater - this doesn't work presently as there's a hard divide between instant and processed reactions + /obj/machinery/chem_heater/Destroy() - QDEL_NULL(beaker) + if(beaker) + UnregisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP) + QDEL_NULL(beaker) return ..() + /obj/machinery/chem_heater/handle_atom_del(atom/A) . = ..() if(A == beaker) @@ -40,9 +70,11 @@ return FALSE if(beaker) try_put_in_hand(beaker, user) + UnregisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP) beaker = null if(new_beaker) beaker = new_beaker + RegisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP, .proc/on_reaction_step) update_icon() return TRUE @@ -58,14 +90,60 @@ /obj/machinery/chem_heater/process(delta_time) ..() + //Tutorial logics + if(tutorial_active) + switch(tutorial_state) + if(TUT_NO_BUFFER) + if(reagents.has_reagent(/datum/reagent/reaction_agent/basic_buffer, 5) && reagents.has_reagent(/datum/reagent/reaction_agent/acidic_buffer, 5)) + tutorial_state = TUT_START + + if(TUT_START) + if(!reagents.has_reagent(/datum/reagent/reaction_agent/basic_buffer, 5) || !reagents.has_reagent(/datum/reagent/reaction_agent/acidic_buffer, 5)) + tutorial_state = TUT_NO_BUFFER + return + if(beaker?.reagents.has_reagent(/datum/reagent/mercury, 10) || beaker?.reagents.has_reagent(/datum/reagent/chlorine, 10)) + tutorial_state = TUT_HAS_REAGENTS + + if(TUT_HAS_REAGENTS) + if(!(beaker?.reagents.has_reagent(/datum/reagent/mercury, 9)) || !(beaker?.reagents.has_reagent(/datum/reagent/chlorine, 9))) + tutorial_state = TUT_MISSING + return + if(beaker?.reagents.chem_temp > 374)//If they heated it up as asked + tutorial_state = TUT_IS_ACTIVE + target_temperature = 375 + beaker.reagents.chem_temp = 375 + + if(TUT_IS_ACTIVE) + if(!(beaker?.reagents.has_reagent(/datum/reagent/mercury)) || !(beaker?.reagents.has_reagent(/datum/reagent/chlorine))) //Slightly concerned that people might take ages to read and it'll react anyways + tutorial_state = TUT_MISSING + return + if(length(beaker?.reagents.reaction_list) == 1)//Only fudge numbers for our intentful reaction + beaker.reagents.chem_temp = 375 + + if(target_temperature >= 390) + tutorial_state = TUT_IS_REACTING + + if(TUT_IS_REACTING) + if(!(beaker?.reagents.has_reagent(/datum/reagent/mercury)) || !(beaker?.reagents.has_reagent(/datum/reagent/chlorine))) + tutorial_state = TUT_COMPLETE + + if(TUT_COMPLETE) + if(beaker?.reagents.has_reagent(/datum/reagent/consumable/failed_reaction)) + tutorial_state = TUT_FAIL + return + if(!beaker?.reagents.has_reagent(/datum/reagent/medicine/calomel)) + tutorial_state = TUT_MISSING + if(machine_stat & NOPOWER) return if(on) if(beaker?.reagents.total_volume) + if(beaker.reagents.is_reacting)//on_reaction_step() handles this + return //keep constant with the chemical acclimator please beaker.reagents.adjust_thermal_energy((target_temperature - beaker.reagents.chem_temp) * heater_coefficient * delta_time * SPECIFIC_HEAT_DEFAULT * beaker.reagents.total_volume) beaker.reagents.handle_reactions() - + /obj/machinery/chem_heater/attackby(obj/item/I, mob/user, params) if(default_deconstruction_screwdriver(user, "mixer0b", "mixer0b", I)) return @@ -83,33 +161,208 @@ updateUsrDialog() update_icon() return + + if(beaker) + if(istype(I, /obj/item/reagent_containers/dropper)) + var/obj/item/reagent_containers/dropper/D = I + D.afterattack(beaker, user, 1) + return + if(istype(I, /obj/item/reagent_containers/syringe)) + var/obj/item/reagent_containers/syringe/S = I + S.afterattack(beaker, user, 1) + return + return ..() /obj/machinery/chem_heater/on_deconstruction() replace_beaker() return ..() +///Forces a UI update every time a reaction step happens inside of the beaker it contains. This is so the UI is in sync with the reaction since it's important that the output matches the current conditions for pH adjustment and temperature. +/obj/machinery/chem_heater/proc/on_reaction_step(datum/reagents/holder, num_reactions, delta_time) + SIGNAL_HANDLER + if(on) + holder.adjust_thermal_energy((target_temperature - beaker.reagents.chem_temp) * heater_coefficient * delta_time * SPECIFIC_HEAT_DEFAULT * beaker.reagents.total_volume * (rand(8,11) * 0.1))//Give it a little wiggle room since we're actively reacting + for(var/ui_client in ui_client_list) + var/datum/tgui/ui = ui_client + if(!ui) + stack_trace("Warning: UI in UI client list is missing in [src] (chem_heater)") + remove_ui_client_list(ui) + continue + ui.send_update() + /obj/machinery/chem_heater/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "ChemHeater", name) ui.open() + add_ui_client_list(ui) + +/obj/machinery/chem_heater/ui_close(mob/user) + for(var/ui_client in ui_client_list) + var/datum/tgui/ui = ui_client + if(ui.user == user) + remove_ui_client_list(ui) + return ..() + +/* +*This adds an open ui client to the list - so that it can be force updated from reaction mechanisms. +* After adding it to the list, it enables a signal incase the ui is deleted - which will call a method to remove it from the list +* This is mostly to ensure we don't have defunct ui instances stored from any condition. +*/ +/obj/machinery/chem_heater/proc/add_ui_client_list(new_ui) + LAZYADD(ui_client_list, new_ui) + RegisterSignal(new_ui, COMSIG_PARENT_QDELETING, .proc/on_ui_deletion) + +///This removes an open ui instance from the ui list and deregsiters the signal +/obj/machinery/chem_heater/proc/remove_ui_client_list(old_ui) + UnregisterSignal(old_ui, COMSIG_PARENT_QDELETING) + LAZYREMOVE(ui_client_list, old_ui) + +///This catches a signal and uses it to delete the ui instance from the list +/obj/machinery/chem_heater/proc/on_ui_deletion(datum/tgui/source, force) + SIGNAL_HANDLER + remove_ui_client_list(source) -/obj/machinery/chem_heater/ui_data() +/obj/machinery/chem_heater/ui_assets() + . = ..() || list() + . += get_asset_datum(/datum/asset/simple/tutorial_advisors) + +/obj/machinery/chem_heater/ui_data(mob/user) var/data = list() data["targetTemp"] = target_temperature data["isActive"] = on data["isBeakerLoaded"] = beaker ? 1 : 0 data["currentTemp"] = beaker ? beaker.reagents.chem_temp : null - data["beakerCurrentVolume"] = beaker ? beaker.reagents.total_volume : null + data["beakerCurrentVolume"] = beaker ? round(beaker.reagents.total_volume, 0.01) : null data["beakerMaxVolume"] = beaker ? beaker.volume : null + data["currentpH"] = beaker ? round(beaker.reagents.ph, 0.01) : null + var/upgrade_level = heater_coefficient*10 + data["upgradeLevel"] = upgrade_level + + var/list/beaker_contents = list() + for(var/r in beaker?.reagents.reagent_list) + var/datum/reagent/reagent = r + beaker_contents.len++ + beaker_contents[length(beaker_contents)] = list("name" = reagent.name, "volume" = round(reagent.volume, 0.01)) + data["beakerContents"] = beaker_contents + + var/list/active_reactions = list() + var/flashing = 14 //for use with alertAfter - since there is no alertBefore, I set the after to 0 if true, or to the max value if false + for(var/_reaction in beaker?.reagents.reaction_list) + var/datum/equilibrium/equilibrium = _reaction + if(!length(beaker.reagents.reaction_list))//I'm not sure why when it explodes it causes the gui to fail (it's missing danger (?) ) + stack_trace("how is this happening??") + continue + if(!equilibrium.reaction.results)//Incase of no result reactions + continue + var/_reagent = equilibrium.reaction.results[1] + var/datum/reagent/reagent = beaker?.reagents.get_reagent(_reagent) //Reactions are named after their primary products + if(!reagent) + continue + var/overheat = FALSE + var/danger = FALSE + var/purity_alert = 2 //same as flashing + if(reagent.purity < equilibrium.reaction.purity_min) + purity_alert = ENABLE_FLASHING//Because 0 is seen as null + danger = TRUE + if(!(flashing == ENABLE_FLASHING))//So that the pH meter flashes for ANY reactions out of optimal + if(equilibrium.reaction.optimal_ph_min > beaker?.reagents.ph || equilibrium.reaction.optimal_ph_max < beaker?.reagents.ph) + flashing = ENABLE_FLASHING + if(equilibrium.reaction.is_cold_recipe) + if(equilibrium.reaction.overheat_temp > beaker?.reagents.chem_temp) + danger = TRUE + overheat = TRUE + else + if(equilibrium.reaction.overheat_temp < beaker?.reagents.chem_temp) + danger = TRUE + overheat = TRUE + if(equilibrium.reaction.reaction_flags & REACTION_COMPETITIVE) //We have a compeitive reaction - concatenate the results for the different reactions + for(var/entry in active_reactions) + if(entry["name"] == reagent.name) //If we have multiple reaction methods for the same result - combine them + entry["reactedVol"] = equilibrium.reacted_vol + entry["targetVol"] = round(equilibrium.target_vol, 1)//Use the first result reagent to name the reaction detected + entry["quality"] = (entry["quality"] + equilibrium.reaction_quality) /2 + continue + active_reactions.len++ + active_reactions[length(active_reactions)] = list("name" = reagent.name, "danger" = danger, "purityAlert" = purity_alert, "quality" = equilibrium.reaction_quality, "overheat" = overheat, "inverse" = reagent.inverse_chem_val, "minPure" = equilibrium.reaction.purity_min, "reactedVol" = equilibrium.reacted_vol, "targetVol" = round(equilibrium.target_vol, 1))//Use the first result reagent to name the reaction detected + data["activeReactions"] = active_reactions + data["isFlashing"] = flashing + + data["acidicBufferVol"] = reagents.get_reagent_amount(/datum/reagent/reaction_agent/acidic_buffer) + data["basicBufferVol"] = reagents.get_reagent_amount(/datum/reagent/reaction_agent/basic_buffer) + data["dispenseVolume"] = dispense_volume + + data["tutorialMessage"] = null + //Tutorial output + if(tutorial_active) + switch(tutorial_state) + if(TUT_NO_BUFFER)//missing buffer + data["tutorialMessage"] = {"It looks like you’re a little low on buffers, here’s how to make more: + +Acidic buffer: 2 parts Sodium + 2 parts Hydrogen + 2 parts Ethanol + 2 parts Water + +Basic buffer: 3 parts Ammonia + 2 parts Chlorine + 2 parts Hydrogen + 2 parts Oxygen + +Heat either up to speed up the reaction. + +When the reactions are done, refill your chamber by pressing the Draw all buttons, to the right of the respective volume indicators. + +To continue with the tutorial, fill both of your acidic and alkaline volumes to at least 5u."} + if(TUT_START)//Default start + data["tutorialMessage"] = {"Hello and welcome to the exciting world of chemistry! This help option will teach you the basic of reactions by guiding you through a calomel reaction. + +For the majority of reactions, the overheat temperature is 900K, and the pH range is 5-9, though it's always worth looking up the ranges as these are changing. Calomel is no different. + +To continue the tutorial, insert a beaker with at least 10u mercury and 10u chlorine added."} + if(TUT_HAS_REAGENTS) //10u Hg and Cl + data["tutorialMessage"] = {"Good job! You'll see that at present this isn't reacting. That's because this reaction needs a minimum temperature of 375K. + +For the most part the hotter your reaction is, the faster it will react when it’s past it’s minimum temperature. But be careful to not heat it too much! "If your reaction is slow, your temperature is too low"! + +When you’re ready, set your temperature to 375K and heat up the beaker to that amount."} + if(TUT_IS_ACTIVE) //heat 375K + data["tutorialMessage"] = {"Great! You should see your reaction slowly progressing. + +Notice the pH dial on the right; the sum pH should be slowly drifting towards the left on the dial. How pure your solution is at the end depends on how well you keep your reaction within the optimal pH range. The dial will flash if any of the present reactions are outside their optimal. "If you're getting sludge, give your pH a nudge"! + +In a moment, we’ll increase the temperature so that our rate is faster. It’s up to you to keep your pH within the limits, so keep an eye on that dial, and get ready to add basic buffer using the injection button to the left of the volume indicator. + +To continue set your target temperature to 390K."} + if(TUT_IS_REACTING) //Heat 390K + data["tutorialMessage"] = "Stay focused on the reaction! You can do it!" + if(TUT_FAIL) //Sludge + data["tutorialMessage"] = "Ah, unfortunately your purity was too low and the reaction fell apart into errant sludge. Don't worry, you can always try again! Be careful though, for some reactions, failing isn't nearly as forgiving." + if(TUT_COMPLETE) //Complete + var/datum/reagent/calo = beaker?.reagents.has_reagent(/datum/reagent/medicine/calomel) + if(!calo) + tutorial_state = TUT_COMPLETE + return + switch(calo.purity) + if(-INFINITY to 0.25) + data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. That's pretty close to the fail purity of 0.15 - which can often make some reactions explode. This chem will invert into Toxic sludge when ingested by another person, and will not cause of calomel's normal effects. Sneaky, huh?" + if(0.25 to 0.6) + data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. Normally, this reaction will resolve above 0.7 without intervention. Are you praticing impure reactions? The lower you go, the higher change you have of getting dangerous effects during a reaction. In some more dangerous reactions, you're riding a fine line between death and an inverse chem, don't forget you can always chill your reaction to give yourself more time to manage it!" + if(0.6 to 0.75) + data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. Normally, this reaction will resolve above 0.7 without intervention. Did you maybe add too much basic buffer and go past 9? If you like - you're welcome to try again. Just double press the help button!" + if(0.75 to 0.85) + data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. You got pretty close to optimal! Feel free to try again if you like by double pressing the help button." + if(0.75 to 0.99) + data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. You got pretty close to optimal! Feel free to try again if you like by double pressing the help button, but this is a respectable purity." + if(0.99 to 1) + data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. Your calomel is as pure as they come! You've mastered the basics of chemistry, but there's plenty more challenges on the horizon. Good luck!" + user.client?.give_award(/datum/award/achievement/misc/chemistry_tut, user) + data["tutorialMessage"] += "\n\nDid you notice that your temperature increased past 390K while reacting too? That's because this reaction is exothermic (heat producing), so for some reactions you might have to adjust your target to compensate. Oh, and you can check your purity by researching and printing off a chemical analyzer at the medlathe (for now)!" + if(TUT_MISSING) //Missing + data["tutorialMessage"] = "Uh oh, something went wrong. Did you take the beaker out, heat it up too fast, or have other things in the beaker? Try restarting the tutorial by double pressing the help button." - var/beakerContents[0] - if(beaker) - for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents.Add(list(list("name" = R.name, "volume" = R.volume))) // list in a list because Byond merges the first list... - data["beakerContents"] = beakerContents return data /obj/machinery/chem_heater/ui_act(action, params) @@ -128,6 +381,103 @@ if(.) target_temperature = clamp(target, 0, 1000) if("eject") - on = FALSE + //Eject doesn't turn it off, so you can preheat for beaker swapping replace_beaker(usr) . = TRUE + if("acidBuffer") + var/target = params["target"] + if(text2num(target) != null) + target = text2num(target) + . = TRUE + if(.) + move_buffer("acid", target) + if("basicBuffer") + var/target = params["target"] + if(text2num(target) != null) + target = text2num(target) //Because the input is flipped + . = TRUE + if(.) + move_buffer("basic", target) + if("disp_vol") + var/target = params["target"] + if(text2num(target) != null) + target = text2num(target) //Because the input is flipped + . = TRUE + if(.) + dispense_volume = target + if("help") + tutorial_active = !tutorial_active + if(tutorial_active) + tutorial_state = 1 + return + tutorial_state = 0 + //Refresh window size + ui_close(usr) + ui_interact(usr, null) + + +///Moves a type of buffer from the heater to the beaker, or vice versa +/obj/machinery/chem_heater/proc/move_buffer(buffer_type, volume) + if(!beaker) + say("No beaker found!") + return + if(buffer_type == "acid") + if(volume < 0) + var/datum/reagent/acid_reagent = beaker.reagents.get_reagent(/datum/reagent/reaction_agent/acidic_buffer) + if(!acid_reagent) + say("Unable to find acidic buffer in beaker to draw from! Please insert a beaker containing acidic buffer.") + return + var/datum/reagent/acid_reagent_heater = reagents.get_reagent(/datum/reagent/reaction_agent/acidic_buffer) + var/cur_vol = 0 + if(acid_reagent_heater) + cur_vol = acid_reagent_heater.volume + volume = 100 - cur_vol + beaker.reagents.trans_id_to(src, acid_reagent.type, volume)//negative because we're going backwards + return + //We must be positive here + reagents.trans_id_to(beaker, /datum/reagent/reaction_agent/acidic_buffer, dispense_volume) + return + + if(buffer_type == "basic") + if(volume < 0) + var/datum/reagent/basic_reagent = beaker.reagents.get_reagent(/datum/reagent/reaction_agent/basic_buffer) + if(!basic_reagent) + say("Unable to find basic buffer in beaker to draw from! Please insert a beaker containing basic buffer.") + return + var/datum/reagent/basic_reagent_heater = reagents.get_reagent(/datum/reagent/reaction_agent/basic_buffer) + var/cur_vol = 0 + if(basic_reagent_heater) + cur_vol = basic_reagent_heater.volume + volume = 100 - cur_vol + beaker.reagents.trans_id_to(src, basic_reagent.type, volume)//negative because we're going backwards + return + reagents.trans_id_to(beaker, /datum/reagent/reaction_agent/basic_buffer, dispense_volume) + return + + +/obj/machinery/chem_heater/proc/get_purity_color(datum/equilibrium/equilibrium) + var/_reagent = equilibrium.reaction.results[1] + var/datum/reagent/reagent = equilibrium.holder.get_reagent(_reagent) + switch(reagent.purity) + if(1 to INFINITY) + return "blue" + if(0.8 to 1) + return "green" + if(reagent.inverse_chem_val to 0.8) + return "olive" + if(equilibrium.reaction.purity_min to reagent.inverse_chem_val) + return "orange" + if(-INFINITY to equilibrium.reaction.purity_min) + return "red" + +//Has a lot of buffer and is upgraded +/obj/machinery/chem_heater/debug + name = "Debug Reaction Chamber" + desc = "Now with even more buffers!" + +/obj/machinery/chem_heater/debug/Initialize() + . = ..() + reagents.maximum_volume = 2000 + reagents.add_reagent(/datum/reagent/reaction_agent/basic_buffer, 980) + reagents.add_reagent(/datum/reagent/reaction_agent/acidic_buffer, 980) + heater_coefficient = 0.4 //hack way to upgrade diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm index 627ef2a3a22292..425d111c1d51f2 100644 --- a/code/modules/reagents/chemistry/machinery/chem_master.dm +++ b/code/modules/reagents/chemistry/machinery/chem_master.dm @@ -195,7 +195,7 @@ /obj/machinery/chem_master/ui_data(mob/user) var/list/data = list() data["isBeakerLoaded"] = beaker ? 1 : 0 - data["beakerCurrentVolume"] = beaker ? beaker.reagents.total_volume : null + data["beakerCurrentVolume"] = beaker ? round(beaker.reagents.total_volume, 0.01) : null data["beakerMaxVolume"] = beaker ? beaker.volume : null data["mode"] = mode data["condi"] = condi @@ -213,13 +213,13 @@ var/beaker_contents[0] if(beaker) for(var/datum/reagent/R in beaker.reagents.reagent_list) - beaker_contents.Add(list(list("name" = R.name, "id" = ckey(R.name), "volume" = R.volume))) // list in a list because Byond merges the first list... + beaker_contents.Add(list(list("name" = R.name, "id" = ckey(R.name), "volume" = round(R.volume, 0.01)))) // list in a list because Byond merges the first list... data["beakerContents"] = beaker_contents var/buffer_contents[0] if(reagents.total_volume) for(var/datum/reagent/N in reagents.reagent_list) - buffer_contents.Add(list(list("name" = N.name, "id" = ckey(N.name), "volume" = N.volume))) // ^ + buffer_contents.Add(list(list("name" = N.name, "id" = ckey(N.name), "volume" = round(N.volume, 0.01)))) // ^ data["bufferContents"] = buffer_contents //Calculated at init time as it never changes @@ -261,9 +261,15 @@ if (!beaker) return FALSE if (to_container == "buffer") + var/datum/reagent/R = beaker.reagents.get_reagent(reagent) + if(!check_reactions(R, beaker.reagents)) + return FALSE beaker.reagents.trans_id_to(src, reagent, amount) return TRUE if (to_container == "beaker" && mode) + var/datum/reagent/R = reagents.get_reagent(reagent) + if(!check_reactions(R, reagents)) + return FALSE reagents.trans_id_to(beaker, reagent, amount) return TRUE return FALSE @@ -324,7 +330,7 @@ "Maximum [vol_each_max] units per item.", "How many units to fill?", vol_each_max)) - vol_each = clamp(vol_each, 0, vol_each_max) + vol_each = round(clamp(vol_each, 0, vol_each_max), 0.01) if(vol_each <= 0) return FALSE // Get item name @@ -420,7 +426,7 @@ state = "Gas" var/const/P = 3 //The number of seconds between life ticks var/T = initial(R.metabolization_rate) * (60 / P) - analyze_vars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold)) + analyze_vars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold), "pH" = initial(R.ph)) screen = "analyze" return TRUE @@ -569,6 +575,25 @@ container.lefthand_file = style["lefthand_file"] container.righthand_file = style["righthand_file"] + +//Checks to see if the target reagent is being created (reacting) and if so prevents transfer +//Only prevents reactant from being moved so that people can still manlipulate input reagents +/obj/machinery/chem_master/proc/check_reactions(datum/reagent/reagent, datum/reagents/holder) + if(!reagent) + return FALSE + var/canMove = TRUE + for(var/e in holder.reaction_list) + var/datum/equilibrium/E = e + if(E.reaction.reaction_flags & REACTION_COMPETITIVE) + continue + for(var/result in E.reaction.required_reagents) + var/datum/reagent/R = result + if(R == reagent.type) + canMove = FALSE + if(!canMove) + say("Cannot move arrested chemical reaction reagents!") + return canMove + /** * Machine that allows to identify and separate reagents in fitting container * as well as to create new containers with separated reagents in it. diff --git a/code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm b/code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm new file mode 100644 index 00000000000000..30bdf93ef11703 --- /dev/null +++ b/code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm @@ -0,0 +1,129 @@ +/* +* A debug chem tester that will process through all recipies automatically and try to react them. +* Highlights low purity reactions and and reactions that don't happen +*/ +/obj/machinery/chem_recipe_debug + name = "chemical reaction tester" + density = TRUE + icon = 'icons/obj/chemical.dmi' + icon_state = "HPLC" + use_power = IDLE_POWER_USE + idle_power_usage = 40 + resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE + ///List of every reaction in the game kept locally for easy access + var/list/cached_reactions = list() + ///What index in the cached_reactions we're in + var/index = 1 + ///If the machine is currently processing through the list + var/processing = FALSE + ///Final output that highlights all of the reactions with inoptimal purity/voolume at base + var/problem_string + ///Final output that highlights all of the reactions with inoptimal purity/voolume at base + var/impure_string + ///The count of reactions that resolve between 1 - 0.9 purity + var/minorImpurity + ///The count of reactions that resolve below 0.9 purity + var/majorImpurity + ///If we failed to react this current chem so use a lower temp + var/failed = 0 + +///Create reagents datum +/obj/machinery/chem_recipe_debug/Initialize() + . = ..() + create_reagents(9000)//I want to make sure everything fits + +///Enable the machine +/obj/machinery/chem_recipe_debug/attackby(obj/item/I, mob/user, params) + . = .() + if(processing) + say("currently processing reaction [index]: [cached_reactions[index]] of [cached_reactions.len]") + return + say("Starting processing") + setup_reactions() + begin_processing() + +///Enable the machine +/obj/machinery/chem_recipe_debug/AltClick(mob/living/user) + . = ..() + if(processing) + say("currently processing reaction [index]: [cached_reactions[index]] of [cached_reactions.len]") + return + say("Starting processing") + setup_reactions() + begin_processing() + +///Resets the index, and creates the cached_reaction list from all possible reactions +/obj/machinery/chem_recipe_debug/proc/setup_reactions() + cached_reactions = list() + for(var/V in GLOB.chemical_reactions_list) + if(is_type_in_list(GLOB.chemical_reactions_list[V], cached_reactions)) + continue + cached_reactions += GLOB.chemical_reactions_list[V] + reagents.clear_reagents() + index = 1 + processing = TRUE + +/* +* The main loop that sets up, creates and displays results from a reaction +* warning: this code is a hot mess +*/ +/obj/machinery/chem_recipe_debug/process(delta_time) + if(processing == FALSE) + setup_reactions() + if(reagents.is_reacting == TRUE) + return + if(index >= cached_reactions.len) + say("Completed testing, missing reactions products (may have exploded) are:") + say("[problem_string]") + say("Problem with results are:") + say("[impure_string]") + say("Reactions with minor impurity: [minorImpurity], reactions with major impurity: [majorImpurity]") + processing = FALSE + end_processing() + if(reagents.reagent_list) + say("Reaction completed for [cached_reactions[index]] final temperature = [reagents.chem_temp], ph = [reagents.ph].") + var/datum/chemical_reaction/C = cached_reactions[index] + for(var/R in C.results) + var/datum/reagent/R2 = reagents.get_reagent(R) + if(!R2) + say("Unable to find product [R] in holder after reaction! reagents found are:") + for(var/R3 in reagents.reagent_list) + say("[R3]") + var/obj/item/reagent_containers/glass/beaker/bluespace/B = new /obj/item/reagent_containers/glass/beaker/bluespace(loc) + reagents.trans_to(B) + B.name = "[cached_reactions[index]]" + if(failed > 0) + problem_string += "[cached_reactions[index]] Unable to find product [R] in holder after reaction! index:[index]\n" + failed++ + continue + say("Reaction has a product [R] [R2.volume]u purity of [R2.purity]") + if(R2.purity < 0.9) + impure_string += "Reaction [cached_reactions[index]] has a product [R] [R2.volume]u purity of [R2.purity] index:[index]\n" + majorImpurity++ + else if (R2.purity < 1) + impure_string += "Reaction [cached_reactions[index]] has a product [R] [R2.volume]u purity of [R2.purity] index:[index]\n" + minorImpurity++ + if(R2.volume < C.results[R]) + impure_string += "Reaction [cached_reactions[index]] has a product [R] [R2.volume]u purity of [R2.purity] index:[index]\n" + reagents.clear_reagents() + if(failed > 1) + index++ + failed = 0 + if(failed == 0) + index++ + var/datum/chemical_reaction/C = cached_reactions[index] + if(!C) + say("Unable to find reaction on index: [index]") + for(var/R in C.required_reagents) + reagents.add_reagent(R, C.required_reagents[R]*20) + for(var/cat in C.required_catalysts) + reagents.add_reagent(cat, C.required_catalysts[cat]) + if(failed == 0) + reagents.chem_temp = C.optimal_temp + if(failed == 1) + reagents.chem_temp = C.required_temp+25 + failed++ + say("Reacting [cached_reactions[index]] starting pH: [reagents.ph] index [index] of [cached_reactions.len]") + if(C.reaction_flags & REACTION_INSTANT) + say("This reaction is instant") + diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm index 480f5f949f2aff..59d04f3d8f02b3 100644 --- a/code/modules/reagents/chemistry/reagents.dm +++ b/code/modules/reagents/chemistry/reagents.dm @@ -44,6 +44,12 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent()) var/current_cycle = 0 ///pretend this is moles var/volume = 0 + /// pH of the reagent + var/ph = 7 + ///Purity of the reagent - for use with internal reaction mechanics only. Use below (creation_purity) if you're writing purity effects into a reagent's use mechanics. + var/purity = 1 + ///the purity of the reagent on creation (i.e. when it's added to a mob and it's purity split it into 2 chems; the purity of the resultant chems are kept as 1, this tracks what the purity was before that) + var/creation_purity = 1 /// color it looks in containers etc var/color = "#000000" // rgb: 0, 0, 0 /// can this reagent be synthesized? (for example: odysseus syringe gun) @@ -76,6 +82,17 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent()) var/list/reagent_removal_skip_list = list() ///The set of exposure methods this penetrates skin with. var/penetrates_skin = VAPOR + /// See fermi_readme.dm REAGENT_DEAD_PROCESS, REAGENT_DONOTSPLIT, REAGENT_INVISIBLE, REAGENT_SNEAKYNAME, REAGENT_SPLITRETAINVOL + var/chemical_flags = NONE + ///impure chem values (see fermi_readme.dm for more details on impure/inverse/failed mechanics): + /// What chemical path is made when metabolised as a function of purity + var/impure_chem = /datum/reagent/impurity + /// If the impurity is below 0.5, replace ALL of the chem with inverse_chem upon metabolising + var/inverse_chem_val = 0.25 + /// What chem is metabolised when purity is below inverse_chem_val + var/inverse_chem = /datum/reagent/impurity/toxic + ///what chem is made at the end of a reaction IF the purity is below the recipies purity_min at the END of a reaction only + var/failed_chem = /datum/reagent/consumable/failed_reaction /datum/reagent/New() SHOULD_CALL_PARENT(TRUE) @@ -128,13 +145,19 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent()) return holder.remove_reagent(type, metabolization_rate * M.metabolism_efficiency) //By default it slowly disappears. +/* +Used to run functions before a reagent is transfered. Returning TRUE will block the transfer attempt. +Primarily used in reagents/reaction_agents +*/ +/datum/reagent/proc/intercept_reagents_transfer(datum/reagents/target) + return FALSE + ///Called after a reagent is transfered /datum/reagent/proc/on_transfer(atom/A, methods=TOUCH, trans_volume) return - /// Called when this reagent is first added to a mob -/datum/reagent/proc/on_mob_add(mob/living/L) +/datum/reagent/proc/on_mob_add(mob/living/L, amount) return /// Called when this reagent is removed while inside a mob @@ -149,6 +172,15 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent()) /datum/reagent/proc/on_mob_end_metabolize(mob/living/L) return +/// Called when a reagent is inside of a mob when they are dead +/datum/reagent/proc/on_mob_dead(mob/living/carbon/C) + if(!(chemical_flags & REAGENT_DEAD_PROCESS)) + return + current_cycle++ + if(length(reagent_removal_skip_list)) + return + holder.remove_reagent(type, metabolization_rate * C.metabolism_efficiency) + /// Called by [/datum/reagents/proc/conditional_update_move] /datum/reagent/proc/on_move(mob/M) return @@ -158,7 +190,7 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent()) return /// Called when two reagents of the same are mixing. -/datum/reagent/proc/on_merge(data) +/datum/reagent/proc/on_merge(data, amount) return /// Called by [/datum/reagents/proc/conditional_update] diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm index 588101a9b19396..1bafe4fe107b75 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm @@ -13,6 +13,7 @@ nutriment_factor = 0 taste_description = "alcohol" metabolization_rate = 0.5 * REAGENTS_METABOLISM + ph = 7.33 var/boozepwr = 65 //Higher numbers equal higher hardness, higher hardness equals more intense alcohol poisoning /* @@ -88,6 +89,7 @@ All effects don't start immediately, but rather get worse over time; the rate is taste_description = "piss water" glass_name = "glass of beer" glass_desc = "A freezing pint of beer." + ph = 4 // Beer is a chemical composition of alcohol and various other things. It's a garbage nutrient but hey, it's still one. Also alcohol is bad, mmmkay? @@ -104,6 +106,7 @@ All effects don't start immediately, but rather get worse over time; the rate is taste_description = "dish water" glass_name = "glass of light beer" glass_desc = "A freezing pint of watery light beer." + ph = 5 /datum/reagent/consumable/ethanol/beer/maltliquor name = "Malt Liquor" @@ -112,6 +115,7 @@ All effects don't start immediately, but rather get worse over time; the rate is taste_description = "sweet corn beer and the hood life" glass_name = "glass of malt liquor" glass_desc = "A freezing pint of malt liquor." + ph = 4.8 /datum/reagent/consumable/ethanol/beer/green name = "Green Beer" @@ -121,6 +125,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_icon_state = "greenbeerglass" glass_name = "glass of green beer" glass_desc = "A freezing pint of green beer. Festive." + ph = 6 /datum/reagent/consumable/ethanol/beer/green/on_mob_life(mob/living/carbon/M) @@ -140,6 +145,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "glass of RR coffee liquor" glass_desc = "DAMN, THIS THING LOOKS ROBUST!" shot_glass_icon_state = "shotglasscream" + ph = 6 /datum/reagent/consumable/ethanol/kahlua/on_mob_life(mob/living/carbon/M) M.dizziness = max(0,M.dizziness-5) @@ -160,6 +166,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "glass of whiskey" glass_desc = "The silky, smokey whiskey goodness inside the glass makes the drink look very classy." shot_glass_icon_state = "shotglassbrown" + ph = 4.5 /datum/reagent/consumable/ethanol/whiskey/kong name = "Kong" @@ -284,6 +291,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "glass of vodka" glass_desc = "The glass contain wodka. Xynta." shot_glass_icon_state = "shotglassclear" + ph = 8.1 /datum/reagent/consumable/ethanol/vodka/on_mob_life(mob/living/carbon/M) M.radiation = max(M.radiation-2,0) @@ -316,6 +324,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_icon_state = "threemileislandglass" glass_name = "Three Mile Island Ice Tea" glass_desc = "A glass of this is sure to prevent a meltdown." + ph = 3.5 /datum/reagent/consumable/ethanol/threemileisland/on_mob_life(mob/living/carbon/M) M.set_drugginess(50) @@ -330,6 +339,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_icon_state = "ginvodkaglass" glass_name = "glass of gin" glass_desc = "A crystal clear glass of Griffeater gin." + ph = 6.9 /datum/reagent/consumable/ethanol/rum name = "Rum" @@ -341,6 +351,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "glass of rum" glass_desc = "Now you want to Pray for a pirate suit, don't you?" shot_glass_icon_state = "shotglassbrown" + ph = 6.5 /datum/reagent/consumable/ethanol/tequila name = "Tequila" @@ -352,6 +363,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "glass of tequila" glass_desc = "Now all that's missing is the weird colored shades!" shot_glass_icon_state = "shotglassgold" + ph = 4 /datum/reagent/consumable/ethanol/vermouth name = "Vermouth" @@ -363,6 +375,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "glass of vermouth" glass_desc = "You wonder why you're even drinking this straight." shot_glass_icon_state = "shotglassclear" + ph = 3.25 /datum/reagent/consumable/ethanol/wine name = "Wine" @@ -374,6 +387,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "glass of wine" glass_desc = "A very classy looking drink." shot_glass_icon_state = "shotglassred" + ph = 3.45 /datum/reagent/consumable/ethanol/wine/on_merge(data) . = ..() @@ -395,6 +409,7 @@ All effects don't start immediately, but rather get worse over time; the rate is boozepwr = 45 quality = DRINK_FANTASTIC taste_description = "scaley sweetness" + ph = 3 /datum/reagent/consumable/ethanol/grappa name = "Grappa" @@ -405,6 +420,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_icon_state = "grappa" glass_name = "glass of grappa" glass_desc = "A fine drink originally made to prevent waste by using the leftovers from winemaking." + ph = 3.5 /datum/reagent/consumable/ethanol/amaretto name = "Amaretto" @@ -427,6 +443,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "glass of cognac" glass_desc = "Damn, you feel like some kind of French aristocrat just by holding this." shot_glass_icon_state = "shotglassbrown" + ph = 3.5 /datum/reagent/consumable/ethanol/absinthe name = "Absinthe" @@ -470,6 +487,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_icon_state = "aleglass" glass_name = "glass of ale" glass_desc = "A freezing pint of delicious Ale." + ph = 4.5 /datum/reagent/consumable/ethanol/goldschlager name = "Goldschlager" @@ -531,6 +549,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "glass of patron" glass_desc = "Drinking patron in the bar, with all the subpar ladies." shot_glass_icon_state = "shotglassclear" + ph = 4.5 /datum/reagent/consumable/ethanol/gintonic name = "Gin and Tonic" @@ -542,6 +561,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_icon_state = "gintonicglass" glass_name = "Gin and Tonic" glass_desc = "A mild but still great cocktail. Drink up, like a true Englishman." + ph = 3 /datum/reagent/consumable/ethanol/rum_coke name = "Rum and Coke" @@ -553,6 +573,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_icon_state = "whiskeycolaglass" glass_name = "Rum and Coke" glass_desc = "The classic go-to of space-fratboys." + ph = 4 /datum/reagent/consumable/ethanol/cuba_libre name = "Cuba Libre" @@ -743,6 +764,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_desc = "Heavy, hot and strong. Just like the Iron fist of the LAW." overdose_threshold = 40 var/datum/brain_trauma/special/beepsky/B + ph = 2 /datum/reagent/consumable/ethanol/beepsky_smash/on_mob_metabolize(mob/living/carbon/M) if(HAS_TRAIT(M, TRAIT_ALCOHOL_TOLERANCE)) @@ -1656,6 +1678,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Bastion Bourbon" glass_desc = "If you're feeling low, count on the buttery flavor of our own bastion bourbon." shot_glass_icon_state = "shotglassgreen" + ph = 4 /datum/reagent/consumable/ethanol/bastion_bourbon/on_mob_metabolize(mob/living/L) var/heal_points = 10 @@ -1986,6 +2009,7 @@ All effects don't start immediately, but rather get worse over time; the rate is can_synth = FALSE var/list/names = list("null fruit" = 1) //Names of the fruits used. Associative list where name is key, value is the percentage of that fruit. var/list/tastes = list("bad coding" = 1) //List of tastes. See above. + ph = 4 /datum/reagent/consumable/ethanol/fruit_wine/on_new(list/data) names = data["names"] @@ -1995,6 +2019,7 @@ All effects don't start immediately, but rather get worse over time; the rate is generate_data_info(data) /datum/reagent/consumable/ethanol/fruit_wine/on_merge(list/data, amount) + ..() var/diff = (amount/volume) if(diff < 1) color = BlendRGB(color, data["color"], diff/2) //The percentage difference over two, so that they take average if equal. @@ -2078,7 +2103,6 @@ All effects don't start immediately, but rather get worse over time; the rate is flavor += english_list(secondary_tastes) taste_description = flavor - /datum/reagent/consumable/ethanol/champagne //How the hell did we not have champagne already!? name = "Champagne" description = "A sparkling wine known for its ability to strike fast and hard." diff --git a/code/modules/reagents/chemistry/reagents/catalyst_reagents.dm b/code/modules/reagents/chemistry/reagents/catalyst_reagents.dm new file mode 100644 index 00000000000000..6e4e2b56c7ed00 --- /dev/null +++ b/code/modules/reagents/chemistry/reagents/catalyst_reagents.dm @@ -0,0 +1,48 @@ +///These alter reaction conditions while they're in the beaker +/datum/reagent/catalyst_agent + name ="Catalyst agent" + ///The typepath of the reagent they that they affect + var/target_reagent_type + ///The minimumvolume required in the beaker for them to have an effect + var/min_volume = 10 + ///The value in which the associated type is modified + var/modifier = 1 + +/datum/reagent/catalyst_agent/proc/consider_catalyst(datum/equilibrium/equilibrium) + for(var/_product in equilibrium.reaction.results) + if(ispath(_product, target_reagent_type)) + return TRUE + return FALSE + +/datum/reagent/catalyst_agent/speed + name ="Speed catalyst agent" + +/datum/reagent/catalyst_agent/speed/consider_catalyst(datum/equilibrium/equilibrium) + . = ..() + if(.) + equilibrium.speed_mod = creation_purity*modifier //So a purity 1 = the modifier, and a purity 0 = the inverse modifier. For this we don't want a negative speed_mod (I have no idea what happens if we do) + equilibrium.time_deficit += (creation_purity)*(0.05 * modifier) //give the reaction a little boost too (40% faster) + +/datum/reagent/catalyst_agent/ph + name ="pH catalyst agent" + +/datum/reagent/catalyst_agent/ph/consider_catalyst(datum/equilibrium/equilibrium) + . = ..() + if(.) + equilibrium.h_ion_mod = ((creation_purity-0.5)*2)*modifier //So a purity 1 = the modifier, and a purity 0 = the inverse modifier + +/datum/reagent/catalyst_agent/temperature + name = "Temperature catalyst agent" + +/datum/reagent/catalyst_agent/temperature/consider_catalyst(datum/equilibrium/equilibrium) + . = ..() + if(.) + equilibrium.thermic_mod = ((creation_purity-0.5)*2)*modifier //So a purity 1 = the modifier, and a purity 0 = the inverse modifier + +///These affect medicines +/datum/reagent/catalyst_agent/speed/medicine + name = "Palladium synthate catalyst" + target_reagent_type = /datum/reagent/medicine + modifier = 2 + ph = 2 //drift towards acidity + color = "#b1b1b1" diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm index 6e0bb7c6e372a8..cae3737431ec83 100644 --- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm @@ -12,6 +12,7 @@ glass_icon_state = "glass_orange" glass_name = "glass of orange juice" glass_desc = "Vitamins! Yay!" + ph = 3.3 /datum/reagent/consumable/orangejuice/on_mob_life(mob/living/carbon/M) if(M.getOxyLoss() && prob(30)) @@ -42,6 +43,7 @@ glass_icon_state = "glass_green" glass_name = "glass of lime juice" glass_desc = "A glass of sweet-sour lime juice." + ph = 2.2 /datum/reagent/consumable/limejuice/on_mob_life(mob/living/carbon/M) if(M.getToxLoss() && prob(20)) @@ -84,6 +86,7 @@ description = "The sweet juice of an apple, fit for all ages." color = "#ECFF56" // rgb: 236, 255, 86 taste_description = "apples" + ph = 3.2 // ~ 2.7 -> 3.7 /datum/reagent/consumable/poisonberryjuice name = "Poison Berry Juice" @@ -116,6 +119,7 @@ glass_icon_state = "lemonglass" glass_name = "glass of lemon juice" glass_desc = "Sour..." + ph = 2 /datum/reagent/consumable/banana name = "Banana Juice" @@ -199,6 +203,7 @@ glass_icon_state = "glass_white" glass_name = "glass of milk" glass_desc = "White and nutritious goodness!" + ph = 6.5 // Milk is good for humans, but bad for plants. The sugars cannot be used by plants, and the milk fat harms growth. Not shrooms though. I can't deal with this now... /datum/reagent/consumable/milk/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index 0137d78c98189a..3ebae322563655 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -13,6 +13,7 @@ description = "An illegal chemical compound used as drug." color = "#60A584" // rgb: 96, 165, 132 overdose_threshold = 30 + ph = 9 /datum/reagent/drug/space_drugs/on_mob_life(mob/living/carbon/M) M.set_drugginess(15) @@ -43,6 +44,7 @@ trippy = FALSE overdose_threshold=15 metabolization_rate = 0.125 * REAGENTS_METABOLISM + ph = 8 //Nicotine is used as a pesticide IRL. /datum/reagent/drug/nicotine/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) @@ -77,6 +79,7 @@ color = "#FA00C8" overdose_threshold = 20 addiction_threshold = 10 + ph = 10 /datum/reagent/drug/crank/on_mob_life(mob/living/carbon/M) if(prob(5)) @@ -126,6 +129,7 @@ color = "#0064B4" overdose_threshold = 20 addiction_threshold = 15 + ph = 9 /datum/reagent/drug/krokodil/on_mob_life(mob/living/carbon/M) @@ -178,6 +182,7 @@ overdose_threshold = 20 addiction_threshold = 10 metabolization_rate = 0.75 * REAGENTS_METABOLISM + ph = 5 /datum/reagent/drug/methamphetamine/on_mob_metabolize(mob/living/L) ..() @@ -263,6 +268,7 @@ addiction_threshold = 10 taste_description = "salt" // because they're bathsalts? var/datum/brain_trauma/special/psychotic_brawling/bath_salts/rage + ph = 8.2 /datum/reagent/drug/bath_salts/on_mob_metabolize(mob/living/L) ..() diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index d271c35184525b..061a53496845e8 100755 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -13,6 +13,10 @@ taste_mult = 4 var/nutriment_factor = 1 * REAGENTS_METABOLISM var/quality = 0 //affects mood, typically higher for mixed drinks with more complex recipes + impure_chem = /datum/reagent/water + inverse_chem_val = 0.1 + inverse_chem = /datum/reagent/water + failed_chem = /datum/reagent/consumable/nutriment /datum/reagent/consumable/on_mob_life(mob/living/carbon/M) current_cycle++ @@ -74,6 +78,7 @@ data = counterlist_normalise(supplied_data) /datum/reagent/consumable/nutriment/on_merge(list/newdata, newvolume) + . = ..() if(!islist(newdata) || !newdata.len) return @@ -268,6 +273,7 @@ description = "A special oil that noticeably chills the body. Extracted from chilly peppers and slimes." color = "#8BA6E9" // rgb: 139, 166, 233 taste_description = "mint" + ph = 13 //HMM! I wonder /datum/reagent/consumable/frostoil/on_mob_life(mob/living/carbon/M) var/cooling = 0 @@ -316,6 +322,7 @@ color = "#B31008" // rgb: 179, 16, 8 taste_description = "scorching agony" penetrates_skin = NONE + ph = 7.4 /datum/reagent/consumable/condensedcapsaicin/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() @@ -391,6 +398,7 @@ color = "#E700E7" // rgb: 231, 0, 231 metabolization_rate = 0.2 * REAGENTS_METABOLISM taste_description = "mushroom" + ph = 11 /datum/reagent/drug/mushroomhallucinogen/on_mob_life(mob/living/carbon/M) if(!M.slurring) @@ -634,6 +642,7 @@ description = "A blinding substance extracted from certain onions." color = "#c0c9a0" taste_description = "bitterness" + ph = 5 /datum/reagent/consumable/tearjuice/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() @@ -677,6 +686,7 @@ description = "An ichor, derived from a certain mushroom, makes for a bad time." color = "#1d043d" taste_description = "bitter mushroom" + ph = 12 /datum/reagent/consumable/entpoly/on_mob_life(mob/living/carbon/M) if(current_cycle >= 10) @@ -697,6 +707,7 @@ description = "A stimulating ichor which causes luminescent fungi to grow on the skin. " color = "#b5a213" taste_description = "tingling mushroom" + ph = 11.2 //Lazy list of mobs affected by the luminosity of this reagent. var/list/mobs_affected @@ -730,6 +741,7 @@ color = "#d3a308" nutriment_factor = 3 * REAGENTS_METABOLISM taste_description = "fruity mushroom" + ph = 10.4 /datum/reagent/consumable/vitfro/on_mob_life(mob/living/carbon/M) if(prob(80)) @@ -744,6 +756,7 @@ nutriment_factor = 5 * REAGENTS_METABOLISM color = "#eef442" // rgb: 238, 244, 66 taste_description = "mournful honking" + ph = 9.2 /datum/reagent/consumable/liquidelectricity @@ -795,6 +808,7 @@ quality = FOOD_AMAZING taste_mult = 100 can_synth = FALSE + ph = 6.1 /datum/reagent/consumable/nutriment/peptides name = "Peptides" diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents.dm new file mode 100644 index 00000000000000..92cea81f3df2f2 --- /dev/null +++ b/code/modules/reagents/chemistry/reagents/impure_reagents.dm @@ -0,0 +1,39 @@ +//Reagents produced by metabolising/reacting fermichems inoptimally, i.e. inverse_chems or impure_chems +//Inverse = Splitting +//Invert = Whole conversion + +//Causes slight liver damage, and that's it. +/datum/reagent/impurity + name = "Chemical Isomers" + description = "Impure chemical isomers made from inoptimal reactions. Causes mild liver damage" + //by default, it will stay hidden on splitting, but take the name of the source on inverting. Cannot be fractioned down either if the reagent is somehow isolated. + chemical_flags = REAGENT_INVISIBLE | REAGENT_SNEAKYNAME | REAGENT_DONOTSPLIT + ph = 3 + overdose_threshold = 0 //So that they're shown as a problem (?) + +/datum/reagent/impurity/on_mob_life(mob/living/carbon/C) + var/obj/item/organ/liver/L = C.getorganslot(ORGAN_SLOT_LIVER) + if(!L)//Though, lets be safe + C.adjustToxLoss(1, FALSE)//Incase of no liver! + return ..() + C.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.5*REM) + return ..() + +/datum/reagent/impurity/toxic + name = "Toxic sludge" + description = "Toxic chemical isomers made from impure reactions. Causes toxin damage" + ph = 2 + +/datum/reagent/impurity/toxic/on_mob_life(mob/living/carbon/C) + C.adjustToxLoss(1, FALSE) + return ..() + +//technically not a impure chem, but it's here because it can only be made with a failed impure reaction +/datum/reagent/consumable/failed_reaction + name = "Viscous sludge" + description = "A off smelling sludge that's created when a reaction gets too impure." + nutriment_factor = -1 + quality = -1 + ph = 1.5 + taste_description = "an awful, strongly chemical taste" + color = "#270d03" diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index f9c760893ace15..874e8f5dc9698d 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -19,6 +19,7 @@ /datum/reagent/medicine/leporazine name = "Leporazine" description = "Leporazine will effectively regulate a patient's body temperature, ensuring it never leaves safe levels." + ph = 8.4 color = "#DB90C6" /datum/reagent/medicine/leporazine/on_mob_life(mob/living/carbon/M) @@ -109,6 +110,7 @@ name = "Synaptizine" description = "Increases resistance to stuns as well as reducing drowsiness and hallucinations." color = "#FF00FF" + ph = 4 /datum/reagent/medicine/synaptizine/on_mob_life(mob/living/carbon/M) M.drowsyness = max(M.drowsyness-5, 0) @@ -129,6 +131,7 @@ name = "Diphen-Synaptizine" description = "Reduces drowsiness, hallucinations, and Histamine from body." color = "#EC536D" // rgb: 236, 83, 109 + ph = 5.2 /datum/reagent/medicine/synaphydramine/on_mob_life(mob/living/carbon/M) M.drowsyness = max(M.drowsyness-5, 0) @@ -147,6 +150,7 @@ description = "A chemical mixture with almost magical healing powers. Its main limitation is that the patient's body temperature must be under 270K for it to metabolise correctly." color = "#0000C8" taste_description = "blue" + ph = 11 /datum/reagent/medicine/cryoxadone/on_mob_life(mob/living/carbon/M) var/power = -0.00003 * (M.bodytemperature ** 2) + 3 @@ -175,6 +179,7 @@ description = "A chemical that derives from Cryoxadone. It specializes in healing clone damage, but nothing else. Requires very cold temperatures to properly metabolize, and metabolizes quicker than cryoxadone." color = "#3D3DC6" taste_description = "muscle" + ph = 13 metabolization_rate = 1.5 * REAGENTS_METABOLISM /datum/reagent/medicine/clonexadone/on_mob_life(mob/living/carbon/M) @@ -190,6 +195,7 @@ description = "A mixture of cryoxadone and slime jelly, that apparently inverses the requirement for its activation." color = "#f7832a" taste_description = "spicy jelly" + ph = 12 /datum/reagent/medicine/pyroxadone/on_mob_life(mob/living/carbon/M) if(M.bodytemperature > BODYTEMP_HEAT_DAMAGE_LIMIT) @@ -222,6 +228,7 @@ reagent_state = SOLID color = "#669900" // rgb: 102, 153, 0 overdose_threshold = 30 + ph = 12.2 taste_description = "fish" /datum/reagent/medicine/rezadone/on_mob_life(mob/living/carbon/M) @@ -253,6 +260,7 @@ description = "Spaceacillin will prevent a patient from conventionally spreading any diseases they are currently infected with. Also reduces infection in serious burns." color = "#E1F2E6" metabolization_rate = 0.1 * REAGENTS_METABOLISM + ph = 8.1 //Goon Chems. Ported mainly from Goonstation. Easily mixable (or not so easily) and provide a variety of effects. @@ -263,6 +271,7 @@ color = "#1E8BFF" metabolization_rate = 0.5 * REAGENTS_METABOLISM overdose_threshold = 25 + ph = 10.7 /datum/reagent/medicine/oxandrolone/on_mob_life(mob/living/carbon/M) if(M.getFireLoss() > 25) @@ -289,6 +298,7 @@ var/last_added = 0 var/maximum_reachable = BLOOD_VOLUME_NORMAL - 10 //So that normal blood regeneration can continue with salglu active var/extra_regen = 0.25 // in addition to acting as temporary blood, also add this much to their actual blood per tick + ph = 5.5 /datum/reagent/medicine/salglu_solution/on_mob_life(mob/living/carbon/M) if(last_added) @@ -326,6 +336,7 @@ reagent_state = LIQUID color = "#6D6374" metabolization_rate = 0.4 * REAGENTS_METABOLISM + ph = 2.6 /datum/reagent/medicine/mine_salve/on_mob_life(mob/living/carbon/C) C.hal_screwyhud = SCREWYHUD_HEALTHY @@ -367,6 +378,7 @@ metabolization_rate = 0.25 * REAGENTS_METABOLISM overdose_threshold = 30 var/healing = 0.5 + ph = 2 /datum/reagent/medicine/omnizine/on_mob_life(mob/living/carbon/M) M.adjustToxLoss(-healing*REM, 0) @@ -397,6 +409,7 @@ color = "#19C832" metabolization_rate = 0.5 * REAGENTS_METABOLISM taste_description = "acid" + ph = 1.5 /datum/reagent/medicine/calomel/on_mob_life(mob/living/carbon/M) for(var/datum/reagent/toxin/R in M.reagents.reagent_list) @@ -412,6 +425,7 @@ reagent_state = LIQUID color = "#BAA15D" metabolization_rate = 2 * REAGENTS_METABOLISM + ph = 12 //It's a reducing agent /datum/reagent/medicine/potass_iodide/on_mob_life(mob/living/carbon/M) if(M.radiation > 0) @@ -424,6 +438,7 @@ reagent_state = LIQUID color = "#E6FFF0" metabolization_rate = 0.5 * REAGENTS_METABOLISM + ph = 1 //One of the best buffers, NEVERMIND! /datum/reagent/medicine/pen_acid/on_mob_life(mob/living/carbon/M) M.radiation -= max(M.radiation-RAD_MOB_SAFE, 0)/50 @@ -441,6 +456,7 @@ color = "#D2D2D2" metabolization_rate = 0.5 * REAGENTS_METABOLISM overdose_threshold = 25 + ph = 2.1 /datum/reagent/medicine/sal_acid/on_mob_life(mob/living/carbon/M) if(M.getBruteLoss() > 25) @@ -462,6 +478,7 @@ reagent_state = LIQUID color = "#00FFFF" metabolization_rate = 0.25 * REAGENTS_METABOLISM + ph = 2 /datum/reagent/medicine/salbutamol/on_mob_life(mob/living/carbon/M) M.adjustOxyLoss(-3*REM, 0) @@ -478,6 +495,7 @@ metabolization_rate = 0.5 * REAGENTS_METABOLISM overdose_threshold = 30 addiction_threshold = 25 + ph = 12 /datum/reagent/medicine/ephedrine/on_mob_metabolize(mob/living/L) ..() @@ -571,6 +589,7 @@ reagent_state = LIQUID color = "#64FFE6" metabolization_rate = 0.5 * REAGENTS_METABOLISM + ph = 11.5 /datum/reagent/medicine/diphenhydramine/on_mob_life(mob/living/carbon/M) if(prob(10)) @@ -587,6 +606,7 @@ metabolization_rate = 0.5 * REAGENTS_METABOLISM overdose_threshold = 30 addiction_threshold = 25 + ph = 8.96 /datum/reagent/medicine/morphine/on_mob_metabolize(mob/living/L) ..() @@ -656,6 +676,7 @@ color = "#404040" //oculine is dark grey, inacusiate is light grey metabolization_rate = 0.25 * REAGENTS_METABOLISM taste_description = "dull toxin" + ph = 10 /datum/reagent/medicine/oculine/on_mob_life(mob/living/carbon/M) var/obj/item/organ/eyes/eyes = M.getorganslot(ORGAN_SLOT_EYES) @@ -680,6 +701,7 @@ name = "Inacusiate" description = "Rapidly repairs damage to the patient's ears to cure deafness, assuming the source of said deafness isn't from genetic mutations, chronic deafness, or a total defecit of ears." //by "chronic" deafness, we mean people with the "deaf" quirk color = "#606060" // ditto + ph = 2 /datum/reagent/medicine/inacusiate/on_mob_life(mob/living/carbon/M) var/obj/item/organ/ears/ears = M.getorganslot(ORGAN_SLOT_EARS) @@ -693,6 +715,7 @@ color = "#1D3535" //slightly more blue, like epinephrine metabolization_rate = 0.25 * REAGENTS_METABOLISM overdose_threshold = 35 + ph = 12 /datum/reagent/medicine/atropine/on_mob_life(mob/living/carbon/M) if(M.health <= M.crit_threshold) @@ -721,6 +744,7 @@ color = "#D2FFFA" metabolization_rate = 0.25 * REAGENTS_METABOLISM overdose_threshold = 30 + ph = 10.2 /datum/reagent/medicine/epinephrine/on_mob_metabolize(mob/living/carbon/M) ..() @@ -769,6 +793,7 @@ metabolization_rate = 1.25 * REAGENTS_METABOLISM taste_description = "magnets" harmful = TRUE + ph = 0.5 // FEED ME SEYMOUR @@ -810,6 +835,7 @@ name = "Mannitol" description = "Efficiently restores brain damage." color = "#A0A0A0" //mannitol is light grey, neurine is lighter grey + ph = 10.4 /datum/reagent/medicine/mannitol/on_mob_life(mob/living/carbon/C) C.adjustOrganLoss(ORGAN_SLOT_BRAIN, -2*REM) @@ -841,6 +867,7 @@ description = "Removes jitteriness and restores genetic defects." color = "#5096C8" taste_description = "acid" + ph = 2 /datum/reagent/medicine/mutadone/on_mob_life(mob/living/carbon/M) M.jitteriness = 0 @@ -854,6 +881,7 @@ description = "Purges alcoholic substance from the patient's body and eliminates its side effects." color = "#00B4C8" taste_description = "raw egg" + ph = 4 /datum/reagent/medicine/antihol/on_mob_life(mob/living/carbon/M) M.dizziness = 0 @@ -874,6 +902,7 @@ color = "#78008C" metabolization_rate = 0.5 * REAGENTS_METABOLISM overdose_threshold = 60 + ph = 8.7 /datum/reagent/medicine/stimulants/on_mob_metabolize(mob/living/L) ..() @@ -910,6 +939,7 @@ reagent_state = LIQUID color = "#FFFFF0" metabolization_rate = 0.5 * REAGENTS_METABOLISM + ph = 6.7 /datum/reagent/medicine/insulin/on_mob_life(mob/living/carbon/M) if(M.AdjustSleeping(-20)) @@ -924,6 +954,7 @@ description = "Stabilizes the breathing of patients. Good for those in critical condition." reagent_state = LIQUID color = "#A4D8D8" + ph = 8.5 /datum/reagent/medicine/inaprovaline/on_mob_life(mob/living/carbon/M) if(M.losebreath >= 5) @@ -961,6 +992,7 @@ reagent_state = SOLID color = "#555555" overdose_threshold = 30 + ph = 11 /datum/reagent/medicine/syndicate_nanites/on_mob_life(mob/living/carbon/M) M.adjustBruteLoss(-5*REM, 0) //A ton of healing - this is a 50 telecrystal investment. @@ -985,6 +1017,7 @@ color = "#FFAF00" metabolization_rate = 0.4 //Math is based on specific metab rate so we want this to be static AKA if define or medicine metab rate changes, we want this to stay until we can rework calculations. overdose_threshold = 25 + ph = 11 /datum/reagent/medicine/earthsblood/on_mob_life(mob/living/carbon/M) if(current_cycle <= 25) //10u has to be processed before u get into THE FUN ZONE @@ -1036,6 +1069,7 @@ reagent_state = LIQUID color = "#27870a" metabolization_rate = 0.4 * REAGENTS_METABOLISM + ph = 4.3 /datum/reagent/medicine/haloperidol/on_mob_life(mob/living/carbon/M) for(var/datum/reagent/drug/R in M.reagents.reagent_list) @@ -1155,6 +1189,7 @@ overdose_threshold = 20 // with the random effects this might be awesome or might kill you at less than 10u (extensively tested) taste_description = "salt" // it actually does taste salty var/overdose_progress = 0 // to track overdose progress + ph = 7.89 /datum/reagent/medicine/modafinil/on_mob_metabolize(mob/living/M) ADD_TRAIT(M, TRAIT_SLEEPIMMUNE, type) @@ -1217,6 +1252,7 @@ color = "#07E79E" metabolization_rate = 0.25 * REAGENTS_METABOLISM overdose_threshold = 30 + ph = 9.12 /datum/reagent/medicine/psicodine/on_mob_metabolize(mob/living/L) ..() diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 32a138cf5a6298..dfeb61eb7fa4da 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -10,6 +10,7 @@ glass_desc = "Are you sure this is tomato juice?" shot_glass_icon_state = "shotglassred" penetrates_skin = NONE + ph = 7.4 /datum/reagent/blood/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message=TRUE, touch_protection=0) . = ..() @@ -95,6 +96,7 @@ taste_description = "gross iron" shot_glass_icon_state = "shotglassred" material = /datum/material/meat + ph = 7.45 /datum/reagent/vaccine //data must contain virus type @@ -217,6 +219,7 @@ glass_name = "glass of holy water" glass_desc = "A glass of holy water." self_consuming = TRUE //divine intervention won't be limited by the lack of a liver + ph = 7.5 //God is alkaline /datum/reagent/water/holywater/on_mob_metabolize(mob/living/L) ..() @@ -297,6 +300,7 @@ glass_name = "glass of oxygenated water" glass_desc = "The father of all refreshments. Surely it tastes great, right?" shot_glass_icon_state = "shotglassclear" + ph = 6.2 /* * Water reaction to turf @@ -323,6 +327,7 @@ taste_description = "suffering" metabolization_rate = 2.5 * REAGENTS_METABOLISM //1u/tick penetrates_skin = TOUCH|VAPOR + ph = 6.5 /datum/reagent/fuel/unholywater/on_mob_life(mob/living/carbon/M) if(iscultist(M)) @@ -347,6 +352,7 @@ name = "Hell Water" description = "YOUR FLESH! IT BURNS!" taste_description = "burning" + ph = 0.1 /datum/reagent/hellwater/on_mob_life(mob/living/carbon/M) M.set_fire_stacks(min(5, M.fire_stacks + 3)) @@ -389,6 +395,7 @@ metabolization_rate = 10 * REAGENTS_METABOLISM // very fast, so it can be applied rapidly. But this changes on an overdose overdose_threshold = 11 //Slightly more than one un-nozzled spraybottle. taste_description = "sour oranges" + ph = 5 /datum/reagent/spraytan/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE) . = ..() @@ -696,6 +703,7 @@ color = "#202040" // rgb: 20, 20, 40 metabolization_rate = 0.25 * REAGENTS_METABOLISM taste_description = "bitterness" + ph = 10 /datum/reagent/serotrotium/on_mob_life(mob/living/carbon/M) if(ishuman(M)) @@ -709,6 +717,7 @@ reagent_state = GAS color = "#808080" // rgb: 128, 128, 128 taste_mult = 0 // oderless and tasteless + ph = 9.2//It's acutally a huge range and very dependant on the chemistry but ph is basically a made up var in it's implementation anyways /datum/reagent/oxygen/expose_turf(turf/open/exposed_turf, reac_volume) @@ -724,6 +733,7 @@ reagent_state = SOLID color = "#6E3B08" // rgb: 110, 59, 8 taste_description = "metal" + ph = 5.5 /datum/reagent/copper/expose_obj(obj/exposed_obj, reac_volume) . = ..() @@ -754,6 +764,7 @@ reagent_state = GAS color = "#808080" // rgb: 128, 128, 128 taste_mult = 0 + ph = 0.1//Now I'm stuck in a trap of my own design. Maybe I should make -ve phes? (not 0 so I don't get div/0 errors) /datum/reagent/potassium name = "Potassium" @@ -782,6 +793,7 @@ reagent_state = SOLID color = "#BF8C00" // rgb: 191, 140, 0 taste_description = "rotten eggs" + ph = 4.5 /datum/reagent/carbon name = "Carbon" @@ -789,6 +801,7 @@ reagent_state = SOLID color = "#1C1300" // rgb: 30, 20, 0 taste_description = "sour chalk" + ph = 5 /datum/reagent/carbon/expose_turf(turf/exposed_turf, reac_volume) . = ..() @@ -805,6 +818,7 @@ reagent_state = GAS color = "#FFFB89" //pale yellow? let's make it light gray taste_description = "chlorine" + ph = 7.4 // You're an idiot for thinking that one of the most corrosive and deadly gasses would be beneficial @@ -829,6 +843,7 @@ reagent_state = GAS color = "#808080" // rgb: 128, 128, 128 taste_description = "acid" + ph = 2 // You're an idiot for thinking that one of the most corrosive and deadly gasses would be beneficial /datum/reagent/fluorine/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) @@ -850,6 +865,7 @@ reagent_state = SOLID color = "#808080" // rgb: 128, 128, 128 taste_description = "salty metal" + ph = 11.6 /datum/reagent/phosphorus name = "Phosphorus" @@ -857,6 +873,7 @@ reagent_state = SOLID color = "#832828" // rgb: 131, 40, 40 taste_description = "vinegar" + ph = 6.5 // Phosphoric salts are beneficial though. And even if the plant suffers, in the long run the tray gets some nutrients. The benefit isn't worth that much. /datum/reagent/phosphorus/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) @@ -872,6 +889,7 @@ reagent_state = SOLID color = "#808080" // rgb: 128, 128, 128 taste_description = "metal" + ph = 11.3 /datum/reagent/lithium/on_mob_life(mob/living/carbon/M) if(!HAS_TRAIT(M, TRAIT_IMMOBILIZED) && !isspaceturf(M.loc)) @@ -885,12 +903,14 @@ description = "Glycerol is a simple polyol compound. Glycerol is sweet-tasting and of low toxicity." color = "#D3B913" taste_description = "sweetness" + ph = 9 /datum/reagent/space_cleaner/sterilizine name = "Sterilizine" description = "Sterilizes wounds in preparation for surgery." color = "#D0EFEE" // space cleaner but lighter taste_description = "bitterness" + ph = 10.5 /datum/reagent/space_cleaner/sterilizine/expose_mob(mob/living/carbon/exposed_carbon, methods=TOUCH, reac_volume) . = ..() @@ -909,6 +929,7 @@ material = /datum/material/iron color = "#606060" //pure iron? let's make it violet of course + ph = 6 /datum/reagent/iron/on_mob_life(mob/living/carbon/C) if(C.blood_volume < BLOOD_VOLUME_NORMAL) @@ -938,6 +959,7 @@ color = "#5E9964" //this used to be silver, but liquid uranium can still be green and it's more easily noticeable as uranium like this so why bother? taste_description = "the inside of a reactor" var/irradiation_level = 1 + ph = 4 material = /datum/material/uranium /datum/reagent/uranium/on_mob_life(mob/living/carbon/M) @@ -971,6 +993,7 @@ taste_description = "the colour blue and regret" irradiation_level = 2*REM material = null + ph = 10 /datum/reagent/uranium/radium/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) . = ..() @@ -985,6 +1008,7 @@ color = "#0000CC" taste_description = "fizzling blue" material = /datum/material/bluespace + ph = 12 /datum/reagent/bluespace/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() @@ -1016,6 +1040,7 @@ color = "#A8A8A8" // rgb: 168, 168, 168 taste_mult = 0 material = /datum/material/glass + ph = 10 /datum/reagent/fuel name = "Welding fuel" @@ -1026,6 +1051,7 @@ glass_name = "glass of welder fuel" glass_desc = "Unless you're an industrial tool, this is probably not safe for consumption." penetrates_skin = NONE + ph = 4 /datum/reagent/fuel/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume)//Splashing people with welding fuel to make them easy to ignite! . = ..() @@ -1045,6 +1071,7 @@ reagent_weight = 0.6 //so it sprays further penetrates_skin = NONE var/clean_types = CLEAN_WASH + ph = 5.5 /datum/reagent/space_cleaner/expose_obj(obj/exposed_obj, reac_volume) . = ..() @@ -1076,6 +1103,7 @@ metabolization_rate = 1.5 * REAGENTS_METABOLISM taste_description = "acid" penetrates_skin = VAPOR + ph = 2 /datum/reagent/space_cleaner/ez_clean/on_mob_life(mob/living/carbon/M) M.adjustBruteLoss(3.33) @@ -1095,6 +1123,7 @@ color = "#ADB5DB" //i hate default violets and 'crypto' keeps making me think of cryo so it's light blue now metabolization_rate = 1.5 * REAGENTS_METABOLISM taste_description = "sourness" + ph = 11.9 /datum/reagent/cryptobiolin/on_mob_life(mob/living/carbon/M) M.Dizzy(1) @@ -1106,6 +1135,7 @@ description = "Impedrezene is a narcotic that impedes one's ability by slowing down the higher brain cell functions." color = "#E07DDD" // pink = happy = dumb taste_description = "numbness" + ph = 9.1 /datum/reagent/impedrezene/on_mob_life(mob/living/carbon/M) M.jitteriness = max(M.jitteriness-5,0) @@ -1150,6 +1180,7 @@ can_synth = FALSE taste_description = "slime" penetrates_skin = NONE + ph = 11 /datum/reagent/fungalspores/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE, touch_protection = 0) . = ..() @@ -1163,6 +1194,7 @@ taste_description = "goo" can_synth = FALSE //special orange man request penetrates_skin = NONE + ph = 11 /datum/reagent/snail/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE, touch_protection = 0) . = ..() @@ -1174,6 +1206,7 @@ description = "A perfluoronated sulfonic acid that forms a foam when mixed with water." color = "#9E6B38" // rgb: 158, 107, 56 taste_description = "metal" + ph = 11 /datum/reagent/foaming_agent// Metal foaming agent. This is lithium hydride. Add other recipes (e.g. LiH + H2O -> LiOH + H2) eventually. name = "Foaming agent" @@ -1181,6 +1214,7 @@ reagent_state = SOLID color = "#664B63" // rgb: 102, 75, 99 taste_description = "metal" + ph = 11.5 /datum/reagent/smart_foaming_agent //Smart foaming agent. Functions similarly to metal foam, but conforms to walls. name = "Smart foaming agent" @@ -1188,6 +1222,7 @@ reagent_state = SOLID color = "#664B63" // rgb: 102, 75, 99 taste_description = "metal" + ph = 11.8 /datum/reagent/ammonia name = "Ammonia" @@ -1195,6 +1230,7 @@ reagent_state = GAS color = "#404030" // rgb: 64, 64, 48 taste_description = "mordant" + ph = 11.6 /datum/reagent/ammonia/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) . = ..() @@ -1210,6 +1246,7 @@ description = "A secondary amine, mildly corrosive." color = "#604030" // rgb: 96, 64, 48 taste_description = "iron" + ph = 12 // This is more bad ass, and pests get hurt by the corrosive nature of it, not the plant. The new trade off is it culls stability. /datum/reagent/diethylamine/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) @@ -1227,6 +1264,7 @@ description = "A gas commonly produced by burning carbon fuels. You're constantly producing this in your lungs." color = "#B0B0B0" // rgb : 192, 192, 192 taste_description = "something unknowable" + ph = 6 /datum/reagent/carbondioxide/expose_turf(turf/open/exposed_turf, reac_volume) if(istype(exposed_turf)) @@ -1241,6 +1279,7 @@ metabolization_rate = 1.5 * REAGENTS_METABOLISM color = "#808080" taste_description = "sweetness" + ph = 5.8 /datum/reagent/nitrous_oxide/expose_turf(turf/open/exposed_turf, reac_volume) . = ..() @@ -1270,6 +1309,7 @@ metabolization_rate = REAGENTS_METABOLISM * 0.5 // Because stimulum/nitryl/freon/hypernoblium are handled through gas breathing, metabolism must be lower for breathcode to keep up color = "E1A116" taste_description = "sourness" + ph = 1.8 /datum/reagent/stimulum/on_mob_metabolize(mob/living/L) ..() @@ -1293,6 +1333,7 @@ metabolization_rate = REAGENTS_METABOLISM * 0.5 // Because stimulum/nitryl/freon/hypernoblium are handled through gas breathing, metabolism must be lower for breathcode to keep up color = "90560B" taste_description = "burning" + ph = 2 /datum/reagent/nitryl/on_mob_metabolize(mob/living/L) ..() @@ -1401,18 +1442,21 @@ colorname = "red" color = "#DA0000" // red random_color_list = list("#FC7474") + ph = 0.5 /datum/reagent/colorful_reagent/powder/orange name = "Orange Powder" colorname = "orange" color = "#FF9300" // orange random_color_list = list("#FF9300") + ph = 2 /datum/reagent/colorful_reagent/powder/yellow name = "Yellow Powder" colorname = "yellow" color = "#FFF200" // yellow random_color_list = list("#FFF200") + ph = 5 /datum/reagent/colorful_reagent/powder/green name = "Green Powder" @@ -1425,12 +1469,14 @@ colorname = "blue" color = "#00B7EF" // blue random_color_list = list("#71CAE5") + ph = 10 /datum/reagent/colorful_reagent/powder/purple name = "Purple Powder" colorname = "purple" color = "#DA00FF" // purple random_color_list = list("#BD8FC4") + ph = 13 /datum/reagent/colorful_reagent/powder/invisible name = "Invisible Powder" @@ -1494,6 +1540,7 @@ color = "#000000" // RBG: 0, 0, 0 var/tox_prob = 0 taste_description = "plant food" + ph = 3 /datum/reagent/plantnutriment/on_mob_life(mob/living/carbon/M) if(prob(tox_prob)) @@ -1583,6 +1630,7 @@ color = "#2D2D2D" taste_description = "bitterness" taste_mult = 1.5 + ph = 1.5 /datum/reagent/stable_plasma/on_mob_life(mob/living/carbon/C) C.adjustPlasma(10) @@ -1594,6 +1642,7 @@ reagent_state = LIQUID color = "#BC8A00" taste_description = "metal" + ph = 4.5 /datum/reagent/carpet name = "Carpet" @@ -1698,6 +1747,7 @@ reagent_state = LIQUID color = "#D35415" taste_description = "chemicals" + ph = 7.8 /datum/reagent/pentaerythritol name = "Pentaerythritol" @@ -1735,6 +1785,7 @@ reagent_state = LIQUID color = "#E7EA91" taste_description = "acid" + ph = 5.5 /datum/reagent/ash name = "Ash" @@ -1742,6 +1793,7 @@ reagent_state = LIQUID color = "#515151" taste_description = "ash" + ph = 6.5 // Ash is also used IRL in gardening, as a fertilizer enhancer and weed killer /datum/reagent/ash/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) @@ -1875,6 +1927,7 @@ reagent_state = LIQUID color = "#60A584" // rgb: 96, 165, 132 taste_description = "cool salt" + ph = 11.2 // Saltpetre is used for gardening IRL, to simplify highly, it speeds up growth and strengthens plants /datum/reagent/saltpetre/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) @@ -1892,6 +1945,7 @@ reagent_state = LIQUID color = "#FFFFD6" // very very light yellow taste_description = "acid" + ph = 11.9 /datum/reagent/drying_agent name = "Drying agent" @@ -1899,6 +1953,7 @@ reagent_state = LIQUID color = "#A70FFF" taste_description = "dryness" + ph = 10.7 /datum/reagent/drying_agent/expose_turf(turf/open/exposed_turf, reac_volume) . = ..() @@ -1965,6 +2020,7 @@ description = "Royal Bee Jelly, if injected into a Queen Space Bee said bee will split into two bees." color = "#00ff80" taste_description = "strange honey" + ph = 3 /datum/reagent/royal_bee_jelly/on_mob_life(mob/living/carbon/M) if(prob(2)) @@ -1985,6 +2041,7 @@ metabolization_rate = INFINITY can_synth = FALSE taste_description = "brains" + ph = 0.5 /datum/reagent/romerol/expose_mob(mob/living/carbon/human/exposed_mob, methods=TOUCH, reac_volume) . = ..() @@ -2041,6 +2098,7 @@ description = "the petroleum based components of plastic." color = "#f7eded" taste_description = "plastic" + ph = 6 /datum/reagent/glitter name = "generic glitter" @@ -2079,6 +2137,7 @@ color = "#AAAAAA55" taste_description = "water" metabolization_rate = 0.25 * REAGENTS_METABOLISM + ph = 15 /datum/reagent/pax/on_mob_metabolize(mob/living/L) ..() @@ -2181,6 +2240,7 @@ var/yuck_cycle = 0 //! The `current_cycle` when puking starts. /datum/reagent/yuck/on_mob_add(mob/living/L) + . = ..() if(HAS_TRAIT(L, TRAIT_NOHUNGER)) //they can't puke holder.del_reagent(type) @@ -2299,6 +2359,7 @@ /datum/reagent/gravitum/on_mob_add(mob/living/L) L.AddElement(/datum/element/forced_gravity, 0) //0 is the gravity, and in this case weightless + return ..() /datum/reagent/gravitum/on_mob_end_metabolize(mob/living/L) L.RemoveElement(/datum/element/forced_gravity, 0) @@ -2371,3 +2432,4 @@ M.adjustOxyLoss(2, FALSE) M.adjustBruteLoss(2, FALSE) ..() + diff --git a/code/modules/reagents/chemistry/reagents/reaction_agents_reagents.dm b/code/modules/reagents/chemistry/reagents/reaction_agents_reagents.dm new file mode 100644 index 00000000000000..26aa514e7acb68 --- /dev/null +++ b/code/modules/reagents/chemistry/reagents/reaction_agents_reagents.dm @@ -0,0 +1,124 @@ +/datum/reagent/reaction_agent + name = "reaction agent" + description = "Hello! I am a bugged reagent. Please report me for my crimes. Thank you!!" + +/datum/reagent/reaction_agent/intercept_reagents_transfer(datum/reagents/target, amount) + if(!target) + return FALSE + if(target.flags & NO_REACT) + return FALSE + if(target.has_reagent(/datum/reagent/stabilizing_agent)) + return FALSE + if(LAZYLEN(target.reagent_list) == 0) + return FALSE + if(LAZYLEN(target.reagent_list) == 1) + if(target.has_reagent(type)) //Allow dispensing into self + return FALSE + return TRUE + +/datum/reagent/reaction_agent/acidic_buffer + name = "Strong acidic buffer" + description = "This reagent will consume itself and move the pH of a beaker towards acidity when added to another." + color = "#fbc314" + ph = 0 + ///The strength of the buffer where (volume/holder.total_volume)*strength. So for 1u added to 50u the ph will decrease by 0.5 + var/strength = 30 + +//Consumes self on addition and shifts ph +/datum/reagent/reaction_agent/acidic_buffer/intercept_reagents_transfer(datum/reagents/target, amount) + . = ..() + if(!.) + return + if(target.ph <= ph) + target.my_atom.audible_message("The beaker froths as the buffer is added, to no effect.") + playsound(target.my_atom, 'sound/chemistry/bufferadd.ogg', 50, TRUE) + holder.remove_reagent(type, amount)//Remove from holder because it's not transfered + return + var/ph_change = -((amount/target.total_volume)*strength) + target.adjust_all_reagents_ph(ph_change, ph, 14) + target.my_atom.audible_message("The beaker fizzes as the ph changes!") + playsound(target.my_atom, 'sound/chemistry/bufferadd.ogg', 50, TRUE) + holder.remove_reagent(type, amount) + +/datum/reagent/reaction_agent/basic_buffer + name = "Strong basic buffer" + description = "This reagent will consume itself and move the pH of a beaker towards alkalinity when added to another." + color = "#3853a4" + ph = 14 + ///The strength of the buffer where (volume/holder.total_volume)*strength. So for 1u added to 50u the ph will increase by 0.5 + var/strength = 30 + +/datum/reagent/reaction_agent/basic_buffer/intercept_reagents_transfer(datum/reagents/target, amount) + . = ..() + if(!.) + return + if(target.ph >= ph) + target.my_atom.audible_message("The beaker froths as the buffer is added, to no effect.") + playsound(target.my_atom, 'sound/chemistry/bufferadd.ogg', 50, TRUE) + holder.remove_reagent(type, amount)//Remove from holder because it's not transfered + return + var/ph_change = (amount/target.total_volume)*strength + target.adjust_all_reagents_ph(ph_change, 0, ph) + target.my_atom.audible_message("The beaker froths as the ph changes!") + playsound(target.my_atom, 'sound/chemistry/bufferadd.ogg', 50, TRUE) + holder.remove_reagent(type, amount) + +//purity testor/reaction agent prefactors + +/datum/reagent/prefactor_a + name = "Interim product alpha" + description = "This reagent is a prefactor to the purity tester reagent, and will react with stable plasma to create it" + color = "#bafa69" + +/datum/reagent/prefactor_b + name = "Interim product beta" + description = "This reagent is a prefactor to the reaction speed agent reagent, and will react with stable plasma to create it" + color = "#8a3aa9" + +/datum/reagent/reaction_agent/purity_tester + name = "Purity tester" + description = "This reagent will consume itself and violently react if there is a highly impure reagent in the beaker." + ph = 3 + color = "#ffffff" + +/datum/reagent/reaction_agent/purity_tester/intercept_reagents_transfer(datum/reagents/target, amount) + . = ..() + if(!.) + return + var/is_inverse = FALSE + for(var/_reagent in target.reagent_list) + var/datum/reagent/reaction_agent/reagent = _reagent + if(reagent.purity <= reagent.inverse_chem_val) + is_inverse = TRUE + if(is_inverse) + target.my_atom.audible_message("The beaker bubbles violently as the reagent is added!") + playsound(target.my_atom, 'sound/chemistry/bufferadd.ogg', 50, TRUE) + else + target.my_atom.audible_message("The added reagent doesn't seem to do much.") + holder.remove_reagent(type, amount) + +/datum/reagent/reaction_agent/speed_agent + name = "Tempomyocin" + description = "This reagent will consume itself and speed up an ongoing reaction, modifying the current reaction's purity by it's own." + ph = 10 + color = "#e61f82" + ///How much the reaction speed is sped up by - for 5u added to 100u, an additional step of 1 will be done up to a max of 2x + var/strength = 20 + + +/datum/reagent/reaction_agent/speed_agent/intercept_reagents_transfer(datum/reagents/target, amount) + . = ..() + if(!.) + return FALSE + if(!length(target.reaction_list))//you can add this reagent to a beaker with no ongoing reactions, so this prevents it from being used up. + return FALSE + amount /= target.reaction_list.len + for(var/_reaction in target.reaction_list) + var/datum/equilibrium/reaction = _reaction + if(!reaction) + CRASH("[_reaction] is in the reaction list, but is not an equilibrium") + var/power = (amount/reaction.target_vol)*strength + power *= creation_purity + power = clamp(power, 0, 2) + reaction.react_timestep(power, creation_purity) + holder.remove_reagent(type, amount) diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index 0e480cd999571a..1f5fc5b6c9f75e 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -29,6 +29,7 @@ color = "#792300" // rgb: 121, 35, 0 toxpwr = 2.5 taste_description = "mushroom" + ph = 13 /datum/reagent/toxin/mutagen name = "Unstable mutagen" @@ -37,6 +38,7 @@ toxpwr = 0 taste_description = "slime" taste_mult = 0.9 + ph = 2.3 /datum/reagent/toxin/mutagen/expose_mob(mob/living/carbon/exposed_mob, methods=TOUCH, reac_volume) . = ..() @@ -72,6 +74,7 @@ toxpwr = 3 material = /datum/material/plasma penetrates_skin = NONE + ph = 4 /datum/reagent/toxin/plasma/on_new(data) . = ..() @@ -139,6 +142,7 @@ color = "#7DC3A0" toxpwr = 0 taste_description = "acid" + ph = 1.2 /datum/reagent/toxin/lexorin/on_mob_life(mob/living/carbon/C) . = TRUE @@ -160,6 +164,7 @@ toxpwr = 0 taste_description = "slime" taste_mult = 1.3 + ph = 10 /datum/reagent/toxin/slimejelly/on_mob_life(mob/living/carbon/M) if(prob(10)) @@ -177,6 +182,7 @@ color = "#CF3600" // rgb: 207, 54, 0 toxpwr = 0 taste_description = "mint" + ph = 8 /datum/reagent/toxin/minttoxin/on_mob_life(mob/living/carbon/M) if(HAS_TRAIT(M, TRAIT_FAT)) @@ -190,6 +196,7 @@ color = "#003333" // rgb: 0, 51, 51 toxpwr = 2 taste_description = "fish" + ph = 12 /datum/reagent/toxin/carpotoxin/on_mob_life(mob/living/carbon/M) . = ..() @@ -206,6 +213,7 @@ taste_description = "death" penetrates_skin = NONE var/fakedeath_active = FALSE + ph = 13 /datum/reagent/toxin/zombiepowder/on_mob_metabolize(mob/living/L) ..() @@ -247,6 +255,7 @@ color = "#664700" // rgb: 102, 71, 0 toxpwr = 0.8 taste_description = "death" + ph = 14.5 /datum/reagent/toxin/ghoulpowder/on_mob_metabolize(mob/living/L) ..() @@ -267,6 +276,7 @@ color = "#B31008" // rgb: 139, 166, 233 toxpwr = 0 taste_description = "sourness" + ph = 11 /datum/reagent/toxin/mindbreaker/on_mob_life(mob/living/carbon/M) M.hallucination += 5 @@ -279,6 +289,7 @@ toxpwr = 1 taste_mult = 1 penetrates_skin = NONE + ph = 2.7 // Plant-B-Gone is just as bad /datum/reagent/toxin/plantbgone/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) @@ -311,6 +322,7 @@ name = "Weed Killer" description = "A harmful toxic mixture to kill weeds. Do not ingest!" color = "#4B004B" // rgb: 75, 0, 75 + ph = 3 //Weed Spray /datum/reagent/toxin/plantbgone/weedkiller/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) @@ -325,6 +337,7 @@ description = "A harmful toxic mixture to kill pests. Do not ingest!" color = "#4B004B" // rgb: 75, 0, 75 toxpwr = 1 + ph = 3.2 //Pest Spray /datum/reagent/toxin/pestkiller/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) @@ -359,6 +372,7 @@ description = "A natural toxin produced by blob spores that inhibits vision when ingested." color = "#9ACD32" toxpwr = 1 + ph = 11 /datum/reagent/toxin/spore/on_mob_life(mob/living/carbon/C) C.damageoverlaytemp = 60 @@ -372,6 +386,7 @@ color = "#9ACD32" toxpwr = 0.5 taste_description = "burning" + ph = 13 /datum/reagent/toxin/spore_burning/on_mob_life(mob/living/carbon/M) M.adjust_fire_stacks(2) @@ -386,6 +401,7 @@ color = "#000067" // rgb: 0, 0, 103 toxpwr = 0 metabolization_rate = 1.5 * REAGENTS_METABOLISM + ph = 11 /datum/reagent/toxin/chloralhydrate/on_mob_life(mob/living/carbon/M) switch(current_cycle) @@ -410,6 +426,7 @@ glass_icon_state = "beerglass" glass_name = "glass of beer" glass_desc = "A freezing pint of beer." + ph = 2 /datum/reagent/toxin/fakebeer/on_mob_life(mob/living/carbon/M) switch(current_cycle) @@ -426,6 +443,7 @@ reagent_state = SOLID color = "#5B2E0D" // rgb: 91, 46, 13 toxpwr = 0.5 + ph = 4.2 /datum/reagent/toxin/teapowder name = "Ground Tea Leaves" @@ -434,6 +452,7 @@ color = "#7F8400" // rgb: 127, 132, 0 toxpwr = 0.1 taste_description = "green tea" + ph = 4.9 /datum/reagent/toxin/mutetoxin //the new zombie powder. name = "Mute Toxin" @@ -442,6 +461,7 @@ color = "#F0F8FF" // rgb: 240, 248, 255 toxpwr = 0 taste_description = "silence" + ph = 12.2 /datum/reagent/toxin/mutetoxin/on_mob_life(mob/living/carbon/M) M.silent = max(M.silent, 3) @@ -850,6 +870,7 @@ var/acidpwr = 10 //the amount of protection removed from the armour taste_description = "acid" self_consuming = TRUE + ph = 2.75 // ...Why? I mean, clearly someone had to have done this and thought, well, acid doesn't hurt plants, but what brought us here, to this point? /datum/reagent/toxin/acid/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) @@ -892,6 +913,7 @@ color = "#5050FF" toxpwr = 2 acidpwr = 42.0 + ph = 1.3 // SERIOUSLY /datum/reagent/toxin/acid/fluacid/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) @@ -912,6 +934,7 @@ color = "#5050FF" toxpwr = 3 acidpwr = 5.0 + ph = 3.3 /datum/reagent/toxin/acid/nitracid/on_mob_life(mob/living/carbon/M) M.adjustFireLoss(volume/10, FALSE) //here you go nervar @@ -962,6 +985,7 @@ /datum/reagent/toxin/bonehurtingjuice/on_mob_add(mob/living/carbon/M) M.say("oof ouch my bones", forced = /datum/reagent/toxin/bonehurtingjuice) + return ..() /datum/reagent/toxin/bonehurtingjuice/on_mob_life(mob/living/carbon/M) M.adjustStaminaLoss(7.5, 0) diff --git a/code/modules/reagents/chemistry/recipes.dm b/code/modules/reagents/chemistry/recipes.dm index a8e0074c0c8849..13fe4af901d79b 100644 --- a/code/modules/reagents/chemistry/recipes.dm +++ b/code/modules/reagents/chemistry/recipes.dm @@ -21,15 +21,42 @@ ///Determines if a chemical reaction can occur inside a mob var/mob_react = TRUE - ///Required temperature for the reaction to begin - var/required_temp = 0 - /// Set to TRUE if you want the recipe to only react when it's BELOW the required temp. - var/is_cold_recipe = FALSE + ///The message shown to nearby people upon mixing, if applicable var/mix_message = "The solution begins to bubble." ///The sound played upon mixing, if applicable var/mix_sound = 'sound/effects/bubbles.ogg' + /// Set to TRUE if you want the recipe to only react when it's BELOW the required temp. + var/is_cold_recipe = FALSE + ///FermiChem! - See fermi_readme.md + ///Required temperature for the reaction to begin, for fermimechanics it defines the lower area of bell curve for determining heat based rate reactions, aka the minimum + var/required_temp = 100 + /// Upper end for above (i.e. the end of the curve section defined by temp_exponent_factor) + var/optimal_temp = 500 + /// Temperature at which reaction explodes - If any reaction is this hot, it explodes! + var/overheat_temp = 900 + /// Lowest value of pH determining pH a 1 value for pH based rate reactions (Plateu phase) + var/optimal_ph_min = 5 + /// Higest value for above + var/optimal_ph_max = 9 + /// How far out pH wil react, giving impurity place (Exponential phase) + var/determin_ph_range = 4 + /// How sharp the temperature exponential curve is (to the power of value) + var/temp_exponent_factor = 2 + /// How sharp the pH exponential curve is (to the power of value) + var/ph_exponent_factor = 2 + /// How much the temperature will change (with no intervention) (i.e. for 30u made the temperature will increase by 100, same with 300u. The final temp will always be start + this value, with the exception con beakers with different specific heats) + var/thermic_constant = 50 + /// pH change per 1u reaction + var/H_ion_release = 0.01 + /// Optimal/max rate possible if all conditions are perfect + var/rate_up_lim = 30 + /// If purity is below 0.15, it calls OverlyImpure() too. Set to 0 to disable this. + var/purity_min = 0.15 + /// bitflags for clear conversions; REACTION_CLEAR_IMPURE, REACTION_CLEAR_INVERSE, REACTION_CLEAR_RETAIN, REACTION_INSTANT + var/reaction_flags = NONE + /datum/chemical_reaction/New() . = ..() SSticker.OnRoundstart(CALLBACK(src,.proc/update_info)) @@ -44,8 +71,14 @@ /datum/chemical_reaction/proc/update_info() return +///REACTION PROCS + /** * Shit that happens on reaction + * Only procs at the START of a reaction + * use reaction_step() for each step of a reaction + * or reaction_end() when the reaction stops + * If reaction_flags & REACTION_INSTANT then this is the only proc that is called. * * Proc where the additional magic happens. * You dont want to handle mob spawning in this since there is a dedicated proc for that.client @@ -53,10 +86,131 @@ * * holder - the datum that holds this reagent, be it a beaker or anything else * * created_volume - volume created when this is mixed. look at 'var/list/results'. */ -/datum/chemical_reaction/proc/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/proc/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) return //I recommend you set the result amount to the total volume of all components. +/** + * Stuff that occurs in the middle of a reaction + * Only procs DURING a reaction + * If reaction_flags & REACTION_INSTANT then this isn't called + * returning END_REACTION will END the reaction + * + * Arguments: + * * reaction - the equilibrium reaction holder that is reaction is processed within - use this to edit delta_t and delta + * * holder - the datum that holds this reagent, be it a beaker or anything else + * * created_volume - volume created per step + * * added_purity - how pure the created volume is per step + * + * Outputs: + * * returning END_REACTION will end the associated reaction - flagging it for deletion and preventing any reaction in that timestep from happening. Make sure to set the vars in the holder to one that can't start it from starting up again. + */ +/datum/chemical_reaction/proc/reaction_step(datum/equilibrium/reaction, datum/reagents/holder, delta_t, delta_ph, step_reaction_vol) + return + +/** + * Stuff that occurs at the end of a reaction. This will proc if the beaker is forced to stop and start again (say for sudden temperature changes). + * Only procs at the END of reaction + * If reaction_flags & REACTION_INSTANT then this isn't called + * if reaction_flags REACTION_CLEAR_IMPURE then the impurity chem is handled here, producing the result in the beaker instead of in a mob + * Likewise for REACTION_CLEAR_INVERSE the inverse chem is produced at the end of the reaction in the beaker + * You should be calling ..() if you're writing a child function of this proc otherwise purity methods won't work correctly + * + * Proc where the additional magic happens. + * You dont want to handle mob spawning in this since there is a dedicated proc for that.client + * Arguments: + * * holder - the datum that holds this reagent, be it a beaker or anything else + * * react_volume - volume created across the whole reaction + */ +/datum/chemical_reaction/proc/reaction_finish(datum/reagents/holder, react_vol) + //failed_chem handler + var/cached_temp = holder.chem_temp + for(var/id in results) + var/datum/reagent/reagent = holder.has_reagent(id) + if(!reagent) + continue + //Split like this so it's easier for people to edit this function in a child + convert_into_failed(reagent, holder) + reaction_clear_check(reagent, holder) + holder.chem_temp = cached_temp + +/** + * Converts a reagent into the type specified by the failed_chem var of the input reagent + * + * Arguments: + * * reagent - the target reagent to convert + */ +/datum/chemical_reaction/proc/convert_into_failed(datum/reagent/reagent, datum/reagents/holder) + if(reagent.purity < purity_min) + var/cached_volume = reagent.volume + holder.remove_reagent(reagent.type, cached_volume, FALSE) + holder.add_reagent(reagent.failed_chem, cached_volume, FALSE, added_purity = 1) + SSblackbox.record_feedback("tally", "chemical_reaction", 1, "[type] failed reactions") + +/** + * REACTION_CLEAR handler + * If the reaction has the REACTION_CLEAR flag, then it will split using purity methods in the beaker instead + * + * Arguments: + * * reagent - the target reagent to convert + */ +/datum/chemical_reaction/proc/reaction_clear_check(datum/reagent/reagent, datum/reagents/holder) + if(!reagent)//Failures can delete R + return + if(reaction_flags & (REACTION_CLEAR_IMPURE | REACTION_CLEAR_INVERSE)) + if(reagent.purity == 1) + return + + var/cached_volume = reagent.volume + if((reaction_flags & REACTION_CLEAR_INVERSE) && reagent.inverse_chem) + if(reagent.inverse_chem_val > reagent.purity) + holder.remove_reagent(reagent.type, cached_volume, FALSE) + holder.add_reagent(reagent.inverse_chem, cached_volume, FALSE, added_purity = 1) + + if((reaction_flags & REACTION_CLEAR_IMPURE) && reagent.impure_chem) + var/impureVol = cached_volume * (1 - reagent.purity) + holder.remove_reagent(reagent.type, (impureVol), FALSE) + holder.add_reagent(reagent.impure_chem, impureVol, FALSE, added_purity = 1) + reagent.creation_purity = reagent.purity + reagent.purity = 1 + +/** + * Occurs when a reation is overheated (i.e. past it's overheatTemp) + * Will be called every tick in the reaction that it is overheated + * If you want this to be a once only proc (i.e. the reaction is stopped after) set reaction.toDelete = TRUE + * The above is useful if you're writing an explosion + * By default the parent proc will reduce the final yield slightly. If you don't want that don't add ..() + * + * Arguments: + * * holder - the datum that holds this reagent, be it a beaker or anything else + * * equilibrium - the equilibrium datum that contains the equilibrium reaction properties and methods + */ +/datum/chemical_reaction/proc/overheated(datum/reagents/holder, datum/equilibrium/equilibrium) + for(var/id in results) + var/datum/reagent/reagent = holder.get_reagent(id) + if(!reagent) + return + reagent.volume = round((reagent.volume*0.98), 0.01) //Slowly lower yield per tick + +/** + * Occurs when a reation is too impure (i.e. it's below purity_min) + * Will be called every tick in the reaction that it is too impure + * If you want this to be a once only proc (i.e. the reaction is stopped after) set reaction.toDelete = TRUE + * The above is useful if you're writing an explosion + * By default the parent proc will reduce the purity of all reagents involved in the reaction in the beaker slightly. If you don't want that don't add ..() + * + * Arguments: + * * holder - the datum that holds this reagent, be it a beaker or anything else + * * equilibrium - the equilibrium datum that contains the equilibrium reaction properties and methods + */ +/datum/chemical_reaction/proc/overly_impure(datum/reagents/holder, datum/equilibrium/equilibrium) + var/affected_list = results + required_reagents + for(var/_reagent in affected_list) + var/datum/reagent/reagent = holder.get_reagent(_reagent) + if(!reagent) + continue + reagent.purity = clamp((reagent.purity-0.01), 0, 1) //slowly reduce purity of reagents + /** * Magical mob spawning when chemicals react * @@ -132,3 +286,107 @@ else if(step_towards(X, T) && moving_power > 1) addtimer(CALLBACK(GLOBAL_PROC, .proc/_step_towards, X, T), 2) + +//////////////////Generic explosions/failures//////////////////// +// It is HIGHLY, HIGHLY recomended that you consume all/a good volume of the reagents/products in an explosion - because it will just keep going forever until the reaction stops +//If you have competitive reactions - it's a good idea to consume ALL reagents in a beaker (or product+reactant), otherwise it'll swing back with the deficit and blow up again + + +//Spews out the inverse of the chems in the beaker of the products/reactants only +/datum/chemical_reaction/proc/explode_invert_smoke(datum/reagents/holder, datum/equilibrium/equilibrium, clear_products = TRUE, clear_reactants = TRUE) + var/datum/reagents/invert_reagents = new (2100, NO_REACT)//I think the biggest size we can get is 2100? + var/datum/effect_system/smoke_spread/chem/smoke = new() + var/sum_volume = 0 + invert_reagents.my_atom = holder.my_atom //Give the gas a fingerprint + for(var/datum/reagent/reagent as anything in holder.reagent_list) //make gas for reagents, has to be done this way, otherwise it never stops Exploding + if(!(reagent.type in required_reagents) || !(reagent.type in results)) + continue + if(reagent.inverse_chem) + invert_reagents.add_reagent(reagent.inverse_chem, reagent.volume, no_react = TRUE) + holder.remove_reagent(reagent.type, reagent.volume) + continue + invert_reagents.add_reagent(reagent.type, reagent.volume, added_purity = reagent.purity, no_react = TRUE) + sum_volume += reagent.volume + holder.remove_reagent(reagent.type, reagent.volume) + if(invert_reagents.reagent_list) + smoke.set_up(invert_reagents, (sum_volume/5), holder.my_atom) + smoke.start() + holder.my_atom.audible_message("The [holder.my_atom] suddenly explodes, launching the aerosolized reagents into the air!") + if(clear_reactants) + clear_reactants(holder) + if(clear_products) + clear_products(holder) + +//Spews out the corrisponding reactions reagents (products/required) of the beaker in a smokecloud. Doesn't spew catalysts +/datum/chemical_reaction/proc/explode_smoke(datum/reagents/holder, datum/equilibrium/equilibrium, clear_products = TRUE, clear_reactants = TRUE) + var/datum/reagents/reagents = new/datum/reagents(2100, NO_REACT)//Lets be safe first + var/datum/effect_system/smoke_spread/chem/smoke = new() + reagents.my_atom = holder.my_atom //fingerprint + var/sum_volume = 0 + for (var/datum/reagent/reagent as anything in holder.reagent_list) + if((reagent.type in required_reagents) || (reagent.type in results)) + reagents.add_reagent(reagent.type, reagent.volume, added_purity = reagent.purity, no_react = TRUE) + holder.remove_reagent(reagent.type, reagent.volume) + if(reagents.reagent_list) + smoke.set_up(reagents, (sum_volume/5), holder.my_atom) + smoke.start() + holder.my_atom.audible_message("The [holder.my_atom] suddenly explodes, launching the aerosolized reagents into the air!") + if(clear_reactants) + clear_reactants(holder) + if(clear_products) + clear_products(holder) + +//Pushes everything out, and damages mobs with 10 brute damage. +/datum/chemical_reaction/proc/explode_shockwave(datum/reagents/holder, datum/equilibrium/equilibrium) + var/turf/this_turf = get_turf(holder.my_atom) + holder.my_atom.audible_message("The [holder.my_atom] suddenly explodes, sending a shockwave rippling through the air!") + playsound(this_turf, 'sound/chemistry/shockwave_explosion.ogg', 80, TRUE) + //Modified goonvortex + for(var/atom/movable/movey in orange(3, this_turf)) + if(isliving(movey)) + var/mob/living/live = movey + live.apply_damage(5)//Since this can be called multiple times + if(movey.anchored) + continue + if(iseffect(movey) || iscameramob(movey) || isdead(movey)) + continue + var/distance = get_dist(movey, this_turf) + var/moving_power = max(4 - distance, 1)//Make sure we're thrown out of range of the next one + var/atom/throw_target = get_edge_target_turf(movey, get_dir(movey, get_step_away(movey, this_turf))) + movey.throw_at(throw_target, moving_power, 1) + + +//Creates a ring of fire in a set range around the beaker location +/datum/chemical_reaction/proc/explode_fire(datum/reagents/holder, datum/equilibrium/equilibrium, range) + explosion(holder.my_atom, 0, 0, 0, 0, flame_range = 3) + holder.my_atom.audible_message("The [holder.my_atom] suddenly errupts in flames!") + +//Clears the beaker of the reagents only +/datum/chemical_reaction/proc/clear_reactants(datum/reagents/holder, volume = null) + if(!holder) + return FALSE + for(var/datum/reagent/reagent as anything in holder.reagent_list) + if(!(reagent.type in required_reagents)) + continue + if(!volume) + holder.remove_reagent(reagent.type, reagent.volume) + else + holder.remove_reagent(reagent.type, volume) + +//Clears the beaker of the product only +/datum/chemical_reaction/proc/clear_products(datum/reagents/holder, volume = null) + if(!holder) + return FALSE + for(var/datum/reagent/reagent as anything in holder.reagent_list) + if(!(reagent.type in results)) + continue + if(!volume) + holder.remove_reagent(reagent.type, reagent.volume) + else + holder.remove_reagent(reagent.type, volume) + +//Clears the beaker of ALL reagents inside +/datum/chemical_reaction/proc/clear_reagents(datum/reagents/holder, volume = null) + if(!holder) + return FALSE + holder.remove_all(volume) diff --git a/code/modules/reagents/chemistry/recipes/cat2_medicines.dm b/code/modules/reagents/chemistry/recipes/cat2_medicines.dm index c266828f1e1e05..a707915787cb22 100644 --- a/code/modules/reagents/chemistry/recipes/cat2_medicines.dm +++ b/code/modules/reagents/chemistry/recipes/cat2_medicines.dm @@ -1,65 +1,67 @@ - /*****BRUTE*****/ -/datum/chemical_reaction/helbital +/datum/chemical_reaction/medicine/helbital results = list(/datum/reagent/medicine/c2/helbital = 3) required_reagents = list(/datum/reagent/consumable/sugar = 1, /datum/reagent/fluorine = 1, /datum/reagent/carbon = 1) mix_message = "The mixture turns into a thick, yellow powder." -/datum/chemical_reaction/libital +/datum/chemical_reaction/medicine/libital results = list(/datum/reagent/medicine/c2/libital = 3) required_reagents = list(/datum/reagent/phenol = 1, /datum/reagent/oxygen = 1, /datum/reagent/nitrogen = 1) -/datum/chemical_reaction/probital +/datum/chemical_reaction/medicine/probital results = list(/datum/reagent/medicine/c2/probital = 4) required_reagents = list(/datum/reagent/copper = 1, /datum/reagent/acetone = 2, /datum/reagent/phosphorus = 1) /*****BURN*****/ -/datum/chemical_reaction/lenturi +/datum/chemical_reaction/medicine/lenturi results = list(/datum/reagent/medicine/c2/lenturi = 5) required_reagents = list(/datum/reagent/ammonia = 1, /datum/reagent/silver = 1, /datum/reagent/sulfur = 1, /datum/reagent/oxygen = 1, /datum/reagent/chlorine = 1) -/datum/chemical_reaction/aiuri +/datum/chemical_reaction/medicine/aiuri results = list(/datum/reagent/medicine/c2/aiuri = 4) required_reagents = list(/datum/reagent/ammonia = 1, /datum/reagent/toxin/acid = 1, /datum/reagent/hydrogen = 2) -/datum/chemical_reaction/hercuri +/datum/chemical_reaction/medicine/hercuri results = list(/datum/reagent/medicine/c2/hercuri = 5) required_reagents = list(/datum/reagent/cryostylane = 3, /datum/reagent/bromine = 1, /datum/reagent/lye = 1) required_temp = 47 is_cold_recipe = TRUE + optimal_temp = 10 + overheat_temp = 5 + thermic_constant = -50 /*****OXY*****/ -/datum/chemical_reaction/convermol +/datum/chemical_reaction/medicine/convermol results = list(/datum/reagent/medicine/c2/convermol = 3) required_reagents = list(/datum/reagent/hydrogen = 1, /datum/reagent/fluorine = 1, /datum/reagent/fuel/oil = 1) required_temp = 370 mix_message = "The mixture rapidly turns into a dense pink liquid." -/datum/chemical_reaction/tirimol +/datum/chemical_reaction/medicine/tirimol results = list(/datum/reagent/medicine/c2/tirimol = 5) required_reagents = list(/datum/reagent/nitrogen = 3, /datum/reagent/acetone = 2) required_catalysts = list(/datum/reagent/toxin/acid = 1) /*****TOX*****/ -/datum/chemical_reaction/seiver +/datum/chemical_reaction/medicine/seiver results = list(/datum/reagent/medicine/c2/seiver = 3) required_reagents = list(/datum/reagent/nitrogen = 1, /datum/reagent/potassium = 1, /datum/reagent/aluminium = 1) -/datum/chemical_reaction/multiver +/datum/chemical_reaction/medicine/multiver results = list(/datum/reagent/medicine/c2/multiver = 2) required_reagents = list(/datum/reagent/ash = 1, /datum/reagent/consumable/salt = 1) mix_message = "The mixture yields a fine black powder." required_temp = 380 -/datum/chemical_reaction/syriniver +/datum/chemical_reaction/medicine/syriniver results = list(/datum/reagent/medicine/c2/syriniver = 5) required_reagents = list(/datum/reagent/sulfur = 1, /datum/reagent/fluorine = 1, /datum/reagent/toxin = 1, /datum/reagent/nitrous_oxide = 2) -/datum/chemical_reaction/penthrite +/datum/chemical_reaction/medicine/penthrite results = list(/datum/reagent/medicine/c2/penthrite = 3) required_reagents = list(/datum/reagent/pentaerythritol = 1, /datum/reagent/acetone = 1, /datum/reagent/toxin/acid/nitracid = 1 , /datum/reagent/wittel = 1) diff --git a/code/modules/reagents/chemistry/recipes/catalysts.dm b/code/modules/reagents/chemistry/recipes/catalysts.dm new file mode 100644 index 00000000000000..7f723dfecb2d0b --- /dev/null +++ b/code/modules/reagents/chemistry/recipes/catalysts.dm @@ -0,0 +1,26 @@ + +///////////////////////////MEDICINES//////////////////////////// + +/datum/chemical_reaction/medical_speed_catalyst + results = list(/datum/reagent/catalyst_agent/speed/medicine = 2) + required_reagents = list(/datum/reagent/medicine/c2/libital = 3, /datum/reagent/medicine/c2/probital = 4, /datum/reagent/toxin/plasma = 2) + mix_message = "The reaction evaporates slightly as the mixture solidifies" + mix_sound = 'sound/chemistry/catalyst.ogg' + required_temp = 320 + optimal_temp = 600 + overheat_temp = 800 + optimal_ph_min = 5 + optimal_ph_max = 6 + determin_ph_range = 5 + temp_exponent_factor = 0.5 + ph_exponent_factor = 4 + thermic_constant = 1000 + H_ion_release = -0.25 + rate_up_lim = 1 + purity_min = 0 + +/datum/chemical_reaction/medical_speed_catalyst/overheated(datum/reagents/holder, datum/equilibrium/equilibrium) + explode_invert_smoke(holder, equilibrium) //Will be better when the inputs have proper invert chems + +/datum/chemical_reaction/medical_speed_catalyst/overly_impure(datum/reagents/holder, datum/equilibrium/equilibrium) + explode_invert_smoke(holder, equilibrium) diff --git a/code/modules/reagents/chemistry/recipes/drugs.dm b/code/modules/reagents/chemistry/recipes/drugs.dm index 5b46a68cb5fac2..2d58aed05cfcc6 100644 --- a/code/modules/reagents/chemistry/recipes/drugs.dm +++ b/code/modules/reagents/chemistry/recipes/drugs.dm @@ -14,10 +14,71 @@ mix_message = "The mixture dries into a pale blue powder." required_temp = 380 + /datum/chemical_reaction/methamphetamine results = list(/datum/reagent/drug/methamphetamine = 4) required_reagents = list(/datum/reagent/medicine/ephedrine = 1, /datum/reagent/iodine = 1, /datum/reagent/phosphorus = 1, /datum/reagent/hydrogen = 1) - required_temp = 374 + required_temp = 372 + optimal_temp = 376//Wow this is tight + overheat_temp = 380 + optimal_ph_min = 6.5 + optimal_ph_max = 7.5 + determin_ph_range = 5 + temp_exponent_factor = 1 + ph_exponent_factor = 1.4 + thermic_constant = 0.1 //exothermic nature is equal to impurty + H_ion_release = -0.025 + rate_up_lim = 12.5 + purity_min = 0.5 //100u will natrually just dip under this w/ no buffer + reaction_flags = REACTION_HEAT_ARBITARY //Heating up is arbitary because of submechanics of this reaction. + +//The less pure it is, the faster it heats up. tg please don't hate me for making your meth even more dangerous +/datum/chemical_reaction/methamphetamine/reaction_step(datum/equilibrium/reaction, datum/reagents/holder, delta_t, delta_ph, step_reaction_vol) + var/datum/reagent/meth = holder.get_reagent(/datum/reagent/drug/methamphetamine) + if(!meth)//First step + reaction.thermic_mod = (1-delta_ph)*5 + return + reaction.thermic_mod = (1-meth.purity)*5 + +/datum/chemical_reaction/methamphetamine/overheated(datum/reagents/holder, datum/equilibrium/equilibrium) + . = ..() + temp_meth_explosion(holder, equilibrium.reacted_vol) + +/datum/chemical_reaction/methamphetamine/overly_impure(datum/reagents/holder, datum/equilibrium/equilibrium) + . = ..() + temp_meth_explosion(holder, equilibrium.reacted_vol) + +/datum/chemical_reaction/methamphetamine/reaction_finish(datum/reagents/holder, react_vol) + var/datum/reagent/meth = holder.get_reagent(/datum/reagent/drug/methamphetamine) + if(!meth)//Other procs before this can already blow us up + return ..() + if(meth.purity < purity_min) + temp_meth_explosion(holder, react_vol) + return + return ..() + +//Refactoring of explosions is coming later, this is till then so it still explodes +/datum/chemical_reaction/methamphetamine/proc/temp_meth_explosion(datum/reagents/holder, explode_vol) + var/power = 5 + round(explode_vol/12, 1) //meth strengthdiv is 12 + if(power <= 0) + return + var/turf/T = get_turf(holder.my_atom) + var/inside_msg + if(ismob(holder.my_atom)) + var/mob/M = holder.my_atom + inside_msg = " inside [ADMIN_LOOKUPFLW(M)]" + var/lastkey = holder.my_atom.fingerprintslast + var/touch_msg = "N/A" + if(lastkey) + var/mob/toucher = get_mob_by_key(lastkey) + touch_msg = "[ADMIN_LOOKUPFLW(toucher)]" + if(!istype(holder.my_atom, /obj/machinery/plumbing)) //excludes standard plumbing equipment from spamming admins with this shit + message_admins("Reagent explosion reaction occurred at [ADMIN_VERBOSEJMP(T)][inside_msg]. Last Fingerprint: [touch_msg].") + log_game("Reagent explosion reaction occurred at [AREACOORD(T)]. Last Fingerprint: [lastkey ? lastkey : "N/A"]." ) + var/datum/effect_system/reagents_explosion/e = new() + e.set_up(power, T, 0, 0) + e.start() + holder.clear_reagents() /datum/chemical_reaction/bath_salts results = list(/datum/reagent/drug/bath_salts = 7) diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm index 0b940900c72521..b542601975f65a 100644 --- a/code/modules/reagents/chemistry/recipes/medicine.dm +++ b/code/modules/reagents/chemistry/recipes/medicine.dm @@ -1,181 +1,190 @@ -/datum/chemical_reaction/leporazine +/datum/chemical_reaction/medicine + required_reagents = null //Don't add this to master list + optimal_temp = 700 + optimal_ph_max = 10 + temp_exponent_factor = 1.2 + ph_exponent_factor = 0.8 + purity_min = 0.1 + rate_up_lim = 35 + +/datum/chemical_reaction/medicine/leporazine results = list(/datum/reagent/medicine/leporazine = 2) required_reagents = list(/datum/reagent/silicon = 1, /datum/reagent/copper = 1) required_catalysts = list(/datum/reagent/toxin/plasma = 5) -/datum/chemical_reaction/rezadone +/datum/chemical_reaction/medicine/rezadone results = list(/datum/reagent/medicine/rezadone = 3) required_reagents = list(/datum/reagent/toxin/carpotoxin = 1, /datum/reagent/cryptobiolin = 1, /datum/reagent/copper = 1) -/datum/chemical_reaction/spaceacillin +/datum/chemical_reaction/medicine/spaceacillin results = list(/datum/reagent/medicine/spaceacillin = 2) required_reagents = list(/datum/reagent/cryptobiolin = 1, /datum/reagent/medicine/epinephrine = 1) -/datum/chemical_reaction/oculine +/datum/chemical_reaction/medicine/oculine results = list(/datum/reagent/medicine/oculine = 3) required_reagents = list(/datum/reagent/medicine/c2/multiver = 1, /datum/reagent/carbon = 1, /datum/reagent/hydrogen = 1) mix_message = "The mixture bubbles noticeably and becomes a dark grey color!" -/datum/chemical_reaction/inacusiate +/datum/chemical_reaction/medicine/inacusiate results = list(/datum/reagent/medicine/inacusiate = 2) required_reagents = list(/datum/reagent/water = 1, /datum/reagent/carbon = 1, /datum/reagent/medicine/c2/multiver = 1) mix_message = "The mixture sputters loudly and becomes a light grey color!" -/datum/chemical_reaction/synaptizine +/datum/chemical_reaction/medicine/synaptizine results = list(/datum/reagent/medicine/synaptizine = 3) required_reagents = list(/datum/reagent/consumable/sugar = 1, /datum/reagent/lithium = 1, /datum/reagent/water = 1) -/datum/chemical_reaction/salglu_solution +/datum/chemical_reaction/medicine/salglu_solution results = list(/datum/reagent/medicine/salglu_solution = 3) required_reagents = list(/datum/reagent/consumable/salt = 1, /datum/reagent/water = 1, /datum/reagent/consumable/sugar = 1) -/datum/chemical_reaction/mine_salve +/datum/chemical_reaction/medicine/mine_salve results = list(/datum/reagent/medicine/mine_salve = 3) required_reagents = list(/datum/reagent/fuel/oil = 1, /datum/reagent/water = 1, /datum/reagent/iron = 1) -/datum/chemical_reaction/mine_salve2 +/datum/chemical_reaction/medicine/mine_salve2 results = list(/datum/reagent/medicine/mine_salve = 15) required_reagents = list(/datum/reagent/toxin/plasma = 5, /datum/reagent/iron = 5, /datum/reagent/consumable/sugar = 1) // A sheet of plasma, a twinkie and a sheet of metal makes four of these -/datum/chemical_reaction/synthflesh +/datum/chemical_reaction/medicine/synthflesh results = list(/datum/reagent/medicine/c2/synthflesh = 3) required_reagents = list(/datum/reagent/blood = 1, /datum/reagent/carbon = 1, /datum/reagent/medicine/c2/libital = 1) -/datum/chemical_reaction/calomel +/datum/chemical_reaction/medicine/calomel results = list(/datum/reagent/medicine/calomel = 2) required_reagents = list(/datum/reagent/mercury = 1, /datum/reagent/chlorine = 1) required_temp = 374 -/datum/chemical_reaction/potass_iodide +/datum/chemical_reaction/medicine/potass_iodide results = list(/datum/reagent/medicine/potass_iodide = 2) required_reagents = list(/datum/reagent/potassium = 1, /datum/reagent/iodine = 1) -/datum/chemical_reaction/pen_acid +/datum/chemical_reaction/medicine/pen_acid results = list(/datum/reagent/medicine/pen_acid = 6) required_reagents = list(/datum/reagent/fuel = 1, /datum/reagent/chlorine = 1, /datum/reagent/ammonia = 1, /datum/reagent/toxin/formaldehyde = 1, /datum/reagent/sodium = 1, /datum/reagent/toxin/cyanide = 1) -/datum/chemical_reaction/sal_acid +/datum/chemical_reaction/medicine/sal_acid results = list(/datum/reagent/medicine/sal_acid = 5) required_reagents = list(/datum/reagent/sodium = 1, /datum/reagent/phenol = 1, /datum/reagent/carbon = 1, /datum/reagent/oxygen = 1, /datum/reagent/toxin/acid = 1) -/datum/chemical_reaction/oxandrolone +/datum/chemical_reaction/medicine/oxandrolone results = list(/datum/reagent/medicine/oxandrolone = 6) required_reagents = list(/datum/reagent/carbon = 3, /datum/reagent/phenol = 1, /datum/reagent/hydrogen = 1, /datum/reagent/oxygen = 1) -/datum/chemical_reaction/salbutamol +/datum/chemical_reaction/medicine/salbutamol results = list(/datum/reagent/medicine/salbutamol = 5) required_reagents = list(/datum/reagent/medicine/sal_acid = 1, /datum/reagent/lithium = 1, /datum/reagent/aluminium = 1, /datum/reagent/bromine = 1, /datum/reagent/ammonia = 1) -/datum/chemical_reaction/ephedrine +/datum/chemical_reaction/medicine/ephedrine results = list(/datum/reagent/medicine/ephedrine = 4) required_reagents = list(/datum/reagent/consumable/sugar = 1, /datum/reagent/fuel/oil = 1, /datum/reagent/hydrogen = 1, /datum/reagent/diethylamine = 1) mix_message = "The solution fizzes and gives off toxic fumes." -/datum/chemical_reaction/diphenhydramine +/datum/chemical_reaction/medicine/diphenhydramine results = list(/datum/reagent/medicine/diphenhydramine = 4) required_reagents = list(/datum/reagent/fuel/oil = 1, /datum/reagent/carbon = 1, /datum/reagent/bromine = 1, /datum/reagent/diethylamine = 1, /datum/reagent/consumable/ethanol = 1) mix_message = "The mixture dries into a pale blue powder." -/datum/chemical_reaction/atropine +/datum/chemical_reaction/medicine/atropine results = list(/datum/reagent/medicine/atropine = 5) required_reagents = list(/datum/reagent/consumable/ethanol = 1, /datum/reagent/acetone = 1, /datum/reagent/diethylamine = 1, /datum/reagent/phenol = 1, /datum/reagent/toxin/acid = 1) -/datum/chemical_reaction/epinephrine +/datum/chemical_reaction/medicine/epinephrine results = list(/datum/reagent/medicine/epinephrine = 6) required_reagents = list(/datum/reagent/phenol = 1, /datum/reagent/acetone = 1, /datum/reagent/diethylamine = 1, /datum/reagent/oxygen = 1, /datum/reagent/chlorine = 1, /datum/reagent/hydrogen = 1) -/datum/chemical_reaction/strange_reagent +/datum/chemical_reaction/medicine/strange_reagent results = list(/datum/reagent/medicine/strange_reagent = 3) required_reagents = list(/datum/reagent/medicine/omnizine = 1, /datum/reagent/water/holywater = 1, /datum/reagent/toxin/mutagen = 1) -/datum/chemical_reaction/strange_reagent/alt +/datum/chemical_reaction/medicine/strange_reagent/alt results = list(/datum/reagent/medicine/strange_reagent = 2) required_reagents = list(/datum/reagent/medicine/omnizine/protozine = 1, /datum/reagent/water/holywater = 1, /datum/reagent/toxin/mutagen = 1) -/datum/chemical_reaction/mannitol +/datum/chemical_reaction/medicine/mannitol results = list(/datum/reagent/medicine/mannitol = 3) required_reagents = list(/datum/reagent/consumable/sugar = 1, /datum/reagent/hydrogen = 1, /datum/reagent/water = 1) mix_message = "The solution slightly bubbles, becoming thicker." -/datum/chemical_reaction/neurine +/datum/chemical_reaction/medicine/neurine results = list(/datum/reagent/medicine/neurine = 3) required_reagents = list(/datum/reagent/medicine/mannitol = 1, /datum/reagent/acetone = 1, /datum/reagent/oxygen = 1) -/datum/chemical_reaction/mutadone +/datum/chemical_reaction/medicine/mutadone results = list(/datum/reagent/medicine/mutadone = 3) required_reagents = list(/datum/reagent/toxin/mutagen = 1, /datum/reagent/acetone = 1, /datum/reagent/bromine = 1) -/datum/chemical_reaction/antihol +/datum/chemical_reaction/medicine/antihol results = list(/datum/reagent/medicine/antihol = 3) required_reagents = list(/datum/reagent/consumable/ethanol = 1, /datum/reagent/medicine/c2/multiver = 1, /datum/reagent/copper = 1) -/datum/chemical_reaction/cryoxadone +/datum/chemical_reaction/medicine/cryoxadone results = list(/datum/reagent/medicine/cryoxadone = 3) required_reagents = list(/datum/reagent/stable_plasma = 1, /datum/reagent/acetone = 1, /datum/reagent/toxin/mutagen = 1) -/datum/chemical_reaction/pyroxadone +/datum/chemical_reaction/medicine/pyroxadone results = list(/datum/reagent/medicine/pyroxadone = 2) required_reagents = list(/datum/reagent/medicine/cryoxadone = 1, /datum/reagent/toxin/slimejelly = 1) -/datum/chemical_reaction/clonexadone +/datum/chemical_reaction/medicine/clonexadone results = list(/datum/reagent/medicine/clonexadone = 2) required_reagents = list(/datum/reagent/medicine/cryoxadone = 1, /datum/reagent/sodium = 1) required_catalysts = list(/datum/reagent/toxin/plasma = 5) -/datum/chemical_reaction/haloperidol +/datum/chemical_reaction/medicine/haloperidol results = list(/datum/reagent/medicine/haloperidol = 5) required_reagents = list(/datum/reagent/chlorine = 1, /datum/reagent/fluorine = 1, /datum/reagent/aluminium = 1, /datum/reagent/medicine/potass_iodide = 1, /datum/reagent/fuel/oil = 1) -/datum/chemical_reaction/regen_jelly +/datum/chemical_reaction/medicine/regen_jelly results = list(/datum/reagent/medicine/regen_jelly = 2) required_reagents = list(/datum/reagent/medicine/omnizine = 1, /datum/reagent/toxin/slimejelly = 1) -/datum/chemical_reaction/higadrite +/datum/chemical_reaction/medicine/higadrite results = list(/datum/reagent/medicine/higadrite = 3) required_reagents = list(/datum/reagent/phenol = 2, /datum/reagent/lithium = 1) -/datum/chemical_reaction/morphine +/datum/chemical_reaction/medicine/morphine results = list(/datum/reagent/medicine/morphine = 2) required_reagents = list(/datum/reagent/carbon = 2, /datum/reagent/hydrogen = 2, /datum/reagent/consumable/ethanol = 1, /datum/reagent/oxygen = 1) required_temp = 480 -/datum/chemical_reaction/modafinil +/datum/chemical_reaction/medicine/modafinil results = list(/datum/reagent/medicine/modafinil = 5) required_reagents = list(/datum/reagent/diethylamine = 1, /datum/reagent/ammonia = 1, /datum/reagent/phenol = 1, /datum/reagent/acetone = 1, /datum/reagent/toxin/acid = 1) required_catalysts = list(/datum/reagent/bromine = 1) // as close to the real world synthesis as possible -/datum/chemical_reaction/psicodine +/datum/chemical_reaction/medicine/psicodine results = list(/datum/reagent/medicine/psicodine = 5) required_reagents = list( /datum/reagent/medicine/mannitol = 2, /datum/reagent/water = 2, /datum/reagent/impedrezene = 1) -/datum/chemical_reaction/granibitaluri +/datum/chemical_reaction/medicine/granibitaluri results = list(/datum/reagent/medicine/granibitaluri = 3) required_reagents = list(/datum/reagent/consumable/salt = 1, /datum/reagent/carbon = 1, /datum/reagent/toxin/acid = 1) required_catalysts = list(/datum/reagent/iron = 5) ///medical stacks -/datum/chemical_reaction/medsuture +/datum/chemical_reaction/medicine/medsuture required_reagents = list(/datum/reagent/cellulose = 10, /datum/reagent/toxin/formaldehyde = 20, /datum/reagent/medicine/polypyr = 15) //This might be a bit much, reagent cost should be reviewed after implementation. -/datum/chemical_reaction/medsuture/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/medicine/medsuture/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/stack/medical/suture/medicated(location) -/datum/chemical_reaction/medmesh +/datum/chemical_reaction/medicine/medmesh required_reagents = list(/datum/reagent/cellulose = 20, /datum/reagent/consumable/aloejuice = 20, /datum/reagent/space_cleaner/sterilizine = 10) -/datum/chemical_reaction/medmesh/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/medicine/medmesh/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/stack/medical/mesh/advanced(location) -/datum/chemical_reaction/poultice +/datum/chemical_reaction/medicine/poultice required_reagents = list(/datum/reagent/toxin/bungotoxin = 20, /datum/reagent/cellulose = 20, /datum/reagent/consumable/aloejuice = 20) -/datum/chemical_reaction/poultice/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/medicine/poultice/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i in 1 to created_volume) new /obj/item/stack/medical/poultice(location) diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm index 978e23daa6d8df..e9ffa8a4f4be4d 100644 --- a/code/modules/reagents/chemistry/recipes/others.dm +++ b/code/modules/reagents/chemistry/recipes/others.dm @@ -39,8 +39,9 @@ /datum/chemical_reaction/plasma_solidification required_reagents = list(/datum/reagent/iron = 5, /datum/reagent/consumable/frostoil = 5, /datum/reagent/toxin/plasma = 20) mob_react = FALSE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/plasma_solidification/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/plasma_solidification/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/stack/sheet/mineral/plasma(location) @@ -48,8 +49,9 @@ /datum/chemical_reaction/gold_solidification required_reagents = list(/datum/reagent/consumable/frostoil = 5, /datum/reagent/gold = 20, /datum/reagent/iron = 1) mob_react = FALSE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/gold_solidification/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/gold_solidification/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/stack/sheet/mineral/gold(location) @@ -57,8 +59,9 @@ /datum/chemical_reaction/uranium_solidification required_reagents = list(/datum/reagent/consumable/frostoil = 5, /datum/reagent/uranium = 20, /datum/reagent/potassium = 1) mob_react = FALSE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/uranium_solidification/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/uranium_solidification/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/stack/sheet/mineral/uranium(location) @@ -71,8 +74,9 @@ required_reagents = list(/datum/reagent/liquidgibs = 10, /datum/reagent/lye = 10) // requires two scooped gib tiles required_temp = 374 mob_react = FALSE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/soapification/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/soapification/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/soap/homemade(location) @@ -81,8 +85,9 @@ required_reagents = list(/datum/reagent/consumable/potato_juice = 10, /datum/reagent/consumable/ethanol/lizardwine = 10, /datum/reagent/monkey_powder = 10, /datum/reagent/drug/krokodil = 10, /datum/reagent/toxin/acid/nitracid = 10, /datum/reagent/baldium = 10, /datum/reagent/consumable/ethanol/hooch = 10, /datum/reagent/bluespace = 10, /datum/reagent/drug/pumpup = 10, /datum/reagent/consumable/space_cola = 10) required_temp = 999 mob_react = FALSE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/omegasoapification/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/omegasoapification/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/soap/omega(location) @@ -91,8 +96,9 @@ required_reagents = list(/datum/reagent/liquidgibs = 5, /datum/reagent/oxygen = 5) // required_temp = 374 mob_react = FALSE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/candlefication/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/candlefication/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/candle(location) @@ -100,8 +106,9 @@ /datum/chemical_reaction/meatification required_reagents = list(/datum/reagent/liquidgibs = 10, /datum/reagent/consumable/nutriment = 10, /datum/reagent/carbon = 10) mob_react = FALSE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/meatification/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/meatification/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = 1, i <= created_volume, i++) new /obj/item/food/meat/slab/meatproduct(location) @@ -175,8 +182,9 @@ required_catalysts = list(/datum/reagent/blood = 1) var/level_min = 1 var/level_max = 2 + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/mix_virus/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/mix_virus/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/datum/reagent/blood/B = locate(/datum/reagent/blood) in holder.reagent_list if(B?.data) var/datum/disease/advance/D = locate(/datum/disease/advance) in B.data["viruses"] @@ -243,7 +251,7 @@ required_reagents = list(/datum/reagent/medicine/synaptizine = 1) required_catalysts = list(/datum/reagent/blood = 1) -/datum/chemical_reaction/mix_virus/rem_virus/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/mix_virus/rem_virus/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/datum/reagent/blood/B = locate(/datum/reagent/blood) in holder.reagent_list if(B?.data) var/datum/disease/advance/D = locate(/datum/disease/advance) in B.data["viruses"] @@ -254,7 +262,7 @@ required_reagents = list(/datum/reagent/toxin/formaldehyde = 1) required_catalysts = list(/datum/reagent/blood = 1) -/datum/chemical_reaction/mix_virus/neuter_virus/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/mix_virus/neuter_virus/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/datum/reagent/blood/B = locate(/datum/reagent/blood) in holder.reagent_list if(B?.data) var/datum/disease/advance/D = locate(/datum/disease/advance) in B.data["viruses"] @@ -269,33 +277,38 @@ /datum/chemical_reaction/surfactant results = list(/datum/reagent/fluorosurfactant = 5) required_reagents = list(/datum/reagent/fluorine = 2, /datum/reagent/carbon = 2, /datum/reagent/toxin/acid = 1) + reaction_flags = REACTION_INSTANT /datum/chemical_reaction/foam required_reagents = list(/datum/reagent/fluorosurfactant = 1, /datum/reagent/water = 1) mob_react = FALSE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/foam/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/foam/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) holder.create_foam(/datum/effect_system/foam_spread,2*created_volume,notification="The solution spews out foam!") /datum/chemical_reaction/metalfoam required_reagents = list(/datum/reagent/aluminium = 3, /datum/reagent/foaming_agent = 1, /datum/reagent/toxin/acid/fluacid = 1) mob_react = FALSE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/metalfoam/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/metalfoam/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) holder.create_foam(/datum/effect_system/foam_spread/metal,5*created_volume,1,"The solution spews out a metallic foam!") /datum/chemical_reaction/smart_foam required_reagents = list(/datum/reagent/aluminium = 3, /datum/reagent/smart_foaming_agent = 1, /datum/reagent/toxin/acid/fluacid = 1) mob_react = TRUE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/smart_foam/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/smart_foam/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) holder.create_foam(/datum/effect_system/foam_spread/metal/smart,5*created_volume,1,"The solution spews out metallic foam!") /datum/chemical_reaction/ironfoam required_reagents = list(/datum/reagent/iron = 3, /datum/reagent/foaming_agent = 1, /datum/reagent/toxin/acid/fluacid = 1) mob_react = FALSE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/ironfoam/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/ironfoam/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) holder.create_foam(/datum/effect_system/foam_spread/metal,5*created_volume,2,"The solution spews out a metallic foam!") /datum/chemical_reaction/foaming_agent @@ -313,6 +326,9 @@ /datum/chemical_reaction/ammonia results = list(/datum/reagent/ammonia = 3) required_reagents = list(/datum/reagent/hydrogen = 3, /datum/reagent/nitrogen = 1) + optimal_ph_min = 1 // Lets increase our range for this basic chem + optimal_ph_max = 12 + H_ion_release = -0.02 //handmade is more neutral /datum/chemical_reaction/diethylamine results = list(/datum/reagent/diethylamine = 2) @@ -321,6 +337,7 @@ /datum/chemical_reaction/space_cleaner results = list(/datum/reagent/space_cleaner = 2) required_reagents = list(/datum/reagent/ammonia = 1, /datum/reagent/water = 1) + rate_up_lim = 40 /datum/chemical_reaction/plantbgone results = list(/datum/reagent/toxin/plantbgone = 5) @@ -329,6 +346,7 @@ /datum/chemical_reaction/weedkiller results = list(/datum/reagent/toxin/plantbgone/weedkiller = 5) required_reagents = list(/datum/reagent/toxin = 1, /datum/reagent/ammonia = 4) + H_ion_release = -0.05 // Push towards acidic /datum/chemical_reaction/pestkiller results = list(/datum/reagent/toxin/pestkiller = 5) @@ -408,22 +426,25 @@ /datum/chemical_reaction/life required_reagents = list(/datum/reagent/medicine/strange_reagent = 1, /datum/reagent/medicine/c2/synthflesh = 1, /datum/reagent/blood = 1) required_temp = 374 + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/life/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/life/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) chemical_mob_spawn(holder, rand(1, round(created_volume, 1)), "Life (hostile)") //defaults to HOSTILE_SPAWN /datum/chemical_reaction/life_friendly required_reagents = list(/datum/reagent/medicine/strange_reagent = 1, /datum/reagent/medicine/c2/synthflesh = 1, /datum/reagent/consumable/sugar = 1) required_temp = 374 + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/life_friendly/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/life_friendly/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) chemical_mob_spawn(holder, rand(1, round(created_volume, 1)), "Life (friendly)", FRIENDLY_SPAWN) /datum/chemical_reaction/corgium required_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/colorful_reagent = 1, /datum/reagent/medicine/strange_reagent = 1, /datum/reagent/blood = 1) required_temp = 374 + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/corgium/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/corgium/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = rand(1, created_volume), i <= created_volume, i++) // More lulz. new /mob/living/simple_animal/pet/dog/corgi(location) @@ -433,12 +454,13 @@ /datum/chemical_reaction/monkey_powder results = list(/datum/reagent/monkey_powder = 5) required_reagents = list(/datum/reagent/consumable/banana = 1, /datum/reagent/consumable/nutriment=2,/datum/reagent/liquidgibs = 1) + reaction_flags = REACTION_INSTANT /datum/chemical_reaction/monkey required_reagents = list(/datum/reagent/monkey_powder = 50, /datum/reagent/water = 1) mix_message = "Expands into a brown mass before shaping itself into a monkey!." -/datum/chemical_reaction/monkey/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/monkey/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/mob/living/carbon/M = holder.my_atom var/location = get_turf(M) if(istype(M, /mob/living/carbon)) @@ -456,8 +478,9 @@ //butterflium /datum/chemical_reaction/butterflium required_reagents = list(/datum/reagent/colorful_reagent = 1, /datum/reagent/medicine/omnizine = 1, /datum/reagent/medicine/strange_reagent = 1, /datum/reagent/consumable/nutriment = 1) + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/butterflium/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/butterflium/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i = rand(1, created_volume), i <= created_volume, i++) new /mob/living/simple_animal/butterfly(location) @@ -466,8 +489,9 @@ /datum/chemical_reaction/scream required_reagents = list(/datum/reagent/medicine/strange_reagent = 1, /datum/reagent/consumable/cream = 5, /datum/reagent/consumable/ethanol/lizardwine = 5 ) required_temp = 374 + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/scream/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/scream/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) playsound(holder.my_atom, pick(list( 'sound/voice/human/malescream_1.ogg', 'sound/voice/human/malescream_2.ogg', 'sound/voice/human/malescream_3.ogg', 'sound/voice/human/malescream_4.ogg', 'sound/voice/human/malescream_5.ogg', 'sound/voice/human/malescream_6.ogg', 'sound/voice/human/femalescream_1.ogg', 'sound/voice/human/femalescream_2.ogg', 'sound/voice/human/femalescream_3.ogg', 'sound/voice/human/femalescream_4.ogg', 'sound/voice/human/femalescream_5.ogg', 'sound/voice/human/wilhelm_scream.ogg')), created_volume*5,TRUE) /datum/chemical_reaction/hair_dye @@ -494,6 +518,7 @@ /datum/chemical_reaction/lye results = list(/datum/reagent/lye = 3) required_reagents = list(/datum/reagent/sodium = 1, /datum/reagent/hydrogen = 1, /datum/reagent/oxygen = 1) + required_temp = 10 //So hercuri still shows life. /datum/chemical_reaction/lye2 results = list(/datum/reagent/lye = 2) @@ -510,8 +535,9 @@ /datum/chemical_reaction/plastic_polymers required_reagents = list(/datum/reagent/fuel/oil = 5, /datum/reagent/toxin/acid = 2, /datum/reagent/ash = 3) required_temp = 374 //lazily consistent with soap & other crafted objects generically created with heat. + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/plastic_polymers/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/plastic_polymers/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i in 1 to created_volume) new /obj/item/stack/sheet/plastic(location) @@ -535,16 +561,18 @@ /datum/chemical_reaction/slime_extractification required_reagents = list(/datum/reagent/toxin/slimejelly = 30, /datum/reagent/consumable/frostoil = 5, /datum/reagent/toxin/plasma = 5) mix_message = "The mixture condenses into a ball." + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/slime_extractification/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/slime_extractification/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) new /obj/item/slime_extract/grey(location) /datum/chemical_reaction/metalgen_imprint required_reagents = list(/datum/reagent/metalgen = 1, /datum/reagent/liquid_dark_matter = 1) results = list(/datum/reagent/metalgen = 1) + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/metalgen_imprint/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/metalgen_imprint/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/datum/reagent/metalgen/MM = holder.get_reagent(/datum/reagent/metalgen) for(var/datum/reagent/R in holder.reagent_list) if(R.material && R.volume >= 40) @@ -590,8 +618,9 @@ required_reagents = list(/datum/reagent/silver = 20, /datum/reagent/carbon = 10) required_temp = 630 mob_react = FALSE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/silver_solidification/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/silver_solidification/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) for(var/i in 1 to created_volume) new /obj/item/stack/sheet/mineral/silver(location) diff --git a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm index 4e36b2e83148d5..a4ea6b5a92695c 100644 --- a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm +++ b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm @@ -1,8 +1,9 @@ /datum/chemical_reaction/reagent_explosion var/strengthdiv = 10 var/modifier = 0 + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/reagent_explosion/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/reagent_explosion/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) explode(holder, created_volume) /datum/chemical_reaction/reagent_explosion/proc/explode(datum/reagents/holder, created_volume) @@ -13,7 +14,7 @@ if(ismob(holder.my_atom)) var/mob/M = holder.my_atom inside_msg = " inside [ADMIN_LOOKUPFLW(M)]" - var/lastkey = holder.my_atom.fingerprintslast + var/lastkey = holder.my_atom.fingerprintslast //This can runtime (null.fingerprintslast) - due to plumbing? var/touch_msg = "N/A" if(lastkey) var/mob/toucher = get_mob_by_key(lastkey) @@ -31,7 +32,7 @@ required_reagents = list(/datum/reagent/glycerol = 1, /datum/reagent/toxin/acid/nitracid = 1, /datum/reagent/toxin/acid = 1) strengthdiv = 2 -/datum/chemical_reaction/reagent_explosion/nitroglycerin/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/reagent_explosion/nitroglycerin/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) if(holder.has_reagent(/datum/reagent/exotic_stabilizer,round(created_volume / 25, CHEMICAL_QUANTISATION_LEVEL))) return @@ -50,7 +51,7 @@ required_temp = 404 strengthdiv = 8 -/datum/chemical_reaction/reagent_explosion/rdx/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/reagent_explosion/rdx/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) if(holder.has_reagent(/datum/reagent/stabilizing_agent)) return holder.remove_reagent(/datum/reagent/rdx, created_volume*2) @@ -67,7 +68,7 @@ strengthdiv = 3.5 //actually a decrease of 1 becaused of how explosions are calculated. This is due to the fact we require 2 reagents modifier = 4 -/datum/chemical_reaction/reagent_explosion/rdx_explosion2/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/reagent_explosion/rdx_explosion2/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/fire_range = round(created_volume/30) var/turf/T = get_turf(holder.my_atom) for(var/turf/turf in range(fire_range,T)) @@ -81,7 +82,7 @@ modifier = 6 -/datum/chemical_reaction/reagent_explosion/rdx_explosion3/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/reagent_explosion/rdx_explosion3/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/fire_range = round(created_volume/20) var/turf/T = get_turf(holder.my_atom) for(var/turf/turf in range(fire_range,T)) @@ -98,7 +99,7 @@ /datum/chemical_reaction/reagent_explosion/tatp/update_info() required_temp = 450 + rand(-49,49) //this gets loaded only on round start -/datum/chemical_reaction/reagent_explosion/tatp/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/reagent_explosion/tatp/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) if(holder.has_reagent(/datum/reagent/exotic_stabilizer,round(created_volume / 50, CHEMICAL_QUANTISATION_LEVEL))) // we like exotic stabilizer return holder.remove_reagent(/datum/reagent/tatp, created_volume) @@ -109,7 +110,7 @@ required_temp = 550 // this makes making tatp before pyro nades, and extreme pain in the ass to make strengthdiv = 3 -/datum/chemical_reaction/reagent_explosion/tatp_explosion/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/reagent_explosion/tatp_explosion/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/strengthdiv_adjust = created_volume / ( 2100 / initial(strengthdiv)) strengthdiv = max(initial(strengthdiv) - strengthdiv_adjust + 1.5 ,1.5) //Slightly better than nitroglycerin . = ..() @@ -135,7 +136,7 @@ required_reagents = list(/datum/reagent/water/holywater = 1, /datum/reagent/potassium = 1) strengthdiv = 20 -/datum/chemical_reaction/reagent_explosion/holyboom/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/reagent_explosion/holyboom/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) if(created_volume >= 150) strengthdiv = 8 ///turf where to play sound @@ -172,7 +173,7 @@ modifier = 5 mix_message = "Sparks start flying around the gunpowder!" -/datum/chemical_reaction/reagent_explosion/gunpowder_explosion/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/reagent_explosion/gunpowder_explosion/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) addtimer(CALLBACK(src, .proc/explode, holder, created_volume), rand(5,10) SECONDS) /datum/chemical_reaction/thermite @@ -182,7 +183,7 @@ /datum/chemical_reaction/emp_pulse required_reagents = list(/datum/reagent/uranium = 1, /datum/reagent/iron = 1) // Yes, laugh, it's the best recipe I could think of that makes a little bit of sense -/datum/chemical_reaction/emp_pulse/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/emp_pulse/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) // 100 created volume = 4 heavy range & 7 light range. A few tiles smaller than traitor EMP grandes. // 200 created volume = 8 heavy range & 14 light range. 4 tiles larger than traitor EMP grenades. @@ -193,7 +194,7 @@ /datum/chemical_reaction/beesplosion required_reagents = list(/datum/reagent/consumable/honey = 1, /datum/reagent/medicine/strange_reagent = 1, /datum/reagent/uranium/radium = 1) -/datum/chemical_reaction/beesplosion/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/beesplosion/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = holder.my_atom.drop_location() if(created_volume < 5) playsound(location,'sound/effects/sparks1.ogg', 100, TRUE) @@ -219,8 +220,9 @@ results = list(/datum/reagent/clf3 = 4) required_reagents = list(/datum/reagent/chlorine = 1, /datum/reagent/fluorine = 3) required_temp = 424 + overheat_temp = 1050 -/datum/chemical_reaction/clf3/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/clf3/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/turf/T = get_turf(holder.my_atom) for(var/turf/turf in range(1,T)) new /obj/effect/hotspot(turf) @@ -233,7 +235,7 @@ modifier = 5 mob_react = FALSE -/datum/chemical_reaction/reagent_explosion/methsplosion/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/reagent_explosion/methsplosion/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/turf/T = get_turf(holder.my_atom) for(var/turf/turf in range(1,T)) new /obj/effect/hotspot(turf) @@ -248,7 +250,7 @@ results = list(/datum/reagent/sorium = 4) required_reagents = list(/datum/reagent/mercury = 1, /datum/reagent/oxygen = 1, /datum/reagent/nitrogen = 1, /datum/reagent/carbon = 1) -/datum/chemical_reaction/sorium/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/sorium/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) if(holder.has_reagent(/datum/reagent/stabilizing_agent)) return holder.remove_reagent(/datum/reagent/sorium, created_volume*4) @@ -260,7 +262,7 @@ required_reagents = list(/datum/reagent/sorium = 1) required_temp = 474 -/datum/chemical_reaction/sorium_vortex/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/sorium_vortex/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/turf/T = get_turf(holder.my_atom) var/range = clamp(sqrt(created_volume), 1, 6) goonchem_vortex(T, 1, range) @@ -269,7 +271,7 @@ results = list(/datum/reagent/liquid_dark_matter = 3) required_reagents = list(/datum/reagent/stable_plasma = 1, /datum/reagent/uranium/radium = 1, /datum/reagent/carbon = 1) -/datum/chemical_reaction/liquid_dark_matter/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/liquid_dark_matter/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) if(holder.has_reagent(/datum/reagent/stabilizing_agent)) return holder.remove_reagent(/datum/reagent/liquid_dark_matter, created_volume*3) @@ -281,7 +283,7 @@ required_reagents = list(/datum/reagent/liquid_dark_matter = 1) required_temp = 474 -/datum/chemical_reaction/ldm_vortex/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/ldm_vortex/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/turf/T = get_turf(holder.my_atom) var/range = clamp(sqrt(created_volume/2), 1, 6) goonchem_vortex(T, 0, range) @@ -289,8 +291,9 @@ /datum/chemical_reaction/flash_powder results = list(/datum/reagent/flash_powder = 3) required_reagents = list(/datum/reagent/aluminium = 1, /datum/reagent/potassium = 1, /datum/reagent/sulfur = 1 ) + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/flash_powder/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/flash_powder/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) if(holder.has_reagent(/datum/reagent/stabilizing_agent)) return var/location = get_turf(holder.my_atom) @@ -311,7 +314,7 @@ required_reagents = list(/datum/reagent/flash_powder = 1) required_temp = 374 -/datum/chemical_reaction/flash_powder_flash/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/flash_powder_flash/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) do_sparks(2, TRUE, location) var/range = created_volume/10 @@ -328,8 +331,9 @@ /datum/chemical_reaction/smoke_powder results = list(/datum/reagent/smoke_powder = 3) required_reagents = list(/datum/reagent/potassium = 1, /datum/reagent/consumable/sugar = 1, /datum/reagent/phosphorus = 1) + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/smoke_powder/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/smoke_powder/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) if(holder.has_reagent(/datum/reagent/stabilizing_agent)) return holder.remove_reagent(/datum/reagent/smoke_powder, created_volume*3) @@ -348,8 +352,9 @@ required_reagents = list(/datum/reagent/smoke_powder = 1) required_temp = 374 mob_react = FALSE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/smoke_powder_smoke/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/smoke_powder_smoke/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) var/smoke_radius = round(sqrt(created_volume / 2), 1) var/datum/effect_system/smoke_spread/chem/S = new @@ -365,7 +370,7 @@ results = list(/datum/reagent/sonic_powder = 3) required_reagents = list(/datum/reagent/oxygen = 1, /datum/reagent/consumable/space_cola = 1, /datum/reagent/phosphorus = 1) -/datum/chemical_reaction/sonic_powder/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/sonic_powder/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) if(holder.has_reagent(/datum/reagent/stabilizing_agent)) return holder.remove_reagent(/datum/reagent/sonic_powder, created_volume*3) @@ -378,7 +383,7 @@ required_reagents = list(/datum/reagent/sonic_powder = 1) required_temp = 374 -/datum/chemical_reaction/sonic_powder_deafen/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/sonic_powder_deafen/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) playsound(location, 'sound/effects/bang.ogg', 25, TRUE) for(var/mob/living/carbon/C in get_hearers_in_view(created_volume/10, location)) @@ -388,7 +393,7 @@ results = list(/datum/reagent/phlogiston = 3) required_reagents = list(/datum/reagent/phosphorus = 1, /datum/reagent/toxin/acid = 1, /datum/reagent/stable_plasma = 1) -/datum/chemical_reaction/phlogiston/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/phlogiston/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) if(holder.has_reagent(/datum/reagent/stabilizing_agent)) return var/turf/open/T = get_turf(holder.my_atom) @@ -404,8 +409,13 @@ /datum/chemical_reaction/cryostylane results = list(/datum/reagent/cryostylane = 3) required_reagents = list(/datum/reagent/water = 1, /datum/reagent/stable_plasma = 1, /datum/reagent/nitrogen = 1) + is_cold_recipe = TRUE //This is kind of a strange reaction that I will come back to tweak later + required_temp = 1000 + optimal_temp = 20 + overheat_temp = 1 + thermic_constant = -250 -/datum/chemical_reaction/cryostylane/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/cryostylane/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) holder.chem_temp = 20 // cools the fuck down return @@ -413,8 +423,9 @@ results = list(/datum/reagent/cryostylane = 1) required_reagents = list(/datum/reagent/cryostylane = 1, /datum/reagent/oxygen = 1) mob_react = FALSE + thermic_constant = -1 -/datum/chemical_reaction/cryostylane_oxygen/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/cryostylane_oxygen/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) holder.chem_temp = max(holder.chem_temp - 10*created_volume,0) /datum/chemical_reaction/pyrosium_oxygen @@ -422,14 +433,14 @@ required_reagents = list(/datum/reagent/pyrosium = 1, /datum/reagent/oxygen = 1) mob_react = FALSE -/datum/chemical_reaction/pyrosium_oxygen/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/pyrosium_oxygen/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) holder.chem_temp += 10*created_volume /datum/chemical_reaction/pyrosium results = list(/datum/reagent/pyrosium = 3) required_reagents = list(/datum/reagent/stable_plasma = 1, /datum/reagent/uranium/radium = 1, /datum/reagent/phosphorus = 1) -/datum/chemical_reaction/pyrosium/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/pyrosium/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) holder.chem_temp = 20 // also cools the fuck down return @@ -452,7 +463,7 @@ mix_sound = 'sound/machines/defib_zap.ogg' var/zap_flags = ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE | ZAP_MOB_STUN -/datum/chemical_reaction/reagent_explosion/teslium_lightning/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/reagent_explosion/teslium_lightning/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/T1 = created_volume * 20 //100 units : Zap 3 times, with powers 2000/5000/12000. Tesla revolvers have a power of 10000 for comparison. var/T2 = created_volume * 50 var/T3 = created_volume * 120 @@ -483,7 +494,7 @@ required_temp = 575 modifier = 1 -/datum/chemical_reaction/reagent_explosion/nitrous_oxide/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/reagent_explosion/nitrous_oxide/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) holder.remove_reagent(/datum/reagent/sorium, created_volume*2) var/turf/turfie = get_turf(holder.my_atom) //generally half as strong as sorium. @@ -499,3 +510,7 @@ required_reagents = list(/datum/reagent/stabilizing_agent = 1,/datum/reagent/fluorosurfactant = 1,/datum/reagent/carbon = 1) required_temp = 200 is_cold_recipe = 1 + optimal_temp = 50 + overheat_temp = 5 + thermic_constant= -1 + H_ion_release = -0.02 diff --git a/code/modules/reagents/chemistry/recipes/reaction_agents.dm b/code/modules/reagents/chemistry/recipes/reaction_agents.dm new file mode 100644 index 00000000000000..4af750fbb61854 --- /dev/null +++ b/code/modules/reagents/chemistry/recipes/reaction_agents.dm @@ -0,0 +1,115 @@ +/datum/chemical_reaction/basic_buffer + results = list(/datum/reagent/reaction_agent/basic_buffer = 10) + required_reagents = list(/datum/reagent/ammonia = 3, /datum/reagent/chlorine = 2, /datum/reagent/hydrogen = 2, /datum/reagent/oxygen = 2) //vagely NH4OH + NH4Cl buffer + mix_message = "The solution fizzes in the beaker." + //FermiChem vars: + required_temp = 250 + optimal_temp = 500 + overheat_temp = 9999 + optimal_ph_min = 0 + optimal_ph_max = 14 + determin_ph_range = 0 + temp_exponent_factor = 4 + ph_exponent_factor = 0 + thermic_constant = 0 + H_ion_release = 0.01 + rate_up_lim = 15 + purity_min = 0 + +/datum/chemical_reaction/acidic_buffer + results = list(/datum/reagent/reaction_agent/acidic_buffer = 10) + required_reagents = list(/datum/reagent/sodium = 2, /datum/reagent/hydrogen = 2, /datum/reagent/consumable/ethanol = 2, /datum/reagent/water = 2) + mix_message = "The solution froths in the beaker." + required_temp = 250 + optimal_temp = 500 + overheat_temp = 9999 + optimal_ph_min = 0 + optimal_ph_max = 14 + determin_ph_range = 0 + temp_exponent_factor = 4 + ph_exponent_factor = 0 + thermic_constant = 0 + H_ion_release = -0.01 + rate_up_lim = 20 + purity_min = 0 + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Example competitive reaction (REACTION_COMPETITIVE) ////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +/datum/chemical_reaction/prefactor_a + results = list(/datum/reagent/prefactor_a = 5) + required_reagents = list(/datum/reagent/phenol = 1, /datum/reagent/consumable/ethanol = 3, /datum/reagent/toxin/plasma = 1) + mix_message = "The solution's viscosity increases." + is_cold_recipe = TRUE + required_temp = 800 + optimal_temp = 300 + overheat_temp = -1 //no overheat + optimal_ph_min = 2 + optimal_ph_max = 12 + determin_ph_range = 5 + temp_exponent_factor = 1 + ph_exponent_factor = 0 + thermic_constant = -400 + H_ion_release = 0 + rate_up_lim = 4 + purity_min = 0.25 + + +/datum/chemical_reaction/prefactor_b + results = list(/datum/reagent/prefactor_b = 5) + required_reagents = list(/datum/reagent/prefactor_a = 5) + mix_message = "The solution's viscosity decreases." + mix_sound = 'sound/chemistry/bluespace.ogg' //Maybe use this elsewhere instead + required_temp = 50 + optimal_temp = 500 + overheat_temp = 500 + optimal_ph_min = 5 + optimal_ph_max = 8 + determin_ph_range = 5 + temp_exponent_factor = 1 + ph_exponent_factor = 2 + thermic_constant = -800 + H_ion_release = -0.02 + rate_up_lim = 6 + purity_min = 0.35 + reaction_flags = REACTION_COMPETITIVE //Competes with /datum/chemical_reaction/prefactor_a/competitive + +/datum/chemical_reaction/prefactor_b/reaction_step(datum/equilibrium/reaction, datum/reagents/holder, delta_t, delta_ph, step_reaction_vol) + . = ..() + if(holder.has_reagent(/datum/reagent/bluespace)) + holder.remove_reagent(/datum/reagent/bluespace, 1) + reaction.delta_t *= 5 + +/datum/chemical_reaction/prefactor_b/overheated(datum/reagents/holder, datum/equilibrium/equilibrium) + . = ..() + explode_shockwave(holder, equilibrium) + var/vol = max(20, holder.total_volume/5) //Not letting you have more than 5 + clear_reagents(holder, vol)//Lest we explode forever + +/datum/chemical_reaction/prefactor_b/overly_impure(datum/reagents/holder, datum/equilibrium/equilibrium) + explode_fire(holder, equilibrium) + var/vol = max(20, holder.total_volume/5) //Not letting you have more than 5 + clear_reagents(holder, vol) + +/datum/chemical_reaction/prefactor_a/competitive //So we have a back and forth reaction + results = list(/datum/reagent/prefactor_a = 5) + required_reagents = list(/datum/reagent/prefactor_b = 5) + rate_up_lim = 3 + reaction_flags = REACTION_COMPETITIVE //Competes with /datum/chemical_reaction/prefactor_b + +//The actual results +/datum/chemical_reaction/prefactor_a/purity_tester + results = list(/datum/reagent/reaction_agent/purity_tester = 5) + required_reagents = list(/datum/reagent/prefactor_a = 5, /datum/reagent/stable_plasma = 5) + H_ion_release = 0.05 + thermic_constant = 0 + +/datum/chemical_reaction/prefactor_b/speed_agent + results = list(/datum/reagent/reaction_agent/speed_agent = 5) + required_reagents = list(/datum/reagent/prefactor_b = 5, /datum/reagent/stable_plasma = 5) + H_ion_release = -0.15 + thermic_constant = 0 + +////////////////////////////////End example///////////////////////////////////////////////////////////////////////////// diff --git a/code/modules/reagents/chemistry/recipes/slime_extracts.dm b/code/modules/reagents/chemistry/recipes/slime_extracts.dm index 4dc292b5375a2b..3102be75b3571d 100644 --- a/code/modules/reagents/chemistry/recipes/slime_extracts.dm +++ b/code/modules/reagents/chemistry/recipes/slime_extracts.dm @@ -1,8 +1,9 @@ /datum/chemical_reaction/slime var/deletes_extract = TRUE + reaction_flags = REACTION_INSTANT -/datum/chemical_reaction/slime/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) use_slime_core(holder) /datum/chemical_reaction/slime/proc/use_slime_core(datum/reagents/holder) @@ -21,7 +22,7 @@ required_container = /obj/item/slime_extract/grey required_other = TRUE -/datum/chemical_reaction/slime/slimespawn/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimespawn/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/mob/living/simple_animal/slime/S = new(get_turf(holder.my_atom), "grey") S.visible_message("Infused with plasma, the core begins to quiver and grow, and a new baby slime emerges from it!") ..() @@ -37,7 +38,7 @@ required_container = /obj/item/slime_extract/grey required_other = TRUE -/datum/chemical_reaction/slime/slimemonkey/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimemonkey/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) for(var/i in 1 to 3) new /obj/item/food/monkeycube(get_turf(holder.my_atom)) ..() @@ -67,7 +68,7 @@ required_container = /obj/item/slime_extract/metal required_other = TRUE -/datum/chemical_reaction/slime/slimemetal/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimemetal/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/turf/location = get_turf(holder.my_atom) new /obj/item/stack/sheet/plasteel(location, 5) new /obj/item/stack/sheet/iron(location, 15) @@ -78,7 +79,7 @@ required_container = /obj/item/slime_extract/metal required_other = TRUE -/datum/chemical_reaction/slime/slimeglass/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimeglass/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/turf/location = get_turf(holder.my_atom) new /obj/item/stack/sheet/rglass(location, 5) new /obj/item/stack/sheet/glass(location, 15) @@ -91,7 +92,7 @@ required_other = TRUE deletes_extract = FALSE //we do delete, but we don't do so instantly -/datum/chemical_reaction/slime/slimemobspawn/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimemobspawn/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/turf/T = get_turf(holder.my_atom) summon_mobs(holder, T) var/obj/item/slime_extract/M = holder.my_atom @@ -131,7 +132,7 @@ required_container = /obj/item/slime_extract/silver required_other = TRUE -/datum/chemical_reaction/slime/slimebork/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimebork/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) //BORK BORK BORK var/turf/T = get_turf(holder.my_atom) @@ -173,7 +174,7 @@ required_container = /obj/item/slime_extract/blue required_other = TRUE -/datum/chemical_reaction/slime/slimestabilizer/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimestabilizer/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/slimepotion/slime/stabilizer(get_turf(holder.my_atom)) ..() @@ -182,7 +183,7 @@ required_container = /obj/item/slime_extract/blue required_other = TRUE -/datum/chemical_reaction/slime/slimefoam/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimefoam/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) holder.create_foam(/datum/effect_system/foam_spread,80, "[src] spews out foam!") //Dark Blue @@ -192,7 +193,7 @@ required_other = TRUE deletes_extract = FALSE -/datum/chemical_reaction/slime/slimefreeze/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimefreeze/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/turf/T = get_turf(holder.my_atom) T.visible_message("The slime extract starts to feel extremely cold!") addtimer(CALLBACK(src, .proc/freeze, holder), 50) @@ -213,7 +214,7 @@ required_container = /obj/item/slime_extract/darkblue required_other = TRUE -/datum/chemical_reaction/slime/slimefireproof/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimefireproof/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/slimepotion/fireproof(get_turf(holder.my_atom)) ..() @@ -230,7 +231,7 @@ required_other = TRUE deletes_extract = FALSE -/datum/chemical_reaction/slime/slimefire/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimefire/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/turf/T = get_turf(holder.my_atom) T.visible_message("The slime extract begins to vibrate adorably!") addtimer(CALLBACK(src, .proc/slime_burn, holder), 50) @@ -258,7 +259,7 @@ required_container = /obj/item/slime_extract/yellow required_other = TRUE -/datum/chemical_reaction/slime/slimeoverload/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/slime/slimeoverload/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) empulse(get_turf(holder.my_atom), 3, 7) ..() @@ -267,7 +268,7 @@ required_container = /obj/item/slime_extract/yellow required_other = TRUE -/datum/chemical_reaction/slime/slimeglow/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimeglow/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/turf/T = get_turf(holder.my_atom) T.visible_message("The slime begins to emit a soft light. Squeezing it will cause it to grow brightly.") new /obj/item/flashlight/slime(T) @@ -279,7 +280,7 @@ required_container = /obj/item/slime_extract/purple required_other = TRUE -/datum/chemical_reaction/slime/slimepsteroid/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimepsteroid/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/slimepotion/slime/steroid(get_turf(holder.my_atom)) ..() @@ -295,7 +296,7 @@ required_container = /obj/item/slime_extract/darkpurple required_other = TRUE -/datum/chemical_reaction/slime/slimeplasma/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimeplasma/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/stack/sheet/mineral/plasma(get_turf(holder.my_atom), 3) ..() @@ -305,7 +306,7 @@ required_container = /obj/item/slime_extract/red required_other = TRUE -/datum/chemical_reaction/slime/slimemutator/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimemutator/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/slimepotion/slime/mutator(get_turf(holder.my_atom)) ..() @@ -314,7 +315,7 @@ required_container = /obj/item/slime_extract/red required_other = TRUE -/datum/chemical_reaction/slime/slimebloodlust/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimebloodlust/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) for(var/mob/living/simple_animal/slime/slime in viewers(get_turf(holder.my_atom), null)) if(slime.docile) //Undoes docility, but doesn't make rabid. slime.visible_message("[slime] forgets its training, becoming wild once again!") @@ -330,7 +331,7 @@ required_container = /obj/item/slime_extract/red required_other = TRUE -/datum/chemical_reaction/slime/slimespeed/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimespeed/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/slimepotion/speed(get_turf(holder.my_atom)) ..() @@ -340,7 +341,7 @@ required_container = /obj/item/slime_extract/pink required_other = TRUE -/datum/chemical_reaction/slime/docility/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/docility/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/slimepotion/slime/docility(get_turf(holder.my_atom)) ..() @@ -349,7 +350,7 @@ required_container = /obj/item/slime_extract/pink required_other = TRUE -/datum/chemical_reaction/slime/gender/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/gender/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/slimepotion/genderchange(get_turf(holder.my_atom)) ..() @@ -367,7 +368,7 @@ required_other = TRUE deletes_extract = FALSE -/datum/chemical_reaction/slime/slimeexplosion/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimeexplosion/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/turf/T = get_turf(holder.my_atom) var/lastkey = holder.my_atom.fingerprintslast var/touch_msg = "N/A" @@ -400,7 +401,7 @@ required_reagents = list(/datum/reagent/toxin/plasma = 1) required_other = TRUE -/datum/chemical_reaction/slime/slimepotion2/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimepotion2/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/slimepotion/slime/sentience(get_turf(holder.my_atom)) ..() @@ -409,7 +410,7 @@ required_reagents = list(/datum/reagent/water = 1) required_other = TRUE -/datum/chemical_reaction/slime/renaming/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/renaming/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/slimepotion/slime/renaming(holder.my_atom.drop_location()) ..() @@ -420,7 +421,7 @@ required_container = /obj/item/slime_extract/adamantine required_other = TRUE -/datum/chemical_reaction/slime/adamantine/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/adamantine/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/stack/sheet/mineral/adamantine(get_turf(holder.my_atom)) ..() @@ -430,7 +431,7 @@ required_container = /obj/item/slime_extract/bluespace required_other = TRUE -/datum/chemical_reaction/slime/slimefloor2/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/slime/slimefloor2/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/stack/tile/bluespace(get_turf(holder.my_atom), 25) ..() @@ -440,7 +441,7 @@ required_container = /obj/item/slime_extract/bluespace required_other = TRUE -/datum/chemical_reaction/slime/slimecrystal/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/slime/slimecrystal/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/obj/item/stack/ore/bluespace_crystal/BC = new (get_turf(holder.my_atom)) BC.visible_message("The [BC.name] appears out of thin air!") ..() @@ -450,7 +451,7 @@ required_container = /obj/item/slime_extract/bluespace required_other = TRUE -/datum/chemical_reaction/slime/slimeradio/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/slime/slimeradio/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/slimepotion/slime/slimeradio(get_turf(holder.my_atom)) ..() @@ -460,7 +461,7 @@ required_container = /obj/item/slime_extract/cerulean required_other = TRUE -/datum/chemical_reaction/slime/slimepsteroid2/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimepsteroid2/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/slimepotion/enhancer(get_turf(holder.my_atom)) ..() @@ -469,7 +470,7 @@ required_container = /obj/item/slime_extract/cerulean required_other = TRUE -/datum/chemical_reaction/slime/slime_territory/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slime_territory/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/areaeditor/blueprints/slime(get_turf(holder.my_atom)) ..() @@ -479,7 +480,7 @@ required_container = /obj/item/slime_extract/sepia required_other = TRUE -/datum/chemical_reaction/slime/slimestop/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimestop/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) addtimer(CALLBACK(src, .proc/slime_stop, holder), 5 SECONDS) /datum/chemical_reaction/slime/slimestop/proc/slime_stop(datum/reagents/holder) @@ -498,7 +499,7 @@ required_container = /obj/item/slime_extract/sepia required_other = TRUE -/datum/chemical_reaction/slime/slimecamera/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimecamera/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/camera(get_turf(holder.my_atom)) new /obj/item/camera_film(get_turf(holder.my_atom)) ..() @@ -508,7 +509,7 @@ required_container = /obj/item/slime_extract/sepia required_other = TRUE -/datum/chemical_reaction/slime/slimefloor/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimefloor/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/stack/tile/sepia(get_turf(holder.my_atom), 25) ..() @@ -518,7 +519,7 @@ required_container = /obj/item/slime_extract/pyrite required_other = TRUE -/datum/chemical_reaction/slime/slimepaint/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimepaint/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/chosen = pick(subtypesof(/obj/item/paint)) new chosen(get_turf(holder.my_atom)) ..() @@ -528,7 +529,7 @@ required_container = /obj/item/slime_extract/pyrite required_other = TRUE -/datum/chemical_reaction/slime/slimecrayon/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slimecrayon/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/chosen = pick(difflist(subtypesof(/obj/item/toy/crayon),typesof(/obj/item/toy/crayon/spraycan))) new chosen(get_turf(holder.my_atom)) ..() @@ -539,7 +540,7 @@ required_other = TRUE required_container = /obj/item/slime_extract/rainbow -/datum/chemical_reaction/slime/slime_rng/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/slime/slime_rng/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) if(created_volume >= 5) var/obj/item/grenade/clusterbuster/slime/S = new (get_turf(holder.my_atom)) S.visible_message("Infused with plasma, the core begins to expand uncontrollably!") @@ -556,7 +557,7 @@ required_other = TRUE required_container = /obj/item/slime_extract/rainbow -/datum/chemical_reaction/slime/slimebomb/on_reaction(datum/reagents/holder, created_volume) +/datum/chemical_reaction/slime/slimebomb/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) var/turf/T = get_turf(holder.my_atom) var/obj/item/grenade/clusterbuster/slime/volatile/S = new (T) S.visible_message("Infused with slime jelly, the core begins to expand uncontrollably!") @@ -577,7 +578,7 @@ required_other = TRUE required_container = /obj/item/slime_extract/rainbow -/datum/chemical_reaction/slime/slime_transfer/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/slime_transfer/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/slimepotion/transference(get_turf(holder.my_atom)) ..() @@ -586,6 +587,6 @@ required_other = TRUE required_container = /obj/item/slime_extract/rainbow -/datum/chemical_reaction/slime/flight_potion/on_reaction(datum/reagents/holder) +/datum/chemical_reaction/slime/flight_potion/on_reaction(datum/equilibrium/reaction, datum/reagents/holder, created_volume) new /obj/item/reagent_containers/glass/bottle/potion/flight(get_turf(holder.my_atom)) ..() diff --git a/code/modules/reagents/chemistry/recipes/special.dm b/code/modules/reagents/chemistry/recipes/special.dm index f257ea821411dc..a016d4c4b65095 100644 --- a/code/modules/reagents/chemistry/recipes/special.dm +++ b/code/modules/reagents/chemistry/recipes/special.dm @@ -34,6 +34,13 @@ GLOBAL_LIST_INIT(medicine_reagents, build_medicine_reagents()) /datum/chemical_reaction/randomized + //Increase default leniency because these are already hard enough + optimal_ph_min = 1 + optimal_ph_max = 13 + temp_exponent_factor = 0 + ph_exponent_factor = 1 + H_ion_release = 0 + var/persistent = FALSE var/persistence_period = 7 //Will reset every x days var/created //creation timestamp @@ -44,6 +51,22 @@ GLOBAL_LIST_INIT(medicine_reagents, build_medicine_reagents()) var/randomize_req_temperature = TRUE var/min_temp = 1 var/max_temp = 600 + ///If the reaction can be exothermic or endothermic randomly too + var/exo_or_endothermic = FALSE + + ///If pH is randomised + var/randomize_req_ph = FALSE + ///Lowest pH value possible + var/min_ph = 0 + ///Highest pH value possible + var/max_ph = 14 + ///How much the range can deviate, and also affects impure range + var/inoptimal_range_ph = 3 + + ///If the impurity failure threshold is randomized between 0 - 0.4 + var/randomize_impurity_minimum = FALSE + ///If the impure/inverse/failure reagents are randomized + var/randomize_impurity_reagents = FALSE var/randomize_inputs = TRUE var/min_input_reagent_amount = 1 @@ -67,8 +90,39 @@ GLOBAL_LIST_INIT(medicine_reagents, build_medicine_reagents()) if(randomize_container) required_container = pick(possible_containers) if(randomize_req_temperature) - required_temp = rand(min_temp,max_temp) is_cold_recipe = pick(TRUE,FALSE) + if(is_cold_recipe) + required_temp = rand(min_temp+50, max_temp) + optimal_temp = rand(min_temp+25, required_temp-10) + overheat_temp = rand(min_temp, optimal_temp-10) + if(overheat_temp >= 200) //Otherwise it can disappear when you're mixing and I don't want this to happen here + overheat_temp = 200 + if(exo_or_endothermic) + thermic_constant = (rand(-200, 200)) + else + required_temp = rand(min_temp, max_temp-50) + optimal_temp = rand(required_temp+10, max_temp-25) + overheat_temp = rand(optimal_temp, max_temp+50) + if(overheat_temp <= 400) + overheat_temp = 400 + if(exo_or_endothermic) + thermic_constant = (rand(-200, 200)) + + if(randomize_req_ph) + optimal_ph_min = min_ph + rand(0, inoptimal_range_ph) + optimal_ph_max = max((max_ph + rand(0, inoptimal_range_ph)), (min_ph + 1)) //Always ensure we've a window of 1 + determin_ph_range = inoptimal_range_ph + H_ion_release = (rand(0, 25)/100)// 0 - 0.25 + + if(randomize_impurity_minimum) + purity_min = (rand(0, 4)/10) + + if(randomize_impurity_reagents) + for(var/rid in required_reagents) + var/datum/reagent/R = GLOB.chemical_reagents_list[rid] + R.impure_chem = get_random_reagent_id() + R.inverse_chem = get_random_reagent_id() + R.failed_chem = get_random_reagent_id() if(randomize_results) results = list() @@ -141,8 +195,18 @@ GLOBAL_LIST_INIT(medicine_reagents, build_medicine_reagents()) return FALSE required_catalysts = req_catalysts - required_temp = recipe_data["required_temp"] is_cold_recipe = recipe_data["is_cold_recipe"] + required_temp = recipe_data["required_temp"] + optimal_temp = recipe_data["optimal_temp"] + overheat_temp = recipe_data["overheat_temp"] + thermic_constant = recipe_data["thermic_constant"] + + optimal_ph_min = recipe_data["optimal_ph_min"] + optimal_ph_max = recipe_data["optimal_ph_max"] + determin_ph_range = recipe_data["determin_ph_range"] + H_ion_release = recipe_data["H_ion_release"] + + purity_min = recipe_data["purity_min"] var/temp_results = unwrap_reagent_list(recipe_data["results"]) if(!temp_results) @@ -226,15 +290,28 @@ GLOBAL_LIST_INIT(medicine_reagents, build_medicine_reagents()) var/datum/reagent/R = GLOB.chemical_reagents_list[rid] dat += "
  • [recipe.required_catalysts[rid]]u of [R.name]
  • " dat += "" - dat += "Mix slowly" + dat += "Mix slowly
      " if(recipe.required_container) var/obj/item/I = recipe.required_container - dat += " in [initial(I.name)]" + dat += "
    • in a [initial(I.name)]
    • " if(recipe.required_temp != 0) if(recipe.is_cold_recipe) - dat += " below [recipe.required_temp] degrees" + dat += "
    • cooling it below [recipe.required_temp] degrees" + dat += " but not below [recipe.overheat_temp] degrees
    • " else - dat += " above [recipe.required_temp] degrees" + dat += "
    • heating it above [recipe.required_temp] degrees" + dat += " but not above [recipe.overheat_temp] degrees" + if(recipe.thermic_constant > 0) + dat += "
    • taking care of it's exothermic nature
    • " + else if(recipe.thermic_constant < 0) + dat += "
    • taking care of it's endothermic nature
    • " + var/datum/chemical_reaction/randomized/random_recipe = recipe + if(random_recipe) + if(random_recipe.randomize_req_ph) + dat += "
    • keeping your pH between [recipe.optimal_ph_min] and [recipe.optimal_ph_max]
    • " + if(random_recipe.randomize_impurity_minimum) + dat += "
    • and your purity above [recipe.purity_min]
    • " + dat += "
    " dat += "." info = dat.Join("") update_icon() diff --git a/code/modules/reagents/chemistry/recipes/toxins.dm b/code/modules/reagents/chemistry/recipes/toxins.dm index ee7515e36d29b4..dc84a712ebb7bc 100644 --- a/code/modules/reagents/chemistry/recipes/toxins.dm +++ b/code/modules/reagents/chemistry/recipes/toxins.dm @@ -48,6 +48,7 @@ results = list(/datum/reagent/toxin/plasma = 12) //One sheet of hot ice makes 200m of plasma required_reagents = list(/datum/reagent/toxin/hot_ice = 1) required_temp = T0C + 30 //Don't burst into flames when you melt + thermic_constant = -200//Counter the heat /datum/chemical_reaction/chloralhydrate results = list(/datum/reagent/toxin/chloralhydrate = 1) diff --git a/code/modules/reagents/reagent_containers/bottle.dm b/code/modules/reagents/reagent_containers/bottle.dm index 0780ba5173aac1..a3dc843946dd71 100644 --- a/code/modules/reagents/reagent_containers/bottle.dm +++ b/code/modules/reagents/reagent_containers/bottle.dm @@ -207,6 +207,31 @@ desc = "A small bottle of atropine." list_reagents = list(/datum/reagent/medicine/atropine = 30) +/obj/item/reagent_containers/glass/bottle/random_buffer + name = "Buffer bottle" + desc = "A small bottle of chemical buffer." + +/obj/item/reagent_containers/glass/bottle/random_buffer/Initialize() + . = ..() + if(prob(50)) + name = "Acidic buffer bottle" + desc = "A small bottle of acidic buffer." + reagents.add_reagent(/datum/reagent/reaction_agent/acidic_buffer, 30) + else + name = "Basic buffer bottle" + desc = "A small bottle of basic buffer." + reagents.add_reagent(/datum/reagent/reaction_agent/basic_buffer, 30) + +/obj/item/reagent_containers/glass/bottle/acidic_buffer + name = "Acidic buffer bottle" + desc = "A small bottle of acidic buffer." + list_reagents = list(/datum/reagent/reaction_agent/acidic_buffer = 30) + +/obj/item/reagent_containers/glass/bottle/basic_buffer + name = "Basic buffer bottle" + desc = "A small bottle of basic buffer." + list_reagents = list(/datum/reagent/reaction_agent/basic_buffer = 30) + /obj/item/reagent_containers/glass/bottle/romerol name = "romerol bottle" desc = "A small bottle of Romerol. The REAL zombie powder." diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm index 1f4f7e9fd290ba..0eb508e6c166b6 100644 --- a/code/modules/research/designs/medical_designs.dm +++ b/code/modules/research/designs/medical_designs.dm @@ -71,6 +71,15 @@ build_path = /obj/item/reagent_containers/glass/beaker/meta category = list("Medical Designs") +/datum/design/ph_meter + name = "Chemical analyser" + id = "ph_meter" + build_type = PROTOLATHE + departmental_flags = DEPARTMENTAL_FLAG_MEDICAL + materials = list(/datum/material/glass = 2500, /datum/material/gold = 1000, /datum/material/titanium = 1000) + build_path = /obj/item/ph_meter + category = list("Medical Designs") + /datum/design/bluespacesyringe name = "Bluespace Syringe" desc = "An advanced syringe that can hold 60 units of chemicals" diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 502f8907928950..87cacd377634d8 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -75,7 +75,7 @@ display_name = "Advanced Biotechnology" description = "Advanced Biotechnology" prereq_ids = list("biotech") - design_ids = list("piercesyringe", "crewpinpointer", "smoke_machine", "plasmarefiller", "limbgrower", "meta_beaker", "healthanalyzer_advanced", "harvester", "holobarrier_med", "detective_scanner", "defibrillator_compact") + design_ids = list("piercesyringe", "crewpinpointer", "smoke_machine", "plasmarefiller", "limbgrower", "meta_beaker", "healthanalyzer_advanced", "harvester", "holobarrier_med", "detective_scanner", "defibrillator_compact", "ph_meter") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) /datum/techweb_node/bio_process diff --git a/code/modules/surgery/organs/liver.dm b/code/modules/surgery/organs/liver.dm index 7ec184309959ca..2b6bb87102aea5 100755 --- a/code/modules/surgery/organs/liver.dm +++ b/code/modules/surgery/organs/liver.dm @@ -105,6 +105,21 @@ if(damage > maxHealth)//cap liver damage damage = maxHealth +/obj/item/organ/liver/on_death() + . = ..() + var/mob/living/carbon/carbon_owner = owner + if(!owner)//If we're outside of a mob + return + if(!iscarbon(carbon_owner)) + CRASH("on_death() called for [src] ([type]) with invalid owner ([isnull(owner) ? "null" : owner.type])") + if(carbon_owner.stat != DEAD) + CRASH("on_death() called for [src] ([type]) with not-dead owner ([owner])") + if((organ_flags & ORGAN_FAILING) && HAS_TRAIT(carbon_owner, TRAIT_NOMETABOLISM))//can't process reagents with a failing liver + return + for(var/reagent in carbon_owner.reagents.reagent_list) + var/datum/reagent/R = reagent + R.on_mob_dead(carbon_owner) + #undef HAS_SILENT_TOXIN #undef HAS_NO_TOXIN #undef HAS_PAINFUL_TOXIN diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm index 8e48ccd0f20a4d..85224d27f394a7 100644 --- a/code/modules/vending/wardrobes.dm +++ b/code/modules/vending/wardrobes.dm @@ -401,7 +401,8 @@ /obj/item/storage/backpack/chemistry = 2, /obj/item/storage/backpack/satchel/chem = 2, /obj/item/storage/backpack/duffelbag/chemistry = 2, - /obj/item/storage/bag/chemistry = 2) + /obj/item/storage/bag/chemistry = 2, + /obj/item/ph_booklet = 3) contraband = list(/obj/item/reagent_containers/spray/syndicate = 2) refill_canister = /obj/item/vending_refill/wardrobe/chem_wardrobe payment_department = ACCOUNT_MED diff --git a/icons/UI_Icons/Achievements/Misc/chem_tut.png b/icons/UI_Icons/Achievements/Misc/chem_tut.png new file mode 100644 index 00000000000000..4edc877f7e5c1d Binary files /dev/null and b/icons/UI_Icons/Achievements/Misc/chem_tut.png differ diff --git a/icons/UI_Icons/Advisors/chem_help_advisor.gif b/icons/UI_Icons/Advisors/chem_help_advisor.gif new file mode 100644 index 00000000000000..9bb2959974b247 Binary files /dev/null and b/icons/UI_Icons/Advisors/chem_help_advisor.gif differ diff --git a/icons/obj/chemical.dmi b/icons/obj/chemical.dmi index 2a2e9c65c18f50..41ca84d9f45b36 100644 Binary files a/icons/obj/chemical.dmi and b/icons/obj/chemical.dmi differ diff --git a/sound/chemistry/SoundSources.txt b/sound/chemistry/SoundSources.txt new file mode 100644 index 00000000000000..9148efb61e356e --- /dev/null +++ b/sound/chemistry/SoundSources.txt @@ -0,0 +1,16 @@ +heatmelt.ogg - from https://freesound.org/people/toiletrolltube/sounds/181483/ + from https://freesound.org/people/MrVasLuk/sounds/304619/ + from https://freesound.org/people/Benboncan/sounds/74899/ + from bubbles2.ogg +heatacid.ogg - from https://freesound.org/people/klankbeeld/sounds/233697/ + from bubbles2.ogg + from fuse.ogg +bufferadd.ogg- https://freesound.org/people/toiletrolltube/sounds/181483/ +heatdamn.ogg - from https://freesound.org/people/klankbeeld/sounds/233697/ +catalyst.ogg - from: https://freesound.org/people/Melthurian/sounds/319384/ + from: https://freesound.org/people/The_Chemical_Workshop/sounds/408137/ +bluespace.ogg - from: https://freesound.org/people/Agonda/sounds/210582/ +shockwave_explosion.ogg from: https://freesound.org/people/Timbre/sounds/115659/ + from: https://freesound.org/people/AVA_MUSIC_GROUP/sounds/397147/ + +Work is licensed under the Creative Commons and Attribution License. \ No newline at end of file diff --git a/sound/chemistry/acidmelt.ogg b/sound/chemistry/acidmelt.ogg new file mode 100644 index 00000000000000..bef257be557ef9 Binary files /dev/null and b/sound/chemistry/acidmelt.ogg differ diff --git a/sound/chemistry/bluespace.ogg b/sound/chemistry/bluespace.ogg new file mode 100644 index 00000000000000..253e62702eaf6c Binary files /dev/null and b/sound/chemistry/bluespace.ogg differ diff --git a/sound/chemistry/bufferadd.ogg b/sound/chemistry/bufferadd.ogg new file mode 100644 index 00000000000000..31bc4404252e60 Binary files /dev/null and b/sound/chemistry/bufferadd.ogg differ diff --git a/sound/chemistry/catalyst.ogg b/sound/chemistry/catalyst.ogg new file mode 100644 index 00000000000000..f26115bfc73ae2 Binary files /dev/null and b/sound/chemistry/catalyst.ogg differ diff --git a/sound/chemistry/heatdam.ogg b/sound/chemistry/heatdam.ogg new file mode 100644 index 00000000000000..ab8492e9660a47 Binary files /dev/null and b/sound/chemistry/heatdam.ogg differ diff --git a/sound/chemistry/heatmelt.ogg b/sound/chemistry/heatmelt.ogg new file mode 100644 index 00000000000000..87dab3a18ff47a Binary files /dev/null and b/sound/chemistry/heatmelt.ogg differ diff --git a/sound/chemistry/shockwave_explosion.ogg b/sound/chemistry/shockwave_explosion.ogg new file mode 100644 index 00000000000000..2dcfaa6c3a6de1 Binary files /dev/null and b/sound/chemistry/shockwave_explosion.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 0d5bcf500d95f3..5d1f5a42adc7c0 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -352,6 +352,7 @@ #include "code\controllers\subsystem\processing\processing.dm" #include "code\controllers\subsystem\processing\projectiles.dm" #include "code\controllers\subsystem\processing\quirks.dm" +#include "code\controllers\subsystem\processing\reagents.dm" #include "code\controllers\subsystem\processing\station.dm" #include "code\controllers\subsystem\processing\wet_floors.dm" #include "code\datums\action.dm" @@ -2914,30 +2915,38 @@ #include "code\modules\reagents\reagent_containers.dm" #include "code\modules\reagents\reagent_dispenser.dm" #include "code\modules\reagents\chemistry\colors.dm" +#include "code\modules\reagents\chemistry\equilibrium.dm" #include "code\modules\reagents\chemistry\holder.dm" +#include "code\modules\reagents\chemistry\items.dm" #include "code\modules\reagents\chemistry\reagents.dm" #include "code\modules\reagents\chemistry\recipes.dm" #include "code\modules\reagents\chemistry\machinery\chem_dispenser.dm" #include "code\modules\reagents\chemistry\machinery\chem_heater.dm" #include "code\modules\reagents\chemistry\machinery\chem_master.dm" +#include "code\modules\reagents\chemistry\machinery\chem_recipe_debug.dm" #include "code\modules\reagents\chemistry\machinery\chem_synthesizer.dm" #include "code\modules\reagents\chemistry\machinery\pandemic.dm" #include "code\modules\reagents\chemistry\machinery\reagentgrinder.dm" #include "code\modules\reagents\chemistry\machinery\smoke_machine.dm" #include "code\modules\reagents\chemistry\reagents\alcohol_reagents.dm" #include "code\modules\reagents\chemistry\reagents\cat2_medicine_reagents.dm" +#include "code\modules\reagents\chemistry\reagents\catalyst_reagents.dm" #include "code\modules\reagents\chemistry\reagents\drink_reagents.dm" #include "code\modules\reagents\chemistry\reagents\drug_reagents.dm" #include "code\modules\reagents\chemistry\reagents\food_reagents.dm" +#include "code\modules\reagents\chemistry\reagents\impure_reagents.dm" #include "code\modules\reagents\chemistry\reagents\medicine_reagents.dm" #include "code\modules\reagents\chemistry\reagents\other_reagents.dm" #include "code\modules\reagents\chemistry\reagents\pyrotechnic_reagents.dm" +#include "code\modules\reagents\chemistry\reagents\reaction_agents_reagents.dm" #include "code\modules\reagents\chemistry\reagents\toxin_reagents.dm" #include "code\modules\reagents\chemistry\recipes\cat2_medicines.dm" +#include "code\modules\reagents\chemistry\recipes\catalysts.dm" #include "code\modules\reagents\chemistry\recipes\drugs.dm" #include "code\modules\reagents\chemistry\recipes\medicine.dm" #include "code\modules\reagents\chemistry\recipes\others.dm" #include "code\modules\reagents\chemistry\recipes\pyrotechnics.dm" +#include "code\modules\reagents\chemistry\recipes\reaction_agents.dm" #include "code\modules\reagents\chemistry\recipes\slime_extracts.dm" #include "code\modules\reagents\chemistry\recipes\special.dm" #include "code\modules\reagents\chemistry\recipes\toxins.dm" diff --git a/tgui/packages/tgui/interfaces/ChemDispenser.js b/tgui/packages/tgui/interfaces/ChemDispenser.js index 2862970076cd8f..3acef433546b9d 100644 --- a/tgui/packages/tgui/interfaces/ChemDispenser.js +++ b/tgui/packages/tgui/interfaces/ChemDispenser.js @@ -1,12 +1,15 @@ import { toFixed } from 'common/math'; import { toTitleCase } from 'common/string'; -import { useBackend } from '../backend'; +import { Fragment } from 'inferno'; +import { useBackend, useLocalState } from '../backend'; import { AnimatedNumber, Box, Button, Icon, LabeledList, ProgressBar, Section } from '../components'; import { Window } from '../layouts'; export const ChemDispenser = (props, context) => { const { act, data } = useBackend(context); const recording = !!data.recordingRecipe; + const [hasCol, setHasCol] = useLocalState( + context, 'has_col', false); // TODO: Change how this piece of shit is built on server side // It has to be a list, not a fucking OBJECT! const recipes = Object.keys(data.recipes) @@ -31,11 +34,22 @@ export const ChemDispenser = (props, context) => {
    - - Recording - + buttons={( + + {recording && ( + + + Recording + + )} +
    diff --git a/tgui/packages/tgui/interfaces/ChemHeater.js b/tgui/packages/tgui/interfaces/ChemHeater.js index 40e09164ae8ac8..b70e6ba3eac557 100644 --- a/tgui/packages/tgui/interfaces/ChemHeater.js +++ b/tgui/packages/tgui/interfaces/ChemHeater.js @@ -1,61 +1,293 @@ +import { resolveAsset } from '../assets'; import { round, toFixed } from 'common/math'; import { useBackend } from '../backend'; -import { AnimatedNumber, Box, Button, LabeledList, NumberInput, Section } from '../components'; +import { AnimatedNumber, Box, Button, NumberInput, Section, ProgressBar, Table, RoundGauge, Flex, Icon, TextArea } from '../components'; +import { TableCell, TableRow } from '../components/Table'; import { Window } from '../layouts'; import { BeakerContents } from './common/BeakerContents'; + export const ChemHeater = (props, context) => { const { act, data } = useBackend(context); const { targetTemp, isActive, + isFlashing, + currentpH, isBeakerLoaded, currentTemp, beakerCurrentVolume, beakerMaxVolume, + acidicBufferVol, + basicBufferVol, + dispenseVolume, + upgradeLevel, + tutorialMessage, beakerContents = [], + activeReactions = [], } = data; - return ( + return ( + width={330} + height={tutorialMessage ? 680 : 350}>
    act('power')} /> + +
    + {!!isBeakerLoaded && ( +
    + toFixed(value) + ' K'} /> - ) || '—'} + value={currentpH} + format={value => 'pH: ' + round(value, 3)} /> + + + + {(_, value) => ( + null} + ranges={{ + "red": [-0.22, 1.5], + "orange": [1.5, 3], + "yellow": [3, 4.5], + "olive": [4.5, 5], + "good": [5, 6], + "green": [6, 8.5], + "teal": [8.5, 9.5], + "blue": [9.5, 11], + "purple": [11, 12.5], + "violet": [12.5, 14], + }} /> + )} + + + + )}> + {activeReactions.length === 0 && ( + + No active reactions. - - -
    + ) || ( + + + + Reaction + + + {upgradeLevel < 4 ? "Status" : "Reaction quality"} + + + Target + + + {activeReactions.map(reaction => ( + + + {reaction.name} + + + {upgradeLevel < 4 && ( + + ) || ( + + {(_, value) => ( + null} + ml={5} + ranges={{ + "red": [0, reaction.minPure], + "orange": [reaction.minPure, reaction.inverse], + "yellow": [reaction.inverse, 0.8], + "green": [0.8, 1], + }} /> + )} + + )} + + + {upgradeLevel > 2 && ( + + {reaction.targetVol}u + + ) || ( + + {reaction.targetVol}u + + )} + + + ))} + +
    + )} + + )} + {tutorialMessage && ( +
    + + {tutorialMessage} +
    + )}
    { {analyzeVars.state} + + {analyzeVars.ph} + {analyzeVars.color} diff --git a/tgui/packages/tgui/interfaces/ChemReactionChamber.js b/tgui/packages/tgui/interfaces/ChemReactionChamber.js index df9e0567be957e..62d5ec94800a09 100644 --- a/tgui/packages/tgui/interfaces/ChemReactionChamber.js +++ b/tgui/packages/tgui/interfaces/ChemReactionChamber.js @@ -1,8 +1,9 @@ import { map } from 'common/collections'; import { classes } from 'common/react'; import { useBackend, useLocalState } from '../backend'; -import { Box, Button, Input, LabeledList, NumberInput, Section } from '../components'; +import { AnimatedNumber, Box, Button, Flex, Input, LabeledList, NumberInput, Section, RoundGauge } from '../components'; import { Window } from '../layouts'; +import { round, toFixed } from 'common/math'; export const ChemReactionChamber = (props, context) => { const { act, data } = useBackend(context); @@ -14,22 +15,99 @@ export const ChemReactionChamber = (props, context) => { reagentQuantity, setReagentQuantity, ] = useLocalState(context, 'reagentQuantity', 1); - const emptying = data.emptying; + const { + emptying, + temperature, + ph, + targetTemp, + isReacting, + } = data; const reagents = data.reagents || []; return ( + height={280}> +
    + + {"Target:"} + + act('temperature', { + target: value, + })} /> + + )}> + + + toFixed(value) + ' K'} /> + + + + + {(_, value) => ( + null} + left={-7.5} + position="absolute" + size={1.50} + ranges={{ + "red": [-0.22, 1.5], + "orange": [1.5, 3], + "yellow": [3, 4.5], + "olive": [4.5, 5], + "good": [5, 6], + "green": [6, 8.5], + "teal": [8.5, 9.5], + "blue": [9.5, 11], + "purple": [11, 12.5], + "violet": [12.5, 14], + }} /> + )} + + + round(value, 3)} /> + + + + +
    - {emptying ? "Emptying" : "Filling"} - + isReacting && ( + + {"Reacting"} + + ) || ( + + {emptying ? "Emptying" : "Filling"} + + ) )}> diff --git a/tgui/packages/tgui/interfaces/common/BeakerContents.js b/tgui/packages/tgui/interfaces/common/BeakerContents.js index 6517c844616c92..0dae690513ddc1 100644 --- a/tgui/packages/tgui/interfaces/common/BeakerContents.js +++ b/tgui/packages/tgui/interfaces/common/BeakerContents.js @@ -1,4 +1,4 @@ -import { Box } from '../../components'; +import { AnimatedNumber, Box } from '../../components'; export const BeakerContents = props => { const { beakerLoaded, beakerContents } = props; @@ -15,7 +15,10 @@ export const BeakerContents = props => { )} {beakerContents.map(chemical => ( - {chemical.volume} units of {chemical.name} + + {" units of "+chemical.name} ))}