Personal crafting & Dependencies

This commit is contained in:
Chompstation Bot
2021-05-06 04:51:46 +00:00
committed by Darlantan
parent 04a8c35801
commit d7f20396de
42 changed files with 2569 additions and 386 deletions

View File

@@ -32,6 +32,12 @@
// Reads the length of L, returning 0 if null
#define LAZYLEN(L) length(L)
#define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += V;
///This is used to add onto lazy assoc list when the value you're adding is a /list/. This one has extra safety over lazyaddassoc because the value could be null (and thus cant be used to += objects)
#define LAZYADDASSOCLIST(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
// Null-safe L.Cut()
#define LAZYCLEARLIST(L) if(L) L.Cut()

View File

@@ -0,0 +1,27 @@
//tablecrafting defines
#define CAT_NONE ""
#define CAT_WEAPONRY "Weaponry"
#define CAT_WEAPON "Weapons"
#define CAT_AMMO "Ammunition"
#define CAT_ROBOT "Robots"
#define CAT_MISC "Misc"
#define CAT_PRIMAL "Tribal"
#define CAT_CLOTHING "Clothing"
#define CAT_FOOD "Foods"
#define CAT_BREAD "Breads"
#define CAT_BURGER "Burgers"
#define CAT_CAKE "Cakes"
#define CAT_EGG "Egg-Based Food"
#define CAT_MEAT "Meats"
#define CAT_MISCFOOD "Misc. Food"
#define CAT_MEXICAN "Mexican Food"
#define CAT_PASTRY "Pastries"
#define CAT_PIE "Pies"
#define CAT_PIZZA "Pizzas"
#define CAT_SALAD "Salads"
#define CAT_SANDWICH "Sandwiches"
#define CAT_SOUP "Soups"
#define CAT_SPAGHETTI "Spaghettis"
#define CAT_ICE "Frozen"
#define CAT_DRINK "Drinks"
#define CAT_CHEMISTRY "Chemistry"

View File

@@ -97,6 +97,10 @@
#define COMSIG_ATOM_FIRE_ACT "atom_fire_act"
///from base of atom/bullet_act(): (/obj/projectile, def_zone)
#define COMSIG_ATOM_BULLET_ACT "atom_bullet_act"
///from base of atom/CheckParts(): (list/parts_list, datum/crafting_recipe/R)
#define COMSIG_ATOM_CHECKPARTS "atom_checkparts"
///from base of atom/CheckParts(): (atom/movable/new_craft) - The atom has just been used in a crafting recipe and has been moved inside new_craft.
#define COMSIG_ATOM_USED_IN_CRAFT "atom_used_in_craft"
///from base of atom/blob_act(): (/obj/structure/blob)
#define COMSIG_ATOM_BLOB_ACT "atom_blob_act"
///from base of atom/acid_act(): (acidpwr, acid_volume)
@@ -732,3 +736,5 @@
///SSalarm signals
#define COMSIG_TRIGGERED_ALARM "ssalarm_triggered"
#define COMSIG_CANCELLED_ALARM "ssalarm_cancelled"
#define COMSIG_REAGENTS_CRAFTING_PING "reagents_crafting_ping"

21
code/__defines/tools.dm Normal file
View File

@@ -0,0 +1,21 @@
// Tool types, if you add new ones please add them to /obj/item/debug/omnitool in code/game/objects/items/debug_items.dm
#define TOOL_CROWBAR "crowbar"
#define TOOL_MULTITOOL "multitool"
#define TOOL_SCREWDRIVER "screwdriver"
#define TOOL_WIRECUTTER "wirecutter"
#define TOOL_WRENCH "wrench"
#define TOOL_WELDER "welder"
#define TOOL_CABLE_COIL "cablecoil"
#define TOOL_ANALYZER "analyzer"
#define TOOL_MINING "mining"
#define TOOL_SHOVEL "shovel"
#define TOOL_RETRACTOR "retractor"
#define TOOL_HEMOSTAT "hemostat"
#define TOOL_CAUTERY "cautery"
#define TOOL_DRILL "drill"
#define TOOL_SCALPEL "scalpel"
#define TOOL_SAW "saw"
#define TOOL_BONESET "bonesetter"
#define TOOL_KNIFE "knife"
#define TOOL_BLOODFILTER "bloodfilter"
#define TOOL_ROLLINGPIN "rollingpin"

View File

@@ -8,4 +8,5 @@ GLOBAL_LIST_EMPTY(wire_color_directory) // This is an associative list with the
GLOBAL_LIST_EMPTY(tagger_locations)
GLOBAL_LIST_INIT(char_directory_tags, list("Pred", "Prey", "Switch", "Non-Vore", "Unset"))
GLOBAL_LIST_INIT(char_directory_erptags, list("Top", "Bottom", "Switch", "No ERP", "Unset"))
GLOBAL_LIST_INIT(char_directory_erptags, list("Top", "Bottom", "Switch", "No ERP", "Unset"))
GLOBAL_LIST_EMPTY(crafting_recipes) //list of all table craft recipes

View File

@@ -416,13 +416,9 @@ This actually tests if they have the same entries and values.
//Mergesort: any value in a list
/proc/sortList(var/list/L)
if(L.len < 2)
return L
var/middle = L.len / 2 + 1 // Copy is first,second-1
return mergeLists(sortList(L.Copy(0,middle)), sortList(L.Copy(middle))) //second parameter null = to end of list
//any value in a list
/proc/sortList(list/L, cmp=/proc/cmp_text_asc)
return sortTim(L.Copy(), cmp)
//Mergsorge: uses sortList() but uses the var's name specifically. This should probably be using mergeAtom() instead
/proc/sortNames(var/list/L)

View File

@@ -256,6 +256,7 @@ GLOBAL_LIST_EMPTY(mannequins)
var/datum/digest_mode/DM = new T
GLOB.digest_modes[DM.id] = DM
// VOREStation Add End
init_crafting_recipes(GLOB.crafting_recipes)
/*
// Custom species traits
@@ -292,8 +293,13 @@ GLOBAL_LIST_EMPTY(mannequins)
return 1 // Hooks must return 1
return 1
/// Inits the crafting recipe list, sorting crafting recipe requirements in the process.
/proc/init_crafting_recipes(list/crafting_recipes)
for(var/path in subtypesof(/datum/crafting_recipe))
var/datum/crafting_recipe/recipe = new path()
recipe.reqs = sortList(recipe.reqs, /proc/cmp_crafting_req_priority)
crafting_recipes += recipe
return crafting_recipes
/* // Uncomment to debug chemical reaction list.
/client/verb/debug_chemical_list()

View File

@@ -64,4 +64,23 @@
return b_score - a_score
/proc/cmp_typepaths_asc(A, B)
return sorttext("[B]","[A]")
return sorttext("[B]","[A]")
/**
* Sorts crafting recipe requirements before the crafting recipe is inserted into GLOB.crafting_recipes
*
* Prioritises [/datum/reagent] to ensure reagent requirements are always processed first when crafting.
* This prevents any reagent_containers from being consumed before the reagents they contain, which can
* lead to runtimes and item duplication when it happens.
*/
/proc/cmp_crafting_req_priority(A, B)
var/lhs
var/rhs
lhs = ispath(A, /datum/reagent) ? 0 : 1
rhs = ispath(B, /datum/reagent) ? 0 : 1
return lhs - rhs
/proc/cmp_text_asc(a,b)
return sorttext(b,a)

View File

@@ -0,0 +1,14 @@
GLOBAL_LIST_EMPTY(string_lists)
/**
* Caches lists with non-numeric stringify-able values (text or typepath).
*/
/proc/string_list(list/values)
var/string_id = values.Join("-")
. = GLOB.string_lists[string_id]
if(.)
return
return GLOB.string_lists[string_id] = values

View File

@@ -18,6 +18,7 @@
/atom/Click(var/location, var/control, var/params) // This is their reaction to being clicked on (standard proc)
if(src)
SEND_SIGNAL(src, COMSIG_CLICK, location, control, params, usr)
usr.ClickOn(src, params)
/atom/DblClick(var/location, var/control, var/params)

View File

@@ -187,3 +187,5 @@
#define ui_mech_airtoggle "WEST+1:-7, SOUTH+8"
#define ui_mech_deco1_f "WEST+2:-7, SOUTH+8"
#define ui_mech_deco2_f "WEST+2:-7, SOUTH+9"
#define ui_crafting "EAST-4:22,SOUTH:5"

View File

@@ -246,6 +246,7 @@
add_overlay(selecting_appearance)
/obj/screen/Click(location, control, params)
..() // why the FUCK was this not called before
if(!usr) return 1
switch(name)
if("toggle")

View File

