/datum/personal_crafting
var/busy
var/viewing_category = 1 //typical powergamer starting on the Weapons tab
var/viewing_subcategory = 1
var/list/categories = list(
CAT_WEAPONRY,
CAT_ROBOT,
CAT_MISC,
CAT_PRIMAL,
CAT_FOOD,
CAT_DECORATIONS,
CAT_CLOTHING)
var/list/subcategories = list(
list( //Weapon subcategories
CAT_WEAPON,
CAT_AMMO),
CAT_NONE, //Robot subcategories
CAT_NONE, //Misc subcategories
CAT_NONE, //Tribal subcategories
list( //Food subcategories
CAT_CAKE,
CAT_SUSHI,
CAT_SANDWICH),
list( //Decoration subcategories
CAT_DECORATION,
CAT_HOLIDAY,
CAT_LARGE_DECORATIONS),
CAT_CLOTHING) //Clothing subcategories
var/display_craftable_only = FALSE
var/display_compact = TRUE
/* This is what procs do:
get_environment - gets a list of things accessable for crafting by user
get_surroundings - takes a list of things and makes a list of key-types to values-amounts of said type in the list
check_contents - takes a recipe and a key-type list and checks if said recipe can be done with available stuff
check_tools - takes recipe, a key-type list, and a user and checks if there are enough tools to do the stuff, checks bugs one level deep
construct_item - takes a recipe and a user, call all the checking procs, calls do_after, checks all the things again, calls requirements_deletion, creates result, calls CheckParts of said result with argument being list returned by deel_reqs
requirements_deletion - 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
*/
/datum/personal_crafting/proc/check_contents(datum/crafting_recipe/R, list/contents)
contents = contents["other"]
main_loop:
for(var/A in R.reqs)
var/needed_amount = R.reqs[A]
for(var/B in contents)
if(ispath(B, A))
if(R.blacklist.Find(B))
continue
if(contents[B] >= R.reqs[A])
continue main_loop
else
needed_amount -= contents[B]
if(needed_amount <= 0)
continue main_loop
else
continue
return 0
for(var/A in R.chem_catalysts)
if(contents[A] < R.chem_catalysts[A])
return 0
return 1
/datum/personal_crafting/proc/get_environment(mob/user)
. = list()
. += user.r_hand
. += user.l_hand
if(!isturf(user.loc))
return
var/list/L = block(get_step(user, SOUTHWEST), get_step(user, NORTHEAST))
for(var/A in L)
var/turf/T = A
if(T.Adjacent(user))
for(var/B in T)
var/atom/movable/AM = B
if(AM.flags_2 & HOLOGRAM_2)
continue
. += AM
for(var/slot in list(SLOT_HUD_RIGHT_STORE, SLOT_HUD_LEFT_STORE))
. += user.get_item_by_slot(slot)
/datum/personal_crafting/proc/get_surroundings(mob/user)
. = list()
.["other"] = list() //paths go in here
.["toolsother"] = list() // items go in here
for(var/obj/item/I in get_environment(user))
if(I.flags_2 & HOLOGRAM_2)
continue
if(istype(I, /obj/item/stack))
var/obj/item/stack/S = I
.["other"][I.type] += S.amount
else
if(istype(I, /obj/item/reagent_containers))
var/obj/item/reagent_containers/RC = I
if(RC.is_drainable())
for(var/datum/reagent/A in RC.reagents.reagent_list)
.["other"][A.type] += A.volume
.["other"][I.type] += 1
.["toolsother"][I] += 1
/datum/personal_crafting/proc/check_tools(mob/user, datum/crafting_recipe/R, list/contents)
if(!R.tools.len) //does not run if no tools are needed
return TRUE
var/list/possible_tools = list()
var/list/tools_used = list()
for(var/obj/item/I in user.contents) //searchs the inventory of the mob
if(isstorage(I))
for(var/obj/item/SI in I.contents)
if(SI.tool_behaviour) //filters for tool behaviours
possible_tools += SI
if(I.tool_behaviour)
possible_tools += I
possible_tools |= contents["toolsother"] // this add contents to possible_tools
main_loop: // checks if all tools found are usable with the recipe
for(var/A in R.tools)
for(var/obj/item/I in possible_tools)
if(A == I.tool_behaviour)
tools_used += I
continue main_loop
return FALSE
for(var/obj/item/T in tools_used)
if(!T.tool_start_check(null, user, 0)) //Check if all our tools are valid for their use
return FALSE
return TRUE
/datum/personal_crafting/proc/check_pathtools(mob/user, datum/crafting_recipe/R, list/contents)
if(!R.pathtools.len) //does not run if no tools are needed
return TRUE
var/list/other_possible_tools = list()
for(var/obj/item/I in user.contents) // searchs the inventory of the mob
if(isstorage(I))
for(var/obj/item/SI in I.contents)
other_possible_tools += SI.type // filters type paths
other_possible_tools += I.type
other_possible_tools |= contents["other"] // this adds contents to the other_possible_tools
main_loop: // checks if all tools found are usable with the recipe
for(var/A in R.pathtools)
for(var/I in other_possible_tools)
if(ispath(I,A))
continue main_loop
return FALSE
return TRUE
/datum/personal_crafting/proc/construct_item(mob/user, datum/crafting_recipe/recipe)
var/list/contents = get_surroundings(user)
var/send_feedback = 1
if(!check_contents(recipe, contents))
return ", missing component."
if(!check_tools(user, recipe, contents))
return ", missing tool."
if(!check_pathtools(user, recipe, contents))
return ", missing tool."
if(!do_after(user, recipe.time, target = user))
return "."
contents = get_surroundings(user)
if(!check_contents(recipe, contents))
return ", missing component."
if(!check_tools(user, recipe, contents))
return ", missing tool."
if(!check_pathtools(user, recipe, contents))
return ", missing tool."
var/list/parts = requirements_deletion(recipe, user)
if(!parts)
return ", missing component."
for(var/possible_result in recipe.result)
var/atom/movable/craft_result = new possible_result (get_turf(user.loc))
craft_result.CheckParts(parts, recipe)
if(isitem(craft_result))
user.put_in_hands(craft_result)
if(send_feedback)
SSblackbox.record_feedback("tally", "object_crafted", 1, craft_result.type)
return 0
/*
* requirements_deletion() is a function that takes crafting_recipe and user mob as input, returns the list of parts the crafting_recipe result is consists of
* Firstly it process the surroundings adding the right amount of ingredients to the appropriate lists, combining splitted items into new recipe result part
* Then it deletes everything it used to create recipe result part and returns the list of parts
*/
/datum/personal_crafting/proc/requirements_deletion(datum/crafting_recipe/recipe, mob/user)
var/list/surroundings = get_environment(user)
var/list/parts_used = list()
var/list/item_stacks_for_deletion = list()
var/list/reagent_list_for_deletion = list()
for(var/thing in recipe.reqs)
var/needed_amount = recipe.reqs[thing]
if(ispath(thing, /datum/reagent))
var/datum/reagent/part_reagent = locate(thing) in parts_used
if(!part_reagent)
part_reagent = new thing()
parts_used += part_reagent
for(var/obj/item/reagent_containers/container in surroundings)
var/datum/reagent/contained_reagent = container.reagents.get_reagent(thing)
if(!contained_reagent)
continue
var/extracted_amount = min(contained_reagent.volume, needed_amount)
reagent_list_for_deletion[thing] += list(list(container, extracted_amount))
part_reagent.volume += extracted_amount
part_reagent.data += contained_reagent.data
needed_amount -= extracted_amount
if(needed_amount <= 0)
break
if(needed_amount > 0)
stack_trace("While crafting [recipe], some of [thing] went missing (still need [needed_amount])!")
continue // ignore the error, and continue crafting for player's benefit
else if(ispath(thing, /obj/item/stack))
var/obj/item/stack/part_stack = locate(thing) in parts_used
if(!part_stack)
part_stack = new thing()
part_stack.amount = 0
parts_used += part_stack
for(var/obj/item/stack/item_stack in (surroundings - item_stacks_for_deletion))
if(!istype(item_stack, thing))
continue
var/extracted_amount = min(item_stack.amount, needed_amount)
item_stacks_for_deletion[item_stack] = extracted_amount
part_stack.amount += extracted_amount
needed_amount -= extracted_amount
if(needed_amount <= 0)
break
if(needed_amount > 0)
stack_trace("While crafting [recipe], some of [thing] went missing (still need [needed_amount])!")
continue
else
for(var/i in 1 to needed_amount)
var/atom/movable/part_atom
for(var/atom/movable/candidate as anything in (surroundings - parts_used))
if(istype(candidate, thing) && !is_type_in_list(candidate, recipe.blacklist))
part_atom = candidate
break
if(!part_atom)
stack_trace("While crafting [recipe], the [thing] went missing!")
continue
parts_used += part_atom
for(var/datum/reagent/reagent_to_delete as anything in reagent_list_for_deletion)
for(var/list/reagent_info in reagent_list_for_deletion[reagent_to_delete])
var/obj/item/reagent_containers/container = reagent_info[1]
var/amount_to_delete = reagent_info[2]
container.reagents.remove_reagent(reagent_to_delete.id, amount_to_delete)
for(var/obj/item/stack/stack_to_delete as anything in item_stacks_for_deletion)
var/amount_to_delete = item_stacks_for_deletion[stack_to_delete]
stack_to_delete.use(amount_to_delete)
// Sort out the used parts into the ones we need to return (denoted by recipe.parts),
// and the ones we need to delete (the rest of recipe.reqs)
var/parts_returned = list()
for(var/part_path in recipe.parts)
for(var/i in 1 to recipe.parts[part_path])
var/part = locate(part_path) in parts_used
if(!part)
stack_trace("Part [part_path] went missing")
parts_returned += part
parts_used -= part
QDEL_LIST_CONTENTS(parts_used)
return parts_returned
/datum/personal_crafting/ui_state(mob/user)
return GLOB.not_incapacitated_turf_state
/datum/personal_crafting/ui_interact(mob/user, datum/tgui/ui = null)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "PersonalCrafting", "Crafting Menu")
ui.open()
/datum/personal_crafting/proc/close(mob/user)
var/datum/tgui/ui = SStgui.get_open_ui(user, src, "main")
if(ui)
ui.close()
/datum/personal_crafting/ui_data(mob/user)
var/list/data = list()
var/list/subs = list()
var/cur_subcategory = CAT_NONE
var/cur_category = categories[viewing_category]
if(islist(subcategories[viewing_category]))
subs = subcategories[viewing_category]
cur_subcategory = subs[viewing_subcategory]
data["busy"] = busy
data["prev_cat"] = categories[prev_cat()]
data["prev_subcat"] = subs[prev_subcat()]
data["category"] = cur_category
data["subcategory"] = cur_subcategory
data["next_cat"] = categories[next_cat()]
data["next_subcat"] = subs[next_subcat()]
data["display_craftable_only"] = display_craftable_only
data["display_compact"] = display_compact
var/list/surroundings = get_surroundings(user)
var/list/can_craft = list()
var/list/cant_craft = list()
for(var/rec in GLOB.crafting_recipes)
var/datum/crafting_recipe/R = rec
if(!R.always_available && !(R.type in user?.mind?.learned_recipes)) //User doesn't actually know how to make this.
continue
if((R.category != cur_category) || (R.subcategory != cur_subcategory))
continue
if(check_contents(R, surroundings))
can_craft += list(build_recipe_data(R))
else
cant_craft += list(build_recipe_data(R))
data["can_craft"] = can_craft
data["cant_craft"] = cant_craft
return data
/datum/personal_crafting/ui_act(action, list/params)
if(..())
return
. = TRUE
switch(action)
if("make")
var/datum/crafting_recipe/TR = locate(params["make"]) in GLOB.crafting_recipes
if(!istype(TR))
return
busy = TRUE
SStgui.update_uis(src)
var/fail_msg = construct_item(usr, TR)
if(!fail_msg)
to_chat(usr, "[TR.name] constructed.")
if(TR.alert_admins_on_craft)
message_admins("[key_name_admin(usr)] has created a [TR.name] at [ADMIN_COORDJMP(usr)]")
else
to_chat(usr, "Construction failed[fail_msg]")
busy = FALSE
SStgui.update_uis(src)
if("forwardCat")
viewing_category = next_cat(FALSE)
if("backwardCat")
viewing_category = prev_cat(FALSE)
if("forwardSubCat")
viewing_subcategory = next_subcat()
if("backwardSubCat")
viewing_subcategory = prev_subcat()
if("toggle_recipes")
display_craftable_only = !display_craftable_only
if("toggle_compact")
display_compact = !display_compact
//Next works nicely with modular arithmetic
/datum/personal_crafting/proc/next_cat(readonly = TRUE)
if(!readonly)
viewing_subcategory = 1
. = viewing_category % categories.len + 1
/datum/personal_crafting/proc/next_subcat()
if(islist(subcategories[viewing_category]))
var/list/subs = subcategories[viewing_category]
. = viewing_subcategory % subs.len + 1
//Previous can go fuck itself
/datum/personal_crafting/proc/prev_cat(readonly = TRUE)
if(!readonly)
viewing_subcategory = 1
if(viewing_category == categories.len)
. = viewing_category-1
else
. = viewing_category % categories.len - 1
if(. <= 0)
. = categories.len
/datum/personal_crafting/proc/prev_subcat()
if(islist(subcategories[viewing_category]))
var/list/subs = subcategories[viewing_category]
if(viewing_subcategory == subs.len)
. = viewing_subcategory-1
else
. = viewing_subcategory % subs.len - 1
if(. <= 0)
. = subs.len
else
. = null
/datum/personal_crafting/proc/build_recipe_data(datum/crafting_recipe/R)
var/list/data = list()
data["name"] = R.name
data["ref"] = "\ref[R]"
var/req_text = ""
var/tool_text = ""
var/catalyst_text = ""
for(var/a 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]"
var/atom/A = a
req_text += " [R.reqs[A]] [initial(A.name)],"
req_text = replacetext(req_text, ",", "", -1)
data["req_text"] = req_text
for(var/a in R.chem_catalysts)
var/atom/A = a //cheat-typecast
catalyst_text += " [R.chem_catalysts[A]] [initial(A.name)],"
catalyst_text = replacetext(catalyst_text, ",", "", -1)
data["catalyst_text"] = catalyst_text
for(var/a in R.pathtools)
if(ispath(a, /obj/item))
var/obj/item/b = a
tool_text += " [initial(b.name)],"
else
tool_text += " [a],"
for(var/a in R.tools)
var/b = a
tool_text += " [b],"
tool_text = replacetext(tool_text, ",", "", -1)
data["tool_text"] = tool_text
return data
//Mind helpers
/datum/mind/proc/teach_crafting_recipe(R)
if(!learned_recipes)
learned_recipes = list()
learned_recipes |= R