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
@@ -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