Files
MrMelbert 8c45d65c4a 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
/🆑
2026-05-23 19:28:07 +02:00

143 lines
5.3 KiB
Plaintext

/// 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()