diff --git a/_maps/RandomRuins/SpaceRuins/originalcontent.dmm b/_maps/RandomRuins/SpaceRuins/originalcontent.dmm index 5f0dd0434a..2945a06b25 100644 --- a/_maps/RandomRuins/SpaceRuins/originalcontent.dmm +++ b/_maps/RandomRuins/SpaceRuins/originalcontent.dmm @@ -913,6 +913,10 @@ }, /turf/open/indestructible/paper, /area/ruin/powered) +"Ns" = ( +/obj/item/paper/secretrecipe, +/turf/open/indestructible/paper, +/area/ruin/powered) (1,1,1) = {" aa @@ -2247,7 +2251,7 @@ ap az af al -aA +Ns bW ca af diff --git a/code/__DEFINES/food.dm b/code/__DEFINES/food.dm index 2c7a66ce40..15c7af5749 100644 --- a/code/__DEFINES/food.dm +++ b/code/__DEFINES/food.dm @@ -16,3 +16,4 @@ #define DRINK_GOOD 2 #define DRINK_VERYGOOD 3 #define DRINK_FANTASTIC 4 +#define FOOD_AMAZING 5 diff --git a/code/__HELPERS/reagents.dm b/code/__HELPERS/reagents.dm new file mode 100644 index 0000000000..f1208abdd3 --- /dev/null +++ b/code/__HELPERS/reagents.dm @@ -0,0 +1,74 @@ +/proc/chem_recipes_do_conflict(datum/chemical_reaction/r1, datum/chemical_reaction/r2) + //do the non-list tests first, because they are cheaper + if(r1.required_container != r2.required_container) + return FALSE + if(r1.is_cold_recipe == r2.is_cold_recipe) + if(r1.required_temp != r2.required_temp) + //one reaction requires a more extreme temperature than the other, so there is no conflict + return FALSE + else + var/datum/chemical_reaction/cold_one = r1.is_cold_recipe ? r1 : r2 + var/datum/chemical_reaction/warm_one = r1.is_cold_recipe ? r2 : r1 + if(cold_one.required_temp < warm_one.required_temp) + //the range of temperatures does not overlap, so there is no conflict + return FALSE + + //find the reactions with the shorter and longer required_reagents list + var/datum/chemical_reaction/long_req + var/datum/chemical_reaction/short_req + if(r1.required_reagents.len > r2.required_reagents.len) + long_req = r1 + short_req = r2 + else if(r1.required_reagents.len < r2.required_reagents.len) + long_req = r2 + short_req = r1 + else + //if they are the same length, sort instead by the length of the catalyst list + //this is important if the required_reagents lists are the same + if(r1.required_catalysts.len > r2.required_catalysts.len) + long_req = r1 + short_req = r2 + else + long_req = r2 + short_req = r1 + + + //check if the shorter reaction list is a subset of the longer one + var/list/overlap = r1.required_reagents & r2.required_reagents + if(overlap.len != short_req.required_reagents.len) + //there is at least one reagent in the short list that is not in the long list, so there is no conflict + return FALSE + + //check to see if the shorter reaction's catalyst list is also a subset of the longer reaction's catalyst list + //if the longer reaction's catalyst list is a subset of the shorter ones, that is fine + //if the reaction lists are the same, the short reaction will have the shorter required_catalysts list, so it will register as a conflict + var/list/short_minus_long_catalysts = short_req.required_catalysts - long_req.required_catalysts + if(short_minus_long_catalysts.len) + //there is at least one unique catalyst for the short reaction, so there is no conflict + return FALSE + + //if we got this far, the longer reaction will be impossible to create if the shorter one is earlier in GLOB.chemical_reactions_list, and will require the reagents to be added in a particular order otherwise + return TRUE + +/proc/get_chemical_reaction(id) + if(!GLOB.chemical_reactions_list) + return + for(var/reagent in GLOB.chemical_reactions_list) + for(var/datum/chemical_reaction/R in GLOB.chemical_reactions_list[reagent]) + if(R.id == id) + return R + +/proc/remove_chemical_reaction(datum/chemical_reaction/R) + if(!GLOB.chemical_reactions_list || !R) + return + for(var/rid in R.required_reagents) + GLOB.chemical_reactions_list[rid] -= R + +//see build_chemical_reactions_list in holder.dm for explanations +/proc/add_chemical_reaction(datum/chemical_reaction/R) + if(!GLOB.chemical_reactions_list || !R.id || !R.required_reagents || !R.required_reagents.len) + return + var/primary_reagent = R.required_reagents[1] + if(!GLOB.chemical_reactions_list[primary_reagent]) + GLOB.chemical_reactions_list[primary_reagent] = list() + GLOB.chemical_reactions_list[primary_reagent] += R diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm index 200e1f907f..b28e7b5807 100644 --- a/code/__HELPERS/time.dm +++ b/code/__HELPERS/time.dm @@ -70,3 +70,6 @@ GLOBAL_VAR_INIT(rollovercheck_last_timeofday, 0) if(hour) hourT = " and [hour] hour[(hour != 1)? "s":""]" return "[day] day[(day != 1)? "s":""][hourT][minuteT][secondT]" + +/proc/daysSince(realtimev) + return round((world.realtime - realtimev) / (24 HOURS)) diff --git a/code/controllers/subsystem/persistence.dm b/code/controllers/subsystem/persistence.dm index 978eedb62f..455b53b1df 100644 --- a/code/controllers/subsystem/persistence.dm +++ b/code/controllers/subsystem/persistence.dm @@ -28,6 +28,7 @@ SUBSYSTEM_DEF(persistence) LoadPhotoPersistence() if(CONFIG_GET(flag/use_antag_rep)) LoadAntagReputation() + LoadRandomizedRecipes() return ..() /datum/controller/subsystem/persistence/proc/LoadSatchels() @@ -206,6 +207,7 @@ SUBSYSTEM_DEF(persistence) SavePhotoPersistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION. if(CONFIG_GET(flag/use_antag_rep)) CollectAntagReputation() + SaveRandomizedRecipes() /datum/controller/subsystem/persistence/proc/GetPhotoAlbums() var/album_path = file("data/photo_albums.json") @@ -371,3 +373,43 @@ SUBSYSTEM_DEF(persistence) fdel(FILE_ANTAG_REP) text2file(json_encode(antag_rep), FILE_ANTAG_REP) +/datum/controller/subsystem/persistence/proc/LoadRandomizedRecipes() + var/json_file = file("data/RandomizedChemRecipes.json") + var/json + if(fexists(json_file)) + json = json_decode(file2text(json_file)) + + for(var/randomized_type in subtypesof(/datum/chemical_reaction/randomized)) + var/datum/chemical_reaction/randomized/R = new randomized_type + var/loaded = FALSE + if(R.persistent && json) + var/list/recipe_data = json[R.id] + if(recipe_data && R.LoadOldRecipe(recipe_data) && (daysSince(R.created) <= R.persistence_period)) + loaded = TRUE + if(!loaded) //We do not have information for whatever reason, just generate new one + R.GenerateRecipe() + + if(!R.HasConflicts()) //Might want to try again if conflicts happened in the future. + add_chemical_reaction(R) + +/datum/controller/subsystem/persistence/proc/SaveRandomizedRecipes() + var/json_file = file("data/RandomizedChemRecipes.json") + var/list/file_data = list() + + //asert globchems done + for(var/randomized_type in subtypesof(/datum/chemical_reaction/randomized)) + var/datum/chemical_reaction/randomized/R = randomized_type + R = get_chemical_reaction(initial(R.id)) //ew, would be nice to add some simple tracking + if(R && R.persistent && R.id) + var/recipe_data = list() + recipe_data["timestamp"] = R.created + recipe_data["required_reagents"] = R.required_reagents + recipe_data["required_catalysts"] = R.required_catalysts + recipe_data["required_temp"] = R.required_temp + recipe_data["is_cold_recipe"] = R.is_cold_recipe + recipe_data["results"] = R.results + recipe_data["required_container"] = "[R.required_container]" + file_data["[R.id]"] = recipe_data + + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data)) diff --git a/code/datums/mood_events/drink_events.dm b/code/datums/mood_events/drink_events.dm index db5a91009e..5d0eb0e0cb 100644 --- a/code/datums/mood_events/drink_events.dm +++ b/code/datums/mood_events/drink_events.dm @@ -22,3 +22,8 @@ description = "That drink was amazing!\n" mood_change = 4 timeout = 1200 + +/datum/mood_event/amazingtaste + description = "Amazing taste!\n" + mood_change = 50 + timeout = 10 MINUTES diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm index cc93a28ed8..dc86ab4e6c 100644 --- a/code/modules/reagents/chemistry/holder.dm +++ b/code/modules/reagents/chemistry/holder.dm @@ -21,7 +21,9 @@ if(GLOB.chemical_reactions_list) return - var/paths = subtypesof(/datum/chemical_reaction) + + //Randomized need to go last since they need to check against conflicts with normal recipes + var/paths = subtypesof(/datum/chemical_reaction) - typesof(/datum/chemical_reaction/randomized) + subtypesof(/datum/chemical_reaction/randomized) GLOB.chemical_reactions_list = list() for(var/path in paths) @@ -29,6 +31,9 @@ var/datum/chemical_reaction/D = new path() var/list/reaction_ids = list() + if(!D.id) + continue + if(D.required_reagents && D.required_reagents.len) for(var/reaction in D.required_reagents) reaction_ids += reaction diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 230a62620b..38db62fd2a 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -32,6 +32,8 @@ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "quality_drink", /datum/mood_event/quality_verygood) if (DRINK_FANTASTIC) SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "quality_drink", /datum/mood_event/quality_fantastic) + if (FOOD_AMAZING) + SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "quality_food", /datum/mood_event/amazingtaste) return ..() /datum/reagent/consumable/nutriment @@ -711,3 +713,14 @@ nutriment_factor = 5 * REAGENTS_METABOLISM color = "#eef442" // rgb: 238, 244, 66 taste_description = "mournful honking" + +/datum/reagent/consumable/secretsauce + name = "secret sauce" + id = "secret_sauce" + description = "What could it be." + nutriment_factor = 2 * REAGENTS_METABOLISM + color = "#792300" + taste_description = "indescribable" + quality = FOOD_AMAZING + taste_mult = 100 + can_synth = FALSE diff --git a/code/modules/reagents/chemistry/recipes/special.dm b/code/modules/reagents/chemistry/recipes/special.dm new file mode 100644 index 0000000000..153372101e --- /dev/null +++ b/code/modules/reagents/chemistry/recipes/special.dm @@ -0,0 +1,212 @@ +GLOBAL_LIST_INIT(food_reagents, build_reagents_to_food()) //reagentid = related food types + +/proc/build_reagents_to_food() + . = list() + for (var/type in subtypesof(/obj/item/reagent_containers/food)) + var/obj/item/reagent_containers/food/item = new type() + for(var/r in item.list_reagents) + if (!.[r]) + .[r] = list() + .[r] += type + qdel(item) + //dang plant snowflake + for (var/type in subtypesof(/obj/item/seeds)) + var/obj/item/seeds/item = new type() + for(var/r in item.reagents_add) + if (!.[r]) + .[r] = list() + .[r] += type + qdel(item) + + +#define RNGCHEM_INPUT "input" +#define RNGCHEM_CATALYSTS "catalysts" +#define RNGCHEM_OUTPUT "output" + +/datum/chemical_reaction/randomized + name = "semi randomized reaction" + + var/persistent = FALSE + var/persistence_period = 7 //Will reset every x days + var/created //creation timestamp + + var/randomize_container = FALSE + var/list/possible_containers = list() + + var/randomize_req_temperature = TRUE + var/min_temp = 1 + var/max_temp = 600 + + var/randomize_inputs = TRUE + var/min_input_reagent_amount = 1 + var/max_input_reagent_amount = 10 + var/min_input_reagents = 2 + var/max_input_reagents = 5 + var/list/possible_reagents = list() + var/min_catalysts = 0 + var/max_catalysts = 2 + var/list/possible_catalysts = list() + + var/randomize_results = FALSE + var/min_output_reagent_amount = 1 + var/max_output_reagent_amount = 5 + var/min_result_reagents = 1 + var/max_result_reagents = 1 + var/list/possible_results = list() + +/datum/chemical_reaction/randomized/proc/GenerateRecipe() + created = world.time + 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(randomize_results) + results = list() + var/list/remaining_possible_results = GetPossibleReagents(RNGCHEM_OUTPUT) + var/out_reagent_count = min(rand(min_result_reagents,max_result_reagents),remaining_possible_results.len) + for(var/i in 1 to out_reagent_count) + var/r_id = pick_n_take(remaining_possible_results) + results[r_id] = rand(min_output_reagent_amount,max_output_reagent_amount) + + if(randomize_inputs) + var/list/remaining_possible_reagents = GetPossibleReagents(RNGCHEM_INPUT) + var/list/remaining_possible_catalysts = GetPossibleReagents(RNGCHEM_CATALYSTS) + + //We're going to assume we're not doing any weird partial reactions for now. + for(var/reagent_type in results) + remaining_possible_catalysts -= reagent_type + remaining_possible_reagents -= reagent_type + + var/in_reagent_count = min(rand(min_input_reagents,max_input_reagents),remaining_possible_reagents.len) + if(in_reagent_count <= 0) + return FALSE + + required_reagents = list() + for(var/i in 1 to in_reagent_count) + var/r_id = pick_n_take(remaining_possible_reagents) + required_reagents[r_id] = rand(min_input_reagent_amount,max_input_reagent_amount) + remaining_possible_catalysts -= r_id //Can't have same reagents both as catalyst and reagent. Or can we ? + + required_catalysts = list() + var/in_catalyst_count = min(rand(min_catalysts,max_catalysts),remaining_possible_catalysts.len) + for(var/i in 1 to in_catalyst_count) + var/r_id = pick_n_take(remaining_possible_catalysts) + required_catalysts[r_id] = rand(min_input_reagent_amount,max_input_reagent_amount) + + return TRUE + +/datum/chemical_reaction/randomized/proc/GetPossibleReagents(kind) + switch(kind) + if(RNGCHEM_INPUT) + return possible_reagents.Copy() + if(RNGCHEM_CATALYSTS) + return possible_catalysts.Copy() + if(RNGCHEM_OUTPUT) + return possible_results.Copy() + +/datum/chemical_reaction/randomized/proc/HasConflicts() + for(var/x in required_reagents) + for(var/datum/chemical_reaction/R in GLOB.chemical_reactions_list[x]) + if(chem_recipes_do_conflict(R,src)) + return TRUE + return FALSE + +/datum/chemical_reaction/randomized/proc/unwrap_reagent_list(list/textreagents) + . = list() + for(var/R in textreagents) + var/pathR = text2path(R) + if(!pathR) + return null + .[pathR] = textreagents[R] + +/datum/chemical_reaction/randomized/proc/LoadOldRecipe(recipe_data) + created = text2num(recipe_data["timestamp"]) + + var/req_reag = unwrap_reagent_list(recipe_data["required_reagents"]) + if(!req_reag) + return FALSE + required_reagents = req_reag + + var/req_catalysts = unwrap_reagent_list(recipe_data["required_catalysts"]) + if(!req_catalysts) + return FALSE + required_catalysts = req_catalysts + + required_temp = recipe_data["required_temp"] + is_cold_recipe = recipe_data["is_cold_recipe"] + + var/temp_results = unwrap_reagent_list(recipe_data["results"]) + if(!temp_results) + return FALSE + results = temp_results + var/containerpath = text2path(recipe_data["required_container"]) + if(!containerpath) + return FALSE + required_container = containerpath + return TRUE + +/datum/chemical_reaction/randomized/secret_sauce + name = "secret sauce creation" + id = "secretsauce" + persistent = TRUE + persistence_period = 7 //Reset every week + randomize_container = TRUE + possible_containers = list(/obj/item/reagent_containers/glass/bucket) //easy way to ensure no common conflicts + randomize_req_temperature = TRUE + results = list("secret_sauce" =1) + +/datum/chemical_reaction/randomized/secret_sauce/GetPossibleReagents(kind) + switch(kind) + if(RNGCHEM_INPUT,RNGCHEM_CATALYSTS) + var/food_reagent_ids = list() + for(var/key in GLOB.food_reagents) + food_reagent_ids += key + return food_reagent_ids + return ..() + + +/obj/item/paper/secretrecipe + name = "old recipe" + var/recipe_id = "secretsauce" + +/obj/item/paper/secretrecipe/examine(mob/user) //Extra secret + if(isobserver(user)) + return + . = ..() + +/obj/item/paper/secretrecipe/Initialize() + . = ..() + if(SSpersistence.initialized) + UpdateInfo() + else + SSticker.OnRoundstart(CALLBACK(src,.proc/UpdateInfo)) + +/obj/item/paper/secretrecipe/proc/UpdateInfo() + var/datum/chemical_reaction/recipe = get_chemical_reaction(recipe_id) + if(!recipe) + info = "This recipe is illegible." + var/list/dat = list("" + if(recipe.required_catalysts.len) + dat += "With following present: " + dat += "Mix slowly" + if(recipe.required_container) + var/obj/item/I = recipe.required_container + dat += " in [initial(I.name)]" + if(recipe.required_temp != 0) + if(recipe.is_cold_recipe) + dat += " below [recipe.required_temp] degrees" + else + dat += " above [recipe.required_temp] degrees" + dat += "." + info = dat.Join("") + update_icon() \ No newline at end of file diff --git a/code/modules/unit_tests/reagent_recipe_collisions.dm b/code/modules/unit_tests/reagent_recipe_collisions.dm index 31027c2cd3..20e875422f 100644 --- a/code/modules/unit_tests/reagent_recipe_collisions.dm +++ b/code/modules/unit_tests/reagent_recipe_collisions.dm @@ -11,57 +11,5 @@ for(var/i2 in (i+1) to reactions.len) var/datum/chemical_reaction/r1 = reactions[i] var/datum/chemical_reaction/r2 = reactions[i2] - if(recipes_do_conflict(r1, r2)) + if(chem_recipes_do_conflict(r1, r2)) Fail("Chemical recipe conflict between [r1.type] and [r2.type]") - -/datum/unit_test/reagent_recipe_collisions/proc/recipes_do_conflict(datum/chemical_reaction/r1, datum/chemical_reaction/r2) - //do the non-list tests first, because they are cheaper - if(r1.required_container != r2.required_container) - return FALSE - if(r1.is_cold_recipe == r2.is_cold_recipe) - if(r1.required_temp != r2.required_temp) - //one reaction requires a more extreme temperature than the other, so there is no conflict - return FALSE - else - var/datum/chemical_reaction/cold_one = r1.is_cold_recipe ? r1 : r2 - var/datum/chemical_reaction/warm_one = r1.is_cold_recipe ? r2 : r1 - if(cold_one.required_temp < warm_one.required_temp) - //the range of temperatures does not overlap, so there is no conflict - return FALSE - - //find the reactions with the shorter and longer required_reagents list - var/datum/chemical_reaction/long_req - var/datum/chemical_reaction/short_req - if(r1.required_reagents.len > r2.required_reagents.len) - long_req = r1 - short_req = r2 - else if(r1.required_reagents.len < r2.required_reagents.len) - long_req = r2 - short_req = r1 - else - //if they are the same length, sort instead by the length of the catalyst list - //this is important if the required_reagents lists are the same - if(r1.required_catalysts.len > r2.required_catalysts.len) - long_req = r1 - short_req = r2 - else - long_req = r2 - short_req = r1 - - - //check if the shorter reaction list is a subset of the longer one - var/list/overlap = r1.required_reagents & r2.required_reagents - if(overlap.len != short_req.required_reagents.len) - //there is at least one reagent in the short list that is not in the long list, so there is no conflict - return FALSE - - //check to see if the shorter reaction's catalyst list is also a subset of the longer reaction's catalyst list - //if the longer reaction's catalyst list is a subset of the shorter ones, that is fine - //if the reaction lists are the same, the short reaction will have the shorter required_catalysts list, so it will register as a conflict - var/list/short_minus_long_catalysts = short_req.required_catalysts - long_req.required_catalysts - if(short_minus_long_catalysts.len) - //there is at least one unique catalyst for the short reaction, so there is no conflict - return FALSE - - //if we got this far, the longer reaction will be impossible to create if the shorter one is earlier in GLOB.chemical_reactions_list, and will require the reagents to be added in a particular order otherwise - return TRUE \ No newline at end of file diff --git a/tgstation.dme b/tgstation.dme index 1a8dd941ab..c0ccbb097b 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -130,6 +130,7 @@ #include "code\__HELPERS\qdel.dm" #include "code\__HELPERS\radiation.dm" #include "code\__HELPERS\radio.dm" +#include "code\__HELPERS\reagents.dm" #include "code\__HELPERS\roundend.dm" #include "code\__HELPERS\sanitize_values.dm" #include "code\__HELPERS\shell.dm" @@ -2480,6 +2481,7 @@ #include "code\modules\reagents\chemistry\recipes\others.dm" #include "code\modules\reagents\chemistry\recipes\pyrotechnics.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" #include "code\modules\reagents\reagent_containers\blood_pack.dm" #include "code\modules\reagents\reagent_containers\borghydro.dm"