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
/🆑
This commit is contained in:
MrMelbert
2025-09-02 18:57:41 -07:00
committed by GitHub
parent 0001c4a5eb
commit d2b92afe55
12 changed files with 857 additions and 269 deletions
@@ -1,3 +1,6 @@
#define PLANT_ANALYZER_STAT_TAB 1
#define PLANT_ANALYZER_CHEM_TAB 2
/obj/item/plant_analyzer
name = "plant analyzer"
desc = "A scanner used to evaluate a plant's various areas of growth, genetic traits and chemicals."
@@ -12,13 +15,21 @@
custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*0.3, /datum/material/glass =SMALL_MATERIAL_AMOUNT*0.2)
/// Cached data from ui_interact
var/list/last_scan_data
/// Weakref to the last thing we scanned
var/datum/weakref/last_tray_scanned
/// Cached data for the product grinder results
var/static/list/product_grinder_results = list()
/// If TRUE the UI opens to the second tab / the chem tab
var/shown_tab = PLANT_ANALYZER_STAT_TAB
/obj/item/plant_analyzer/Initialize(mapload)
. = ..()
register_item_context()
/obj/item/plant_analyzer/Destroy()
STOP_PROCESSING(SSobj, src)
return ..()
/obj/item/plant_analyzer/add_item_context(
obj/item/source,
list/context,
@@ -63,6 +74,7 @@
return ITEM_INTERACT_SUCCESS
return ITEM_INTERACT_BLOCKING
shown_tab = PLANT_ANALYZER_STAT_TAB
return analyze(user, interacting_with)
/// Same as above, but with right click. Right-clicking scans for chemicals.
@@ -86,6 +98,7 @@
return ITEM_INTERACT_SUCCESS
return ITEM_INTERACT_BLOCKING
shown_tab = PLANT_ANALYZER_CHEM_TAB
return analyze(user, scan_target)
/*
@@ -123,8 +136,22 @@
ui = new(user, src, "PlantAnalyzer", "Plant Analyzer")
ui.open()
/obj/item/plant_analyzer/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
switch(action)
if("setTab")
var/tab = params["tab"]
if(tab == PLANT_ANALYZER_STAT_TAB || tab == PLANT_ANALYZER_CHEM_TAB)
shown_tab = tab
return TRUE
/obj/item/plant_analyzer/ui_data(mob/user)
return last_scan_data
var/list/data = list()
data["active_tab"] = shown_tab
data += last_scan_data
return data
/obj/item/plant_analyzer/ui_static_data(mob/user)
var/list/data = list()
@@ -140,13 +167,22 @@
data["trait_db"] += trait_data
return data
/obj/item/plant_analyzer/process(seconds_per_tick)
var/atom/real_last_tray_scanned = last_tray_scanned?.resolve()
if(QDELETED(real_last_tray_scanned))
return PROCESS_KILL
if(loc.Adjacent(real_last_tray_scanned))
analyze(null, real_last_tray_scanned)
/// Called when our analyzer is used on something
/obj/item/plant_analyzer/proc/analyze(mob/user, atom/target)
var/obj/item/graft/graft
var/obj/item/seeds/seed
var/obj/machinery/hydroponics/tray
playsound(src, SFX_INDUSTRIAL_SCAN, 20, TRUE, -2, TRUE, FALSE)
if(user)
playsound(src, SFX_INDUSTRIAL_SCAN, 20, TRUE, -2, TRUE, FALSE)
if (istype(target, /obj/machinery/hydroponics))
tray = target
@@ -166,12 +202,16 @@
if (!seed && !tray && !graft)
return NONE
if(!user.can_read(src))
return ITEM_INTERACT_BLOCKING
if(user)
if(!user.can_read(src))
return ITEM_INTERACT_BLOCKING
START_PROCESSING(SSobj, src)
last_tray_scanned = WEAKREF(tray) // sets it to null if no tray
last_scan_data = list(
"tray_data" = null,
"seed_data" = null,
"plant_data" = null,
"graft_data" = null,
)
@@ -179,6 +219,7 @@
last_scan_data["tray_data"] = list(
"plant_health" = tray.plant_health,
"plant_age" = tray.age,
"is_dead" = tray.plant_status == HYDROTRAY_PLANT_DEAD,
"name" = tray.name,
"icon" = tray.icon,
"icon_state" = tray.icon_state,
@@ -198,15 +239,27 @@
"toxins_max" = MAX_TRAY_TOXINS,
"reagents" = list(),
)
for(var/datum/reagent/reagent in tray.reagents.reagent_list)
for(var/datum/reagent/reagent as anything in tray.reagents.reagent_list)
last_scan_data["tray_data"]["reagents"] += list(list(
"name" = reagent.name,
"volume" = reagent.volume
"volume" = round(reagent.volume, CHEMICAL_QUANTISATION_LEVEL),
"color" = reagent.color,
))
if(seed)
last_scan_data["seed_data"] = make_seed_data(seed)
if(isitem(target) && target.reagents)
last_scan_data["plant_data"] = list(
"reagents" = list(),
)
for(var/datum/reagent/reagent as anything in target.reagents.reagent_list)
last_scan_data["plant_data"]["reagents"] += list(list(
"name" = reagent.name,
"volume" = round(reagent.volume, CHEMICAL_QUANTISATION_LEVEL),
"color" = reagent.color,
))
if(graft)
last_scan_data["graft_data"] = list(
"name" = graft.parent_name,
@@ -218,10 +271,11 @@
"endurance" = graft.endurance,
"weed_rate" = graft.weed_rate,
"weed_chance" = graft.weed_chance,
"graft_gene" = graft.stored_trait.type
"graft_gene" = graft.stored_trait.type,
)
ui_interact(user)
if(user)
ui_interact(user)
return ITEM_INTERACT_SUCCESS
@@ -280,4 +334,15 @@
seed_data["juice_name"] = product_grinder_results[seed.product]["juice_name"]
seed_data["grind_results"] = product_grinder_results[seed.product]["grind_results"]
seed_data["unique_labels"] = list()
seed_data["unique_collapsibles"] = list()
var/list/unique_data = seed.get_unique_analyzer_data()
for(var/label in unique_data)
seed_data[islist(unique_data[label]) ? "unique_collapsibles" : "unique_labels"] += list(list(
"label" = label,
"data" = unique_data[label],
))
return seed_data
#undef PLANT_ANALYZER_STAT_TAB
#undef PLANT_ANALYZER_CHEM_TAB