Ghosts once again can see people's true names and roundstart jobs (#84951)

## About The Pull Request

#83186 made it so ghosts are fooled by disguises, like wearing a mask
and an ID. This PR fixes that behavior, instead always displaying the
person's real name and their face/ID name (if their face name is somehow
different from real name) in brackets.

Additionally, this PR makes orbit menu prioritize "real" job name and
icon, aka the ones the person spawned with. If they don't have an
assigned job, it will fall back to current behavior of looking it up
from their ID. Also, searching people includes both their fake and real
name.

## Why It's Good For The Game

Ghosts really, really shouldn't be fooled by wearing a gas mask and an
ID. **Especially** admin ghosts.

## Changelog
🆑
qol: Ghost orbit menu now always displays person's real name and their
roundstart job and cannot be fooled by disguises.
/🆑
This commit is contained in:
SmArtKar
2024-07-17 19:00:54 -04:00
committed by SkyratBot
parent c05c57338d
commit 0cfb6bf4fe
13 changed files with 101 additions and 26 deletions
+2 -1
View File
@@ -113,5 +113,6 @@
return name_chaser
/// Used by mobs to determine the name for someone wearing a mask, or with a disfigured or missing face. By default just returns the atom's name. add_id_name will control whether or not we append "(as [id_name])".
/atom/proc/get_visible_name(add_id_name)
/// force_real_name will always return real_name and add (as face_name/id_name) if it doesn't match their appearance
/atom/proc/get_visible_name(add_id_name, force_real_name)
return name
+17 -5
View File
@@ -211,14 +211,26 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new)
if(issilicon(player))
serialized["job"] = player.job
serialized["icon"] = "borg"
else
var/obj/item/card/id/id_card = player.get_idcard(hand_first = FALSE)
serialized["job"] = id_card?.get_trim_assignment()
serialized["icon"] = id_card?.get_trim_sechud_icon_state()
return serialized
var/obj/item/card/id/id_card = player.get_idcard(hand_first = FALSE)
serialized["job"] = id_card?.get_trim_assignment()
serialized["icon"] = id_card?.get_trim_sechud_icon_state()
var/datum/job/job = player.mind?.assigned_role
if (isnull(job))
return serialized
serialized["mind_job"] = job.title
var/datum/outfit/outfit = job.get_outfit()
if (isnull(outfit))
return serialized
var/datum/id_trim/trim = outfit.id_trim
if (!isnull(trim))
serialized["mind_icon"] = trim::sechud_icon_state
return serialized
/// Gets a list: Misc data and whether it's critical. Handles all snowflakey type cases
/datum/orbit_menu/proc/get_misc_data(atom/movable/atom_poi) as /list
var/list/misc = list()
@@ -57,15 +57,27 @@
return if_no_id
//repurposed proc. Now it combines get_id_name() and get_face_name() to determine a mob's name variable. Made into a separate proc as it'll be useful elsewhere
/mob/living/carbon/human/get_visible_name(add_id_name = TRUE)
if(HAS_TRAIT(src, TRAIT_UNKNOWN))
return "Unknown"
/mob/living/carbon/human/get_visible_name(add_id_name = TRUE, force_real_name = FALSE)
var/list/identity = list(null, null)
SEND_SIGNAL(src, COMSIG_HUMAN_GET_VISIBLE_NAME, identity)
var/signal_face = LAZYACCESS(identity, VISIBLE_NAME_FACE)
var/signal_id = LAZYACCESS(identity, VISIBLE_NAME_ID)
var/face_name = !isnull(signal_face) ? signal_face : get_face_name("")
var/id_name = !isnull(signal_id) ? signal_id : get_id_name("")
if (force_real_name)
var/fake_name
if (face_name && face_name != real_name)
fake_name = face_name
if(add_id_name && id_name && id_name != real_name)
if (!isnull(fake_name) && id_name != face_name)
fake_name = "[fake_name]/[id_name]"
else
fake_name = id_name
if (HAS_TRAIT(src, TRAIT_UNKNOWN) || (!face_name && !id_name))
fake_name = "Unknown"
return "[real_name][fake_name ? " (as [fake_name])" : ""]"
if(HAS_TRAIT(src, TRAIT_UNKNOWN))
return "Unknown"
if(face_name)
if(add_id_name && id_name && (id_name != face_name))
return "[face_name] (as [id_name])"
@@ -4,6 +4,7 @@ import { Antagonist, Observable } from './types';
type Props = {
item: Observable | Antagonist;
realNameDisplay: boolean;
};
type IconSettings = {
@@ -22,7 +23,7 @@ const antagIcon: IconSettings = {
};
export function JobIcon(props: Props) {
const { item } = props;
const { item, realNameDisplay } = props;
let iconSettings: IconSettings;
if ('antag' in item) {
@@ -32,16 +33,18 @@ export function JobIcon(props: Props) {
}
// We don't need to cast here but typescript isn't smart enough to know that
const { icon = '', job = '' } = item;
const { icon = '', job = '', mind_icon = '', mind_job = '' } = item;
const usedIcon = realNameDisplay ? mind_icon || icon : icon;
const usedJob = realNameDisplay ? mind_job || job : job;
return (
<div className="JobIcon">
{icon === 'borg' ? (
<Icon color="lightblue" name={JOB2ICON[job]} ml={0.3} mt={0.4} />
<Icon color="lightblue" name={JOB2ICON[usedJob]} ml={0.3} mt={0.4} />
) : (
<DmIcon
icon={iconSettings.dmi}
icon_state={icon}
icon_state={usedIcon}
style={{
transform: iconSettings.transform,
}}
@@ -21,7 +21,8 @@ export function OrbitBlade(props) {
const { data } = useBackend<OrbitData>();
const { orbiting } = data;
const { setBladeOpen } = useContext(OrbitContext);
const { setBladeOpen, realNameDisplay, setRealNameDisplay } =
useContext(OrbitContext);
return (
<Stack vertical width="244px">
@@ -44,6 +45,24 @@ export function OrbitBlade(props) {
<Stack.Item>
<ViewModeSelector />
</Stack.Item>
<Stack.Item>
<Section
buttons={
<Button
color="transparent"
icon="passport"
selected={realNameDisplay}
onClick={() => setRealNameDisplay(!realNameDisplay)}
/>
}
color="label"
title="Real Name Display"
>
Real Name mode will display actual character names and their
roundstart jobs insteas of being based on their worn ID. If the person
lacks a roundstart job, it will still display their ID job icon.
</Section>
</Stack.Item>
{!!orbiting && (
<Stack.Item>
<OrbitInfo />
@@ -115,7 +134,7 @@ function OrbitInfo(props) {
<Stack.Item>
<Stack>
<Stack.Item>
<JobIcon item={orbiting} />
<JobIcon item={orbiting} realNameDisplay={false} />
</Stack.Item>
<Stack.Item color="label" grow>
{job}
@@ -25,7 +25,8 @@ type Props = {
export function OrbitCollapsible(props: Props) {
const { color, section = [], title } = props;
const { autoObserve, searchQuery, viewMode } = useContext(OrbitContext);
const { autoObserve, realNameDisplay, searchQuery, viewMode } =
useContext(OrbitContext);
const filteredSection = section.filter((observable) =>
isJobOrNameMatch(observable, searchQuery),
@@ -53,6 +54,7 @@ export function OrbitCollapsible(props: Props) {
const content = (
<OrbitItem
autoObserve={autoObserve}
realNameDisplay={realNameDisplay}
color={color}
item={item}
key={item.ref}
@@ -66,7 +68,7 @@ export function OrbitCollapsible(props: Props) {
return (
<Tooltip
content={<OrbitTooltip item={item} />}
content={<OrbitTooltip item={item} realNameDisplay={false} />}
key={item.ref}
position="bottom-start"
>
@@ -9,13 +9,14 @@ import { Antagonist, Observable, OrbitData, ViewMode } from './types';
type Props = {
item: Observable | Antagonist;
autoObserve: boolean;
realNameDisplay: boolean;
viewMode: ViewMode;
color: string | undefined;
};
/** Each button on the observable section */
export function OrbitItem(props: Props) {
const { item, autoObserve, viewMode, color } = props;
const { item, autoObserve, realNameDisplay, viewMode, color } = props;
const { full_name, icon, job, name, orbiters, ref } = item;
const { act, data } = useBackend<OrbitData>();
@@ -33,7 +34,7 @@ export function OrbitItem(props: Props) {
display: 'flex',
}}
>
{validIcon && <JobIcon item={item} />}
{validIcon && <JobIcon item={item} realNameDisplay={realNameDisplay} />}
<Button
color={getDisplayColor(item, viewMode, color)}
@@ -41,7 +42,9 @@ export function OrbitItem(props: Props) {
>
<Stack>
<Stack.Item>
{capitalizeFirst(getDisplayName(full_name, name))}
{realNameDisplay
? capitalizeFirst(name || full_name)
: capitalizeFirst(getDisplayName(full_name, name))}
</Stack.Item>
{!!orbiters && (
<Stack.Item>
@@ -12,10 +12,12 @@ export function OrbitSearchBar(props) {
const {
autoObserve,
bladeOpen,
realNameDisplay,
searchQuery,
viewMode,
setAutoObserve,
setBladeOpen,
setRealNameDisplay,
setSearchQuery,
setViewMode,
} = useContext(OrbitContext);
@@ -103,6 +105,17 @@ export function OrbitSearchBar(props) {
tooltipPosition="bottom-start"
/>
</Stack.Item>
<Stack.Item>
<Button
color="transparent"
icon="passport"
onClick={() => setRealNameDisplay(!realNameDisplay)}
selected={realNameDisplay}
tooltip="Toggle real name display. When active, you'll see real
names instead of disguises in orbit menu."
tooltipPosition="bottom-start"
/>
</Stack.Item>
<Stack.Item>
<Button
color="transparent"
@@ -3,12 +3,13 @@ import { Antagonist, Observable } from './types';
type Props = {
item: Observable | Antagonist;
realNameDisplay: boolean;
};
/** Displays some info on the mob as a tooltip. */
export function OrbitTooltip(props: Props) {
const { item } = props;
const { extra, full_name, health, job } = item;
const { item, realNameDisplay } = props;
const { extra, full_name, health, job, mind_job } = item;
let antag;
if ('antag' in item) {
@@ -18,6 +19,7 @@ export function OrbitTooltip(props: Props) {
const extraInfo = extra?.split(':');
const displayHealth = !!health && health >= 0 ? `${health}%` : 'Critical';
const showAFK = 'client' in item && !item.client;
const displayJob = realNameDisplay ? mind_job : job;
return (
<>
@@ -34,8 +36,8 @@ export function OrbitTooltip(props: Props) {
{!!full_name && (
<LabeledList.Item label="Real ID">{full_name}</LabeledList.Item>
)}
{!!job && !antag && (
<LabeledList.Item label="Job">{job}</LabeledList.Item>
{!!displayJob && !antag && (
<LabeledList.Item label="Job">{displayJob}</LabeledList.Item>
)}
{!!antag && (
<LabeledList.Item label="Threat">{antag}</LabeledList.Item>
@@ -126,10 +126,11 @@ export function isJobOrNameMatch(
): boolean {
if (!searchQuery) return true;
const { full_name, job } = observable;
const { full_name, job, name } = observable;
return (
full_name?.toLowerCase().includes(searchQuery?.toLowerCase()) ||
name?.toLowerCase().includes(searchQuery?.toLowerCase()) ||
job?.toLowerCase().includes(searchQuery?.toLowerCase()) ||
false
);
@@ -11,6 +11,7 @@ import { ViewMode } from './types';
export function Orbit(props) {
const [autoObserve, setAutoObserve] = useState(false);
const [bladeOpen, setBladeOpen] = useState(false);
const [realNameDisplay, setRealNameDisplay] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [viewMode, setViewMode] = useState<ViewMode>(VIEWMODE.Health);
@@ -23,6 +24,8 @@ export function Orbit(props) {
setAutoObserve,
bladeOpen,
setBladeOpen,
realNameDisplay,
setRealNameDisplay,
searchQuery,
setSearchQuery,
viewMode,
@@ -59,6 +62,8 @@ type Context = {
setAutoObserve: Dispatch<SetStateAction<boolean>>;
bladeOpen: boolean;
setBladeOpen: Dispatch<SetStateAction<boolean>>;
realNameDisplay: boolean;
setRealNameDisplay: Dispatch<SetStateAction<boolean>>;
searchQuery: string;
setSearchQuery: Dispatch<SetStateAction<string>>;
viewMode: ViewMode;
@@ -27,7 +27,9 @@ export type Observable = {
extra: string;
health: number;
icon: string;
mind_icon: string;
job: string;
mind_job: string;
name: string;
orbiters: number;
}>;
@@ -1,8 +1,8 @@
.JobIcon {
background: black;
height: 20px;
overflow: clip;
padding: 1px 1px 0 1px;
overflow: hidden;
width: 20px;
}