diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 2b8dc67cb684..82599891087e 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -224,19 +224,29 @@
#define COMSIG_LIVING_GET_PULLED "living_start_pulled"
/////////////////
-
-#define COMSIG_ENTER_AREA "enter_area" //from base of area/Entered(): (/area). Sent to "area-sensitive" movables, see __DEFINES/traits.dm for info.
-#define COMSIG_EXIT_AREA "exit_area" //from base of area/Exited(): (/area). Sent to "area-sensitive" movables, see __DEFINES/traits.dm for info.
-
-#define COMSIG_CLICK "atom_click" //from base of atom/Click(): (location, control, params, mob/user)
-#define COMSIG_CLICK_SHIFT "shift_click" //from base of atom/ShiftClick(): (/mob)
- #define COMPONENT_ALLOW_EXAMINATE 1 //Allows the user to examinate regardless of client.eye.
-#define COMSIG_CLICK_CTRL "ctrl_click" //from base of atom/CtrlClickOn(): (/mob)
-#define COMSIG_CLICK_ALT "alt_click" //from base of atom/AltClick(): (/mob)
-#define COMSIG_CLICK_CTRL_SHIFT "ctrl_shift_click" //from base of atom/CtrlShiftClick(/mob)
-#define COMSIG_MOUSEDROP_ONTO "mousedrop_onto" //from base of atom/MouseDrop(): (/atom/over, /mob/user)
+//from base of area/Entered(): (/area). Sent to "area-sensitive" movables, see __DEFINES/traits.dm for info.
+#define COMSIG_ENTER_AREA "enter_area"
+//from base of area/Exited(): (/area). Sent to "area-sensitive" movables, see __DEFINES/traits.dm for info.
+#define COMSIG_EXIT_AREA "exit_area"
+//from base of atom/Click(): (location, control, params, mob/user)
+#define COMSIG_CLICK "atom_click"
+//from base of atom/ShiftClick(): (/mob)
+#define COMSIG_CLICK_SHIFT "shift_click"
+//Allows the user to examinate regardless of client.eye.
+ #define COMPONENT_ALLOW_EXAMINATE 1
+//from base of atom/CtrlClickOn(): (/mob)
+#define COMSIG_CLICK_CTRL "ctrl_click"
+//from base of atom/AltClick(): (/mob)
+#define COMSIG_CLICK_ALT "alt_click"
+//from base of atom/CtrlShiftClick(/mob)
+#define COMSIG_CLICK_CTRL_SHIFT "ctrl_shift_click"
+///from base of atom/CtrlShiftRightClick(/mob)
+#define COMSIG_CLICK_CTRL_SHIFT_RIGHT "ctrl_shift_right_click"
+//from base of atom/MouseDrop(): (/atom/over, /mob/user)
+#define COMSIG_MOUSEDROP_ONTO "mousedrop_onto"
#define COMPONENT_NO_MOUSEDROP 1
-#define COMSIG_MOUSEDROPPED_ONTO "mousedropped_onto" //from base of atom/MouseDrop_T: (/atom/from, /mob/user)
+//from base of atom/MouseDrop_T: (/atom/from, /mob/user)
+#define COMSIG_MOUSEDROPPED_ONTO "mousedropped_onto"
///from base of area/proc/power_change(): ()
#define COMSIG_AREA_POWER_CHANGE "area_power_change"
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index ad9d5ae5abc8..f0b505114646 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -335,7 +335,7 @@
#define SHADOW_SPECIES_LIGHT_THRESHOLD 0.2
//MINOR TWEAKS/MISC
-#define AGE_MIN 17 //youngest a character can be
+#define AGE_MIN 18 //youngest a character can be
#define AGE_MAX 85 //oldest a character can be
#define AGE_MINOR 20 //legal age of space drinking and smoking
#define WIZARD_AGE_MIN 30 //youngest a wizard can be
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index 376e023940de..28e2464aa8a9 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -24,7 +24,7 @@
#define LAZYCLEARLIST(L) if(L) L.Cut()
#define SANITIZE_LIST(L) (islist(L) ? L : list())
#define reverseList(L) reverseRange(L.Copy())
-#define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += list(V);
+#define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += V;
#define LAZYADDASSOCLIST(L, K, V) if(!L) { L = list(); } L[K] += list(V);
#define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; }
#define LAZYACCESSASSOC(L, I, K) L ? L[I] ? L[I][K] ? L[I][K] : null : null : null
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index fdeadc13b61a..8437730c3844 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -217,6 +217,12 @@ GLOBAL_LIST_INIT(skin_tones, sortList(list(
"african2"
)))
+/proc/pick_species_adjective(mob/living/carbon/human/H)
+ if(isipc(H))
+ return pick(GLOB.ipc_preference_adjectives)
+ else
+ return pick(GLOB.preference_adjectives)
+
GLOBAL_LIST_EMPTY(species_list)
/proc/age2agedescription(age)
diff --git a/code/_globalvars/lists/names.dm b/code/_globalvars/lists/names.dm
index ecc1acb6f0e1..ff452072fbec 100644
--- a/code/_globalvars/lists/names.dm
+++ b/code/_globalvars/lists/names.dm
@@ -27,6 +27,8 @@ GLOBAL_LIST_INIT(verbs, world.file2list("strings/names/verbs.txt"))
GLOBAL_LIST_INIT(ing_verbs, world.file2list("strings/names/ing_verbs.txt"))
GLOBAL_LIST_INIT(adverbs, world.file2list("strings/names/adverbs.txt"))
GLOBAL_LIST_INIT(adjectives, world.file2list("strings/names/adjectives.txt"))
+GLOBAL_LIST_INIT(preference_adjectives, world.file2list("strings/preference_adjectives.txt"))
+GLOBAL_LIST_INIT(ipc_preference_adjectives, world.file2list("strings/ipc_preference_adjectives.txt"))
GLOBAL_LIST_INIT(dream_strings, world.file2list("strings/dreamstrings.txt"))
//loaded on startup because of "
//would include in rsc if ' was used
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index 81ce3ceec1eb..993026c0d5e0 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -356,7 +356,6 @@
/**
* Control+Shift click
- * Unused except for AI
*/
/mob/proc/CtrlShiftClickOn(atom/A)
A.CtrlShiftClick(src)
diff --git a/code/controllers/subsystem/pai.dm b/code/controllers/subsystem/pai.dm
index 7c2bf71cad6a..b7ef35e63663 100644
--- a/code/controllers/subsystem/pai.dm
+++ b/code/controllers/subsystem/pai.dm
@@ -146,7 +146,7 @@ SUBSYSTEM_DEF(pai)
continue
if(!(ROLE_PAI in G.client.prefs.be_special))
continue
- to_chat(G, "[user] is requesting a pAI personality! Use the pAI button to submit yourself as one.")
+ to_chat(G, "[user.real_name] is requesting a pAI personality! Use the pAI button to submit yourself as one.")
addtimer(CALLBACK(src, PROC_REF(spam_again)), spam_delay)
var/list/available = list()
for(var/datum/paiCandidate/c in SSpai.candidates)
diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm
index c27e0bd1b7ae..60b4db1d1ce9 100644
--- a/code/datums/chatmessage.dm
+++ b/code/datums/chatmessage.dm
@@ -182,7 +182,7 @@
message.maptext = complete_text
// View the message
- LAZYADDASSOC(owned_by.seen_messages, message_loc, src)
+ LAZYADDASSOCLIST(owned_by.seen_messages, message_loc, src)
owned_by.images |= message
animate(message, alpha = 255, time = CHAT_MESSAGE_SPAWN_TIME)
diff --git a/code/datums/guestbook.dm b/code/datums/guestbook.dm
new file mode 100644
index 000000000000..99104f09d715
--- /dev/null
+++ b/code/datums/guestbook.dm
@@ -0,0 +1,148 @@
+/**
+ * THE GUESTBOOK DATUM // ripped straight from mojave.
+ *
+ * Essentially, this datum handles the people that a given human knows,
+ * to handle getting the correct names on examine and saycode.
+ */
+/datum/guestbook
+ /// Associative list of known guests, real_name = known_name
+ var/list/known_names
+
+/datum/guestbook/Destroy(force)
+ known_names = null
+ return ..()
+
+/datum/guestbook/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Guestbook", "[user.real_name]'s Guestbook")
+ ui.set_autoupdate(FALSE)
+ ui.open()
+
+/datum/guestbook/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/guestbook/ui_data(mob/user)
+ var/list/data = list()
+ var/list/names = list()
+ for(var/real_name in known_names)
+ var/given_name = LAZYACCESS(known_names, real_name)
+ names += list(list("real_name" = real_name, "given_name" = given_name))
+ data["names"] = names
+ return data
+
+/datum/guestbook/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return .
+ switch(action)
+ if("rename_guest")
+ var/real_name = params["real_name"]
+ var/new_name = params["new_name"]
+ new_name = reject_bad_name(new_name, max_length = 42)
+ if(!new_name)
+ to_chat(usr, span_warning("That's a pretty terrible name. You can do better."))
+ return FALSE
+ if(!rename_guest(usr, null, real_name, new_name, silent = FALSE))
+ return FALSE
+ return TRUE
+ if("delete_guest")
+ var/real_name = params["real_name"]
+ if(!remove_guest(usr, null, real_name, silent = FALSE))
+ return FALSE
+ return TRUE
+
+/datum/guestbook/proc/try_add_guest(mob/user, mob/living/carbon/human/guest, silent = FALSE)
+ if(user == guest)
+ if(!silent)
+ to_chat(user, span_warning("That's you! You already know yourself plenty."))
+ return FALSE
+ if(!visibility_checks(user, guest, silent))
+ return FALSE
+ var/given_name = input(user, "What name do you want to give to [guest]?", "Guestbook Name", guest.get_visible_name())
+ if(!given_name)
+ if(!silent)
+ to_chat(user, span_warning("Nevermind."))
+ return FALSE
+ given_name = reject_bad_name(given_name)
+ if(!given_name)
+ if(!silent)
+ to_chat(user, span_warning("That's a pretty terrible name. You can do better."))
+ return FALSE
+ if(!visibility_checks(user, guest, silent))
+ return FALSE
+ var/face_name = guest.get_face_name("ForgetMeNot")
+ if(LAZYACCESS(known_names, face_name))
+ if(!rename_guest(user, guest, face_name, given_name, silent))
+ return FALSE
+ else
+ if(!add_guest(user, guest, face_name, given_name, silent))
+ return FALSE
+ return TRUE
+
+/datum/guestbook/proc/add_guest(mob/user, mob/living/carbon/guest, real_name, given_name, silent = TRUE)
+ //Already exists, should be handled by rename_guest()
+ var/existing_name = LAZYACCESS(known_names, real_name)
+ if(existing_name)
+ if(!silent)
+ to_chat(user, span_warning("You already know them as \"[existing_name]\"."))
+ return FALSE
+ LAZYADDASSOC(known_names, real_name, given_name)
+ if(!silent)
+ to_chat(user, span_notice("You memorize the face of [guest] as \"[given_name]\"."))
+ return TRUE
+
+/datum/guestbook/proc/rename_guest(mob/user, mob/living/carbon/guest, real_name, given_name, silent = TRUE)
+ var/old_name = LAZYACCESS(known_names, real_name)
+ if(!old_name)
+ return FALSE
+ known_names[real_name] = given_name
+ if(!silent)
+ to_chat(user, span_notice("You re-memorize the face of \"[old_name]\" as \"[given_name]\"."))
+ return TRUE
+
+/datum/guestbook/proc/try_remove_guest(mob/user, mob/living/carbon/human/guest, silent = FALSE)
+ if(user == guest)
+ if(!silent)
+ to_chat(user, span_warning("That's you! You'll never forget yourself."))
+ return
+ if(!visibility_checks(user, guest, silent))
+ return FALSE
+ var/face_name = guest.get_face_name("ForgetMeNot")
+ if(!remove_guest(user, guest, face_name, silent))
+ return FALSE
+ return TRUE
+
+/datum/guestbook/proc/remove_guest(mob/user, mob/living/carbon/guest, real_name, silent = TRUE)
+ //Already exists, should be handled by rename_guest()
+ var/existing_name = LAZYACCESS(known_names, real_name)
+ if(!existing_name)
+ if(!silent)
+ to_chat(user, span_warning("You don't know them in the first place."))
+ return FALSE
+ LAZYREMOVE(known_names, real_name)
+ if(!silent)
+ to_chat(user, span_notice("You forget the face of \"[existing_name]\"."))
+ return TRUE
+
+/datum/guestbook/proc/get_known_name(mob/user, mob/living/carbon/guest, real_name)
+ if(user == guest || isAdminObserver(user))
+ return real_name
+ return LAZYACCESS(known_names, real_name)
+
+/datum/guestbook/proc/visibility_checks(mob/user, mob/living/carbon/human/guest, silent = FALSE)
+ if(QDELETED(guest))
+ if(!silent)
+ to_chat(user, span_warning("What?"))
+ return FALSE
+ var/visible_name = guest.get_visible_name("")
+ var/face_name = guest.get_face_name("")
+ if(!visible_name || !face_name)
+ if(!silent)
+ to_chat(user, span_warning("You can't see their face very well!"))
+ return FALSE
+ if(get_dist(user, guest) > 4)
+ if(!silent)
+ to_chat(user, span_warning("You need to take a closer look at them!"))
+ return FALSE
+ return TRUE
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index fc91d2c71de1..36ec4a1b5ae6 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -79,6 +79,9 @@
/// The index for our current scar slot, so we don't have to constantly check the savefile (unlike the slots themselves, this index is independent of selected char slot, and increments whenever a valid char is joined with)
var/current_scar_slot_index
+ /// Guestbook datum, in case we actually make use of the guestbook mechanics
+ var/datum/guestbook/guestbook
+
///Skill multiplier, adjusts how much xp you get/loose from adjust_xp. Dont override it directly, add your reason to experience_multiplier_reasons and use that as a key to put your value in there.
var/experience_multiplier = 1
///Skill multiplier list, just slap your multiplier change onto this with the type it is coming from as key.
@@ -95,6 +98,7 @@
key = _key
soulOwner = src
martial_art = default_martial_art
+ guestbook = new()
init_known_skills()
/datum/mind/Destroy()
@@ -102,6 +106,7 @@
if(islist(antag_datums))
QDEL_LIST(antag_datums)
QDEL_NULL(language_holder)
+ QDEL_NULL(guestbook)
set_current(null)
soulOwner = null
return ..()
diff --git a/code/datums/progressbar.dm b/code/datums/progressbar.dm
index 5ffa3778edc6..25621a613eeb 100644
--- a/code/datums/progressbar.dm
+++ b/code/datums/progressbar.dm
@@ -37,7 +37,7 @@
bar.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
user = User
- LAZYADDASSOC(user.progressbars, bar_loc, src)
+ LAZYADDASSOCLIST(user.progressbars, bar_loc, src)
var/list/bars = user.progressbars[bar_loc]
listindex = bars.len
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 6c6849724bde..043db01e592f 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -1673,7 +1673,11 @@
active_hud.screentip_text.maptext = ""
else
//We inline a MAPTEXT() here, because there's no good way to statically add to a string like this
- active_hud.screentip_text.maptext = "[name]"
+ active_hud.screentip_text.maptext = "[get_screentip_name(client)]"
+
+/// Returns the atom name that should be used on screentip
+/atom/proc/get_screentip_name(client/hovering_client)
+ return name
///Called whenever a player is spawned on the same turf as this atom.
/atom/proc/join_player_here(mob/M)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 7dd3d612ae81..989db20efd1f 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -560,7 +560,7 @@
if(!client)
return
if(new_virtual_z)
- LAZYADDASSOC(SSmobs.players_by_virtual_z, "[new_virtual_z]", src)
+ LAZYADDASSOCLIST(SSmobs.players_by_virtual_z, "[new_virtual_z]", src)
SSidlenpcpool.try_wakeup_virtual_z(new_virtual_z)
/mob/dead/on_virtual_z_change(new_virtual_z, previous_virtual_z)
@@ -570,7 +570,7 @@
if(!client)
return
if(new_virtual_z)
- LAZYADDASSOC(SSmobs.dead_players_by_virtual_z, "[new_virtual_z]", src)
+ LAZYADDASSOCLIST(SSmobs.dead_players_by_virtual_z, "[new_virtual_z]", src)
// Make sure you know what you're doing if you call this, this is intended to only be called by byond directly.
// You probably want CanPass()
diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm
index a847b44d39a1..b54c192f4407 100644
--- a/code/game/machinery/navbeacon.dm
+++ b/code/game/machinery/navbeacon.dm
@@ -49,7 +49,7 @@
if(previous_virtual_z)
LAZYREMOVEASSOC(GLOB.navbeacons, "[previous_virtual_z]", src)
if(new_virtual_z)
- LAZYADDASSOC(GLOB.navbeacons, "[new_virtual_z]", src)
+ LAZYADDASSOCLIST(GLOB.navbeacons, "[new_virtual_z]", src)
..()
// set the transponder codes assoc list from codes_txt
diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm
index b08d7bf6737c..078c435bd213 100644
--- a/code/game/objects/effects/landmarks.dm
+++ b/code/game/objects/effects/landmarks.dm
@@ -45,7 +45,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark)
. = ..()
GLOB.start_landmarks_list += src
if(jobspawn_override)
- LAZYADDASSOC(GLOB.jobspawn_overrides, name, src)
+ LAZYADDASSOCLIST(GLOB.jobspawn_overrides, name, src)
if(name != "start")
tag = "start*[name]"
diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm
index 630759c85afe..2e93b662799a 100644
--- a/code/game/objects/items/cards_ids.dm
+++ b/code/game/objects/items/cards_ids.dm
@@ -140,8 +140,8 @@
playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE)
/obj/item/card/id
- name = "identification card"
- desc = "A card used to provide ID and determine access across the station."
+ name = "access card"
+ desc = "These cards provide access to different sections of a ship."
icon_state = "id"
item_state = "card-id"
lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi'
@@ -159,7 +159,7 @@
var/obj/machinery/paystand/my_store
var/uses_overlays = TRUE
var/icon/cached_flat_icon
- var/registered_age = 13 // default age for ss13 players
+ var/registered_age = 18 // default age for ss13 players
var/job_icon
var/faction_icon
@@ -180,10 +180,7 @@
/obj/item/card/id/attack_self(mob/user)
if(Adjacent(user))
- var/minor
- if(registered_name && registered_age && registered_age < AGE_MINOR)
- minor = " (MINOR)"
- user.visible_message("[user] shows you: [icon2html(src, viewers(user))] [src.name][minor].", "You show \the [src.name][minor].")
+ user.visible_message("[user] shows you: [icon2html(src, viewers(user))] \the [initial(name)] [(!registered_name) ? "(" : "([registered_name]"][(!assignment) ? ")" : ", [assignment])"].", "You show \the [initial(name)] [(!registered_name) ? "(" : "([registered_name],"] [(!assignment) ? ")" : "[assignment])"].")
add_fingerprint(user)
/obj/item/card/id/vv_edit_var(var_name, var_value)
@@ -324,12 +321,14 @@
/obj/item/card/id/examine(mob/user)
. = ..()
if(registered_account)
- . += "The account linked to the ID belongs to '[registered_account.account_holder]' and reports a balance of [registered_account.account_balance] cr."
+ . += "The account linked to the card belongs to '[registered_account.account_holder]' and reports a balance of [registered_account.account_balance] cr."
. += "There's more information below, you can look again to take a closer look..."
/obj/item/card/id/examine_more(mob/user)
var/list/msg = list("You examine [src] closer, and note the following...")
+ if(registered_name)
+ msg += "This access card is assigned to [registered_name]."
if(registered_age)
msg += "The card indicates that the holder is [registered_age] years old. [(registered_age < AGE_MINOR) ? "There's a holographic stripe that reads 'MINOR: DO NOT SERVE ALCOHOL OR TOBACCO' along the bottom of the card." : ""]"
if(mining_points)
@@ -407,16 +406,14 @@
/*
Usage:
update_label()
- Sets the id name to whatever registered_name and assignment is
+ Sets the id name to whatever the assignment is
*/
/obj/item/card/id/proc/update_label()
- var/blank = !registered_name
- name = "[blank ? initial(name) : "[registered_name]'s ID Card"][(!assignment) ? "" : " ([assignment])"]"
+ name = "[(istype(src, /obj/item/card/id/syndicate)) ? "[initial(name)]" : "access card"][(!assignment) ? "" : " ([assignment])"]"
/obj/item/card/id/silver
- name = "silver identification card"
- desc = "A silver card which shows honour and dedication."
+ desc = "A silver-colored card, usually given to higher-ranking officials in ships and stations."
icon_state = "silver"
item_state = "silver_id"
lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi'
@@ -428,8 +425,7 @@ update_label()
access = list(ACCESS_CHANGE_IDS)
/obj/item/card/id/gold
- name = "gold identification card"
- desc = "A golden card which shows power and might."
+ desc = "A golden-colored card, usually given to those at the top of the hierarchy in a ship."
icon_state = "gold"
item_state = "gold_id"
lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi'
@@ -532,10 +528,7 @@ update_label()
access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER)
/obj/item/card/id/syndicate_command
- name = "syndicate ID card"
- desc = "An ID straight from the Syndicate."
- registered_name = "Syndicate"
- assignment = "Syndicate Overlord"
+ desc = "An access card widely utilized by Coalition splinters in the frontier."
icon_state = "syndie"
access = list(ACCESS_SYNDICATE)
uses_overlays = FALSE
@@ -569,15 +562,12 @@ update_label()
/obj/item/card/id/patient //Aegis ID
assignment = "Long Term Patient"
uses_overlays = FALSE
- access = list(ACCESS_SYNDICATE)
/obj/item/card/id/captains_spare
- desc = "The spare ID of the High Lord himself."
icon_state = "gold"
item_state = "gold_id"
lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi'
- registered_name = "Captain"
assignment = "Captain"
registered_age = null
@@ -596,11 +586,9 @@ update_label()
..()
/obj/item/card/id/centcom
- name = "\improper CentCom ID"
- desc = "An ID straight from Central Command."
+ name = "\improper Nanotrasen Central Command access card"
+ desc = "An access card sourced from Nanotrasen's Central Command."
icon_state = "centcom"
- registered_name = "Central Command"
- assignment = "Central Command"
uses_overlays = FALSE
registered_age = null
@@ -615,8 +603,6 @@ update_label()
name = "\improper CentCom ID"
desc = "An ERT ID card."
icon_state = "ert_commander"
- registered_name = "Emergency Response Team Commander"
- assignment = "Emergency Response Team Commander"
uses_overlays = FALSE
registered_age = null
@@ -625,8 +611,6 @@ update_label()
. = ..()
/obj/item/card/id/ert/security
- registered_name = "Security Response Officer"
- assignment = "Security Response Officer"
icon_state = "ert_security"
/obj/item/card/id/ert/security/Initialize()
@@ -634,8 +618,6 @@ update_label()
. = ..()
/obj/item/card/id/ert/engineer
- registered_name = "Engineering Response Officer"
- assignment = "Engineering Response Officer"
icon_state = "ert_engineer"
/obj/item/card/id/ert/engineer/Initialize()
@@ -643,8 +625,6 @@ update_label()
. = ..()
/obj/item/card/id/ert/medical
- registered_name = "Medical Response Officer"
- assignment = "Medical Response Officer"
icon_state = "ert_medic"
/obj/item/card/id/ert/medical/Initialize()
@@ -652,8 +632,6 @@ update_label()
. = ..()
/obj/item/card/id/ert/chaplain
- registered_name = "Religious Response Officer"
- assignment = "Religious Response Officer"
icon_state = "ert_chaplain"
/obj/item/card/id/ert/chaplain/Initialize()
@@ -661,8 +639,6 @@ update_label()
. = ..()
/obj/item/card/id/ert/janitor
- registered_name = "Janitorial Response Officer"
- assignment = "Janitorial Response Officer"
icon_state = "ert_janitor"
/obj/item/card/id/ert/janitor/Initialize()
@@ -670,8 +646,6 @@ update_label()
. = ..()
/obj/item/card/id/ert/clown
- registered_name = "Entertainment Response Officer"
- assignment = "Entertainment Response Officer"
icon_state = "ert_clown"
/obj/item/card/id/ert/clown/Initialize()
@@ -679,12 +653,10 @@ update_label()
. = ..()
/obj/item/card/id/ert/deathsquad
- name = "\improper Death Squad ID"
- desc = "A Death Squad ID card."
+ desc = "An access card colored in black and red."
icon_state = "deathsquad" //NO NO SIR DEATH SQUADS ARENT A PART OF NANOTRASEN AT ALL
- registered_name = "Death Commando"
- assignment = "Death Commando"
uses_overlays = FALSE
+ job_icon = "deathsquad"
/obj/item/card/id/debug
name = "\improper Debug ID"
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 00c619da8353..361358892520 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -1404,7 +1404,7 @@
say(message, language)
return NOPASS
-/obj/item/toy/dummy/GetVoice()
+/obj/item/toy/dummy/GetVoice(if_no_voice = "Unknown")
return doll_name
/obj/item/toy/seashell
diff --git a/code/game/say.dm b/code/game/say.dm
index 306ad62fefdc..30ef86920efe 100644
--- a/code/game/say.dm
+++ b/code/game/say.dm
@@ -51,10 +51,28 @@ GLOBAL_LIST_INIT(freqcolor, list())
//Radio freq/name display
var/freqpart = radio_freq ? "\[[get_radio_name(radio_freq)]\] " : ""
//Speaker name
- var/namepart = "[speaker.GetVoice()][speaker.get_alt_name()]"
- if(face_name && ishuman(speaker))
- var/mob/living/carbon/human/H = speaker
- namepart = "[H.get_face_name()]" //So "fake" speaking like in hallucinations does not give the speaker away if disguised
+
+ var/namepart = speaker.GetVoice()
+ var/atom/movable/reliable_narrator = speaker
+ if(istype(speaker, /atom/movable/virtualspeaker)) //ugh
+ var/atom/movable/virtualspeaker/fakespeaker = speaker
+ reliable_narrator = fakespeaker.source
+ if(ishuman(reliable_narrator))
+ //So "fake" speaking like in hallucinations does not give the speaker away if disguised
+ if(face_name)
+ var/mob/living/carbon/human/human_narrator = reliable_narrator
+ namepart = human_narrator.name
+ //otherwise, do guestbook handling
+ else if(ismob(src))
+ var/mob/mob_source = src
+ if(mob_source.mind?.guestbook)
+ var/known_name = mob_source.mind.guestbook.get_known_name(src, reliable_narrator, namepart)
+ if(known_name)
+ namepart = "[known_name]"
+ else
+ var/mob/living/carbon/human/human_narrator = reliable_narrator
+ namepart = "[human_narrator.get_generic_name(prefixed = TRUE, lowercase = FALSE)]"
+
//End name span.
var/endspanpart = ""
@@ -66,9 +84,9 @@ GLOBAL_LIST_INIT(freqcolor, list())
else
messagepart = lang_treat(speaker, message_language, raw_message, spans, message_mods)
- var/datum/language/D = GLOB.language_datum_instances[message_language]
- if(istype(D) && D.display_icon(src))
- languageicon = "[D.get_icon()] "
+ var/datum/language/language = GLOB.language_datum_instances[message_language]
+ if(istype(language) && language.display_icon(src))
+ languageicon = "[language.get_icon()] "
messagepart = " [say_emphasis(messagepart)]"
@@ -176,7 +194,7 @@ GLOBAL_LIST_INIT(freqcolor, list())
return "2"
return "0"
-/atom/movable/proc/GetVoice()
+/atom/movable/proc/GetVoice(if_no_voice = "Unknown")
return "[src]" //Returns the atom's name, prepended with 'The' if it's not a proper noun
/atom/movable/proc/IsVocal()
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 82cb857576c1..5bb4b25cc1f1 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -39,7 +39,7 @@
return
var/body = "
Options for [M.key]"
- body += "Options panel for [M]"
+ body += "Options panel for [M.real_name]"
if(M.client)
body += " played by [M.client] "
body += "[M.client.holder ? M.client.holder.rank : "Player"]"
diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm
index c1845945485f..7d5dfccf2804 100644
--- a/code/modules/admin/create_mob.dm
+++ b/code/modules/admin/create_mob.dm
@@ -23,6 +23,7 @@
H.facial_hair_color = H.hair_color
H.eye_color = random_eye_color()
H.dna.blood_type = random_blood_type()
+ H.generic_adjective = pick_species_adjective(H)
// Mutant randomizing, doesn't affect the mob appearance unless it's the specific mutant.
H.dna.features["mcolor"] = random_short_color()
diff --git a/code/modules/admin/verbs/one_click_antag.dm b/code/modules/admin/verbs/one_click_antag.dm
index ac05c3afdd4f..dff0335b7e9e 100644
--- a/code/modules/admin/verbs/one_click_antag.dm
+++ b/code/modules/admin/verbs/one_click_antag.dm
@@ -493,6 +493,16 @@
teamSpawned++
if(teamSpawned)
+ // guestbook
+ for(var/datum/mind/member in ert_team.members)
+ var/member_mob = member.current
+ for(var/datum/mind/other_member in ert_team.members)
+ // skip yourself
+ if(other_member.name == member.name)
+ continue
+ var/mob/living/carbon/human/other_member_mob = other_member.current
+ member.guestbook.add_guest(member_mob, other_member_mob, other_member_mob.real_name, other_member_mob.real_name, TRUE)
+
message_admins("[ertemplate.rename_team] has spawned with the mission: [ertemplate.mission]")
//Open the Armory doors
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index ebf32c163d89..901200f69928 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -158,7 +158,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/list/custom_names = list()
var/preferred_ai_core_display = "Blue"
var/prefered_security_department = SEC_DEPT_RANDOM
-
+ var/generic_adjective = "Unremarkable"
//Quirk list
var/list/all_quirks = list()
@@ -847,6 +847,11 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "[features["body_size"]]
"
+
+ dat += "Character Adjective
"
+
+ dat += "[generic_adjective]
"
+
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
@@ -2074,6 +2079,15 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(phobiaType)
phobia = phobiaType
+ if("generic_adjective")
+ var/selectAdj
+ if(istype(pref_species, /datum/species/ipc))
+ selectAdj = input(user, "In one word, how would you describe your character's appereance?", "Character Preference", generic_adjective) as null|anything in GLOB.ipc_preference_adjectives
+ else
+ selectAdj = input(user, "In one word, how would you describe your character's appereance?", "Character Preference", generic_adjective) as null|anything in GLOB.preference_adjectives
+ if(selectAdj)
+ generic_adjective = selectAdj
+
if ("max_chat_length")
var/desiredlength = input(user, "Choose the max character length of shown Runechat messages. Valid range is 1 to [CHAT_MESSAGE_MAX_LENGTH] (default: [initial(max_chat_length)]))", "Character Preference", max_chat_length) as null|num
if (!isnull(desiredlength))
@@ -2473,6 +2487,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
character.set_species(chosen_species, icon_update = FALSE, pref_load = TRUE)
//Because of how set_species replaces all bodyparts with new ones, hair needs to be set AFTER species.
character.dna.real_name = character.real_name
+ character.generic_adjective = generic_adjective
character.hair_color = hair_color
character.facial_hair_color = facial_hair_color
character.grad_color = features["grad_color"]
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 0b95e291b794..cce6b66ac612 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -405,6 +405,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
READ_FILE(S["jumpsuit_style"], jumpsuit_style)
READ_FILE(S["uplink_loc"], uplink_spawn_loc)
READ_FILE(S["phobia"], phobia)
+ READ_FILE(S["generic_adjective"], generic_adjective)
READ_FILE(S["randomise"], randomise)
READ_FILE(S["body_size"], features["body_size"])
READ_FILE(S["prosthetic_limbs"], prosthetic_limbs)
@@ -594,6 +595,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["randomise"] , randomise)
WRITE_FILE(S["species"] , pref_species.id)
WRITE_FILE(S["phobia"] , phobia)
+ WRITE_FILE(S["generic_adjective"] , generic_adjective)
WRITE_FILE(S["body_size"] , features["body_size"])
WRITE_FILE(S["prosthetic_limbs"] , prosthetic_limbs)
WRITE_FILE(S["feature_mcolor"] , features["mcolor"])
diff --git a/code/modules/client/verbs/looc.dm b/code/modules/client/verbs/looc.dm
index 47d4e0e82aec..1c66a077a065 100644
--- a/code/modules/client/verbs/looc.dm
+++ b/code/modules/client/verbs/looc.dm
@@ -59,32 +59,33 @@ GLOBAL_VAR_INIT(normal_looc_colour, "#6699CC")
mob.log_talk(raw_msg, LOG_LOOC, tag = "(LOOC)")
- var/list/heard = get_hearers_in_view(7, get_top_level_mob(src.mob))
- for(var/mob/M in heard)
- if(!M.client)
+ var/list/heard = get_hearers_in_view(7, get_top_level_mob(mob))
+ for(var/mob/hearer_mob in heard)
+ var/client/hearer = hearer_mob.client
+
+ if(!hearer)
continue
- var/client/C = M.client
- if(key in C.prefs.ignoring)
+ if(key in hearer.prefs.ignoring)
continue
- if(holder?.fakekey in C.prefs.ignoring)
+ if(holder?.fakekey in hearer.prefs.ignoring)
continue
- if(!(C.prefs.chat_toggles & CHAT_LOOC))
+ if(!(hearer.prefs.chat_toggles & CHAT_LOOC))
continue
//Handled before admins so that they see this if they're in range anyways
- if(C.prefs.chat_on_map && mob.invisibility <= M.see_invisible)
- M.create_chat_message(mob, null, "\[LOOC: [raw_msg]\]", null, LOOC_MESSAGE)
+ if(hearer.prefs.chat_on_map && mob.invisibility <= hearer_mob.see_invisible)
+ hearer_mob.create_chat_message(mob, null, "\[LOOC: [raw_msg]\]", null, LOOC_MESSAGE)
- if(C in GLOB.admins)
+ if(hearer in GLOB.admins)
continue //handled in the next loop
if(GLOB.LOOC_COLOR)
- to_chat(C, "LOOC: [src.mob.name]: [msg]", MESSAGE_TYPE_LOOC)
+ to_chat(hearer, "LOOC: [mob.get_screentip_name(hearer)]: [msg]", MESSAGE_TYPE_LOOC)
else
- to_chat(C, "LOOC: [src.mob.name]: [msg]", MESSAGE_TYPE_LOOC)
+ to_chat(hearer, "LOOC: [mob.get_screentip_name(hearer)]: [msg]", MESSAGE_TYPE_LOOC)
for(var/client/C in GLOB.admins)
if(key in C.prefs.ignoring)
@@ -100,9 +101,9 @@ GLOBAL_VAR_INIT(normal_looc_colour, "#6699CC")
if (C.mob in heard)
prefix = "LOOC"
if(GLOB.LOOC_COLOR)
- to_chat(C, "[ADMIN_FLW(usr)] [prefix]: [src.key]/[src.mob.name]: [msg]", MESSAGE_TYPE_LOOC)
+ to_chat(C, "[ADMIN_FLW(usr)] [prefix]: [key]/[mob.real_name]: [msg]", MESSAGE_TYPE_LOOC)
else
- to_chat(C, "[ADMIN_FLW(usr)] [prefix]: [src.key]/[src.mob.name]: [msg]", MESSAGE_TYPE_LOOC)
+ to_chat(C, "[ADMIN_FLW(usr)] [prefix]: [key]/[mob.real_name]: [msg]", MESSAGE_TYPE_LOOC)
/proc/toggle_looc(toggle = null)
if(toggle == null)
diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm
index 203bcf416b13..f5c37b18997d 100644
--- a/code/modules/clothing/chameleon.dm
+++ b/code/modules/clothing/chameleon.dm
@@ -150,7 +150,7 @@
card.assignment = J.name
card.update_appearance()
card.assignment = old_assignment
- card.update_label()
+ card.name = "[(istype(src, /obj/item/card/id/syndicate)) ? "[initial(name)]" : "access card"][(!old_assignment) ? "" : " ([old_assignment])"]"
H.sec_hud_set_ID()
qdel(outfit)
diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm
index ee0d7789355e..20ad6e089730 100644
--- a/code/modules/jobs/job_types/_job.dm
+++ b/code/modules/jobs/job_types/_job.dm
@@ -25,7 +25,6 @@
var/display_order = JOB_DISPLAY_ORDER_DEFAULT
-
///Levels unlocked at roundstart in physiology
var/list/roundstart_experience
diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm
index 5a1e5bbf3387..85c60a15b031 100644
--- a/code/modules/mob/dead/dead.dm
+++ b/code/modules/mob/dead/dead.dm
@@ -107,7 +107,7 @@ INITIALIZE_IMMEDIATE(/mob/dead)
. = ..()
if(!client)
return
- LAZYADDASSOC(SSmobs.dead_players_by_virtual_z, "[virtual_z()]", src)
+ LAZYADDASSOCLIST(SSmobs.dead_players_by_virtual_z, "[virtual_z()]", src)
/mob/dead/Logout()
. = ..()
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index 9baa46f526b5..5ccba743bb1b 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -249,8 +249,8 @@
observer.client.init_verbs()
observer.update_appearance()
observer.stop_sound_channel(CHANNEL_LOBBYMUSIC)
- deadchat_broadcast(" has observed.", "[observer.real_name]", follow_target = observer, turf_target = get_turf(observer), message_type = DEADCHAT_DEATHRATTLE)
QDEL_NULL(mind)
+ deadchat_broadcast(" has observed.", "[observer.real_name]", follow_target = observer, turf_target = get_turf(observer), message_type = DEADCHAT_DEATHRATTLE)
qdel(src)
return TRUE
@@ -329,6 +329,7 @@
var/mob/living/carbon/human/humanc = character
ship.manifest_inject(humanc, client, job)
GLOB.data_core.manifest_inject(humanc, client)
+ ship.add_mob_to_crew_guestbook(humanc)
AnnounceArrival(humanc, job.name, ship)
AddEmploymentContract(humanc)
SSblackbox.record_feedback("tally", "species_spawned", 1, humanc.dna.species.name)
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index b024be2f96f7..59f97d172ad1 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -8,7 +8,6 @@
var/t_has = p_have()
var/t_is = p_are()
var/obscure_name
-
var/list/obscured = check_obscured_slots()
var/skipface = ((wear_mask?.flags_inv & HIDEFACE) || (head?.flags_inv & HIDEFACE))
@@ -16,10 +15,25 @@
var/mob/living/L = user
if(HAS_TRAIT(L, TRAIT_PROSOPAGNOSIA))
obscure_name = TRUE
- var/apparent_species
- if(dna?.species && !skipface)
- apparent_species = ", \an [dna.species.name]"
- . = list("This is [!obscure_name ? name : "Unknown"][apparent_species]!")
+
+ . = list(span_info("This is [name]!"))
+
+ if(user != src)
+ if(!obscure_name && !skipface)
+ var/face_name = get_face_name("")
+ if(face_name)
+ //if we have no guestbook, we just KNOW okay?
+ var/known_name = user.mind?.guestbook ? user.mind.guestbook.get_known_name(user, src, face_name) : face_name
+ if(known_name)
+ . += "You know them as [known_name]."
+ else
+ . += "You don't recognize [t_him]. You can Ctrl-Shift click [t_him] to memorize their face."
+ else
+ . += "You can't see [t_his] face very well."
+ else
+ . += "You can't see [t_his] face very well."
+ else
+ . += "It's you, [real_name]."
//uniform
if(w_uniform && !(ITEM_SLOT_ICLOTHING in obscured))
@@ -402,18 +416,4 @@
. = ..()
if ((wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE)))
return
- var/age_text
- switch(age)
- if(-INFINITY to 25)
- age_text = "very young"
- if(26 to 35)
- age_text = "of adult age"
- if(36 to 55)
- age_text = "middle-aged"
- if(56 to 75)
- age_text = "rather old"
- if(76 to 100)
- age_text = "very old"
- if(101 to INFINITY)
- age_text = "withering away"
- . += list(span_notice("[p_they(TRUE)] appear[p_s()] to be [age_text]."))
+ . += list(span_notice("[p_they(TRUE)] appear[p_s()] to be [get_age()]."))
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index f3802e381c55..f08855d373f7 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1258,6 +1258,23 @@
return FALSE
return ..()
+/mob/living/carbon/human/CtrlShiftClick(mob/user)
+ . = ..()
+ if(isobserver(user) || !user.mind?.guestbook)
+ return
+ INVOKE_ASYNC(user.mind.guestbook, TYPE_PROC_REF(/datum/guestbook, try_add_guest), user, src, FALSE)
+
+/mob/living/carbon/human/get_screentip_name(client/hovering_client)
+ . = ..()
+ var/mob/hovering_mob = hovering_client?.mob
+ if(!hovering_mob?.mind?.guestbook)
+ return .
+ var/face_name = get_face_name("")
+ var/known_name = hovering_mob.mind.guestbook.get_known_name(hovering_mob, src, face_name)
+ if(known_name)
+ return known_name
+ return .
+
/mob/living/carbon/human/species
var/race = null
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index 5b638d330690..8cc9e5002749 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -61,6 +61,9 @@
var/special_voice = "" // For changing our voice. Used by a symptom.
+ /// Adjective used in get_generic_name(), if any
+ var/generic_adjective
+
var/bleed_rate = 0 //how much are we bleeding
var/bleedsuppress = 0 //for stopping bloodloss, eventually this will be limb-based like bleeding
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index 2f9814112711..b98466301f63 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -31,28 +31,19 @@
return pda.owner
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()
- var/face_name = get_face_name("")
- var/id_name = get_id_name("")
if(name_override)
return name_override
- if(face_name)
- if(id_name && (id_name != face_name))
- return "[face_name] (as [id_name])"
- return face_name
- if(id_name)
- return id_name
- return "Unknown"
+ return get_generic_name(lowercase = TRUE)
//Returns "Unknown" if facially disfigured and real_name if not. Useful for setting name when Fluacided or when updating a human's name variable
-/mob/living/carbon/human/proc/get_face_name(if_no_face="Unknown")
- if(wear_mask && (wear_mask.flags_inv&HIDEFACE)) //Wearing a mask which hides our face, use id-name if possible
+/mob/living/carbon/human/proc/get_face_name(if_no_face = get_generic_name(lowercase = TRUE))
+ if(wear_mask && (wear_mask.flags_inv & HIDEFACE)) //Wearing a mask which hides our face, use id-name if possible
return if_no_face
- if(head && (head.flags_inv&HIDEFACE))
- return if_no_face //Likewise for hats
+ if(head && (head.flags_inv & HIDEFACE))
+ return if_no_face //Likewise for hats
var/obj/item/bodypart/O = get_bodypart(BODY_ZONE_HEAD)
- if(!O || (HAS_TRAIT(src, TRAIT_DISFIGURED)) || (O.brutestate+O.burnstate)>2 || cloneloss>50 || !real_name) //disfigured. use id-name if possible
+ if(!O || (HAS_TRAIT(src, TRAIT_DISFIGURED)) || (O.brutestate+O.burnstate)>2 || cloneloss>50 || !real_name) //disfigured. use id-name if possible
return if_no_face
return real_name
@@ -181,3 +172,56 @@
destination.socks = socks
destination.socks_color = socks_color
destination.jumpsuit_style = jumpsuit_style
+
+/mob/living/carbon/human/proc/get_age()
+ var/obscured = check_obscured_slots()
+ var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
+ if((obscured & ITEM_SLOT_ICLOTHING) && skipface || isipc(src))
+ return ""
+ switch(age)
+ if(70 to INFINITY)
+ return "Geriatric"
+ if(60 to 70)
+ return "Elderly"
+ if(50 to 60)
+ return "Old"
+ if(40 to 50)
+ return "Middle-Aged"
+ if(24 to 40)
+ return "" //not necessary because this is basically the most common age range
+ if(18 to 24)
+ return "Young"
+ else
+ return "Puzzling"
+
+/mob/living/carbon/human/proc/get_generic_name(prefixed = FALSE, lowercase = FALSE)
+ var/obscured = check_obscured_slots()
+ var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
+ var/hide_features = (obscured & ITEM_SLOT_ICLOTHING) && skipface
+ var/visible_adjective
+ if(generic_adjective && !hide_features)
+ visible_adjective = "[generic_adjective] "
+ var/visible_age = get_age()
+ if(visible_age)
+ visible_age = "[visible_age] "
+ var/visible_gender = get_gender()
+ var/final_string = "[visible_adjective][visible_age][dna.species.name] [visible_gender]"
+ if(prefixed)
+ final_string = "\A [final_string]"
+ return lowercase ? lowertext(final_string) : final_string
+
+/mob/living/carbon/human/proc/get_gender()
+ var/visible_gender = p_they()
+ switch(visible_gender)
+ if("he")
+ visible_gender = "Man"
+ if("she")
+ visible_gender = "Woman"
+ if("they")
+ if(ishuman(src))
+ visible_gender = "Person"
+ else
+ visible_gender = "Creature"
+ else
+ visible_gender = "Thing"
+ return visible_gender
diff --git a/code/modules/mob/living/carbon/human/human_say.dm b/code/modules/mob/living/carbon/human/human_say.dm
index 039141bb5fd5..6b0c1ff1c768 100644
--- a/code/modules/mob/living/carbon/human/human_say.dm
+++ b/code/modules/mob/living/carbon/human/human_say.dm
@@ -7,16 +7,14 @@
else
. = ..()
-/mob/living/carbon/human/GetVoice()
+/mob/living/carbon/human/GetVoice(if_no_voice = get_generic_name())
if(istype(wear_mask, /obj/item/clothing/mask/chameleon))
- var/obj/item/clothing/mask/chameleon/V = wear_mask
- if(V.voice_change && wear_id)
+ var/obj/item/clothing/mask/chameleon/chameleon_mask = wear_mask
+ if(chameleon_mask.voice_change && wear_id)
var/obj/item/card/id/idcard = wear_id.GetID()
if(istype(idcard))
return idcard.registered_name
- else
- return real_name
- if(istype(wear_mask, /obj/item/clothing/mask/gas/syndicate/voicechanger))
+ else if(istype(wear_mask, /obj/item/clothing/mask/gas/syndicate/voicechanger))
var/obj/item/clothing/mask/gas/syndicate/voicechanger/V = wear_mask
if(V.voice_change && wear_id)
var/obj/item/card/id/idcard = wear_id.GetID()
@@ -26,18 +24,17 @@
return real_name
else
return real_name
- if(istype(wear_mask, /obj/item/clothing/mask/infiltrator))
- var/obj/item/clothing/mask/infiltrator/V = wear_mask
- if(V.voice_unknown)
- return ("Unknown")
- else
- return real_name
+ else if(istype(wear_mask, /obj/item/clothing/mask/infiltrator))
+ var/obj/item/clothing/mask/infiltrator/infiltrator_mask = wear_mask
+ if(infiltrator_mask.voice_unknown)
+ return if_no_voice
if(mind)
var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling)
if(changeling && changeling.mimicing)
return changeling.mimicing
- if(GetSpecialVoice())
- return GetSpecialVoice()
+ var/special_voice = GetSpecialVoice()
+ if(special_voice)
+ return special_voice
return real_name
/mob/living/carbon/human/IsVocal()
diff --git a/code/modules/mob/living/carbon/human/species_types/IPC.dm b/code/modules/mob/living/carbon/human/species_types/IPC.dm
index 381708757fd1..6506ee40c086 100644
--- a/code/modules/mob/living/carbon/human/species_types/IPC.dm
+++ b/code/modules/mob/living/carbon/human/species_types/IPC.dm
@@ -1,5 +1,5 @@
/datum/species/ipc // im fucking lazy mk2 and cant get sprites to normally work
- name = "\improper Integrated Positronic Chassis" //inherited from the real species, for health scanners and things
+ name = "\improper Positronic" //inherited from the real species, for health scanners and things
id = SPECIES_IPC
sexes = FALSE
species_age_min = 0
diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm
index 02ddf79f6bc4..b512dc50b2bf 100644
--- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm
@@ -1,5 +1,5 @@
/datum/species/moth
- name = "\improper Mothman"
+ name = "\improper Mothperson"
id = SPECIES_MOTH
default_color = "00FF00"
species_traits = list(LIPS, NOEYESPRITES, TRAIT_ANTENNAE, HAIR, EMOTE_OVERLAY)
diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
index ec9afd777f2c..d3c900c56786 100644
--- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
@@ -1,5 +1,5 @@
/datum/species/plasmaman
- name = "\improper Plasmaman"
+ name = "\improper Phorid"
id = SPECIES_PLASMAMAN
sexes = 0
meat = /obj/item/stack/sheet/mineral/plasma
diff --git a/code/modules/mob/living/login.dm b/code/modules/mob/living/login.dm
index d59e3f77781e..62098a940937 100644
--- a/code/modules/mob/living/login.dm
+++ b/code/modules/mob/living/login.dm
@@ -18,7 +18,7 @@
var/virtual_z = virtual_z()
- LAZYADDASSOC(SSmobs.players_by_virtual_z, "[virtual_z]", src)
+ LAZYADDASSOCLIST(SSmobs.players_by_virtual_z, "[virtual_z]", src)
SSidlenpcpool.try_wakeup_virtual_z(virtual_z)
//Vents
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index daa987904737..cfd7d9153c8d 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -1063,3 +1063,4 @@
ghostize(1)
QDEL_NULL(src)
+
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index e4ead25880f9..3670e14a6405 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -636,7 +636,7 @@
switch(togglestatus)
if(AI_Z_OFF)
- LAZYADDASSOC(SSidlenpcpool.idle_mobs_by_virtual_level, virt_z, src)
+ LAZYADDASSOCLIST(SSidlenpcpool.idle_mobs_by_virtual_level, virt_z, src)
else
LAZYREMOVEASSOC(SSidlenpcpool.idle_mobs_by_virtual_level, virt_z, src)
diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm
index 8eb5bc620722..d60755693489 100644
--- a/code/modules/mob/living/status_procs.dm
+++ b/code/modules/mob/living/status_procs.dm
@@ -478,11 +478,11 @@
for(var/listed_type in slowdown_type)
if(ispath(listed_type))
listed_type = "[listed_type]" //Path2String
- LAZYADDASSOC(movespeed_mod_immunities, listed_type, source)
+ LAZYADDASSOCLIST(movespeed_mod_immunities, listed_type, source)
else
if(ispath(slowdown_type))
slowdown_type = "[slowdown_type]" //Path2String
- LAZYADDASSOC(movespeed_mod_immunities, slowdown_type, source)
+ LAZYADDASSOCLIST(movespeed_mod_immunities, slowdown_type, source)
if(update)
update_movespeed()
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index d475891fc28f..8b768e5164fb 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -204,27 +204,34 @@
if(self_message)
hearers -= src
- var/raw_msg = message
- if(visible_message_flags & EMOTE_MESSAGE)
- message = "[src][separation][message]"
-
for(var/mob/M in hearers)
if(!M.client)
continue
- //This entire if/else chain could be in two lines but isn't for readibilties sake.
var/msg = message
+
+ //This entire if/else chain could be in two lines but isn't for readibilties sake.
if(M.see_invisible < invisibility)//if src is invisible to M
msg = blind_message
else if(T != loc && T != src) //if src is inside something and not a turf.
msg = blind_message
else if(T.lighting_object && T.lighting_object.invisibility <= M.see_invisible && T.is_softly_lit()) //if it is too dark.
msg = blind_message
+ else if(visible_message_flags & EMOTE_MESSAGE)
+ var/shown_name = name
+ if(M.mind?.guestbook && ishuman(src))
+ var/mob/living/carbon/human/human_source = src
+ var/known_name = M.mind.guestbook.get_known_name(M, src, human_source.get_face_name())
+ if(known_name)
+ shown_name = known_name
+
+ msg = "[shown_name][separation][message]"
+
if(!msg)
continue
if(visible_message_flags & EMOTE_MESSAGE && runechat_prefs_check(M, visible_message_flags))
- M.create_chat_message(src, raw_message = raw_msg, runechat_flags = visible_message_flags)
+ M.create_chat_message(src, raw_message = message, runechat_flags = visible_message_flags)
M.show_message(msg, MSG_VISUAL, blind_message, MSG_AUDIBLE)
@@ -250,12 +257,24 @@
if(self_message)
hearers -= src
var/raw_msg = message
- if(audible_message_flags & EMOTE_MESSAGE)
- message = "[src][separation][message]"
for(var/mob/M in hearers)
- if(audible_message_flags & EMOTE_MESSAGE && runechat_prefs_check(M, audible_message_flags))
- M.create_chat_message(src, raw_message = raw_msg, runechat_flags = audible_message_flags)
- M.show_message(message, MSG_AUDIBLE, deaf_message, MSG_VISUAL)
+ var/msg = raw_msg
+
+ //emote handling
+ if(audible_message_flags & EMOTE_MESSAGE)
+ var/shown_name = name
+ if(M.mind?.guestbook && ishuman(src))
+ var/mob/living/carbon/human/human_source = src
+ var/known_name = M.mind.guestbook.get_known_name(M, src, human_source.GetVoice())
+ if(known_name)
+ shown_name = known_name
+
+ msg = "[shown_name][separation][message]"
+
+ if(runechat_prefs_check(M, audible_message_flags) && M.can_hear())
+ M.create_chat_message(src, raw_message = raw_msg, runechat_flags = audible_message_flags)
+
+ M.show_message(msg, MSG_AUDIBLE, deaf_message, MSG_VISUAL)
/**
* Show a message to all mobs in earshot of this one
@@ -670,6 +689,24 @@
else
to_chat(src, "You don't have a mind datum for some reason, so you can't add a note to it.")
+///Shows guestbook tgui window
+/mob/verb/guestbook()
+ set name = "Guestbook"
+ set category = "IC"
+ set desc = "View your character's Guestbook."
+ // the reason why there are two observer checks in here is because the mind datum sometimes carries over to ghosts.
+ // this is something i should probably fix instead of adding a fallback check, but...
+ if(isobserver(src))
+ to_chat(src, span_warning("You have to be in the current round to do that!"))
+ return
+ if(!mind)
+ var/fail_message = "You have no mind!"
+ if(isobserver(src))
+ fail_message += " You have to be in the current round at some point to have one."
+ to_chat(src, span_warning(fail_message))
+ return
+ mind.guestbook.ui_interact(usr)
+
/**
* Allows you to respawn, abandoning your current mob
*
diff --git a/code/modules/overmap/ships/controlled_ship_datum.dm b/code/modules/overmap/ships/controlled_ship_datum.dm
index 8174a3e365f4..b87d3efda187 100644
--- a/code/modules/overmap/ships/controlled_ship_datum.dm
+++ b/code/modules/overmap/ships/controlled_ship_datum.dm
@@ -307,6 +307,25 @@
job_holder_refs[human_job] = list()
job_holder_refs[human_job] += WEAKREF(H)
+/**
+ * adds a mob's real name to a crew's guestbooks
+ *
+ * * H - human mob to add to the crew's guestbooks
+ */
+/datum/overmap/ship/controlled/proc/add_mob_to_crew_guestbook(mob/living/carbon/human/H)
+ // iterate over the human list to find crewmembers
+ for(var/mob/living/carbon/human/crewmember as anything in GLOB.human_list)
+ if(crewmember == H)
+ continue
+ if(!(crewmember.real_name in manifest))
+ continue
+ if(!crewmember.mind?.guestbook)
+ continue
+
+ // add the mob to the crewmember's guestbook and viceversa
+ crewmember.mind.guestbook.add_guest(crewmember, H, H.real_name, H.real_name, TRUE)
+ H.mind.guestbook.add_guest(H, crewmember, crewmember.real_name, crewmember.real_name, TRUE)
+
/datum/overmap/ship/controlled/proc/set_owner_mob(mob/new_owner)
if(owner_mob)
// we (hopefully) don't have to hook qdeletion,
diff --git a/shiptest.dme b/shiptest.dme
index 1f3455c4c948..8b12b6d39298 100644
--- a/shiptest.dme
+++ b/shiptest.dme
@@ -403,6 +403,7 @@
#include "code\datums\emotes.dm"
#include "code\datums\ert.dm"
#include "code\datums\forced_movement.dm"
+#include "code\datums\guestbook.dm"
#include "code\datums\holocall.dm"
#include "code\datums\http.dm"
#include "code\datums\hud.dm"
diff --git a/strings/ipc_preference_adjectives.txt b/strings/ipc_preference_adjectives.txt
new file mode 100644
index 000000000000..a243b2d77fc5
--- /dev/null
+++ b/strings/ipc_preference_adjectives.txt
@@ -0,0 +1,71 @@
+Bedraggled
+Brawny
+Bulky
+Burly
+Calm
+Chaotic
+Charming
+Delicate
+Dignified
+Disgusting
+Disturbing
+Dull
+Effeminate
+Elegant
+Energetic
+Exasperated
+Exotic
+Faint
+Feisty
+Flamboyant
+Fragile
+Frail
+Friendly
+Gentle
+Hawkish
+Hefty
+Hobbling
+Hyper
+Imposing
+Jaded
+Lax
+Lean
+Limp
+Lithe
+Lopsided
+Lovely
+Mangled
+Masculine
+Messy
+Nimble
+Petite
+Pompous
+Pugnacious
+Repulsive
+Robust
+Rough
+Rusted
+Scarred
+Shifty
+Sickly
+Skittish
+Sleek
+Slender
+Slovenly
+Sluggish
+Spacy
+Stiff
+Stony
+Stylish
+Unattractive
+Unremarkable
+Unsightly
+Verbose
+Vigorous
+Waifish
+Wilted
+Wily
+Withered
+Worn-Out
+Zealous
+Zesty
diff --git a/strings/preference_adjectives.txt b/strings/preference_adjectives.txt
new file mode 100644
index 000000000000..0d67f16803f8
--- /dev/null
+++ b/strings/preference_adjectives.txt
@@ -0,0 +1,117 @@
+Angsty
+Awkward
+Bedraggled
+Blemished
+Bony
+Brawny
+Breathtaking
+Bruised
+Bulky
+Burly
+Calm
+Chaotic
+Charming
+Chubby
+Coarse
+Deformed
+Delicate
+Despondent
+Dignified
+Disgusting
+Disturbing
+Dull
+Effeminate
+Elegant
+Emaciated
+Energetic
+Energetic
+Exasperated
+Exotic
+Faint
+Feisty
+Flabby
+Flamboyant
+Fragile
+Frail
+Frazzled
+Friendly
+Gap-toothed
+Gaunt
+Gentle
+Gloomy
+Gormless
+Hawkish
+Hawkish
+Healthy
+Hefty
+Hobbling
+Hyper
+Imposing
+Inscrutable
+Jaded
+Lax
+Lean
+Limp
+Lithe
+Lopsided
+Lovely
+Malnourished
+Mangled
+Mangled
+Masculine
+Messy
+Muscular
+Nimble
+Pathetic
+Peppy
+Petite
+Pompous
+Pugnacious
+Quievering
+Radical
+Repulsive
+Robust
+Roguish
+Rough
+Scarred
+Scrawny
+Sculpted
+Shifty
+Shrewd
+Sickly
+Skittish
+Sleek
+Sleepy
+Slender
+Slimy
+Slovenly
+Sluggish
+Sly
+Smooth
+Sniveling
+Soulrendered
+Spacy
+Stiff
+Stony
+Stout
+Strapping
+Sturdy
+Stylish
+Swarthy
+Tense
+Tubular
+Unattractive
+Unblemished
+Unhealthy
+Unremarkable
+Unsightly
+Verbose
+Vigorous
+Waifish
+Wilted
+Wily
+Withered
+Worn-Out
+Wrinkly
+Zealous
+Zesty
diff --git a/tgui/packages/tgui/interfaces/Guestbook.tsx b/tgui/packages/tgui/interfaces/Guestbook.tsx
new file mode 100644
index 000000000000..d1fb09aa8499
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Guestbook.tsx
@@ -0,0 +1,74 @@
+import { useBackend } from '../backend';
+import { useLocalState } from '../backend';
+import { Stack, Button, Input, Section, Box } from '../components';
+import { Window } from '../layouts';
+
+type Info = {
+ names: NameData[];
+};
+
+type NameData = {
+ real_name: string;
+ given_name: string;
+};
+
+export const Guestbook = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { names = [] } = data;
+
+ const [lastNameBeforeEdit, setLastNameBeforeEdit] = useLocalState<
+ string | null
+ >(context, 'lastNameBeforeEdit', null);
+
+ return (
+
+
+ {(!names.length && ) || (
+
+ {names.map((name) => (
+
+
+
+ ))}
+
+ )}
+
+
+ );
+};