diff --git a/code/datums/components/dullahan.dm b/code/datums/components/dullahan.dm
new file mode 100644
index 0000000000..ee6d5beef0
--- /dev/null
+++ b/code/datums/components/dullahan.dm
@@ -0,0 +1,81 @@
+/datum/component/dullahan
+ var/obj/item/dullahan_head/dullahan_head
+
+
+/datum/component/dullahan/Initialize()
+ var/mob/living/carbon/human/H = parent
+ if(!H)
+ return
+
+ dullahan_head = new(get_turf(H))
+
+ dullahan_head.name = "[H.name]'s head"
+ dullahan_head.desc = "the decapitated head of [H.name]"
+ dullahan_head.owner = H
+
+ // make sure the brain can't decay or fall out
+ var/obj/item/organ/brain/B = H.getorganslot(ORGAN_SLOT_BRAIN)
+ B.zone = "abstract" // it exists in the ethereal plain
+ B.organ_flags = ORGAN_NO_SPOIL | ORGAN_NO_DISMEMBERMENT | ORGAN_VITAL
+
+ // moving the brain's zone means we don't need the head to survive
+ var/obj/item/bodypart/head/head = H.get_bodypart(BODY_ZONE_HEAD)
+ head.drop_limb()
+ qdel(head)
+
+ dullahan_head.update_appearance()
+
+/obj/item/dullahan_head
+ name = "coders lament"
+ desc = "you shouldn't be reading this"
+ flags_1 = HEAR_1
+ var/mob/living/carbon/human/owner
+ // this is for keeping track of the overlays because you can't read the actual overlays list as it's a special byond var
+ var/list/overlays_standing
+
+// allow the 'fake' head to relay speech back to the mob
+/obj/item/dullahan_head/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode)
+ if(owner)
+ var/namepart = "[speaker.GetVoice()][speaker.get_alt_name()]"
+ var/hrefpart = ""
+ var/treated_message = lang_treat(speaker, message_language, raw_message, spans, message_mode)
+ var/rendered = "[hrefpart][namepart] [treated_message]"
+
+ if (owner.client?.prefs.chat_on_map && (owner.client.prefs.see_chat_non_mob || ismob(speaker)))
+ owner.create_chat_message(speaker, message_language, raw_message, spans, message_mode)
+ owner.show_message(rendered, MSG_AUDIBLE)
+
+// update head sprite
+/obj/item/dullahan_head/proc/remove_head_overlays()
+ overlays_standing = list()
+ cut_overlays()
+
+/obj/item/dullahan_head/proc/add_head_overlay(var/overlay)
+ overlays_standing += overlay
+ add_overlay(overlay)
+
+/obj/item/dullahan_head/update_appearance()
+ remove_head_overlays()
+ // to do this without duplicating large amounts of code
+ // it's best to regenerate the head, then remove it once we have the overlays we want
+ owner.regenerate_limb(BODY_ZONE_HEAD, TRUE) // don't heal them
+ owner.regenerate_icons() // yes i know it's expensive but do you want me to rewrite our entire overlay system
+ var/obj/item/bodypart/head/head = owner.get_bodypart(BODY_ZONE_HEAD)
+ add_overlay(head.get_limb_icon(FALSE, TRUE, TRUE))
+ for(var/overlay in owner.overlays_standing)
+ if(istype(overlay, /mutable_appearance))
+ var/mutable_appearance/mutable = overlay
+ message_admins("category is [mutable.category] and icon is [mutable.icon_state]")
+ if(mutable.category == "HEAD")
+ add_head_overlay(mutable)
+ else
+ if(islist(overlay))
+ var/list/list_appearances = overlay
+ for(var/overlay2 in list_appearances)
+ if(istype(overlay2, /mutable_appearance))
+ var/mutable_appearance/mutable = overlay2
+ message_admins("category is [mutable.category] and icon is [mutable.icon_state]")
+ if(mutable.category == "HEAD")
+ add_head_overlay(mutable)
+ //head.drop_limb()
+ //qdel(head)
\ No newline at end of file
diff --git a/code/datums/mutable_appearance.dm b/code/datums/mutable_appearance.dm
index b04f55ef0e..104147ebeb 100644
--- a/code/datums/mutable_appearance.dm
+++ b/code/datums/mutable_appearance.dm
@@ -4,6 +4,9 @@
// Mutable appearances are children of images, just so you know.
+/mutable_appearance
+ var/category // listen i want to store some meta information on mutable appearances don't judge me
+
/mutable_appearance/New()
..()
plane = FLOAT_PLANE // No clue why this is 0 by default yet images are on FLOAT_PLANE
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm b/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm
index 08ae75d728..964a171e59 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm
@@ -77,6 +77,8 @@
//For soft-restricting markings to species IDs
var/list/recommended_species
+ var/mutable_category // simply do not worry about this value
+
/datum/sprite_accessory/proc/is_not_visible(var/mob/living/carbon/human/H, var/tauric) //return if the accessory shouldn't be shown
return FALSE
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/ears.dm b/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
index 628a5e53ac..294c3b78f2 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
@@ -2,6 +2,7 @@
icon = 'icons/mob/mutant_bodyparts.dmi'
mutant_part_string = "ears"
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER, BODY_FRONT_LAYER)
+ mutable_category = "HEAD"
/datum/sprite_accessory/ears/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm b/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm
index f3e179e802..06d4c34f72 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm
@@ -4,6 +4,7 @@
/datum/sprite_accessory/facial_hair
icon = 'icons/mob/hair.dmi'
gender = MALE // barf (unless you're a dorf, dorfs dig chix w/ beards :P)
+ mutable_category = "HEAD"
// please make sure they're sorted alphabetically and categorized
/datum/sprite_accessory/facial_hair/shaved //this is exempt from the alphabetical sort
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm b/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm
index e002fa369d..9e868d69c6 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm
@@ -3,6 +3,7 @@
//////////////////////
/datum/sprite_accessory/hair
icon = 'icons/mob/hair.dmi' // default icon for all hairs
+ mutable_category = "HEAD"
// please make sure they're sorted alphabetically and, where needed, categorized
// try to capitalize the names please~
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/horns.dm b/code/modules/mob/dead/new_player/sprite_accessories/horns.dm
index 0d097b24f0..14e340fc2e 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/horns.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/horns.dm
@@ -2,6 +2,7 @@
icon = 'icons/mob/mutant_bodyparts.dmi'
color_src = HORNCOLOR
relevant_layers = list(HORNS_LAYER)
+ mutable_category = "HEAD"
/datum/sprite_accessory/horns/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/ipc_synths.dm b/code/modules/mob/dead/new_player/sprite_accessories/ipc_synths.dm
index 8bda196faa..f73a4a35a1 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/ipc_synths.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/ipc_synths.dm
@@ -6,6 +6,7 @@
icon = 'modular_citadel/icons/mob/ipc_screens.dmi'
color_src = null
relevant_layers = list(BODY_ADJ_LAYER)
+ mutable_category = "HEAD"
/datum/sprite_accessory/screen/blank
name = "Blank"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm b/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm
index 75b62e8c0f..aa9ec41dcc 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm
@@ -2,6 +2,7 @@
icon = 'icons/mob/mutant_bodyparts.dmi'
mutant_part_string = "snout"
relevant_layers = list(BODY_ADJ_LAYER, BODY_FRONT_LAYER)
+ mutable_category = "HEAD"
/datum/sprite_accessory/snouts/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm b/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm
index 08897ade34..870f4f5ba4 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm
@@ -5,6 +5,7 @@
color_src = MUTCOLORS
name = "Synthetic Lizard - Snout"
icon_state = "synthliz_basic"
+ mutable_category = "HEAD"
/datum/sprite_accessory/snouts/mam_snouts/synthliz/synthliz_under
icon = 'modular_citadel/icons/mob/synthliz_snouts.dmi'
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index ddbb23741b..c8cc58c916 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -697,6 +697,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
fhair_file = 'icons/mob/facialhair_extensions.dmi'
var/mutable_appearance/facial_overlay = mutable_appearance(fhair_file, fhair_state, -HAIR_LAYER)
+ facial_overlay.category = "HEAD"
if(!forced_colour)
if(hair_color)
@@ -734,8 +735,10 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(!hair_hidden || dynamic_hair_suffix)
var/mutable_appearance/hair_overlay = mutable_appearance(layer = -HAIR_LAYER)
+ hair_overlay.category = "HEAD"
var/mutable_appearance/gradient_overlay = mutable_appearance(layer = -HAIR_LAYER)
- if(!hair_hidden && !H.getorgan(/obj/item/organ/brain)) //Applies the debrained overlay if there is no brain
+ gradient_overlay.category = "HEAD"
+ if(!hair_hidden && !H.getorgan(/obj/item/organ/brain) && !H.GetComponent(/datum/component/dullahan)) //Applies the debrained overlay if there is no brain (ignore if they are dullahan)
if(!(NOBLOOD in species_traits))
hair_overlay.icon = 'icons/mob/human_parts.dmi'
hair_overlay.icon_state = "debrained"
@@ -811,6 +814,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
// lipstick
if(H.lip_style && (LIPS in species_traits))
var/mutable_appearance/lip_overlay = mutable_appearance('icons/mob/lips.dmi', "lips_[H.lip_style]", -BODY_LAYER)
+ lip_overlay.category = "HEAD"
lip_overlay.color = H.lip_color
if(OFFSET_LIPS in H.dna.species.offset_features)
@@ -822,8 +826,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
// eyes
if(!(NOEYES in species_traits))
var/has_eyes = H.getorganslot(ORGAN_SLOT_EYES)
- if(!has_eyes)
+ if(!has_eyes && !H.GetComponent(/datum/component/dullahan))
standing += mutable_appearance('icons/mob/eyes.dmi', "eyes_missing", -BODY_LAYER)
+ message_admins("EYES MISSING APPLIED 2")
else
var/left_state = DEFAULT_LEFT_EYE_STATE
var/right_state = DEFAULT_RIGHT_EYE_STATE
@@ -832,6 +837,8 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
right_state = eye_type + "_right_eye"
var/mutable_appearance/left_eye = mutable_appearance('icons/mob/eyes.dmi', left_state, -BODY_LAYER)
var/mutable_appearance/right_eye = mutable_appearance('icons/mob/eyes.dmi', right_state, -BODY_LAYER)
+ left_eye.category = "HEAD"
+ right_eye.category = "HEAD"
if((EYECOLOR in species_traits) && has_eyes)
left_eye.color = "#" + H.left_eye_color
right_eye.color = "#" + H.right_eye_color
@@ -1009,6 +1016,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
for(var/bodypart in relevant_layers[layer])
var/datum/sprite_accessory/S = bodypart
var/mutable_appearance/accessory_overlay = mutable_appearance(S.icon, layer = -layernum)
+ accessory_overlay.category = S.mutable_category
bodypart = S.mutant_part_string || dna_feature_as_text_string[S]
if(S.gender_specific)
@@ -1113,6 +1121,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(S.extra) //apply the extra overlay, if there is one
var/mutable_appearance/extra_accessory_overlay = mutable_appearance(S.icon, layer = -layernum)
+ extra_accessory_overlay.category = S.mutable_category
if(S.gender_specific)
extra_accessory_overlay.icon_state = "[g]_[bodypart]_extra_[S.icon_state]_[layertext]"
else
@@ -1159,6 +1168,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(S.extra2) //apply the extra overlay, if there is one
var/mutable_appearance/extra2_accessory_overlay = mutable_appearance(S.icon, layer = -layernum)
+ extra2_accessory_overlay.category = S.mutable_category
if(S.gender_specific)
extra2_accessory_overlay.icon_state = "[g]_[bodypart]_extra2_[S.icon_state]_[layertext]"
else
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index a0620c8990..f225afdd79 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -791,8 +791,9 @@ use_mob_overlay_icon: if FALSE, it will always use the default_icon_file even if
// eyes
if(!(NOEYES in dna.species.species_traits))
var/has_eyes = getorganslot(ORGAN_SLOT_EYES)
- if(!has_eyes)
+ if(!has_eyes && !GetComponent(/datum/component/dullahan))
add_overlay(mutable_appearance('icons/mob/eyes.dmi', "eyes_missing", -BODY_LAYER))
+ message_admins("EYES MISSING APPLIED")
else
var/left_state = DEFAULT_LEFT_EYE_STATE
var/right_state = DEFAULT_RIGHT_EYE_STATE
@@ -803,6 +804,8 @@ use_mob_overlay_icon: if FALSE, it will always use the default_icon_file even if
right_state = eye_type + "_right_eye"
var/mutable_appearance/left_eye = mutable_appearance('icons/mob/eyes.dmi', left_state, -BODY_LAYER)
var/mutable_appearance/right_eye = mutable_appearance('icons/mob/eyes.dmi', right_state, -BODY_LAYER)
+ left_eye.category = "HEAD"
+ right_eye.category = "HEAD"
if((EYECOLOR in dna.species.species_traits) && has_eyes)
left_eye.color = "#" + left_eye_color
right_eye.color = "#" + right_eye_color
diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm
index 8c283f5ebc..7278dc6888 100644
--- a/code/modules/surgery/bodyparts/head.dm
+++ b/code/modules/surgery/bodyparts/head.dm
@@ -134,7 +134,7 @@
I.pixel_y = px_y
add_overlay(standing)
-/obj/item/bodypart/head/get_limb_icon(dropped)
+/obj/item/bodypart/head/get_limb_icon(dropped, ignore_brain = FALSE, ignore_eyes = FALSE)
if(custom_head)
return
cut_overlays()
@@ -152,7 +152,7 @@
. += facial_overlay
//Applies the debrained overlay if there is no brain
- if(!brain)
+ if(!brain && !ignore_brain)
var/image/debrain_overlay = image(layer = -HAIR_LAYER, dir = SOUTH)
if(animal_origin == ALIEN_BODYPART)
debrain_overlay.icon = 'icons/mob/animal_parts.dmi'
@@ -175,12 +175,13 @@
// lipstick
if(lip_style)
- var/image/lips_overlay = image('icons/mob/lips.dmi', "lips_[lip_style]", -BODY_LAYER, SOUTH)
+ var/mutable_appearance/lips_overlay = mutable_appearance('icons/mob/lips.dmi', "lips_[lip_style]", -BODY_LAYER, SOUTH)
+ lips_overlay.category = "HEAD"
lips_overlay.color = lip_color
. += lips_overlay
// eyes
- if(eyes)
+ if(eyes || ignore_eyes)
var/left_state = DEFAULT_LEFT_EYE_STATE
var/right_state = DEFAULT_RIGHT_EYE_STATE
if(owner && owner.dna.species)
@@ -189,17 +190,20 @@
left_state = eye_type + "_left_eye"
right_state = eye_type + "_right_eye"
if(left_state != DEFAULT_NO_EYE_STATE)
- var/image/left_eye = image('icons/mob/hair.dmi', left_state, -BODY_LAYER, SOUTH)
+ var/mutable_appearance/left_eye = mutable_appearance('icons/mob/hair.dmi', left_state, -BODY_LAYER, SOUTH)
+ left_eye.category = "HEAD"
if(eyes.left_eye_color)
left_eye.color = "#" + eyes.left_eye_color
. += left_eye
if(right_state != DEFAULT_NO_EYE_STATE)
- var/image/right_eye = image('icons/mob/hair.dmi', right_state, -BODY_LAYER, SOUTH)
+ var/mutable_appearance/right_eye = mutable_appearance('icons/mob/hair.dmi', right_state, -BODY_LAYER, SOUTH)
+ right_eye.category = "HEAD"
if(eyes.right_eye_color)
right_eye.color = "#" + eyes.right_eye_color
. += right_eye
else
var/eyes_overlay = image('icons/mob/hair.dmi', "eyes_missing", -BODY_LAYER, SOUTH)
+ message_admins("EYES MISSING ALSO IGNORE EYES IS [ignore_eyes]")
. += eyes_overlay
/obj/item/bodypart/head/monkey
diff --git a/tgstation.dme b/tgstation.dme
index 4fb3920c45..9fd6194e67 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -521,6 +521,7 @@
#include "code\datums\components\combat_mode.dm"
#include "code\datums\components\construction.dm"
#include "code\datums\components\dejavu.dm"
+#include "code\datums\components\dullahan.dm"
#include "code\datums\components\earprotection.dm"
#include "code\datums\components\edible.dm"
#include "code\datums\components\edit_complainer.dm"