diff --git a/code/_helpers/maths.dm b/code/_helpers/maths.dm
index 416fcaf1ac3..a873997afef 100644
--- a/code/_helpers/maths.dm
+++ b/code/_helpers/maths.dm
@@ -225,3 +225,19 @@
line += locate(current_x_step, current_y_step, starting_z)
return line
+
+
+/// Returns the distance between two points
+#define DIST_BETWEEN_TWO_POINTS(ax, ay, bx, by) (sqrt((bx-ax)*(bx-ax))+((by-ay)*(by-ay)))
+
+/**
+ * Returns bearing of object relative to observer (0-360)
+ * a is the observer, b is the other object
+ *
+ * observer_x - Observer's X coordinate
+ * observer_y - Observer's Y coordinate
+ * target_x - Target's X coordinate
+ * target_y - Target's Y coordinate
+ */
+#define BEARING_RELATIVE(observer_x, observer_y, target_x, target_y) (90 - Atan2(target_x - observer_x, target_y - observer_y))
+
diff --git a/code/modules/overmap/ships/computers/helm.dm b/code/modules/overmap/ships/computers/helm.dm
index bd4be0a028d..63ffbab4144 100644
--- a/code/modules/overmap/ships/computers/helm.dm
+++ b/code/modules/overmap/ships/computers/helm.dm
@@ -175,22 +175,22 @@
/obj/machinery/computer/ship/helm/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
if(..())
- return TOPIC_HANDLED
+ return TRUE
if(!connected)
- return TOPIC_HANDLED
+ return TRUE
if(action == "add")
var/datum/computer_file/data/waypoint/R = new()
var/sec_name = input("Input naviation entry name", "New navigation entry", "Sector #[known_sectors.len]") as text
if(!CanInteract(usr, physical_state))
- return TOPIC_NOACTION
+ return FALSE
if(!sec_name)
sec_name = "Sector #[known_sectors.len]"
R.fields["name"] = sec_name
if(sec_name in known_sectors)
to_chat(usr, "Sector with that name already exists, please input a different name.")
- return TOPIC_REFRESH
+ return TRUE
switch(params["add"])
if("current")
R.fields["x"] = connected.x
@@ -198,10 +198,10 @@
if("new")
var/newx = input("Input new entry x coordinate", "Coordinate input", connected.x) as num
if(!CanInteract(usr, physical_state))
- return TOPIC_REFRESH
+ return TRUE
var/newy = input("Input new entry y coordinate", "Coordinate input", connected.y) as num
if(!CanInteract(usr, physical_state))
- return TOPIC_NOACTION
+ return FALSE
R.fields["x"] = Clamp(newx, 1, world.maxx)
R.fields["y"] = Clamp(newy, 1, world.maxy)
known_sectors[sec_name] = R
@@ -291,7 +291,7 @@
check_processing()
else
to_chat(usr, SPAN_WARNING("Your software does not allow you to interact with the piloting controls."))
- return TOPIC_HANDLED
+ return TRUE
add_fingerprint(usr)
updateUsrDialog()
diff --git a/code/modules/overmap/ships/computers/sensors.dm b/code/modules/overmap/ships/computers/sensors.dm
index f33968300ff..d7ae897fc12 100644
--- a/code/modules/overmap/ships/computers/sensors.dm
+++ b/code/modules/overmap/ships/computers/sensors.dm
@@ -68,10 +68,13 @@
else if(sound_token)
QDEL_NULL(sound_token)
-/obj/machinery/computer/ship/sensors/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- if(!linked)
- display_reconnect_dialog(user, "sensors")
- return
+/obj/machinery/computer/ship/sensors/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Sensors", capitalize_first_letters(name))
+ ui.open()
+
+/obj/machinery/computer/ship/sensors/ui_data(mob/user)
simple_asset_ensure_is_sent(user, /datum/asset/simple/paper)
@@ -79,6 +82,20 @@
data["viewing"] = viewing_overmap(user)
data["muted"] = muted
+
+ data["grid_x"] = linked.x
+ data["grid_y"] = linked.y
+ data["direction"] = dir2angle(linked.dir)
+ var/linked_x = linked.x
+ var/linked_y = linked.y
+ var/obj/effect/overmap/visitable/ship/linked_ship = linked
+ if(istype(linked_ship))
+ linked_x += linked_ship.position[1] / 2.0
+ linked_y += linked_ship.position[2] / 2.0
+ data["is_ship"] = TRUE
+ data["x"] = linked_x
+ data["y"] = linked_y
+
if(sensors)
data["on"] = sensors.use_power
data["range"] = sensors.range
@@ -128,17 +145,41 @@
for(var/obj/effect/overmap/visitable/identified_contact in contact_datums)
potential_contacts |= identified_contact
- for(var/obj/effect/overmap/O in potential_contacts)
- if(linked == O)
+ for(var/obj/effect/overmap/contact in potential_contacts)
+ if(linked == contact)
continue
- if(!O.scannable)
+ if(!contact.scannable)
continue
- var/bearing = round(90 - Atan2(O.x - linked.x, O.y - linked.y),5)
+
+ var/obj/effect/overmap/visitable/ship/landable/contact_landable = contact
+ var/landed = (istype(contact_landable) && contact_landable.status == SHIP_STATUS_LANDED)
+
+ var/contact_x = contact.x
+ var/contact_y = contact.y
+
+ var/obj/effect/overmap/visitable/ship/contact_ship = contact
+ if(istype(contact_ship))
+ contact_x += contact_ship.position[1] / 2.0
+ contact_y += contact_ship.position[2] / 2.0
+
+ var/bearing = round(BEARING_RELATIVE(linked_x, linked_y, contact_x, contact_y),5)
if(bearing < 0)
bearing += 360
- contacts.Add(list(list("name"=O.name, "ref"="\ref[O]", "bearing"=bearing, "can_datalink"=(!(O in connected.datalinked)))))
- if(length(contacts))
- data["contacts"] = contacts
+
+ var/distance = DIST_BETWEEN_TWO_POINTS(linked_x, linked_y, contact_x, contact_y)
+
+ contacts.Add(list(list(
+ "name"=contact.name,
+ "ref"="\ref[contact]",
+ "bearing"=bearing,
+ "can_datalink"=(!(contact in connected.datalinked)),
+ "distance"=distance,
+ "landed"=landed,
+ "x"=contact_x,
+ "y"=contact_y
+ )))
+
+ data["contacts"] = contacts
// Add datalink requests
if(length(connected.datalink_requests))
@@ -176,58 +217,53 @@
else
data["id_status"] = "NOBEACON" //Should not really happen.
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
- if (!ui)
- ui = new(user, src, ui_key, "shipsensors.tmpl", "[linked.get_real_name()] Sensors Control", 600, 530, src)
- ui.set_initial_data(data)
- ui.open()
- ui.set_auto_update(1)
+ return data
-/obj/machinery/computer/ship/sensors/Topic(href, href_list)
+/obj/machinery/computer/ship/sensors/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
if (..())
- return TOPIC_HANDLED
+ return TRUE
if (!linked)
- return TOPIC_NOACTION
+ return FALSE
- if (href_list["viewing"])
+ if (action == "viewing")
if(usr)
viewing_overmap(usr) ? unlook(usr) : look(usr)
- return TOPIC_REFRESH
+ return TRUE
- if (href_list["link"])
+ if (action == "link")
find_sensors_and_iff()
- return TOPIC_REFRESH
+ return TRUE
if(sensors)
- if (href_list["range"])
+ if (action == "range")
var/nrange = input("Set new sensors range", "Sensor range", sensors.range) as num|null
if(!CanInteract(usr, default_state))
- return TOPIC_NOACTION
+ return FALSE
if (nrange)
sensors.set_desired_range(Clamp(nrange, 1, sensors.max_range))
- return TOPIC_REFRESH
- if(href_list["range_choice"])
- var/nrange = text2num(href_list["range_choice"])
+ return TRUE
+ if(action == "range_choice")
+ var/nrange = text2num(params["range_choice"])
if(!CanInteract(usr, default_state))
- return TOPIC_NOACTION
+ return FALSE
if(nrange)
sensors.set_desired_range(Clamp(nrange, 1, sensors.max_range))
- return TOPIC_REFRESH
- if (href_list["toggle"])
+ return TRUE
+ if (action == "toggle")
sensors.toggle()
- return TOPIC_REFRESH
+ return TRUE
- if(href_list["deep_scan_toggle"])
+ if(action == "deep_scan_toggle")
sensors.deep_scan_toggled = !sensors.deep_scan_toggled
- return TOPIC_REFRESH
+ return TRUE
if(identification)
- if(href_list["toggle_id"])
+ if(action == "toggle_id")
identification.toggle()
- return TOPIC_REFRESH
+ return TRUE
- if(href_list["change_ship_class"])
+ if(action == "change_ship_class")
if(!identification.use_power)
to_chat(usr, SPAN_WARNING("You cannot do this while the IFF is off!"))
return
@@ -241,9 +277,9 @@
linked.set_new_class(new_class)
playsound(src, 'sound/machines/twobeep.ogg', 50)
visible_message(SPAN_NOTICE("\The [src] beeps, \"IFF change to ship class registered.\""))
- return TOPIC_REFRESH
+ return TRUE
- if(href_list["change_ship_name"])
+ if(action == "change_ship_name")
if(!identification.use_power)
to_chat(usr, SPAN_WARNING("You cannot do this while the IFF is off!"))
return
@@ -257,20 +293,20 @@
linked.set_new_designation(new_name)
playsound(src, 'sound/machines/twobeep.ogg', 50)
visible_message(SPAN_NOTICE("\The [src] beeps, \"IFF change to ship designation registered.\""))
- return TOPIC_REFRESH
+ return TRUE
- if (href_list["scan-action"])
- switch(href_list["scan-action"])
+ if (action == "scan_action")
+ switch(params["scan_action"])
if("clear")
contact_details = null
if("print")
if(contact_details)
playsound(loc, "sound/machines/dotprinter.ogg", 30, 1)
new/obj/item/paper/(get_turf(src), contact_details, "paper (Sensor Scan - [contact_name])")
- return TOPIC_HANDLED
+ return TRUE
- if (href_list["scan"])
- var/obj/effect/overmap/O = locate(href_list["scan"])
+ if (action == "scan")
+ var/obj/effect/overmap/O = locate(params["scan"])
if(istype(O) && !QDELETED(O))
if((O in view(7,linked))|| (O in contact_datums))
playsound(loc, "sound/machines/dotprinter.ogg", 30, 1)
@@ -280,37 +316,37 @@
to_chat(usr, SPAN_NOTICE("Successfully scanned [O]."))
contact_name = O.name
contact_details = O.get_scan_data(usr)
- return TOPIC_HANDLED
+ return TRUE
- if (href_list["request_datalink"])
- var/obj/effect/overmap/visitable/O = locate(href_list["request_datalink"])
+ if (action == "request_datalink")
+ var/obj/effect/overmap/visitable/O = locate(params["request_datalink"])
if(istype(O) && !QDELETED(O))
if((O in view(7,linked)) || (O in contact_datums))
for(var/obj/machinery/computer/ship/sensors/sensor_console in O.consoles)
sensor_console.connected.datalink_requests |= src.connected
- return TOPIC_HANDLED
+ return TRUE
- if (href_list["accept_datalink_requests"])
- var/obj/effect/overmap/visitable/O = locate(href_list["accept_datalink_requests"])
+ if (action == "accept_datalink_requests")
+ var/obj/effect/overmap/visitable/O = locate(params["accept_datalink_requests"])
for(var/obj/machinery/computer/ship/sensors/sensor_console in src.connected.consoles)
sensor_console.datalink_add_ship_datalink(O)
break
src.connected.datalink_requests -= O // Remove the request
- return TOPIC_HANDLED
+ return TRUE
- if (href_list["decline_datalink_requests"])
- var/obj/effect/overmap/visitable/O = locate(href_list["decline_datalink_requests"])
+ if (action == "decline_datalink_requests")
+ var/obj/effect/overmap/visitable/O = locate(params["decline_datalink_requests"])
src.connected.datalink_requests -= O // Remove the request
- if (href_list["remove_datalink"])
- var/obj/effect/overmap/visitable/O = locate(href_list["remove_datalink"])
+ if (action == "remove_datalink")
+ var/obj/effect/overmap/visitable/O = locate(params["remove_datalink"])
for(var/obj/machinery/computer/ship/sensors/rescinder_sensor_console in src.connected.consoles) // Get sensor console from the rescinder
rescinder_sensor_console.datalink_remove_ship_datalink(O, TRUE)
- return TOPIC_HANDLED
+ return TRUE
- if (href_list["play_message"])
- var/caller = href_list["play_message"]
+ if (action == "play_message")
+ var/caller = params["play_message"]
var/datum/distress_beacon/beacon = SSdistress.active_distress_beacons[caller]
var/mob/living/carbon/human/sender = beacon.user
var/user_name = beacon.user_name
@@ -318,15 +354,15 @@
visible_message(SPAN_NOTICE("\The [src] beeps a few times as it replays the distress message."))
playsound(src, 'sound/machines/compbeep5.ogg')
visible_message(SPAN_ITALIC("[accent_icon] [user_name] explains, \"[beacon.distress_message]\""))
- return TOPIC_HANDLED
+ return TRUE
- if(href_list["inbound_fire"])
- var/direction = href_list["inbound_fire"]
+ if(action == "inbound_fire")
+ var/direction = params["inbound_fire"]
if(direction != "clear")
security_announcement.Announce("Enemy fire inbound, enemy fire inbound! [sanitizeSafe(direction)]!", "Brace for shock!", sound('sound/mecha/internaldmgalarm.ogg', volume = 90), 0)
else
security_announcement.Announce("No fire is incoming at the current moment, resume damage control.", "Space clear!", sound('sound/misc/announcements/security_level_old.ogg'), 0)
- return TOPIC_HANDLED
+ return TRUE
/obj/machinery/shipsensors
name = "sensors suite"
diff --git a/html/changelogs/DreamySkrell-sensors-tgui.yml b/html/changelogs/DreamySkrell-sensors-tgui.yml
new file mode 100644
index 00000000000..3d96718bd62
--- /dev/null
+++ b/html/changelogs/DreamySkrell-sensors-tgui.yml
@@ -0,0 +1,42 @@
+################################
+# Example Changelog File
+#
+# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
+#
+# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
+# When it is, any changes listed below will disappear.
+#
+# Valid Prefixes:
+# bugfix
+# wip (For works in progress)
+# tweak
+# soundadd
+# sounddel
+# rscadd (general adding of nice things)
+# rscdel (general deleting of nice things)
+# imageadd
+# imagedel
+# maptweak
+# spellcheck (typo fixes)
+# experiment
+# balance
+# admin
+# backend
+# security
+# refactor
+#################################
+
+# Your name.
+author: DreamySkrell
+
+# 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, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
+# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
+changes:
+ - refactor: "Rewrites sensors from nanoui to tgui."
+ - rscadd: "Adds sensor contacts compass to sensors console."
diff --git a/nano/templates/shipsensors.tmpl b/nano/templates/shipsensors.tmpl
deleted file mode 100644
index bd080ab0e2e..00000000000
--- a/nano/templates/shipsensors.tmpl
+++ /dev/null
@@ -1,219 +0,0 @@
-
Sensor Array Control Console
-
- {{:helper.link(data.on ? 'Switch Off' : 'Switch On', 'gear', { 'toggle' : 1 }, data.status != 'MISSING' ? null : 'disabled', data.on ? 'selected' : null)}}
-
-
- Status:
-
-
- {{:data.status}}
-
-
-
-
- Range:
-
-
- {{for data.range_choices}}
- {{:helper.link(value, null, { 'range_choice' : value }, null, ( value==data.range ? 'selected' : ( value==data.desired_range ? 'disabled' : null ) ) )}}
- {{/for}}
-
-
- {{if data.deep_scan_range > 0}}
-
-
- {{:data.deep_scan_name}}
-
-
- Effective Range: {{:data.deep_scan_range}}
- {{:helper.link(data.deep_scan_toggled ? 'Deactivate' : 'Activate', 'gear', {'deep_scan_toggle' : 1}, data.status != 'MISSING' ? null : 'disabled', data.deep_scan_toggled ? 'selected' : null)}}
-
-
- {{/if}}
-
-
-
-
- Integrity:
-
-
- {{if data.health < (data.max_health * 0.25)}}
- {{:helper.displayBar(data.health, 0, data.max_health, 'bad')}}
-
{{:data.health}}/{{:data.max_health}}
- {{else data.health < data.max_health *.75}}
- {{:helper.displayBar(data.health, 0, data.max_health, 'average')}}
-
{{:data.health}}/{{:data.max_health}}
- {{else}}
- {{:helper.displayBar(data.health, 0, data.max_health, 'good')}}
-
{{:data.health}}/{{:data.max_health}}
- {{/if}}
-
-
-
-
- Temperature:
-
-
- {{if data.heat < (data.critical_heat * 0.5)}}
- {{:helper.displayBar(data.heat, 0, data.critical_heat, 'good')}}
- {{else data.heat < (data.critical_heat * 0.75)}}
- {{:helper.displayBar(data.heat, 0, data.critical_heat, 'average')}}
- {{else}}
- {{:helper.displayBar(data.heat, 0, data.critical_heat, 'bad')}}
- {{/if}}
-
-
- {{if data.heat < (data.critical_heat * 0.5)}}
- Temperature low.
- {{else data.heat < (data.critical_heat * 0.75)}}
- Sensor temperature high!
- {{else}}
- TEMPERATURE CRITICAL: Disable or reduce power immediately!
- {{/if}}
-
-
-
-
-
-
- Sector map view
- {{:helper.link(data.viewing ? 'Engaged' : 'Disengaged', 'shuffle', { 'viewing' : 1 }, null, data.viewing ? 'selected' : null)}}
-
-
-
-IFF Management
-
- {{:helper.link(data.id_on ? 'Switch off' : 'Switch on', 'gear', { 'toggle_id' : 1 }, data.id_status != 'NOBEACON' ? null : 'disabled', data.id_on ? 'selected' : null)}}
-
-
- Status:
-
-
- {{:data.id_status}}
-
-
-
-
- Class:
-
-
- {{if data.can_change_class}}
- {{:helper.link(data.id_class, 'gear', { 'change_ship_class' : 1 })}}
- {{else}}
- {{:data.id_class}}
- {{/if}}
-
-
-
- Designation:
-
-
- {{if data.can_change_name}}
- {{:helper.link(data.id_name, 'gear', { 'change_ship_name' : 1 })}}
- {{else}}
- {{:data.id_name}}
- {{/if}}
-
-
-
-Sensor Contacts
-
-{{if data.contacts}}
-
- {{for data.contacts}}
-
- | {{:helper.link('Scan', 'search' ,{ 'scan' : value.ref }, null, null)}} |
-
- {{if value.can_datalink}}
- {{:helper.link('Datalink', 'search' ,{ 'request_datalink' : value.ref }, null, null)}}
- {{/if}}
- |
- {{:value.name}}, bearing {{:value.bearing}} |
-
- {{/for}}
-
- {{if data.contact_details}}
-
- {{:data.contact_details}}
-
-
-
- {{:helper.link('Print', 'search', { 'scan-action' : 'print' })}}
- {{:helper.link('Clear', 'refresh', { 'scan-action' : 'clear' })}}
-
-{{/if}}
-{{/if}}
-
-
-Datalinks
-
-{{if data.datalink_requests}}
-
- {{for data.datalink_requests}}
-
-
-
{{:helper.link('Accept', 'search' ,{ 'accept_datalink_requests' : value.ref }, null, null)}} |
- {{:helper.link('Decline', 'search' ,{ 'decline_datalink_requests' : value.ref }, null, null)}} |
- {{:value.name}} |
-
-
- {{/for}}
-
-{{/if}}
-{{if data.datalinked}}
-
Connected Datalinks
-
- {{for data.datalinked}}
-
-
-
{{:helper.link('Rescind', 'search' ,{ 'remove_datalink' : value.ref }, null, null)}} |
- {{:value.name}} |
-
-
- {{/for}}
-
-{{/if}}
-
-
-{{if data.id_name == 'Horizon'}}
- Announce Inbound fire
-
-
- {{:helper.link('', 'triangle-1-nw', { 'inbound_fire' : 'Fore-Port' }, null, null)}}
- {{:helper.link('', 'triangle-1-n', { 'inbound_fire' : 'Fore' }, null, null)}}
- {{:helper.link('', 'triangle-1-ne', { 'inbound_fire' : 'Fore-Starboard' }, null, null)}}
-
-
- {{:helper.link('', 'triangle-1-w', { 'inbound_fire' : 'Port' }, null, null)}}
- {{:helper.link('', 'circle-close', { 'inbound_fire' : 'clear' }, null, null)}}
- {{:helper.link('', 'triangle-1-e', { 'inbound_fire' : 'Starboard' }, null, null)}}
-
-
- {{:helper.link('', 'triangle-1-sw', { 'inbound_fire' : 'Bow-Port' }, null, null)}}
- {{:helper.link('', 'triangle-1-s', { 'inbound_fire' : 'Bow' }, null, null)}}
- {{:helper.link('', 'triangle-1-se', { 'inbound_fire' : 'Bow-Starboard' }, null, null)}}
-
-
-{{/if}}
-
-Distress Beacons
-
-
Press the 'Listen' button to listen to the distress message.
-{{if data.distress_beacons}}
-
- {{for data.distress_beacons}}
-
-
-
{{:helper.link('Listen', 'info' ,{ 'play_message' : value.caller }, null, null)}} |
- {{:value.caller}}, bearing {{:value.bearing}}, sent by {{:value.sender}} |
-
-
- {{/for}}
-
-{{/if}}
-
-{{if data.status == 'MISSING'}}
-
- {{:helper.link('Link up with the sensor suite', 'gear', { 'link' : 1 }, data.status == 'MISSING' ? null : 'disabled', null)}}
-
-{{/if}}
diff --git a/tgui/packages/common/math.ts b/tgui/packages/common/math.ts
index 9dc1d655693..b9f7bfda9d5 100644
--- a/tgui/packages/common/math.ts
+++ b/tgui/packages/common/math.ts
@@ -4,6 +4,8 @@
* @license MIT
*/
+// Further modified for Aurora
+
/**
* Limits a number to the range between 'min' and 'max'.
*/
@@ -96,3 +98,10 @@ export const numberOfDecimalDigits = (value) => {
}
return 0;
};
+
+// Linear interpolation between two values.
+export const lerp = (value1: number, value2: number, amount: number) => {
+ amount = amount < 0 ? 0 : amount;
+ amount = amount > 1 ? 1 : amount;
+ return value1 + (value2 - value1) * amount;
+};
diff --git a/tgui/packages/tgui/interfaces/Sensors.tsx b/tgui/packages/tgui/interfaces/Sensors.tsx
new file mode 100644
index 00000000000..e891d9b2b8f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Sensors.tsx
@@ -0,0 +1,621 @@
+import { BooleanLike } from '../../common/react';
+import { useBackend } from '../backend';
+import { Box, Button, Section, Table, ProgressBar, Slider } from '../components';
+import { NtosWindow } from '../layouts';
+import { round, clamp } from 'common/math';
+import { Color } from 'common/color';
+import { capitalizeAll } from 'common/string';
+
+export type SensorsData = {
+ viewing: BooleanLike;
+ muted: BooleanLike;
+ grid_x: number;
+ grid_y: number;
+ x: number;
+ y: number;
+ direction: number;
+ is_ship: BooleanLike;
+ on: BooleanLike;
+ range: number;
+ health: number;
+ max_health: number;
+ deep_scan_name: string;
+ deep_scan_range: number;
+ deep_scan_toggled: BooleanLike;
+ heat: number;
+ critical_heat: number;
+ status: string;
+ desired_range: number;
+ range_choices: number[];
+ contacts: ContactData[];
+
+ id_on: BooleanLike;
+ id_status: string;
+ id_class: string;
+ id_name: string;
+ can_change_class: BooleanLike;
+ can_change_name: BooleanLike;
+ contact_details: string;
+
+ distress_beacons: DistressBeaconData[];
+ distress_range: number;
+
+ datalink_requests: { name: string; ref: string }[];
+ datalinked: { name: string; ref: string }[];
+};
+
+type ContactData = {
+ name: string;
+ ref: string;
+ bearing: number;
+ can_datalink: BooleanLike;
+ landed: BooleanLike;
+ distance: number;
+ x: number;
+ y: number;
+ color: string; // assigned in ts
+};
+
+type DistressBeaconData = {
+ caller: string;
+ sender: string;
+ bearing: number;
+};
+
+const SensorSection = function (act, data: SensorsData) {
+ const range_choice_max = data.range_choices[data.range_choices.length - 1];
+
+ return (
+ act('viewing')}
+ />
+ }>
+
+
+ State:
+
+
+
+
+ Status:
+ {
+ switch (data.status) {
+ case 'DESTROYED':
+ return 'bad';
+ case 'NO POWER':
+ return 'bad';
+ case 'VACUUM SEAL BROKEN':
+ return 'blue';
+ case 'OK':
+ return 'good';
+ default:
+ return null;
+ }
+ })()}>
+ {data.status}
+
+
+
+ Range:
+
+
+ act('range_choice', { range_choice: value })
+ }>
+ Desired Range: {data.desired_range} / {range_choice_max}
+
+
+ Current Range: {data.range} / {range_choice_max}
+
+
+
+ {data.deep_scan_range > 0 ? (
+
+ {data.deep_scan_name}:
+
+ Effective Range: {data.deep_scan_range}
+ {', '}
+
+
+ ) : (
+ ''
+ )}
+
+ Integrity:
+
+ {
+ if (data.health > (data.max_health / 3) * 2) {
+ return 'green';
+ } else if (data.health > (data.max_health / 3) * 1) {
+ return 'yellow';
+ } else {
+ return 'red';
+ }
+ })()}
+ minValue={0}
+ maxValue={data.max_health}
+ value={data.health}>
+ {data.health} / {data.max_health}
+
+
+
+
+ Temperature:
+
+ {
+ if (data.heat > (data.critical_heat / 3) * 2) {
+ return 'red';
+ } else if (data.heat > (data.critical_heat / 3) * 1) {
+ return 'yellow';
+ } else {
+ return 'green';
+ }
+ })()}
+ minValue={0}
+ maxValue={data.critical_heat}
+ value={data.heat}>
+ {data.heat} / {data.critical_heat}
+
+
+
+
+
+ );
+};
+
+const ContactsSection = function (act, data: SensorsData) {
+ return (
+
+ {data.contacts && data.contacts.length ? (
+
+
+
+ Designation
+ B
+ X
+ Y
+ D
+ C
+
+ {data.contacts.map((contact: ContactData, i) => (
+
+
+
+
+ {capitalizeAll(contact.name)}
+
+ {contact.landed ? (
+ ''
+ ) : (
+ <>
+ {contact.bearing}
+ {contact.x}
+ {contact.y}
+
+ {new String(round(contact.distance, 2)).padStart(6, '0')}
+
+
+ ██
+
+ >
+ )}
+
+ ))}
+
+ ) : (
+ ''
+ )}
+
+ );
+};
+
+const ContactDetailsSection = function (act, data: SensorsData) {
+ if (data.contact_details && data.contact_details !== '') {
+ /* eslint-disable react/no-danger */
+ const contact_details = (
+
+ );
+ /* eslint-enable */
+ return (
+
+
+ {contact_details}
+
+
+ );
+ } else {
+ return '';
+ }
+};
+
+const CompassSection = function (context, act, data: SensorsData) {
+ return (
+
+
+
+
+
+ );
+};
+
+const DatalinksSection = function (act, data: SensorsData) {
+ return (
+
+ {data.datalink_requests && data.datalink_requests.length ? (
+
+
+ Datalink Requests:
+
+ {data.datalink_requests.map((request) => (
+
+
+
+ act('accept_datalink_requests', {
+ accept_datalink_requests: request.ref,
+ })
+ }
+ />
+
+
+
+ act('decline_datalink_requests', {
+ decline_datalink_requests: request.ref,
+ })
+ }
+ />
+
+ {capitalizeAll(request.name)}
+
+ ))}
+
+ ) : (
+ ''
+ )}
+ {data.datalinked && data.datalinked.length ? (
+
+
+ Active Datalinks:
+
+ {data.datalinked.map((datalinked) => (
+
+ {capitalizeAll(datalinked.name)}
+
+
+ act('remove_datalink', {
+ remove_datalink: datalinked.ref,
+ })
+ }
+ />
+
+
+ ))}
+
+ ) : (
+ ''
+ )}
+ {data.contacts &&
+ data.contacts.length &&
+ data.contacts.some((contact) => contact.can_datalink) ? (
+
+
+ Potential Datalinks:
+
+ {data.contacts
+ .filter((contact) => contact.can_datalink)
+ .map((contact) => (
+
+ {capitalizeAll(contact.name)}
+
+
+ act('request_datalink', {
+ request_datalink: contact.ref,
+ })
+ }
+ />
+
+
+ ))}
+
+ ) : (
+ ''
+ )}
+
+ );
+};
+
+const IFFSection = function (act, data: SensorsData) {
+ return (
+
+
+
+ State:
+
+ act('toggle_id')}
+ />
+
+
+
+ Status:
+ {data.id_status}
+
+
+ Class:
+ {data.id_class}
+
+
+ Designation:
+ {data.id_name}
+
+ {data.can_change_class || data.can_change_name ? (
+
+
+
+ {data.can_change_class ? (
+ act('change_ship_class')}
+ />
+ ) : (
+ ''
+ )}
+ {data.can_change_name ? (
+ act('change_ship_name')}
+ />
+ ) : (
+ ''
+ )}
+
+
+ ) : (
+ ''
+ )}
+
+
+ );
+};
+
+const DistressSection = function (act, data: SensorsData) {
+ return (
+
+ {data.distress_beacons && data.distress_beacons.length ? (
+
+
+
+ Vessel
+ Bearing
+ Sender
+
+ {data.distress_beacons.map((beacon: DistressBeaconData) => (
+
+
+
+ act('play_message', { play_message: beacon.caller })
+ }
+ />
+
+ {beacon.caller}
+ {beacon.bearing}
+ {beacon.sender}
+
+ ))}
+
+ ) : (
+ 'None received.'
+ )}
+
+ );
+};
+
+export const Sensors = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ {
+ let color_i = 0;
+ const colors = [
+ 'red',
+ 'green',
+ 'purple',
+ 'orange',
+ 'cyan',
+ 'yellow',
+ 'maroon',
+ 'olive',
+ 'white',
+ ];
+
+ let bearing_color_map: Map = new Map();
+
+ data.contacts?.forEach((contact, i) => {
+ if (!bearing_color_map[contact.bearing]) {
+ bearing_color_map[contact.bearing] = colors[color_i];
+ color_i++;
+ if (color_i >= colors.length) {
+ color_i = 0;
+ }
+ }
+ contact.color = bearing_color_map[contact.bearing];
+ });
+ }
+
+ return (
+
+
+ {data.status === 'MISSING' ? (
+ act('link')}
+ />
+ ) : (
+ ''
+ )}
+ {SensorSection(act, data)}
+ {CompassSection(context, act, data)}
+ {ContactsSection(act, data)}
+ {ContactDetailsSection(act, data)}
+ {DatalinksSection(act, data)}
+ {IFFSection(act, data)}
+ {DistressSection(act, data)}
+
+
+ );
+};