diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index 5a4e7c0380..fb44916ee1 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -210,6 +210,7 @@
// /mob/living/carbon signals
#define COMSIG_CARBON_SOUNDBANG "carbon_soundbang" //from base of mob/living/carbon/soundbang_act(): (list(intensity))
+#define COMSIG_CARBON_IDENTITY_TRANSFERRED_TO "carbon_id_transferred_to" //from datum/dna/transfer_identity(): (datum/dna, transfer_SE)
// /mob/living/simple_animal/hostile signals
#define COMSIG_HOSTILE_ATTACKINGTARGET "hostile_attackingtarget"
@@ -274,6 +275,8 @@
#define COMSIG_HUMAN_MELEE_UNARMED_ATTACK "human_melee_unarmed_attack" //from mob/living/carbon/human/UnarmedAttack(): (atom/target)
#define COMSIG_HUMAN_MELEE_UNARMED_ATTACKBY "human_melee_unarmed_attackby" //from mob/living/carbon/human/UnarmedAttack(): (mob/living/carbon/human/attacker)
#define COMSIG_HUMAN_DISARM_HIT "human_disarm_hit" //Hit by successful disarm attack (mob/living/carbon/human/attacker,zone_targeted)
+#define COMSIG_HUMAN_PREFS_COPIED_TO "human_prefs_copied_to" //from datum/preferences/copy_to(): (datum/preferences, icon_updates, roundstart_checks)
+#define COMSIG_HUMAN_HARDSET_DNA "human_hardset_dna" //from mob/living/carbon/human/hardset_dna(): (ui, se, newreal_name, newblood_type, datum/species, newfeatures)
// /datum/species signals
#define COMSIG_SPECIES_GAIN "species_gain" //from datum/species/on_species_gain(): (datum/species/new_species, datum/species/old_species)
diff --git a/code/__HELPERS/_cit_helpers.dm b/code/__HELPERS/_cit_helpers.dm
index 4ae30838e4..ffc1bcc6b7 100644
--- a/code/__HELPERS/_cit_helpers.dm
+++ b/code/__HELPERS/_cit_helpers.dm
@@ -107,38 +107,6 @@ GLOBAL_VAR_INIT(miscreants_allowed, FALSE)
if(!src.holder) return
message_admins("[key_name_admin(usr)] manually reloaded mentors")
-//Flavor Text
-/mob/proc/set_flavor()
- set name = "Set Flavor Text"
- set desc = "Sets an extended description of your character's features."
- set category = "IC"
-
- var/new_flavor = stripped_multiline_input(usr, "Set the flavor text in your 'examine' verb. This can also be used for OOC notes and preferences!", "Flavor Text", flavor_text, MAX_FAVOR_LEN, TRUE)
- if(!isnull(new_flavor))
- flavor_text = html_decode(new_flavor)
- to_chat(src, "Your flavor text has been updated.")
-
-//Flavor Text
-/mob/proc/set_flavor_2()
- set name = "Set Temporary Flavor Text"
- set desc = "Sets a description of your character's current appearance. Use this for emotions, poses etc."
- set category = "IC"
-
- var/new_flavor = stripped_multiline_input(usr, "Set the temporary flavor text in your 'examine' verb. This should be used only for things pertaining to the current round!", "Short-Term Flavor Text", flavor_text_2, MAX_FAVOR_LEN, TRUE)
- if(!isnull(new_flavor))
- flavor_text_2 = html_decode(new_flavor)
- to_chat(src, "Your temporary flavor text has been updated.")
-
-/mob/proc/print_flavor_text(flavor,temp = FALSE)
- if(!flavor)
- return
- // We are decoding and then encoding to not only get correct amount of characters, but also to prevent partial escaping characters being shown.
- var/msg = html_decode(replacetext(flavor, "\n", " "))
- if(length_char(msg) <= 40)
- return "[html_encode(msg)]"
- else
- return "[html_encode(copytext_char(msg, 1, 37))]... More..."
-
//LOOC toggles
/client/verb/listen_looc()
set name = "Show/Hide LOOC"
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index 68c6839482..3994dcc8bc 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -51,10 +51,11 @@
if(ishuman(destination))
var/mob/living/carbon/human/H = destination
H.give_genitals(TRUE)//This gives the body the genitals of this DNA. Used for any transformations based on DNA
- destination.flavor_text = destination.dna.features["flavor_text"] //Update the flavor_text to use new dna text
if(transfer_SE)
destination.dna.struc_enzymes = struc_enzymes
+ SEND_SIGNAL(destination, COMSIG_CARBON_IDENTITY_TRANSFERRED_TO, src, transfer_SE)
+
/datum/dna/proc/copy_dna(datum/dna/new_dna)
new_dna.unique_enzymes = unique_enzymes
new_dna.struc_enzymes = struc_enzymes
@@ -284,7 +285,6 @@
if(newfeatures)
dna.features = newfeatures
- flavor_text = dna.features["flavor_text"] //Update the flavor_text to use new dna text
if(mrace)
var/datum/species/newrace = new mrace.type
@@ -306,6 +306,8 @@
dna.struc_enzymes = se
domutcheck()
+ SEND_SIGNAL(src, COMSIG_HUMAN_HARDSET_DNA, ui, se, newreal_name, newblood_type, mrace, newfeatures)
+
if(mrace || newfeatures || ui)
update_body()
update_hair()
diff --git a/code/datums/elements/flavor_text.dm b/code/datums/elements/flavor_text.dm
new file mode 100644
index 0000000000..f823f4aa79
--- /dev/null
+++ b/code/datums/elements/flavor_text.dm
@@ -0,0 +1,113 @@
+/datum/element/flavor_text
+ element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
+ id_arg_index = 3
+ var/flavor_name = "Flavor Text"
+ var/procpath/verb_instance
+ var/invoke_proc
+ var/list/texts_by_mob = list()
+ var/addendum = "This can also be used for OOC notes and preferences!"
+ var/always_show = FALSE
+ var/max_len = MAX_FAVOR_LEN
+
+/datum/element/flavor_text/Attach(datum/target, text, _proc, _name = "Flavor Text", _desc = "Sets an extended description of your character's features.", _addendum, _max_len = MAX_FAVOR_LEN, _always_show = FALSE, can_edit = TRUE)
+ . = ..()
+
+ if(. == ELEMENT_INCOMPATIBLE || !isatom(target)) //no reason why this shouldn't work on atoms too.
+ return ELEMENT_INCOMPATIBLE
+
+ texts_by_mob[target] = text
+ if(_name)
+ flavor_name = _name
+ if(_proc)
+ invoke_proc = _proc
+ if(_max_len)
+ max_len = _max_len
+ if(!isnull(addendum))
+ addendum = _addendum
+ always_show = _always_show
+
+ RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/show_flavor)
+
+ if(can_edit && ismob(target)) //but only mobs receive the proc/verb for the time being
+ var/mob/M = target
+ if(!verb_instance)
+ verb_instance = new /datum/element/flavor_text/proc/set_flavor (src, "Set [_name]", _desc)
+ M.verbs += verb_instance
+
+/datum/element/flavor_text/Detach(atom/A)
+ . = ..()
+ UnregisterSignal(A, COMSIG_PARENT_EXAMINE)
+ texts_by_mob -= A
+ A.verbs -= verb_instance
+
+/datum/element/flavor_text/proc/show_flavor(atom/target, mob/user, list/examine_list)
+ if(!always_show && isliving(target))
+ var/mob/living/L = target
+ var/unknown = L.get_visible_name() == "Unknown"
+ if(!unknown && iscarbon(target))
+ var/mob/living/carbon/C = L
+ unknown = (C.wear_mask && (C.wear_mask.flags_inv & HIDEFACE)) || (C.head && (C.head.flags_inv & HIDEFACE))
+ if(unknown)
+ if(!("...?" in examine_list)) //can't think of anything better in case of multiple flavor texts.
+ examine_list += "...?"
+ return
+ var/text = texts_by_mob[target]
+ if(!text)
+ return
+ var/msg = replacetext(text, "\n", " ")
+ if(length_char(msg) <= 40)
+ return "[html_encode(msg)]"
+ else
+ return "[html_encode(copytext_char(msg, 1, 37))]... More..."
+
+/datum/element/flavor_text/Topic(href, href_list)
+ . = ..()
+ if(.)
+ return
+ if(href_list["show_flavor"])
+ var/atom/target = href_list["show_flavor"]
+ var/text = texts_by_mob[target]
+ if(text)
+ usr << browse("
[target.name][replacetext(texts_by_mob[target], "\n", "
")]", "window=[target.name];size=500x200")
+ onclose(usr, "[target.name]")
+ return TRUE
+
+/datum/element/flavor_text/proc/set_flavor()
+ set category = "IC"
+
+ if(!(usr in texts_by_mob))
+ return
+
+ var/lower_name = lowertext(flavor_name)
+ var/new_text = stripped_multiline_input(usr, "Set the [lower_name] displayed on 'examine'. [addendum]", flavor_name, texts_by_mob[usr], max_len, TRUE)
+ if(!isnull(new_text) && (usr in texts_by_mob))
+ texts_by_mob[usr] = html_decode(new_text)
+ to_chat(src, "Your [lower_name] has been updated.")
+ if(invoke_proc)
+ INVOKE_ASYNC(usr, invoke_proc, new_text)
+
+//subtypes with additional hooks for DNA and preferences.
+/datum/element/flavor_text/carbon
+ invoke_proc = /mob/living/carbon.proc/update_flavor_text_feature
+
+/datum/element/flavor_text/carbon/Attach(datum/target, text, _proc, _name = "Flavor Text", _desc = "Sets an extended description of your character's features.", _addendum, _max_len = MAX_FAVOR_LEN, _always_show = FALSE, can_edit = TRUE)
+ if(!iscarbon(target))
+ return ELEMENT_INCOMPATIBLE
+ . = ..()
+ if(. == ELEMENT_INCOMPATIBLE)
+ return
+ RegisterSignal(target, COMSIG_CARBON_IDENTITY_TRANSFERRED_TO, .proc/update_dna_flavor_text)
+ if(ishuman(target))
+ RegisterSignal(target, COMSIG_HUMAN_PREFS_COPIED_TO, .proc/update_prefs_flavor_text)
+ RegisterSignal(target, COMSIG_HUMAN_HARDSET_DNA, .proc/update_dna_flavor_text)
+
+
+/datum/element/flavor_text/carbon/Detach(mob/living/carbon/C)
+ . = ..()
+ UnregisterSignal(C, list(COMSIG_CARBON_IDENTITY_TRANSFERRED_TO, COMSIG_HUMAN_PREFS_COPIED_TO, COMSIG_HUMAN_HARDSET_DNA))
+
+/datum/element/flavor_text/carbon/proc/update_dna_flavor_text(mob/living/carbon/C)
+ texts_by_mob[C] = C.dna.features["flavor_text"]
+
+/datum/element/flavor_text/carbon/proc/update_prefs_flavor_text(mob/living/carbon/human/H, datum/preferences/P, icon_updates = TRUE, roundstart_checks = TRUE)
+ texts_by_mob[H] = P.features["flavor_text"]
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index cdc3fbc5b9..a48680fb80 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -2390,6 +2390,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
else
character.Digitigrade_Leg_Swap(TRUE)
+ SEND_SIGNAL(character, COMSIG_HUMAN_PREFS_COPIED_TO, src, icon_updates, roundstart_checks)
+
//let's be sure the character updates
if(icon_updates)
character.update_body()
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 574a5aedd2..5f8b90bb5f 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -1010,3 +1010,8 @@
/mob/living/carbon/can_hold_items()
return TRUE
+
+/mob/living/carbon/proc/update_flavor_text_feature(new_text)
+ if(!dna)
+ return
+ dna.features["flavor_text"] = new_text
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index fec82b357f..cb21534d25 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -25,15 +25,10 @@
var/list/obscured = check_obscured_slots()
var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
- if(ishuman(src)) //user just returned, y'know, the user's own species. dumb.
- var/mob/living/carbon/human/H = src
- var/datum/species/pref_species = H.dna.species
- if(get_visible_name() == "Unknown") // same as flavor text, but hey it works.
- . += "You can't make out what species they are."
- else if(skipface)
- . += "You can't make out what species they are."
- else
- . += "[t_He] [t_is] a [H.dna.custom_species ? H.dna.custom_species : pref_species.name]!"
+ if(skipface || get_visible_name() == "Unknown")
+ . += "You can't make out what species they are."
+ else
+ . += "[t_He] [t_is] a [dna.custom_species ? dna.custom_species : dna.species.name]!"
//uniform
if(w_uniform && !(SLOT_W_UNIFORM in obscured))
@@ -389,18 +384,8 @@
else if(isobserver(user) && traitstring)
. += "Traits: [traitstring]"
- //No flavor text unless the face can be seen. Prevents certain metagaming with impersonation.
- var/invisible_man = skipface || get_visible_name() == "Unknown"
- if(invisible_man)
- . += "...?"
- else
- var/flavor = print_flavor_text(flavor_text)
- if(flavor)
- . += flavor
- var/temp_flavor = print_flavor_text(flavor_text_2,TRUE)
- if(temp_flavor)
- . += temp_flavor
- SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .)
+ SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .) //This also handles flavor texts now
+
. += "*---------*"
/mob/living/proc/status_effect_examines(pronoun_replacement) //You can include this in any mob's examine() to show the examine texts of status effects!
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 423277863f..2320715dac 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -9,8 +9,6 @@
verbs += /mob/living/proc/mob_sleep
verbs += /mob/living/proc/lay_down
verbs += /mob/living/carbon/human/proc/underwear_toggle //fwee
- verbs += /mob/proc/set_flavor
- verbs += /mob/proc/set_flavor_2
//initialize limbs first
create_bodyparts()
@@ -40,6 +38,10 @@
. = ..()
if(!CONFIG_GET(flag/disable_human_mood))
AddComponent(/datum/component/mood)
+ AddElement(/datum/element/flavor_text/carbon)
+ AddElement(/datum/element/flavor_text, null, null, "Temporary Flavor Text",
+ "Sets a description of your character's current appearance. Use this for emotions, poses etc.",
+ "This should be used only for things pertaining to the current round!")
/mob/living/carbon/human/Destroy()
QDEL_NULL(physiology)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 1ca6299efb..f9acb8bd45 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -504,14 +504,6 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0)
unset_machine()
src << browse(null, t1)
- if(href_list["flavor_more"])
- usr << browse(text("[][]", name, replacetext(flavor_text, "\n", "
")), text("window=[];size=500x200", name))
- onclose(usr, "[name]")
-
- if(href_list["flavor2_more"])
- usr << browse(text("[][]", name, replacetext(flavor_text_2, "\n", "
")), text("window=[];size=500x200", name))
- onclose(usr, "[name]")
-
if(href_list["refresh"])
if(machine && in_range(src, usr))
show_inv(machine)
diff --git a/modular_citadel/code/modules/client/preferences.dm b/modular_citadel/code/modules/client/preferences.dm
index 8b0ba2dd43..f1dbe31d02 100644
--- a/modular_citadel/code/modules/client/preferences.dm
+++ b/modular_citadel/code/modules/client/preferences.dm
@@ -49,6 +49,5 @@
/datum/preferences/copy_to(mob/living/carbon/human/character, icon_updates = 1)
..()
character.give_genitals(TRUE)
- character.flavor_text = features["flavor_text"] //Let's update their flavor_text at least initially
if(icon_updates)
character.update_genitals()
diff --git a/tgstation.dme b/tgstation.dme
index 73beef27e5..e83df63774 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -499,6 +499,7 @@
#include "code\datums\elements\dusts_on_leaving_area.dm"
#include "code\datums\elements\earhealing.dm"
#include "code\datums\elements\firestacker.dm"
+#include "code\datums\elements\flavor_text.dm"
#include "code\datums\elements\ghost_role_eligibility.dm"
#include "code\datums\elements\mob_holder.dm"
#include "code\datums\elements\swimming.dm"