@@ -0,0 +1,507 @@
/datum/component/personal_crafting/Initialize()
if(ismob(parent))
RegisterSignal(parent, COMSIG_MOB_CLIENT_LOGIN, .proc/create_mob_button)
/datum/component/personal_crafting/proc/create_mob_button(mob/user, client/CL)
// SIGNAL_HANDLER
var/datum/hud/H = user.hud_used
var/obj/screen/craft/C = new()
C.icon = H.ui_style
H.other += C
CL.screen += C
RegisterSignal(C, COMSIG_CLICK, .proc/component_ui_interact)
/datum/component/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 = list(
CAT_WEAPON,
CAT_AMMO,
),
CAT_ROBOT = CAT_NONE,
CAT_MISC = CAT_NONE,
CAT_PRIMAL = CAT_NONE,
CAT_FOOD = list(
CAT_BREAD,
CAT_BURGER,
CAT_CAKE,
CAT_EGG,
CAT_ICE,
CAT_MEAT,
CAT_MISCFOOD,
CAT_PASTRY,
CAT_PIE,
CAT_PIZZA,
CAT_SALAD,
CAT_SANDWICH,
CAT_SOUP,
CAT_SPAGHETTI,
),
CAT_DRINK = CAT_NONE,
CAT_CLOTHING = CAT_NONE,
)
var/cur_category = CAT_NONE
var/cur_subcategory = CAT_NONE
var/datum/action/innate/crafting/button
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 del_reqs, creates result, calls CheckParts of said result with argument being list returned by deel_reqs
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)
/datum/component/personal_crafting/proc/get_environment(atom/a, list/blacklist = null, radius_range = 1)
. = list()
if(!isturf(a.loc))
return
for(var/atom/movable/AM in range(radius_range, a))
if(/*(AM.flags_1 & HOLOGRAM_1) ||*/ (blacklist && (AM.type in blacklist)))
continue
. += AM
/datum/component/personal_crafting/proc/get_surroundings(atom/a, list/blacklist=null)
. = list()
.["tool_qualities"] = list()
.["other"] = list()
.["instances"] = list()
.["machinery"] = list()
for(var/obj/object in get_environment(a, blacklist))
if(isitem(object))
var/obj/item/item = object
LAZYADDASSOCLIST(.["instances"], item.type, item)
if(istype(item, /obj/item/stack))
var/obj/item/stack/stack = item
.["other"][item.type] += stack.amount
else if(item.tool_qualities)
.["tool_qualities"] |= item.tool_qualities
.["other"][item.type] += 1
else
if(istype(item, /obj/item/weapon/reagent_containers))
var/obj/item/weapon/reagent_containers/container = item
// if(container.is_drainable())
if(container.is_open_container()) // this isn't exactly the same
for(var/datum/reagent/reagent in container.reagents.reagent_list)
.["other"][reagent.type] += reagent.volume
.["other"][item.type] += 1
else if (istype(object, /obj/machinery))
LAZYADDASSOCLIST(.["machinery"], object.type, object)
/// 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))
return TRUE
var/list/available_tools = list()
var/list/present_qualities = list()
for(var/obj/item/contained_item in source.contents)
// if(contained_item.GetComponent(/datum/component/storage))
if(istype(contained_item, /obj/item/weapon/storage)) // cursed
for(var/obj/item/subcontained_item in contained_item.contents)
available_tools[subcontained_item.type] = TRUE
for(var/behavior in subcontained_item.tool_qualities)
present_qualities[behavior] = TRUE
available_tools[contained_item.type] = TRUE
for(var/behavior in contained_item.tool_qualities)
present_qualities[behavior] = TRUE
for(var/quality in surroundings["tool_behaviour"])
present_qualities[quality] = TRUE
for(var/path in surroundings["other"])
available_tools[path] = TRUE
for(var/required_quality in recipe.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)
continue
return FALSE
return TRUE
/datum/component/personal_crafting/proc/construct_item(atom/a, datum/crafting_recipe/R)
var/list/contents = 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."
/*Del reqs works like this:
Loop over reqs var of the recipe
Set var amt to the value current cycle req is pointing to, its amount of type we need to delete
Get var/surroundings list of things accessable to crafting by get_environment()
Check the type of the current cycle req
If its reagent then do a while loop, inside it try to locate() reagent containers, inside such containers try to locate needed reagent, if there isn't remove thing from surroundings
If there is enough reagent in the search result then delete the needed amount, create the same type of reagent with the same data var and put it into deletion list
If there isn't enough take all of that reagent from the container, put into deletion list, substract the amt var by the volume of reagent, remove the container from surroundings list and keep searching
While doing above stuff check deletion list if it already has such reagnet, if yes merge instead of adding second one
If its stack check if it has enough amount
If yes create new stack with the needed amount and put in into deletion list, substract taken amount from the stack
If no put all of the stack in the deletion list, substract its amount from amt and keep searching
While doing above stuff check deletion list if it already has such stack type, if yes try to merge them instead of adding new one
If its anything else just locate() in in the list in a while loop, each find --s the amt var and puts the found stuff in deletion loop
Then do a loop over parts var of the recipe
Do similar stuff to what we have done above, but now in deletion list, until the parts conditions are satisfied keep taking from the deletion list and putting it into parts list for return
After its done loop over deletion list and delete all the shit that wasn't taken by parts loop
del_reqs return the list of parts resulting object will receive as argument of CheckParts proc, on the atom level it will add them all to the contents, on all other levels it calls ..() and does whatever is needed afterwards but from contents list already
*/
/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/requirements = list()
if(R.reqs)
requirements += R.reqs
if(R.machinery)
requirements += R.machinery
main_loop:
for(var/path_key in requirements)
amt = R.reqs[path_key] || R.machinery[path_key]
if(!amt)//since machinery can have 0 aka CRAFTING_MACHINERY_USE - i.e. use it, don't consume it!
continue main_loop
surroundings = get_environment(a, R.blacklist)
surroundings -= Deletion
if(ispath(path_key, /datum/reagent))
var/datum/reagent/RG = new path_key
var/datum/reagent/RGNT
while(amt > 0)
var/obj/item/weapon/reagent_containers/RC = locate() in surroundings
RG = RC.reagents.get_reagent(path_key)
if(RG)
if(!locate(RG.type) in Deletion)
Deletion += new RG.type()
if(RG.volume > amt)
RG.volume -= amt
data = RG.data
RC.reagents.conditional_update(RC)
RG = locate(RG.type) in Deletion
RG.volume = amt
RG.data += data
continue main_loop
else
surroundings -= RC
amt -= RG.volume
RC.reagents.reagent_list -= RG
RC.reagents.conditional_update(RC)
RGNT = locate(RG.type) in Deletion
RGNT.volume += RG.volume
RGNT.data += RG.data
qdel(RG)
SEND_SIGNAL(RC.reagents, COMSIG_REAGENTS_CRAFTING_PING) // - [] TODO: Make this entire thing less spaghetti
else
surroundings -= RC
else if(ispath(path_key, /obj/item/stack))
var/obj/item/stack/S
var/obj/item/stack/SD
while(amt > 0)
S = locate(path_key) in surroundings
if(S.amount >= amt)
if(!locate(S.type) in Deletion)
SD = new S.type()
Deletion += SD
S.use(amt)
SD = locate(S.type) in Deletion
SD.amount += amt
continue main_loop
else
amt -= S.amount
if(!locate(S.type) in Deletion)
Deletion += S
else
data = S.amount
S = locate(S.type) in Deletion
S.add(data)
surroundings -= S
else
var/atom/movable/I
while(amt > 0)
I = locate(path_key) in surroundings
Deletion += I
surroundings -= I
amt--
var/list/partlist = list(R.parts.len)
for(var/M in R.parts)
partlist[M] = R.parts[M]
for(var/part in R.parts)
if(istype(part, /datum/reagent))
var/datum/reagent/RG = locate(part) in Deletion
if(RG.volume > partlist[part])
RG.volume = partlist[part]
. += RG
Deletion -= RG
continue
else if(istype(part, /obj/item/stack))
var/obj/item/stack/ST = locate(part) in Deletion
if(ST.amount > partlist[part])
ST.amount = partlist[part]
. += ST
Deletion -= ST
continue
else
while(partlist[part] > 0)
var/atom/movable/AM = locate(part) in Deletion
. += AM
Deletion -= AM
partlist[part] -= 1
while(Deletion.len)
var/DL = Deletion[Deletion.len]
Deletion.Cut(Deletion.len)
// 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(DL, /obj/item/weapon/reagent_containers))
var/obj/item/weapon/reagent_containers/container = DL
container.reagents.clear_reagents()
// container.reagents.expose(container.loc, TOUCH)
else if(istype(DL, /obj/item/weapon/storage))
var/obj/item/weapon/storage/container = DL
container.spill()
container.close_all()
qdel(DL)
/datum/component/personal_crafting/proc/component_ui_interact(atom/movable/screen/craft/image, location, control, params, user)
// SIGNAL_HANDLER
if(user == parent)
INVOKE_ASYNC(src, .proc/tgui_interact, user)
/datum/component/personal_crafting/tgui_state(mob/user)
return GLOB.tgui_not_incapacitated_turf_state
//For the UI related things we're going to assume the user is a mob rather than typesetting it to an atom as the UI isn't generated if the parent is an atom
/datum/component/personal_crafting/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
cur_category = categories[1]
if(islist(categories[cur_category]))
var/list/subcats = categories[cur_category]
cur_subcategory = subcats[1]
else
cur_subcategory = CAT_NONE
ui = new(user, src, "PersonalCrafting")
ui.open()
/datum/component/personal_crafting/tgui_data(mob/user)
var/list/data = list()
data["busy"] = busy
data["category"] = cur_category
data["subcategory"] = cur_subcategory
data["display_craftable_only"] = display_craftable_only
data["display_compact"] = display_compact
var/list/surroundings = get_surroundings(user)
var/list/craftability = 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
craftability["[REF(R)]"] = check_contents(user, R, surroundings)
data["craftability"] = craftability
return data
/datum/component/personal_crafting/tgui_static_data(mob/user)
var/list/data = list()
var/list/crafting_recipes = list()
for(var/rec in GLOB.crafting_recipes)
var/datum/crafting_recipe/R = rec
if(R.name == "") //This is one of the invalid parents that sneaks in
continue
if(!R.always_available && !(R.type in user?.mind?.learned_recipes)) //User doesn't actually know how to make this.
continue
if(isnull(crafting_recipes[R.category]))
crafting_recipes[R.category] = list()
if(R.subcategory == CAT_NONE)
crafting_recipes[R.category] += list(build_recipe_data(R))
else
if(isnull(crafting_recipes[R.category][R.subcategory]))
crafting_recipes[R.category][R.subcategory] = list()
crafting_recipes[R.category]["has_subcats"] = TRUE
crafting_recipes[R.category][R.subcategory] += list(build_recipe_data(R))
data["crafting_recipes"] = crafting_recipes
return data
/datum/component/personal_crafting/tgui_act(action, params)
. = ..()
if(.)
return
switch(action)
if("make")
var/mob/user = usr
var/datum/crafting_recipe/TR = locate(params["recipe"]) in GLOB.crafting_recipes
busy = TRUE
tgui_interact(user)
var/atom/movable/result = construct_item(user, TR)
if(!istext(result)) //We made an item and didn't get a fail message
if(ismob(user) && isitem(result)) //In case the user is actually possessing a non mob like a machine
user.put_in_hands(result)
else
result.forceMove(user.drop_location())
to_chat(user, "<span class='notice'>[TR.name] constructed.</span>")
TR.on_craft_completion(user, result)
else
to_chat(user, "<span class='warning'>Construction failed[result]</span>")
busy = FALSE
if("toggle_recipes")
display_craftable_only = !display_craftable_only
. = TRUE
if("toggle_compact")
display_compact = !display_compact
. = TRUE
if("set_category")
cur_category = params["category"]
cur_subcategory = params["subcategory"] || ""
. = TRUE
/datum/component/personal_crafting/proc/build_recipe_data(datum/crafting_recipe/R)
var/list/data = list()
data["name"] = R.name
data["ref"] = "[REF(R)]"
var/list/req_text = list()
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/obj/machinery/content as anything in R.machinery)
req_text += "[R.reqs[content]] [initial(content.name)]"
if(R.additional_req_text)
req_text += R.additional_req_text
data["req_text"] = req_text.Join(", ")
for(var/atom/req_catalyst as anything in R.chem_catalysts)
catalyst_text += "[R.chem_catalysts[req_catalyst]] [initial(req_catalyst.name)]"
data["catalyst_text"] = catalyst_text.Join(", ")
for(var/required_quality in R.tool_behaviors)
tool_list += required_quality
for(var/obj/item/required_path as anything in R.tool_paths)
tool_list += initial(required_path.name)
data["tool_text"] = tool_list.Join(", ")
return data
//Mind helpers
/datum/mind/proc/teach_crafting_recipe(R)
if(!learned_recipes)
learned_recipes = list()
learned_recipes |= R
// Screen objects
/obj/screen/craft
name = "crafting menu"
icon = 'icons/mob/screen/midnight.dmi'
icon_state = "craft"
screen_loc = ui_crafting

