diff --git a/code/modules/hydroponics/gene_modder.dm b/code/modules/hydroponics/gene_modder.dm
index 211dde094e5..8d155da7efd 100644
--- a/code/modules/hydroponics/gene_modder.dm
+++ b/code/modules/hydroponics/gene_modder.dm
@@ -15,13 +15,12 @@
var/list/trait_genes = list()
var/datum/plant_gene/target
- var/operation = ""
var/max_potency = 50 // See RefreshParts() for how these work
var/max_yield = 2
var/min_production = 12
var/max_endurance = 10 // IMPT: ALSO AFFECTS LIFESPAN
- var/min_wchance = 67
- var/min_wrate = 10
+ var/min_weed_chance = 67
+ var/min_weed_rate = 10
/obj/machinery/plantgenes/Initialize(mapload)
. = ..()
@@ -70,17 +69,18 @@
max_endurance = initial(max_endurance) + (SM.rating * 25) // 35,60,85,100 Clamps at 10min 100max
for(var/obj/item/stock_parts/micro_laser/ML in component_parts)
- var/wratemod = ML.rating * 2.5
- min_wrate = FLOOR(10-wratemod, 1) // 7,5,2,0 Clamps at 0 and 10 You want this low
- min_wchance = 67-(ML.rating*16) // 48,35,19,3 Clamps at 0 and 67 You want this low
+ var/weed_rate_mod = ML.rating * 2.5
+ min_weed_rate = FLOOR(10-weed_rate_mod, 1) // 7,5,2,0 Clamps at 0 and 10 You want this low
+ min_weed_chance = 67-(ML.rating*16) // 48,35,19,3 Clamps at 0 and 67 You want this low
+
for(var/obj/item/circuitboard/plantgenes/vaultcheck in component_parts)
if(istype(vaultcheck, /obj/item/circuitboard/plantgenes/vault)) // TRAIT_DUMB BOTANY TUTS
max_potency = 100
max_yield = 10
min_production = 1
max_endurance = 100
- min_wchance = 0
- min_wrate = 0
+ min_weed_chance = 0
+ min_weed_rate = 0
/obj/machinery/plantgenes/update_icon_state()
if((stat & (BROKEN|NOPOWER)))
@@ -107,310 +107,226 @@
return
if(istype(I, /obj/item/seeds))
- if(seed)
- to_chat(user, "A sample is already loaded into the machine! ")
- else
- if(!user.drop_item())
- return
- insert_seed(I)
- to_chat(user, "You add [I] to the machine. ")
- interact(user)
- return
+ add_seed(I, user)
else if(istype(I, /obj/item/disk/plantgene))
- if(disk)
- to_chat(user, "A data disk is already loaded into the machine! ")
- else
- if(!user.drop_item())
- return
- disk = I
- disk.forceMove(src)
- to_chat(user, "You add [I] to the machine. ")
- interact(user)
+ add_disk(I, user)
else
return ..()
+/obj/machinery/plantgenes/proc/add_seed(obj/item/seeds/new_seed, mob/user)
+ if(seed)
+ to_chat(user, "A sample is already loaded into the machine! ")
+ return
+ if(!user.drop_item())
+ return
+ insert_seed(new_seed)
+ to_chat(user, "You add [new_seed] to the machine. ")
+ ui_interact(user)
+
+/obj/machinery/plantgenes/proc/add_disk(obj/item/disk/plantgene/new_disk, mob/user)
+ if(disk)
+ to_chat(user, "A data disk is already loaded into the machine! ")
+ return
+ if(!user.drop_item())
+ return
+ disk = new_disk
+ disk.forceMove(src)
+ to_chat(user, "You add [new_disk] to the machine. ")
+ ui_interact(user)
/obj/machinery/plantgenes/attack_hand(mob/user)
if(..())
return
- interact(user)
+ ui_interact(user)
/obj/machinery/plantgenes/attack_ghost(mob/user)
- interact(user)
+ ui_interact(user)
-/obj/machinery/plantgenes/interact(mob/user)
- add_fingerprint(user)
- user.set_machine(src)
- if(!user)
- return
+/obj/machinery/plantgenes/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "GeneModder", name, 500, 700, master_ui, state)
+ ui.open()
- var/datum/browser/popup = new(user, "plantdna", "Plant DNA Manipulator", 450, 600)
+/obj/machinery/plantgenes/ui_data(mob/user)
+ var/list/data = list()
- var/dat = ""
+ data["has_seed"] = seed ? TRUE : FALSE
+ data["has_disk"] = disk ? TRUE : FALSE
- if(operation)
- if(!seed || (!target && operation != "insert"))
- operation = ""
- target = null
- interact(user)
- return
- if((operation == "replace" || operation == "insert") && (!disk || !disk.gene))
- operation = ""
- target = null
- interact(user)
- return
+ data["core_genes"] = list()
+ data["reagent_genes"] = list()
+ data["trait_genes"] = list()
- dat += "
Confirm Operation "
- dat += "Are you sure you want to [operation] "
- switch(operation)
- if("remove")
- dat += "[target.get_name()] gene from \the [seed] ? "
- if("extract")
- dat += "[target.get_name()] gene from \the [seed] ? "
- dat += "The sample will be destroyed in process! "
- if(istype(target, /datum/plant_gene/core))
- var/datum/plant_gene/core/gene = target
- if(istype(target, /datum/plant_gene/core/potency))
- if(gene.value > max_potency)
- dat += " This device's extraction capabilities are currently limited to [max_potency] potency. "
- dat += "Target gene will be degraded to [max_potency] potency on extraction."
- else if(istype(target, /datum/plant_gene/core/lifespan))
- if(gene.value > max_endurance)
- dat += " This device's extraction capabilities are currently limited to [max_endurance] lifespan. "
- dat += "Target gene will be degraded to [max_endurance] Lifespan on extraction."
- else if(istype(target, /datum/plant_gene/core/endurance))
- if(gene.value > max_endurance)
- dat += " This device's extraction capabilities are currently limited to [max_endurance] endurance. "
- dat += "Target gene will be degraded to [max_endurance] endurance on extraction."
- else if(istype(target, /datum/plant_gene/core/yield))
- if(gene.value > max_yield)
- dat += " This device's extraction capabilities are currently limited to [max_yield] yield. "
- dat += "Target gene will be degraded to [max_yield] yield on extraction."
- else if(istype(target, /datum/plant_gene/core/production))
- if(gene.value < min_production)
- dat += " This device's extraction capabilities are currently limited to [min_production] production. "
- dat += "Target gene will be degraded to [min_production] production on extraction."
- else if(istype(target, /datum/plant_gene/core/weed_rate))
- if(gene.value < min_wrate)
- dat += " This device's extraction capabilities are currently limited to [min_wrate] weed rate. "
- dat += "Target gene will be degraded to [min_wrate] weed rate on extraction."
- else if(istype(target, /datum/plant_gene/core/weed_chance))
- if(gene.value < min_wchance)
- dat += " This device's extraction capabilities are currently limited to [min_wchance] weed chance. "
- dat += "Target gene will be degraded to [min_wchance] weed chance on extraction."
- if("replace")
- dat += "[target.get_name()] gene with [disk.gene.get_name()] ? "
- if("insert")
- dat += "[disk.gene.get_name()] gene into \the [seed] ? "
- dat += "
"
- popup.set_content(dat)
- popup.open()
- return
+ data["has_reagent"] = FALSE
+ data["has_trait"] = FALSE
- dat+= ""
-
- dat += "
"
-
- dat += "
"
-
- dat += "
"
+ data["seed"] = list()
if(seed)
- var/can_insert = disk && disk.gene && disk.gene.can_add(seed)
- var/can_extract = disk && !disk.read_only
+ data["seed"] = list(
+ "image" = "[icon2base64(icon(initial(seed.icon), initial(seed.icon_state), SOUTH, 1))]",
+ "name" = seed.name,
+ "variant" = seed.variant
+ )
- dat += "
Core Genes "
- for(var/a in core_genes)
- var/datum/plant_gene/G = a
- if(!G)
- continue
- dat += "[G.get_name()] "
- if(can_extract)
- dat += "Extract "
- if(can_insert && istype(disk.gene, G.type))
- dat += "Replace "
- dat += " "
- dat += "
"
+ for(var/datum/plant_gene/core/c_gene in core_genes)
+ var/list/seed_info = list(
+ "name" = c_gene.get_name(),
+ "id" = c_gene.UID(),
+ "is_type" = disk && istype(disk.gene, c_gene)
+ )
+ data["core_genes"] += list(seed_info)
+ // there will always be core genes, if there isnt, something has gone very wrong
- if(seed.yield != -1)
- dat += "
Content Genes "
- if(reagent_genes.len)
- dat += "
"
- for(var/a in reagent_genes)
- var/datum/plant_gene/G = a
- dat += "[G.get_name()] "
- if(can_extract)
- dat += "Extract "
- dat += "Remove "
- dat += " "
- dat += "
"
- else
- dat += "No content-related genes detected in sample.
"
- dat += "
"
- if(can_insert && istype(disk.gene, /datum/plant_gene/reagent))
- dat += "Insert: [disk.gene.get_name()] "
+ for(var/datum/plant_gene/reagent/r_gene in reagent_genes)
+ var/list/seed_info = list(
+ "name" = r_gene.get_name(),
+ "id" = r_gene.UID()
+ )
+ data["reagent_genes"] += list(seed_info)
+ data["has_reagent"] = TRUE
- dat += "
Trait Genes "
- if(trait_genes.len)
- dat += "
"
- for(var/a in trait_genes)
- var/datum/plant_gene/G = a
- dat += "[G.get_name()] "
- if(can_extract)
- dat += "Extract "
- dat += "Remove "
- dat += " "
- dat += "
"
- else
- dat += "No trait-related genes detected in sample.
"
- if(can_insert && istype(disk.gene, /datum/plant_gene/trait))
- dat += "
Insert: [disk.gene.get_name()] "
- dat += "
"
+ for(var/datum/plant_gene/trait/t_gene in trait_genes)
+ var/list/seed_info = list(
+ "name" = t_gene.get_name(),
+ "id" = t_gene.UID()
+ )
+ data["trait_genes"] += list(seed_info)
+ data["has_trait"] = TRUE
- dat += "
Variant "
- dat += "[seed.variant ? seed.variant : "None"] "
- dat += "Edit "
- if(seed.variant)
- dat += "Remove "
- dat += "
"
- else
- dat += " No sample found.Please, insert a plant sample to use this device. "
- popup.set_content(dat)
- popup.open()
+ data["disk"] = list()
+ if(disk)
+ var/disk_name = "Empty Disk"
+ if(disk.gene)
+ disk_name = disk.gene.get_name()
+ if(disk.read_only)
+ disk_name = "[disk_name] (Read Only)"
+ data["disk"] = list(
+ "name" = disk_name,
+ "can_insert" = disk.gene?.can_add(seed),
+ "can_extract" = !disk.read_only,
+ "is_core" = istype(disk?.gene, /datum/plant_gene/core)
+ )
-/obj/machinery/plantgenes/Topic(href, list/href_list)
+ data["modal"] = ui_modal_data(src)
+
+ return data
+
+/obj/machinery/plantgenes/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
if(..())
- return 1
- usr.set_machine(src)
+ return
- if(href_list["eject_seed"] && !operation)
- if(seed)
- seed.forceMove(loc)
- seed.verb_pickup()
- seed = null
- update_genes()
- update_icon(UPDATE_OVERLAYS)
- else
- var/obj/item/I = usr.get_active_hand()
- if(istype(I, /obj/item/seeds))
- if(!usr.drop_item())
- return
- insert_seed(I)
- to_chat(usr, "You add [I] to the machine. ")
- update_icon(UPDATE_OVERLAYS)
- else if(href_list["eject_disk"] && !operation)
- if(disk)
- disk.forceMove(loc)
- disk.verb_pickup()
- disk = null
- update_genes()
- else
- var/obj/item/I = usr.get_active_hand()
- if(istype(I, /obj/item/disk/plantgene))
- if(!usr.drop_item())
- return
- disk = I
- disk.forceMove(src)
- to_chat(usr, "You add [I] to the machine. ")
- else if(href_list["op"] == "insert" && disk && disk.gene && seed)
- if(!operation) // Wait for confirmation
- operation = "insert"
- else
+ . = TRUE
+
+ // we dont care what we get from modal act, as long as its not null because we only have boolean modals
+ if(ui_modal_act(src, action, params))
+ return
+
+ var/mob/user = ui.user
+
+ target = seed?.get_gene(params["id"])
+
+ switch(action)
+ if("eject_seed")
+ if(seed)
+ seed.forceMove(loc)
+ user.put_in_hands(seed)
+ seed = null
+ update_genes()
+ update_icon(UPDATE_OVERLAYS)
+ else
+ var/obj/item/I = user.get_active_hand()
+ if(istype(I, /obj/item/seeds))
+ add_seed(I, user)
+
+ if("eject_disk")
+ if(disk)
+ disk.forceMove(loc)
+ user.put_in_hands(disk)
+ disk = null
+ update_genes()
+ else
+ var/obj/item/I = user.get_active_hand()
+ if(istype(I, /obj/item/disk/plantgene))
+ add_disk(I, user)
+
+ if("variant_name")
+ seed.variant_prompt(user, src)
+ // uses the default byond prompt, but it works
+
+ if("extract")
+ var/dat = "Are you sure you want to extract [target.get_name()] gene from the [seed]? The sample will be destroyed in process!"
+ if(istype(target, /datum/plant_gene/core))
+ var/datum/plant_gene/core/core_gene = target
+ var/genemod_var = core_gene.get_genemod_variable(src) // polymorphism my beloved
+
+ if((core_gene.use_max && core_gene.value < genemod_var) || (!core_gene.use_max && core_gene.value > genemod_var))
+ var/gene_name = lowertext(core_gene.name)
+ dat += " This device's extraction capabilities are currently limited to [genemod_var] [gene_name]. \
+ Target gene will be degraded to [genemod_var] [gene_name] on extraction."
+
+ ui_modal_boolean(src, action, dat, yes_text = "Extract", no_text = "Cancel", delegate = PROC_REF(gene_extract))
+
+ if("replace")
+ ui_modal_boolean(src, action, "Are you sure you want to replace [target.get_name()] gene with [disk.gene.get_name()]?", yes_text = "Replace", no_text = "Cancel", delegate = PROC_REF(gene_replace))
+
+ if("remove")
+ ui_modal_boolean(src, action, "Are you sure you want to remove [target.get_name()] gene from the [seed]" , yes_text = "Remove", no_text = "Cancel", delegate = PROC_REF(gene_remove))
+
+ if("insert")
if(!istype(disk.gene, /datum/plant_gene/core) && disk.gene.can_add(seed))
seed.genes += disk.gene.Copy()
if(istype(disk.gene, /datum/plant_gene/reagent))
seed.reagents_from_genes()
- update_genes()
- repaint_seed()
- operation = ""
- target = null
+ update_genes()
+ repaint_seed()
+ // this doesnt need a modal, its easy enough to just remove the inserted gene
- else if(href_list["gene"] && seed)
- var/datum/plant_gene/G = seed.get_gene(href_list["gene"])
- if(!G || !href_list["op"] || !(href_list["op"] in list("remove", "extract", "replace")))
- interact(usr)
- return
- if(!operation || target != G) // Wait for confirmation
- target = G
- operation = href_list["op"]
+/obj/machinery/plantgenes/proc/gene_remove()
+ if(istype(target, /datum/plant_gene/core))
+ return
+ seed.genes -= target
+ if(istype(target, /datum/plant_gene/reagent))
+ seed.reagents_from_genes()
+ repaint_seed()
+ update_genes()
+ target = null
- else if(operation == href_list["op"] && target == G)
- switch(href_list["op"])
- if("remove")
- if(!istype(G, /datum/plant_gene/core))
- seed.genes -= G
- if(istype(G, /datum/plant_gene/reagent))
- seed.reagents_from_genes()
- repaint_seed()
- if("extract")
- if(disk && !disk.read_only)
- disk.gene = G.Copy()
- if(istype(disk.gene, /datum/plant_gene/core))
- var/datum/plant_gene/core/gene = disk.gene
- if(istype(disk.gene, /datum/plant_gene/core/potency))
- gene.value = min(gene.value, max_potency)
- else if(istype(disk.gene, /datum/plant_gene/core/lifespan))
- gene.value = min(gene.value, max_endurance) //INTENDED
- else if(istype(disk.gene, /datum/plant_gene/core/endurance))
- gene.value = min(gene.value, max_endurance)
- else if(istype(disk.gene, /datum/plant_gene/core/production))
- gene.value = max(gene.value, min_production)
- else if(istype(disk.gene, /datum/plant_gene/core/yield))
- gene.value = min(gene.value, max_yield)
- else if(istype(disk.gene, /datum/plant_gene/core/weed_rate))
- gene.value = max(gene.value, min_wrate)
- else if(istype(disk.gene, /datum/plant_gene/core/weed_chance))
- gene.value = max(gene.value, min_wchance)
- disk.update_name()
- QDEL_NULL(seed)
- update_icon(UPDATE_OVERLAYS)
- if("replace")
- if(disk && disk.gene && istype(disk.gene, G.type) && istype(G, /datum/plant_gene/core))
- seed.genes -= G
- var/datum/plant_gene/core/C = disk.gene.Copy()
- seed.genes += C
- C.apply_stat(seed)
- repaint_seed()
- if("insert")
- if(disk && disk.gene && !istype(disk.gene, /datum/plant_gene/core) && disk.gene.can_add(seed))
- seed.genes += disk.gene.Copy()
- if(istype(disk.gene, /datum/plant_gene/reagent))
- seed.reagents_from_genes()
- disk.gene.apply_vars(seed)
- repaint_seed()
+/obj/machinery/plantgenes/proc/gene_extract()
+ if(!disk || disk.read_only)
+ return
+ disk.gene = target.Copy()
+ if(istype(disk.gene, /datum/plant_gene/core))
+ var/datum/plant_gene/core/core_gene = disk.gene
+ var/genemod_var = core_gene.get_genemod_variable(src)
+ if(core_gene.use_max)
+ core_gene.value = max(core_gene.value, genemod_var)
+ else
+ core_gene.value = min(core_gene.value, genemod_var)
- update_genes()
- operation = ""
- target = null
- else if(href_list["abort"])
- operation = ""
- target = null
- else if(href_list["set_v"])
- if(!seed)
- return
- seed.variant_prompt(usr, src)
- else if(href_list["del_v"])
- if(!seed)
- return
- seed.variant = null
- seed.apply_variant_name()
- to_chat(usr, "You remove the [seed.plantname]'s variant designation. ")
+ disk.update_name()
+ QDEL_NULL(seed)
+ update_icon(UPDATE_OVERLAYS)
+ update_genes()
+ target = null
- interact(usr)
+/obj/machinery/plantgenes/proc/gene_replace()
+ if(!disk?.gene)
+ return
+ if(!istype(target, /datum/plant_gene/core))
+ return
+ if(!istype(disk.gene, target.type))
+ return // you can't replace a endurance gene with a weed chance gene, etc
+ seed.genes -= target
+ var/datum/plant_gene/core/C = disk.gene.Copy()
+ seed.genes += C
+ C.apply_stat(seed)
+ repaint_seed()
+ update_genes()
+ target = null
/obj/machinery/plantgenes/proc/insert_seed(obj/item/seeds/S)
if(!istype(S) || seed)
diff --git a/code/modules/hydroponics/plant_genes.dm b/code/modules/hydroponics/plant_genes.dm
index 7b76eca1253..7e07f3f37a0 100644
--- a/code/modules/hydroponics/plant_genes.dm
+++ b/code/modules/hydroponics/plant_genes.dm
@@ -19,6 +19,7 @@
// Core plant genes store 5 main variables: lifespan, endurance, production, yield, potency
/datum/plant_gene/core
var/value
+ var/use_max = FALSE
/datum/plant_gene/core/get_name()
return "[name] [value]"
@@ -41,6 +42,12 @@
return FALSE
return S.get_gene(type)
+/datum/plant_gene/core/proc/get_genemod_variable(obj/machinery/plantgenes/modder)
+ if(!modder)
+ stack_trace("A plant gene ([get_name()]) tried to get a genemod variable without being in a genemodder.")
+ return
+ stack_trace("A plant gene ([get_name()]) tried to get a genemod variable, but had no override.")
+
/datum/plant_gene/core/lifespan
name = "Lifespan"
value = 25
@@ -48,6 +55,11 @@
/datum/plant_gene/core/lifespan/apply_stat(obj/item/seeds/S)
S.lifespan = value
+/datum/plant_gene/core/lifespan/get_genemod_variable(obj/machinery/plantgenes/modder)
+ if(!modder) // Let the parent handle it
+ return ..()
+ return modder.max_endurance // Yes, this is intended. It is used for both lifespan and endurance
+
/datum/plant_gene/core/endurance
name = "Endurance"
@@ -56,14 +68,24 @@
/datum/plant_gene/core/endurance/apply_stat(obj/item/seeds/S)
S.endurance = value
+/datum/plant_gene/core/endurance/get_genemod_variable(obj/machinery/plantgenes/modder)
+ if(!modder) // Let the parent handle it
+ return ..()
+ return modder.max_endurance
+
/datum/plant_gene/core/production
name = "Production Speed"
value = 6
+ use_max = TRUE
/datum/plant_gene/core/production/apply_stat(obj/item/seeds/S)
S.production = value
+/datum/plant_gene/core/production/get_genemod_variable(obj/machinery/plantgenes/modder)
+ if(!modder) // Let the parent handle it
+ return ..()
+ return modder.min_production
/datum/plant_gene/core/yield
name = "Yield"
@@ -72,6 +94,11 @@
/datum/plant_gene/core/yield/apply_stat(obj/item/seeds/S)
S.yield = value
+/datum/plant_gene/core/yield/get_genemod_variable(obj/machinery/plantgenes/modder)
+ if(!modder) // Let the parent handle it
+ return ..()
+ return modder.max_yield
+
/datum/plant_gene/core/potency
name = "Potency"
@@ -80,22 +107,39 @@
/datum/plant_gene/core/potency/apply_stat(obj/item/seeds/S)
S.potency = value
+/datum/plant_gene/core/potency/get_genemod_variable(obj/machinery/plantgenes/modder)
+ if(!modder) // Let the parent handle it
+ return ..()
+ return modder.max_potency
+
/datum/plant_gene/core/weed_rate
name = "Weed Growth Rate"
value = 1
+ use_max = TRUE
/datum/plant_gene/core/weed_rate/apply_stat(obj/item/seeds/S)
S.weed_rate = value
+/datum/plant_gene/core/weed_rate/get_genemod_variable(obj/machinery/plantgenes/modder)
+ if(!modder) // Let the parent handle it
+ return ..()
+ return modder.min_weed_rate
+
/datum/plant_gene/core/weed_chance
name = "Weed Vulnerability"
value = 5
+ use_max = TRUE
/datum/plant_gene/core/weed_chance/apply_stat(obj/item/seeds/S)
S.weed_chance = value
+/datum/plant_gene/core/weed_chance/get_genemod_variable(obj/machinery/plantgenes/modder)
+ if(!modder) // Let the parent handle it
+ return ..()
+ return modder.min_weed_chance
+
// Reagent genes store reagent ID and reagent ratio. Amount of reagent in the plant = 1 + (potency * rate)
/datum/plant_gene/reagent
diff --git a/tgui/packages/tgui/interfaces/GeneModder.js b/tgui/packages/tgui/interfaces/GeneModder.js
new file mode 100644
index 00000000000..cdc1b5e23c8
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/GeneModder.js
@@ -0,0 +1,261 @@
+import { useBackend } from "../backend";
+import {
+ Button,
+ Section,
+ Flex,
+ Icon,
+ Collapsible,
+ LabeledList } from "../components";
+import { ComplexModal } from '../interfaces/common/ComplexModal';
+import { Window } from "../layouts";
+
+export const GeneModder = (props, context) => {
+ const { data } = useBackend(context);
+ const { has_seed } = data;
+
+ return (
+
+
+
+
+ {has_seed === 0 ? (
+
+ ) : (
+
+ )}
+
+
+ );
+};
+
+const Genes = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { disk } = data;
+
+ return (
+ act('insert')}
+ />
+ }>
+
+
+
+
+ );
+};
+
+const MissingSeed = (props, context) => {
+ return (
+
+
+
+
+ The plant DNA manipulator is missing a seed.
+
+
+
+ );
+};
+
+const Storage = (props, context) => {
+ const { act, data } = useBackend(context);
+ const {
+ has_seed,
+ seed,
+ has_disk,
+ disk
+ } = data;
+
+ let show_seed;
+ let show_disk;
+
+ if(has_seed) {
+ show_seed =
+
+
+ act('eject_seed')}
+ />
+ act('variant_name')}
+ />
+
+ } else {
+ show_seed =
+
+ act('eject_seed')}
+ />
+
+ }
+
+ if (has_disk) {
+ show_disk = disk.name
+ } else {
+ show_disk = "None"
+ }
+
+ return (
+
+
+
+ {show_seed}
+
+
+
+ act('eject_disk')}
+ />
+
+
+
+
+ );
+};
+
+const CoreGenes = (props, context) => {
+ const { act, data } = useBackend(context);
+ const {
+ disk,
+ core_genes
+ } = data;
+
+ return (
+
+ {core_genes.map((gene) => (
+
+
+ {gene.name}
+
+
+ act('extract', {id: gene.id})}
+ />
+
+
+ act('replace', {id: gene.id})}
+ />
+
+
+ ))}
+
+ );
+};
+
+const ReagentGenes = (props, context) => {
+ const { data } = useBackend(context);
+ const { reagent_genes, has_reagent } = data;
+
+ return (
+
+ );
+};
+
+const TraitGenes = (props, context) => {
+ const { data } = useBackend(context);
+ const { trait_genes, has_trait } = data;
+
+ return (
+
+ );
+};
+
+
+const OtherGenes = (props, context) => {
+ const {
+ title,
+ gene_set,
+ do_we_show
+ } = props;
+ const { act, data } = useBackend(context);
+ const { disk } = data;
+
+ return (
+
+ {do_we_show ? (
+ gene_set.map((gene) => (
+
+
+ {gene.name}
+
+
+ act('extract', {id: gene.id})}
+ />
+
+
+ act('remove', {id: gene.id})}
+ />
+
+
+ ))
+ ) : (
+
+ No Genes Detected
+
+ )}
+
+ );
+};
diff --git a/tgui/packages/tgui/public/tgui.bundle.js b/tgui/packages/tgui/public/tgui.bundle.js
index fb19d31dc9a..70757e726bc 100644
--- a/tgui/packages/tgui/public/tgui.bundle.js
+++ b/tgui/packages/tgui/public/tgui.bundle.js
@@ -1,5 +1,5 @@
-!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=230)}([function(e,t,n){"use strict";t.__esModule=!0;var o=n(232);Object.keys(o).forEach((function(e){"default"!==e&&"__esModule"!==e&&(e in t&&t[e]===o[e]||(t[e]=o[e]))}))},function(e,t,n){"use strict";t.__esModule=!0,t.useSharedState=t.useLocalState=t.useBackend=t.deleteLocalState=t.backendUpdate=t.backendSetSharedState=t.backendReducer=t.backendDeleteSharedState=void 0;var o=n(31),r=n(43);t.backendUpdate=function(e){return{type:"backend/update",payload:e}};var a=function(e,t){return{type:"backend/setSharedState",payload:{key:e,nextState:t}}};t.backendSetSharedState=a;var c=function(e){return{type:"backend/deleteSharedState",payload:e}};t.backendDeleteSharedState=c;t.backendReducer=function(e,t){var n=t.type,o=t.payload;if("backend/update"===n){var a=Object.assign({},e.config,o.config),c=Object.assign({},e.data,o.static_data,o.data),i=Object.assign({},e.shared);if(o.shared)for(var l=0,d=Object.keys(o.shared);l1?n-1:0),r=1;rn?n:e};t.clamp01=function(e){return e<0?0:e>1?1:e};t.scale=function(e,t,n){return(e-t)/(n-t)};t.round=function(e,t){return!e||isNaN(e)?e:(t|=0,a=(e*=n=Math.pow(10,t))>0|-(e<0),r=Math.abs(e%1)>=.4999999999854481,o=Math.floor(e),r&&(e=o+(a>0)),(r?e:Math.round(e))/n);var n,o,r,a};t.toFixed=function(e,t){return void 0===t&&(t=0),Number(e).toFixed(Math.max(t,0))};var o=function(e,t){return t&&e>=t[0]&&e<=t[1]};t.inRange=o;t.keyOfMatchingRange=function(e,t){for(var n=0,r=Object.keys(t);n0&&(t.style=l),t};t.computeBoxProps=N;var b=function(e){var t=e.textColor||e.color,n=e.backgroundColor;return(0,o.classes)([u(t)&&"color-"+t,u(n)&&"color-bg-"+n])};t.computeBoxClassName=b;var V=function(e){var t=e.as,n=void 0===t?"div":t,o=e.className,c=e.children,l=function(e,t){if(null==e)return{};var n,o,r={},a=Object.keys(e);for(o=0;o=0||(r[n]=e[n]);return r}(e,i);if("function"==typeof c)return c(N(e));var d="string"==typeof o?o+" "+b(l):b(l),u=N(l);return(0,r.createVNode)(a.VNodeFlags.HtmlElement,n,d,c,a.ChildFlags.UnknownChildren,u)};t.Box=V,V.defaultHooks=o.pureComponentHooks},function(e,t,n){"use strict";function o(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return r(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return r(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var o=0;return function(){return o>=e.length?{done:!0}:{done:!1,value:e[o++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function r(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=new Array(t);n",apos:"'"};return e.replace(/ /gi,"\n").replace(/<\/?[a-z0-9-_]+[^>]*>/gi,"").replace(/&(nbsp|amp|quot|lt|gt|apos);/g,(function(e,n){return t[n]})).replace(/?([0-9]+);/gi,(function(e,t){var n=parseInt(t,10);return String.fromCharCode(n)})).replace(/?([0-9a-f]+);/gi,(function(e,t){var n=parseInt(t,16);return String.fromCharCode(n)}))};t.buildQueryString=function(e){return Object.keys(e).map((function(t){return encodeURIComponent(t)+"="+encodeURIComponent(e[t])})).join("&")}},function(e,t,n){"use strict";var o=n(40);e.exports=function(e){return o(e.length)}},function(e,t,n){"use strict";var o=n(5),r=n(12),a=n(49),c=n(129),i=n(127);e.exports=function(e,t,n,l){var d=!!l&&!!l.unsafe,u=!!l&&!!l.enumerable,s=!!l&&!!l.noTargetGet,m=l&&l.name!==undefined?l.name:t;return r(n)&&c(n,m,l),e===o?(u?e[t]=n:i(t,n),e):(d?!s&&e[t]&&(u=!0):delete e[t],u?e[t]=n:a(e,t,n),e)}},function(e,t,n){"use strict";var o=n(60),r=n(7),a=n(85),c=n(19),i=n(23),l=n(91),d=r([].push),u=function(e){var t=1==e,n=2==e,r=3==e,u=4==e,s=6==e,m=7==e,p=5==e||s;return function(h,f,C,N){for(var b,V,g=c(h),v=a(g),y=o(f,C),_=i(v),x=0,k=N||l,L=t?k(h,_):n||m?k(h,0):undefined;_>x;x++)if((p||x in v)&&(V=y(b=v[x],x,g),e))if(t)L[x]=V;else if(V)switch(e){case 3:return!0;case 5:return b;case 6:return x;case 2:d(L,b)}else switch(e){case 4:return!1;case 7:d(L,b)}return s?-1:r||u?u:L}};e.exports={forEach:u(0),map:u(1),filter:u(2),some:u(3),every:u(4),find:u(5),findIndex:u(6),filterReject:u(7)}},function(e,t,n){"use strict";var o=n(8),r=n(14),a=n(100),c=n(64),i=n(29),l=n(56),d=n(18),u=n(172),s=Object.getOwnPropertyDescriptor;t.f=o?s:function(e,t){if(e=i(e),t=l(t),u)try{return s(e,t)}catch(n){}if(d(e,t))return c(!r(a.f,e,t),e[t])}},function(e,t,n){"use strict";var o=n(5),r=n(12),a=n(70),c=o.TypeError;e.exports=function(e){if(r(e))return e;throw c(a(e)+" is not a function")}},function(e,t,n){"use strict";var o=n(5),r=n(12),a=function(e){return r(e)?e:undefined};e.exports=function(e,t){return arguments.length<2?a(o[e]):o[e]&&o[e][t]}},function(e,t,n){"use strict";var o=n(85),r=n(30);e.exports=function(e){return o(r(e))}},function(e,t,n){"use strict";var o=n(5).TypeError;e.exports=function(e){if(e==undefined)throw o("Can't call method on "+e);return e}},function(e,t,n){"use strict";function o(e,t,n,o,r,a,c){try{var i=e[a](c),l=i.value}catch(d){return void n(d)}i.done?t(l):Promise.resolve(l).then(o,r)}t.__esModule=!0,t.winset=t.winget=t.runCommand=t.callByondAsync=t.callByond=t.IS_IE8=void 0;var r=window.Byond,a=function(){var e=navigator.userAgent.match(/Trident\/(\d+).+?;/i);if(!e)return null;var t=e[1];return t?parseInt(t,10):null}(),c=null!==a&&a<=6;t.IS_IE8=c;var i=function(e,t){void 0===t&&(t={}),r.call(e,t)};t.callByond=i;var l=function(e,t){void 0===t&&(t={}),window.__callbacks__=window.__callbacks__||[];var n=window.__callbacks__.length,o=new Promise((function(e){window.__callbacks__.push(e)}));return r.call(e,Object.assign({},t,{callback:"__callbacks__["+n+"]"})),o};t.callByondAsync=l;t.runCommand=function(e){return i("winset",{command:e})};var d=function(){var e,t=(e=regeneratorRuntime.mark((function n(e,t){var o;return regeneratorRuntime.wrap((function(n){for(;;)switch(n.prev=n.next){case 0:return n.next=2,l("winget",{id:e,property:t});case 2:return o=n.sent,n.abrupt("return",o[t]);case 4:case"end":return n.stop()}}),n)})),function(){var t=this,n=arguments;return new Promise((function(r,a){var c=e.apply(t,n);function i(e){o(c,r,a,i,l,"next",e)}function l(e){o(c,r,a,i,l,"throw",e)}i(undefined)}))});return function(e,n){return t.apply(this,arguments)}}();t.winget=d;t.winset=function(e,t,n){var o;return i("winset",((o={})[e+"."+t]=n,o))}},function(e,t,n){"use strict";t.__esModule=!0,t.zipWith=t.zip=t.uniqBy=t.toKeyedArray=t.toArray=t.sortBy=t.reduce=t.map=t.filter=void 0;t.toArray=function(e){if(Array.isArray(e))return e;if("object"==typeof e){var t=Object.prototype.hasOwnProperty,n=[];for(var o in e)t.call(e,o)&&n.push(e[o]);return n}return[]};t.toKeyedArray=function(e,t){return void 0===t&&(t="key"),o((function(e,n){var o;return Object.assign(((o={})[t]=n,o),e)}))(e)};t.filter=function(e){return function(t){if(null===t&&t===undefined)return t;if(Array.isArray(t)){for(var n=[],o=0;oi)return 1}return 0};t.sortBy=function(){for(var e=arguments.length,t=new Array(e),n=0;n"+l+""+t+">"}},function(e,t,n){"use strict";var o=n(6);e.exports=function(e){return o((function(){var t=""[e]('"');return t!==t.toLowerCase()||t.split('"').length>3}))}},function(e,t,n){"use strict";var o,r,a,c=n(174),i=n(5),l=n(7),d=n(11),u=n(49),s=n(18),m=n(126),p=n(102),h=n(87),f=i.TypeError,C=i.WeakMap;if(c||m.state){var N=m.state||(m.state=new C),b=l(N.get),V=l(N.has),g=l(N.set);o=function(e,t){if(V(N,e))throw new f("Object already initialized");return t.facade=e,g(N,e,t),t},r=function(e){return b(N,e)||{}},a=function(e){return V(N,e)}}else{var v=p("state");h[v]=!0,o=function(e,t){if(s(e,v))throw new f("Object already initialized");return t.facade=e,u(e,v,t),t},r=function(e){return s(e,v)?e[v]:{}},a=function(e){return s(e,v)}}e.exports={set:o,get:r,has:a,enforce:function(e){return a(e)?r(e):o(e,{})},getterFor:function(e){return function(t){var n;if(!d(t)||(n=r(t)).type!==e)throw f("Incompatible receiver, "+e+" required");return n}}}},function(e,t,n){"use strict";var o=Math.ceil,r=Math.floor;e.exports=function(e){var t=+e;return t!=t||0===t?0:(t>0?r:o)(t)}},function(e,t,n){"use strict";var o=n(39),r=Math.min;e.exports=function(e){return e>0?r(o(e),9007199254740991):0}},function(e,t,n){"use strict";t.__esModule=!0,t.computeFlexProps=t.computeFlexItemProps=t.FlexItem=t.Flex=void 0;var o=n(0),r=n(10),a=n(31),c=n(21),i=["className","direction","wrap","align","alignContent","justify","inline","spacing","spacingPrecise"],l=["className","grow","order","shrink","basis","align"];function d(e,t){if(null==e)return{};var n,o,r={},a=Object.keys(e);for(o=0;o=0||(r[n]=e[n]);return r}var u=function(e){var t=e.className,n=e.direction,o=e.wrap,c=e.align,l=e.alignContent,u=e.justify,s=e.inline,m=e.spacing,p=void 0===m?0:m,h=e.spacingPrecise,f=void 0===h?0:h,C=d(e,i);return Object.assign({className:(0,r.classes)(["Flex",a.IS_IE8&&("column"===n?"Flex--ie8--column":"Flex--ie8"),s&&"Flex--inline",p>0&&"Flex--spacing--"+p,f>0&&"Flex--spacingPrecise--"+f,t]),style:Object.assign({},C.style,{"flex-direction":n,"flex-wrap":o,"align-items":c,"align-content":l,"justify-content":u})},C)};t.computeFlexProps=u;var s=function(e){return(0,o.normalizeProps)((0,o.createComponentVNode)(2,c.Box,Object.assign({},u(e))))};t.Flex=s,s.defaultHooks=r.pureComponentHooks;var m=function(e){var t=e.className,n=e.grow,o=e.order,i=e.shrink,u=e.basis,s=void 0===u?e.width:u,m=e.align,p=d(e,l);return Object.assign({className:(0,r.classes)(["Flex__item",a.IS_IE8&&"Flex__item--ie8",t]),style:Object.assign({},p.style,{"flex-grow":n,"flex-shrink":i,"flex-basis":(0,c.unit)(s),order:o,"align-self":m})},p)};t.computeFlexItemProps=m;var p=function(e){return(0,o.normalizeProps)((0,o.createComponentVNode)(2,c.Box,Object.assign({},m(e))))};t.FlexItem=p,p.defaultHooks=r.pureComponentHooks,s.Item=p},function(e,t,n){"use strict";var o=n(5),r=n(18),a=n(12),c=n(19),i=n(102),l=n(139),d=i("IE_PROTO"),u=o.Object,s=u.prototype;e.exports=l?u.getPrototypeOf:function(e){var t=c(e);if(r(t,d))return t[d];var n=t.constructor;return a(n)&&t instanceof n?n.prototype:t instanceof u?s:null}},function(e,t,n){"use strict";t.__esModule=!0,t.timeAgo=t.getGasLabel=t.getGasColor=t.UI_UPDATE=t.UI_INTERACTIVE=t.UI_DISABLED=t.UI_CLOSE=t.RADIO_CHANNELS=t.CSS_COLORS=t.COLORS=void 0;t.UI_INTERACTIVE=2;t.UI_UPDATE=1;t.UI_DISABLED=0;t.UI_CLOSE=-1;t.COLORS={department:{command:"#526aff",security:"#CF0000",medical:"#009190",science:"#993399",engineering:"#A66300",supply:"#9F8545",service:"#80A000",centcom:"#78789B",other:"#C38312"},damageType:{oxy:"#3498db",toxin:"#2ecc71",burn:"#e67e22",brute:"#e74c3c"}};t.CSS_COLORS=["black","white","red","orange","yellow","olive","green","teal","blue","violet","purple","pink","brown","grey","good","average","bad","label"];t.RADIO_CHANNELS=[{name:"Syndicate",freq:1213,color:"#a52a2a"},{name:"SyndTeam",freq:1244,color:"#a52a2a"},{name:"Red Team",freq:1215,color:"#ff4444"},{name:"Blue Team",freq:1217,color:"#3434fd"},{name:"Response Team",freq:1345,color:"#2681a5"},{name:"Special Ops",freq:1341,color:"#2681a5"},{name:"Supply",freq:1347,color:"#b88646"},{name:"Service",freq:1349,color:"#6ca729"},{name:"Science",freq:1351,color:"#c68cfa"},{name:"Command",freq:1353,color:"#5177ff"},{name:"Procedure",freq:1339,color:"#F70285"},{name:"Medical",freq:1355,color:"#57b8f0"},{name:"Medical(I)",freq:1485,color:"#57b8f0"},{name:"Engineering",freq:1357,color:"#f37746"},{name:"Security",freq:1359,color:"#dd3535"},{name:"Security(I)",freq:1475,color:"#dd3535"},{name:"AI Private",freq:1343,color:"#d65d95"},{name:"Common",freq:1459,color:"#1ecc43"}];var o=[{id:"o2",name:"Oxygen",label:"O\u2082",color:"blue"},{id:"n2",name:"Nitrogen",label:"N\u2082",color:"red"},{id:"co2",name:"Carbon Dioxide",label:"CO\u2082",color:"grey"},{id:"plasma",name:"Plasma",label:"Plasma",color:"pink"},{id:"water_vapor",name:"Water Vapor",label:"H\u2082O",color:"grey"},{id:"nob",name:"Hyper-noblium",label:"Hyper-nob",color:"teal"},{id:"n2o",name:"Nitrous Oxide",label:"N\u2082O",color:"red"},{id:"no2",name:"Nitryl",label:"NO\u2082",color:"brown"},{id:"tritium",name:"Tritium",label:"Tritium",color:"green"},{id:"bz",name:"BZ",label:"BZ",color:"purple"},{id:"stim",name:"Stimulum",label:"Stimulum",color:"purple"},{id:"pluox",name:"Pluoxium",label:"Pluoxium",color:"blue"},{id:"miasma",name:"Miasma",label:"Miasma",color:"olive"},{id:"hydrogen",name:"Hydrogen",label:"H\u2082",color:"white"},{id:"ab",name:"Agent B",label:"Agent B",color:"purple"}];t.getGasLabel=function(e,t){var n=String(e).toLowerCase(),r=o.find((function(e){return e.id===n||e.name.toLowerCase()===n}));return r&&r.label||t||e};t.getGasColor=function(e){var t=String(e).toLowerCase(),n=o.find((function(e){return e.id===t||e.name.toLowerCase()===t}));return n&&n.color};t.timeAgo=function(e,t){if(e>t)return"in the future";var n=(t/=10)-(e/=10);if(n>3600){var o=Math.round(n/3600);return o+" hour"+(1===o?"":"s")+" ago"}if(n>60){var r=Math.round(n/60);return r+" minute"+(1===r?"":"s")+" ago"}var a=Math.round(n);return a+" second"+(1===a?"":"s")+" ago"}},function(e,t,n){"use strict";var o=n(7),r=o({}.toString),a=o("".slice);e.exports=function(e){return a(r(e),8,-1)}},function(e,t,n){"use strict";var o=n(7);e.exports=o({}.isPrototypeOf)},function(e,t,n){"use strict";var o=n(84),r=Function.prototype,a=r.apply,c=r.call;e.exports="object"==typeof Reflect&&Reflect.apply||(o?c.bind(a):function(){return c.apply(a,arguments)})},function(e,t,n){"use strict";t.__esModule=!0,t.LabeledListItem=t.LabeledListDivider=t.LabeledList=void 0;var o=n(0),r=n(10),a=n(21),c=n(220),i=function(e){var t=e.children;return(0,o.createVNode)(1,"table","LabeledList",t,0)};t.LabeledList=i,i.defaultHooks=r.pureComponentHooks;var l=function(e){var t=e.className,n=e.label,c=e.labelColor,i=void 0===c?"label":c,l=e.color,d=e.textAlign,u=e.verticalAlign,s=e.buttons,m=e.content,p=e.children,h=e.noColon,f=void 0!==h&&h?"":":";return(0,o.createVNode)(1,"tr",(0,r.classes)(["LabeledList__row",t]),[(0,o.createComponentVNode)(2,a.Box,{as:"td",color:i,verticalAlign:u,className:(0,r.classes)(["LabeledList__cell","LabeledList__label"]),children:n?n+f:null}),(0,o.createComponentVNode)(2,a.Box,{as:"td",color:l,textAlign:d,verticalAlign:u,className:(0,r.classes)(["LabeledList__cell","LabeledList__content"]),colSpan:s?undefined:2,children:[m,p]}),s&&(0,o.createVNode)(1,"td","LabeledList__cell LabeledList__buttons",s,0)],0)};t.LabeledListItem=l,l.defaultHooks=r.pureComponentHooks;var d=function(e){var t=e.size?(0,a.unit)(Math.max(0,e.size-1)):0;return(0,o.createVNode)(1,"tr","LabeledList__row",(0,o.createVNode)(1,"td",null,(0,o.createComponentVNode)(2,c.Divider),2,{colSpan:3,style:{"padding-top":t,"padding-bottom":t}}),2)};t.LabeledListDivider=d,d.defaultHooks=r.pureComponentHooks,i.Item=l,i.Divider=d},function(e,t,n){"use strict";var o=n(28);e.exports=o("navigator","userAgent")||""},function(e,t,n){"use strict";var o=n(8),r=n(17),a=n(64);e.exports=o?function(e,t,n){return r.f(e,t,a(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){"use strict";var o=n(39),r=Math.max,a=Math.min;e.exports=function(e,t){var n=o(e);return n<0?r(n+t,0):a(n,t)}},function(e,t,n){"use strict";var o,r=n(9),a=n(133),c=n(131),i=n(87),l=n(177),d=n(128),u=n(102),s=u("IE_PROTO"),m=function(){},p=function(e){return"