From 9ecf77ca2802e1df20c32b3911a93e68cd5b7fb3 Mon Sep 17 00:00:00 2001 From: Jeremiah <42397676+jlsnow301@users.noreply.github.com> Date: Sun, 16 Oct 2022 11:05:39 -0700 Subject: [PATCH] Adds extended tooltip information to observables in the orbit ui (#70547) A continuation of #68389 which addresses an issue that still bothers me to this day: The orbit menu displays a player's name as a combo of name id transform. It can get lengthy to a point where the names clip the entire screen (as buttons do not multiline). This PR shortens excessively long player names on the orbit menu and adds a tooltip that will show extended info like full name, health and job titles. Mostly drawn from concerns brought up in the original. --- code/modules/mob/dead/observer/orbit.dm | 30 +++- tgui/packages/tgui/interfaces/Orbit.tsx | 193 +++++++++++++++++------- 2 files changed, 166 insertions(+), 57 deletions(-) diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm index fdcfc15b8e0..dd2efeb58b4 100644 --- a/code/modules/mob/dead/observer/orbit.dm +++ b/code/modules/mob/dead/observer/orbit.dm @@ -57,7 +57,7 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new) var/poi_ref = REF(mob_poi) serialized["ref"] = poi_ref - serialized["name"] = name + serialized["full_name"] = name if(isobserver(mob_poi)) var/number_of_orbiters = length(mob_poi.get_all_orbiters()) @@ -81,6 +81,15 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new) var/datum/mind/mind = mob_poi.mind var/was_antagonist = FALSE + serialized["job"] = mind?.assigned_role?.title + serialized["name"] = mob_poi.real_name + serialized["health"] = null + // Cast the mob so we can get health + var/mob/living/player + if(isliving(mob_poi)) // Kind of silly here since we've already checked for dead mobs + player = mob_poi + serialized["health"] = FLOOR((player.health / player.maxHealth * 100), 1) + for(var/datum/antagonist/antag_datum as anything in mind.antag_datums) if (antag_datum.show_to_ghosts) was_antagonist = TRUE @@ -97,8 +106,27 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new) misc += list(list( "ref" = REF(atom_poi), "name" = name, + "extra" = null, // Just in case you want to add anything )) + // Display the supermatter crystal integrity + if(istype(atom_poi, /obj/machinery/power/supermatter_crystal)) + var/obj/machinery/power/supermatter_crystal/crystal = atom_poi + misc[length(misc)]["extra"] = "Integrity: [crystal.get_integrity_percent()]%" + continue + // Display the nuke timer + if(istype(atom_poi, /obj/machinery/nuclearbomb)) + var/obj/machinery/nuclearbomb/bomb = atom_poi + if(bomb.timing) + misc[length(misc)]["extra"] = "Timer: [bomb.countdown?.displayed_text]s" + continue + // Display the holder if its a nuke disk + if(istype(atom_poi, /obj/item/disk/nuclear)) + var/obj/item/disk/nuclear/disk = atom_poi + var/mob/holder = disk.pulledby || get(disk, /mob) + misc[length(misc)]["extra"] = "Location: [holder?.real_name || "Unsecured"]" + continue + return list( "alive" = alive, "antagonists" = antagonists, diff --git a/tgui/packages/tgui/interfaces/Orbit.tsx b/tgui/packages/tgui/interfaces/Orbit.tsx index a4802487f5d..69211ee824a 100644 --- a/tgui/packages/tgui/interfaces/Orbit.tsx +++ b/tgui/packages/tgui/interfaces/Orbit.tsx @@ -1,32 +1,31 @@ import { useBackend, useLocalState } from '../backend'; import { filter, sortBy } from 'common/collections'; import { capitalizeFirst, multiline } from 'common/string'; -import { Button, Collapsible, Icon, Input, Section, Stack } from '../components'; +import { Box, Button, Collapsible, Icon, Input, LabeledList, NoticeBox, Section, Stack } from '../components'; import { Window } from '../layouts'; import { flow } from 'common/fp'; -type AntagGroup = [string, Observable[]]; +type AntagGroup = [string, Antags]; + +type Antags = Array; type Data = { - alive: Observable[]; - antagonists: Observable[]; - dead: Observable[]; - ghosts: Observable[]; - misc: Observable[]; - npcs: Observable[]; + alive: Array; + antagonists: Antags; + dead: Array; + ghosts: Array; + misc: Array; + npcs: Array; }; type Observable = { - ref: string; - antag?: string; - name: string; + extra?: string; + full_name: string; + health?: number; + job?: string; + name?: string; orbiters?: number; -}; - -type SectionProps = { - color?: string; - section: Observable[]; - title: string; + ref: string; }; const ANTAG2COLOR = { @@ -103,17 +102,17 @@ const ObservableSearch = (props, context) => { 'searchQuery', '' ); - /** Gets a list of Observable[], then filters the most relevant to orbit */ + /** Gets a list of Observables, then filters the most relevant to orbit */ const orbitMostRelevant = (searchQuery: string): void => { /** Returns the most orbited observable that matches the search. */ const mostRelevant: Observable = flow([ // Filters out anything that doesn't match search filter((observable) => - observable.name?.toLowerCase().includes(searchQuery?.toLowerCase()) + isJobOrNameMatch(observable, searchQuery) ), // Sorts descending by orbiters - sortBy((poi) => -(poi.orbiters || 0)), - // Makes a single Observable[] list for an easy search + sortBy((observable) => -(observable.orbiters || 0)), + // Makes a single Observables list for an easy search ])([alive, antagonists, dead, ghosts, misc, npcs].flat())[0]; if (mostRelevant !== undefined) { act('orbit', { @@ -180,7 +179,7 @@ const ObservableContent = (props, context) => { misc = [], npcs = [], } = data; - let collatedAntagonists: AntagGroup[] = []; + let collatedAntagonists: Array = []; if (antagonists.length) { collatedAntagonists = collateAntagonists(antagonists); } @@ -210,21 +209,24 @@ const ObservableContent = (props, context) => { * Displays a collapsible with a map of observable items. * Filters the results if there is a provided search query. */ -const ObservableSection = (props: SectionProps, context) => { +const ObservableSection = ( + props: { + color?: string; + section: Array; + title: string; + }, + context +) => { const { color = 'grey', section = [], title } = props; if (!section.length) { return null; } - const [searchQuery, setSearchQuery] = useLocalState( - context, - 'searchQuery', - '' - ); - const filteredSection: Observable[] = flow([ - filter((poi) => - poi.name?.toLowerCase().includes(searchQuery?.toLowerCase()) + const [searchQuery] = useLocalState(context, 'searchQuery', ''); + const filteredSection: Array = flow([ + filter((observable) => + isJobOrNameMatch(observable, searchQuery) ), - sortBy((poi) => poi.name.toLowerCase()), + sortBy((observable) => observable.name?.toLowerCase()), ])(section); if (!filteredSection.length) { return null; @@ -245,32 +247,29 @@ const ObservableSection = (props: SectionProps, context) => { ); }; -/** Renders an observable button */ +/** Renders an observable button that has tooltip info for living Observables*/ const ObservableItem = ( props: { color: string; item: Observable }, context ) => { const { act } = useBackend(context); - const { - color, - item: { name, orbiters, ref }, - } = props; - const [autoObserve, setAutoObserve] = useLocalState( - context, - 'autoObserve', - false - ); - const threat = getThreat(orbiters || 0); + const { color, item } = props; + const { extra, full_name, health, name, orbiters, ref } = item; + const [autoObserve] = useLocalState(context, 'autoObserve', false); + const threat = getThreat(orbiters ?? 0); + const displayName = getDisplayName(name, full_name); return (