diff --git a/code/modules/detectivework/microscope/dnascanner.dm b/code/modules/detectivework/microscope/dnascanner.dm
index 70c1e4fc7e2..d2eaa4251ae 100644
--- a/code/modules/detectivework/microscope/dnascanner.dm
+++ b/code/modules/detectivework/microscope/dnascanner.dm
@@ -4,85 +4,111 @@
desc = "A high tech machine that is designed to read DNA samples properly."
icon = 'icons/obj/forensics.dmi'
icon_state = "dnaopen"
- anchored = 1
- density = 1
+ anchored = TRUE
+ density = TRUE
var/obj/item/forensics/swab/bloodsamp = null
- var/closed = 0
- var/scanning = 0
+ var/closed = FALSE
+ var/scanning = FALSE
var/scanner_progress = 0
var/scanner_rate = 2.50
var/last_process_worldtime = 0
var/report_num = 0
/obj/machinery/dnaforensics/attackby(obj/item/attacking_item, mob/user)
-
- if(bloodsamp)
- to_chat(user, SPAN_WARNING("There is already a sample in the machine."))
- return
-
- if(closed)
- to_chat(user, SPAN_WARNING("Open the cover before inserting the sample."))
- return
-
var/obj/item/forensics/swab/swab = attacking_item
- if(istype(swab) && swab.is_used())
- user.unEquip(attacking_item)
- src.bloodsamp = swab
- swab.forceMove(src)
- to_chat(user, SPAN_NOTICE("You insert \the [attacking_item] into \the [src]."))
- else
- to_chat(user, SPAN_WARNING("\The [src] only accepts used swabs."))
- return
+ // General intended use (w swabs)
+ if(istype(swab))
+ if(swab.is_used())
+ // Already a sample loaded
+ if(bloodsamp)
+ to_chat(user, SPAN_WARNING("There is already a sample in the machine."))
+ return FALSE
+ // Open the lid, dummy
+ if(closed)
+ to_chat(user, SPAN_WARNING("Open the cover before inserting the sample."))
+ return FALSE
+ // Insert the sample
+ else
+ user.unEquip(attacking_item)
+ src.bloodsamp = swab
+ swab.forceMove(src)
+ to_chat(user, SPAN_NOTICE("You insert \the [attacking_item] into \the [src]."))
+ return TRUE
+ // Used swabs only
+ else
+ to_chat(user, SPAN_WARNING("\The [src] only accepts used swabs."))
+ return FALSE
-/obj/machinery/dnaforensics/ui_interact(mob/user, ui_key = "main",var/datum/nanoui/ui = null)
- if(stat & (NOPOWER)) return
- if(user.stat || user.restrained()) return
+ else if(attacking_item.tool_behaviour == TOOL_WRENCH)
+ attacking_item.play_tool_sound(get_turf(src), 100)
+ balloon_alert(user, "[anchored ? "secure" : "unsecure"]")
+ user.visible_message("[user.name] [anchored ? "secures" : "unsecures"] the bolts holding [src.name] to the floor.", \
+ "You [anchored ? "secure" : "unsecure"] the bolts holding [src] to the floor.", \
+ "You hear a ratchet")
+ anchored = !anchored
+ return TRUE
+ return ..()
+
+/obj/machinery/dnaforensics/ui_interact(mob/user, datum/tgui/ui)
+ if(stat & NOPOWER)
+ return
+ if(use_check_and_message(user))
+ return
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "DnaForensics", "QuikScan DNA Analyzer")
+ ui.open()
+ ui.set_autoupdate(TRUE)
+
+/obj/machinery/dnaforensics/ui_data(mob/user)
var/list/data = list()
+
data["scan_progress"] = round(scanner_progress)
data["scanning"] = scanning
data["bloodsamp"] = (bloodsamp ? bloodsamp.name : "")
data["bloodsamp_desc"] = (bloodsamp ? (bloodsamp.desc ? bloodsamp.desc : "No information on record.") : "")
- data["lidstate"] = closed
+ data["lid_closed"] = closed
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data)
- if (!ui)
- ui = new(user, src, ui_key, "dnaforensics.tmpl", "QuikScan DNA Analyzer", 540, 326)
- ui.set_initial_data(data)
- ui.open()
- ui.set_auto_update(1)
+ return data
-/obj/machinery/dnaforensics/Topic(href, href_list)
+/obj/machinery/dnaforensics/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
- if(..()) return 1
+ switch(action)
+ if("scanItem")
+ if(scanning)
+ scanning = FALSE
+ return TRUE
- if(stat & (NOPOWER))
- return 0 // don't update UIs attached to this object
-
- if(href_list["scanItem"])
- if(scanning)
- scanning = 0
- else
- if(bloodsamp)
- if(closed == 1)
- scanner_progress = 0
- scanning = 1
- to_chat(usr, SPAN_NOTICE("Scan initiated."))
- update_icon()
- else
- to_chat(usr, SPAN_NOTICE("Please close sample lid before initiating scan."))
- else
+ if(!bloodsamp)
to_chat(usr, SPAN_WARNING("Insert an item to scan."))
+ return TRUE
- if(href_list["ejectItem"])
- if(bloodsamp)
- bloodsamp.forceMove(src.loc)
- bloodsamp = null
+ if(closed)
+ scanner_progress = 0
+ scanning = TRUE
+ to_chat(usr, SPAN_NOTICE("Scan initiated."))
+ update_icon()
+ return TRUE
- if(href_list["toggleLid"])
- toggle_lid()
+ to_chat(usr, SPAN_NOTICE("Please close sample lid before initiating scan."))
+ return TRUE
- return 1
+ if("ejectItem")
+ if(bloodsamp && !scanning)
+ bloodsamp.forceMove(src.loc)
+ bloodsamp = null
+ return TRUE
+ return FALSE
+
+ if("toggleLid")
+ toggle_lid()
+ return TRUE
+
+ return FALSE
/obj/machinery/dnaforensics/process()
if(scanning)
@@ -108,6 +134,7 @@
P.stamped = list(/obj/item/stamp)
P.overlays = list("paper_stamped")
//dna data itself
+ var/datetime = "[worlddate2text()] [worldtime2text()]"
var/data = "No scan information available."
if(bloodsamp.dna != null)
data = "Spectometric analysis on provided sample has determined the presence of [length(bloodsamp.dna)] string\s of DNA.
"
@@ -117,7 +144,7 @@
data += "DNA: [blood]
"
else
data += "No DNA found.
"
- info = "[src] analysis report #[report_num]
"
+ info = "[src] forensic report #[report_num]
Scanned [datetime]
"
info += "Scanned item: [bloodsamp.name]
" + data
P.set_content_unsafe(pname, info)
print(P, user = usr)
@@ -138,7 +165,7 @@
set name = "Toggle Lid"
set src in oview(1)
- if(usr.stat || !isliving(usr))
+ if(use_check_and_message(usr))
return
if(scanning)
diff --git a/code/modules/overmap/ships/computers/engine_control.dm b/code/modules/overmap/ships/computers/engine_control.dm
index ee74eded8c1..b5b0f707f60 100644
--- a/code/modules/overmap/ships/computers/engine_control.dm
+++ b/code/modules/overmap/ships/computers/engine_control.dm
@@ -10,7 +10,7 @@
var/display_state = "status"
/obj/machinery/computer/ship/engines/cockpit
- density = 0
+ density = FALSE
icon = 'icons/obj/cockpit_console.dmi'
icon_state = "right"
icon_screen = "engine"
@@ -28,91 +28,116 @@
can_pass_under = FALSE
light_power_on = 1
-/obj/machinery/computer/ship/engines/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
+/obj/machinery/computer/ship/engines/ui_interact(mob/user, datum/tgui/ui)
if(!connected)
display_reconnect_dialog(user, "ship control systems")
return
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "EnginesControl", "[connected.get_real_name()] Engines Control")
+ ui.open()
+
+/obj/machinery/computer/ship/engines/ui_data(mob/user)
+ var/list/data = list()
+
+ if(!connected)
+ return
- var/data[0]
data["state"] = display_state
- data["global_state"] = connected.engines_state
- data["global_limit"] = round(connected.thrust_limit*100)
- var/total_thrust = 0
+ data["global_state"] = !!connected.engines_state
+ data["global_limit"] = round(connected.thrust_limit * 100)
+
+ var/total_thrust = 0
+ var/list/enginfo = list()
- var/list/enginfo[0]
for(var/datum/ship_engine/E in connected.engines)
- var/list/rdata[0]
- rdata["eng_type"] = E.name
- rdata["eng_on"] = E.is_on()
- rdata["eng_thrust"] = E.get_thrust()
- rdata["eng_thrust_limiter"] = round(E.get_thrust_limit()*100)
- rdata["eng_status"] = E.get_status()
- rdata["eng_reference"] = "[REF(E)]"
+ var/list/edata = list()
+ edata["eng_type"] = E.name
+ edata["eng_on"] = !!E.is_on()
+ edata["eng_thrust"] = E.get_thrust() * 10
+ edata["eng_thrust_limiter"] = round(E.get_thrust_limit() * 100)
+ edata["eng_status"] = E.get_status()
+ edata["eng_reference"] = "[REF(E)]"
total_thrust += E.get_thrust()
- enginfo.Add(list(rdata))
+ enginfo += list(edata)
data["engines_info"] = enginfo
data["total_thrust"] = total_thrust
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
- if (!ui)
- ui = new(user, src, ui_key, "engines_control.tmpl", "[connected.get_real_name()] Engines Control", 390, 530)
- ui.set_initial_data(data)
- ui.open()
- ui.set_auto_update(1)
+ return data
-/obj/machinery/computer/ship/engines/Topic(href, href_list)
- if(..())
- return TOPIC_HANDLED
+/obj/machinery/computer/ship/engines/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
- if(href_list["state"])
- display_state = href_list["state"]
- return TOPIC_REFRESH
+ if(!connected)
+ return FALSE
- if(href_list["global_toggle"])
- connected.engines_state = !connected.engines_state
- for(var/datum/ship_engine/E in connected.engines)
- if(connected.engines_state == !E.is_on())
- E.toggle()
- return TOPIC_REFRESH
+ if(use_check_and_message(usr))
+ return FALSE
- if(href_list["set_global_limit"])
- var/newlim = tgui_input_number(usr, "Input the new thrust limit.", "Thrust Limit", connected.thrust_limit*100, 100, 0)
- if(!CanInteract(usr, GLOB.physical_state))
- return TOPIC_NOACTION
- connected.thrust_limit = clamp(newlim/100, 0, 1)
- for(var/datum/ship_engine/E in connected.engines)
- E.set_thrust_limit(connected.thrust_limit)
- return TOPIC_REFRESH
+ switch(action)
+ if("set_state")
+ var/new_state = params["state"]
+ if(new_state in list("status", "engines"))
+ display_state = new_state
+ return TRUE
+ return FALSE
- if(href_list["global_limit"])
- connected.thrust_limit = clamp(connected.thrust_limit + text2num(href_list["global_limit"]), 0, 1)
- for(var/datum/ship_engine/E in connected.engines)
- E.set_thrust_limit(connected.thrust_limit)
- return TOPIC_REFRESH
+ if("global_toggle")
+ connected.engines_state = !connected.engines_state
+ for(var/datum/ship_engine/E in connected.engines)
+ if(connected.engines_state == !E.is_on())
+ E.toggle()
+ return TRUE
- if(href_list["engine"])
- if(href_list["set_limit"])
- var/datum/ship_engine/E = locate(href_list["engine"])
- var/newlim = tgui_input_number(usr, "Input the new thrust limit.", "Thrust Limit", E.get_thrust_limit(), 100, 0)
- if(!CanInteract(usr, GLOB.physical_state))
- return
- var/limit = clamp(newlim/100, 0, 1)
- if(istype(E))
- E.set_thrust_limit(limit)
- return TOPIC_REFRESH
- if(href_list["limit"])
- var/datum/ship_engine/E = locate(href_list["engine"])
- var/limit = clamp(E.get_thrust_limit() + text2num(href_list["limit"]), 0, 1)
- if(istype(E))
- E.set_thrust_limit(limit)
- return TOPIC_REFRESH
+ if("set_global_limit")
+ var/newlim = tgui_input_number(usr, "Input the new thrust limit.", "Thrust Limit", connected.thrust_limit * 100, 100, 0)
+ if(isnull(newlim))
+ return FALSE
- if(href_list["toggle"])
- var/datum/ship_engine/E = locate(href_list["engine"])
- if(istype(E))
- E.toggle()
- return TOPIC_REFRESH
- return TOPIC_REFRESH
- return TOPIC_NOACTION
+ connected.thrust_limit = clamp(newlim / 100, 0, 1)
+ for(var/datum/ship_engine/E in connected.engines)
+ E.set_thrust_limit(connected.thrust_limit)
+ return TRUE
+
+ if("global_limit_delta")
+ var/delta = text2num(params["delta"])
+ connected.thrust_limit = round(clamp(connected.thrust_limit + delta, 0, 1),0.1)
+ for(var/datum/ship_engine/E in connected.engines)
+ E.set_thrust_limit(connected.thrust_limit)
+ return TRUE
+
+ if("engine_set_limit")
+ var/datum/ship_engine/E = locate(params["engine"])
+ if(!istype(E))
+ return FALSE
+ var/newlim = tgui_input_number(usr, "Input the new thrust limit.", "Thrust Limit", E.get_thrust_limit() * 100, 100, 0)
+ if(isnull(newlim))
+ return FALSE
+
+ var/limit = clamp(newlim / 100, 0, 1)
+ E.set_thrust_limit(limit)
+ return TRUE
+
+ if("engine_limit_delta")
+ var/datum/ship_engine/E = locate(params["engine"])
+ if(!istype(E))
+ return FALSE
+
+ var/delta = text2num(params["delta"])
+ var/limit = clamp(E.get_thrust_limit() + delta, 0, 1)
+ E.set_thrust_limit(limit)
+ return TRUE
+
+ if("engine_toggle")
+ var/datum/ship_engine/E = locate(params["engine"])
+ if(!istype(E))
+ return FALSE
+
+ E.toggle()
+ return TRUE
+
+ return FALSE
diff --git a/code/modules/overmap/ships/computers/helm.dm b/code/modules/overmap/ships/computers/helm.dm
index a9007f68442..5bf6720203e 100644
--- a/code/modules/overmap/ships/computers/helm.dm
+++ b/code/modules/overmap/ships/computers/helm.dm
@@ -339,8 +339,10 @@
light_power_on = 1
/obj/machinery/computer/ship/navigation/attack_hand(mob/user)
+ if(stat & (NOPOWER|BROKEN))
+ return FALSE
if(use_check_and_message(user))
- return
+ return FALSE
if(!emagged && !allowed(user))
to_chat(user, SPAN_WARNING("Access denied."))
return FALSE
diff --git a/code/modules/overmap/ships/computers/ship.dm b/code/modules/overmap/ships/computers/ship.dm
index c14847be733..27291e47e34 100644
--- a/code/modules/overmap/ships/computers/ship.dm
+++ b/code/modules/overmap/ships/computers/ship.dm
@@ -65,12 +65,14 @@ somewhere on that shuttle. Subtypes of these can be then used to perform ship ov
return ..()
/obj/machinery/computer/ship/attack_hand(mob/user)
+ if(stat & (NOPOWER|BROKEN))
+ return FALSE
// Snowflake case for checking player characters for a Pilot Spacecraft Skill.
// Only player characters will have the component. Which will both always be present on them, and will only enable its own return logic if it exists.
// NPCs, Ghostroles, and Offship Antags that don't generate skills are unaffected by this check by intentional design so that we don't have to account for them.
if (user.GetComponent(PILOT_SPACECRAFT_SKILL_COMPONENT)?.skill_level == SKILL_LEVEL_UNFAMILIAR)
to_chat(user, SPAN_WARNING("There's just so many buttons... You have no idea where to even begin."))
- return
+ return FALSE
if(use_check_and_message(user))
return
diff --git a/code/modules/overmap/ships/engines/engine.dm b/code/modules/overmap/ships/engines/engine.dm
index 3184ca02278..76eb67477d1 100644
--- a/code/modules/overmap/ships/engines/engine.dm
+++ b/code/modules/overmap/ships/engines/engine.dm
@@ -14,13 +14,13 @@ GLOBAL_LIST_INIT_TYPED(ship_engines, /datum/ship_engine, list())
/datum/ship_engine/proc/can_burn()
return 0
-//Tries to fire the engine with power modifier (0..1). Returns thrust.
-//Power modifier is intended to be used for maneuvers or the like,
-//that should use less/more fuel/electricity than an actual burn.
+/// Tries to fire the engine with power modifier (0..1). Returns thrust.
+/// Power modifier is intended to be used for maneuvers or the like,
+/// that should use less/more fuel/electricity than an actual burn.
/datum/ship_engine/proc/burn(var/power_modifier = 1)
return 0
-//Returns status string for this engine
+/// Returns status string for this engine
/datum/ship_engine/proc/get_status()
return "All systems nominal"
diff --git a/code/modules/overmap/ships/engines/gas_thruster.dm b/code/modules/overmap/ships/engines/gas_thruster.dm
index 1ec2c4e43dd..91a662b3cf4 100644
--- a/code/modules/overmap/ships/engines/gas_thruster.dm
+++ b/code/modules/overmap/ships/engines/gas_thruster.dm
@@ -158,20 +158,33 @@
/obj/machinery/atmospherics/unary/engine/proc/get_status()
. = list()
- .+= "Location: [get_area(src)]."
- if(stat & NOPOWER)
- .+= "Insufficient power to operate."
- if(!check_fuel())
- .+= "Insufficient fuel for a burn."
- if(stat & BROKEN)
- .+= "Inoperable engine configuration."
- if(blockage)
- .+= "Obstruction of airflow detected."
- .+= "Propellant total mass: [round(air_contents.get_mass(),0.01)] kg."
- .+= "Propellant used per burn: [round(air_contents.get_mass() * volume_per_burn * thrust_limit / air_contents.volume,0.01)] kg."
- .+= "Propellant pressure: [round(XGM_PRESSURE(air_contents)/1000,0.1)] MPa."
- . = jointext(.,"
")
+ . += list(list(
+ "text" = "Location: [get_area(src)].",
+ "severity" = "info"
+ ))
+
+ if(stat & NOPOWER)
+ . += list(list("text" = "Insufficient power to operate.", "severity" = "bad"))
+ if(!check_fuel())
+ . += list(list("text" = "Insufficient fuel for a burn.", "severity" = "bad"))
+ if(stat & BROKEN)
+ . += list(list("text" = "Inoperable engine configuration.", "severity" = "bad"))
+ if(blockage)
+ . += list(list("text" = "Obstruction of airflow detected.", "severity" = "bad"))
+
+ . += list(list(
+ "text" = "Propellant total mass: [round(air_contents.get_mass(), 0.01)] kg.",
+ "severity" = null
+ ))
+ . += list(list(
+ "text" = "Propellant used per burn: [round(air_contents.get_mass() * volume_per_burn * thrust_limit / air_contents.volume, 0.01)] kg.",
+ "severity" = null
+ ))
+ . += list(list(
+ "text" = "Propellant pressure: [round(XGM_PRESSURE(air_contents)/1000, 0.1)] MPa.",
+ "severity" = null
+ ))
/obj/machinery/atmospherics/unary/engine/power_change()
. = ..()
@@ -246,7 +259,7 @@
new/obj/effect/engine_exhaust(T, dir, is_cmb && air_contents.temperature >= PHORON_MINIMUM_BURN_TEMPERATURE)
/obj/machinery/atmospherics/unary/engine/proc/calculate_thrust(datum/gas_mixture/propellant, used_part = 1)
- return round(sqrt(propellant.get_mass() * used_part * sqrt(XGM_PRESSURE(air_contents)/200)),0.1)
+ return round(sqrt(propellant.get_mass() * used_part * sqrt(XGM_PRESSURE(air_contents)/200)),0.01)
//Exhaust effect
/obj/effect/engine_exhaust
diff --git a/code/modules/overmap/ships/engines/ion_thruster.dm b/code/modules/overmap/ships/engines/ion_thruster.dm
index 83f8df3f1ed..b14f5c52c6d 100644
--- a/code/modules/overmap/ships/engines/ion_thruster.dm
+++ b/code/modules/overmap/ships/engines/ion_thruster.dm
@@ -52,7 +52,7 @@
var/datum/ship_engine/ion/controller
var/thrust_limit = 1
- var/on = 1
+ var/on = TRUE
var/burn_cost = 36000
var/generated_thrust = 2.5
@@ -66,11 +66,14 @@
/obj/machinery/ion_engine/proc/get_status()
. = list()
- .+= "Location: [get_area(src)]."
- if(!powered())
- .+= "Insufficient power to operate."
- . = jointext(.,"
")
+ . += list(list(
+ "text" = "Location: [get_area(src)].",
+ "severity" = "info"
+ ))
+
+ if(!powered())
+ . += list(list("text" = "Insufficient power to operate.", "severity" = "bad"))
/obj/machinery/ion_engine/proc/burn(var/power_modifier = 1)
if(!on && !powered())
diff --git a/code/modules/overmap/ships/engines/maneuvering_thrusters.dm b/code/modules/overmap/ships/engines/maneuvering_thrusters.dm
index 53f64ed8f90..79e11336af7 100644
--- a/code/modules/overmap/ships/engines/maneuvering_thrusters.dm
+++ b/code/modules/overmap/ships/engines/maneuvering_thrusters.dm
@@ -71,8 +71,11 @@
/obj/machinery/maneuvering_engine/proc/get_status()
. = list()
- .+= "Location: [get_area(src)]."
- . = jointext(.,"
")
+
+ . += list(list(
+ "text" = "Location: [get_area(src)].",
+ "severity" = "info"
+ ))
/obj/machinery/maneuvering_engine/proc/burn(var/power_modifier = 1)
. = thrust_limit * generated_thrust * power_modifier
diff --git a/html/changelogs/Bat-NanoConversions.yml b/html/changelogs/Bat-NanoConversions.yml
new file mode 100644
index 00000000000..c30f48056f3
--- /dev/null
+++ b/html/changelogs/Bat-NanoConversions.yml
@@ -0,0 +1,7 @@
+author: Batrachophrenoboocosmomachia
+delete-after: True
+changes:
+ - refactor: "Migrates DNA Analyzer NanoUI to TGUI."
+ - refactor: "Migrates Engines Control NanoUI to TGUI."
+ - qol: "DNA Analyzer reports now provide the date and time the scan was produced."
+ - bugfix: "Ship computers no longer function after their power is cut."
diff --git a/nano/templates/dnaforensics.tmpl b/nano/templates/dnaforensics.tmpl
deleted file mode 100644
index efe69e6158a..00000000000
--- a/nano/templates/dnaforensics.tmpl
+++ /dev/null
@@ -1,45 +0,0 @@
-Machine Status
-
-
- {{:helper.link(data.scanning ? 'Halt Scan' : 'Begin Scan', 'signal-diag', {'scanItem' : 1}, null)}}
-
-
- {{:helper.link((data.lidstate ? 'Open Lid' : 'Close Lid'), (data.lidstate ? 'unlocked' : 'locked'), {'toggleLid' : 1}, null)}}
-
-
- {{:helper.link('Eject item', 'eject', {'ejectItem' : 1}, (data.bloodsamp && !data.scanning) ? null : 'disabled')}}
-
-
-Scanner
-
-
Scan progress:
-
- {{:helper.displayBar(data.scan_progress, 0, 100, 'good')}}
- {{:data.scan_progress}} %
-
-
- {{if data.scan_progress >= 100}}
- Scan completed successfully.
- {{/if}}
-
-
-
-
-
Item:
-
- {{if data.bloodsamp}}
- {{:data.bloodsamp}}
- {{else}}
- No item inserted
- {{/if}}
-
-
-
-
Heuristic analysis:
-
- {{if data.bloodsamp_desc}}
- {{:data.bloodsamp_desc}}
- {{/if}}
-
-
-
\ No newline at end of file
diff --git a/tgui/packages/tgui/interfaces/DnaForensics.tsx b/tgui/packages/tgui/interfaces/DnaForensics.tsx
new file mode 100644
index 00000000000..4df146a9e40
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/DnaForensics.tsx
@@ -0,0 +1,90 @@
+import { useBackend } from '../backend';
+import { Box, Button, LabeledList, ProgressBar, Section } from '../components';
+import { round } from 'common/math';
+import { Window } from '../layouts';
+import { BooleanLike } from '../../common/react';
+
+type Data = {
+ scan_progress: number;
+ scanning: BooleanLike;
+ bloodsamp: string;
+ bloodsamp_desc: string;
+ lid_closed: BooleanLike;
+};
+
+export const DnaForensics = (props, context) => {
+ const { act, data } = useBackend(context);
+ const hasItem = !!data.bloodsamp;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {round(data.scan_progress, 1)}%
+
+ {data.scan_progress >= 100 && (
+
+ Scan completed successfully.
+
+ )}
+
+
+
+
+
+
+ {hasItem ? (
+ {data.bloodsamp}
+ ) : (
+ No item inserted
+ )}
+
+
+ {data.bloodsamp_desc ? (
+ {data.bloodsamp_desc}
+ ) : (
+
+ )}
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/EnginesControl.tsx b/tgui/packages/tgui/interfaces/EnginesControl.tsx
new file mode 100644
index 00000000000..96778fbd62d
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/EnginesControl.tsx
@@ -0,0 +1,217 @@
+import { useBackend } from '../backend';
+import { Window } from '../layouts';
+import {
+ Box,
+ Button,
+ LabeledList,
+ NoticeBox,
+ Section,
+ Stack,
+ Tabs,
+} from '../components';
+import { round } from 'common/math';
+import { BooleanLike } from '../../common/react';
+
+type EngineInfo = {
+ eng_type: string;
+ eng_on: BooleanLike;
+ eng_thrust: number;
+ eng_thrust_limiter: number; // percentage
+ eng_status: StatusLine[];
+ eng_reference: string;
+};
+
+type StatusLine = {
+ text: string;
+ severity?: 'good' | 'average' | 'bad';
+};
+
+type Data = {
+ state: 'status' | 'engines' | string;
+ global_state: BooleanLike;
+ global_limit: number; // percentage
+ total_thrust: number;
+ engines_info: EngineInfo[];
+};
+
+export const EnginesControl = (_props, context) => {
+ const { act, data } = useBackend(context);
+
+ const engines = data.engines_info || [];
+ const globalOn = !!data.global_state;
+
+ const severityToColor = (sev?: StatusLine['severity']) => {
+ switch (sev) {
+ case 'bad':
+ return 'bad';
+ case 'average':
+ return 'average';
+ case 'good':
+ return 'good';
+ default:
+ return undefined;
+ }
+ };
+
+ return (
+
+
+
+
+ act('set_state', { state: 'status' })}
+ >
+ Overall info
+
+ act('set_state', { state: 'engines' })}
+ >
+ Details
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {round(data.total_thrust * 10, 0.1) / 10}
+
+
+
+
+ {!engines.length && No engines detected.}
+
+ {data.state === 'status' &&
+ engines.map((E, i) => (
+
+
+
+
+ act('engine_toggle', { engine: E.eng_reference })
+ }
+ >
+ {E.eng_on ? 'Shutdown' : 'Power up'}
+
+
+
+ {round(E.eng_thrust * 10, 0.1) / 10}
+
+
+ {round(E.eng_thrust_limiter, 1)}%
+
+
+
+ ))}
+
+ {data.state === 'engines' &&
+ engines.map((E, i) => (
+
+
+
+
+ act('engine_toggle', { engine: E.eng_reference })
+ }
+ >
+ {E.eng_on ? 'Shutdown' : 'Power up'}
+
+
+
+ {E.eng_type}
+
+
+
+ {E.eng_on ? 'Online' : 'Offline'}
+
+
+ {E.eng_status?.length ? (
+ E.eng_status.map((line, idx) => (
+
+ {line.text}
+
+ ))
+ ) : (
+ -
+ )}
+
+
+
+
+ {round(E.eng_thrust * 10, 0.1) / 10}
+
+
+
+
+
+
+ act('engine_limit_delta', {
+ engine: E.eng_reference,
+ delta: -0.1,
+ })
+ }
+ />
+
+
+
+ act('engine_set_limit', { engine: E.eng_reference })
+ }
+ >
+ {round(E.eng_thrust_limiter, 1)}%
+
+
+
+
+ act('engine_limit_delta', {
+ engine: E.eng_reference,
+ delta: 0.1,
+ })
+ }
+ />
+
+
+
+
+
+ ))}
+
+
+ );
+};
+
+export default EnginesControl;