diff --git a/code/datums/components/crafting/recipes.dm b/code/datums/components/crafting/recipes.dm
index 01129916d63..fdc3654d379 100644
--- a/code/datums/components/crafting/recipes.dm
+++ b/code/datums/components/crafting/recipes.dm
@@ -138,6 +138,7 @@
result = /obj/item/tailclub
reqs = list(/obj/item/organ/tail/lizard = 1,
/obj/item/stack/sheet/iron = 1)
+ blacklist = list(/obj/item/organ/tail/lizard/fake)
time = 40
category = CAT_WEAPONRY
subcategory = CAT_WEAPON
@@ -147,6 +148,7 @@
result = /obj/item/melee/chainofcommand/tailwhip
reqs = list(/obj/item/organ/tail/lizard = 1,
/obj/item/stack/cable_coil = 1)
+ blacklist = list(/obj/item/organ/tail/lizard/fake)
time = 40
category = CAT_WEAPONRY
subcategory = CAT_WEAPON
diff --git a/code/game/machinery/limbgrower.dm b/code/game/machinery/limbgrower.dm
index a2916c1d390..46d86afccb5 100644
--- a/code/game/machinery/limbgrower.dm
+++ b/code/game/machinery/limbgrower.dm
@@ -1,9 +1,5 @@
-#define LIMBGROWER_MAIN_MENU 1
-#define LIMBGROWER_CATEGORY_MENU 2
-#define LIMBGROWER_CHEMICAL_MENU 3
-//use these for the menu system
-
-
+/// The limbgrower. Makes organd and limbs with synthflesh and chems.
+/// See [limbgrower_designs.dm] for everything we can make.
/obj/machinery/limbgrower
name = "limb grower"
desc = "It grows new limbs using Synthflesh."
@@ -15,135 +11,222 @@
active_power_usage = 100
circuit = /obj/item/circuitboard/machine/limbgrower
- var/operating = FALSE
- var/disabled = FALSE
+ /// The category of limbs we're browing in our UI.
+ var/selected_category = "human"
+ /// If we're currently printing something.
var/busy = FALSE
- var/prod_coeff = 1
+ /// How efficient our machine is. Better parts = less chemicals used and less power used. Range of 1 to 0.25.
+ var/production_coefficient = 1
+ /// How long it takes for us to print a limb. Affected by production_coefficient.
+ var/production_speed = 3 SECONDS
+ /// The design we're printing currently.
var/datum/design/being_built
+ /// Our internal techweb for limbgrower designs.
var/datum/techweb/stored_research
- var/selected_category
- var/screen = 1
- var/list/categories = list(
- "human",
- "lizard",
- "moth",
- "plasmaman",
- "ethereal",
- "other"
- )
+ /// All the categories of organs we can print.
+ var/list/categories = list("human", "lizard", "moth", "plasmaman", "ethereal", "other")
/obj/machinery/limbgrower/Initialize()
create_reagents(100, OPENCONTAINER)
stored_research = new /datum/techweb/specialized/autounlocking/limbgrower
. = ..()
+ AddComponent(/datum/component/plumbing/simple_demand)
-/obj/machinery/limbgrower/ui_interact(mob/user)
+/obj/machinery/limbgrower/ui_interact(mob/user, datum/tgui/ui)
. = ..()
- if(!is_operational)
- return
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Limbgrower")
+ ui.open()
- var/dat = main_win(user)
+/obj/machinery/limbgrower/ui_state(mob/user)
+ return GLOB.physical_state
- switch(screen)
- if(LIMBGROWER_MAIN_MENU)
- dat = main_win(user)
- if(LIMBGROWER_CATEGORY_MENU)
- dat = category_win(user,selected_category)
- if(LIMBGROWER_CHEMICAL_MENU)
- dat = chemical_win(user)
+/obj/machinery/limbgrower/ui_data(mob/user)
+ var/list/data = list()
- var/datum/browser/popup = new(user, "Limb Grower", name, 400, 500)
- popup.set_content(dat)
- popup.open()
+ for(var/datum/reagent/reagent_id in reagents.reagent_list)
+ var/list/reagent_data = list(
+ reagent_name = reagent_id.name,
+ reagent_amount = reagent_id.volume,
+ reagent_type = reagent_id.type
+ )
+ data["reagents"] += list(reagent_data)
+
+ data["total_reagents"] = reagents.total_volume
+ data["max_reagents"] = reagents.maximum_volume
+ data["busy"] = busy
+
+ return data
+
+/obj/machinery/limbgrower/ui_static_data(mob/user)
+ var/list/data = list()
+ data["categories"] = list()
+
+ var/species_categories = categories.Copy()
+ for(var/species in species_categories)
+ species_categories[species] = list()
+ for(var/design_id in stored_research.researched_designs)
+ var/datum/design/limb_design = SSresearch.techweb_design_by_id(design_id)
+ for(var/found_category in species_categories)
+ if(found_category in limb_design.category)
+ species_categories[found_category] += limb_design
+
+ for(var/category in species_categories)
+ var/list/category_data = list(
+ name = category,
+ designs = list(),
+ )
+ for(var/datum/design/found_design in species_categories[category])
+ var/list/all_reagents = list()
+ for(var/reagent_typepath in found_design.reagents_list)
+ var/datum/reagent/reagent_id = find_reagent_object_from_type(reagent_typepath)
+ var/list/reagent_data = list(
+ name = reagent_id.name,
+ amount = (found_design.reagents_list[reagent_typepath] * production_coefficient),
+ )
+ all_reagents += list(reagent_data)
+
+ category_data["designs"] += list(list(
+ parent_category = category,
+ name = found_design.name,
+ id = found_design.id,
+ needed_reagents = all_reagents,
+ ))
+
+ data["categories"] += list(category_data)
+
+ return data
/obj/machinery/limbgrower/on_deconstruction()
- for(var/obj/item/reagent_containers/glass/G in component_parts)
- reagents.trans_to(G, G.reagents.maximum_volume)
+ for(var/obj/item/reagent_containers/glass/our_beaker in component_parts)
+ reagents.trans_to(our_beaker, our_beaker.reagents.maximum_volume)
..()
-/obj/machinery/limbgrower/attackby(obj/item/O, mob/living/user, params)
+/obj/machinery/limbgrower/attackby(obj/item/user_item, mob/living/user, params)
if (busy)
to_chat(user, "The Limb Grower is busy. Please wait for completion of previous operation.")
return
- if(default_deconstruction_screwdriver(user, "limbgrower_panelopen", "limbgrower_idleoff", O))
- updateUsrDialog()
+ if(istype(user_item, /obj/item/disk/design_disk/limbs))
+ user.visible_message("[user] begins to load \the [user_item] in \the [src]...",
+ "You begin to load designs from \the [user_item]...",
+ "You hear the clatter of a floppy drive.")
+ busy = TRUE
+ var/obj/item/disk/design_disk/limbs/limb_design_disk = user_item
+ if(do_after(user, 2 SECONDS, target = src))
+ for(var/datum/design/found_design in limb_design_disk.blueprints)
+ stored_research.add_design(found_design)
+ update_static_data(user)
+ busy = FALSE
return
- if(panel_open && default_deconstruction_crowbar(O))
+ if(default_deconstruction_screwdriver(user, "limbgrower_panelopen", "limbgrower_idleoff", user_item))
+ ui_close(user)
+ return
+
+ if(panel_open && default_deconstruction_crowbar(user_item))
return
if(user.combat_mode) //so we can hit the machine
return ..()
-/obj/machinery/limbgrower/Topic(href, href_list)
- if(..())
+/obj/machinery/limbgrower/ui_act(action, list/params)
+ . = ..()
+ if(.)
return
- if (!busy)
- if(href_list["menu"])
- screen = text2num(href_list["menu"])
- if(href_list["category"] in categories) //Don't let people send invalid categories, selected_category is displayed to anyone who looks at the machine in several places
- selected_category = href_list["category"]
+ if (busy)
+ to_chat(usr, "The limb grower is busy. Please wait for completion of previous operation.")
+ return
- if(href_list["disposeI"]) //Get rid of a reagent incase you add the wrong one by mistake
- reagents.del_reagent(text2path(href_list["disposeI"]))
+ switch(action)
- if(href_list["make"])
+ if("empty_reagent")
+ reagents.del_reagent(text2path(params["reagent_type"]))
+ . = TRUE
- /////////////////
- //href protection
- being_built = stored_research.isDesignResearchedID(href_list["make"]) //check if it's a valid design
+ if("make_limb")
+ being_built = stored_research.isDesignResearchedID(params["design_id"])
if(!being_built)
- return
+ CRASH("[src] was passed an invalid design id!")
+ /// All the reagents we're using to make our organ.
+ var/list/consumed_reagents_list = being_built.reagents_list.Copy()
+ /// The amount of power we're going to use, based on how much reagent we use.
+ var/power = 0
- var/synth_cost = being_built.reagents_list[/datum/reagent/medicine/c2/synthflesh]*prod_coeff
- var/power = max(2000, synth_cost/5)
+ for(var/reagent_id in consumed_reagents_list)
+ consumed_reagents_list[reagent_id] *= production_coefficient
+ if(!reagents.has_reagent(reagent_id, consumed_reagents_list[reagent_id]))
+ audible_message("The [src] buzzes.")
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
+ return
- if(reagents.has_reagent(/datum/reagent/medicine/c2/synthflesh, being_built.reagents_list[/datum/reagent/medicine/c2/synthflesh]*prod_coeff))
- busy = TRUE
- use_power(power)
- flick("limbgrower_fill",src)
- icon_state = "limbgrower_idleon"
- addtimer(CALLBACK(src, .proc/build_item),32*prod_coeff)
+ power = max(2000, (power + consumed_reagents_list[reagent_id]))
- else
- to_chat(usr, "The limb grower is busy. Please wait for completion of previous operation.")
+ busy = TRUE
+ use_power(power)
+ flick("limbgrower_fill",src)
+ icon_state = "limbgrower_idleon"
+ selected_category = params["active_tab"]
+ addtimer(CALLBACK(src, .proc/build_item, consumed_reagents_list), production_speed * production_coefficient)
+ . = TRUE
- updateUsrDialog()
return
-/obj/machinery/limbgrower/proc/build_item()
- if(reagents.has_reagent(/datum/reagent/medicine/c2/synthflesh, being_built.reagents_list[/datum/reagent/medicine/c2/synthflesh]*prod_coeff)) //sanity check, if this happens we are in big trouble
- reagents.remove_reagent(/datum/reagent/medicine/c2/synthflesh,being_built.reagents_list[/datum/reagent/medicine/c2/synthflesh]*prod_coeff)
- var/buildpath = being_built.build_path
- if(ispath(buildpath, /obj/item/bodypart)) //This feels like spatgheti code, but i need to initilise a limb somehow
- build_limb(buildpath)
- else
- //Just build whatever it is
- new buildpath(loc)
- else
- src.visible_message("Something went very wrong, there isn't enough synthflesh anymore!")
- busy = FALSE
- flick("limbgrower_unfill",src)
- icon_state = "limbgrower_idleoff"
- updateUsrDialog()
+/*
+ * The process of beginning to build a limb or organ.
+ * Goes through and sanity checks that we actually have enough reagent to build our item.
+ * Then, remove those reagents from our reagents datum.
+ *
+ * After the reagents are handled, we can proceede with making the limb or organ. (Limbs are handled in a separate proc)
+ *
+ * modified_consumed_reagents_list - the list of reagents we will consume on build, modified by the production coefficient.
+ */
+/obj/machinery/limbgrower/proc/build_item(list/modified_consumed_reagents_list)
+ for(var/reagent_id in modified_consumed_reagents_list)
+ if(!reagents.has_reagent(reagent_id, modified_consumed_reagents_list[reagent_id]))
+ audible_message("The [src] buzzes.")
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
+ break
+ reagents.remove_reagent(reagent_id, modified_consumed_reagents_list[reagent_id])
+
+ var/built_typepath = being_built.build_path
+ // If we have a bodypart, we need to initialize the limb on its own. Otherwise we can build it here.
+ if(ispath(built_typepath, /obj/item/bodypart))
+ build_limb(built_typepath)
+ else
+ new built_typepath(loc)
+
+ busy = FALSE
+ flick("limbgrower_unfill", src)
+ icon_state = "limbgrower_idleoff"
+
+/*
+ * The process of putting together a limb.
+ * This is called from after we remove the reagents, so this proc is just initializing the limb type.
+ *
+ * This proc handles skin / mutant color, greyscaling, names and descriptions, and various other limb creation steps.
+ *
+ * buildpath - the path of the bodypart we're building.
+ */
/obj/machinery/limbgrower/proc/build_limb(buildpath)
- //i need to create a body part manually using a set icon (otherwise it doesnt appear)
- var/obj/item/bodypart/limb
- limb = new buildpath(loc)
- if(selected_category=="human" || selected_category=="lizard" || selected_category=="ethereal") //Species with greyscale parts should be included here
- if(selected_category=="human") //humans don't use the full colour spectrum, they use random_skin_tone
+ /// The limb we're making with our buildpath, so we can edit it.
+ var/obj/item/bodypart/limb = new buildpath(loc)
+ /// Species with greyscale limbs.
+ var/list/greyscale_species = list("human", "lizard", "ethereal")
+ if(selected_category in greyscale_species) //Species with greyscale parts should be included here
+ if(selected_category == "human") //humans don't use the full colour spectrum, they use random_skin_tone
limb.skin_tone = random_skin_tone()
else
limb.species_color = random_short_color()
-
limb.icon = 'icons/mob/human_parts_greyscale.dmi'
limb.should_draw_greyscale = TRUE
else
limb.icon = 'icons/mob/human_parts.dmi'
+
// Set this limb up using the species name and body zone
limb.icon_state = "[selected_category]_[limb.body_zone]"
limb.name = "\improper biosynthetic [selected_category] [parse_zone(limb.body_zone)]"
@@ -154,83 +237,40 @@
/obj/machinery/limbgrower/RefreshParts()
reagents.maximum_volume = 0
- for(var/obj/item/reagent_containers/glass/G in component_parts)
- reagents.maximum_volume += G.volume
- G.reagents.trans_to(src, G.reagents.total_volume)
- var/T=1.2
- for(var/obj/item/stock_parts/manipulator/M in component_parts)
- T -= M.rating*0.2
- prod_coeff = min(1,max(0,T)) // Coeff going 1 -> 0,8 -> 0,6 -> 0,4
+ for(var/obj/item/reagent_containers/glass/our_beaker in component_parts)
+ reagents.maximum_volume += our_beaker.volume
+ our_beaker.reagents.trans_to(src, our_beaker.reagents.total_volume)
+ production_coefficient = 1.25
+ for(var/obj/item/stock_parts/manipulator/our_manipulator in component_parts)
+ production_coefficient -= our_manipulator.rating * 0.25
+ production_coefficient = clamp(production_coefficient, 0, 1) // coefficient goes from 1 -> 0.75 -> 0.5 -> 0.25
/obj/machinery/limbgrower/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Storing up to [reagents.maximum_volume]u of synthflesh.
Synthflesh consumption at [prod_coeff*100]%."
+ . += "The status display reads: Storing up to [reagents.maximum_volume]u of reagents.
Reagent consumption rate at [production_coefficient * 100]%."
-/obj/machinery/limbgrower/proc/main_win(mob/user)
- var/dat = "
Limb Grower Menu:
"
- dat += "
Chemical Storage"
- dat += materials_printout()
- dat += "
"
-
- for(var/C in categories)
- dat += "| [C] | "
- dat += "
"
- //one category per line
-
- dat += "
"
- return dat
-
-/obj/machinery/limbgrower/proc/category_win(mob/user,selected_category)
- var/dat = "Return to main menu"
- dat += "Browsing [selected_category]:
"
- dat += materials_printout()
-
- for(var/v in stored_research.researched_designs)
- var/datum/design/D = SSresearch.techweb_design_by_id(v)
- if(!(selected_category in D.category))
- continue
- if(disabled || !can_build(D))
- dat += "
[D.name]"
- else
- dat += "
[D.name]"
- dat += "[get_design_cost(D)]
"
-
- dat += "
"
- return dat
-
-
-/obj/machinery/limbgrower/proc/chemical_win(mob/user)
- var/dat = "Return to main menu"
- dat += "Browsing Chemical Storage:
"
- dat += materials_printout()
-
- for(var/datum/reagent/R in reagents.reagent_list)
- dat += "[R.name]: [R.volume]"
- dat += "
Purge"
-
- dat += "
"
- return dat
-
-/obj/machinery/limbgrower/proc/materials_printout()
- var/dat = "Total amount:> [reagents.total_volume] / [reagents.maximum_volume] cm3
"
- return dat
-
-/obj/machinery/limbgrower/proc/can_build(datum/design/D)
- return (reagents.has_reagent(/datum/reagent/medicine/c2/synthflesh, D.reagents_list[/datum/reagent/medicine/c2/synthflesh]*prod_coeff)) //Return whether the machine has enough synthflesh to produce the design
-
-/obj/machinery/limbgrower/proc/get_design_cost(datum/design/D)
- var/dat
- if(D.reagents_list[/datum/reagent/medicine/c2/synthflesh])
- dat += "[D.reagents_list[/datum/reagent/medicine/c2/synthflesh] * prod_coeff] SynthFlesh"
- return dat
+/*
+ * Checks our reagent list to see if a design can be built.
+ *
+ * limb_design - the design we're checking for buildability.
+ *
+ * returns TRUE if we have enough reagent to build it. Returns FALSE if we do not.
+ */
+/obj/machinery/limbgrower/proc/can_build(datum/design/limb_design)
+ for(var/datum/reagent/reagent_id in limb_design.reagents_list)
+ if(!reagents.has_reagent(reagent_id, limb_design.reagents_list[reagent_id] * production_coefficient))
+ return FALSE
+ return TRUE
+/// Emagging a limbgrower allows you to build synthetic armblades.
/obj/machinery/limbgrower/emag_act(mob/user)
if(obj_flags & EMAGGED)
return
- for(var/id in SSresearch.techweb_designs)
- var/datum/design/D = SSresearch.techweb_design_by_id(id)
- if((D.build_type & LIMBGROWER) && ("emagged" in D.category))
- stored_research.add_design(D)
+ for(var/design_id in SSresearch.techweb_designs)
+ var/datum/design/found_design = SSresearch.techweb_design_by_id(design_id)
+ if((found_design.build_type & LIMBGROWER) && ("emagged" in found_design.category))
+ stored_research.add_design(found_design)
to_chat(user, "A warning flashes onto the screen, stating that safety overrides have been deactivated!")
obj_flags |= EMAGGED
+ update_static_data(user)
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm
index 813b8f63ebd..4e7c29ddaa7 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm
@@ -10,6 +10,7 @@
/obj/item/organ/tail/lizard = 1,
/datum/reagent/consumable/ethanol = 100
)
+ blacklist = list(/obj/item/organ/tail/lizard/fake)
result = /obj/item/reagent_containers/food/drinks/bottle/lizardwine
category = CAT_DRINK
diff --git a/code/modules/research/designs/limbgrower_designs.dm b/code/modules/research/designs/limbgrower_designs.dm
index 97b1474e227..c7f62dfbc7c 100644
--- a/code/modules/research/designs/limbgrower_designs.dm
+++ b/code/modules/research/designs/limbgrower_designs.dm
@@ -34,6 +34,22 @@
build_path = /obj/item/bodypart/r_leg
category = list("initial","human","lizard","moth","plasmaman","ethereal")
+/datum/design/digi_leftleg
+ name = "Digitigrade Left Leg"
+ id = "digi_leftleg"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 30)
+ build_path = /obj/item/bodypart/l_leg/digitigrade
+ category = list("lizard")
+
+/datum/design/digi_rightleg
+ name = "Digitigrade Right Leg"
+ id = "digi_rightleg"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 30)
+ build_path = /obj/item/bodypart/r_leg/digitigrade
+ category = list("lizard")
+
//Non-limb limb designs
/datum/design/heart
@@ -42,7 +58,7 @@
build_type = LIMBGROWER
reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 30)
build_path = /obj/item/organ/heart
- category = list("other","initial")
+ category = list("human","initial")
/datum/design/lungs
name = "Lungs"
@@ -50,7 +66,7 @@
build_type = LIMBGROWER
reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 20)
build_path = /obj/item/organ/lungs
- category = list("other","initial")
+ category = list("human","initial")
/datum/design/liver
name = "Liver"
@@ -58,7 +74,7 @@
build_type = LIMBGROWER
reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 20)
build_path = /obj/item/organ/liver
- category = list("other","initial")
+ category = list("human","initial")
/datum/design/stomach
name = "Stomach"
@@ -66,7 +82,7 @@
build_type = LIMBGROWER
reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 15)
build_path = /obj/item/organ/stomach
- category = list("other","initial")
+ category = list("human","initial")
/datum/design/appendix
name = "Appendix"
@@ -74,7 +90,7 @@
build_type = LIMBGROWER
reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 5) //why would you need this
build_path = /obj/item/organ/appendix
- category = list("other","initial")
+ category = list("human","initial")
/datum/design/eyes
name = "Eyes"
@@ -82,7 +98,7 @@
build_type = LIMBGROWER
reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 10)
build_path = /obj/item/organ/eyes
- category = list("other","initial")
+ category = list("human","initial")
/datum/design/ears
name = "Ears"
@@ -90,7 +106,7 @@
build_type = LIMBGROWER
reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 10)
build_path = /obj/item/organ/ears
- category = list("other","initial")
+ category = list("human","initial")
/datum/design/tongue
name = "Tongue"
@@ -98,8 +114,106 @@
build_type = LIMBGROWER
reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 10)
build_path = /obj/item/organ/tongue
+ category = list("human","initial")
+
+// Grows a fake lizard tail - not usable in lizard wine and other similar recipes.
+/datum/design/lizard_tail
+ name = "Lizard Tail"
+ id = "liztail"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 20)
+ build_path = /obj/item/organ/tail/lizard/fake
+ category = list("lizard")
+
+/datum/design/lizard_tongue
+ name = "Forked Tongue"
+ id = "liztongue"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 20)
+ build_path = /obj/item/organ/tongue/lizard
+ category = list("lizard")
+
+/datum/design/monkey_tail
+ name = "Monkey Tail"
+ id = "monkeytail"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 20)
+ build_path = /obj/item/organ/tail/monkey
category = list("other","initial")
+/datum/design/cat_tail
+ name = "Cat Tail"
+ id = "cattail"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 20)
+ build_path = /obj/item/organ/tail/cat
+ category = list("human")
+
+/datum/design/cat_ears
+ name = "Cat Ears"
+ id = "catears"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 10)
+ build_path = /obj/item/organ/ears/cat
+ category = list("human")
+
+/datum/design/plasmaman_lungs
+ name = "Plasma Filter"
+ id = "plasmamanlungs"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 10, /datum/reagent/toxin/plasma = 20)
+ build_path = /obj/item/organ/lungs/plasmaman
+ category = list("plasmaman")
+
+/datum/design/plasmaman_tongue
+ name = "Plasma Bone Tongue"
+ id = "plasmamantongue"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 10, /datum/reagent/toxin/plasma = 20)
+ build_path = /obj/item/organ/tongue/bone/plasmaman
+ category = list("plasmaman")
+
+/datum/design/plasmaman_liver
+ name = "Reagent Processing Crystal"
+ id = "plasmamanliver"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 10, /datum/reagent/toxin/plasma = 20)
+ build_path = /obj/item/organ/liver/plasmaman
+ category = list("plasmaman")
+
+/datum/design/plasmaman_stomach
+ name = "Digestive Crystal"
+ id = "plasmamanstomach"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 10, /datum/reagent/toxin/plasma = 20)
+ build_path = /obj/item/organ/stomach/bone/plasmaman
+ category = list("plasmaman")
+
+/datum/design/ethereal_stomach
+ name = "Biological Battery"
+ id = "etherealstomach"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 10, /datum/reagent/consumable/liquidelectricity = 20)
+ build_path = /obj/item/organ/stomach/ethereal
+ category = list("ethereal")
+
+/datum/design/ethereal_tongue
+ name = "Electrical Discharger"
+ id = "etherealtongue"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 10, /datum/reagent/consumable/liquidelectricity = 20)
+ build_path = /obj/item/organ/tongue/ethereal
+ category = list("ethereal")
+
+// Intentionally not growable by normal means - for balance conerns.
+/datum/design/ethereal_heart
+ name = "Crystal Core"
+ id = "etherealheart"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 10, /datum/reagent/consumable/liquidelectricity = 20)
+ build_path = /obj/item/organ/heart/ethereal
+ category = list("ethereal")
+
/datum/design/armblade
name = "Arm Blade"
id = "armblade"
@@ -107,3 +221,68 @@
reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 75)
build_path = /obj/item/melee/synthetic_arm_blade
category = list("other","emagged")
+
+/// Design disks and designs - for adding limbs and organs to the limbgrower.
+/obj/item/disk/design_disk/limbs
+ name = "Limb Design Disk"
+ desc = "A disk containing limb and organ designs for a limbgrower."
+ icon_state = "datadisk1"
+ /// List of all limb designs this disk contains.
+ var/list/limb_designs = list()
+
+/obj/item/disk/design_disk/limbs/Initialize()
+ . = ..()
+ max_blueprints = limb_designs.len
+ for(var/design in limb_designs)
+ var/datum/design/new_design = design
+ blueprints += new new_design
+
+/datum/design/limb_disk
+ name = "Limb Design Disk"
+ desc = "Contains designs for various limbs."
+ id = "limbdesign_parent"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 300, /datum/material/glass = 100)
+ build_path = /obj/item/disk/design_disk/limbs
+ category = list("Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+
+/obj/item/disk/design_disk/limbs/felinid
+ name = "Felinid Limb Design Disk"
+ limb_designs = list(/datum/design/cat_tail, /datum/design/cat_ears)
+
+/datum/design/limb_disk/felinid
+ name = "Felinid Limb Design Disk"
+ desc = "Contains designs for felinid bodyparts for the limbgrower - Felinid ears and tail."
+ id = "limbdesign_felinid"
+ build_path = /obj/item/disk/design_disk/limbs/felinid
+
+/obj/item/disk/design_disk/limbs/lizard
+ name = "Lizard Limb Design Disk"
+ limb_designs = list(/datum/design/lizard_tail, /datum/design/lizard_tongue, /datum/design/digi_leftleg, /datum/design/digi_rightleg)
+
+/datum/design/limb_disk/lizard
+ name = "Lizard Limb Design Disk"
+ desc = "Contains designs for lizard bodyparts for the limbgrower - Lizard tongue, tail, and digitigrade legs."
+ id = "limbdesign_lizard"
+ build_path = /obj/item/disk/design_disk/limbs/lizard
+
+/obj/item/disk/design_disk/limbs/plasmaman
+ name = "Plasmaman Limb Design Disk"
+ limb_designs = list(/datum/design/plasmaman_stomach, /datum/design/plasmaman_liver, /datum/design/plasmaman_lungs, /datum/design/plasmaman_tongue)
+
+/datum/design/limb_disk/plasmaman
+ name = "Plasmaman Limb Design Disk"
+ desc = "Contains designs for plasmaman organs for the limbgrower - Plasmaman tongue, liver, stomach, and lungs."
+ id = "limbdesign_plasmaman"
+ build_path = /obj/item/disk/design_disk/limbs/plasmaman
+
+/obj/item/disk/design_disk/limbs/ethereal
+ name = "Ethereal Limb Design Disk"
+ limb_designs = list(/datum/design/ethereal_stomach, /datum/design/ethereal_tongue)
+
+/datum/design/limb_disk/ethereal
+ name = "Ethereal Limb Design Disk"
+ desc = "Contains designs for ethereal organs for the limbgrower - Ethereal tongue and stomach."
+ id = "limbdesign_ethereal"
+ build_path = /obj/item/disk/design_disk/limbs/ethereal
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index 37168ebdca3..026af3cad8a 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -82,6 +82,15 @@
required_experiments = list(/datum/experiment/scanning/points/slime/easy)
discount_experiments = list(/datum/experiment/scanning/random/material/meat = 4000) //Big discount to reinforce doing it.
+/datum/techweb_node/xenoorgan_biotech
+ id = "xenoorgan_bio"
+ display_name = "Xeno-organ Biology"
+ description = "Plasmaman, Ethereals, Lizardpeople... What makes our non-human crewmembers tick?"
+ prereq_ids = list("adv_biotech")
+ design_ids = list("limbdesign_felinid", "limbdesign_lizard", "limbdesign_plasmaman", "limbdesign_ethereal")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 6500)
+ discount_experiments = list(/datum/experiment/scanning/random/cytology/easy = 1000, /datum/experiment/scanning/points/slime/expert = 5000)
+
/datum/techweb_node/bio_process
id = "bio_process"
display_name = "Biological Processing"
diff --git a/code/modules/surgery/organs/tails.dm b/code/modules/surgery/organs/tails.dm
index 2ca70316d7a..4e7673e74c6 100644
--- a/code/modules/surgery/organs/tails.dm
+++ b/code/modules/surgery/organs/tails.dm
@@ -81,6 +81,10 @@
new_tail.tail_type = tail_type
new_tail.spines = spines
+/obj/item/organ/tail/lizard/fake
+ name = "fabricated lizard tail"
+ desc = "A fabricated severed lizard tail. This one's made of synthflesh. Probably not usable for lizard wine."
+
/obj/item/organ/tail/monkey
name = "monkey tail"
desc = "A severed monkey tail. Does not look like a banana."
diff --git a/tgui/packages/tgui/interfaces/Limbgrower.js b/tgui/packages/tgui/interfaces/Limbgrower.js
new file mode 100644
index 00000000000..71ed3f2cd0d
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Limbgrower.js
@@ -0,0 +1,94 @@
+import { useBackend, useSharedState } from '../backend';
+import { Box, Button, Dimmer, Icon, LabeledList, Section, Tabs } from '../components';
+import { Window } from '../layouts';
+
+export const Limbgrower = (props, context) => {
+ const { act, data } = useBackend(context);
+ const {
+ reagents = [],
+ total_reagents,
+ max_reagents,
+ categories = [],
+ busy,
+ } = data;
+ const [tab, setTab] = useSharedState(
+ context, 'category', categories[0]?.name);
+ const designList = categories
+ .find(category => category.name === tab)
+ ?.designs;
+
+ return (
+
+ {!!busy && (
+
+
+ {' Building...'}
+
+ )}
+
+
+
+ {total_reagents} / {max_reagents} reagent capacity used.
+
+
+ {reagents.map(reagent => (
+ act('empty_reagent', {
+ reagent_type: reagent.reagent_type,
+ })} />
+ )}>
+ {reagent.reagent_amount}u
+
+ ))}
+
+
+
+
+ {categories.map(category => (
+ setTab(category.name)}>
+ {category.name}
+
+ ))}
+
+
+ {designList.map(design => (
+ act('make_limb', {
+ design_id: design.id,
+ active_tab: design.parent_category,
+ })} />
+ )}>
+ {design.needed_reagents.map(reagent => (
+
+ {reagent.name}: {reagent.amount}u
+
+ ))}
+
+ ))}
+
+
+
+
+ );
+};