View File

@@ -0,0 +1,34 @@
/**
* Ensure a list of atoms/reagents exists inside this atom
*
* Goes throught he list of passed in parts, if they're reagents, adds them to our reagent holder
* creating the reagent holder if it exists.
*
* If the part is a moveable atom and the previous location of the item was a mob/living,
* it calls the inventory handler transferItemToLoc for that mob/living and transfers the part
* to this atom
*
* Otherwise it simply forceMoves the atom into this atom
*/
/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)
parts_list.Cut()
/obj/machinery/CheckParts(list/parts_list)
..()
RefreshParts()

View File

@@ -0,0 +1,56 @@
///If the machine is used/deleted in the crafting process
#define CRAFTING_MACHINERY_CONSUME 1
///If the machine is only "used" i.e. it checks to see if it's nearby and allows crafting, but doesn't delete it
#define CRAFTING_MACHINERY_USE 0
/datum/crafting_recipe
var/name = "" //in-game display name
var/list/reqs = list() //type paths of items consumed associated with how many are needed
var/list/blacklist = list() //type paths of items explicitly not allowed as an ingredient
var/result //type path of item resulting from this craft
/// String defines of items needed but not consumed. Lazy list.
var/list/tool_behaviors
/// Type paths of items needed but not consumed. Lazy list.
var/list/tool_paths
var/time = 30 //time in deciseconds
var/list/parts = list() //type paths of items that will be placed in the result
var/list/chem_catalysts = list() //like tool_behaviors but for reagents
var/category = CAT_NONE //where it shows up in the crafting UI
var/subcategory = CAT_NONE
var/always_available = TRUE //Set to FALSE if it needs to be learned first.
/// Additonal requirements text shown in UI
var/additional_req_text
///Required machines for the craft, set the assigned value of the typepath to CRAFTING_MACHINERY_CONSUME or CRAFTING_MACHINERY_USE. Lazy associative list: type_path key -> flag value.
var/list/machinery
///Should only one object exist on the same turf?
var/one_per_turf = FALSE
/datum/crafting_recipe/New()
if(!(result in reqs))
blacklist += result
if(tool_behaviors)
tool_behaviors = string_list(tool_behaviors)
if(tool_paths)
tool_paths = string_list(tool_paths)
/**
* Run custom pre-craft checks for this recipe
*
* user: The /mob that initiated the crafting
* collected_requirements: A list of lists of /obj/item instances that satisfy reqs. Top level list is keyed by requirement path.
*/
/datum/crafting_recipe/proc/check_requirements(mob/user, list/collected_requirements)
return TRUE
/datum/crafting_recipe/proc/on_craft_completion(mob/user, atom/result)
return
/datum/crafting_recipe/stunprod
name = "Stunprod"
result = /obj/item/weapon/melee/baton/cattleprod
reqs = list(/obj/item/weapon/handcuffs/cable = 1,
/obj/item/stack/rods = 1,
/obj/item/weapon/tool/wirecutters = 1)
time = 40
category = CAT_WEAPONRY
subcategory = CAT_WEAPON

View File

@@ -0,0 +1,29 @@
/obj/item
var/list/tool_qualities
/// Used to check for a specific tool quality on an item.
/// Returns TRUE or FALSE depending on whether `tool_quality` is found.
/obj/item/proc/has_tool_quality(tool_quality)
return !!LAZYFIND(tool_qualities, tool_quality)
/* Legacy Support */
/// DEPRECATED PROC: DO NOT USE IN NEW CODE
/obj/item/proc/is_screwdriver()
return has_tool_quality(TOOL_SCREWDRIVER)
/// DEPRECATED PROC: DO NOT USE IN NEW CODE
/obj/item/proc/is_wrench()
return has_tool_quality(TOOL_WRENCH)
/// DEPRECATED PROC: DO NOT USE IN NEW CODE
/obj/item/proc/is_crowbar()
return has_tool_quality(TOOL_CROWBAR)
/// DEPRECATED PROC: DO NOT USE IN NEW CODE
/obj/item/proc/is_wirecutter()
return has_tool_quality(TOOL_WIRECUTTER)
/// DEPRECATED PROC: DO NOT USE IN NEW CODE
/obj/item/proc/is_multitool()
return has_tool_quality(TOOL_MULTITOOL)

View File

@@ -58,6 +58,7 @@
var/list/purchase_log = new
var/used_TC = 0
var/list/learned_recipes //List of learned recipe TYPES.
// the world.time since the mob has been brigged, or -1 if not at all
var/brigged_since = -1

View File

@@ -908,31 +908,6 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
/obj/item/proc/apply_accessories(var/image/standing)
return standing
/*
* Assorted tool procs, so any item can emulate any tool, if coded
*/
/obj/item/proc/is_screwdriver()
return FALSE
/obj/item/proc/is_wrench()
return FALSE
/obj/item/proc/is_crowbar()
return FALSE
/obj/item/proc/is_wirecutter()
return FALSE
// These next three might bug out or runtime, unless someone goes back and finds a way to generalize their specific code
/obj/item/proc/is_cable_coil()
return FALSE
/obj/item/proc/is_multitool()
return FALSE
/obj/item/proc/is_welder()
return FALSE
/obj/item/MouseEntered(location,control,params)
. = ..()
if(usr.is_preference_enabled(/datum/client_preference/inv_tooltips) && ((src in usr) || isstorage(loc))) // If in inventory or in storage we're looking at

View File

@@ -29,6 +29,7 @@
var/obj/machinery/connectable //Used to connect machinery.
var/weakref_wiring //Used to store weak references for integrated circuitry. This is now the Omnitool.
toolspeed = 1
tool_qualities = list(TOOL_MULTITOOL)
/obj/item/device/multitool/attack_self(mob/living/user)
var/choice = alert("What do you want to do with \the [src]?","Multitool Menu", "Switch Mode", "Clear Buffers", "Cancel")
@@ -65,9 +66,6 @@
return
/obj/item/device/multitool/is_multitool()
return TRUE
/obj/item/device/multitool/cyborg
name = "multitool"
desc = "Optimised and stripped-down version of a regular multitool."

