diff --git a/code/__HELPERS/spatial_info.dm b/code/__HELPERS/spatial_info.dm
index 4cc71bb091b..2f3ae753327 100644
--- a/code/__HELPERS/spatial_info.dm
+++ b/code/__HELPERS/spatial_info.dm
@@ -214,6 +214,16 @@
for(var/obj/item/radio/radio as anything in radios)
. |= get_hearers_in_LOS(radio.canhear_range, radio)
+/// A filter to be applied to get_hearers_in_x, that removes any non-mob hearers, converting them to their relevant mob if one exists (such as dullahan heads).
+/// Modifies input list.
+/proc/mob_only_listeners(list/atom/movable/hearers)
+ RETURN_TYPE(/list/mob)
+ for(var/hearer_index in 1 to hearers.len)
+ var/atom/movable/hearer = hearers[hearer_index]
+ hearers[hearer_index] = hearer.get_listening_mob()
+ list_clear_nulls(hearers)
+ return hearers
+
//Used when converting pixels to tiles to make them accurate
#define OFFSET_X (0.5 / ICON_SIZE_X)
#define OFFSET_Y (0.5 / ICON_SIZE_Y)
diff --git a/code/controllers/subsystem/tts.dm b/code/controllers/subsystem/tts.dm
index 1106318e021..5bd65b63e19 100644
--- a/code/controllers/subsystem/tts.dm
+++ b/code/controllers/subsystem/tts.dm
@@ -105,7 +105,10 @@ SUBSYSTEM_DEF(tts)
return
var/channel = SSsounds.random_available_channel()
- for(var/mob/listening_mob in listeners | SSmobs.dead_players_by_zlevel[turf_source.z])//observers always hear through walls
+ for(var/atom/movable/hearer in listeners | SSmobs.dead_players_by_zlevel[turf_source.z])//observers always hear through walls
+ var/mob/listening_mob = hearer.get_listening_mob()
+ if(isnull(listening_mob))
+ continue
if(QDELING(listening_mob))
stack_trace("TTS tried to play a sound to a deleted mob.")
continue
@@ -115,13 +118,13 @@ SUBSYSTEM_DEF(tts)
if(volume_modifier == 0 || (tts_pref == TTS_SOUND_OFF))
continue
- var/sound_volume = ((listening_mob == target)? 60 : 85) + volume_offset
+ var/sound_volume = ((hearer == target)? 60 : 85) + volume_offset
sound_volume = sound_volume*volume_modifier
var/datum/language_holder/holder = listening_mob.get_language_holder()
var/audio_to_use = (tts_pref == TTS_SOUND_BLIPS) ? audio_blips : audio
if(!holder.has_language(language))
continue
- if(get_dist(listening_mob, turf_source) <= range)
+ if(get_dist(hearer, turf_source) <= range)
listening_mob.playsound_local(
turf_source,
vol = sound_volume,
diff --git a/code/datums/quirks/negative_quirks/blindness.dm b/code/datums/quirks/negative_quirks/blindness.dm
index 8dafede1fe1..42517e05344 100644
--- a/code/datums/quirks/negative_quirks/blindness.dm
+++ b/code/datums/quirks/negative_quirks/blindness.dm
@@ -20,6 +20,11 @@
blindfold.colored_before = TRUE
give_item_to_holder(blindfold, list(LOCATION_EYES, LOCATION_HANDS))
+/datum/quirk/item_quirk/blindness/is_species_appropriate(datum/species/mob_species)
+ if(ispath(mob_species, /datum/species/dullahan))
+ return FALSE
+ return ..()
+
/datum/quirk/item_quirk/blindness/add(client/client_source)
quirk_holder.become_blind(QUIRK_TRAIT)
diff --git a/code/datums/quirks/negative_quirks/brain_problems.dm b/code/datums/quirks/negative_quirks/brain_problems.dm
index 8bafa07b9dd..4ecc9c0713a 100644
--- a/code/datums/quirks/negative_quirks/brain_problems.dm
+++ b/code/datums/quirks/negative_quirks/brain_problems.dm
@@ -29,5 +29,10 @@
notify_player = TRUE,
)
+/datum/quirk/item_quirk/brainproblems/is_species_appropriate(datum/species/mob_species)
+ if(ispath(mob_species, /datum/species/dullahan))
+ return FALSE
+ return ..()
+
/datum/quirk/item_quirk/brainproblems/process(seconds_per_tick)
quirk_holder.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.2 * seconds_per_tick)
diff --git a/code/datums/quirks/negative_quirks/fluoride_stare.dm b/code/datums/quirks/negative_quirks/fluoride_stare.dm
index 97b9c75f3e9..c14efee3014 100644
--- a/code/datums/quirks/negative_quirks/fluoride_stare.dm
+++ b/code/datums/quirks/negative_quirks/fluoride_stare.dm
@@ -10,6 +10,11 @@
quirk_flags = QUIRK_HUMAN_ONLY
mail_goodies = list(/obj/item/reagent_containers/cup/bottle/salglu_solution, /obj/item/light/bulb)
+/datum/quirk/item_quirk/fluoride_stare/is_species_appropriate(datum/species/mob_species)
+ if(ispath(mob_species, /datum/species/dullahan))
+ return FALSE
+ return ..()
+
/datum/quirk/item_quirk/fluoride_stare/add_unique(client/client_source)
var/obj/item/reagent_containers/cup/bottle/salglu_solution/saline = new(get_turf(quirk_holder))
give_item_to_holder(saline, list(
diff --git a/code/datums/quirks/negative_quirks/nearsighted.dm b/code/datums/quirks/negative_quirks/nearsighted.dm
index a9e945861d7..3c0b8f78c53 100644
--- a/code/datums/quirks/negative_quirks/nearsighted.dm
+++ b/code/datums/quirks/negative_quirks/nearsighted.dm
@@ -14,6 +14,11 @@
associated_typepath = /datum/quirk/item_quirk/nearsighted
customization_options = list(/datum/preference/choiced/glasses)
+/datum/quirk/item_quirk/nearsighted/is_species_appropriate(datum/species/mob_species)
+ if(ispath(mob_species, /datum/species/dullahan))
+ return FALSE
+ return ..()
+
/datum/quirk/item_quirk/nearsighted/add_unique(client/client_source)
var/glasses_name = client_source?.prefs.read_preference(/datum/preference/choiced/glasses) || "Regular"
var/obj/item/clothing/glasses/glasses_type
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 18943c0b6ee..c4eff9208cc 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -814,7 +814,10 @@
return ..()
/mob/living/carbon/can_be_revived()
- if(!get_organ_by_type(/obj/item/organ/brain) && (!IS_CHANGELING(src)) || HAS_TRAIT(src, TRAIT_HUSK))
+ if(HAS_TRAIT(src, TRAIT_HUSK))
+ return FALSE
+ var/brainless_creature = IS_CHANGELING(src) || isdullahan(src)
+ if(!brainless_creature && !get_organ_by_type(/obj/item/organ/brain))
return FALSE
return ..()
@@ -842,6 +845,9 @@
return DEFIB_FAIL_FAILING_HEART
var/obj/item/organ/brain/current_brain = get_organ_by_type(/obj/item/organ/brain)
+ if(isdullahan(src))
+ var/datum/species/dullahan/dullahan_species = src.dna.species
+ current_brain = locate() in dullahan_species.my_head.loc
if (QDELETED(current_brain))
return DEFIB_FAIL_NO_BRAIN
diff --git a/code/modules/mob/living/carbon/human/species_types/dullahan.dm b/code/modules/mob/living/carbon/human/species_types/dullahan.dm
index 7375c259243..e499d768eff 100644
--- a/code/modules/mob/living/carbon/human/species_types/dullahan.dm
+++ b/code/modules/mob/living/carbon/human/species_types/dullahan.dm
@@ -44,6 +44,9 @@
RegisterSignal(human, COMSIG_CARBON_ATTACH_LIMB, PROC_REF(on_gained_part))
var/obj/item/bodypart/head/head = human.get_bodypart(BODY_ZONE_HEAD)
+ head.speech_span = null
+ if(isnull(human.drop_location()))
+ return
head?.drop_limb()
if(QDELETED(head)) //drop_limb() deletes the limb if no drop location exists and character setup dummies are located in nullspace.
return
@@ -54,16 +57,20 @@
var/obj/item/organ/eyes/eyes = new /obj/item/organ/eyes(head)
eyes.eye_color_left = human.eye_color_left
eyes.eye_color_right = human.eye_color_right
- eyes.bodypart_insert(my_head)
+ eyes.bodypart_insert(head)
human.update_body()
+ head.update_limb()
head.update_icon_dropped()
RegisterSignal(head, COMSIG_QDELETING, PROC_REF(on_head_destroyed))
+ RegisterSignal(my_head, COMSIG_MOVABLE_MOVED, PROC_REF(on_relay_move))
/// If we gained a new body part, it had better not be a head
/datum/species/dullahan/proc/on_gained_part(mob/living/carbon/human/dullahan, obj/item/bodypart/part)
SIGNAL_HANDLER
- if (part.body_zone != BODY_ZONE_HEAD)
+ if(part.body_zone != BODY_ZONE_HEAD)
return
+ if(isnull(dullahan.drop_location()))
+ return // don't gib nullspace
my_head = null
dullahan.investigate_log("has been gibbed by having an illegal head put on [dullahan.p_their()] shoulders.", INVESTIGATE_DEATHS)
dullahan.gib(DROP_ALL_REMAINS) // Yeah so giving them a head on their body is really not a good idea, so their original head will remain but uh, good luck fixing it after that.
@@ -72,12 +79,20 @@
/datum/species/dullahan/proc/on_head_destroyed()
SIGNAL_HANDLER
var/mob/living/human = my_head?.owner
- if (QDELETED(human))
+ if(QDELETED(human))
return // guess we already died
my_head = null
human.investigate_log("has been gibbed by the loss of [human.p_their()] head.", INVESTIGATE_DEATHS)
human.gib(DROP_ALL_REMAINS)
+/// Head was butchered? No more dullahan
+/datum/species/dullahan/proc/on_relay_move()
+ SIGNAL_HANDLER
+ if(QDELETED(my_head?.owner) || !isdullahan(my_head?.owner))
+ return
+ my_head.owner.gib(DROP_ALL_REMAINS)
+ QDEL_NULL(my_head)
+
/datum/species/dullahan/on_species_loss(mob/living/carbon/human/human)
. = ..()
if(my_head)
@@ -169,7 +184,8 @@
organ_flags = ORGAN_ORGANIC //not vital
/obj/item/organ/tongue/dullahan
- zone = "abstract"
+ zone = BODY_ZONE_CHEST
+ organ_flags = parent_type::organ_flags | ORGAN_UNREMOVABLE
modifies_speech = TRUE
/obj/item/organ/tongue/dullahan/handle_speech(datum/source, list/speech_args)
@@ -179,17 +195,23 @@
var/datum/species/dullahan/dullahan_species = human.dna.species
if(isobj(dullahan_species.my_head.loc))
var/obj/head = dullahan_species.my_head.loc
- head.say(speech_args[SPEECH_MESSAGE], spans = speech_args[SPEECH_SPANS], sanitize = FALSE, language = speech_args[SPEECH_LANGUAGE], message_range = speech_args[SPEECH_RANGE])
+ if(speech_args[SPEECH_MODS][WHISPER_MODE]) // whisper away
+ speech_args[SPEECH_SPANS] |= SPAN_ITALICS
+ head.say(speech_args[SPEECH_MESSAGE], spans = speech_args[SPEECH_SPANS], sanitize = FALSE, language = speech_args[SPEECH_LANGUAGE], message_range = speech_args[SPEECH_RANGE], message_mods = speech_args[SPEECH_MODS])
speech_args[SPEECH_MESSAGE] = ""
/obj/item/organ/ears/dullahan
- zone = "abstract"
+ zone = BODY_ZONE_CHEST
+ organ_flags = parent_type::organ_flags | ORGAN_UNREMOVABLE
+ decay_factor = 0
/obj/item/organ/eyes/dullahan
name = "head vision"
desc = "An abstraction."
actions_types = list(/datum/action/item_action/organ_action/dullahan)
- zone = "abstract"
+ zone = BODY_ZONE_CHEST
+ organ_flags = parent_type::organ_flags | ORGAN_UNREMOVABLE
+ decay_factor = 0
tint = INFINITY // to switch the vision perspective to the head on species_gain() without issue.
/datum/action/item_action/organ_action/dullahan
@@ -223,7 +245,7 @@
START_PROCESSING(SSobj, src)
RegisterSignal(owner, COMSIG_CARBON_REGENERATE_LIMBS, PROC_REF(unlist_head))
RegisterSignal(owner, COMSIG_LIVING_REVIVE, PROC_REF(retrieve_head))
- RegisterSignal(owner, COMSIG_HUMAN_PREFS_APPLIED, PROC_REF(update_prefs_name))
+ RegisterSignal(owner, COMSIG_HUMAN_PREFS_APPLIED, PROC_REF(on_prefs_loaded))
become_hearing_sensitive(ROUNDSTART_TRAIT)
/obj/item/dullahan_relay/Destroy()
@@ -231,32 +253,33 @@
owner = null
return ..()
-/obj/item/dullahan_relay/process()
- if(istype(loc, /obj/item/bodypart/head) && !QDELETED(owner))
- return
- qdel(src)
- return PROCESS_KILL
-
/// Updates our names after applying name prefs
-/obj/item/dullahan_relay/proc/update_prefs_name(mob/living/carbon/human/wearer)
+/obj/item/dullahan_relay/proc/on_prefs_loaded(mob/living/carbon/human/headless)
SIGNAL_HANDLER
var/obj/item/bodypart/head/detached_head = loc
if (!istype(detached_head))
return // It's so over
- detached_head.real_name = wearer.real_name
- detached_head.name = wearer.real_name
+ detached_head.real_name = headless.real_name
+ detached_head.name = headless.real_name
+ name = headless.real_name
+ detached_head.voice = headless.voice
+ detached_head.pitch = pitch
var/obj/item/organ/brain/brain = locate(/obj/item/organ/brain) in detached_head
- brain.name = "[wearer.name]'s brain"
+ brain.name = "[headless.name]'s brain"
+
+ detached_head.copy_appearance_from(headless, overwrite_eyes = TRUE)
+ detached_head.update_icon_dropped()
/obj/item/dullahan_relay/Hear(atom/movable/speaker, message_language, raw_message, radio_freq, radio_freq_name, radio_freq_color, list/spans, list/message_mods = list(), message_range)
. = ..()
- owner?.Hear(speaker, message_language, raw_message, radio_freq, radio_freq_name, radio_freq_color, spans, message_mods, message_range)
-
-///Adds the owner to the list of hearers in hearers_in_view(), for visible/hearable on top of say messages
-/obj/item/dullahan_relay/proc/include_owner(datum/source, list/hearers)
- SIGNAL_HANDLER
- if(!QDELETED(owner))
- hearers += owner
+ var/dist = get_dist(speaker, src) - message_range
+ if(dist > 0 && dist <= EAVESDROP_EXTRA_RANGE)
+ raw_message = stars(raw_message)
+ if(message_range != INFINITY && dist > EAVESDROP_EXTRA_RANGE)
+ return FALSE
+ if(!owner)
+ return FALSE
+ return owner.Hear(speaker, message_language, raw_message, radio_freq, radio_freq_name, radio_freq_color, spans, message_mods, message_range = INFINITY)
///Stops dullahans from gibbing when regenerating limbs
/obj/item/dullahan_relay/proc/unlist_head(datum/source, list/excluded_zones)
@@ -273,13 +296,3 @@
var/turf/body_turf = get_turf(owner)
if(head && istype(head) && body_turf && !(head in owner.get_all_contents()))
head.forceMove(body_turf)
-
-/obj/item/dullahan_relay/Destroy()
- if(!QDELETED(owner))
- var/mob/living/carbon/human/human = owner
- if(isdullahan(human))
- var/datum/species/dullahan/dullahan_species = human.dna.species
- dullahan_species.my_head = null
- owner.gib(DROP_ALL_REMAINS)
- owner = null
- return ..()
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index cc57babea8a..9e9455da1cf 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -286,44 +286,42 @@
if(!islist(ignored_mobs))
ignored_mobs = list(ignored_mobs)
- var/list/hearers = get_hearers_in_view(vision_distance, src) //caches the hearers and then removes ignored mobs.
+ var/list/hearers = mob_only_listeners(get_hearers_in_view(vision_distance, src)) //caches the hearers and then removes ignored mobs.
hearers -= ignored_mobs
- if(self_message)
- hearers -= src
-
var/raw_msg = message
if(visible_message_flags & WITH_EMPHASIS_MESSAGE)
message = apply_message_emphasis(message)
if(visible_message_flags & EMOTE_MESSAGE)
message = span_emote("[src] [message]")
- for(var/mob/M in hearers)
- if(!M.client)
+ for(var/mob/hearing_mob as anything in hearers)
+ if(!hearing_mob?.client)
+ continue
+ if(self_message && hearing_mob == src)
continue
//This entire if/else chain could be in two lines but isn't for readibilties sake.
var/msg = message
var/msg_type = MSG_VISUAL
- if(M.see_invisible < invisibility)//if src is invisible to M
+ if(hearing_mob.see_invisible < invisibility)//if src is invisible to M
msg = blind_message
msg_type = MSG_AUDIBLE
else if(T != loc && T != src) //if src is inside something and not a turf.
- if(M != loc) // Only give the blind message to hearers that aren't the location
+ if(hearing_mob != loc) // Only give the blind message to hearers that aren't the location
msg = blind_message
msg_type = MSG_AUDIBLE
- else if(!HAS_TRAIT(M, TRAIT_HEAR_THROUGH_DARKNESS) && M.lighting_cutoff < LIGHTING_CUTOFF_HIGH && T.is_softly_lit() && !in_range(T,M)) //if it is too dark, unless we're right next to them.
+ else if(!HAS_TRAIT(hearing_mob, TRAIT_HEAR_THROUGH_DARKNESS) && hearing_mob.lighting_cutoff < LIGHTING_CUTOFF_HIGH && T.is_softly_lit() && !in_range(T,hearing_mob)) //if it is too dark, unless we're right next to them.
msg = blind_message
msg_type = MSG_AUDIBLE
if(!msg)
continue
- if(visible_message_flags & EMOTE_MESSAGE && runechat_prefs_check(M, visible_message_flags) && !M.is_blind())
- M.create_chat_message(src, raw_message = raw_msg, runechat_flags = visible_message_flags)
-
- M.show_message(msg, msg_type, blind_message, MSG_AUDIBLE)
+ if(visible_message_flags & EMOTE_MESSAGE && runechat_prefs_check(hearing_mob, visible_message_flags) && !hearing_mob.is_blind())
+ hearing_mob.create_chat_message(src, raw_message = raw_msg, runechat_flags = visible_message_flags)
+ hearing_mob.show_message(msg, msg_type, blind_message, MSG_AUDIBLE)
///Adds the functionality to self_message.
/mob/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs, visible_message_flags = NONE)
@@ -360,18 +358,20 @@
* * audible_message_flags (optional) is the type of message being sent.
*/
/atom/proc/audible_message(message, deaf_message, hearing_distance = DEFAULT_MESSAGE_RANGE, self_message, audible_message_flags = NONE)
- var/list/hearers = get_hearers_in_view(hearing_distance, src)
- if(self_message)
- hearers -= src
+ var/list/hearers = mob_only_listeners(get_hearers_in_view(hearing_distance, src))
var/raw_msg = message
if(audible_message_flags & WITH_EMPHASIS_MESSAGE)
message = apply_message_emphasis(message)
if(audible_message_flags & EMOTE_MESSAGE)
message = span_emote("[src] [message]")
- for(var/mob/M in hearers)
- if(audible_message_flags & EMOTE_MESSAGE && 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(message, MSG_AUDIBLE, deaf_message, MSG_VISUAL)
+ for(var/mob/hearing_mob as anything in hearers)
+ if(!hearing_mob?.client)
+ continue
+ if(self_message && hearing_mob == src)
+ continue
+ if(audible_message_flags & EMOTE_MESSAGE && runechat_prefs_check(hearing_mob, audible_message_flags) && hearing_mob.can_hear())
+ hearing_mob.create_chat_message(src, raw_message = raw_msg, runechat_flags = audible_message_flags)
+ hearing_mob.show_message(message, MSG_AUDIBLE, deaf_message, MSG_VISUAL)
/**
* Show a message to all mobs in earshot of this one
@@ -405,6 +405,21 @@
if(self_runechat && (audible_message_flags & EMOTE_MESSAGE) && runechat_prefs_check(src, audible_message_flags))
create_chat_message(src, raw_message = raw_self_message, runechat_flags = audible_message_flags)
+/// Gets a linked mob, letting atoms act as proxies for actions that rely on hearing sensitivity.
+/// For example, AIs hearing around their holopads, and dullahans hearing around their heads.
+/// Normal say messages are handled by Hear(), this is for other visible/audible messages
+/atom/movable/proc/get_listening_mob()
+ return
+
+/obj/effect/overlay/holo_pad_hologram/get_listening_mob()
+ return Impersonation
+
+/obj/item/dullahan_relay/get_listening_mob()
+ return owner
+
+/mob/get_listening_mob()
+ return src
+
///Returns the client runechat visible messages preference according to the message type.
/atom/proc/runechat_prefs_check(mob/target, visible_message_flags = NONE)
if(!target.client?.prefs.read_preference(/datum/preference/toggle/enable_runechat))
diff --git a/code/modules/surgery/bodyparts/head_hair_and_lips.dm b/code/modules/surgery/bodyparts/head_hair_and_lips.dm
index 9348551ab02..3f7cb5228e7 100644
--- a/code/modules/surgery/bodyparts/head_hair_and_lips.dm
+++ b/code/modules/surgery/bodyparts/head_hair_and_lips.dm
@@ -3,7 +3,6 @@
/// Part of `update_limb()`, basically does all the head specific icon stuff.
/obj/item/bodypart/head/proc/update_hair_and_lips(dropping_limb, is_creating)
var/mob/living/carbon/human/human_head_owner = owner
- var/datum/species/owner_species = human_head_owner?.dna.species
//HIDDEN CHECKS START
hair_hidden = FALSE
@@ -43,18 +42,40 @@
if(!is_creating || !owner)
return
+ copy_appearance_from(human_head_owner)
- lip_style = human_head_owner.lip_style
- lip_color = human_head_owner.lip_color
- hairstyle = human_head_owner.hairstyle
- hair_alpha = owner_species.hair_alpha
- hair_color = human_head_owner.hair_color
- facial_hairstyle = human_head_owner.facial_hairstyle
- facial_hair_alpha = owner_species.facial_hair_alpha
- facial_hair_color = human_head_owner.facial_hair_color
- fixed_hair_color = owner_species.get_fixed_hair_color(human_head_owner) //Can be null
- gradient_styles = human_head_owner.grad_style.Copy()
- gradient_colors = human_head_owner.grad_color.Copy()
+/obj/item/bodypart/head/proc/copy_appearance_from(mob/living/carbon/human/target, overwrite_eyes = FALSE)
+ var/datum/species/target_species = target.dna.species
+
+ lip_style = target.lip_style
+ lip_color = target.lip_color
+ hairstyle = target.hairstyle
+ hair_alpha = target_species.hair_alpha
+ hair_color = target.hair_color
+ facial_hairstyle = target.facial_hairstyle
+ facial_hair_alpha = target_species.facial_hair_alpha
+ facial_hair_color = target.facial_hair_color
+ fixed_hair_color = target_species.get_fixed_hair_color(target) //Can be null
+ gradient_styles = target.grad_style.Copy()
+ gradient_colors = target.grad_color.Copy()
+ var/obj/item/organ/eyes/peepers = locate() in src
+ if(peepers)
+ if(overwrite_eyes || isnull(initial(peepers.eye_color_left)))
+ peepers.eye_color_left = target.eye_color_left
+ if(overwrite_eyes || isnull(initial(peepers.eye_color_right)))
+ peepers.eye_color_right = target.eye_color_right
+
+ if(HAS_TRAIT(target, TRAIT_USES_SKINTONES))
+ skin_tone = target.skin_tone
+ else if(HAS_TRAIT(target, TRAIT_MUTANT_COLORS))
+ skin_tone = ""
+ if(target_species.fixed_mut_color)
+ species_color = target_species.fixed_mut_color
+ else
+ species_color = target.dna.features["mcolor"]
+ else
+ skin_tone = ""
+ species_color = ""
/obj/item/bodypart/head/proc/get_hair_and_lips_icon(dropped)
SHOULD_CALL_PARENT(TRUE)