diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm
index 85b3363e073..a6d8287c1db 100644
--- a/code/game/machinery/cloning.dm
+++ b/code/game/machinery/cloning.dm
@@ -353,13 +353,8 @@
/obj/item/weapon/storage/box/disks/New()
..()
- new /obj/item/weapon/disk/data(src)
- new /obj/item/weapon/disk/data(src)
- new /obj/item/weapon/disk/data(src)
- new /obj/item/weapon/disk/data(src)
- new /obj/item/weapon/disk/data(src)
- new /obj/item/weapon/disk/data(src)
- new /obj/item/weapon/disk/data(src)
+ for(var/i in 1 to 7)
+ new /obj/item/weapon/disk/data(src)
/*
* Manual -- A big ol' manual.
diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm
index 79f88ef5797..b0775204b67 100644
--- a/code/game/machinery/constructable_frame.dm
+++ b/code/game/machinery/constructable_frame.dm
@@ -791,3 +791,14 @@ obj/item/weapon/circuitboard/rdserver
req_components = list(
/obj/item/weapon/stock_parts/console_screen = 1,
/obj/item/weapon/stock_parts/matter_bin = 3)
+
+/obj/item/weapon/circuitboard/plantgenes
+ name = "circuit board (Plant DNA Manipulator)"
+ build_path = /obj/machinery/plantgenes
+ board_type = "machine"
+ origin_tech = "programming=2;biotech=3"
+ req_components = list(
+ /obj/item/weapon/stock_parts/manipulator = 1,
+ /obj/item/weapon/stock_parts/micro_laser = 2,
+ /obj/item/weapon/stock_parts/console_screen = 1,
+ /obj/item/weapon/stock_parts/scanning_module = 1,)
\ No newline at end of file
diff --git a/code/modules/hydroponics/gene_modder.dm b/code/modules/hydroponics/gene_modder.dm
new file mode 100644
index 00000000000..37695f75e65
--- /dev/null
+++ b/code/modules/hydroponics/gene_modder.dm
@@ -0,0 +1,387 @@
+/obj/machinery/plantgenes
+ name = "plant DNA manipulator"
+ desc = "An advanced device designed to manipulate plant genetic makeup."
+ icon = 'icons/obj/hydroponics/equipment.dmi'
+ icon_state = "dnamod"
+ density = 1
+ anchored = 1
+
+ var/obj/item/seeds/seed
+ var/obj/item/weapon/disk/plantgene/disk
+
+ var/list/core_genes = list()
+ var/list/reagent_genes = list()
+ var/list/trait_genes = list()
+
+ var/datum/plant_gene/target
+ var/operation = ""
+ var/rating = 0 // TODO: add failures, make it useful
+
+/obj/machinery/plantgenes/New()
+ ..()
+ component_parts = list()
+ component_parts += new /obj/item/weapon/circuitboard/plantgenes(src)
+ component_parts += new /obj/item/weapon/stock_parts/console_screen(src)
+ component_parts += new /obj/item/weapon/stock_parts/scanning_module(src)
+ component_parts += new /obj/item/weapon/stock_parts/micro_laser(src)
+ component_parts += new /obj/item/weapon/stock_parts/micro_laser(src)
+ component_parts += new /obj/item/weapon/stock_parts/manipulator(src)
+ RefreshParts()
+
+/obj/machinery/plantgenes/RefreshParts()
+ rating = 0
+ for(var/obj/item/weapon/stock_parts/S in component_parts)
+ rating += S.rating-1
+
+/obj/machinery/plantgenes/update_icon()
+ ..()
+ overlays.Cut()
+ if((stat & (BROKEN|NOPOWER)))
+ icon_state = "dnamod-off"
+ else
+ icon_state = "dnamod"
+ if(seed)
+ overlays += "dnamod-dna"
+ if(panel_open)
+ overlays += "dnamod-open"
+
+/obj/machinery/plantgenes/attackby(obj/item/I, mob/user, params)
+ if(default_deconstruction_screwdriver(user, "dnamod", "dnamod", I))
+ update_icon()
+ return
+ if(exchange_parts(user, I))
+ return
+ if(default_deconstruction_crowbar(I))
+ return
+ if(isrobot(user))
+ return
+
+ if(istype(I, /obj/item/seeds))
+ if(seed)
+ user << "A sample is already loaded into the machine!"
+ else
+ if(!user.drop_item())
+ return
+ insert_seed(I)
+ user << "You add [I] to the machine."
+ interact(user)
+ return
+ else if(istype(I, /obj/item/weapon/disk/plantgene))
+ if(disk)
+ user << "A data disk is already loaded into the machine!"
+ else
+ if(!user.drop_item())
+ return
+ disk = I
+ disk.loc = src
+ user << "You add [I] to the machine."
+ interact(user)
+ else
+ ..()
+
+
+/obj/machinery/plantgenes/attack_hand(mob/user)
+ if(..())
+ return
+ interact(user)
+
+/obj/machinery/plantgenes/interact(mob/user)
+ user.set_machine(src)
+ if(!user)
+ return
+
+ var/datum/browser/popup = new(user, "plantdna", "Plant DNA Manipulator", 450, 600) // Set up the popup browser window
+ if(!( in_range(src, user) || istype(user, /mob/living/silicon) ))
+ popup.close()
+ return
+
+ var/dat = ""
+
+ 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
+
+ dat += "
Confirm Operation
"
+ dat += "Are you sure you want to [operation] "
+ switch(operation)
+ if("remove")
+ dat += "[target.get_name()] gene from \the [seed]?
"
+// dat += "Failure may damage or destroy the sample!" // No failures yet. TODO: add failures
+ if("extract")
+ dat += "[target.get_name()] gene from \the [seed]?
"
+ dat += "The sample will be destroyed in process!"
+ if("replace")
+ dat += "[target.get_name()] gene with [disk.gene.get_name()]?
"
+// dat += "Failure may damage or destroy the sample!" // No failures yet. TODO: add failures
+ if("insert")
+ dat += "[disk.gene.get_name()] gene into \the [seed]?
"
+// dat += "Failure may damage or destroy the sample!" // No failures yet. TODO: add failures
+ dat += "
"
+ popup.set_content(dat)
+ popup.open()
+ return
+
+ dat+= ""
+
+ dat += "
"
+
+ dat += "
"
+
+ dat += "
"
+
+ if(seed)
+ var/can_insert = disk && disk.gene && disk.gene.can_add(seed)
+ var/can_extract = disk && !disk.read_only
+
+ 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 += "
"
+
+ 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()]"
+
+ 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 += "
"
+ else
+ dat += "
No sample found.
Please, insert a plant sample to use this device."
+ popup.set_content(dat)
+ popup.open()
+
+
+/obj/machinery/plantgenes/Topic(var/href, var/list/href_list)
+ if(..())
+ return
+ usr.set_machine(src)
+
+ if(href_list["eject_seed"] && !operation)
+ if (seed)
+ seed.loc = src.loc
+ seed.verb_pickup()
+ seed = null
+ update_genes()
+ update_icon()
+ else
+ var/obj/item/I = usr.get_active_hand()
+ if (istype(I, /obj/item/seeds))
+ if(!usr.drop_item())
+ return
+ insert_seed(I)
+ usr << "You add [I] to the machine."
+ update_icon()
+ else if(href_list["eject_disk"] && !operation)
+ if (disk)
+ disk.loc = src.loc
+ disk.verb_pickup()
+ disk = null
+ update_genes()
+ else
+ var/obj/item/I = usr.get_active_hand()
+ if(istype(I, /obj/item/weapon/disk/plantgene))
+ if(!usr.drop_item())
+ return
+ disk = I
+ disk.loc = src
+ 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
+ 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()
+ operation = ""
+ target = null
+
+ 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"]
+
+ 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()
+ if("extract")
+ if(disk && !disk.read_only)
+ disk.gene = G
+ disk.update_name()
+ qdel(seed)
+ seed = null
+ update_icon()
+ 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)
+ 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()
+
+ update_genes()
+ operation = ""
+ target = null
+ else if(href_list["abort"])
+ operation = ""
+ target = null
+
+ interact(usr)
+
+/obj/machinery/plantgenes/proc/insert_seed(obj/item/seeds/S)
+ if(!istype(S) || seed)
+ return
+ S.loc = src
+ seed = S
+ update_genes()
+ update_icon()
+
+/obj/machinery/plantgenes/proc/update_genes()
+ core_genes = list()
+ reagent_genes = list()
+ trait_genes = list()
+
+ if(seed)
+ var/gene_paths = list(
+ /datum/plant_gene/core/potency,
+ /datum/plant_gene/core/yield,
+ /datum/plant_gene/core/production,
+ /datum/plant_gene/core/endurance,
+ /datum/plant_gene/core/lifespan
+ )
+ for(var/a in gene_paths)
+ core_genes += seed.get_gene(a)
+
+ for(var/datum/plant_gene/reagent/G in seed.genes)
+ reagent_genes += G
+ for(var/datum/plant_gene/trait/G in seed.genes)
+ trait_genes += G
+
+
+
+/*
+ * Plant DNA disk
+ */
+
+/obj/item/weapon/disk/plantgene
+ name = "plant data disk"
+ desc = "A disk for storing plant genetic data."
+ icon_state = "datadisk2"
+ materials = list(MAT_METAL=30, MAT_GLASS=10)
+ var/datum/plant_gene/gene
+ var/read_only = 0 //Well, it's still a floppy disk
+
+/obj/item/weapon/disk/plantgene/New()
+ ..()
+ overlays += "datadisk_gene"
+ src.pixel_x = rand(-5, 5)
+ src.pixel_y = rand(-5, 5)
+
+/obj/item/weapon/disk/plantgene/attackby(obj/item/weapon/W, mob/user, params)
+ ..()
+ if(istype(W, /obj/item/weapon/pen))
+ var/t = stripped_input(user, "What would you like the label to be?", name, null)
+ if(user.get_active_hand() != W)
+ return
+ if(!in_range(src, user) && loc != user)
+ return
+ if(t)
+ name = "plant data disk - '[t]'"
+ else
+ name = "plant data disk"
+
+/obj/item/weapon/disk/plantgene/proc/update_name()
+ if(gene)
+ name = "plant data disk - '[gene.get_name()]'"
+ else
+ name = "plant data disk"
+
+/obj/item/weapon/disk/plantgene/attack_self(mob/user)
+ read_only = !read_only
+ user << "You flip the write-protect tab to [src.read_only ? "protected" : "unprotected"]."
+
+/obj/item/weapon/disk/plantgene/examine(mob/user)
+ ..()
+ user << "The write-protect tab is set to [src.read_only ? "protected" : "unprotected"]."
+
+
+/*
+ * Plant DNA Disks Box
+ */
+/obj/item/weapon/storage/box/disks_plantgene
+ name = "plant data disks box"
+ icon_state = "disk_kit"
+
+/obj/item/weapon/storage/box/disks_plantgene/New()
+ ..()
+ for(var/i in 1 to 7)
+ new /obj/item/weapon/disk/plantgene(src)
\ No newline at end of file
diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm
index 02226ad0ade..59caa40fe7c 100644
--- a/code/modules/hydroponics/grown.dm
+++ b/code/modules/hydroponics/grown.dm
@@ -23,7 +23,7 @@
else if(ispath(seed))
// This is for adminspawn or map-placed growns. They get the default stats of their seed type.
seed = new seed()
- seed.potency = 50
+ seed.adjust_potency(50-seed.potency)
else // Something is terribly wrong
qdel(src)
return
@@ -77,7 +77,7 @@
if(reag_txt)
msg += reag_txt
- msg += "*---------*"
+ msg += "
*---------*"
user << msg
return
return
@@ -128,8 +128,8 @@
var/datum/plant_gene/trait/slip/S = seed.get_gene(/datum/plant_gene/trait/slip)
if(S && !ispath(trash, /obj/item/weapon/grown) && iscarbon(AM))
var/mob/living/carbon/M = AM
- var/stun = min(seed.potency * S.rate * 2, 1)
- var/weaken = min(seed.potency * S.rate, 0.5)
+ var/stun = max(seed.potency * S.rate * 2, 1)
+ var/weaken = max(seed.potency * S.rate, 0.5)
if(M.slip(stun, weaken, src))
for(var/datum/plant_gene/trait/T in seed.genes)
T.on_slip(src, M)
diff --git a/code/modules/hydroponics/grown/citrus.dm b/code/modules/hydroponics/grown/citrus.dm
index 7f5bc5e7f27..cf05ca2b1a8 100644
--- a/code/modules/hydroponics/grown/citrus.dm
+++ b/code/modules/hydroponics/grown/citrus.dm
@@ -30,7 +30,7 @@
// Orange
/obj/item/seeds/orange
- name = "pack of orange seed"
+ name = "pack of orange seeds"
desc = "Sour seeds."
icon_state = "seed-orange"
species = "orange"
diff --git a/code/modules/hydroponics/growninedible.dm b/code/modules/hydroponics/growninedible.dm
index a97cf99a19e..6af91f91aa9 100644
--- a/code/modules/hydroponics/growninedible.dm
+++ b/code/modules/hydroponics/growninedible.dm
@@ -17,7 +17,7 @@
else if(ispath(seed))
// This is for adminspawn or map-placed growns. They get the default stats of their seed type.
seed = new seed()
- seed.potency = 50
+ seed.adjust_potency(50-seed.potency)
else // Something is terribly wrong
qdel(src)
return
@@ -53,8 +53,8 @@
var/datum/plant_gene/trait/slip/S = seed.get_gene(/datum/plant_gene/trait/slip)
if(S && iscarbon(AM))
var/mob/living/carbon/M = AM
- var/stun = min(seed.potency * S.rate * 2, 1)
- var/weaken = min(seed.potency * S.rate, 0.5)
+ var/stun = max(seed.potency * S.rate * 2, 1)
+ var/weaken = max(seed.potency * S.rate, 0.5)
if(M.slip(stun, weaken, src))
for(var/datum/plant_gene/trait/T in seed.genes)
T.on_slip(src, M)
diff --git a/code/modules/hydroponics/plant_genes.dm b/code/modules/hydroponics/plant_genes.dm
index 364a0b68d2a..24e3e9e0527 100644
--- a/code/modules/hydroponics/plant_genes.dm
+++ b/code/modules/hydroponics/plant_genes.dm
@@ -4,11 +4,15 @@
/datum/plant_gene/proc/get_name() // Used for manipulator display and gene disk name.
return name
+/datum/plant_gene/proc/can_add(obj/item/seeds/S)
+ return TRUE
+
/datum/plant_gene/proc/Copy()
return new type
+
// Core plant genes store 5 main variables: lifespan, endurance, production, yield, potency
/datum/plant_gene/core
var/value
@@ -16,6 +20,9 @@
/datum/plant_gene/core/get_name()
return "[name] [value]"
+/datum/plant_gene/core/proc/apply_stat(obj/item/seeds/S)
+ return
+
/datum/plant_gene/core/New(var/i = null)
..()
if(!isnull(i))
@@ -26,27 +33,47 @@
C.value = value
return C
+/datum/plant_gene/core/can_add(obj/item/seeds/S)
+ return S.get_gene(src.type)
/datum/plant_gene/core/lifespan
name = "Lifespan"
value = 25
+/datum/plant_gene/core/lifespan/apply_stat(obj/item/seeds/S)
+ S.lifespan = value
+
+
/datum/plant_gene/core/endurance
name = "Endurance"
value = 15
+/datum/plant_gene/core/endurance/apply_stat(obj/item/seeds/S)
+ S.endurance = value
+
+
/datum/plant_gene/core/production
name = "Production Speed"
value = 6
+/datum/plant_gene/core/production/apply_stat(obj/item/seeds/S)
+ S.production = value
+
+
/datum/plant_gene/core/yield
name = "Yield"
value = 3
+/datum/plant_gene/core/yield/apply_stat(obj/item/seeds/S)
+ S.yield = value
+
+
/datum/plant_gene/core/potency
name = "Potency"
value = 10
+/datum/plant_gene/core/potency/apply_stat(obj/item/seeds/S)
+ S.potency = value
// Reagent genes store reagent ID and reagent ratio. Amount of reagent in the plant = 1 + (potency * rate)
@@ -79,6 +106,11 @@
G.rate = rate
return G
+/datum/plant_gene/reagent/can_add(obj/item/seeds/S)
+ for(var/datum/plant_gene/reagent/R in S.genes)
+ if(R.reagent_id == reagent_id)
+ return FALSE
+ return TRUE
// Various traits affecting the product. Each must be somehow useful.
@@ -86,12 +118,21 @@
var/rate = 0.05
var/examine_line = ""
var/list/origin_tech = null
+ var/trait_id // must be set and equal for any two traits of the same type
/datum/plant_gene/trait/Copy()
var/datum/plant_gene/trait/G = ..()
G.rate = rate
return G
+/datum/plant_gene/trait/can_add(obj/item/seeds/S)
+ for(var/datum/plant_gene/trait/R in S.genes)
+ if(trait_id && R.trait_id == trait_id)
+ return FALSE
+ if(type == R.type)
+ return FALSE
+ return TRUE
+
/datum/plant_gene/trait/proc/on_new(obj/item/weapon/reagent_containers/food/snacks/grown/G, newloc)
if(origin_tech) // This ugly code segment adds RnD tech levels to resulting plants.
if(G.origin_tech)
@@ -179,6 +220,7 @@
name = "Bioluminescence"
rate = 0.1
examine_line = "It emits a soft glow."
+ trait_id = "glow"
/datum/plant_gene/trait/glow/proc/get_lum(obj/item/seeds/S)
return round(S.potency*rate)
diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm
index 26ce249e646..b13dd503231 100644
--- a/code/modules/hydroponics/seeds.dm
+++ b/code/modules/hydroponics/seeds.dm
@@ -78,6 +78,7 @@
for(var/g in genes)
var/datum/plant_gene/G = g
S.genes += G.Copy()
+ S.reagents_add = reagents_add.Copy() // Faster than grabbing the list from genes.
return S
/obj/item/seeds/proc/get_gene(typepath)
@@ -141,10 +142,10 @@
if(T.reagents)
for(var/reagent_id in reagents_add)
if(reagent_id == "blood") // Hack to make blood in plants always O-
- T.reagents.add_reagent(reagent_id, 1 + round(potency * reagents_add[reagent_id]), list("blood_type"="O-"))
+ T.reagents.add_reagent(reagent_id, 1 + round(potency * reagents_add[reagent_id], 1), list("blood_type"="O-"))
continue
- T.reagents.add_reagent(reagent_id, 1 + round(potency * reagents_add[reagent_id]))
+ T.reagents.add_reagent(reagent_id, 1 + round(potency * reagents_add[reagent_id]), 1)
return 1
diff --git a/icons/obj/hydroponics/equipment.dmi b/icons/obj/hydroponics/equipment.dmi
index 904eb38c53c..9b35f0851ad 100644
Binary files a/icons/obj/hydroponics/equipment.dmi and b/icons/obj/hydroponics/equipment.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 9927e75c18f..d64ff1487ef 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1089,6 +1089,7 @@
#include "code\modules\html_interface\cards\cards.dm"
#include "code\modules\html_interface\nanotrasen\nanotrasen.dm"
#include "code\modules\hydroponics\biogenerator.dm"
+#include "code\modules\hydroponics\gene_modder.dm"
#include "code\modules\hydroponics\grown.dm"
#include "code\modules\hydroponics\growninedible.dm"
#include "code\modules\hydroponics\hydroitemdefines.dm"