View File

@@ -176,17 +176,6 @@ var/last_chew = 0
/obj/item/weapon/handcuffs/cable/white
color = "#FFFFFF"
/obj/item/weapon/handcuffs/cable/attackby(var/obj/item/I, mob/user as mob)
..()
if(istype(I, /obj/item/stack/rods))
var/obj/item/stack/rods/R = I
if (R.use(1))
var/obj/item/weapon/material/wirerod/W = new(get_turf(user))
user.put_in_hands(W)
to_chat(user, "<span class='notice'>You wrap the cable restraint around the top of the rod.</span>")
qdel(src)
update_icon(user)
/obj/item/weapon/handcuffs/cyborg
dispenser = 1

View File

@@ -38,33 +38,3 @@
qdel(W)
qdel(src)
return
/obj/item/weapon/material/wirerod
name = "wired rod"
desc = "A rod with some wire wrapped around the top. It'd be easy to attach something to the top bit."
icon_state = "wiredrod"
item_state = "rods"
force = 8
throwforce = 10
w_class = ITEMSIZE_NORMAL
attack_verb = list("hit", "bludgeoned", "whacked", "bonked")
force_divisor = 0.1
thrown_force_divisor = 0.1
/obj/item/weapon/material/wirerod/attackby(var/obj/item/I, mob/user as mob)
..()
var/obj/item/finished
if(istype(I, /obj/item/weapon/material/shard) || istype(I, /obj/item/weapon/material/butterflyblade))
var/obj/item/weapon/material/tmp_shard = I
finished = new /obj/item/weapon/material/twohanded/spear(get_turf(user), tmp_shard.material.name)
to_chat(user, "<span class='notice'>You fasten \the [I] to the top of the rod with the cable.</span>")
else if(I.is_wirecutter())
finished = new /obj/item/weapon/melee/baton/cattleprod(get_turf(user))
to_chat(user, "<span class='notice'>You fasten the wirecutters to the top of the rod with the cable, prongs outward.</span>")
if(finished)
user.drop_from_inventory(src)
user.drop_from_inventory(I)
qdel(I)
qdel(src)
user.put_in_hands(finished)
update_icon(user)

View File

@@ -20,9 +20,7 @@
drop_sound = 'sound/items/drop/crowbar.ogg'
pickup_sound = 'sound/items/pickup/crowbar.ogg'
toolspeed = 1
/obj/item/weapon/tool/crowbar/is_crowbar()
return TRUE
tool_qualities = list(TOOL_CROWBAR)
/obj/item/weapon/tool/crowbar/red
icon = 'icons/obj/tools.dmi'

View File

@@ -21,6 +21,7 @@
attack_verb = list("stabbed")
sharp = 1
toolspeed = 1
tool_qualities = list(TOOL_SCREWDRIVER)
var/random_color = TRUE
/obj/item/weapon/tool/screwdriver/suicide_act(mob/user)
@@ -67,10 +68,6 @@
M = user
return eyestab(M,user)
/obj/item/weapon/tool/screwdriver/is_screwdriver()
return TRUE
/datum/category_item/catalogue/anomalous/precursor_a/alien_screwdriver
name = "Precursor Alpha Object - Hard Light Torgue Tool"
desc = "This appears to be a tool, with a solid handle, and a thin hard light \

View File

@@ -21,6 +21,8 @@
//R&D tech level
origin_tech = list(TECH_ENGINEERING = 1)
tool_qualities = list(TOOL_WELDER)
//Welding tool specific stuff
var/welding = 0 //Whether or not the welding tool is off(0), on(1) or currently welding(2)

View File

@@ -22,6 +22,7 @@
sharp = 1
edge = 1
toolspeed = 1
tool_qualities = list(TOOL_WIRECUTTER)
var/random_color = TRUE
/obj/item/weapon/tool/wirecutters/New()
@@ -43,10 +44,6 @@
else
..()
/obj/item/weapon/tool/wirecutters/is_wirecutter()
return TRUE
/datum/category_item/catalogue/anomalous/precursor_a/alien_wirecutters
name = "Precursor Alpha Object - Wire Seperator"
desc = "An object appearing to have a tool shape. It has two handles, and two \

View File

@@ -17,9 +17,7 @@
toolspeed = 1
drop_sound = 'sound/items/drop/wrench.ogg'
pickup_sound = 'sound/items/pickup/wrench.ogg'
/obj/item/weapon/tool/wrench/is_wrench()
return TRUE
tool_qualities = list(TOOL_WRENCH)
/obj/item/weapon/tool/wrench/cyborg
name = "automatic wrench"

View File

@@ -266,7 +266,6 @@
prob(4);/obj/item/weapon/material/butterfly,
prob(6);/obj/item/weapon/material/butterflyblade,
prob(6);/obj/item/weapon/material/butterflyhandle,
prob(6);/obj/item/weapon/material/wirerod,
prob(2);/obj/item/weapon/material/butterfly/switchblade,
prob(2);/obj/item/clothing/gloves/knuckledusters,
prob(1);/obj/item/weapon/material/knife/tacknife,

View File

