Orbit refactor + features part dos: Icons, health bars. (#70580)

* orbit refactor + features

* Via suggestion

Co-Authored-By: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-Authored-By: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>

* cleanup

* readds heatmap optional + bug fix

* fixes bugs

* naming conventions

* what

* indeed

* modular via suggestion

Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>

* nice

* pai fix

* misc searching

Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
This commit is contained in:
Jeremiah
2022-10-19 22:23:13 -04:00
committed by GitHub
co-authored by John Willard LemonInTheDark
parent 986d9516e7
commit 03f2f3e2b0
7 changed files with 278 additions and 185 deletions
+2
View File
@@ -16,6 +16,8 @@
var/intern_alt_name = null
/// The icon_state associated with this trim, as it will show on the security HUD.
var/sechud_icon_state = SECHUD_UNKNOWN
/// Icons to be displayed in the orbit ui. Source: FontAwesome v5.
var/orbit_icon
/// Accesses that this trim unlocks on a card it is imprinted on. These accesses never take wildcard slots and can be added and removed at will.
var/list/access = list()
+33
View File
@@ -79,6 +79,7 @@
/datum/id_trim/job/assistant
assignment = "Assistant"
trim_state = "trim_assistant"
orbit_icon = "toolbox"
sechud_icon_state = SECHUD_ASSISTANT
minimal_access = list()
extra_access = list(
@@ -105,6 +106,7 @@
/datum/id_trim/job/atmospheric_technician
assignment = "Atmospheric Technician"
trim_state = "trim_atmospherictechnician"
orbit_icon = "fan"
department_color = COLOR_ENGINEERING_ORANGE
subdepartment_color = COLOR_ENGINEERING_ORANGE
sechud_icon_state = SECHUD_ATMOSPHERIC_TECHNICIAN
@@ -134,6 +136,7 @@
/datum/id_trim/job/bartender
assignment = "Bartender"
trim_state = "trim_bartender"
orbit_icon = "cocktail"
department_color = COLOR_SERVICE_LIME
subdepartment_color = COLOR_SERVICE_LIME
sechud_icon_state = SECHUD_BARTENDER
@@ -158,6 +161,7 @@
/datum/id_trim/job/botanist
assignment = "Botanist"
trim_state = "trim_botanist"
orbit_icon = "seedling"
department_color = COLOR_SERVICE_LIME
subdepartment_color = COLOR_SERVICE_LIME
sechud_icon_state = SECHUD_BOTANIST
@@ -182,6 +186,7 @@
assignment = "Captain"
intern_alt_name = "Captain-in-Training"
trim_state = "trim_captain"
orbit_icon = "crown"
department_color = COLOR_COMMAND_BLUE
subdepartment_color = COLOR_COMMAND_BLUE
department_state = "departmenthead"
@@ -204,6 +209,7 @@
/datum/id_trim/job/cargo_technician
assignment = "Cargo Technician"
trim_state = "trim_cargotechnician"
orbit_icon = "box"
department_color = COLOR_CARGO_BROWN
subdepartment_color = COLOR_CARGO_BROWN
sechud_icon_state = SECHUD_CARGO_TECHNICIAN
@@ -228,6 +234,7 @@
/datum/id_trim/job/chaplain
assignment = "Chaplain"
trim_state = "trim_chaplain"
orbit_icon = "cross"
department_color = COLOR_SERVICE_LIME
subdepartment_color = COLOR_SERVICE_LIME
sechud_icon_state = SECHUD_CHAPLAIN
@@ -249,6 +256,7 @@
/datum/id_trim/job/chemist
assignment = "Chemist"
trim_state = "trim_chemist"
orbit_icon = "prescription-bottle"
department_color = COLOR_MEDICAL_BLUE
subdepartment_color = COLOR_MEDICAL_BLUE
sechud_icon_state = SECHUD_CHEMIST
@@ -275,6 +283,7 @@
assignment = "Chief Engineer"
intern_alt_name = "Chief Engineer-in-Training"
trim_state = "trim_stationengineer"
orbit_icon = "user-astronaut"
department_color = COLOR_COMMAND_BLUE
subdepartment_color = COLOR_ENGINEERING_ORANGE
department_state = "departmenthead"
@@ -316,6 +325,7 @@
assignment = "Chief Medical Officer"
intern_alt_name = "Chief Medical Officer-in-Training"
trim_state = "trim_medicaldoctor"
orbit_icon = "user-md"
department_color = COLOR_COMMAND_BLUE
subdepartment_color = COLOR_MEDICAL_BLUE
department_state = "departmenthead"
@@ -353,6 +363,7 @@
/datum/id_trim/job/clown
assignment = "Clown"
trim_state = "trim_clown"
orbit_icon = "face-grin-tears"
department_color = COLOR_MAGENTA
subdepartment_color = COLOR_MAGENTA
sechud_icon_state = SECHUD_CLOWN
@@ -371,6 +382,7 @@
/datum/id_trim/job/cook
assignment = "Cook"
trim_state = "trim_cook"
orbit_icon = "utensils"
department_color = COLOR_SERVICE_LIME
subdepartment_color = COLOR_SERVICE_LIME
sechud_icon_state = SECHUD_COOK
@@ -398,6 +410,7 @@
/datum/id_trim/job/curator
assignment = "Curator"
trim_state = "trim_curator"
orbit_icon = "book"
department_color = COLOR_SERVICE_LIME
subdepartment_color = COLOR_SERVICE_LIME
sechud_icon_state = SECHUD_CURATOR
@@ -418,6 +431,7 @@
/datum/id_trim/job/detective
assignment = "Detective"
trim_state = "trim_detective"
orbit_icon = "user-secret"
department_color = COLOR_SECURITY_RED
subdepartment_color = COLOR_SECURITY_RED
sechud_icon_state = SECHUD_DETECTIVE
@@ -455,6 +469,7 @@
/datum/id_trim/job/geneticist
assignment = "Geneticist"
trim_state = "trim_geneticist"
orbit_icon = "dna"
department_color = COLOR_SCIENCE_PINK
subdepartment_color = COLOR_SCIENCE_PINK
sechud_icon_state = SECHUD_GENETICIST
@@ -482,6 +497,7 @@
assignment = "Head of Personnel"
intern_alt_name = "Head of Personnel-in-Training"
trim_state = "trim_headofpersonnel"
orbit_icon = "dog"
department_color = COLOR_COMMAND_BLUE
subdepartment_color = COLOR_SERVICE_LIME
department_state = "departmenthead"
@@ -532,6 +548,7 @@
assignment = "Head of Security"
intern_alt_name = "Head of Security-in-Training"
trim_state = "trim_securityofficer"
orbit_icon = "user-shield"
department_color = COLOR_COMMAND_BLUE
subdepartment_color = COLOR_SECURITY_RED
department_state = "departmenthead"
@@ -588,6 +605,7 @@
/datum/id_trim/job/janitor
assignment = "Janitor"
trim_state = "trim_janitor"
orbit_icon = "broom"
department_color = COLOR_SERVICE_LIME
subdepartment_color = COLOR_SERVICE_LIME
sechud_icon_state = SECHUD_JANITOR
@@ -608,6 +626,7 @@
/datum/id_trim/job/lawyer
assignment = "Lawyer"
trim_state = "trim_lawyer"
orbit_icon = "gavel"
department_color = COLOR_SERVICE_LIME
subdepartment_color = COLOR_SECURITY_RED
sechud_icon_state = SECHUD_LAWYER
@@ -628,6 +647,7 @@
/datum/id_trim/job/medical_doctor
assignment = "Medical Doctor"
trim_state = "trim_medicaldoctor"
orbit_icon = "staff-snake"
department_color = COLOR_MEDICAL_BLUE
subdepartment_color = COLOR_MEDICAL_BLUE
sechud_icon_state = SECHUD_MEDICAL_DOCTOR
@@ -653,6 +673,7 @@
/datum/id_trim/job/mime
assignment = "Mime"
trim_state = "trim_mime"
orbit_icon = "comment-slash"
department_color = COLOR_SILVER
subdepartment_color = COLOR_WHITE
sechud_icon_state = SECHUD_MIME
@@ -671,6 +692,7 @@
/datum/id_trim/job/paramedic
assignment = "Paramedic"
trim_state = "trim_paramedic"
orbit_icon = "truck-medical"
department_color = COLOR_MEDICAL_BLUE
subdepartment_color = COLOR_MEDICAL_BLUE
sechud_icon_state = SECHUD_PARAMEDIC
@@ -700,6 +722,7 @@
/datum/id_trim/job/prisoner
assignment = "Prisoner"
trim_state = "trim_warden"
orbit_icon = "lock-keyhole"
department_color = COLOR_PRISONER_BLACK
subdepartment_color = COLOR_PRISONER_ORANGE
sechud_icon_state = SECHUD_PRISONER
@@ -742,6 +765,7 @@
/datum/id_trim/job/psychologist
assignment = "Psychologist"
trim_state = "trim_psychologist"
orbit_icon = "brain"
department_color = COLOR_SERVICE_LIME
subdepartment_color = COLOR_MEDICAL_BLUE
sechud_icon_state = SECHUD_PSYCHOLOGIST
@@ -762,6 +786,7 @@
/datum/id_trim/job/quartermaster
assignment = "Quartermaster"
trim_state = "trim_quartermaster"
orbit_icon = "sack-dollar"
department_color = COLOR_COMMAND_BLUE
subdepartment_color = COLOR_CARGO_BROWN
department_state = "departmenthead"
@@ -797,6 +822,7 @@
assignment = "Research Director"
intern_alt_name = "Research Director-in-Training"
trim_state = "trim_scientist"
orbit_icon = "user-graduate"
department_color = COLOR_COMMAND_BLUE
subdepartment_color = COLOR_SCIENCE_PINK
department_state = "departmenthead"
@@ -844,6 +870,7 @@
/datum/id_trim/job/roboticist
assignment = "Roboticist"
trim_state = "trim_roboticist"
orbit_icon = "battery-half"
department_color = COLOR_SCIENCE_PINK
subdepartment_color = COLOR_SCIENCE_PINK
sechud_icon_state = SECHUD_ROBOTICIST
@@ -871,6 +898,7 @@
/datum/id_trim/job/scientist
assignment = "Scientist"
trim_state = "trim_scientist"
orbit_icon = "flask"
department_color = COLOR_SCIENCE_PINK
subdepartment_color = COLOR_SCIENCE_PINK
sechud_icon_state = SECHUD_SCIENTIST
@@ -899,6 +927,7 @@
/datum/id_trim/job/security_officer
assignment = "Security Officer"
trim_state = "trim_securityofficer"
orbit_icon = "shield-halved"
department_color = COLOR_SECURITY_RED
subdepartment_color = COLOR_SECURITY_RED
sechud_icon_state = SECHUD_SECURITY_OFFICER
@@ -1019,6 +1048,7 @@
/datum/id_trim/job/shaft_miner
assignment = "Shaft Miner"
trim_state = "trim_shaftminer"
orbit_icon = "digging"
department_color = COLOR_CARGO_BROWN
subdepartment_color = COLOR_SCIENCE_PINK
sechud_icon_state = SECHUD_SHAFT_MINER
@@ -1055,6 +1085,7 @@
/datum/id_trim/job/station_engineer
assignment = "Station Engineer"
trim_state = "trim_stationengineer"
orbit_icon = "gears"
department_color = COLOR_ENGINEERING_ORANGE
subdepartment_color = COLOR_ENGINEERING_ORANGE
sechud_icon_state = SECHUD_STATION_ENGINEER
@@ -1084,6 +1115,7 @@
/datum/id_trim/job/virologist
assignment = "Virologist"
trim_state = "trim_virologist"
orbit_icon = "virus"
department_color = COLOR_MEDICAL_BLUE
subdepartment_color = COLOR_MEDICAL_BLUE
sechud_icon_state = SECHUD_VIROLOGIST
@@ -1108,6 +1140,7 @@
/datum/id_trim/job/warden
assignment = "Warden"
trim_state = "trim_warden"
orbit_icon = "handcuffs"
department_color = COLOR_SECURITY_RED
subdepartment_color = COLOR_SECURITY_RED
sechud_icon_state = SECHUD_WARDEN
+11 -8
View File
@@ -81,14 +81,18 @@ 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
if(isliving(mob_poi)) // handles edge cases like blob
var/mob/living/player = mob_poi
serialized["health"] = FLOOR((player.health / player.maxHealth * 100), 1)
if(issilicon(player))
serialized["job"] = player.job
else
var/obj/item/card/id/id_card = player.get_idcard(hand_first = FALSE)
serialized["job"] = id_card?.get_trim_assignment()
var/datum/id_trim/trim = id_card?.trim
serialized["job_icon"] = trim?.orbit_icon
for(var/datum/antagonist/antag_datum as anything in mind.antag_datums)
if (antag_datum.show_to_ghosts)
@@ -105,8 +109,7 @@ 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
"full_name" = name,
))
// Display the supermatter crystal integrity
@@ -0,0 +1,43 @@
export const ANTAG2COLOR = {
'Abductors': 'pink',
'Ash Walkers': 'olive',
'Biohazards': 'brown',
'CentCom': 'teal',
} as const;
export const ANTAG2GROUP = {
'Abductor Agent': 'Abductors',
'Abductor Scientist': 'Abductors',
'Ash Walker': 'Ash Walkers',
'Blob': 'Biohazards',
'Sentient Disease': 'Biohazards',
'CentCom Commander': 'CentCom',
'CentCom Head Intern': 'CentCom',
'CentCom Intern': 'CentCom',
'CentCom Official': 'CentCom',
'Central Command': 'CentCom',
'Clown Operative': 'Clown Operatives',
'Clown Operative Leader': 'Clown Operatives',
'Nuclear Operative': 'Nuclear Operatives',
'Nuclear Operative Leader': 'Nuclear Operatives',
'Space Wizard': 'Wizard Federation',
'Wizard Apprentice': 'Wizard Federation',
'Wizard Minion': 'Wizard Federation',
} as const;
export const JOB2ICON = {
'AI': 'eye',
'Cyborg': 'robot',
'Personal AI': 'mobile-alt',
} as const;
export enum THREAT {
Low = 2,
Medium = 5,
High = 8,
}
export enum HEALTH {
Good = 69,
Average = 19,
}
@@ -0,0 +1,106 @@
import { sortBy } from 'common/collections';
import { ANTAG2GROUP, HEALTH, THREAT } from './constants';
import type { AntagGroup, Antags, Observable } from './types';
/**
* Collates antagonist groups into their own separate sections.
* Some antags are grouped together lest they be listed separately,
* ie: Nuclear Operatives. See: ANTAG_GROUPS.
*/
export const collateAntagonists = (antagonists: Antags) => {
const collatedAntagonists = {}; // Hate that I cant use a map here
antagonists.map((player) => {
const { antag } = player;
const resolvedName: string = ANTAG2GROUP[antag] || antag;
if (!collatedAntagonists[resolvedName]) {
collatedAntagonists[resolvedName] = [];
}
collatedAntagonists[resolvedName].push(player);
});
const sortedAntagonists = sortBy<AntagGroup>(([key]) => key)(
Object.entries(collatedAntagonists)
);
return sortedAntagonists;
};
/** Returns a disguised name in case the person is wearing someone else's ID */
export const getDisplayName = (full_name: string, name?: string) => {
if (!name) {
return full_name;
}
if (
!full_name?.includes('[') ||
full_name.match(/\(as /) ||
full_name.match(/^Unknown/)
) {
return name;
}
// return only the name before the first ' [' or ' ('
return `"${full_name.split(/ \[| \(/)[0]}"`;
};
/** Returns the display color for certain health percentages */
const getHealthColor = (health: number) => {
switch (true) {
case health > HEALTH.Good:
return 'good';
case health > HEALTH.Average:
return 'average';
default:
return 'bad';
}
};
/** Returns the display color based on orbiter numbers */
const getThreatColor = (orbiters: number) => {
switch (true) {
case orbiters > THREAT.High:
return 'violet';
case orbiters > THREAT.Medium:
return 'blue';
case orbiters > THREAT.Low:
return 'teal';
default:
return 'good';
}
};
/**
* ### getDisplayColor
* Displays color for buttons based on the health or orbiter count. Toggleable.
* @param {Observable} item - The point of interest.
* @param {boolean} heatMap - Whether the user has heat map toggled.
* @param {string} color - OPTIONAL: The color to default to.
*/
export const getDisplayColor = (
item: Observable,
heatMap: boolean,
color?: string
) => {
const { health, orbiters = 0 } = item;
if (typeof health !== 'number') {
return color ? 'good' : 'grey';
}
if (heatMap) {
return getThreatColor(orbiters);
}
return getHealthColor(health);
};
/** Checks if a full name or job title matches the search. */
export const isJobOrNameMatch = (
observable: Observable,
searchQuery: string
) => {
if (!searchQuery) {
return true;
}
const { full_name, job } = observable;
return (
full_name?.toLowerCase().includes(searchQuery?.toLowerCase()) ||
job?.toLowerCase().includes(searchQuery?.toLowerCase()) ||
false
);
};
@@ -1,73 +1,19 @@
import { useBackend, useLocalState } from '../backend';
import { filter, sortBy } from 'common/collections';
import { capitalizeFirst, multiline } from 'common/string';
import { Box, Button, Collapsible, Icon, Input, LabeledList, NoticeBox, Section, Stack } from '../components';
import { Window } from '../layouts';
import { flow } from 'common/fp';
type AntagGroup = [string, Antags];
type Antags = Array<Observable & { antag: string }>;
type Data = {
alive: Array<Observable>;
antagonists: Antags;
dead: Array<Observable>;
ghosts: Array<Observable>;
misc: Array<Observable>;
npcs: Array<Observable>;
};
type Observable = {
extra?: string;
full_name: string;
health?: number;
job?: string;
name?: string;
orbiters?: number;
ref: string;
};
const ANTAG2COLOR = {
'Abductors': 'pink',
'Ash Walkers': 'olive',
'Biohazards': 'brown',
'CentCom': 'teal',
} as const;
const ANTAG2GROUP = {
'Abductor Agent': 'Abductors',
'Abductor Scientist': 'Abductors',
'Ash Walker': 'Ash Walkers',
'Blob': 'Biohazards',
'Sentient Disease': 'Biohazards',
'CentCom Commander': 'CentCom',
'CentCom Head Intern': 'CentCom',
'CentCom Intern': 'CentCom',
'CentCom Official': 'CentCom',
'Central Command': 'CentCom',
'Clown Operative': 'Clown Operatives',
'Clown Operative Leader': 'Clown Operatives',
'Nuclear Operative': 'Nuclear Operatives',
'Nuclear Operative Leader': 'Nuclear Operatives',
'Space Wizard': 'Wizard Federation',
'Wizard Apprentice': 'Wizard Federation',
'Wizard Minion': 'Wizard Federation',
} as const;
enum THREAT {
None,
Small = 'teal',
Medium = 'blue',
Large = 'violet',
}
import { capitalizeFirst, multiline } from 'common/string';
import { useBackend, useLocalState } from 'tgui/backend';
import { Button, Collapsible, Icon, Input, LabeledList, NoticeBox, Section, Stack } from 'tgui/components';
import { Window } from 'tgui/layouts';
import { collateAntagonists, getDisplayColor, getDisplayName, isJobOrNameMatch } from './helpers';
import { ANTAG2COLOR, JOB2ICON } from './constants';
import type { AntagGroup, Observable, OrbitData } from './types';
export const Orbit = (props, context) => {
return (
<Window title="Orbit" width={400} height={550}>
<Window.Content scrollable>
<Stack fill vertical>
<Stack.Item mt={0}>
<Stack.Item>
<ObservableSearch />
</Stack.Item>
<Stack.Item mt={0.2} grow>
@@ -83,7 +29,7 @@ export const Orbit = (props, context) => {
/** Controls filtering out the list of observables via search */
const ObservableSearch = (props, context) => {
const { act, data } = useBackend<Data>(context);
const { act, data } = useBackend<OrbitData>(context);
const {
alive = [],
antagonists = [],
@@ -97,13 +43,18 @@ const ObservableSearch = (props, context) => {
'autoObserve',
false
);
const [heatMap, setHeatMap] = useLocalState<boolean>(
context,
'heatMap',
false
);
const [searchQuery, setSearchQuery] = useLocalState<string>(
context,
'searchQuery',
''
);
/** Gets a list of Observables, then filters the most relevant to orbit */
const orbitMostRelevant = (searchQuery: string): void => {
const orbitMostRelevant = (searchQuery: string) => {
/** Returns the most orbited observable that matches the search. */
const mostRelevant: Observable = flow([
// Filters out anything that doesn't match search
@@ -139,6 +90,16 @@ const ObservableSearch = (props, context) => {
/>
</Stack.Item>
<Stack.Divider />
<Stack.Item>
<Button
color="transparent"
icon={!heatMap ? 'heart' : 'ghost'}
onClick={() => setHeatMap(!heatMap)}
tooltip={multiline`Toggles between highlighting health or
orbiters.`}
tooltipPosition="bottom-start"
/>
</Stack.Item>
<Stack.Item>
<Button
color={autoObserve ? 'good' : 'transparent'}
@@ -151,12 +112,11 @@ const ObservableSearch = (props, context) => {
</Stack.Item>
<Stack.Item>
<Button
inline
color="transparent"
tooltip="Refresh"
tooltipPosition="bottom-start"
icon="sync-alt"
onClick={() => act('refresh')}
tooltip="Refresh"
tooltipPosition="bottom-start"
/>
</Stack.Item>
</Stack>
@@ -170,7 +130,7 @@ const ObservableSearch = (props, context) => {
* observable group.
*/
const ObservableContent = (props, context) => {
const { data } = useBackend<Data>(context);
const { data } = useBackend<OrbitData>(context);
const {
alive = [],
antagonists = [],
@@ -196,7 +156,7 @@ const ObservableContent = (props, context) => {
/>
);
})}
<ObservableSection color="good" section={alive} title="Alive" />
<ObservableSection color="blue" section={alive} title="Alive" />
<ObservableSection section={dead} title="Dead" />
<ObservableSection section={ghosts} title="Ghosts" />
<ObservableSection section={misc} title="Misc" />
@@ -217,7 +177,7 @@ const ObservableSection = (
},
context
) => {
const { color = 'grey', section = [], title } = props;
const { color, section = [], title } = props;
if (!section.length) {
return null;
}
@@ -226,7 +186,11 @@ const ObservableSection = (
filter<Observable>((observable) =>
isJobOrNameMatch(observable, searchQuery)
),
sortBy<Observable>((observable) => observable.name?.toLowerCase()),
sortBy<Observable>((observable) =>
getDisplayName(observable.full_name, observable.name)
.replace(/^"/, '')
.toLowerCase()
),
])(section);
if (!filteredSection.length) {
return null;
@@ -236,8 +200,8 @@ const ObservableSection = (
<Stack.Item>
<Collapsible
bold
color={color}
open={color !== 'grey'}
color={color ?? 'grey'}
open={!!color}
title={title + ` - (${filteredSection.length})`}>
{filteredSection.map((poi, index) => {
return <ObservableItem color={color} item={poi} key={index} />;
@@ -249,28 +213,28 @@ const ObservableSection = (
/** Renders an observable button that has tooltip info for living Observables*/
const ObservableItem = (
props: { color: string; item: Observable },
props: { color?: string; item: Observable },
context
) => {
const { act } = useBackend<Data>(context);
const { act } = useBackend<OrbitData>(context);
const { color, item } = props;
const { extra, full_name, health, name, orbiters, ref } = item;
const { extra, full_name, job, job_icon, health, name, orbiters, ref } = item;
const [autoObserve] = useLocalState<boolean>(context, 'autoObserve', false);
const threat = getThreat(orbiters ?? 0);
const displayName = getDisplayName(name, full_name);
const [heatMap] = useLocalState<boolean>(context, 'heatMap', false);
return (
<Button
color={threat || color}
color={getDisplayColor(item, heatMap, color)}
icon={job_icon || (job && JOB2ICON[job]) || null}
onClick={() => act('orbit', { auto_observe: autoObserve, ref: ref })}
tooltip={(!!health || !!extra) && <ObservableTooltip item={item} />}
tooltipPosition="bottom-start">
{capitalizeFirst(displayName)}
{capitalizeFirst(getDisplayName(full_name, name))}
{!!orbiters && (
<>
{' '}
({orbiters}{' '}
<Icon mr={0} name={threat === THREAT.Large ? 'skull' : 'ghost'} />)
<Icon mr={0} name={'ghost'} />
{orbiters}
</>
)}
</Button>
@@ -283,6 +247,7 @@ const ObservableTooltip = (props: { item: Observable }) => {
item: { extra, full_name, job, health },
} = props;
const extraInfo = extra?.split(':');
const displayHealth = !!health && health >= 0 ? `${health}%` : 'Critical';
return (
<>
@@ -290,106 +255,24 @@ const ObservableTooltip = (props: { item: Observable }) => {
Last Known Data
</NoticeBox>
<LabeledList>
{!!extraInfo && (
{extraInfo ? (
<LabeledList.Item label={extraInfo[0]}>
{extraInfo[1]}
</LabeledList.Item>
)}
{!!full_name && (
<LabeledList.Item label="Name">{full_name}</LabeledList.Item>
)}
{!!job && <LabeledList.Item label="Job">{job}</LabeledList.Item>}
{!!health && (
<LabeledList.Item label="Health">
{getHealthLabel(health!)}
</LabeledList.Item>
) : (
<>
{!!full_name && (
<LabeledList.Item label="Name">{full_name}</LabeledList.Item>
)}
{!!job && <LabeledList.Item label="Job">{job}</LabeledList.Item>}
{!!health && (
<LabeledList.Item label="Health">
{displayHealth}
</LabeledList.Item>
)}
</>
)}
</LabeledList>
</>
);
};
/**
* Collates antagonist groups into their own separate sections.
* Some antags are grouped together lest they be listed separately,
* ie: Nuclear Operatives. See: ANTAG_GROUPS.
*/
const collateAntagonists = (antagonists: Antags) => {
const collatedAntagonists = {}; // Hate that I cant use a map here
antagonists.map((player) => {
const { antag } = player;
const resolvedName: string = ANTAG2GROUP[antag] || antag;
if (!collatedAntagonists[resolvedName]) {
collatedAntagonists[resolvedName] = [];
}
collatedAntagonists[resolvedName].push(player);
});
const sortedAntagonists = sortBy<AntagGroup>(([key]) => key)(
Object.entries(collatedAntagonists)
);
return sortedAntagonists;
};
/** Returns a disguised name in case the person is wearing someone else's ID */
const getDisplayName = (name: string | undefined, full_name: string) => {
if (!name) {
return full_name;
}
if (
!full_name?.includes('[') ||
full_name.match(/\(as /) ||
full_name.match(/^Unknown/)
) {
return name;
}
// return only the name before the first ' [' or ' ('
return `"${full_name.split(/ \[| \(/)[0]}"`;
};
/** Returns some labels for a player's health */
const getHealthLabel = (health: number) => {
if (health >= 100) {
return <Box color="blue">Great</Box>;
}
if (health >= 75) {
return <Box color="green">Good</Box>;
}
if (health >= 50) {
return <Box color="yellow">Fair</Box>;
}
if (health >= 25) {
return <Box color="orange">Poor</Box>;
}
if (health > 0) {
return <Box color="orange">Bad</Box>;
}
if (health <= 0) {
return <Box color="red">Critical</Box>;
}
};
/** Takes the amount of orbiters and returns some style options */
const getThreat = (orbiters: number) => {
if (!orbiters || orbiters <= 2) {
return THREAT.None;
} else if (orbiters === 3) {
return THREAT.Small;
} else if (orbiters <= 6) {
return THREAT.Medium;
} else {
return THREAT.Large;
}
};
/** Checks if a full name or job title matches the search. */
const isJobOrNameMatch = (observable: Observable, searchQuery: string) => {
const { full_name, name, job } = observable;
const displayName = full_name ?? name;
return (
displayName?.toLowerCase().includes(searchQuery?.toLowerCase()) ||
job?.toLowerCase().includes(searchQuery?.toLowerCase()) ||
false
);
};
@@ -0,0 +1,23 @@
export type AntagGroup = [string, Antags];
export type Antags = Array<Observable & { antag: string }>;
export type OrbitData = {
alive: Array<Observable>;
antagonists: Antags;
dead: Array<Observable>;
ghosts: Array<Observable>;
misc: Array<Observable>;
npcs: Array<Observable>;
};
export type Observable = {
extra?: string;
full_name: string;
health?: number;
job?: string;
job_icon?: string;
name?: string;
orbiters?: number;
ref: string;
};