diff --git a/code/__DEFINES/dcs/signals/signals_screentips.dm b/code/__DEFINES/dcs/signals/signals_screentips.dm
index 8f7326ee2ee..31a851c0483 100644
--- a/code/__DEFINES/dcs/signals/signals_screentips.dm
+++ b/code/__DEFINES/dcs/signals/signals_screentips.dm
@@ -21,3 +21,15 @@
/// Tells the contextual screentips system that the list context was mutated.
#define CONTEXTUAL_SCREENTIP_SET (1 << 0)
+
+
+/// A user screentip name override.
+/// These are used for mobs that may override the names of atoms they hover over.
+/// Examples include prosopagnosia (sees human names as Unknown regardless of what they are).
+/// Called on /mob with a mutable screentip name list, the item being used, and the atom hovered over.
+/// A screentip name override list is a list used for returning a string value from the signal. Only the first value matters.
+/// If you mutate the list in this signal, you must return SCREENTIP_NAME_SET.
+#define COMSIG_MOB_REQUESTING_SCREENTIP_NAME_FROM_USER "mob_requesting_screentip_name_from_user"
+
+/// Tells the screentips system that the list names was mutated.
+#define SCREENTIP_NAME_SET (1 << 0)
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index a9e9f5f1aab..82301ffb484 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -44,6 +44,10 @@
#define VENTCRAWLER_NUDE 1
#define VENTCRAWLER_ALWAYS 2
+// Flags for the mob_flags var on /mob
+/// May override the names used in screentips of OTHER OBJECTS hovered over.
+#define MOB_HAS_SCREENTIPS_NAME_OVERRIDE (1 << 0)
+
//Mob bio-types flags
///The mob is organic, can heal from medical sutures.
#define MOB_ORGANIC (1 << 0)
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
index 7f6735cb5d7..0865867196c 100644
--- a/code/_globalvars/bitfields.dm
+++ b/code/_globalvars/bitfields.dm
@@ -259,6 +259,10 @@ DEFINE_BITFIELD(mob_biotypes, list(
"MOB_UNDEAD" = MOB_UNDEAD,
))
+DEFINE_BITFIELD(mob_flags, list(
+ "MOB_HAS_SCREENTIPS_NAME_OVERRIDE" = MOB_HAS_SCREENTIPS_NAME_OVERRIDE,
+))
+
DEFINE_BITFIELD(mob_respiration_type, list(
"RESPIRATION_OXYGEN" = RESPIRATION_OXYGEN,
"RESPIRATION_N2" = RESPIRATION_N2,
diff --git a/code/datums/quirks/negative_quirks/prosopagnosia.dm b/code/datums/quirks/negative_quirks/prosopagnosia.dm
index 8634e13bf63..9b41713e6ce 100644
--- a/code/datums/quirks/negative_quirks/prosopagnosia.dm
+++ b/code/datums/quirks/negative_quirks/prosopagnosia.dm
@@ -7,3 +7,19 @@
medical_record_text = "Patient suffers from prosopagnosia and cannot recognize faces."
hardcore_value = 5
mail_goodies = list(/obj/item/skillchip/appraiser) // bad at recognizing faces but good at recognizing IDs
+
+/datum/quirk/prosopagnosia/add(client/client_source)
+ RegisterSignal(quirk_holder, COMSIG_MOB_REQUESTING_SCREENTIP_NAME_FROM_USER, PROC_REF(screentip_name_override))
+ quirk_holder.mob_flags |= MOB_HAS_SCREENTIPS_NAME_OVERRIDE
+
+/datum/quirk/prosopagnosia/remove()
+ UnregisterSignal(quirk_holder, COMSIG_MOB_REQUESTING_SCREENTIP_NAME_FROM_USER)
+
+/datum/quirk/prosopagnosia/proc/screentip_name_override(datum/source, list/returned_name, obj/item/held_item, atom/hovered)
+ SIGNAL_HANDLER
+
+ if(!ishuman(hovered))
+ return NONE
+
+ returned_name[1] = "Unknown"
+ return SCREENTIP_NAME_SET
diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm
index b45912c6053..8b6e630c34d 100644
--- a/code/game/atom/_atom.dm
+++ b/code/game/atom/_atom.dm
@@ -881,10 +881,18 @@
var/shift_lmb_ctrl_shift_lmb_line = ""
var/extra_lines = 0
var/extra_context = ""
+ var/used_name = name
if(isliving(user) || isovermind(user) || isaicamera(user) || (ghost_screentips && isobserver(user)))
var/obj/item/held_item = user.get_active_held_item()
+ if (user.mob_flags & MOB_HAS_SCREENTIPS_NAME_OVERRIDE)
+ var/list/returned_name = list(used_name)
+
+ var/name_override_returns = SEND_SIGNAL(user, COMSIG_MOB_REQUESTING_SCREENTIP_NAME_FROM_USER, returned_name, held_item, src)
+ if (name_override_returns & SCREENTIP_NAME_SET)
+ used_name = returned_name[1]
+
if (flags_1 & HAS_CONTEXTUAL_SCREENTIPS_1 || held_item?.item_flags & ITEM_HAS_CONTEXTUAL_SCREENTIPS)
var/list/context = list()
@@ -950,9 +958,9 @@
new_maptext = ""
else
//We inline a MAPTEXT() here, because there's no good way to statically add to a string like this
- new_maptext = "[name][extra_context]"
+ new_maptext = "[used_name][extra_context]"
- if (length(name) * 10 > active_hud.screentip_text.maptext_width)
+ if (length(used_name) * 10 > active_hud.screentip_text.maptext_width)
INVOKE_ASYNC(src, PROC_REF(set_hover_maptext), client, active_hud, new_maptext)
return
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 7f439db6bc9..c5192e681a1 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -63,6 +63,9 @@
///Cursor icon used when holding shift over things
var/examine_cursor_icon = 'icons/effects/mouse_pointers/examine_pointer.dmi'
+ /// Mob bitflags
+ var/mob_flags = NONE
+
/// Whether a mob is alive or dead. TODO: Move this to living - Nodrak (2019, still here)
var/stat = CONSCIOUS