From ae87e4574b4f1d27f96aebed4dc66dbcb5c45876 Mon Sep 17 00:00:00 2001 From: Atermonera Date: Thu, 2 Sep 2021 18:51:41 -0700 Subject: [PATCH] Refactors crafting to support requirement alternatives --- code/datums/components/crafting/crafting.dm | 311 ++++++++++++------ .../components/crafting/crafting_external.dm | 39 ++- code/datums/components/crafting/recipes.dm | 52 +++ 3 files changed, 289 insertions(+), 113 deletions(-) diff --git a/code/datums/components/crafting/crafting.dm b/code/datums/components/crafting/crafting.dm index f4a1fc2c3b..332e0fd509 100644 --- a/code/datums/components/crafting/crafting.dm +++ b/code/datums/components/crafting/crafting.dm @@ -62,55 +62,7 @@ del_reqs - takes recipe and a user, loops over the recipes reqs var and tries to find everything in the list make by get_environment and delete it/add to parts list, then returns the said list */ -/** - * Check that the contents of the recipe meet the requirements. - * - * user: The /mob that initated the crafting. - * R: The /datum/crafting_recipe being attempted. - * contents: List of items to search for R's reqs. - */ -/datum/component/personal_crafting/proc/check_contents(atom/a, datum/crafting_recipe/R, list/contents) - var/list/item_instances = contents["instances"] - var/list/machines = contents["machinery"] - contents = contents["other"] - - - var/list/requirements_list = list() - - // Process all requirements - for(var/requirement_path in R.reqs) - // Check we have the appropriate amount available in the contents list - var/needed_amount = R.reqs[requirement_path] - for(var/content_item_path in contents) - // Right path and not blacklisted - if(!ispath(content_item_path, requirement_path) || R.blacklist.Find(content_item_path)) - continue - - needed_amount -= contents[content_item_path] - if(needed_amount <= 0) - break - - if(needed_amount > 0) - return FALSE - - // Store the instances of what we will use for R.check_requirements() for requirement_path - var/list/instances_list = list() - for(var/instance_path in item_instances) - if(ispath(instance_path, requirement_path)) - instances_list += item_instances[instance_path] - - requirements_list[requirement_path] = instances_list - - for(var/requirement_path in R.chem_catalysts) - if(contents[requirement_path] < R.chem_catalysts[requirement_path]) - return FALSE - - for(var/machinery_path in R.machinery) - if(!machines[machinery_path])//We don't care for volume with machines, just if one is there or not - return FALSE - - return R.check_requirements(a, requirements_list) - +// Returns a list of objects available /datum/component/personal_crafting/proc/get_environment(atom/a, list/blacklist = null, radius_range = 1) . = list() @@ -122,13 +74,13 @@ continue . += AM - +// Returns an associative list containing the types of tools available, and the paths of objects available /datum/component/personal_crafting/proc/get_surroundings(atom/a, list/blacklist=null) . = list() - .["tool_qualities"] = list() - .["other"] = list() - .["instances"] = list() - .["machinery"] = list() + .["tool_qualities"] = list() // List of tool types available + .["other"] = list() // List of reagents/material stacks available + .["instances"] = list() // List of /obj/items available, maybe? + .["machinery"] = list() // List of /obj/machinery available for(var/obj/object in get_environment(a, blacklist)) if(isitem(object)) var/obj/item/item = object @@ -150,11 +102,55 @@ else if (istype(object, /obj/machinery)) LAZYADDASSOCLIST(.["machinery"], object.type, object) +/** + * Check that the contents of the recipe meet the requirements. + * + * user: The /mob that initated the crafting. + * R: The /datum/crafting_recipe being attempted. + * contents: List of items to search for R's reqs. + */ +/datum/component/personal_crafting/proc/check_contents(atom/a, datum/crafting_recipe/R, list/contents) + var/list/item_instances = contents["instances"] + contents = contents["other"] + var/list/requirements_list = list() + + // Process all requirements + for(var/list/requirement in R.reqs) + var/satisfied = FALSE + for(var/requirement_path in requirement) + // Check we have the appropriate amount available in the contents list + var/needed_amount = requirement[requirement_path] + for(var/content_item_path in contents) + // Right path and not blacklisted + if(!ispath(content_item_path, requirement_path) || R.blacklist.Find(content_item_path)) + continue + + needed_amount -= contents[content_item_path] + if(needed_amount <= 0) + break + + if(needed_amount > 0) + continue + + // Store the instances of what we will use for R.check_requirements() for requirement_path + var/list/instances_list = list() + for(var/instance_path in item_instances) + if(ispath(instance_path, requirement_path)) + instances_list += item_instances[instance_path] + + requirements_list[requirement_path] = instances_list + satisfied = TRUE + break + if(!satisfied) + return FALSE + + return R.check_requirements(a, requirements_list) + /// Returns a boolean on whether the tool requirements of the input recipe are satisfied by the input source and surroundings. -/datum/component/personal_crafting/proc/check_tools(atom/source, datum/crafting_recipe/recipe, list/surroundings) - if(!length(recipe.tool_behaviors) && !length(recipe.tool_paths)) +/datum/component/personal_crafting/proc/check_tools(atom/source, datum/crafting_recipe/R, list/surroundings) + if(!length(R.tool_behaviors) && !length(R.tool_paths)) return TRUE var/list/available_tools = list() var/list/present_qualities = list() @@ -176,50 +172,70 @@ for(var/path in surroundings["other"]) available_tools[path] = TRUE - for(var/required_quality in recipe.tool_behaviors) + for(var/required_quality in R.tool_behaviors) if(present_qualities[required_quality]) continue return FALSE - for(var/required_path in recipe.tool_paths) - var/found_this_tool = FALSE - for(var/tool_path in available_tools) - if(!ispath(required_path, tool_path)) - continue - found_this_tool = TRUE - break - if(found_this_tool) + for(var/required_path in R.tool_paths) + if(is_path_in_list(required_path, available_tools)) continue return FALSE return TRUE +/datum/component/personal_crafting/proc/check_reagents(atom/source, datum/crafting_recipe/R, list/surroundings) + var/list/reagents = surroundings["other"] + for(var/requirement_path in R.chem_catalysts) + if(reagents[requirement_path] < R.chem_catalysts[requirement_path]) + return FALSE + return TRUE + +/datum/component/personal_crafting/proc/check_machinery(atom/source, datum/crafting_recipe/R, list/surroundings) + var/list/machines = surroundings["machinery"] + for(var/machinery_path in R.machinery) + if(!machines[machinery_path])//We don't care for volume with machines, just if one is there or not + return FALSE + return TRUE + +/datum/component/personal_crafting/proc/check_requirements(atom/source, datum/crafting_recipe/R, list/surroundings) + if(!check_contents(source, R, surroundings)) + return ", missing component." + if(!check_tools(source, R, surroundings)) + return ", missing tool." + if(!check_reagents(source, R, surroundings)) + return ", missing reagents." + if(!check_machinery(source, R, surroundings)) + return ", missing machinery." + return /datum/component/personal_crafting/proc/construct_item(atom/a, datum/crafting_recipe/R) - var/list/contents = get_surroundings(a,R.blacklist) + var/list/surroundings = get_surroundings(a,R.blacklist) // var/send_feedback = 1 - if(check_contents(a, R, contents)) - if(check_tools(a, R, contents)) - if(R.one_per_turf) - for(var/content in get_turf(a)) - if(istype(content, R.result)) - return ", object already present." - //If we're a mob we'll try a do_after; non mobs will instead instantly construct the item - if(ismob(a) && !do_after(a, R.time, target = a)) - return "." - contents = get_surroundings(a,R.blacklist) - if(!check_contents(a, R, contents)) - return ", missing component." - if(!check_tools(a, R, contents)) - return ", missing tool." - var/list/parts = del_reqs(R, a) - var/atom/movable/I = new R.result (get_turf(a.loc)) - I.CheckParts(parts, R) - // if(send_feedback) - // SSblackbox.record_feedback("tally", "object_crafted", 1, I.type) - return I //Send the item back to whatever called this proc so it can handle whatever it wants to do with the new item - return ", missing tool." - return ", missing component." + . = check_requirements(a, R, surroundings) + if(.) + return + + if(R.one_per_turf) + for(var/content in get_turf(a)) + if(istype(content, R.result)) + return ", object already present." + + //If we're a mob we'll try a do_after; non mobs will instead instantly construct the item + if(ismob(a) && !do_after(a, R.time, target = a)) + return "." + + surroundings = get_surroundings(a, R.blacklist) + . = check_requirements(a, R, surroundings) + if(.) + return + + var/list/parts = del_reqs(R, a) + var/atom/movable/I = new R.result (get_turf(a.loc)) + I.CheckParts(parts, R) + // if(send_feedback) + // SSblackbox.record_feedback("tally", "object_crafted", 1, I.type) + return I //Send the item back to whatever called this proc so it can handle whatever it wants to do with the new item /*Del reqs works like this: @@ -246,16 +262,17 @@ */ /datum/component/personal_crafting/proc/del_reqs(datum/crafting_recipe/R, atom/a) - var/list/surroundings - var/list/Deletion = list() - . = list() - var/data - var/amt + var/list/surroundings = get_environment(a) + var/list/parts = list("items" = list()) + if(R.get_parts_reagents_volume()) + parts["reagents"] = new /datum/reagents(R.get_parts_reagents_volume()) // Datums don't have create_reagents() var/list/requirements = list() if(R.reqs) - requirements += R.reqs + for(var/list/L in R.reqs) + requirements += L if(R.machinery) requirements += R.machinery +<<<<<<< HEAD main_loop: for(var/path_key in requirements) amt = R.reqs[path_key] || R.machinery[path_key] @@ -359,6 +376,100 @@ container.spill() container.close_all() qdel(DL) +======= + + // Try to find everything that was actually used to craft + for(var/path_key in requirements) + var/amt = requirements[path_key] + if(amt <= 0)//since machinery can have 0 aka CRAFTING_MACHINERY_USE - i.e. use it, don't consume it! + continue + + // If the path is in R.parts, we want to grab those to stuff into the product + var/amt_to_transfer = 0 + if(is_path_in_list(path_key, R.parts)) + amt_to_transfer = R.parts[path_key] + + + // Reagent: gotta go sniffing in all the beakers + if(ispath(path_key, /datum/reagent)) + var/datum/reagent/reagent = path_key + var/id = initial(reagent.id) + + for(var/obj/item/weapon/reagent_containers/RC in surroundings) + // Found everything we need + if(amt <= 0 && amt_to_transfer <= 0) + break + + // If we need to keep any to put in the new object, pull it out + if(amt_to_transfer > 0) + var/A = RC.reagents.trans_id_to(parts["reagents"], id, amt_to_transfer) + amt_to_transfer -= A + amt -= A + + // If we need to consume some amount of it + if(amt > 0) + var/datum/reagent/RG = RC.reagents.get_reagent(id) + var/A = min(RG.volume, amt) + RC.reagents.remove_reagent(id, A) + amt -= A + SEND_SIGNAL(RC.reagents, COMSIG_REAGENTS_CRAFTING_PING) + + // Material stacks may have to accumulate across multiple stacks + else if(ispath(path_key, /obj/item/stack)) + for(var/obj/item/stack/S in surroundings) + if(amt <= 0 && amt_to_transfer <= 0) + break + + // This could put 50 stacks in an object but frankly so long as the amount's right we don't care + if(amt_to_transfer > 0) + var/obj/item/stack/split = S.split(amt_to_transfer) + if(istype(split)) + parts["items"] += split + amt_to_transfer -= split.get_amount() + amt -= split.get_amount() + + if(amt > 0) + var/A = min(amt, S.get_amount()) + if(S.use(A)) + amt -= A + + + else // Just a regular item. Find them all and delete them + for(var/atom/movable/I in surroundings) + if(amt <= 0 && amt_to_transfer <= 0) + break + + if(!istype(I, path_key)) + continue + + // Special case: the reagents may be needed for other recipes + if(istype(I, /obj/item/weapon/reagent_containers)) + var/obj/item/weapon/reagent_containers/RC = I + if(RC.reagents.total_volume > 0) + continue + + // We're using it for something + amt-- + + // Prepare to stuff inside product, don't delete it + if(is_path_in_list(path_key, parts)) + parts["items"] += I + amt_to_transfer-- + continue + + // Snowflake handling of reagent containers and storage atoms. + // If we consumed them in our crafting, we should dump their contents out before qdeling them. + if(istype(I, /obj/item/weapon/reagent_containers)) + var/obj/item/weapon/reagent_containers/container = I + container.reagents.clear_reagents() + // container.reagents.expose(container.loc, TOUCH) + else if(istype(I, /obj/item/weapon/storage)) + var/obj/item/weapon/storage/container = I + container.spill() + container.close_all() + qdel(I) + return parts +>>>>>>> 5a261195eab... Refactors crafting to support requirement alternatives (#8184) /datum/component/personal_crafting/proc/component_ui_interact(source, location, control, params, user) // SIGNAL_HANDLER @@ -487,10 +598,14 @@ var/list/tool_list = list() var/list/catalyst_text = list() - for(var/atom/req_atom as anything in R.reqs) - //We just need the name, so cheat-typecast to /atom for speed (even tho Reagents are /datum they DO have a "name" var) - //Also these are typepaths so sadly we can't just do "[a]" - req_text += "[R.reqs[req_atom]] [initial(req_atom.name)]" + for(var/list/req in R.reqs) + var/list/L = list() + for(var/atom/req_atom as anything in req) + //We just need the name, so cheat-typecast to /atom for speed (even tho Reagents are /datum they DO have a "name" var) + //Also these are typepaths so sadly we can't just do "[a]" + L += "[req[req_atom]] [initial(req_atom.name)]" + req_text += L.Join(" OR ") + for(var/obj/machinery/content as anything in R.machinery) req_text += "[R.reqs[content]] [initial(content.name)]" if(R.additional_req_text) diff --git a/code/datums/components/crafting/crafting_external.dm b/code/datums/components/crafting/crafting_external.dm index e40d501132..8346a0fa58 100644 --- a/code/datums/components/crafting/crafting_external.dm +++ b/code/datums/components/crafting/crafting_external.dm @@ -12,21 +12,30 @@ */ /atom/proc/CheckParts(list/parts_list, datum/crafting_recipe/R) SEND_SIGNAL(src, COMSIG_ATOM_CHECKPARTS, parts_list, R) - if(parts_list) - for(var/A in parts_list) - if(istype(A, /datum/reagent)) - if(!reagents) - reagents = new() - reagents.reagent_list.Add(A) - reagents.conditional_update() - else if(ismovable(A)) - var/atom/movable/M = A - if(isliving(M.loc)) - var/mob/living/L = M.loc - L.unEquip(M, target = src) - else - M.forceMove(src) - SEND_SIGNAL(M, COMSIG_ATOM_USED_IN_CRAFT, src) + if(LAZYLEN(parts_list)) + if(istype(parts_list["reagents"], /datum/reagents)) + var/datum/reagents/RG = parts_list["reagents"] + if(istype(reagents)) + RG.trans_to_holder(reagents, RG.total_volume) + else + reagents = RG + RG.my_atom = src + reagents.conditional_update() + + for(var/atom/movable/M as anything in parts_list["items"]) + if(isliving(M.loc)) + var/mob/living/L = M.loc + L.unEquip(M, target = src) + else + M.forceMove(src) + SEND_SIGNAL(M, COMSIG_ATOM_USED_IN_CRAFT, src) + + var/list/L = parts_list["reagents"] + if(LAZYLEN(L)) + L.Cut() + L = parts_list["items"] + if(LAZYLEN(L)) + L.Cut() parts_list.Cut() /obj/machinery/CheckParts(list/parts_list) diff --git a/code/datums/components/crafting/recipes.dm b/code/datums/components/crafting/recipes.dm index bd5aec3395..444f63b087 100644 --- a/code/datums/components/crafting/recipes.dm +++ b/code/datums/components/crafting/recipes.dm @@ -47,3 +47,55 @@ /datum/crafting_recipe/proc/on_craft_completion(mob/user, atom/result) return +<<<<<<< HEAD +======= + +// Computes the total reagents volume +/datum/crafting_recipe/proc/get_parts_reagents_volume() + . = 0 + for(var/list/L in parts) + for(var/path in L) + if(ispath(path, /datum/reagent)) + . += L[path] + +/datum/crafting_recipe/stunprod + name = "Stunprod" + result = /obj/item/weapon/melee/baton/cattleprod + reqs = list(list(/obj/item/weapon/handcuffs/cable = 1), + list(/obj/item/stack/rods = 1), + list(/obj/item/weapon/tool/wirecutters = 1)) + time = 40 + category = CAT_WEAPONRY + subcategory = CAT_WEAPON + +/datum/crafting_recipe/spear + name = "Spear" + result = /obj/item/weapon/material/twohanded/spear + reqs = list(list(/obj/item/weapon/handcuffs/cable = 1), + list(/obj/item/stack/rods = 1), + list(/obj/item/weapon/material/shard = 1, + /obj/item/weapon/material/butterflyblade = 1) + ) + parts = list(/obj/item/weapon/material/shard = 1, + /obj/item/weapon/material/butterflyblade = 1) + time = 40 + category = CAT_WEAPONRY + subcategory = CAT_WEAPON + +// Locate one of the things that set the material type, and update it from the default (glass) +/datum/crafting_recipe/spear/on_craft_completion(mob/user, atom/result) + var/obj/item/weapon/material/M + for(var/path in parts) + var/obj/item/weapon/material/N = locate(path) in result + if(istype(N, path)) + if(!istype(M)) + M = N + else + N.forceMove(get_turf(result)) + if(!istype(M)) + return + + var/obj/item/weapon/material/twohanded/spear/S = result + S.set_material(M.material.name) + qdel(M) +>>>>>>> 5a261195eab... Refactors crafting to support requirement alternatives (#8184)