Files
Bubberstation/code/modules/hydroponics/seeds.dm
MrMelbert d2b92afe55 Plant Analyzer UX Revamp (#92667)
## About The Pull Request

<img width="1300" height="706" alt="image"
src="https://github.com/user-attachments/assets/c7971eab-6af3-4fea-94f5-b76104696b73"
/>

1. Chems are no longer hidden in an icon's tooltip. There is a separate
tab for chems. Right clicking to do a chem scan will pull up the chem
tab, but you can also just use the two buttons at the top - Closes
#91172

2. You no longer need to repeatedly scan a tray to update the UI.
Standing adjacent to a scanned tray will update it regularly.

3. Units have been redefined from pure seconds to minutes/seconds where
applicable.

4. The report has been swapped around, with tray on top and plant on
bottom.

5. The nutrient bar is now colored based on contents of the tray. It no
longer has a tooltip listing the tray's contents, that's now in the
chemicals tab.

6. Auto-grow, pollinating, and yield modifier icons have been brought
center below the tray, instead of in the top right section. They now
change color to make it more apparent at a glance.

7. Traits have been moved from icons to a collapsible list at the bottom
of the UI, and are now printed entirely. They still have tooltips
explaining what they do.

8. Grafted gene was moved center below the plant, and is now printed
entirely.

9. Mutations have been moved to a collapsible list center below the
plant.

10. Plants now indicate if they are dead in the UI.

11. Scanning produce now lists all the chemicals inside it on top of all
the chemicals it produces. - Closes #89443, Closes #90793

12. Restores lost behavior of seed unique data - Closes #90706

Also

13. Added Hydroponics to Runtime station

14. Added screentip for dumping reagents from hydro-trays

## Why It's Good For The Game

While I was initially supportive of the move to a plant analyzer UI it
was undeniable it was a huge UX stepdown from printing results to chat.

This PR aims to bring back some of those lost UX features by making more
information visible at a glance (instead of requiring memorization of
mystery meat icons or hovering over many elements to find information
hidden in tooltips)

I know it's a little bit of a mess with the colors and layout, I planned
on doing a whole custom css and layout and such but I kinda just wanted
to get it out.

## Changelog

🆑 Melbert
qol: Plant analyzer: Scanning a tray will regularly update the UI with
the tray's information while you stand adjacent to it, no longer
requiring you re-scan the tray to update it.
qol: Plant analyzer: UI is now split between chems and stats - RMB will
open directly to the chem tab.
qol: Plant analyzer: UI received several UX changes - listing out more
information at a glance, rather than behind tooltips or icons
fix: Plant analyzer: Chem scanning plants now shows the plant's current
chemical contents once again (on top of their genes)
fix: Plant analyzer: Unique seed data (kudzu mutation, replica pod dna)
is shown again
qol: Added hydroponics to runtime station
qol: Added screentip to hydroponic trays for clearing reagents
/🆑
2025-09-02 18:57:41 -07:00

661 lines
25 KiB
Plaintext

// ********************************************************
// Here's all the seeds (plants) that can be used in hydro
// ********************************************************
/obj/item/seeds
icon = 'icons/obj/service/hydroponics/seeds.dmi'
icon_state = "seed" // Unknown plant seed - these shouldn't exist in-game.
worn_icon_state = "seed"
w_class = WEIGHT_CLASS_TINY
resistance_flags = FLAMMABLE
/// Name of plant when planted.
var/plantname = "Plants"
/// A type path. The thing that is created when the plant is harvested.
var/obj/item/product
///Describes the product on the product path.
var/productdesc
/// Used to update icons. Should match the name in the sprites unless all icon_* are overridden.
var/species = ""
///the file that stores the sprites of the growing plant from this seed.
var/growing_icon = 'icons/obj/service/hydroponics/growing.dmi'
/// Used to override grow icon (default is `"[species]-grow"`). You can use one grow icon for multiple closely related plants with it.
var/icon_grow
/// Used to override dead icon (default is `"[species]-dead"`). You can use one dead icon for multiple closely related plants with it.
var/icon_dead
/// Used to override harvest icon (default is `"[species]-harvest"`). If null, plant will use `[icon_grow][growthstages]`.
var/icon_harvest
/// Used to offset the plant sprite so that it appears at proper height in the tray
var/plant_icon_offset = 8
/// How long before the plant begins to take damage from age.
var/lifespan = 25
/// Amount of health the plant has.
var/endurance = 15
/// Used to determine which sprite to switch to when growing.
var/maturation = 6
/// Changes the amount of time needed for a plant to become harvestable.
var/production = 6
/// Amount of growns created per harvest. If is -1, the plant/shroom/weed is never meant to be harvested.
var/yield = 3
/// The 'power' of a plant. Generally effects the amount of reagent in a plant, also used in other ways.
var/potency = 10
/// Amount of growth sprites the plant has.
var/growthstages = 6
// Chance that a plant will mutate in each stage of its life.
var/instability = 5
/// How rare the plant is. Used for giving points to cargo when shipping off to CentCom.
var/rarity = 0
/// The type of plants that this plant can mutate into.
var/list/mutatelist
/// Starts as a list of paths, is converted to a list of types on init. Plant gene datums are stored here, see plant_genes.dm for more info.
var/list/genes = list()
/// A list of reagents to add to product.
var/list/reagents_add
// Format: /datum/reagent/type = potency multiplier
// Stronger reagents must always come first to avoid being displaced by weaker ones.
// Total amount of any reagent in plant is calculated by formula: max(round(potency * multiplier), 1)
///If the chance below passes, then this many weeds sprout during growth
var/weed_rate = 1
///Percentage chance per tray update to grow weeds
var/weed_chance = 5
///Determines if the plant has had a graft removed or not.
var/grafted = FALSE
///Type-path of trait to be applied when grafting a plant.
var/graft_gene
///Determines if the plant should be allowed to mutate early at 30+ instability.
var/seed_flags = MUTATE_EARLY
/obj/item/seeds/Initialize(mapload, nogenes = FALSE)
. = ..()
pixel_x = base_pixel_x + rand(-8, 8)
pixel_y = base_pixel_y + rand(-8, 8)
if(!icon_grow)
icon_grow = "[species]-grow"
if(!icon_dead)
icon_dead = "[species]-dead"
if(!icon_harvest && !get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism) && yield != -1)
icon_harvest = "[species]-harvest"
if(!nogenes)
for(var/plant_gene in genes)
if(ispath(plant_gene))
genes -= plant_gene
genes += new plant_gene
// Go through all traits in their genes and call on_new_seed from them.
for(var/datum/plant_gene/trait/traits in genes)
traits.on_new_seed(src)
for(var/reag_id in reagents_add)
genes += new /datum/plant_gene/reagent(reag_id, reagents_add[reag_id])
reagents_from_genes() //quality coding
var/static/list/hovering_item_typechecks = list(
/obj/item/plant_analyzer = list(
SCREENTIP_CONTEXT_LMB = "Scan seed stats",
SCREENTIP_CONTEXT_RMB = "Scan seed chemicals"
),
)
AddElement(/datum/element/contextual_screentip_item_typechecks, hovering_item_typechecks)
/obj/item/seeds/Destroy()
// No AS ANYTHING here, because the list/genes could have typepaths in it.
for(var/datum/plant_gene/gene in genes)
gene.on_removed(src)
qdel(gene)
genes.Cut()
return ..()
/obj/item/seeds/examine(mob/user)
. = ..()
. += span_notice("Use a pen on it to rename it or change its description.")
if(reagents_add && user.can_see_reagents())
. += span_notice("- Plant Reagents -")
for(var/datum/plant_gene/reagent/reagent_gene in genes)
. += span_notice("- [reagent_gene.get_name()] -")
/// Copy all the variables from one seed to a new instance of the same seed and return it.
/obj/item/seeds/proc/Copy()
var/obj/item/seeds/copy_seed = new type(null, TRUE)
// Copy all the stats
copy_seed.lifespan = lifespan
copy_seed.endurance = endurance
copy_seed.maturation = maturation
copy_seed.production = production
copy_seed.yield = yield
copy_seed.potency = potency
copy_seed.instability = instability
copy_seed.weed_rate = weed_rate
copy_seed.weed_chance = weed_chance
copy_seed.name = name
copy_seed.plantname = plantname
copy_seed.desc = desc
copy_seed.productdesc = productdesc
copy_seed.genes = list()
for(var/datum/plant_gene/gene in genes)
var/datum/plant_gene/copied_gene = gene.Copy()
copy_seed.genes += copied_gene
copied_gene.on_new_seed(copy_seed)
copy_seed.reagents_add = reagents_add.Copy() // Faster than grabbing the list from genes.
return copy_seed
/obj/item/seeds/proc/get_gene(typepath)
return (locate(typepath) in genes)
/obj/item/seeds/proc/reagents_from_genes()
reagents_add = list()
for(var/datum/plant_gene/reagent/R in genes)
reagents_add[R.reagent_id] = R.rate
/obj/item/seeds/proc/mutate(lifemut = 2, endmut = 5, productmut = 1, yieldmut = 2, potmut = 25, wrmut = 2, wcmut = 5, traitmut = 0, stabmut = 3)
adjust_lifespan(rand(-lifemut,lifemut))
adjust_endurance(rand(-endmut,endmut))
adjust_production(rand(-productmut,productmut))
adjust_yield(rand(-yieldmut,yieldmut))
adjust_potency(rand(-potmut,potmut))
adjust_instability(rand(-stabmut,stabmut))
adjust_weed_rate(rand(-wrmut, wrmut))
adjust_weed_chance(rand(-wcmut, wcmut))
if(prob(traitmut))
if(prob(50))
add_random_traits(1, 1)
else
add_random_reagents(1, 1)
/obj/item/seeds/bullet_act(obj/projectile/proj) //Works with the Somatoray to modify plant variables.
if(!istype(proj, /obj/projectile/energy/flora/yield))
return ..()
var/rating = 1
if(istype(loc, /obj/machinery/hydroponics))
var/obj/machinery/hydroponics/H = loc
rating = H.rating
if(yield == 0)//Oh god don't divide by zero you'll doom us all.
adjust_yield(1 * rating)
else if(prob(1/(yield * yield) * 100))//This formula gives you diminishing returns based on yield. 100% with 1 yield, decreasing to 25%, 11%, 6, 4, 2...
adjust_yield(1 * rating)
return BULLET_ACT_HIT
// Harvest procs
/obj/item/seeds/proc/getYield()
var/return_yield = yield
for(var/datum/plant_gene/trait/trait in genes)
if(trait.trait_flags & TRAIT_NO_POLLINATION)
return return_yield
var/obj/machinery/hydroponics/parent = loc
if(istype(loc, /obj/machinery/hydroponics))
if(parent.yieldmod == 0)
return_yield = min(return_yield, 1)//1 if above zero, 0 otherwise
else
return_yield *= (parent.yieldmod)
return return_yield
/obj/item/seeds/proc/harvest(mob/user)
///Reference to the tray/soil the seeds are planted in.
var/obj/machinery/hydroponics/parent = loc //for ease of access
///Count used for creating the correct amount of results to the harvest.
var/t_amount = 0
///List of plants all harvested from the same batch.
var/list/result = list()
///Tile of the harvester to deposit the growables.
var/output_loc = parent.Adjacent(user) ? user.drop_location() : parent.drop_location() //needed for TK
///Name of the grown products.
var/product_name
///The Number of products produced by the plant, typically the yield. Modified by certain traits.
var/product_count = getYield()
while(t_amount < product_count)
var/obj/item/food/grown/t_prod
if(instability >= 30 && (seed_flags & MUTATE_EARLY) && LAZYLEN(mutatelist) && prob(instability/3))
var/obj/item/seeds/mutated_seed = pick(mutatelist)
t_prod = initial(mutated_seed.product)
if(!t_prod)
continue
mutated_seed = new mutated_seed
for(var/datum/plant_gene/trait/trait in parent.myseed.genes)
if((trait.mutability_flags & PLANT_GENE_MUTATABLE) && trait.can_add(mutated_seed))
mutated_seed.genes += trait.Copy()
t_prod = new t_prod(output_loc, new_seed = mutated_seed)
t_prod.transform = initial(t_prod.transform)
t_prod.transform *= TRANSFORM_USING_VARIABLE(t_prod.seed.potency, 100) + 0.5
ADD_TRAIT(t_prod, TRAIT_PLANT_WILDMUTATE, INNATE_TRAIT)
t_amount++
if(t_prod.seed)
t_prod.seed.set_instability(round(instability * 0.5))
continue
else
t_prod = new product(output_loc, new_seed = src)
if(parent.myseed.plantname != initial(parent.myseed.plantname))
t_prod.name = LOWER_TEXT(parent.myseed.plantname)
if(productdesc)
t_prod.desc = productdesc
t_prod.seed.name = parent.myseed.name
t_prod.seed.desc = parent.myseed.desc
t_prod.seed.plantname = parent.myseed.plantname
result.Add(t_prod) // User gets a consumable
if(!t_prod)
return
t_amount++
product_name = parent.myseed.plantname
if(product_count >= 1)
SSblackbox.record_feedback("tally", "food_harvested", product_count, product_name)
parent.update_tray(user, product_count)
return result
/**
* This is where plant chemical products are handled.
*
* Individually, the formula for individual amounts of chemicals is Potency * the chemical production %, rounded to the fullest 1.
* Specific chem handling is also handled here, like bloodtype, food taste within nutriment, and the auto-distilling/autojuicing traits.
* This is where chemical reactions can occur, and the heating / cooling traits effect the reagent container.
*/
/obj/item/seeds/proc/prepare_result(obj/item/T)
if(!T.reagents)
CRASH("[T] has no reagents.")
var/reagent_max = 0
for(var/rid in reagents_add)
reagent_max += reagents_add[rid]
if(IS_EDIBLE(T) || istype(T, /obj/item/grown))
var/obj/item/food/grown/grown_edible = T
var/reagent_purity = get_reagent_purity()
for(var/rid in reagents_add)
var/reagent_overflow_mod = reagents_add[rid]
if(reagent_max > 1)
reagent_overflow_mod = (reagents_add[rid]/ reagent_max)
var/edible_vol = grown_edible.reagents ? grown_edible.reagents.maximum_volume : 0
var/amount = max(1, round((edible_vol)*(potency/100) * reagent_overflow_mod, 1)) //the plant will always have at least 1u of each of the reagents in its reagent production traits
var/list/data
if(rid == /datum/reagent/blood) // Hack to make blood in plants always O-
data = list("blood_type" = get_blood_type(BLOOD_TYPE_O_MINUS))
if(istype(grown_edible) && (rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin))
data = grown_edible.tastes // apple tastes of apple.
T.reagents.add_reagent(rid, amount, data, added_purity = reagent_purity)
//Handles the juicing trait, swaps nutriment and vitamins for that species various juices if they exist. Mutually exclusive with distilling.
if(get_gene(/datum/plant_gene/trait/juicing) && grown_edible.juice_typepath)
grown_edible.juice(juicer = FALSE) //we pass FALSE & not null because Byond default args will subtitute it with the default value
else if(get_gene(/datum/plant_gene/trait/brewing))
grown_edible.ferment()
/// The number of nutriments we have inside of our plant, for use in our heating / cooling genes
var/num_nutriment = T.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment)
// Heats up the plant's contents by 25 kelvin per 1 unit of nutriment. Mutually exclusive with cooling.
if(get_gene(/datum/plant_gene/trait/chem_heating))
T.visible_message(span_notice("[T] releases freezing air, consuming its nutriments to heat its contents."))
T.reagents.remove_reagent(/datum/reagent/consumable/nutriment, num_nutriment)
T.reagents.chem_temp = min(1000, (T.reagents.chem_temp + num_nutriment * 25))
T.reagents.handle_reactions()
playsound(T.loc, 'sound/effects/wounds/sizzle2.ogg', 5)
// Cools down the plant's contents by 5 kelvin per 1 unit of nutriment. Mutually exclusive with heating.
else if(get_gene(/datum/plant_gene/trait/chem_cooling))
T.visible_message(span_notice("[T] releases a blast of hot air, consuming its nutriments to cool its contents."))
T.reagents.remove_reagent(/datum/reagent/consumable/nutriment, num_nutriment)
T.reagents.chem_temp = max(3, (T.reagents.chem_temp + num_nutriment * -5))
T.reagents.handle_reactions()
playsound(T.loc, 'sound/effects/space_wind.ogg', 50)
/// Returns reagent purity based on seed stats
/obj/item/seeds/proc/get_reagent_purity()
var/purity_from_lifespan = lifespan / 400 //up to +25% for lifespan
var/purity_from_endurance = endurance / 400 //up to +25% for endurance
var/purity_from_instability = rand(-instability, instability) / 400 //up to +-25% at random for instability
var/result_purity = clamp(0.5 + purity_from_lifespan + purity_from_endurance + purity_from_instability, 0, 1) //50% base + stats
return result_purity
/// Setters procs ///
/**
* Adjusts seed yield up or down according to adjustamt. (Max 10)
*/
/obj/item/seeds/proc/adjust_yield(adjustamt)
if(yield == -1) // Unharvestable shouldn't suddenly turn harvestable
return
var/max_yield = MAX_PLANT_YIELD
var/min_yield = 0
for(var/datum/plant_gene/trait/trait in genes)
if(trait.trait_flags & TRAIT_HALVES_YIELD)
max_yield = round(max_yield/2)
break
if(get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism))
min_yield = FUNGAL_METAB_YIELD_MIN
yield = clamp(yield + adjustamt, min_yield, max_yield)
/**
* Adjusts seed lifespan up or down according to adjustamt. (Max 100)
*/
/obj/item/seeds/proc/adjust_lifespan(adjustamt)
lifespan = clamp(lifespan + adjustamt, 10, MAX_PLANT_LIFESPAN)
/**
* Adjusts seed endurance up or down according to adjustamt. (Max 100)
*/
/obj/item/seeds/proc/adjust_endurance(adjustamt)
endurance = clamp(endurance + adjustamt, MIN_PLANT_ENDURANCE, MAX_PLANT_ENDURANCE)
/**
* Adjusts seed production seed up or down according to adjustamt. (Max 10)
*/
/obj/item/seeds/proc/adjust_production(adjustamt)
if(yield == -1)
return
production = clamp(production + adjustamt, 1, MAX_PLANT_PRODUCTION)
/**
* Adjusts seed potency up or down according to adjustamt. (Max 100)
*/
/obj/item/seeds/proc/adjust_potency(adjustamt)
if(potency == -1)
return
potency = clamp(potency + adjustamt, 0, MAX_PLANT_POTENCY)
/**
* Adjusts seed instability up or down according to adjustamt. (Max 100)
*/
/obj/item/seeds/proc/adjust_instability(adjustamt)
if(instability == -1)
return
instability = clamp(instability + adjustamt, 0, MAX_PLANT_INSTABILITY)
/**
* Adjusts seed weed grwoth speed up or down according to adjustamt. (Max 10)
*/
/obj/item/seeds/proc/adjust_weed_rate(adjustamt)
weed_rate = clamp(weed_rate + adjustamt, 0, MAX_PLANT_WEEDRATE)
/**
* Adjusts seed weed chance up or down according to adjustamt. (Max 67%)
*/
/obj/item/seeds/proc/adjust_weed_chance(adjustamt)
weed_chance = clamp(weed_chance + adjustamt, 0, MAX_PLANT_WEEDCHANCE)
//Directly setting stats
/**
* Sets the plant's yield stat to the value of adjustamt. (Max 10, or 5 with some traits)
*/
/obj/item/seeds/proc/set_yield(adjustamt)
if(yield == -1) // Unharvestable shouldn't suddenly turn harvestable
return
var/max_yield = MAX_PLANT_YIELD
var/min_yield = 0
for(var/datum/plant_gene/trait/trait in genes)
if(trait.trait_flags & TRAIT_HALVES_YIELD)
max_yield = round(max_yield/2)
break
if(get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism))
min_yield = FUNGAL_METAB_YIELD_MIN
yield = clamp(adjustamt, min_yield, max_yield)
/**
* Sets the plant's lifespan stat to the value of adjustamt. (Max 100)
*/
/obj/item/seeds/proc/set_lifespan(adjustamt)
lifespan = clamp(adjustamt, 10, MAX_PLANT_LIFESPAN)
/**
* Sets the plant's endurance stat to the value of adjustamt. (Max 100)
*/
/obj/item/seeds/proc/set_endurance(adjustamt)
endurance = clamp(adjustamt, MIN_PLANT_ENDURANCE, MAX_PLANT_ENDURANCE)
/**
* Sets the plant's production stat to the value of adjustamt. (Max 10)
*/
/obj/item/seeds/proc/set_production(adjustamt)
if(yield == -1)
return
production = clamp(adjustamt, 1, MAX_PLANT_PRODUCTION)
/**
* Sets the plant's potency stat to the value of adjustamt. (Max 100)
*/
/obj/item/seeds/proc/set_potency(adjustamt)
if(potency == -1)
return
potency = clamp(adjustamt, 0, MAX_PLANT_POTENCY)
/**
* Sets the plant's instability stat to the value of adjustamt. (Max 100)
*/
/obj/item/seeds/proc/set_instability(adjustamt)
if(instability == -1)
return
instability = clamp(adjustamt, 0, MAX_PLANT_INSTABILITY)
/**
* Sets the plant's weed production rate to the value of adjustamt. (Max 10)
*/
/obj/item/seeds/proc/set_weed_rate(adjustamt)
weed_rate = clamp(adjustamt, 0, MAX_PLANT_WEEDRATE)
/**
* Sets the plant's weed growth percentage to the value of adjustamt. (Max 67%)
*/
/obj/item/seeds/proc/set_weed_chance(adjustamt)
weed_chance = clamp(adjustamt, 0, MAX_PLANT_WEEDCHANCE)
/**
* Override for seeds with unique text for their analyzer. (No newlines at the start or end of unique text!)
* Returns null if no unique data
* Return an assoc list (label = text) to add a new line to the analyzer
* Return an assoc list (label = list(text = tooltip, text = tooltip)) to add a new collapsible section to the analyzer
*/
/obj/item/seeds/proc/get_unique_analyzer_data()
return null
/**
* Override for seeds with special chem reactions.
*/
/obj/item/seeds/proc/on_chem_reaction(datum/reagents/reagents)
return
/obj/item/seeds/attackby(obj/item/O, mob/user, list/modifiers, list/attack_modifiers)
if(IS_WRITING_UTENSIL(O))
var/choice = tgui_input_list(usr, "What would you like to change?", "Seed Alteration", list("Plant Name", "Seed Description", "Product Description"))
if(isnull(choice))
return
if(!user.can_perform_action(src))
return
switch(choice)
if("Plant Name")
var/newplantname = reject_bad_text(tgui_input_text(user, "Write a new plant name", "Plant Name", plantname, max_length = MAX_NAME_LEN))
if(isnull(newplantname))
return
if(!user.can_perform_action(src))
return
name = "[LOWER_TEXT(newplantname)]"
plantname = newplantname
if("Seed Description")
var/newdesc = tgui_input_text(user, "Write a new seed description", "Seed Description", desc, max_length = MAX_DESC_LEN)
if(isnull(newdesc))
return
if(!user.can_perform_action(src))
return
desc = newdesc
if("Product Description")
if(product && !productdesc)
productdesc = initial(product.desc)
var/newproductdesc = tgui_input_text(user, "Write a new product description", "Product Description", productdesc, max_length = MAX_DESC_LEN)
if(isnull(newproductdesc))
return
if(!user.can_perform_action(src))
return
productdesc = newproductdesc
..() // Fallthrough to item/attackby() so that bags can pick seeds up
/obj/item/seeds/proc/randomize_stats()
set_lifespan(rand(25, 60))
set_endurance(rand(15, 35))
set_production(rand(2, 10))
set_yield(rand(1, 10))
set_potency(rand(10, 35))
set_weed_rate(rand(1, 10))
set_weed_chance(rand(5, 100))
maturation = rand(6, 12)
/obj/item/seeds/proc/add_random_reagents(lower = 0, upper = 2)
var/amount_random_reagents = rand(lower, upper)
for(var/i in 1 to amount_random_reagents)
var/random_amount = rand(4, 15) * 0.01 // this must be multiplied by 0.01, otherwise, it will not properly associate
var/datum/plant_gene/reagent/R = new(get_random_reagent_id(), random_amount)
if(R.can_add(src))
if(!R.try_upgrade_gene(src))
genes += R
else
qdel(R)
reagents_from_genes()
/obj/item/seeds/proc/add_random_traits(lower = 0, upper = 2)
var/amount_random_traits = rand(lower, upper)
for(var/i in 1 to amount_random_traits)
var/random_trait = pick(subtypesof(/datum/plant_gene/trait))
var/datum/plant_gene/trait/picked_random_trait = new random_trait
if((picked_random_trait.mutability_flags & PLANT_GENE_MUTATABLE) && picked_random_trait.can_add(src))
genes += picked_random_trait
else
qdel(picked_random_trait)
/obj/item/seeds/proc/add_random_plant_type(normal_plant_chance = 75)
if(prob(normal_plant_chance))
var/random_plant_type = pick(subtypesof(/datum/plant_gene/trait/plant_type))
var/datum/plant_gene/trait/plant_type/P = new random_plant_type
if(P.can_add(src))
genes += P
else
qdel(P)
/obj/item/seeds/proc/remove_random_reagents(lower = 0, upper = 2)
var/amount_random_reagents = rand(lower, upper)
for(var/i in 1 to amount_random_reagents)
var/datum/reagent/chemical = pick(reagents_add)
qdel(chemical)
/**
* Creates a graft from this plant.
*
* Creates a new graft from this plant.
* Sets the grafts trait to this plants graftable trait.
* Gives the graft a reference to this plant.
* Copies all the relevant stats from this plant to the graft.
* Returns the created graft.
*/
/obj/item/seeds/proc/create_graft()
var/obj/item/graft/snip = new(loc, graft_gene)
snip.parent_name = plantname
snip.name += " ([plantname])"
// Copy over stats so the graft can outlive its parent.
snip.lifespan = lifespan
snip.endurance = endurance
snip.production = production
snip.weed_rate = weed_rate
snip.weed_chance = weed_chance
snip.yield = yield
return snip
/**
* Applies a graft to this plant.
*
* Adds the graft trait to this plant if possible.
* Increases plant stats by 2/3 of the grafts stats to a maximum of 100 (10 for yield).
* Returns TRUE if the graft could apply its trait successfully, FALSE if it fails to apply the trait.
* NOTE even if the graft fails to apply the trait it still adjusts the plant's stats and reagents.
*
* Arguments:
* - [snip][/obj/item/graft]: The graft being used applied to this plant.
*/
/obj/item/seeds/proc/apply_graft(obj/item/graft/snip)
. = TRUE
var/datum/plant_gene/new_trait = snip.stored_trait
if(new_trait?.can_add(src))
genes += new_trait.Copy()
else
. = FALSE
// Adjust stats based on graft stats
set_lifespan(round(max(lifespan, (lifespan + (2/3)*(snip.lifespan - lifespan)))))
set_endurance(round(max(endurance, (endurance + (2/3)*(snip.endurance - endurance)))))
set_production(round(max(production, (production + (2/3)*(snip.production - production)))))
set_weed_rate(round(max(weed_rate, (weed_rate + (2/3)*(snip.weed_rate - weed_rate)))))
set_weed_chance(round(max(weed_chance, (weed_chance+ (2/3)*(snip.weed_chance - weed_chance)))))
set_yield(round(max(yield, (yield + (2/3)*(snip.yield - yield)))))
// Add in any reagents, too.
reagents_from_genes()
return
/*
* Both `/item/food/grown` and `/item/grown` implement a seed variable which tracks
* plant statistics, genes, traits, etc. This proc gets the seed for either grown food or
* grown inedibles and returns it, or returns null if it's not a plant.
*
* Returns an `/obj/item/seeds` ref for grown foods or grown inedibles.
* - returned seed CAN be null in weird cases but in all applications it SHOULD NOT be.
* Returns null if it is not a plant.
*/
/obj/item/proc/get_plant_seed()
return null
/obj/item/food/grown/get_plant_seed()
return seed
/obj/item/grown/get_plant_seed()
return seed
/obj/item/seeds/proc/perform_reagent_pollination(obj/item/seeds/donor)
var/list/datum/plant_gene/reagent/valid_reagents = list()
for(var/datum/plant_gene/reagent/donor_reagent in donor.genes)
var/repeated = FALSE
for(var/datum/plant_gene/reagent/receptor_reagent in genes)
if(donor_reagent.reagent_id == receptor_reagent.reagent_id)
if(receptor_reagent.rate < donor_reagent.rate)
receptor_reagent.rate = donor_reagent.rate
// sucessful pollination/upgrade, we stop here.
reagents_from_genes()
return
else
repeated = TRUE
break
if(!repeated)
valid_reagents += donor_reagent
if(length(valid_reagents))
// pick a valid reagent that our receptor seed don't have and add the gene to it
var/datum/plant_gene/reagent/selected_reagent = pick(valid_reagents)
genes += selected_reagent.Copy()
reagents_from_genes()
/// Returns a mutable appearance to be used as an overlay for the plant in hydro trays.
/obj/item/seeds/proc/get_tray_overlay(age, status)
var/mutable_appearance/plant_overlay = mutable_appearance(growing_icon, layer = OBJ_LAYER + 0.01)
switch(status)
if(HYDROTRAY_PLANT_DEAD)
plant_overlay.icon_state = icon_dead
if(HYDROTRAY_PLANT_HARVESTABLE)
plant_overlay.icon_state = icon_harvest || "[icon_grow][growthstages]"
else
var/t_growthstate = clamp(round((age / maturation) * growthstages), 1, growthstages)
plant_overlay.icon_state = "[icon_grow][t_growthstate]"
plant_overlay.pixel_z = plant_icon_offset
return plant_overlay