diff --git a/code/__DEFINES/crafting.dm b/code/__DEFINES/crafting.dm index 09b35c5d093..5d2cc6632e9 100644 --- a/code/__DEFINES/crafting.dm +++ b/code/__DEFINES/crafting.dm @@ -27,9 +27,9 @@ #define CAT_SPAGHETTI "Spaghettis" #define CAT_ICE "Frozen" -#define RECIPE_MICROWAVE "Microwave" -#define RECIPE_OVEN "Oven" -#define RECIPE_GRILL "Grill" -#define RECIPE_CANDY "Candy" +#define RECIPE_MICROWAVE "microwave" +#define RECIPE_OVEN "oven" +#define RECIPE_GRILL "grill" +#define RECIPE_CANDY "candy machine" #define RECIPE_FAIL null diff --git a/code/__DEFINES/misc_defines.dm b/code/__DEFINES/misc_defines.dm index a837dc7a72b..790c9f9ea66 100644 --- a/code/__DEFINES/misc_defines.dm +++ b/code/__DEFINES/misc_defines.dm @@ -703,3 +703,7 @@ do { \ /// A helper used by `restrict_file_types.py` to identify types to restrict in a file. Not used by byond at all. #define RESTRICT_TYPE(type) // do nothing + +#define INGREDIENT_CHECK_EXACT 1 +#define INGREDIENT_CHECK_FAILURE 0 +#define INGREDIENT_CHECK_SURPLUS -1 diff --git a/code/__HELPERS/trait_helpers.dm b/code/__HELPERS/trait_helpers.dm index 3cb5928179a..e3778abe37f 100644 --- a/code/__HELPERS/trait_helpers.dm +++ b/code/__HELPERS/trait_helpers.dm @@ -233,6 +233,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_TABLE_LEAP "table_leap" // Lets bartender and chef mount tables faster #define TRAIT_NEVER_MISSES_DISPOSALS "trait_never_misses_disposals" // For janitors landing disposal throws #define TRAIT_SLEIGHT_OF_HAND "sleight_of_hand" +#define TRAIT_KNOWS_COOKING_RECIPES "knows_cooking_recipes" //***** ITEM AND MOB TRAITS *****// /// Show what machine/door wires do when held. diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 79d3208719c..ca7ef0c3adc 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -2082,3 +2082,18 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new) /proc/is_color_dark(color, threshold = 25) var/hsl = rgb2num(color, COLORSPACE_HSL) return hsl[3] < threshold + +/** + * This proc takes a list of types, and returns them in the format below. + * [type] = amount of type in list. + * Useful for recipes. + */ +/proc/type_list_to_counted_assoc_list(list/origin_list) + var/list/return_list = list() + for(var/datum/path as anything in origin_list) + if(isdatum(path)) + path = path.type + if(!return_list[path]) + return_list[path] = 0 + return_list[path] += 1 + return return_list diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index 2e1fc13f5db..3d3cf117030 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -81,7 +81,6 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_NOSELFIGNITION_HEAD_ONLY" = TRAIT_NOSELFIGNITION_HEAD_ONLY, "TRAIT_CONTORTED_BODY" = TRAIT_CONTORTED_BODY, "TRAIT_DEFLECTS_PROJECTILES" = TRAIT_DEFLECTS_PROJECTILES, - "TRAIT_TABLE_LEAP" = TRAIT_TABLE_LEAP, "TRAIT_DODGE_ALL_THROWN_OBJECTS" = TRAIT_DODGE_ALL_OBJECTS, "TRAIT_SUPERMATTER_IMMUNE" = TRAIT_SUPERMATTER_IMMUNE, "TRAIT_BADASS" = TRAIT_BADASS, @@ -98,7 +97,8 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_HOLY" = TRAIT_HOLY, "TRAIT_SLEIGHT_OF_HAND" = TRAIT_SLEIGHT_OF_HAND, "TRAIT_TABLE_LEAP" = TRAIT_TABLE_LEAP, - "TRAIT_NEVER_MISSES_DISPOSALS" = TRAIT_NEVER_MISSES_DISPOSALS + "TRAIT_NEVER_MISSES_DISPOSALS" = TRAIT_NEVER_MISSES_DISPOSALS, + "TRAIT_KNOWS_COOKING_RECIPES" = TRAIT_KNOWS_COOKING_RECIPES ), /obj = list( diff --git a/code/datums/recipe.dm b/code/datums/recipe.dm index 8d0f7a7c0a1..f10e70ed027 100644 --- a/code/datums/recipe.dm +++ b/code/datums/recipe.dm @@ -42,26 +42,49 @@ var/duplicate = TRUE var/byproduct // example: = /obj/item/kitchen/mould // byproduct to return, such as a mould or trash -/datum/recipe/proc/check_reagents(datum/reagents/avail_reagents) //1=precisely, 0=insufficiently, -1=superfluous - . = 1 +/datum/recipe/proc/check_reagents(datum/reagents/avail_reagents) + . = INGREDIENT_CHECK_EXACT for(var/r_r in reagents) var/aval_r_amnt = avail_reagents.get_reagent_amount(r_r) if(!(abs(aval_r_amnt - reagents[r_r])<0.5)) //if NOT equals if(aval_r_amnt>reagents[r_r]) - . = -1 + . = INGREDIENT_CHECK_SURPLUS else - return 0 - if((reagents?(length(reagents)):(0)) < length(avail_reagents.reagent_list)) - return -1 + return INGREDIENT_CHECK_FAILURE + if((reagents ? length(reagents) : 0) < length(avail_reagents.reagent_list)) + return INGREDIENT_CHECK_SURPLUS + +/** + * Similarly to the function above, this checks for reagents, except instead of being passed a reagent holder, we're passed + * [reagent_id] = amount as num. + * Returns INGREDIENT_CHECK_EXACT if we have the precise amount thats requested. + * Returns INGREDIENT_CHECK_FAILURE if we do not have enough. + * Returns INGREDIENT_CHECK_SURPLUS if we have MORE than requested. + */ +/datum/recipe/proc/check_reagents_assoc_list(list/avail_reagents) + . = INGREDIENT_CHECK_EXACT + for(var/required_reagent_id in reagents) + var/provided_reagent_amount = avail_reagents[required_reagent_id] + if(!provided_reagent_amount) + return INGREDIENT_CHECK_FAILURE + + if(abs(provided_reagent_amount - reagents[required_reagent_id]) >= 0.5) // If amount is outside of the 0.5 unit tolerance + if(provided_reagent_amount > reagents[required_reagent_id]) // we have more than necessary + . = INGREDIENT_CHECK_SURPLUS + else + return INGREDIENT_CHECK_FAILURE // we don't have enough + + if(length(reagents) < length(avail_reagents)) + return INGREDIENT_CHECK_SURPLUS /datum/recipe/proc/check_items(obj/container, list/ignored_items = null) //1=precisely, 0=insufficiently, -1=superfluous - . = 1 + . = INGREDIENT_CHECK_EXACT var/list/checklist = items ? items.Copy() : list() for(var/obj/O in container) if(ignored_items && is_type_in_list(O, ignored_items)) //skip if this is something we are ignoring continue if(!items) - return -1 + return INGREDIENT_CHECK_SURPLUS var/found = 0 for(var/type in checklist) if(istype(O,type)) @@ -69,9 +92,32 @@ found = 1 break if(!found) - . = -1 + . = INGREDIENT_CHECK_SURPLUS if(length(checklist)) - return 0 + return INGREDIENT_CHECK_FAILURE + +/** + * Similarly to the function above, this checks for items, except instead of being passed a reagent holder, we're passed + * [type_path] = amount as num. + * Returns INGREDIENT_CHECK_EXACT if we have the precise amount thats requested. + * Returns INGREDIENT_CHECK_FAILURE if we do not have enough. + * Returns INGREDIENT_CHECK_SURPLUS if we have MORE than requested. + */ +/datum/recipe/proc/check_items_assoc_list(list/given_objects) + . = INGREDIENT_CHECK_EXACT + var/list/checklist = items ? items.Copy() : list() + for(var/obj/item/I as anything in given_objects) // path + var/amount_given = given_objects[I] + if(!length(checklist)) + return INGREDIENT_CHECK_SURPLUS + + for(var/i in 1 to amount_given) + if(I in checklist) + checklist -= I + else if(I in items) // make sure the recipe requires this item before declaring it surplus + . = INGREDIENT_CHECK_SURPLUS + if(length(checklist)) // we didnt get everything + return INGREDIENT_CHECK_FAILURE //general version /datum/recipe/proc/make(obj/container) @@ -94,7 +140,7 @@ container.reagents.clear_reagents() return result_obj -/proc/select_recipe(list/datum/recipe/available_recipes, obj/obj, exact = 1 as num, list/ignored_items = null) +/proc/select_recipe(list/datum/recipe/available_recipes, obj/obj, exact = INGREDIENT_CHECK_EXACT, list/ignored_items = null) if(!exact) exact = -1 var/list/datum/recipe/possible_recipes = new diff --git a/code/datums/spells/chef.dm b/code/datums/spells/chef.dm new file mode 100644 index 00000000000..642f5476f44 --- /dev/null +++ b/code/datums/spells/chef.dm @@ -0,0 +1,17 @@ +/datum/spell/chef/expert_chef + name = "Expert Chef Knowledge" + desc = "Find things you can cook with the items in reach." + school = "chef" + clothes_req = FALSE + base_cooldown = 5 SECONDS + human_req = TRUE + action_icon_state = "chef" + action_background_icon_state = "bg_default" + still_recharging_msg = "All this thinking makes your head hurt, wait a bit longer." + +/datum/spell/chef/expert_chef/cast(list/targets, mob/user = usr) + var/mob/living/carbon/human/H = targets[1] + H.expert_chef_knowledge() + +/datum/spell/chef/expert_chef/create_new_targeting() + return new /datum/spell_targeting/self diff --git a/code/game/jobs/job/support.dm b/code/game/jobs/job/support.dm index c8678be4e11..b24597ec1fa 100644 --- a/code/game/jobs/job/support.dm +++ b/code/game/jobs/job/support.dm @@ -244,7 +244,9 @@ var/datum/martial_art/cqc/under_siege/justacook = new justacook.teach(H) // requires mind ADD_TRAIT(H.mind, TRAIT_TABLE_LEAP, ROUNDSTART_TRAIT) - + ADD_TRAIT(H.mind, TRAIT_KNOWS_COOKING_RECIPES, ROUNDSTART_TRAIT) + if(H.mind) + H.mind.AddSpell(new /datum/spell/chef/expert_chef) /datum/job/hydro title = "Botanist" diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm index e3e6b5d0b43..f9a3c96d3dd 100644 --- a/code/game/objects/items/trash.dm +++ b/code/game/objects/items/trash.dm @@ -38,8 +38,9 @@ icon_state = "twimsts" /obj/item/trash/popcorn - name = "Popcorn" + name = "popcorn" icon_state = "popcorn" + gender = PLURAL /obj/item/trash/sosjerky name = "Scaredy's Private Reserve Beef Jerky" diff --git a/code/modules/food_and_drinks/food/foods/misc_food.dm b/code/modules/food_and_drinks/food/foods/misc_food.dm index 7d5b6bcf6b0..ae9e946f60e 100644 --- a/code/modules/food_and_drinks/food/foods/misc_food.dm +++ b/code/modules/food_and_drinks/food/foods/misc_food.dm @@ -283,6 +283,7 @@ name = "popcorn" desc = "Now let's find some cinema." icon_state = "popcorn" + gender = PLURAL trash = /obj/item/trash/popcorn var/unpopped = 0 filling_color = "#FFFAD4" diff --git a/code/modules/food_and_drinks/kitchen_machinery/kitchen_machine.dm b/code/modules/food_and_drinks/kitchen_machinery/kitchen_machine.dm index 2058df28982..09e60e96ff3 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/kitchen_machine.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/kitchen_machine.dm @@ -440,6 +440,19 @@ if(dat) . += "It contains:
[dat]" + if(!HAS_MIND_TRAIT(user, TRAIT_KNOWS_COOKING_RECIPES)) + return + + if(locate(/obj/item/mixing_bowl) in contents) + . += "You can't see inside the mixing bowl, you're not sure what it would do." + return + + var/list/recipes = choose_recipes() + if(length(recipes) == 1 && recipes[1][2] != RECIPE_FAIL) + var/datum/recipe/recipe = recipes[1][2] + var/obj/item/result = recipe.result + . += "Your expert chef knowledge tells you that this would make \a [initial(result.name)]." + /obj/machinery/kitchen_machine/attack_hand(mob/user) if(stat & (BROKEN|NOPOWER) || panel_open || !anchored) return diff --git a/code/modules/mob/living/carbon/human/human_mob.dm b/code/modules/mob/living/carbon/human/human_mob.dm index eade62cabf0..dc78288d967 100644 --- a/code/modules/mob/living/carbon/human/human_mob.dm +++ b/code/modules/mob/living/carbon/human/human_mob.dm @@ -1967,3 +1967,67 @@ Eyes need to have significantly high darksight to shine unless the mob has the X set category = "IC" update_flavor_text() + +/mob/living/carbon/human/proc/expert_chef_knowledge() + if(!HAS_TRAIT(mind, TRAIT_KNOWS_COOKING_RECIPES)) + return + + var/list/possible_cookware = view(1, src) + + for(var/obj/item/storage/storage in possible_cookware) + possible_cookware += storage.contents + possible_cookware -= storage + + shuffle_inplace(possible_cookware) + + var/list/available_ingredients = list() + var/list/available_reagents = list() + for(var/obj/item/item in possible_cookware) + if(item.container_type == OPENCONTAINER && item.reagents) + for(var/datum/reagent/reagent in item.reagents.reagent_list) + if(!available_reagents[reagent.id]) + available_reagents[reagent.id] = 0 + available_reagents[reagent.id] += reagent.volume + else + if(!available_ingredients[item.type]) + available_ingredients[item.type] = 0 + available_ingredients[item.type] += 1 + + var/list/possible_recipes = list() + for(var/datum/recipe/recipe_type as anything in subtypesof(/datum/recipe)) + if(!recipe_type.result) + continue + var/datum/recipe/recipe = new recipe_type() + if(recipe.check_reagents_assoc_list(available_reagents) != INGREDIENT_CHECK_FAILURE && recipe.check_items_assoc_list(available_ingredients) != INGREDIENT_CHECK_FAILURE) + if(istype(recipe, /datum/recipe/microwave)) + possible_recipes[recipe] = RECIPE_MICROWAVE + else if(istype(recipe, /datum/recipe/oven)) + possible_recipes[recipe] = RECIPE_OVEN + else if(istype(recipe, /datum/recipe/grill)) + possible_recipes[recipe] = RECIPE_GRILL + else if(istype(recipe, /datum/recipe/candy)) + possible_recipes[recipe] = RECIPE_CANDY + else + possible_recipes[recipe] = "something? This shouldn't happen, make a bug report" + + if(!length(possible_recipes)) + to_chat(src, "You can't think of anything to cook with the items around you.") + return + + var/list/message = list("You draw upon your extensive experience in space food, contemplating what you could make with the items around you...") + for(var/datum/recipe/recipe in possible_recipes) + var/list/assoc = type_list_to_counted_assoc_list(recipe.items) + var/list/ingredient_list = list() + for(var/obj/item/path_key as anything in assoc) + ingredient_list += "[assoc[path_key]] [initial(path_key.name)]\s" + + var/list/required_reagents = list() + for(var/reagent_id in recipe.reagents) + var/datum/reagent/temp = GLOB.chemical_reagents_list[reagent_id] + if(temp) + required_reagents += "[recipe.reagents[reagent_id]] unit\s of [temp.name]" + + var/obj/item/result = recipe.result + message += "\tI could make [result.gender == PLURAL ? "some" : "a"] [bicon(result)] [result.name] by using \a [possible_recipes[recipe]] with [english_list(ingredient_list)][length(required_reagents) ? ", along with [english_list(required_reagents)]" : ""]." + qdel(recipe) + to_chat(src, chat_box_examine(message.Join("
"))) diff --git a/icons/mob/actions/actions.dmi b/icons/mob/actions/actions.dmi index e9b7ef94895..ed1b9f9f312 100644 Binary files a/icons/mob/actions/actions.dmi and b/icons/mob/actions/actions.dmi differ diff --git a/paradise.dme b/paradise.dme index 8f0b4deab90..e307ba51e9b 100644 --- a/paradise.dme +++ b/paradise.dme @@ -555,6 +555,7 @@ #include "code\datums\spells\charge.dm" #include "code\datums\spells\charge_up.dm" #include "code\datums\spells\charge_up_bounce.dm" +#include "code\datums\spells\chef.dm" #include "code\datums\spells\cluwne.dm" #include "code\datums\spells\conjure.dm" #include "code\datums\spells\conjure_item.dm"