Files
Paradise/code/modules/hydroponics/gene_modder.dm
T
MigratingcocofruitandGitHub 6db06b1eb8 Plant DNA Manipulator update (#25370)
* WIP

* changes

* more changes

* chnges

* update

* changes

* I suck at TGUI if you see this please help

* UI a bit better still figuring it out

* More things

* Eject Select, Select empty and autoselect empty implemented. can insert replaace stat/all stats from disk list

* Ready to make a PR

Disks can be loaded into the machine from boxed, an empty disk is automatically selected once an extraction is performed if one is available. All insert and replace buttons moved to the disk lists. Window size and scaling changed to fit LSD properly without wrapping.

* minor ui tweak, fixes core stat disk being auto selected unintentionally. Loading via box now makes a sound.

* rebuild TGUI

* rebuild tgui

* implemented RO button/indicator in UI, Added null checks for can_add

* slightly shrink some buttons rebuild TGUI

* Rebuild TGUI

* use istype for type checks

* removes the disk compartmentalizer from maps

* removes Disk Compartmentalizer

* actually removes the disk compartmentalizer

* rebuild tgui and remove all remaining disk compartmentalizer sprites

* fixes a message being output to world instead of the user

* auto doc and readability improvements.

* some fixes

adjusts display name to display the new doubled percentages. Stored seed no longer counts as a disk for the purpose of reaching the disk capacity.

* Rebuild TGUI
2024-06-28 18:57:32 +00:00

681 lines
21 KiB
Plaintext

/obj/machinery/plantgenes
name = "plant DNA manipulator"
desc = "An advanced device designed to manipulate plant genetic makeup."
icon = 'icons/obj/hydroponics/equipment.dmi'
pass_flags = PASSTABLE
icon_state = "dnamod"
density = TRUE
anchored = TRUE
/// the seed stored in the machine
var/obj/item/seeds/seed
/// the disk in use
var/obj/item/disk/plantgene/disk
/// list of the seed's trait genes
var/list/core_genes = list()
/// list of the seed's trait genes
var/list/reagent_genes = list()
/// list of the seed's trait genes
var/list/trait_genes = list()
/// disk capacity
var/disk_capacity = 100
/// gene targeted for operation
var/datum/plant_gene/target
/// maximum potency that can be put on a disk
var/max_potency = 50 // See RefreshParts() for how these work
/// maximum yield that can be put on a disk
var/max_yield = 2
/// minimum production that can be put on a disk
var/min_production = 12
/// maximum endurance that can be put on a disk
var/max_endurance = 10 // IMPT: ALSO AFFECTS LIFESPAN
/// minimum weed growth chance that can be put on a disk
var/min_weed_chance = 67
/// minimum weed growth rate that can be put on a disk
var/min_weed_rate = 10
/// amount of seeds needed to make a core stat disk
var/seeds_for_bulk_core = 5
/// index of disk in use in the content list
var/disk_index = 0
/obj/machinery/plantgenes/Initialize(mapload)
. = ..()
component_parts = list()
component_parts += new /obj/item/circuitboard/plantgenes(null)
component_parts += new /obj/item/stack/sheet/glass(null)
component_parts += new /obj/item/stock_parts/scanning_module(null)
component_parts += new /obj/item/stock_parts/micro_laser(null)
component_parts += new /obj/item/stock_parts/manipulator(null)
RefreshParts()
/obj/machinery/plantgenes/seedvault/Initialize(mapload)
. = ..()
component_parts = list()
component_parts += new /obj/item/circuitboard/plantgenes/vault(null)
component_parts += new /obj/item/stack/sheet/glass(null)
component_parts += new /obj/item/stock_parts/scanning_module(null)
component_parts += new /obj/item/stock_parts/micro_laser(null)
component_parts += new /obj/item/stock_parts/manipulator(null)
RefreshParts()
/obj/machinery/plantgenes/Destroy()
core_genes.Cut()
reagent_genes.Cut()
trait_genes.Cut()
target = null
QDEL_NULL(seed)
QDEL_NULL(disk)
return ..()
/obj/machinery/plantgenes/RefreshParts() // Comments represent the max you can set per tier, respectively. seeds.dm [219] clamps these for us but we don't want to mislead the viewer.
for(var/obj/item/stock_parts/manipulator/M in component_parts)
if(M.rating > 3)
max_potency = 95
else
max_potency = initial(max_potency) + (M.rating**3) // 51,58,77,95 Clamps at 100
max_yield = initial(max_yield) + (M.rating*2) // 4,6,8,10 Clamps at 10
for(var/obj/item/stock_parts/scanning_module/SM in component_parts)
if(SM.rating > 3) //If you create t5 parts I'm a step ahead mwahahaha!
min_production = 1
else
min_production = 12 - (SM.rating * 3) //9,6,3,1. Requires if to avoid going below clamp [1]
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/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
var/total_rating = 0
for(var/obj/item/stock_parts/S in component_parts)
total_rating += S.rating
switch(clamp(total_rating, 1, 12))
if(1 to 3)
seeds_for_bulk_core = 5
if(4 to 6)
seeds_for_bulk_core = 4
if(7 to 9)
seeds_for_bulk_core = 3
if(10 to 11)
seeds_for_bulk_core = 2
else
seeds_for_bulk_core = 1
for(var/obj/item/circuitboard/plantgenes/vaultcheck in component_parts)
if(istype(vaultcheck, /obj/item/circuitboard/plantgenes/vault)) // TRAIT_DUMB BOTANY TUTS
total_rating = 12
max_potency = 100
max_yield = 10
min_production = 1
max_endurance = 100
min_weed_chance = 0
min_weed_rate = 0
seeds_for_bulk_core = 1
/obj/machinery/plantgenes/update_icon_state()
if((stat & (BROKEN|NOPOWER)))
icon_state = "dnamod-off"
else
icon_state = "dnamod"
/obj/machinery/plantgenes/update_overlays()
. = ..()
if(seed)
. += "dnamod-dna"
if(panel_open)
. += "dnamod-open"
/obj/machinery/plantgenes/attackby(obj/item/I, mob/user, params)
if(default_deconstruction_screwdriver(user, "dnamod", "dnamod", I))
update_icon(UPDATE_OVERLAYS)
return
if(exchange_parts(user, I))
return
if(default_deconstruction_crowbar(user, I))
return
if(isrobot(user))
return
if(istype(I, /obj/item/seeds))
add_seed(I, user)
else if(istype(I, /obj/item/unsorted_seeds))
to_chat(user, "<span class='warning'>You need to sort [I] first!</span>")
return ..()
else if(istype(I, /obj/item/disk/plantgene) || istype(I, /obj/item/storage/box))
add_disk(I, user)
else
return ..()
/obj/machinery/plantgenes/proc/add_seed(obj/item/seeds/new_seed, mob/user)
if(seed)
to_chat(user, "<span class='warning'>A sample is already loaded into the machine!</span>")
return
if(!user.drop_item())
return
insert_seed(new_seed)
to_chat(user, "<span class='notice'>You add [new_seed] to the machine.</span>")
ui_interact(user)
/obj/machinery/plantgenes/proc/add_disk(obj/item/disk/plantgene/new_disk, mob/user)
if(length(contents) - (seed ? 1 : 0) >= disk_capacity)
to_chat(user, "<span class='warning'>[src] cannot hold any more disks!</span>")
return
if(istype(new_disk, /obj/item/storage/box))
var/has_disks = FALSE
for(var/obj/item/disk/plantgene/D in new_disk.contents)
if(length(contents)- (seed ? 1 : 0) >= disk_capacity)
to_chat(user, "<span class='notice'>You fill [src] with disks.</span>")
break
has_disks = TRUE
D.forceMove(src)
if(!disk)
disk = D
if(has_disks)
playsound(loc, 'sound/items/handling/cardboardbox_drop.ogg', 50)
to_chat(user, "<span class='notice'>You load [src] from [new_disk].</span>")
else
to_chat(user, "<span class='notice'>[new_disk] contains no disks.</span>")
SStgui.update_uis(src)
return
if(!user.drop_item())
return
if(!disk)
disk = new_disk
new_disk.forceMove(src)
to_chat(user, "<span class='notice'>You add [new_disk] to the machine.</span>")
ui_interact(user)
/obj/machinery/plantgenes/attack_hand(mob/user)
if(..())
return
ui_interact(user)
/obj/machinery/plantgenes/attack_ghost(mob/user)
ui_interact(user)
/obj/machinery/plantgenes/ui_state(mob/user)
return GLOB.default_state
/obj/machinery/plantgenes/ui_interact(mob/user, datum/tgui/ui = null)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "GeneModder", name)
ui.set_autoupdate(FALSE)
ui.open()
/obj/machinery/plantgenes/ui_data(mob/user)
var/list/data = list()
data["has_seed"] = seed ? TRUE : FALSE
data["has_disk"] = disk ? TRUE : FALSE
data["core_genes"] = list()
data["reagent_genes"] = list()
data["trait_genes"] = list()
data["has_reagent"] = FALSE
data["has_trait"] = FALSE
data["seed"] = list()
if(seed)
var/icon/base64icon = GLOB.seeds_cached_base64_icons["[initial(seed.icon)][initial(seed.icon_state)]"]
if(!base64icon)
base64icon = icon2base64(icon(initial(seed.icon), initial(seed.icon_state), SOUTH, 1))
GLOB.seeds_cached_base64_icons["[initial(seed.icon)][initial(seed.icon_state)]"] = base64icon
data["seed"] = list(
"image" = "[base64icon]",
"name" = seed.name,
"variant" = seed.variant
)
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
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
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
data["disk"] = list()
if(disk)
var/disk_name = disk.ui_name
if(disk.read_only)
disk_name = "[disk_name] (Read Only)"
var/can_insert = FALSE
if(seed)
can_insert = disk.gene?.can_add(seed)
data["disk"] = list(
"name" = disk_name,
"can_insert" = can_insert,
"can_extract" = !disk.read_only,
"is_core" = istype(disk?.gene, /datum/plant_gene/core),
"is_bulk_core" = disk?.is_bulk_core && (disk.seeds_needed <= disk.seeds_scanned)
)
data["modal"] = ui_modal_data(src)
var/list/stats = list()
var/list/traits = list()
var/list/reagents = list()
var/empty_disks = 0
data["stat_disks"] = list()
data["trait_disks"] = list()
data["reagent_disks"] = list()
for(var/i in 1 to length(contents))
if(istype(contents[i], /obj/item/disk/plantgene))
var/obj/item/disk/plantgene/D = contents[i]
if(!D.gene && !D.is_bulk_core)
empty_disks++
else if(D.is_bulk_core)
stats.Add(list(list("display_name" = D.ui_name, "index" = i, "stat" = "All", "ready" = D.seeds_needed <= D.seeds_scanned, "read_only" = D.read_only)))
else if(istype(D.gene, /datum/plant_gene/core))
var/datum/plant_gene/core/C = D.gene
stats.Add(list(list("display_name" = C.name +" "+ num2text(C.value), "index" = i, "stat" = C.name, "read_only" = D.read_only)))
else if(istype(D.gene, /datum/plant_gene/trait))
var/insertable = D.gene?.can_add(seed)
traits.Add(list(list("display_name" = D.gene.name, "index" = i, "can_insert" = insertable, "read_only" = D.read_only)))
else if(istype(D.gene, /datum/plant_gene/reagent))
var/datum/plant_gene/reagent/R = D.gene
var/insertable = R?.can_add(seed)
reagents.Add(list(list("display_name" = "[R.name] [num2text(R.rate*200)]%", "index" = i, "can_insert" = insertable, "read_only" = D.read_only)))
if(length(stats))
data["stat_disks"] = stats
if(length(traits))
data["trait_disks"] = traits
if(length(reagents))
data["reagent_disks"] = reagents
data["empty_disks"] = empty_disks
return data
/obj/machinery/plantgenes/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
if(..())
return
. = 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
if(params["id"])
target = seed?.get_gene(params["id"])
else if(params["stat"] && params["stat"] != "All")
for(var/datum/plant_gene/core/c_gene in core_genes)
if(c_gene.name == params["stat"])
target = c_gene
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")
var/obj/item/disk/plantgene/D = contents[text2num(params["index"])]
if(D)
D.forceMove(loc)
user.put_in_hands(D)
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("bulk_extract_core")
var/dat = "Are you sure you want to extract all core genes from the [seed]? The sample will be destroyed in the process!"
var/prev_seeds = 0
if(disk.is_bulk_core && disk.core_matches(seed))
prev_seeds = disk.seeds_scanned
if(seeds_for_bulk_core > prev_seeds + 1)
var/remaining = seeds_for_bulk_core - prev_seeds - 1
dat += " This device needs [seeds_for_bulk_core] samples to produce a usable core gene disk. You will need [remaining] more sample[ remaining > 1 ? "s" : ""] with identical core genes."
ui_modal_boolean(src, action, dat, yes_text = "Extract", no_text = "Cancel", delegate = PROC_REF(bulk_extract_core))
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("bulk_replace_core")
disk_index = text2num(params["index"])
ui_modal_boolean(src, action, "Are you sure you want to replace ALL core genes of the [seed]?" , yes_text = "Replace", no_text = "Cancel", delegate = PROC_REF(bulk_replace_core))
if("replace")
disk_index = text2num(params["index"])
var/obj/item/disk/plantgene/D = contents[text2num(params["index"])]
ui_modal_boolean(src, action, "Are you sure you want to replace [target.get_name()] gene with [D.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")
var/obj/item/disk/plantgene/D = contents[text2num(params["index"])]
if(D.gene && (istype(D.gene, /datum/plant_gene/trait) || istype(D.gene, /datum/plant_gene/reagent)) && D.gene.can_add(seed))
seed.genes += D.gene.Copy()
if(istype(D.gene, /datum/plant_gene/reagent))
seed.reagents_from_genes()
update_genes()
repaint_seed()
if("select")
disk = contents[text2num(params["index"])]
if("select_empty_disk")
for(var/obj/item/disk/plantgene/D in contents)
if(!D.gene && !D.is_bulk_core)
disk = D
return
if("eject_empty_disk")
for(var/obj/item/disk/plantgene/D in contents)
if(!D.gene && !D.is_bulk_core)
D.forceMove(loc)
user.put_in_hands(D)
update_genes()
return
to_chat(user, "<span class='warning'> No Empty Disks to Eject!</span>")
if("set_read_only")
var/obj/item/disk/plantgene/D = contents[text2num(params["index"])]
D.read_only = !D.read_only
/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
/obj/machinery/plantgenes/proc/gene_extract()
if(!disk || disk.read_only)
return
disk.is_bulk_core = FALSE
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)
disk.update_name()
QDEL_NULL(seed)
update_icon(UPDATE_OVERLAYS)
update_genes()
target = null
//replace with empty disk if possible
for(var/obj/item/disk/plantgene/D in contents)
if(!D.gene && !D.is_bulk_core)
disk = D
return
/obj/machinery/plantgenes/proc/gene_replace()
var/obj/item/disk/plantgene/D = contents[disk_index]
if(!D?.gene || D.is_bulk_core)
return
if(!istype(target, /datum/plant_gene/core))
return
if(!istype(D.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 = D.gene.Copy()
seed.genes += C
C.apply_stat(seed)
repaint_seed()
update_genes()
target = null
/obj/machinery/plantgenes/proc/bulk_extract_core()
if(!disk || disk.read_only)
return
disk.seeds_needed = seeds_for_bulk_core
if(disk.core_matches(seed))
disk.seeds_scanned += 1
else
disk.seeds_scanned = 1
disk.is_bulk_core = TRUE
disk.gene = null
disk.core_genes = list()
for(var/datum/plant_gene/core/gene in core_genes)
var/datum/plant_gene/core/C = gene.Copy()
disk.core_genes += C
disk.update_name()
QDEL_NULL(seed)
update_icon(UPDATE_OVERLAYS)
update_genes()
target = null
if(disk.seeds_scanned >= disk.seeds_needed)
for(var/obj/item/disk/plantgene/D in contents)
if(!D.gene && (!D.is_bulk_core))
disk = D
return
/obj/machinery/plantgenes/proc/bulk_replace_core()
var/obj/item/disk/plantgene/D = contents[disk_index]
if(!D?.is_bulk_core)
return
if(D.seeds_scanned < D.seeds_needed)
return
for(var/datum/plant_gene/gene in seed.genes)
if(istype(gene, /datum/plant_gene/core))
seed.genes -= gene
for(var/datum/plant_gene/core/gene in D.core_genes)
var/datum/plant_gene/core/C = 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)
return
S.forceMove(src)
seed = S
update_genes()
update_icon(UPDATE_OVERLAYS)
/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,
/datum/plant_gene/core/weed_rate,
/datum/plant_gene/core/weed_chance
)
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
/obj/machinery/plantgenes/proc/repaint_seed()
if(!seed)
return
if(copytext(seed.name, 1, 13) == "experimental")
return // Already modded name and icon
seed.name = "experimental " + seed.name
seed.icon_state = "seed-x"
/*
* Plant DNA disk
*/
/obj/item/disk/plantgene
name = "plant data disk"
desc = "A disk for storing plant genetic data."
icon_state = "datadisk_hydro"
materials = list(MAT_METAL=30, MAT_GLASS=10)
var/ui_name = "Empty Disk"
var/is_bulk_core = FALSE
var/read_only = 0 //Well, it's still a floppy disk
// For single genes
var/datum/plant_gene/gene
// For bulk core genes
var/list/core_genes = list()
var/seeds_scanned = 0
var/seeds_needed = 5
/obj/item/disk/plantgene/New()
..()
update_icon(UPDATE_OVERLAYS)
/obj/item/disk/plantgene/Destroy()
QDEL_NULL(gene)
return ..()
/obj/item/disk/plantgene/attackby(obj/item/W, mob/user, params)
..()
if(is_pen(W))
rename_interactive(user, W)
/obj/item/disk/plantgene/update_name()
. = ..()
if(HAS_TRAIT(src, TRAIT_CMAGGED))
name = "nuclear authentication disk"
ui_name = "nuclear authentication disk?"
return
if(!is_bulk_core && gene)
name = "[gene.get_name()] (plant data disk)"
ui_name = "[gene.get_name()]"
else if(is_bulk_core)
name = ""
if(seeds_scanned < seeds_needed)
name += "[round(seeds_scanned/seeds_needed*100,1)]% of "
name += "Core gene set "
ui_name = "Core "
for(var/i in 1 to length(core_genes))
if(i > 1)
name += "/"
ui_name += "/"
var/datum/plant_gene/core/core_gene = core_genes[i]
name += "[core_gene.value]"
ui_name += "[core_gene.value]"
name += " (plant data disk)"
if(seeds_scanned < seeds_needed)
ui_name += " ([round(seeds_scanned / seeds_needed * 100, 1)]%)"
else
name = "plant data disk"
ui_name = "Empty Disk"
/obj/item/disk/plantgene/proc/core_matches(obj/item/seeds/seed)
if(!is_bulk_core)
return FALSE
for(var/datum/plant_gene/core/gene in core_genes)
var/datum/plant_gene/core/seed_gene = seed.get_gene(gene.type)
if(gene.value != seed_gene.value)
return FALSE
return TRUE
/obj/item/disk/plantgene/update_desc()
. = ..()
if(HAS_TRAIT(src, TRAIT_CMAGGED))
desc = "Better keep this safe."
return
desc = "A disk for storing plant genetic data."
/obj/item/disk/plantgene/update_icon_state()
. = ..()
if(HAS_TRAIT(src, TRAIT_CMAGGED))
icon_state = "nucleardisk"
return
icon_state = "datadisk_hydro"
/obj/item/disk/plantgene/update_overlays()
. = ..()
if(HAS_TRAIT(src, TRAIT_CMAGGED))
return
. += "datadisk_gene"
/obj/item/disk/plantgene/attack_self(mob/user)
if(HAS_TRAIT(src, TRAIT_CMAGGED))
return
read_only = !read_only
to_chat(user, "<span class='notice'>You flip the write-protect tab to [read_only ? "protected" : "unprotected"].</span>")
/obj/item/disk/plantgene/cmag_act(mob/user)
if(!HAS_TRAIT(src, TRAIT_CMAGGED))
to_chat(user, "<span class='warning'>The bananium ooze flips a couple bits on the plant disk's display, making it look just like the..!</span>")
ADD_TRAIT(src, TRAIT_CMAGGED, CLOWN_EMAG)
update_appearance(UPDATE_NAME|UPDATE_DESC|UPDATE_ICON)
playsound(src, "sparks", 75, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
/obj/item/disk/plantgene/uncmag()
update_appearance(UPDATE_NAME|UPDATE_DESC|UPDATE_ICON)
/obj/item/disk/plantgene/examine(mob/user)
. = ..()
if(!HAS_TRAIT(src, TRAIT_CMAGGED))
. += "The write-protect tab is set to [read_only ? "protected" : "unprotected"]."
return
if((user.mind.assigned_role == "Captain" || user.mind.special_role == SPECIAL_ROLE_NUKEOPS) && (user.Adjacent(src)))
. += "<span class='warning'>... Wait. This isn't the nuclear authentication disk! It's a clever forgery!</span>"