Xenobotany device NanoUI conversions (#21856)

changes:
- refactor: "Converts xenobotany's Bioballistic Delivery System
interface from NanoUI to TGUI."
- refactor: "Converts xenobotany's Lysis-Isolation Centrifuge interface
from NanoUI to TGUI."
<img width="949" height="532" alt="image"
src="https://github.com/user-attachments/assets/91930b83-1a2d-4ecb-801d-153b1ae52ed1"
/>
This commit is contained in:
Batrachophreno
2026-02-22 12:22:31 +00:00
committed by GitHub
parent 6def662c56
commit cb2718db78
4 changed files with 400 additions and 148 deletions
+150 -148
View File
@@ -5,7 +5,9 @@
icon_state = "disk"
w_class = WEIGHT_CLASS_TINY
/// The current gene data on the disk.
var/list/genes = list()
/// The player-facing name of the gene's source.
var/genesource = "unknown"
/obj/item/disk/botany/New()
@@ -35,22 +37,28 @@
/obj/machinery/botany
icon = 'icons/obj/hydroponics_machines.dmi'
icon_state = "hydrotray3"
density = 1
anchored = 1
density = TRUE
anchored = TRUE
var/obj/item/seeds/seed // Currently loaded seed packet.
var/obj/item/disk/botany/loaded_disk //Currently loaded data disk.
var/open = 0
var/active = 0
/// Currently loaded seed packet.
var/obj/item/seeds/seed
/// Currently loaded data disk.
var/obj/item/disk/botany/loaded_disk
/// Is the machine current performing a task?
var/active = FALSE
/// Duration the task requires for completion.
var/action_time = 5
/// World time when the last action was queued.
var/last_action = 0
var/eject_disk = 0
var/failed_task = 0
var/disk_needs_genes = 0
/// Whether the currently queued task should eject the current disk, if present.
var/eject_disk = FALSE
/// Whether the task has failed or not.
var/failed_task = FALSE
/// Helps determine whether or not a given operation requires a disk to be loaded with genes or to be empty.
var/disk_needs_genes = FALSE
/// Gets run every process tick.
/obj/machinery/botany/process()
..()
if(!active) return
@@ -95,11 +103,11 @@
return
if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER)
open = !open
to_chat(user, SPAN_NOTICE("You [open ? "open" : "close"] the maintenance panel."))
panel_open = !panel_open
to_chat(user, SPAN_NOTICE("You [panel_open ? "open" : "close"] the maintenance panel."))
return
if(open)
if(panel_open)
if(attacking_item.tool_behaviour == TOOL_CROWBAR)
dismantle()
return
@@ -127,7 +135,52 @@
return
..()
// Allows for a trait to be extracted from a seed packet, destroying that seed.
/// UI data shared between both editor and extractor machinery.
/obj/machinery/botany/ui_data(mob/user)
var/list/data = list()
data["activity"] = active
data["loadedSeed"] = seed ? "[seed.name]" : null
data["disk"] = loaded_disk ? TRUE : FALSE
return data
/// UI action behavior shared between both editor and extractor machinery.
/obj/machinery/botany/ui_act(action, params)
. = ..()
if(.)
return
switch(action)
if("eject_packet")
if(!seed)
return FALSE
seed.forceMove(get_turf(src))
if(seed.seed.name == "new line" || isnull(SSplants.seeds[seed.seed.name]))
seed.seed.uid = SSplants.seeds.len + 1
seed.seed.name = "[seed.seed.uid]"
SSplants.seeds[seed.seed.name] = seed.seed
seed.update_seed()
visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps and spits out [seed].")
seed = null
if("eject_disk")
if(!loaded_disk)
return FALSE
loaded_disk.forceMove(get_turf(src))
visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps and spits out [loaded_disk].")
loaded_disk = null
else
return FALSE
return TRUE
/// Allows for a trait to be extracted from a seed packet, destroying that seed.
/obj/machinery/botany/extractor
name = "lysis-isolation centrifuge"
icon_state = "centrifuge"
@@ -136,194 +189,142 @@
/obj/item/stock_parts/manipulator = 3,
/obj/item/stock_parts/scanning_module = 1
)
/// Currently scanned seed genetic structure.
var/datum/seed/genetics
/// Increments with each scan, stops allowing gene mods after a certain point.
var/degradation = 0
var/datum/seed/genetics // Currently scanned seed genetic structure.
var/degradation = 0 // Increments with each scan, stops allowing gene mods after a certain point.
/obj/machinery/botany/extractor/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
/obj/machinery/botany/extractor/ui_interact(mob/user, datum/tgui/ui)
if(!user)
return
var/list/data = list()
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "BotanyIsolator", "Lysis-Isolation Centrifuge")
ui.open()
var/list/geneMasks = SSplants.gene_masked_list
data["geneMasks"] = geneMasks
/obj/machinery/botany/extractor/ui_data(mob/user)
var/list/data = ..()
data["activity"] = active
data["degradation"] = degradation
if(loaded_disk)
data["disk"] = 1
else
data["disk"] = 0
if(seed)
data["loaded"] = "[seed.name]"
else
data["loaded"] = 0
data["geneMasks"] = SSplants.gene_masked_list
if(genetics)
data["hasGenetics"] = 1
data["hasGeneticsData"] = TRUE
data["sourceName"] = genetics.display_name
if(!genetics.roundstart)
data["sourceName"] += " (variety #[genetics.uid])"
else
data["hasGenetics"] = 0
data["sourceName"] = 0
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "botany_isolator.tmpl", "Lysis-isolation Centrifuge UI", 470, 450)
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
data["loadedseed"] = seed ? "[seed.name]" : "Unknown"
data["disk"] = loaded_disk ? TRUE : FALSE
/obj/machinery/botany/Topic(href, href_list)
return data
if(..())
return 1
/obj/machinery/botany/extractor/ui_act(action, params)
switch(action)
if("scan_genome")
if(!seed)
return FALSE
if(href_list["eject_packet"])
if(!seed) return
seed.forceMove(get_turf(src))
last_action = world.time
active = TRUE
if(seed.seed.name == "new line" || isnull(SSplants.seeds[seed.seed.name]))
seed.seed.uid = SSplants.seeds.len + 1
seed.seed.name = "[seed.seed.uid]"
SSplants.seeds[seed.seed.name] = seed.seed
if(seed && seed.seed)
genetics = seed.seed
degradation = 0
seed.update_seed()
visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps and spits out [seed].")
qdel(seed)
seed = null
seed = null
return TRUE
if(href_list["eject_disk"])
if(!loaded_disk) return
loaded_disk.forceMove(get_turf(src))
visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps and spits out [loaded_disk].")
loaded_disk = null
if("get_gene")
if(!genetics || !loaded_disk)
return TRUE
usr.set_machine(src)
src.add_fingerprint(usr)
var/gene_tag = params["gene"]
if(!gene_tag)
return TRUE
/obj/machinery/botany/extractor/Topic(href, href_list)
last_action = world.time
active = TRUE
if(..())
return 1
var/datum/plantgene/P = genetics.get_gene(gene_tag)
if(!P)
return TRUE
usr.set_machine(src)
src.add_fingerprint(usr)
loaded_disk.genes += P
if(href_list["scan_genome"])
loaded_disk.genesource = "[genetics.display_name]"
if(!genetics.roundstart)
loaded_disk.genesource += " (variety #[genetics.uid])"
if(!seed) return
loaded_disk.name += " ([SSplants.gene_tag_masks[gene_tag]], #[genetics.uid])"
loaded_disk.desc += " The label reads 'gene [SSplants.gene_tag_masks[gene_tag]], sampled from [genetics.display_name]'."
eject_disk = TRUE
last_action = world.time
active = 1
degradation += rand(20,60)
if(degradation >= 100)
failed_task = TRUE
genetics = null
degradation = 0
return TRUE
if(seed && seed.seed)
genetics = seed.seed
degradation = 0
qdel(seed)
seed = null
if(href_list["get_gene"])
if(!genetics || !loaded_disk) return
last_action = world.time
active = 1
var/datum/plantgene/P = genetics.get_gene(href_list["get_gene"])
if(!P) return
loaded_disk.genes += P
loaded_disk.genesource = "[genetics.display_name]"
if(!genetics.roundstart)
loaded_disk.genesource += " (variety #[genetics.uid])"
loaded_disk.name += " ([SSplants.gene_tag_masks[href_list["get_gene"]]], #[genetics.uid])"
loaded_disk.desc += " The label reads \'gene [SSplants.gene_tag_masks[href_list["get_gene"]]], sampled from [genetics.display_name]\'."
eject_disk = 1
degradation += rand(20,60)
if(degradation >= 100)
failed_task = 1
if("clear_buffer")
if(!genetics)
return
genetics = null
degradation = 0
return TRUE
if(href_list["clear_buffer"])
if(!genetics) return
genetics = null
degradation = 0
return ..()
src.updateUsrDialog()
return
// Fires an extracted trait into another packet of seeds with a chance
// of destroying it based on the size/complexity of the plasmid.
/// Injects an extracted trait into another packet of seeds with a chance of destroying it based on the size/complexity of the plasmid.
/obj/machinery/botany/editor
name = "bioballistic delivery system"
icon_state = "traitgun"
disk_needs_genes = 1
disk_needs_genes = TRUE
component_types = list(
/obj/item/circuitboard/botany_editor,
/obj/item/stock_parts/manipulator = 3,
/obj/item/stock_parts/scanning_module = 1
)
/obj/machinery/botany/editor/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
/obj/machinery/botany/editor/ui_interact(mob/user, datum/tgui/ui)
if(!user)
return
var/list/data = list()
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "BotanyEditor", "Bioballistic Delivery System")
ui.open()
data["activity"] = active
/obj/machinery/botany/editor/ui_data(mob/user)
var/list/data = ..()
if(seed)
data["degradation"] = seed.modified
else
data["degradation"] = 0
data["degradation"] = seed ? seed.modified : 0
data["disk"] = !!loaded_disk
if(loaded_disk && loaded_disk.genes.len)
data["disk"] = 1
data["sourceName"] = loaded_disk.genesource
data["locus"] = ""
for(var/datum/plantgene/P in loaded_disk.genes)
if(data["locus"] != "") data["locus"] += ", "
if(data["locus"] != "")
data["locus"] += ", "
data["locus"] += "[SSplants.gene_tag_masks[P.genetype]]"
else
data["disk"] = 0
data["sourceName"] = 0
data["locus"] = 0
data["loadedseed"] = seed ? "[seed.name]" : null
if(seed)
data["loaded"] = "[seed.name]"
else
data["loaded"] = 0
return data
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "botany_editor.tmpl", "Bioballistic Delivery UI", 470, 450)
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
/obj/machinery/botany/editor/Topic(href, href_list)
if(..())
return 1
if(href_list["apply_gene"])
if(!loaded_disk || !seed) return
/obj/machinery/botany/editor/ui_act(action, params)
if(action == "apply_gene")
if(!loaded_disk || !seed)
return TRUE
last_action = world.time
active = 1
active = TRUE
if(!isnull(SSplants.seeds[seed.seed.name]))
seed.seed = seed.seed.diverge(1)
@@ -331,15 +332,16 @@
seed.update_seed()
if(prob(seed.modified))
failed_task = 1
failed_task = TRUE
seed.modified = 101
for(var/datum/plantgene/gene in loaded_disk.genes)
seed.seed.apply_gene(gene)
seed.modified += rand(5,10)
usr.set_machine(src)
src.add_fingerprint(usr)
return TRUE
return ..()
/obj/item/circuitboard/botany_extractor
name = T_BOARD("lysis-isolation centrifuge")
+14
View File
@@ -0,0 +1,14 @@
# Your name.
author: Batrachophrenoboocosmomachia
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit.
# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog.
changes:
- refactor: "Converts xenobotany's Bioballistic Delivery System interface from NanoUI to TGUI."
- refactor: "Converts xenobotany's Lysis-Isolation Centrifuge interface from NanoUI to TGUI."
@@ -0,0 +1,98 @@
// botany_editor.tsx
import { Window } from '../layouts';
import { BooleanLike } from '../../common/react';
import { useBackend } from '../backend';
import { Box, Button, LabeledList, NoticeBox, Section } from '../components';
type Data = {
activity: BooleanLike;
degradation: number;
disk: BooleanLike;
sourceName: string;
locus: string;
loadedseed: string;
};
export const BotanyEditor = (_props, context) => {
const { act, data } = useBackend<Data>(context);
const { activity, degradation, disk, sourceName, locus, loadedseed } = data;
const hasLoadedSeed = !!loadedseed;
const viable = degradation <= 100;
return (
<Window width={470} height={450} title="Bioballistic Delivery System">
<Window.Content scrollable>
{activity ? (
<NoticeBox>Bioballistics systems engaged...</NoticeBox>
) : (
<Box>
<Section title="Buffered Genetic Data">
{disk ? (
<Box>
<LabeledList>
<LabeledList.Item label="Source">
{String(sourceName)}
</LabeledList.Item>
<LabeledList.Item label="Gene decay">
{viable ? (
`${degradation}%`
) : (
<Box color="bad">
<b>FURTHER AMENDMENTS NONVIABLE</b>
</Box>
)}
</LabeledList.Item>
<LabeledList.Item label="Locus">
{String(locus)}
</LabeledList.Item>
</LabeledList>
<Button
icon="eject"
content="Eject Disk"
onClick={() => act('eject_disk')}
/>
</Box>
) : (
<NoticeBox>No disk loaded.</NoticeBox>
)}
</Section>
<Section title="Loaded Material">
{hasLoadedSeed ? (
<Box>
<LabeledList>
<LabeledList.Item label="Target">
{loadedseed}
</LabeledList.Item>
</LabeledList>
{viable && (
<Button
icon="cog"
content="Apply Gene Mods"
mr={1}
onClick={() => act('apply_gene')}
/>
)}
<Button
icon="eject"
disable={!hasLoadedSeed}
content="Eject Target"
onClick={() => act('eject_packet')}
/>
</Box>
) : (
<NoticeBox>No target seed packet loaded.</NoticeBox>
)}
</Section>
</Box>
)}
</Window.Content>
</Window>
);
};
export default BotanyEditor;
@@ -0,0 +1,138 @@
// botany_isolator.tsx
import { useBackend } from '../backend';
import { Box, Button, LabeledList, NoticeBox, Section } from '../components';
import { Window } from '../layouts';
import { BooleanLike } from '../../common/react';
type GeneMask = {
mask: string;
tag: string;
};
type Data = {
activity: BooleanLike;
degradation: number;
disk: BooleanLike;
loadedSeed: string;
sourceName: string;
HasGeneticsData: BooleanLike;
geneMasks: GeneMask[] | null;
};
export const BotanyIsolator = (_props, context) => {
const { act, data } = useBackend<Data>(context);
const { activity, degradation, disk, loadedSeed, sourceName, geneMasks } =
data;
const hasDisk = !!disk;
const hasLoadedSeed = !!loadedSeed;
const masks = Array.isArray(geneMasks) ? geneMasks : [];
return (
<Window width={470} height={450} title="Lysis-Isolation Centrifuge">
<Window.Content scrollable>
{activity ? (
<NoticeBox>Scanning...</NoticeBox>
) : (
<Box>
<Section title="Buffered Genetic Data">
{hasLoadedSeed ? (
<Box>
<LabeledList>
<LabeledList.Item label="Source">
{String(sourceName)}
</LabeledList.Item>
<LabeledList.Item label="Gene decay">
{degradation}%
</LabeledList.Item>
</LabeledList>
{hasDisk ? (
<Box mt={1}>
{masks.length > 0 ? (
<Box>
{masks.map((entry) => (
<Box key={entry.tag} mb={0.5}>
<Button
content={`Extract ${entry.mask}`}
icon="arrow-down"
onClick={() =>
act('get_gene', { gene: entry.tag })
}
/>
</Box>
))}
</Box>
) : (
<NoticeBox>
Data error. Genetic record corrupt.
</NoticeBox>
)}
<Box mt={1}>
<Button
icon="eject"
content="Eject Loaded Disk"
mr={1}
onClick={() => act('eject_disk')}
/>
<Button
icon="trash"
content="Clear Genetic Buffer"
onClick={() => act('clear_buffer')}
/>
</Box>
</Box>
) : (
<NoticeBox>No disk inserted.</NoticeBox>
)}
</Box>
) : (
<Box>
<NoticeBox>No data buffered.</NoticeBox>
{hasDisk && (
<Box mt={1}>
<Button
icon="eject"
content="Eject Loaded Disk"
onClick={() => act('eject_disk')}
/>
</Box>
)}
</Box>
)}
</Section>
<Section title="Loaded Material">
{hasLoadedSeed ? (
<Box>
<LabeledList>
<LabeledList.Item label="Target">
{loadedSeed}
</LabeledList.Item>
</LabeledList>
<Button
icon="search"
content="Buffer Genome"
mr={1}
onClick={() => act('scan_genome')}
/>
<Button
icon="eject"
content="Eject Target"
onClick={() => act('eject_packet')}
/>
</Box>
) : (
<NoticeBox>No seed packet loaded.</NoticeBox>
)}
</Section>
</Box>
)}
</Window.Content>
</Window>
);
};
export default BotanyIsolator;