Files
Bubberstation/code/datums/components/toggle_attached_clothing.dm
7f4171fd4d Adds bodyshape arg to a lot of missing spots, generalizes 'wear_digi_version', adds female gender shaping to digi sprites (#96633)
## About The Pull Request

Adds female gender shaping to digi species (such as lizards), for the
top half only.

<details><summary>Shown here</summary>

<img width="337" height="464" alt="dreamseeker_HhKjfrmJ1f"
src="https://github.com/user-attachments/assets/a5a57580-6798-40e4-925c-b3a63e0e540e"
/>

</details>

Also just adds a bunch of bodyshape args now that we are passing that to
the clothing rendering. This will help with anything where you want
unique digi handling for any specific items. Also fixes some missing
args and even improper args in some of these proc overrides.

## Why It's Good For The Game

Makes things more convenient for coders. Adds some more customization
options for feminine lizardfolk.

## Changelog

🆑
code: bodyshape is now accessible in build_worn_overlays() and similar
procs.
image: lizards now have support female gender shaping for their suits
/🆑

---------

Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
2026-06-30 20:02:53 +00:00

218 lines
8.1 KiB
Plaintext

/**
* Component which allows clothing to deploy a different kind of clothing onto you.
* The simplest example is hooded suits deploying hoods onto your head.
*/
/datum/component/toggle_attached_clothing
/// Instance of the item we're creating
var/obj/item/deployable
/// Action used to toggle deployment
var/datum/action/item_action/toggle_action
/// Typepath of what we're creating
var/deployable_type
/// Which slot this item equips into
var/equipped_slot
/// Name of toggle action
var/action_name = ""
/// If true, we delete our deployable on toggle rather than putting it in nullspace
var/destroy_on_removal
/// Current state of our deployable equipment
var/currently_deployed = FALSE
/// What should be added to the end of the parent icon state when equipment is deployed? Set to "" for no change
var/parent_icon_state_suffix = ""
/// Icon state for overlay to display over the parent item while deployable item is not deployed
var/down_overlay_state_suffix = ""
/// Overlay to display over the parent item while deployable item is not deployed
var/mutable_appearance/undeployed_overlay
/// Optional callback triggered before deploying, return TRUE to continue or FALSE to cancel
var/datum/callback/pre_creation_check
/// Optional callback triggered when we create our deployable equipment
var/datum/callback/on_created
/// Optional callback triggered when we have deployed our equipment
var/datum/callback/on_deployed
/// Optional callback triggered before we hide our equipment, before as we may delete it afterwards
var/datum/callback/on_removed
/datum/component/toggle_attached_clothing/Initialize(
deployable_type,
equipped_slot,
action_name = "Toggle",
destroy_on_removal = FALSE,
parent_icon_state_suffix = "",
down_overlay_state_suffix = "",
datum/callback/pre_creation_check,
datum/callback/on_created,
datum/callback/on_deployed,
datum/callback/on_removed,
)
. = ..()
if (!isitem(parent))
return COMPONENT_INCOMPATIBLE
if (!deployable_type || !equipped_slot)
return COMPONENT_REDUNDANT
src.deployable_type = deployable_type
src.equipped_slot = equipped_slot
src.destroy_on_removal = destroy_on_removal
src.parent_icon_state_suffix = parent_icon_state_suffix
src.down_overlay_state_suffix = down_overlay_state_suffix
src.pre_creation_check = pre_creation_check
src.on_created = on_created
src.on_deployed = on_deployed
src.on_removed = on_removed
var/obj/item/clothing_parent = parent
toggle_action = new(parent)
toggle_action.name = action_name
clothing_parent.add_item_action(toggle_action)
RegisterSignal(parent, COMSIG_ITEM_UI_ACTION_CLICK, PROC_REF(on_toggle_pressed))
RegisterSignal(parent, COMSIG_ITEM_UI_ACTION_SLOT_CHECKED, PROC_REF(on_action_slot_checked))
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_parent_equipped))
RegisterSignal(parent, COMSIG_ITEM_POST_UNEQUIP, PROC_REF(remove_deployable))
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED_AS_OUTFIT, PROC_REF(on_parent_equipped_outfit))
if (down_overlay_state_suffix)
var/overlay_state = "[initial(clothing_parent.post_init_icon_state) || initial(clothing_parent.icon_state)][down_overlay_state_suffix]"
undeployed_overlay = mutable_appearance(initial(clothing_parent.worn_icon), overlay_state, -SUIT_LAYER)
RegisterSignal(parent, COMSIG_ITEM_GET_WORN_OVERLAYS, PROC_REF(on_checked_overlays))
clothing_parent.update_slot_icon()
if (!destroy_on_removal)
create_deployable()
/datum/component/toggle_attached_clothing/Destroy(force)
unequip_deployable()
QDEL_NULL(deployable)
QDEL_NULL(toggle_action)
undeployed_overlay = null
pre_creation_check = null
on_created = null
on_deployed = null
on_removed = null
return ..()
/// Toggle deployable when the UI button is clicked
/datum/component/toggle_attached_clothing/proc/on_toggle_pressed(obj/item/source, mob/user, datum/action)
SIGNAL_HANDLER
if (action != toggle_action)
return
toggle_deployable()
return COMPONENT_ACTION_HANDLED
/// Called when action attempts to check what slot the item is worn in
/datum/component/toggle_attached_clothing/proc/on_action_slot_checked(obj/item/clothing/source, mob/user, datum/action, slot)
SIGNAL_HANDLER
if (action != toggle_action)
return
if (!(source.slot_flags & slot))
return COMPONENT_ITEM_ACTION_SLOT_INVALID
/// Apply an overlay while the item is not deployed
/datum/component/toggle_attached_clothing/proc/on_checked_overlays(obj/item/source, list/overlays, mutable_appearance/standing, isinhands, icon_file, bodyshape)
SIGNAL_HANDLER
if (isinhands || currently_deployed)
return
overlays += undeployed_overlay
/// Deploys gear if it is hidden, hides it if it is deployed
/datum/component/toggle_attached_clothing/proc/toggle_deployable()
if (currently_deployed)
remove_deployable()
return
var/obj/item/parent_gear = parent
if (!ishuman(parent_gear.loc))
return
var/mob/living/carbon/human/wearer = parent_gear.loc
if (wearer.is_holding(parent_gear))
parent_gear.balloon_alert(wearer, "wear it first!")
return
if (wearer.get_item_by_slot(equipped_slot))
parent_gear.balloon_alert(wearer, "slot occupied!")
return
if (!deployable && !create_deployable())
return
if (!wearer.equip_to_slot_if_possible(deployable, slot = equipped_slot))
if(destroy_on_removal)
remove_deployable()
return
currently_deployed = TRUE
on_deployed?.Invoke(deployable)
if (parent_icon_state_suffix)
parent_gear.icon_state = "[initial(parent_gear.post_init_icon_state) || initial(parent_gear.icon_state)][parent_icon_state_suffix]"
parent_gear.worn_icon_state = parent_gear.icon_state
parent_gear.update_slot_icon()
wearer.update_mob_action_buttons()
/// Undeploy gear if it moves slots somehow
/datum/component/toggle_attached_clothing/proc/on_parent_equipped(obj/item/clothing/source, mob/equipper, slot)
SIGNAL_HANDLER
if (slot & equipped_slot)
return
remove_deployable()
/// Display deployed if worn in an outfit
/datum/component/toggle_attached_clothing/proc/on_parent_equipped_outfit(obj/item/clothing/source, mob/equipper, visuals_only, slot)
SIGNAL_HANDLER
create_deployable()
toggle_deployable()
/// Create our gear, returns true if we actually made anything
/datum/component/toggle_attached_clothing/proc/create_deployable()
if (deployable)
return FALSE
if (pre_creation_check && !pre_creation_check.Invoke())
return FALSE
deployable = new deployable_type(parent)
if (!istype(deployable))
stack_trace("Tried to create non-clothing item from toggled clothing.")
RegisterSignal(deployable, COMSIG_ITEM_DROPPED, PROC_REF(on_deployed_dropped))
RegisterSignal(deployable, COMSIG_ITEM_EQUIPPED, PROC_REF(on_deployed_equipped))
RegisterSignal(deployable, COMSIG_QDELETING, PROC_REF(on_deployed_destroyed))
on_created?.Invoke(deployable)
return TRUE
/// Undeploy gear if you drop it
/datum/component/toggle_attached_clothing/proc/on_deployed_dropped()
SIGNAL_HANDLER
remove_deployable()
/// Undeploy gear if it moves slots somehow
/datum/component/toggle_attached_clothing/proc/on_deployed_equipped(obj/item/clothing/source, mob/equipper, slot)
SIGNAL_HANDLER
if (source.slot_flags & slot)
return
remove_deployable()
/// Undeploy gear if it is deleted
/datum/component/toggle_attached_clothing/proc/on_deployed_destroyed()
SIGNAL_HANDLER
remove_deployable()
deployable = null
/// Removes our deployed equipment from the wearer
/datum/component/toggle_attached_clothing/proc/remove_deployable()
SIGNAL_HANDLER
unequip_deployable()
if (!currently_deployed)
return
currently_deployed = FALSE
on_removed?.Invoke(deployable)
var/obj/item/parent_gear = parent
if(destroy_on_removal)
QDEL_NULL(deployable)
if(parent_icon_state_suffix)
parent_gear.icon_state = "[initial(parent_gear.post_init_icon_state) || initial(parent_gear.icon_state)]"
parent_gear.worn_icon_state = parent_gear.icon_state
parent_gear.update_slot_icon()
parent_gear.update_item_action_buttons()
/// Removes an equipped deployable atom upon its retraction or destruction
/datum/component/toggle_attached_clothing/proc/unequip_deployable()
if (!deployable)
return
if (!ishuman(deployable.loc))
deployable.forceMove(parent)
return
var/mob/living/carbon/human/wearer = deployable.loc
wearer.transferItemToLoc(deployable, parent, force = TRUE, silent = TRUE)