@@ -145,7 +145,6 @@
/obj/random/cargopod/item_to_spawn()
return pick(prob(10);/obj/item/weapon/contraband/poster,\
prob(8);/obj/item/weapon/haircomb,\
prob(6);/obj/item/weapon/material/wirerod,\
prob(6);/obj/item/weapon/storage/pill_bottle/paracetamol,\
prob(6);/obj/item/weapon/material/butterflyblade,\
prob(6);/obj/item/weapon/material/butterflyhandle,\

View File

@@ -255,7 +255,6 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh
/obj/item/stack/material/cardboard{amount = 5},
/obj/item/weapon/contraband/poster,
/obj/item/weapon/contraband/poster/custom,
/obj/item/weapon/material/wirerod,
/obj/item/weapon/newspaper,
/obj/item/weapon/paper/crumpled,
/obj/item/weapon/paper/crumpled/bloody

View File

@@ -52,6 +52,15 @@
dna.real_name = real_name
sync_organ_dna()
<<<<<<< HEAD
||||||| parent of b383e17875... Merge pull request #10243 from ShadowLarkens/personal_crafting
//verbs |= /mob/living/proc/toggle_selfsurgery //VOREStation Removal
=======
//verbs |= /mob/living/proc/toggle_selfsurgery //VOREStation Removal
AddComponent(/datum/component/personal_crafting)
>>>>>>> b383e17875... Merge pull request #10243 from ShadowLarkens/personal_crafting
/mob/living/carbon/human/Destroy()
human_mob_list -= src
for(var/organ in organs)

View File

@@ -44,6 +44,7 @@
disconnect_time = null //VOREStation Addition: clear the disconnect time
sight |= SEE_SELF
..()
SEND_SIGNAL(src, COMSIG_MOB_LOGIN)
if(loc && !isturf(loc))
client.eye = loc
@@ -81,4 +82,5 @@
update_client_z(T.z)
if(cloaked && cloaked_selfimage)
client.images += cloaked_selfimage
client.images += cloaked_selfimage
SEND_SIGNAL(src, COMSIG_MOB_CLIENT_LOGIN, client)

View File

@@ -510,6 +510,7 @@ obj/structure/cable/proc/cableColor(var/colorC)
stacktype = /obj/item/stack/cable_coil
drop_sound = 'sound/items/drop/accessory.ogg'
pickup_sound = 'sound/items/pickup/accessory.ogg'
tool_qualities = list(TOOL_CABLE_COIL)
/obj/item/stack/cable_coil/cyborg
name = "cable coil synthesizer"

View File

@@ -210,7 +210,7 @@ GLOBAL_LIST_EMPTY(gravity_generators)
update_icon()
return
if(GRAV_NEEDS_WELDING)
if(I.is_welder())
if(I.has_tool_quality(TOOL_WELDER))
var/obj/item/weapon/weldingtool/W = I
if(W.remove_fuel(0,user))
to_chat(user, "<span class='notice'>You mend the damaged framework.</span>")

View File

@@ -80,7 +80,7 @@
material_needs[component_needed] = rand(1,3)
if(ispath(my_guntype, /obj/item/weapon/gun/launcher) && prob(50))
var/component_needed = pick(/obj/item/weapon/tape_roll, /obj/item/weapon/material/wirerod)
var/component_needed = pick(/obj/item/weapon/tape_roll, /obj/item/stack/rods, /obj/item/weapon/handcuffs/cable)
material_needs[component_needed] = 1
if(ispath(my_guntype, /obj/item/weapon/gun/magnetic) && prob(70))

View File

@@ -37,7 +37,8 @@
CheckParts()
FireModeModify()
/obj/item/weapon/gun/energy/modular/proc/CheckParts() //What parts do we have inside us, and how good are they?
/obj/item/weapon/gun/energy/modular/CheckParts() //What parts do we have inside us, and how good are they?
..()
capacitor_rating = 0
laser_rating = 0
manipulator_rating = 0

View File

@@ -0,0 +1,250 @@
/datum/reagent
var/name = "Reagent"
var/id = "reagent"
var/description = "A non-descript chemical."
var/taste_description = "bitterness"
var/taste_mult = 1 //how this taste compares to others. Higher values means it is more noticable
var/datum/reagents/holder = null
var/reagent_state = SOLID
var/list/data = null
var/volume = 0
var/metabolism = REM // This would be 0.2 normally
var/list/filtered_organs = list() // Organs that will slow the processing of this chemical.
var/mrate_static = FALSE //If the reagent should always process at the same speed, regardless of species, make this TRUE
var/ingest_met = 0
var/touch_met = 0
var/dose = 0
var/max_dose = 0
var/overdose = 0 //Amount at which overdose starts
var/overdose_mod = 1 //Modifier to overdose damage
var/can_overdose_touch = FALSE // Can the chemical OD when processing on touch?
var/scannable = 0 // Shows up on health analyzers.
var/affects_dead = 0 // Does this chem process inside a corpse?
var/affects_robots = 0 // Does this chem process inside a Synth?
var/allergen_type = GENERIC // What potential allergens does this contain?
var/allergen_factor = 1 // If the potential allergens are mixed and low-volume, they're a bit less dangerous. Needed for drinks because they're a single reagent compared to food which contains multiple seperate reagents.
var/cup_icon_state = null
var/cup_name = null
var/cup_desc = null
var/cup_center_of_mass = null
var/color = "#000000"
var/color_weight = 1
var/glass_icon = DRINK_ICON_DEFAULT
var/glass_name = "something"
var/glass_desc = "It's a glass of... what, exactly?"
var/list/glass_special = null // null equivalent to list()
/datum/reagent/proc/remove_self(var/amount) // Shortcut
if(holder)
holder.remove_reagent(id, amount)
// This doesn't apply to skin contact - this is for, e.g. extinguishers and sprays. The difference is that reagent is not directly on the mob's skin - it might just be on their clothing.
/datum/reagent/proc/touch_mob(var/mob/M, var/amount)
return
/datum/reagent/proc/touch_obj(var/obj/O, var/amount) // Acid melting, cleaner cleaning, etc
return
/datum/reagent/proc/touch_turf(var/turf/T, var/amount) // Cleaner cleaning, lube lubbing, etc, all go here
return
/datum/reagent/proc/on_mob_life(var/mob/living/carbon/M, var/alien, var/datum/reagents/metabolism/location) // Currently, on_mob_life is called on carbons. Any interaction with non-carbon mobs (lube) will need to be done in touch_mob.
if(!istype(M))
return
if(!affects_dead && M.stat == DEAD)
return
if(!affects_robots && M.isSynthetic())
return
if(!istype(location))
return
var/datum/reagents/metabolism/active_metab = location
var/removed = metabolism
var/ingest_rem_mult = 1
var/ingest_abs_mult = 1
if(!mrate_static == TRUE)
// Modifiers
for(var/datum/modifier/mod in M.modifiers)
if(!isnull(mod.metabolism_percent))
removed *= mod.metabolism_percent
ingest_rem_mult *= mod.metabolism_percent
// Species
removed *= M.species.metabolic_rate
ingest_rem_mult *= M.species.metabolic_rate
// Metabolism
removed *= active_metab.metabolism_speed
ingest_rem_mult *= active_metab.metabolism_speed
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(!H.isSynthetic())
if(H.species.has_organ[O_HEART] && (active_metab.metabolism_class == CHEM_BLOOD))
var/obj/item/organ/internal/heart/Pump = H.internal_organs_by_name[O_HEART]
if(!Pump)
removed *= 0.1
else if(Pump.standard_pulse_level == PULSE_NONE) // No pulse normally means chemicals process a little bit slower than normal.
removed *= 0.8
else // Otherwise, chemicals process as per percentage of your current pulse, or, if you have no pulse but are alive, by a miniscule amount.
removed *= max(0.1, H.pulse / Pump.standard_pulse_level)
if(H.species.has_organ[O_STOMACH] && (active_metab.metabolism_class == CHEM_INGEST))
var/obj/item/organ/internal/stomach/Chamber = H.internal_organs_by_name[O_STOMACH]
if(Chamber)
ingest_rem_mult *= max(0.1, 1 - (Chamber.damage / Chamber.max_damage))
else
ingest_rem_mult = 0.1
if(H.species.has_organ[O_INTESTINE] && (active_metab.metabolism_class == CHEM_INGEST))
var/obj/item/organ/internal/intestine/Tube = H.internal_organs_by_name[O_INTESTINE]
if(Tube)
ingest_abs_mult *= max(0.1, 1 - (Tube.damage / Tube.max_damage))
else
ingest_abs_mult = 0.1
else
var/obj/item/organ/internal/heart/machine/Pump = H.internal_organs_by_name[O_PUMP]
var/obj/item/organ/internal/stomach/machine/Cycler = H.internal_organs_by_name[O_CYCLER]
if(active_metab.metabolism_class == CHEM_BLOOD)
if(Pump)
removed *= 1.1 - Pump.damage / Pump.max_damage
else
removed *= 0.1
else if(active_metab.metabolism_class == CHEM_INGEST) // If the pump is damaged, we waste chems from the tank.
if(Pump)
ingest_abs_mult *= max(0.25, 1 - Pump.damage / Pump.max_damage)
else
ingest_abs_mult *= 0.2
if(Cycler) // If we're damaged, we empty our tank slower.
ingest_rem_mult = max(0.1, 1 - (Cycler.damage / Cycler.max_damage))
else
ingest_rem_mult = 0.1
else if(active_metab.metabolism_class == CHEM_TOUCH) // Machines don't exactly absorb chemicals.
removed *= 0.5
if(filtered_organs && filtered_organs.len)
for(var/organ_tag in filtered_organs)
var/obj/item/organ/internal/O = H.internal_organs_by_name[organ_tag]
if(O && !O.is_broken() && prob(max(0, O.max_damage - O.damage)))
removed *= 0.8
if(active_metab.metabolism_class == CHEM_INGEST)
ingest_rem_mult *= 0.8
if(ingest_met && (active_metab.metabolism_class == CHEM_INGEST))
removed = ingest_met * ingest_rem_mult
if(touch_met && (active_metab.metabolism_class == CHEM_TOUCH))
removed = touch_met
removed = min(removed, volume)
max_dose = max(volume, max_dose)
dose = min(dose + removed, max_dose)
if(removed >= (metabolism * 0.1) || removed >= 0.1) // If there's too little chemical, don't affect the mob, just remove it
switch(active_metab.metabolism_class)
if(CHEM_BLOOD)
affect_blood(M, alien, removed)
if(CHEM_INGEST)
affect_ingest(M, alien, removed * ingest_abs_mult)
if(CHEM_TOUCH)
affect_touch(M, alien, removed)
if(overdose && (volume > overdose * M?.species.chemOD_threshold) && (active_metab.metabolism_class != CHEM_TOUCH && !can_overdose_touch))
overdose(M, alien, removed)
if(M.species.allergens & allergen_type) //uhoh, we can't handle this!
var/damage_severity = M.species.allergen_damage_severity*allergen_factor
var/disable_severity = M.species.allergen_disable_severity*allergen_factor
if(M.species.allergen_reaction & AG_TOX_DMG)
M.adjustToxLoss(damage_severity)
if(M.species.allergen_reaction & AG_OXY_DMG)
M.adjustOxyLoss(damage_severity)
if(prob(2.5*disable_severity))
M.emote(pick("cough","gasp","choke"))
if(M.species.allergen_reaction & AG_EMOTE)
if(prob(2.5*disable_severity)) //this has a higher base chance, but not *too* high
M.emote(pick("pale","shiver","twitch"))
if(M.species.allergen_reaction & AG_PAIN)
M.adjustHalLoss(disable_severity)
if(M.species.allergen_reaction & AG_WEAKEN)
M.Weaken(disable_severity)
if(M.species.allergen_reaction & AG_BLURRY)
M.eye_blurry = max(M.eye_blurry, disable_severity)
if(M.species.allergen_reaction & AG_SLEEPY)
M.drowsyness = max(M.drowsyness, disable_severity)
remove_self(removed)
return
/datum/reagent/proc/affect_blood(var/mob/living/carbon/M, var/alien, var/removed)
return
/datum/reagent/proc/affect_ingest(var/mob/living/carbon/M, var/alien, var/removed)
M.bloodstr.add_reagent(id, removed)
return
/datum/reagent/proc/affect_touch(var/mob/living/carbon/M, var/alien, var/removed)
return
/datum/reagent/proc/overdose(var/mob/living/carbon/M, var/alien, var/removed) // Overdose effect.
if(alien == IS_DIONA)
return
if(ishuman(M))
var/mob/living/carbon/human/H = M
overdose_mod *= H.species.chemOD_mod
// 6 damage per unit at minimum, scales with excessive reagents. Rounding should help keep damage consistent between ingest / inject, but isn't perfect.
// Hardcapped at 3.6 damage per tick, or 18 damage per unit at 0.2 metabolic rate so that you can't instakill people with overdoses by feeding them infinite periadaxon.
// Overall, max damage is slightly less effective than hydrophoron, and 1/5 as effective as cyanide.
M.adjustToxLoss(min(removed * overdose_mod * round(3 + 3 * volume / overdose), 3.6))
/datum/reagent/proc/initialize_data(var/newdata) // Called when the reagent is created.
if(!isnull(newdata))
data = newdata
return
/datum/reagent/proc/mix_data(var/newdata, var/newamount) // You have a reagent with data, and new reagent with its own data get added, how do you deal with that?
return
/datum/reagent/proc/get_data() // Just in case you have a reagent that handles data differently.
if(data && istype(data, /list))
return data.Copy()
else if(data)
return data
return null
/datum/reagent/Destroy() // This should only be called by the holder, so it's already handled clearing its references
holder = null
. = ..()
// Called when reagents are removed from a container, most likely after metabolizing in a mob
/datum/reagent/proc/on_remove(var/atom/A)
return
// Called when a mob dies
/datum/reagent/proc/on_mob_death(var/mob/M)
return
//on transfer to new container, return 1 to allow it to continue
/datum/reagent/proc/on_transfer(var/volume)
return 1
/* DEPRECATED - TODO: REMOVE EVERYWHERE */
/datum/reagent/proc/reaction_turf(var/turf/target)
touch_turf(target)
/datum/reagent/proc/reaction_obj(var/obj/target)
touch_obj(target)
/datum/reagent/proc/reaction_mob(var/mob/target)
touch_mob(target)

File diff suppressed because it is too large Load Diff

View File

@@ -1,251 +1,251 @@
/datum/reagent
var/name = "Reagent"
var/id = "reagent"
var/description = "A non-descript chemical."
var/taste_description = "bitterness"
var/taste_mult = 1 //how this taste compares to others. Higher values means it is more noticable
var/datum/reagents/holder = null
var/reagent_state = SOLID
var/list/data = null
var/volume = 0
var/metabolism = REM // This would be 0.2 normally
var/list/filtered_organs = list() // Organs that will slow the processing of this chemical.
var/mrate_static = FALSE //If the reagent should always process at the same speed, regardless of species, make this TRUE
var/ingest_met = 0
var/touch_met = 0
var/dose = 0
var/max_dose = 0
var/overdose = 0 //Amount at which overdose starts
var/overdose_mod = 1 //Modifier to overdose damage
var/can_overdose_touch = FALSE // Can the chemical OD when processing on touch?
var/scannable = 0 // Shows up on health analyzers.
var/affects_dead = 0 // Does this chem process inside a corpse?
var/affects_robots = 0 // Does this chem process inside a Synth?
var/allergen_type = GENERIC // What potential allergens does this contain?
var/allergen_factor = 1 // If the potential allergens are mixed and low-volume, they're a bit less dangerous. Needed for drinks because they're a single reagent compared to food which contains multiple seperate reagents.
var/cup_icon_state = null
var/cup_name = null
var/cup_desc = null
var/cup_center_of_mass = null
var/color = "#000000"
var/color_weight = 1
var/glass_icon = DRINK_ICON_DEFAULT
var/glass_name = "something"
var/glass_desc = "It's a glass of... what, exactly?"
var/list/glass_special = null // null equivalent to list()
/datum/reagent/proc/remove_self(var/amount) // Shortcut
if(holder)
holder.remove_reagent(id, amount)
// This doesn't apply to skin contact - this is for, e.g. extinguishers and sprays. The difference is that reagent is not directly on the mob's skin - it might just be on their clothing.
/datum/reagent/proc/touch_mob(var/mob/M, var/amount)
return
/datum/reagent/proc/touch_obj(var/obj/O, var/amount) // Acid melting, cleaner cleaning, etc
return
/datum/reagent/proc/touch_turf(var/turf/T, var/amount) // Cleaner cleaning, lube lubbing, etc, all go here
return
/datum/reagent/proc/on_mob_life(var/mob/living/carbon/M, var/alien, var/datum/reagents/metabolism/location) // Currently, on_mob_life is called on carbons. Any interaction with non-carbon mobs (lube) will need to be done in touch_mob.
if(!istype(M))
return
if(!affects_dead && M.stat == DEAD)
return
if(!affects_robots && M.isSynthetic())
return
if(!istype(location))
return
var/datum/reagents/metabolism/active_metab = location
var/removed = metabolism
var/ingest_rem_mult = 1
var/ingest_abs_mult = 1
if(!mrate_static == TRUE)
// Modifiers
for(var/datum/modifier/mod in M.modifiers)
if(!isnull(mod.metabolism_percent))
removed *= mod.metabolism_percent
ingest_rem_mult *= mod.metabolism_percent
// Species
removed *= M.species.metabolic_rate
ingest_rem_mult *= M.species.metabolic_rate
// Metabolism
removed *= active_metab.metabolism_speed
ingest_rem_mult *= active_metab.metabolism_speed
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(!H.isSynthetic())
if(H.species.has_organ[O_HEART] && (active_metab.metabolism_class == CHEM_BLOOD))
var/obj/item/organ/internal/heart/Pump = H.internal_organs_by_name[O_HEART]
if(!Pump)
removed *= 0.1
else if(Pump.standard_pulse_level == PULSE_NONE) // No pulse normally means chemicals process a little bit slower than normal.
removed *= 0.8
else // Otherwise, chemicals process as per percentage of your current pulse, or, if you have no pulse but are alive, by a miniscule amount.
removed *= max(0.1, H.pulse / Pump.standard_pulse_level)
if(H.species.has_organ[O_STOMACH] && (active_metab.metabolism_class == CHEM_INGEST))
var/obj/item/organ/internal/stomach/Chamber = H.internal_organs_by_name[O_STOMACH]
if(Chamber)
ingest_rem_mult *= max(0.1, 1 - (Chamber.damage / Chamber.max_damage))
else
ingest_rem_mult = 0.1
if(H.species.has_organ[O_INTESTINE] && (active_metab.metabolism_class == CHEM_INGEST))
var/obj/item/organ/internal/intestine/Tube = H.internal_organs_by_name[O_INTESTINE]
if(Tube)
ingest_abs_mult *= max(0.1, 1 - (Tube.damage / Tube.max_damage))
else
ingest_abs_mult = 0.1
else
var/obj/item/organ/internal/heart/machine/Pump = H.internal_organs_by_name[O_PUMP]
var/obj/item/organ/internal/stomach/machine/Cycler = H.internal_organs_by_name[O_CYCLER]
if(active_metab.metabolism_class == CHEM_BLOOD)
if(Pump)
removed *= 1.1 - Pump.damage / Pump.max_damage
else
removed *= 0.1
else if(active_metab.metabolism_class == CHEM_INGEST) // If the pump is damaged, we waste chems from the tank.
if(Pump)
ingest_abs_mult *= max(0.25, 1 - Pump.damage / Pump.max_damage)
else
ingest_abs_mult *= 0.2
if(Cycler) // If we're damaged, we empty our tank slower.
ingest_rem_mult = max(0.1, 1 - (Cycler.damage / Cycler.max_damage))
else
ingest_rem_mult = 0.1
else if(active_metab.metabolism_class == CHEM_TOUCH) // Machines don't exactly absorb chemicals.
removed *= 0.5
if(filtered_organs && filtered_organs.len)
for(var/organ_tag in filtered_organs)
var/obj/item/organ/internal/O = H.internal_organs_by_name[organ_tag]
if(O && !O.is_broken() && prob(max(0, O.max_damage - O.damage)))
removed *= 0.8
if(active_metab.metabolism_class == CHEM_INGEST)
ingest_rem_mult *= 0.8
if(ingest_met && (active_metab.metabolism_class == CHEM_INGEST))
removed = ingest_met * ingest_rem_mult
if(touch_met && (active_metab.metabolism_class == CHEM_TOUCH))
removed = touch_met
removed = min(removed, volume)
max_dose = max(volume, max_dose)
dose = min(dose + removed, max_dose)
if(removed >= (metabolism * 0.1) || removed >= 0.1) // If there's too little chemical, don't affect the mob, just remove it
switch(active_metab.metabolism_class)
if(CHEM_BLOOD)
affect_blood(M, alien, removed)
if(CHEM_INGEST)
affect_ingest(M, alien, removed * ingest_abs_mult)
if(CHEM_TOUCH)
affect_touch(M, alien, removed)
if(overdose && (volume > overdose * M?.species.chemOD_threshold) && (active_metab.metabolism_class != CHEM_TOUCH && !can_overdose_touch))
overdose(M, alien, removed)
if(M.species.allergens & allergen_type) //uhoh, we can't handle this!
var/damage_severity = M.species.allergen_damage_severity*allergen_factor
var/disable_severity = M.species.allergen_disable_severity*allergen_factor
if(M.species.allergen_reaction & AG_TOX_DMG)
M.adjustToxLoss(damage_severity)
if(M.species.allergen_reaction & AG_OXY_DMG)
M.adjustOxyLoss(damage_severity)
if(prob(2.5*disable_severity))
M.emote(pick("cough","gasp","choke"))
if(M.species.allergen_reaction & AG_EMOTE)
if(prob(2.5*disable_severity)) //this has a higher base chance, but not *too* high
M.emote(pick("pale","shiver","twitch"))
if(M.species.allergen_reaction & AG_PAIN)
M.adjustHalLoss(disable_severity)
if(M.species.allergen_reaction & AG_WEAKEN)
M.Weaken(disable_severity)
if(M.species.allergen_reaction & AG_BLURRY)
M.eye_blurry = max(M.eye_blurry, disable_severity)
if(M.species.allergen_reaction & AG_SLEEPY)
M.drowsyness = max(M.drowsyness, disable_severity)
remove_self(removed)
return
/datum/reagent/proc/affect_blood(var/mob/living/carbon/M, var/alien, var/removed)
return
/datum/reagent/proc/affect_ingest(var/mob/living/carbon/M, var/alien, var/removed)
M.bloodstr.add_reagent(id, removed)
return
/datum/reagent/proc/affect_touch(var/mob/living/carbon/M, var/alien, var/removed)
return
/datum/reagent/proc/overdose(var/mob/living/carbon/M, var/alien, var/removed) // Overdose effect.
if(alien == IS_DIONA)
return
if(ishuman(M))
var/mob/living/carbon/human/H = M
overdose_mod *= H.species.chemOD_mod
// 6 damage per unit at minimum, scales with excessive reagents. Rounding should help keep damage consistent between ingest / inject, but isn't perfect.
// Hardcapped at 3.6 damage per tick, or 18 damage per unit at 0.2 metabolic rate so that you can't instakill people with overdoses by feeding them infinite periadaxon.
// Overall, max damage is slightly less effective than hydrophoron, and 1/5 as effective as cyanide.
M.adjustToxLoss(min(removed * overdose_mod * round(3 + 3 * volume / overdose), 3.6))
/datum/reagent/proc/initialize_data(var/newdata) // Called when the reagent is created.
if(!isnull(newdata))
data = newdata
return
/datum/reagent/proc/mix_data(var/newdata, var/newamount) // You have a reagent with data, and new reagent with its own data get added, how do you deal with that?
return
/datum/reagent/proc/get_data() // Just in case you have a reagent that handles data differently.
if(data && istype(data, /list))
return data.Copy()
else if(data)
return data
return null
/datum/reagent/Destroy() // This should only be called by the holder, so it's already handled clearing its references
holder = null
. = ..()
//YW edit start
// Called when reagents are removed from a container, most likely after metabolizing in a mob
/datum/reagent/proc/on_remove(var/atom/A)
return
// Called when a mob dies
/datum/reagent/proc/on_mob_death(var/mob/M)
return
//on transfer to new container, return 1 to allow it to continue
/datum/reagent/proc/on_transfer(var/volume)
return 1
//YW edit end
/* DEPRECATED - TODO: REMOVE EVERYWHERE */
/datum/reagent/proc/reaction_turf(var/turf/target)
touch_turf(target)
/datum/reagent/proc/reaction_obj(var/obj/target)
touch_obj(target)
/datum/reagent/proc/reaction_mob(var/mob/target)
touch_mob(target)
/datum/reagent
var/name = "Reagent"
var/id = "reagent"
var/description = "A non-descript chemical."
var/taste_description = "bitterness"
var/taste_mult = 1 //how this taste compares to others. Higher values means it is more noticable
var/datum/reagents/holder = null
var/reagent_state = SOLID
var/list/data = null
var/volume = 0
var/metabolism = REM // This would be 0.2 normally
var/list/filtered_organs = list() // Organs that will slow the processing of this chemical.
var/mrate_static = FALSE //If the reagent should always process at the same speed, regardless of species, make this TRUE
var/ingest_met = 0
var/touch_met = 0
var/dose = 0
var/max_dose = 0
var/overdose = 0 //Amount at which overdose starts
var/overdose_mod = 1 //Modifier to overdose damage
var/can_overdose_touch = FALSE // Can the chemical OD when processing on touch?
var/scannable = 0 // Shows up on health analyzers.
var/affects_dead = 0 // Does this chem process inside a corpse?
var/affects_robots = 0 // Does this chem process inside a Synth?
var/allergen_type = GENERIC // What potential allergens does this contain?
var/allergen_factor = 1 // If the potential allergens are mixed and low-volume, they're a bit less dangerous. Needed for drinks because they're a single reagent compared to food which contains multiple seperate reagents.
var/cup_icon_state = null
var/cup_name = null
var/cup_desc = null
var/cup_center_of_mass = null
var/color = "#000000"
var/color_weight = 1
var/glass_icon = DRINK_ICON_DEFAULT
var/glass_name = "something"
var/glass_desc = "It's a glass of... what, exactly?"
var/list/glass_special = null // null equivalent to list()
/datum/reagent/proc/remove_self(var/amount) // Shortcut
if(holder)
holder.remove_reagent(id, amount)
// This doesn't apply to skin contact - this is for, e.g. extinguishers and sprays. The difference is that reagent is not directly on the mob's skin - it might just be on their clothing.
/datum/reagent/proc/touch_mob(var/mob/M, var/amount)
return
/datum/reagent/proc/touch_obj(var/obj/O, var/amount) // Acid melting, cleaner cleaning, etc
return
/datum/reagent/proc/touch_turf(var/turf/T, var/amount) // Cleaner cleaning, lube lubbing, etc, all go here
return
/datum/reagent/proc/on_mob_life(var/mob/living/carbon/M, var/alien, var/datum/reagents/metabolism/location) // Currently, on_mob_life is called on carbons. Any interaction with non-carbon mobs (lube) will need to be done in touch_mob.
if(!istype(M))
return
if(!affects_dead && M.stat == DEAD)
return
if(!affects_robots && M.isSynthetic())
return
if(!istype(location))
return
var/datum/reagents/metabolism/active_metab = location
var/removed = metabolism
var/ingest_rem_mult = 1
var/ingest_abs_mult = 1
if(!mrate_static == TRUE)
// Modifiers
for(var/datum/modifier/mod in M.modifiers)
if(!isnull(mod.metabolism_percent))
removed *= mod.metabolism_percent
ingest_rem_mult *= mod.metabolism_percent
// Species
removed *= M.species.metabolic_rate
ingest_rem_mult *= M.species.metabolic_rate
// Metabolism
removed *= active_metab.metabolism_speed
ingest_rem_mult *= active_metab.metabolism_speed
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(!H.isSynthetic())
if(H.species.has_organ[O_HEART] && (active_metab.metabolism_class == CHEM_BLOOD))
var/obj/item/organ/internal/heart/Pump = H.internal_organs_by_name[O_HEART]
if(!Pump)
removed *= 0.1
else if(Pump.standard_pulse_level == PULSE_NONE) // No pulse normally means chemicals process a little bit slower than normal.
removed *= 0.8
else // Otherwise, chemicals process as per percentage of your current pulse, or, if you have no pulse but are alive, by a miniscule amount.
removed *= max(0.1, H.pulse / Pump.standard_pulse_level)
if(H.species.has_organ[O_STOMACH] && (active_metab.metabolism_class == CHEM_INGEST))
var/obj/item/organ/internal/stomach/Chamber = H.internal_organs_by_name[O_STOMACH]
if(Chamber)
ingest_rem_mult *= max(0.1, 1 - (Chamber.damage / Chamber.max_damage))
else
ingest_rem_mult = 0.1
if(H.species.has_organ[O_INTESTINE] && (active_metab.metabolism_class == CHEM_INGEST))
var/obj/item/organ/internal/intestine/Tube = H.internal_organs_by_name[O_INTESTINE]
if(Tube)
ingest_abs_mult *= max(0.1, 1 - (Tube.damage / Tube.max_damage))
else
ingest_abs_mult = 0.1
else
var/obj/item/organ/internal/heart/machine/Pump = H.internal_organs_by_name[O_PUMP]
var/obj/item/organ/internal/stomach/machine/Cycler = H.internal_organs_by_name[O_CYCLER]
if(active_metab.metabolism_class == CHEM_BLOOD)
if(Pump)
removed *= 1.1 - Pump.damage / Pump.max_damage
else
removed *= 0.1
else if(active_metab.metabolism_class == CHEM_INGEST) // If the pump is damaged, we waste chems from the tank.
if(Pump)
ingest_abs_mult *= max(0.25, 1 - Pump.damage / Pump.max_damage)
else
ingest_abs_mult *= 0.2
if(Cycler) // If we're damaged, we empty our tank slower.
ingest_rem_mult = max(0.1, 1 - (Cycler.damage / Cycler.max_damage))
else
ingest_rem_mult = 0.1
else if(active_metab.metabolism_class == CHEM_TOUCH) // Machines don't exactly absorb chemicals.
removed *= 0.5
if(filtered_organs && filtered_organs.len)
for(var/organ_tag in filtered_organs)
var/obj/item/organ/internal/O = H.internal_organs_by_name[organ_tag]
if(O && !O.is_broken() && prob(max(0, O.max_damage - O.damage)))
removed *= 0.8
if(active_metab.metabolism_class == CHEM_INGEST)
ingest_rem_mult *= 0.8
if(ingest_met && (active_metab.metabolism_class == CHEM_INGEST))
removed = ingest_met * ingest_rem_mult
if(touch_met && (active_metab.metabolism_class == CHEM_TOUCH))
removed = touch_met
removed = min(removed, volume)
max_dose = max(volume, max_dose)
dose = min(dose + removed, max_dose)
if(removed >= (metabolism * 0.1) || removed >= 0.1) // If there's too little chemical, don't affect the mob, just remove it
switch(active_metab.metabolism_class)
if(CHEM_BLOOD)
affect_blood(M, alien, removed)
if(CHEM_INGEST)
affect_ingest(M, alien, removed * ingest_abs_mult)
if(CHEM_TOUCH)
affect_touch(M, alien, removed)
if(overdose && (volume > overdose * M?.species.chemOD_threshold) && (active_metab.metabolism_class != CHEM_TOUCH && !can_overdose_touch))
overdose(M, alien, removed)
if(M.species.allergens & allergen_type) //uhoh, we can't handle this!
var/damage_severity = M.species.allergen_damage_severity*allergen_factor
var/disable_severity = M.species.allergen_disable_severity*allergen_factor
if(M.species.allergen_reaction & AG_TOX_DMG)
M.adjustToxLoss(damage_severity)
if(M.species.allergen_reaction & AG_OXY_DMG)
M.adjustOxyLoss(damage_severity)
if(prob(2.5*disable_severity))
M.emote(pick("cough","gasp","choke"))
if(M.species.allergen_reaction & AG_EMOTE)
if(prob(2.5*disable_severity)) //this has a higher base chance, but not *too* high
M.emote(pick("pale","shiver","twitch"))
if(M.species.allergen_reaction & AG_PAIN)
M.adjustHalLoss(disable_severity)
if(M.species.allergen_reaction & AG_WEAKEN)
M.Weaken(disable_severity)
if(M.species.allergen_reaction & AG_BLURRY)
M.eye_blurry = max(M.eye_blurry, disable_severity)
if(M.species.allergen_reaction & AG_SLEEPY)
M.drowsyness = max(M.drowsyness, disable_severity)
remove_self(removed)
return
/datum/reagent/proc/affect_blood(var/mob/living/carbon/M, var/alien, var/removed)
return
/datum/reagent/proc/affect_ingest(var/mob/living/carbon/M, var/alien, var/removed)
M.bloodstr.add_reagent(id, removed)
return
/datum/reagent/proc/affect_touch(var/mob/living/carbon/M, var/alien, var/removed)
return
/datum/reagent/proc/overdose(var/mob/living/carbon/M, var/alien, var/removed) // Overdose effect.
if(alien == IS_DIONA)
return
if(ishuman(M))
var/mob/living/carbon/human/H = M
overdose_mod *= H.species.chemOD_mod
// 6 damage per unit at minimum, scales with excessive reagents. Rounding should help keep damage consistent between ingest / inject, but isn't perfect.
// Hardcapped at 3.6 damage per tick, or 18 damage per unit at 0.2 metabolic rate so that you can't instakill people with overdoses by feeding them infinite periadaxon.
// Overall, max damage is slightly less effective than hydrophoron, and 1/5 as effective as cyanide.
M.adjustToxLoss(min(removed * overdose_mod * round(3 + 3 * volume / overdose), 3.6))
/datum/reagent/proc/initialize_data(var/newdata) // Called when the reagent is created.
if(!isnull(newdata))
data = newdata
return
/datum/reagent/proc/mix_data(var/newdata, var/newamount) // You have a reagent with data, and new reagent with its own data get added, how do you deal with that?
return
/datum/reagent/proc/get_data() // Just in case you have a reagent that handles data differently.
if(data && istype(data, /list))
return data.Copy()
else if(data)
return data
return null
/datum/reagent/Destroy() // This should only be called by the holder, so it's already handled clearing its references
holder = null
. = ..()
//YW edit start
// Called when reagents are removed from a container, most likely after metabolizing in a mob
/datum/reagent/proc/on_remove(var/atom/A)
return
// Called when a mob dies
/datum/reagent/proc/on_mob_death(var/mob/M)
return
//on transfer to new container, return 1 to allow it to continue
/datum/reagent/proc/on_transfer(var/volume)
return 1
//YW edit end
/* DEPRECATED - TODO: REMOVE EVERYWHERE */
/datum/reagent/proc/reaction_turf(var/turf/target)
touch_turf(target)
/datum/reagent/proc/reaction_obj(var/obj/target)
touch_obj(target)
/datum/reagent/proc/reaction_mob(var/mob/target)
touch_mob(target)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -0,0 +1,187 @@
import { useBackend, useLocalState } from '../backend';
import { Button, Dimmer, Flex, Icon, LabeledList, Section, Tabs } from '../components';
import { Window } from '../layouts';
export const PersonalCrafting = (props, context) => {
const { act, data } = useBackend(context);
const {
busy,
display_craftable_only,
display_compact,
} = data;
const crafting_recipes = data.crafting_recipes || {};
// Sort everything into flat categories
const categories = [];
const recipes = [];
for (let category of Object.keys(crafting_recipes)) {
const subcategories = crafting_recipes[category];
if ('has_subcats' in subcategories) {
for (let subcategory of Object.keys(subcategories)) {
if (subcategory === 'has_subcats') {
continue;
}
// Push category
categories.push({
name: subcategory,
category,
subcategory,
});
// Push recipes
const _recipes = subcategories[subcategory];
for (let recipe of _recipes) {
recipes.push({
...recipe,
category: subcategory,
});
}
}
continue;
}
// Push category
categories.push({
name: category,
category,
});
// Push recipes
const _recipes = crafting_recipes[category];
for (let recipe of _recipes) {
recipes.push({
...recipe,
category,
});
}
}
// Sort out the tab state
const [tab, setTab] = useLocalState(
context, 'tab', categories[0]?.name);
const shownRecipes = recipes
.filter(recipe => recipe.category === tab);
return (
<Window
title="Crafting Menu"
width={700}
height={800}>
<Window.Content scrollable>
{!!busy && (
<Dimmer fontSize="32px">
<Icon name="cog" spin={1} />
{' Crafting...'}
</Dimmer>
)}
<Section
title="Personal Crafting"
buttons={(
<>
<Button.Checkbox
content="Compact"
checked={display_compact}
onClick={() => act('toggle_compact')} />
<Button.Checkbox
content="Craftable Only"
checked={display_craftable_only}
onClick={() => act('toggle_recipes')} />
</>
)}>
<Flex>
<Flex.Item>
<Tabs vertical>
{categories.map(category => (
<Tabs.Tab
key={category.name}
selected={category.name === tab}
onClick={() => {
setTab(category.name);
act('set_category', {
category: category.category,
subcategory: category.subcategory,
});
}}>
{category.name}
</Tabs.Tab>
))}
</Tabs>
</Flex.Item>
<Flex.Item grow={1} basis={0}>
<CraftingList craftables={shownRecipes} />
</Flex.Item>
</Flex>
</Section>
</Window.Content>
</Window>
);
};
const CraftingList = (props, context) => {
const {
craftables = [],
} = props;
const { act, data } = useBackend(context);
const {
craftability = {},
display_compact,
display_craftable_only,
} = data;
return craftables.map(craftable => {
if (display_craftable_only && !craftability[craftable.ref]) {
return null;
}
// Compact display
if (display_compact) {
return (
<LabeledList.Item
key={craftable.name}
label={craftable.name}
className="candystripe"
buttons={(
<Button
icon="cog"
content="Craft"
disabled={!craftability[craftable.ref]}
tooltip={craftable.tool_text && (
'Tools needed: ' + craftable.tool_text
)}
tooltipPosition="left"
onClick={() => act('make', {
recipe: craftable.ref,
})} />
)}>
{craftable.req_text}
</LabeledList.Item>
);
}
// Full display
return (
<Section
key={craftable.name}
title={craftable.name}
level={2}
buttons={(
<Button
icon="cog"
content="Craft"
disabled={!craftability[craftable.ref]}
onClick={() => act('make', {
recipe: craftable.ref,
})} />
)}>
<LabeledList>
{!!craftable.req_text && (
<LabeledList.Item label="Required">
{craftable.req_text}
</LabeledList.Item>
)}
{!!craftable.catalyst_text && (
<LabeledList.Item label="Catalyst">
{craftable.catalyst_text}
</LabeledList.Item>
)}
{!!craftable.tool_text && (
<LabeledList.Item label="Tools">
{craftable.tool_text}
</LabeledList.Item>
)}
</LabeledList>
</Section>
);
});
};

File diff suppressed because one or more lines are too long

View File

@@ -43,6 +43,7 @@
#include "code\__defines\chemistry_vr.dm"
#include "code\__defines\color.dm"
#include "code\__defines\construction.dm"
#include "code\__defines\crafting.dm"
#include "code\__defines\damage_organs.dm"
#include "code\__defines\dna.dm"
#include "code\__defines\exosuit_fab.dm"
@@ -96,6 +97,7 @@
#include "code\__defines\tgs.config.dm"
#include "code\__defines\tgs.dm"
#include "code\__defines\tgui.dm"
#include "code\__defines\tools.dm"
#include "code\__defines\turfs.dm"
#include "code\__defines\typeids.dm"
#include "code\__defines\unit_tests.dm"
@@ -140,6 +142,7 @@
#include "code\_helpers\names.dm"
#include "code\_helpers\sanitize_values.dm"
#include "code\_helpers\storage.dm"
#include "code\_helpers\string_lists.dm"
#include "code\_helpers\text.dm"
#include "code\_helpers\text_vr.dm"
#include "code\_helpers\time.dm"
@@ -355,6 +358,10 @@
#include "code\datums\autolathe\tools_vr.dm"
#include "code\datums\components\_component.dm"
#include "code\datums\components\resize_guard.dm"
#include "code\datums\components\crafting\crafting.dm"
#include "code\datums\components\crafting\crafting_external.dm"
#include "code\datums\components\crafting\recipes.dm"
#include "code\datums\components\crafting\tool_quality.dm"
#include "code\datums\elements\_element.dm"
#include "code\datums\game_masters\_common.dm"
#include "code\datums\helper_datums\construction_datum.dm"