From b46b586d7bedce2a971b0e2f30e6720204dbe699 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Fri, 2 Apr 2021 03:01:40 -0500 Subject: [PATCH] TGUI Limbgrower + Limbgrower refactoring and design expansion (#57955) Refactors the limbgrower to modernize the code. Now, the limbgrower can accept any type of reagent in limbgrower designs. Adds simple plumbing demand to the limbgrower, so you can pipe synthflesh into it. Adds monkey tails, felinid ears and tails, lizard digitigrade legs, lizard tongues, fake lizard tails (unusable in lizard-wine and similar recipes), plasmaman organs, and ethereal organs (minus the heart) to the limbgrower via limbgrower design disks. These disks can be printed from the medical lathe once the required technology has been researched. Adds a technology node to unlock the limbgrower design disks after advanced biotech, xenoorgan biology. In the future, this could have an experiment requirement - maybe to scan multiple types of species. Co-authored-by: Aleksej Komarov --- code/datums/components/crafting/recipes.dm | 2 + code/game/machinery/limbgrower.dm | 348 ++++++++++-------- .../recipes/tablecraft/recipes_drink.dm | 1 + .../research/designs/limbgrower_designs.dm | 193 +++++++++- code/modules/research/techweb/all_nodes.dm | 9 + code/modules/surgery/organs/tails.dm | 4 + tgui/packages/tgui/interfaces/Limbgrower.js | 94 +++++ 7 files changed, 490 insertions(+), 161 deletions(-) create mode 100644 tgui/packages/tgui/interfaces/Limbgrower.js 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 += "" - dat += "" - //one category per line - - dat += "
[C]
" - 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 + + ))} + + ))} + +
+
+
+ ); +};