State based digi and snout bodyshape (#96030)

## About The Pull Request

Digi and snout bodyshape are added and removed if digi or snout is
hidden
There now also exists a digitigrade bodytype which is not removed if the
bodyshape his

Bodyshape is solely used for determining how the body is shaped for
rendering
Bodytype is used for checking stuff like "can digis wear shoes" 

This lets us clean up rendering code a bit, allowing us to pass around
bodyshape rather than needing to check our wearer for digi and whatnot

Digi itself is now a component for legs that tracks - on item equip or
unequip - if said items squish the legs.

## Why It's Good For The Game

Right now we do some super jank stuff like updating body every time we
update our jumpsuit which is obviously a bit wasteful
This should make things easier to manage and work with (and maybe more
performant?)

## Changelog
🆑 Melbert
refactor: Refactored how digitigrade and snouts render, report any
oddities with that
/🆑
This commit is contained in:
MrMelbert
2026-05-23 19:28:07 +02:00
committed by GitHub
parent 64e8af2d7e
commit 8c45d65c4a
13 changed files with 207 additions and 117 deletions
@@ -60,6 +60,11 @@
/// Called from bodypart being removed /obj/item/bodypart/proc/drop_limb(mob/living/carbon/old_owner, special, dismembered)
#define COMSIG_BODYPART_REMOVED "bodypart_removed"
/// From /obj/item/bodypart/proc/update_limb(): (dropping_limb, is_creating)
#define COMSIG_BODYPART_UPDATED "bodypart_updated"
/// From /datum/component/butchering/create_replacement_limb(): (replacement limb)
#define COMSIG_BODYPART_BUTCHERED "bodypart_butchered"
/// Sent to a limb when something *attempts* to change its surgery state (old_state, new_state, changed_states)
#define COMSIG_BODYPART_UPDATING_SURGERY_STATE "bodypart_updating_surgery_state"
@@ -212,3 +217,5 @@
#define COMSIG_START_DREAMING "start_dreaming"
/// A mob has finished dreaming: (datum/dream/finished_dream)
#define COMSIG_END_DREAMING "end_dreaming"
/// From /mob/living/carbon/item_coverage_changed: (added_slots, removed_slots)
#define COMSIG_CARBON_ITEM_COVERAGE_CHANGED "carbon_item_coverage_changed"
+2
View File
@@ -172,6 +172,8 @@
#define BODYTYPE_SHADOW (1<<7)
//This limb is a ghost limb and can phase through walls.
#define BODYTYPE_GHOST (1<<8)
/// Analagous to BODYSHAPE_DIGITIGRADE, though this one is not removed if the mob's shape changed
#define BODYTYPE_DIGITIGRADE (1<<9)
// Bodyshape defines for how things can be worn, i.e., what "shape" the mob sprite is
///The limb fits the human mold. This is not meant to be literal, if the sprite "fits" on a human, it is "humanoid", regardless of origin.
+1
View File
@@ -318,6 +318,7 @@
wound.remove_wound()
wound.apply_wound(replacement, silent = TRUE)
SEND_SIGNAL(target, COMSIG_BODYPART_BUTCHERED, replacement)
return replacement
/datum/component/butchering/proc/start_butcher(obj/item/source, mob/living/target, mob/living/user)
+142
View File
@@ -0,0 +1,142 @@
/// Updates the limb id of a bodypart if the mob is wearing digitigrade squishing clothing
/datum/component/digitigrade_limb
/// Id when wearing digitigrade squishing clothing
var/squashed_id
/// Id when not wearing digitigrade squishing clothing
var/free_id
/// Lazylist of refs that are squishing this limb
VAR_PRIVATE/list/squashing_us
/datum/component/digitigrade_limb/Initialize(squashed_id, free_id)
if(!istype(parent, /obj/item/bodypart/leg))
return COMPONENT_INCOMPATIBLE
var/obj/item/bodypart/limb = parent
limb.bodytype |= BODYTYPE_DIGITIGRADE
src.squashed_id = squashed_id
src.free_id = free_id || initial(limb.limb_id)
RegisterSignal(parent, COMSIG_BODYPART_UPDATED, PROC_REF(update_limb_id_comsig))
RegisterSignal(parent, COMSIG_BODYPART_ATTACHED, PROC_REF(on_attach))
RegisterSignal(parent, COMSIG_BODYPART_REMOVED, PROC_REF(on_remove))
RegisterSignal(parent, COMSIG_BODYPART_BUTCHERED, PROC_REF(on_butchered))
if(ishuman(limb.owner))
on_attach(limb, limb.owner)
/datum/component/digitigrade_limb/Destroy()
UnregisterSignal(parent, COMSIG_BODYPART_UPDATED)
UnregisterSignal(parent, COMSIG_BODYPART_ATTACHED)
UnregisterSignal(parent, COMSIG_BODYPART_REMOVED)
UnregisterSignal(parent, COMSIG_BODYPART_BUTCHERED)
var/obj/item/bodypart/limb = parent
if(!QDELING(parent) && ishuman(limb.owner))
on_remove(limb, limb.owner)
limb.bodytype &= ~BODYTYPE_DIGITIGRADE
return ..()
/datum/component/digitigrade_limb/proc/on_attach(obj/item/bodypart/limb, mob/living/carbon/new_limb_owner)
SIGNAL_HANDLER
RegisterSignal(new_limb_owner, COMSIG_MOB_EQUIPPED_ITEM, PROC_REF(equipped_item))
RegisterSignal(new_limb_owner, COMSIG_MOB_DROPPED_ITEM, PROC_REF(unequipped_item))
RegisterSignal(new_limb_owner, COMSIG_CARBON_ITEM_COVERAGE_CHANGED, PROC_REF(coverage_changed))
for(var/obj/item/equipped as anything in new_limb_owner.get_equipped_items())
if(equipped_item(new_limb_owner, equipped, new_limb_owner.get_slot_by_item(equipped)))
LAZYOR(squashing_us, REF(equipped))
/datum/component/digitigrade_limb/proc/on_remove(obj/item/bodypart/limb, mob/living/carbon/old_limb_owner)
SIGNAL_HANDLER
UnregisterSignal(old_limb_owner, COMSIG_MOB_EQUIPPED_ITEM)
UnregisterSignal(old_limb_owner, COMSIG_MOB_DROPPED_ITEM)
UnregisterSignal(old_limb_owner, COMSIG_CARBON_ITEM_COVERAGE_CHANGED)
LAZYNULL(squashing_us)
update_limb_id()
/datum/component/digitigrade_limb/proc/equipped_item(mob/living/carbon/equipper, obj/item/equipped_item, slot)
SIGNAL_HANDLER
if((slot & equipped_item.slot_flags) && item_squishes_limb(equipped_item, equipper))
LAZYOR(squashing_us, REF(equipped_item))
update_limb_id()
/datum/component/digitigrade_limb/proc/unequipped_item(mob/living/carbon/equipper, obj/item/equipped_item)
SIGNAL_HANDLER
LAZYREMOVE(squashing_us, REF(equipped_item))
update_limb_id()
// Snowflake case for updating whether our jumpsuit should squish us
/datum/component/digitigrade_limb/proc/coverage_changed(mob/living/carbon/equipper, added_slots, removed_slots)
SIGNAL_HANDLER
if(!((added_slots|removed_slots) & HIDEJUMPSUIT))
return
var/obj/item/clothing/uniform = equipper.get_item_by_slot(ITEM_SLOT_ICLOTHING)
if(isnull(uniform))
return
var/uniform_ref = REF(uniform)
if(uniform_ref in squashing_us)
if(added_slots & HIDEJUMPSUIT)
LAZYREMOVE(squashing_us, uniform_ref)
update_limb_id()
else
if((removed_slots & HIDEJUMPSUIT) && item_squishes_limb(uniform, equipper))
LAZYOR(squashing_us, uniform_ref)
update_limb_id()
/datum/component/digitigrade_limb/proc/item_squishes_limb(obj/item/equipped_item, mob/living/carbon/equipper)
if(!(equipped_item.body_parts_covered & (LEGS|FEET)))
return FALSE
switch(equipper.get_slot_by_item(equipped_item))
if(ITEM_SLOT_FEET, ITEM_SLOT_OCLOTHING)
return !(equipped_item.supports_variations_flags & DIGITIGRADE_VARIATIONS)
if(ITEM_SLOT_ICLOTHING) // If the jumpsuit is obscured, it shouldn't contribute to squishing
return !(equipped_item.supports_variations_flags & DIGITIGRADE_VARIATIONS) && !(equipper.obscured_slots & HIDEJUMPSUIT)
return FALSE
/// This is just ran on update_limb() to ensure we always have the correct ID
/datum/component/digitigrade_limb/proc/update_limb_id_comsig()
SIGNAL_HANDLER
update_limb_id(sprite_update = FALSE)
/// Digitigrade limbs that are butchered add the component to the replacement limb
/datum/component/digitigrade_limb/proc/on_butchered(datum/source, obj/item/bodypart/replacement)
SIGNAL_HANDLER
squashed_id = "[initial(replacement.limb_id)]_[BODYPART_ID_DIGITIGRADE]"
free_id = initial(replacement.limb_id)
replacement.TakeComponent(src)
/datum/component/digitigrade_limb/proc/update_limb_id(sprite_update = TRUE)
var/obj/item/bodypart/limb = parent
var/old_id = limb.limb_id
if(LAZYLEN(squashing_us))
limb.limb_id = squashed_id
limb.remove_bodyshape(BODYSHAPE_DIGITIGRADE)
else
limb.limb_id = free_id
limb.add_bodyshape(BODYSHAPE_DIGITIGRADE)
if(!sprite_update || old_id == limb.limb_id)
return
if(isnull(limb.owner))
limb.update_icon_dropped()
return
// Ensures any items that with a variation are updated
for(var/obj/item/thing as anything in limb.owner.get_equipped_items(INCLUDE_PROSTHETICS|INCLUDE_ABSTRACT))
if(thing.supports_variations_flags & DIGITIGRADE_VARIATIONS)
thing.update_slot_icon()
// Updates underwear and mob sprites
limb.owner.update_body()
@@ -270,7 +270,7 @@
if(IS_RIGHT_INDEX(get_held_index_of_item(I)))
icon_file = I.righthand_file
hands += I.build_worn_icon(default_layer = HANDS_LAYER, default_icon_file = icon_file, isinhands = TRUE)
hands += I.build_worn_icon(default_layer = HANDS_LAYER, default_icon_file = icon_file, isinhands = TRUE, bodyshape = bodyshape)
return hands
/mob/living/carbon/get_fire_overlay(stacks, on_fire)
@@ -344,7 +344,7 @@
if(wear_mask)
if(!(obscured_slots & HIDEMASK))
overlays_standing[FACEMASK_LAYER] = wear_mask.build_worn_icon(default_layer = FACEMASK_LAYER, default_icon_file = 'icons/mob/clothing/mask.dmi')
overlays_standing[FACEMASK_LAYER] = wear_mask.build_worn_icon(default_layer = FACEMASK_LAYER, default_icon_file = 'icons/mob/clothing/mask.dmi', bodyshape = bodyshape)
update_hud_wear_mask(wear_mask)
apply_overlay(FACEMASK_LAYER)
@@ -358,7 +358,7 @@
if(wear_neck)
if(!(obscured_slots & HIDENECK))
overlays_standing[NECK_LAYER] = wear_neck.build_worn_icon(default_layer = NECK_LAYER, default_icon_file = 'icons/mob/clothing/neck.dmi')
overlays_standing[NECK_LAYER] = wear_neck.build_worn_icon(default_layer = NECK_LAYER, default_icon_file = 'icons/mob/clothing/neck.dmi', bodyshape = bodyshape)
update_hud_neck(wear_neck)
apply_overlay(NECK_LAYER)
@@ -371,7 +371,7 @@
inv.update_appearance()
if(back)
overlays_standing[BACK_LAYER] = back.build_worn_icon(default_layer = BACK_LAYER, default_icon_file = 'icons/mob/clothing/back.dmi')
overlays_standing[BACK_LAYER] = back.build_worn_icon(default_layer = BACK_LAYER, default_icon_file = 'icons/mob/clothing/back.dmi', bodyshape = bodyshape)
update_hud_back(back)
apply_overlay(BACK_LAYER)
@@ -399,7 +399,7 @@
if(head)
if(!(obscured_slots & HIDEHEADGEAR))
overlays_standing[HEAD_LAYER] = head.build_worn_icon(default_layer = HEAD_LAYER, default_icon_file = 'icons/mob/clothing/head/default.dmi')
overlays_standing[HEAD_LAYER] = head.build_worn_icon(default_layer = HEAD_LAYER, default_icon_file = 'icons/mob/clothing/head/default.dmi', bodyshape = bodyshape)
update_hud_head(head)
apply_overlay(HEAD_LAYER)
@@ -593,7 +593,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
if(ITEM_SLOT_FEET)
if(H.num_legs < 2)
return FALSE
if((H.bodyshape & BODYSHAPE_DIGITIGRADE) && !(I.item_flags & IGNORE_DIGITIGRADE))
if((H.bodytype & BODYTYPE_DIGITIGRADE) && !(I.item_flags & IGNORE_DIGITIGRADE))
if(!(I.supports_variations_flags & DIGITIGRADE_VARIATIONS))
if(!disable_warning)
to_chat(H, span_warning("The footwear around here isn't compatible with your feet!"))
@@ -120,6 +120,7 @@ There are several things that need to be remembered:
female_uniform = woman ? uniform.female_sprite_flags : null,
override_state = target_overlay,
override_file = handled_by_bodyshape ? icon_file : null,
bodyshape = bodyshape,
)
var/obj/item/bodypart/chest/my_chest = get_bodypart(BODY_ZONE_CHEST)
@@ -127,7 +128,6 @@ There are several things that need to be remembered:
overlays_standing[UNIFORM_LAYER] = uniform_overlay
apply_overlay(UNIFORM_LAYER)
check_body_shape(BODYSHAPE_DIGITIGRADE, ITEM_SLOT_ICLOTHING)
/mob/living/carbon/human/update_worn_id()
remove_overlay(ID_LAYER)
@@ -147,7 +147,7 @@ There are several things that need to be remembered:
var/icon_file = 'icons/mob/clothing/id.dmi'
id_overlay = wear_id.build_worn_icon(default_layer = ID_LAYER, default_icon_file = icon_file)
id_overlay = wear_id.build_worn_icon(default_layer = ID_LAYER, default_icon_file = icon_file, bodyshape = bodyshape)
if(!id_overlay)
return
@@ -193,7 +193,7 @@ There are several things that need to be remembered:
var/icon_file = 'icons/mob/clothing/hands.dmi'
var/mutable_appearance/gloves_overlay = gloves.build_worn_icon(default_layer = GLOVES_LAYER, default_icon_file = icon_file)
var/mutable_appearance/gloves_overlay = gloves.build_worn_icon(default_layer = GLOVES_LAYER, default_icon_file = icon_file, bodyshape = bodyshape)
var/feature_y_offset = 0
//needs to be typed, hand_bodyparts can have nulls
@@ -240,7 +240,7 @@ There are several things that need to be remembered:
var/icon_file = 'icons/mob/clothing/eyes.dmi'
var/mutable_appearance/glasses_overlay = glasses.build_worn_icon(default_layer = GLASSES_LAYER, default_icon_file = icon_file)
var/mutable_appearance/glasses_overlay = glasses.build_worn_icon(default_layer = GLASSES_LAYER, default_icon_file = icon_file, bodyshape = bodyshape)
my_head.worn_glasses_offset?.apply_offset(glasses_overlay)
overlays_standing[GLASSES_LAYER] = glasses_overlay
apply_overlay(GLASSES_LAYER)
@@ -266,7 +266,7 @@ There are several things that need to be remembered:
var/icon_file = 'icons/mob/clothing/ears.dmi'
var/mutable_appearance/ears_overlay = ears.build_worn_icon(default_layer = EARS_LAYER, default_icon_file = icon_file)
var/mutable_appearance/ears_overlay = ears.build_worn_icon(default_layer = EARS_LAYER, default_icon_file = icon_file, bodyshape = bodyshape)
my_head.worn_ears_offset?.apply_offset(ears_overlay)
overlays_standing[EARS_LAYER] = ears_overlay
apply_overlay(EARS_LAYER)
@@ -287,7 +287,7 @@ There are several things that need to be remembered:
var/icon_file = 'icons/mob/clothing/neck.dmi'
var/mutable_appearance/neck_overlay = worn_item.build_worn_icon(default_layer = NECK_LAYER, default_icon_file = icon_file)
var/mutable_appearance/neck_overlay = worn_item.build_worn_icon(default_layer = NECK_LAYER, default_icon_file = icon_file, bodyshape = bodyshape)
var/obj/item/bodypart/chest/my_chest = get_bodypart(BODY_ZONE_CHEST)
my_chest?.worn_neck_offset?.apply_offset(neck_overlay)
overlays_standing[NECK_LAYER] = neck_overlay
@@ -313,7 +313,7 @@ There are several things that need to be remembered:
var/icon_file = DEFAULT_SHOES_FILE
var/mutable_appearance/shoes_overlay = shoes.build_worn_icon(default_layer = SHOES_LAYER, default_icon_file = icon_file)
var/mutable_appearance/shoes_overlay = shoes.build_worn_icon(default_layer = SHOES_LAYER, default_icon_file = icon_file, bodyshape = bodyshape)
if(!shoes_overlay)
return
@@ -330,7 +330,6 @@ There are several things that need to be remembered:
overlays_standing[SHOES_LAYER] = shoes_overlay
apply_overlay(SHOES_LAYER)
check_body_shape(BODYSHAPE_DIGITIGRADE, ITEM_SLOT_FEET)
/mob/living/carbon/human/update_suit_storage()
remove_overlay(SUIT_STORE_LAYER)
@@ -367,13 +366,12 @@ There are several things that need to be remembered:
var/icon_file = 'icons/mob/clothing/head/default.dmi'
var/mutable_appearance/head_overlay = head.build_worn_icon(default_layer = HEAD_LAYER, default_icon_file = icon_file)
var/mutable_appearance/head_overlay = head.build_worn_icon(default_layer = HEAD_LAYER, default_icon_file = icon_file, bodyshape = bodyshape)
var/obj/item/bodypart/head/my_head = get_bodypart(BODY_ZONE_HEAD)
my_head?.worn_head_offset?.apply_offset(head_overlay)
overlays_standing[HEAD_LAYER] = head_overlay
apply_overlay(HEAD_LAYER)
check_body_shape(BODYSHAPE_SNOUTED, ITEM_SLOT_HEAD)
/mob/living/carbon/human/update_worn_belt()
remove_overlay(BELT_LAYER)
@@ -391,7 +389,7 @@ There are several things that need to be remembered:
var/icon_file = 'icons/mob/clothing/belt.dmi'
var/mutable_appearance/belt_overlay = belt.build_worn_icon(default_layer = BELT_LAYER, default_icon_file = icon_file)
var/mutable_appearance/belt_overlay = belt.build_worn_icon(default_layer = BELT_LAYER, default_icon_file = icon_file, bodyshape = bodyshape)
var/obj/item/bodypart/chest/my_chest = get_bodypart(BODY_ZONE_CHEST)
my_chest?.worn_belt_offset?.apply_offset(belt_overlay)
overlays_standing[BELT_LAYER] = belt_overlay
@@ -414,13 +412,12 @@ There are several things that need to be remembered:
var/icon_file = DEFAULT_SUIT_FILE
var/mutable_appearance/suit_overlay = wear_suit.build_worn_icon(default_layer = SUIT_LAYER, default_icon_file = icon_file)
var/mutable_appearance/suit_overlay = wear_suit.build_worn_icon(default_layer = SUIT_LAYER, default_icon_file = icon_file, bodyshape = bodyshape)
var/obj/item/bodypart/chest/my_chest = get_bodypart(BODY_ZONE_CHEST)
my_chest?.worn_suit_offset?.apply_offset(suit_overlay)
overlays_standing[SUIT_LAYER] = suit_overlay
apply_overlay(SUIT_LAYER)
check_body_shape(BODYSHAPE_DIGITIGRADE, ITEM_SLOT_OCLOTHING)
/mob/living/carbon/human/update_pockets()
if(client && hud_used)
@@ -463,12 +460,11 @@ There are several things that need to be remembered:
var/icon_file = 'icons/mob/clothing/mask.dmi'
var/mutable_appearance/mask_overlay = wear_mask.build_worn_icon(default_layer = FACEMASK_LAYER, default_icon_file = icon_file)
var/mutable_appearance/mask_overlay = wear_mask.build_worn_icon(default_layer = FACEMASK_LAYER, default_icon_file = icon_file, bodyshape = bodyshape)
my_head.worn_mask_offset?.apply_offset(mask_overlay)
overlays_standing[FACEMASK_LAYER] = mask_overlay
apply_overlay(FACEMASK_LAYER)
check_body_shape(BODYSHAPE_SNOUTED, ITEM_SLOT_MASK)
/mob/living/carbon/human/update_worn_back()
remove_overlay(BACK_LAYER)
@@ -487,7 +483,7 @@ There are several things that need to be remembered:
var/icon_file = 'icons/mob/clothing/back.dmi'
back_overlay = back.build_worn_icon(default_layer = BACK_LAYER, default_icon_file = icon_file)
back_overlay = back.build_worn_icon(default_layer = BACK_LAYER, default_icon_file = icon_file, bodyshape = bodyshape)
if(!back_overlay)
return
@@ -520,7 +516,7 @@ There are several things that need to be remembered:
var/mutable_appearance/hand_overlay
var/icon_file = IS_RIGHT_INDEX(held_index) ? worn_item.righthand_file : worn_item.lefthand_file
hand_overlay = worn_item.build_worn_icon(default_layer = HANDS_LAYER, default_icon_file = icon_file, isinhands = TRUE)
hand_overlay = worn_item.build_worn_icon(default_layer = HANDS_LAYER, default_icon_file = icon_file, isinhands = TRUE, bodyshape = bodyshape)
var/obj/item/bodypart/arm/held_in_hand = hand_bodyparts[held_index]
held_in_hand?.held_hand_offset?.apply_offset(hand_overlay)
@@ -758,6 +754,7 @@ generate/load female uniform sprites matching all previously decided variables
female_uniform = NO_FEMALE_UNIFORM,
override_state = null,
override_file = null,
bodyshape = NONE,
)
//Find a valid icon_state from variables+arguments
@@ -767,9 +764,6 @@ generate/load female uniform sprites matching all previously decided variables
//Find a valid layer from variables+arguments
var/layer2use = alternate_worn_layer || default_layer
var/mob/living/carbon/wearer = loc
var/is_digi = istype(wearer) && (wearer.bodyshape & BODYSHAPE_DIGITIGRADE) && !wearer.is_digitigrade_squished()
var/mutable_appearance/draw_target // MA of the item itself, not the final result
var/icon/building_icon // used to construct an icon across multiple procs before converting it to MA
if(female_uniform)
@@ -779,7 +773,7 @@ generate/load female uniform sprites matching all previously decided variables
type = female_uniform,
greyscale_colors = greyscale_colors,
)
if(!isinhands && is_digi && (supports_variations_flags & CLOTHING_DIGITIGRADE_MASK))
if(!isinhands && (bodyshape & BODYSHAPE_DIGITIGRADE) && (supports_variations_flags & CLOTHING_DIGITIGRADE_MASK))
building_icon = wear_digi_version(
base_icon = building_icon || icon(file2use, t_state),
item = src,
@@ -879,22 +873,21 @@ generate/load female uniform sprites matching all previously decided variables
return .
// Underwear, Undershirts & Socks
var/active_bodyshapes = get_active_bodyshapes()
if(underwear)
var/datum/sprite_accessory/clothing/underwear/undie_accessory = SSaccessories.underwear_list[underwear]
var/mutable_appearance/underwear_overlay = undie_accessory?.make_appearance(underwear_color, physique, active_bodyshapes)
var/mutable_appearance/underwear_overlay = undie_accessory?.make_appearance(underwear_color, physique, bodyshape)
if(underwear_overlay)
. += underwear_overlay
if(undershirt)
var/datum/sprite_accessory/clothing/undershirt/shirt_accessory = SSaccessories.undershirt_list[undershirt]
var/mutable_appearance/shirt_overlay = shirt_accessory?.make_appearance(null, physique, active_bodyshapes)
var/mutable_appearance/shirt_overlay = shirt_accessory?.make_appearance(null, physique, bodyshape)
if(shirt_overlay)
. += shirt_overlay
if(socks && num_legs >= 2 && !(bodyshape & BODYSHAPE_DIGITIGRADE))
var/datum/sprite_accessory/clothing/socks/sock_accessory = SSaccessories.socks_list[socks]
var/mutable_appearance/socks_overlay = sock_accessory?.make_appearance(null, physique, active_bodyshapes)
var/mutable_appearance/socks_overlay = sock_accessory?.make_appearance(null, physique, bodyshape)
if(socks_overlay)
. += socks_overlay
@@ -970,44 +963,6 @@ generate/load female uniform sprites matching all previously decided variables
update_eyes()
update_hair()
/**
* Used to perform regular updates to the limbs of humans with special bodyshapes
*
* * check_shapes: The bodyshapes to check for.
* Any limbs or organs which share this shape, will be updated.
* * ignore_slots: The slots to ignore when updating the limbs.
* This is useful for things like digitigrade legs, where we can skip some slots that we're already updating.
*
* return an integer, the number of limbs updated
*/
/mob/living/carbon/human/proc/check_body_shape(check_shapes = BODYSHAPE_DIGITIGRADE|BODYSHAPE_SNOUTED, ignore_slots = NONE)
. = 0
if(!(bodyshape & check_shapes))
// optimization - none of our limbs or organs have the desired shape
return .
for(var/obj/item/bodypart/limb as anything in get_bodyparts())
var/checked_bodyshape = limb.bodyshape
// accounts for stuff like snouts
for(var/obj/item/organ/organ in limb)
checked_bodyshape |= organ.external_bodyshapes
// any limb needs to be updated, so stop here and do it
if(checked_bodyshape & check_shapes)
. = update_body_parts()
break
if(!.)
return
// hardcoding this here until bodypart updating is more sane
// we need to update clothing items that may have been affected by bodyshape updates
if(check_shapes & BODYSHAPE_DIGITIGRADE)
for(var/obj/item/thing as anything in get_equipped_items(INCLUDE_PROSTHETICS|INCLUDE_ABSTRACT))
if(thing.slot_flags & ignore_slots)
continue
if(thing.supports_variations_flags & DIGITIGRADE_VARIATIONS)
thing.update_slot_icon()
// Hooks into human apply overlay so that we can modify all overlays applied through standing overlays to our height system.
// Some of our overlays will be passed through a displacement filter to make our mob look taller or shorter.
// Some overlays can't be displaced as they're too close to the edge of the sprite or cross the middle point in a weird way.
@@ -260,7 +260,10 @@
* * removed_slots - slots that were removed from obscured_slots
*/
/mob/living/carbon/proc/item_coverage_changed(added_slots, removed_slots)
SEND_SIGNAL(src, COMSIG_CARBON_ITEM_COVERAGE_CHANGED, added_slots, removed_slots)
update_clothing(hidden_slots_to_inventory_slots(added_slots|removed_slots))
if((added_slots|removed_slots) & HIDESNOUT)
synchronize_bodyshapes()
if((added_slots|removed_slots) & (HIDEHAIR|HIDEFACIALHAIR))
update_hair()
if((added_slots|removed_slots) & HIDEEYES)
@@ -1146,6 +1146,8 @@
/obj/item/bodypart/proc/update_limb(dropping_limb = FALSE, is_creating = FALSE)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_BODYPART_UPDATED, dropping_limb, is_creating)
if(IS_ORGANIC_LIMB(src))
// Try to add a cached blood type data, we must do it in here because for some reason DNA gets initialized AFTER the mob's limbs are created.
// Should be fine as this gets called before all the important stuff happens
@@ -1783,6 +1785,22 @@
return
REMOVE_TRAIT(owner, old_trait, bodypart_trait_source)
/// Add a bodyshape to the bodypart, then synchronize with the owner if necessary
/obj/item/bodypart/proc/add_bodyshape(new_shape)
if(bodyshape & new_shape)
return
bodyshape |= new_shape
owner?.synchronize_bodyshapes()
/// Remove a bodyshape from the bodypart, then synchronize with the owner if necessary
/obj/item/bodypart/proc/remove_bodyshape(old_shape)
if(!(bodyshape & old_shape))
return
bodyshape &= ~old_shape
owner?.synchronize_bodyshapes()
/// Add one or multiple surgical states to the bodypart
/obj/item/bodypart/proc/add_surgical_state(new_states)
if(!new_states)
+3 -10
View File
@@ -284,17 +284,10 @@
all_limb_flags |= organ.external_bodyshapes
all_limb_flags |= limb.bodyshape
bodyshape = all_limb_flags
if(obscured_slots & HIDESNOUT)
all_limb_flags &= ~BODYSHAPE_SNOUTED
/// Get all bodyshapes but filter out bodyshapes that are currently being hidden
/mob/living/carbon/proc/get_active_bodyshapes()
var/active_shapes = bodyshape
// future todo: both of these are state based, maybe we can just remove relevant bodyshapes directly. would remove the need for this proc
if((active_shapes & BODYSHAPE_DIGITIGRADE) && is_digitigrade_squished())
active_shapes &= ~BODYSHAPE_DIGITIGRADE
if((active_shapes & BODYSHAPE_SNOUTED) && (obscured_slots & HIDESNOUT))
active_shapes &= ~BODYSHAPE_SNOUTED
return active_shapes
bodyshape = all_limb_flags
/proc/skintone2hex(skin_tone)
. = 0
@@ -51,50 +51,26 @@
icon_greyscale = 'icons/mob/human/species/lizard/bodyparts.dmi'
limb_id = SPECIES_LIZARD
/// Checks if this mob is wearing anything that does not have a valid sprite set for digitigrade legs
/// (In other words, is the mob's digitigrade body squished by its clothing?)
/mob/living/carbon/proc/is_digitigrade_squished()
return FALSE
/mob/living/carbon/human/is_digitigrade_squished()
var/obj/item/clothing/shoes/worn_shoes = shoes
var/obj/item/clothing/under/worn_suit = wear_suit
var/obj/item/clothing/under/worn_uniform = w_uniform
var/uniform_compatible = isnull(worn_uniform) \
|| (worn_uniform.supports_variations_flags & DIGITIGRADE_VARIATIONS) \
|| !(worn_uniform.body_parts_covered & LEGS) \
|| (obscured_slots & HIDEJUMPSUIT) // If suit hides our jumpsuit, it doesn't matter if it squishes
var/suit_compatible = isnull(worn_suit) \
|| (worn_suit.supports_variations_flags & DIGITIGRADE_VARIATIONS) \
|| !(worn_suit.body_parts_covered & LEGS)
var/shoes_compatible = isnull(worn_shoes) \
|| (worn_shoes.supports_variations_flags & DIGITIGRADE_VARIATIONS)
return !uniform_compatible || !suit_compatible || !shoes_compatible
/obj/item/bodypart/leg/left/digitigrade
icon_greyscale = 'icons/mob/human/species/lizard/bodyparts.dmi'
limb_id = BODYPART_ID_DIGITIGRADE
species_id = SPECIES_LIZARD
bodyshape = BODYSHAPE_HUMANOID | BODYSHAPE_DIGITIGRADE
bodyshape = BODYSHAPE_HUMANOID
footprint_sprite = FOOTPRINT_SPRITE_CLAWS
footstep_type = FOOTSTEP_MOB_CLAW
/obj/item/bodypart/leg/left/digitigrade/update_limb(dropping_limb = FALSE, is_creating = FALSE)
/obj/item/bodypart/leg/left/digitigrade/Initialize(mapload)
. = ..()
limb_id = owner?.is_digitigrade_squished() ? SPECIES_LIZARD : BODYPART_ID_DIGITIGRADE
AddComponent(/datum/component/digitigrade_limb, SPECIES_LIZARD, initial(limb_id))
/obj/item/bodypart/leg/right/digitigrade
icon_greyscale = 'icons/mob/human/species/lizard/bodyparts.dmi'
limb_id = BODYPART_ID_DIGITIGRADE
species_id = SPECIES_LIZARD
bodyshape = BODYSHAPE_HUMANOID | BODYSHAPE_DIGITIGRADE
bodyshape = BODYSHAPE_HUMANOID
footprint_sprite = FOOTPRINT_SPRITE_CLAWS
footstep_type = FOOTSTEP_MOB_CLAW
/obj/item/bodypart/leg/right/digitigrade/update_limb(dropping_limb = FALSE, is_creating = FALSE)
/obj/item/bodypart/leg/right/digitigrade/Initialize(mapload)
. = ..()
limb_id = owner?.is_digitigrade_squished() ? SPECIES_LIZARD : BODYPART_ID_DIGITIGRADE
AddComponent(/datum/component/digitigrade_limb, SPECIES_LIZARD, initial(limb_id))
@@ -481,10 +481,6 @@
. = ..()
AddElement(/datum/element/blood_limb_overlay)
/obj/item/bodypart/leg/left/skeleton/nonfunctional/update_limb(dropping_limb = FALSE, is_creating = FALSE)
. = ..()
limb_id = ((bodyshape & BODYSHAPE_DIGITIGRADE) && owner?.is_digitigrade_squished()) ? initial(limb_id) : "[initial(limb_id)]_[BODYPART_ID_DIGITIGRADE]"
/obj/item/bodypart/leg/right/skeleton/nonfunctional
limb_id = BODYPART_ID_BONE
disabling_threshold_percentage = 0
@@ -493,10 +489,6 @@
. = ..()
AddElement(/datum/element/blood_limb_overlay)
/obj/item/bodypart/leg/right/skeleton/nonfunctional/update_limb(dropping_limb = FALSE, is_creating = FALSE)
. = ..()
limb_id = ((bodyshape & BODYSHAPE_DIGITIGRADE) && owner?.is_digitigrade_squished()) ? initial(limb_id) : "[initial(limb_id)]_[BODYPART_ID_DIGITIGRADE]"
///MUSHROOM
/obj/item/bodypart/head/mushroom
limb_id = SPECIES_MUSHROOM
+1
View File
@@ -1187,6 +1187,7 @@
#include "code\datums\components\defaceable.dm"
#include "code\datums\components\dejavu.dm"
#include "code\datums\components\deployable.dm"
#include "code\datums\components\digitigrade_linb.dm"
#include "code\datums\components\direct_explosive_trap.dm"
#include "code\datums\components\earprotection.dm"
#include "code\datums\components\echolocation.dm"