From 8c45d65c4a423ff6ed1fcdcbbde54990d600f2c9 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Sat, 23 May 2026 12:28:07 -0500 Subject: [PATCH] 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 :cl: Melbert refactor: Refactored how digitigrade and snouts render, report any oddities with that /:cl: --- .../signals/signals_mob/signals_mob_carbon.dm | 7 + code/__DEFINES/mobs.dm | 2 + code/datums/components/butchering.dm | 1 + code/datums/components/digitigrade_linb.dm | 142 ++++++++++++++++++ .../mob/living/carbon/carbon_update_icons.dm | 10 +- .../mob/living/carbon/human/_species.dm | 2 +- .../living/carbon/human/human_update_icons.dm | 81 +++------- code/modules/mob/living/carbon/inventory.dm | 3 + code/modules/surgery/bodyparts/_bodyparts.dm | 18 +++ code/modules/surgery/bodyparts/helpers.dm | 13 +- .../species_parts/lizard_bodyparts.dm | 36 +---- .../bodyparts/species_parts/misc_bodyparts.dm | 8 - tgstation.dme | 1 + 13 files changed, 207 insertions(+), 117 deletions(-) create mode 100644 code/datums/components/digitigrade_linb.dm diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index 62f59572f57..a8af2cc5c66 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -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" diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 67a1dc8b747..0f4a28a1f94 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -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. diff --git a/code/datums/components/butchering.dm b/code/datums/components/butchering.dm index bdc41329a74..0ccaa013943 100644 --- a/code/datums/components/butchering.dm +++ b/code/datums/components/butchering.dm @@ -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) diff --git a/code/datums/components/digitigrade_linb.dm b/code/datums/components/digitigrade_linb.dm new file mode 100644 index 00000000000..6b7776474b5 --- /dev/null +++ b/code/datums/components/digitigrade_linb.dm @@ -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() diff --git a/code/modules/mob/living/carbon/carbon_update_icons.dm b/code/modules/mob/living/carbon/carbon_update_icons.dm index 8c011445492..07f8d0e89ed 100644 --- a/code/modules/mob/living/carbon/carbon_update_icons.dm +++ b/code/modules/mob/living/carbon/carbon_update_icons.dm @@ -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) diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 72f9968d250..996a9e3ca84 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -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!")) diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm index e11b01fb53b..dce12788dec 100644 --- a/code/modules/mob/living/carbon/human/human_update_icons.dm +++ b/code/modules/mob/living/carbon/human/human_update_icons.dm @@ -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. diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm index 049b0ccb42a..1f761d9dfd2 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -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) diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 4d11d2b0f93..dad783c27e9 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -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) diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm index 258162453a7..c3e8319120d 100644 --- a/code/modules/surgery/bodyparts/helpers.dm +++ b/code/modules/surgery/bodyparts/helpers.dm @@ -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 diff --git a/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm index 45889011818..00858221c05 100644 --- a/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm @@ -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)) diff --git a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm index 41037f538c4..888e9e790a8 100644 --- a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm @@ -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 diff --git a/tgstation.dme b/tgstation.dme index 444b015fbed..802f7ba7817 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -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"