Minor accessory rendering refactor (#96915)

## About The Pull Request

Accessories were using a weird appearance cache whose dubious
performance gains are not worth the pain, I've removed it in favor of
more sane appearance getters on accessories. Moving from a cache to an
overlay list also fixes height offsets.
Also fixed a few cases of unnecessary clothing updates which probably
negated the "gain" from said cache.
Closes #96908
Closes #96910

## Changelog
🆑
refactor: Refactored accessory rendering, fixing missing overlay updates
and making them affected by height
fix: More than one accessory can now render above worn suits
/🆑
This commit is contained in:
SmArtKar
2026-07-11 23:54:18 +02:00
committed by GitHub
parent 10f3a57e0d
commit d19a1f8e06
5 changed files with 33 additions and 58 deletions
+1 -1
View File
@@ -154,7 +154,7 @@
if(!istype(undershirt) || !LAZYLEN(undershirt.attached_accessories))
return
if(alternate_worn_layer)
. += undershirt.accessory_overlay
. += undershirt.get_accessory_overlays()
/obj/item/clothing/neck/tie/blue
name = "blue tie"
+4 -5
View File
@@ -30,12 +30,11 @@
if(!ishuman(wearer) || !wearer.w_uniform)
return
var/obj/item/clothing/under/undershirt = wearer.w_uniform
if(!istype(undershirt) || !LAZYLEN(undershirt.attached_accessories))
if(!istype(undershirt))
return
var/obj/item/clothing/accessory/displayed = undershirt.attached_accessories[1]
if(displayed.above_suit && undershirt.accessory_overlay)
. += undershirt.accessory_overlay
for(var/obj/item/clothing/accessory/accessory as anything in undershirt.attached_accessories)
if (accessory.above_suit)
. += accessory.generate_accessory_overlay(undershirt)
/obj/item/clothing/suit/separate_worn_overlays(mutable_appearance/standing, mutable_appearance/draw_target, isinhands = FALSE, icon_file, bodyshape = NONE)
. = ..()
+11 -24
View File
@@ -42,9 +42,6 @@
var/max_number_of_accessories = 5
/// A list of all accessories attached to us.
var/list/obj/item/clothing/accessory/attached_accessories
/// The overlay of the accessory we're demonstrating. Only index 1 will show up.
/// This is the overlay on the MOB, not the item itself.
var/mutable_appearance/accessory_overlay
/datum/armor/clothing_under
bio = 10
@@ -110,16 +107,13 @@
return changed ? CONTEXTUAL_SCREENTIP_SET : .
/obj/item/clothing/under/worn_overlays(mutable_appearance/standing, isinhands = FALSE, icon_file, bodyshape = NONE)
. = ..()
if(isinhands)
return
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform")
if(accessory_overlay)
. += accessory_overlay
. += get_accessory_overlays()
/obj/item/clothing/under/separate_worn_overlays(mutable_appearance/standing, mutable_appearance/draw_target, isinhands = FALSE, icon_file, bodyshape = NONE)
. = ..()
@@ -389,25 +383,15 @@
/// Removes the passed accesory from our accessories list
/obj/item/clothing/under/proc/remove_accessory(obj/item/clothing/accessory/removed, update = TRUE)
// Remove it from the list before detaching
LAZYREMOVE(attached_accessories, removed)
removed.detach(src, update)
removed.detach(src)
if(update)
update_accessory_overlay()
/// Handles creating, updating and cutting the worn overlay mutable appearance.
/obj/item/clothing/under/proc/update_accessory_overlay()
if(!length(attached_accessories))
accessory_overlay = null
else
accessory_overlay = mutable_appearance()
for(var/obj/item/clothing/accessory/accessory as anything in attached_accessories)
accessory_overlay.overlays += accessory.generate_accessory_overlay(src)
update_appearance() // so we update the suit inventory overlay too
/// Get a list of all accessory overlays
/obj/item/clothing/under/proc/get_accessory_overlays()
. = list()
for(var/obj/item/clothing/accessory/accessory as anything in attached_accessories)
. += accessory.generate_accessory_overlay(src)
/obj/item/clothing/under/Exited(atom/movable/gone, direction)
. = ..()
@@ -420,7 +404,10 @@
for(var/obj/item/clothing/accessory/worn_accessory as anything in attached_accessories)
remove_accessory(worn_accessory, update = FALSE)
worn_accessory.forceMove(drop_to)
update_accessory_overlay()
if (ishuman(loc))
var/mob/living/carbon/human/wearer = loc
wearer.update_clothing(slot_flags)
/obj/item/clothing/under/atom_destruction(damage_flag)
dump_attachments()
@@ -60,18 +60,12 @@
// If accessory is being worn, make sure it updates on the player
/obj/item/clothing/accessory/update_greyscale()
. = ..()
var/obj/item/clothing/under/attached_to = loc
if(!istype(attached_to))
return
var/mob/living/carbon/human/wearer = attached_to.loc
if(!istype(wearer))
return
attached_to.update_accessory_overlay()
if(istype(wearer) && wearer.get_item_by_slot(ITEM_SLOT_ICLOTHING) == attached_to)
wearer.update_clothing(attached_to.slot_flags)
/**
* Try to attach this accessory to the passed clothing article.
@@ -110,14 +104,10 @@
LAZYADD(attached_to.attached_accessories, src)
forceMove(attached_to)
if(!attached_to.accessory_overlay)
attached_to.accessory_overlay = mutable_appearance()
attached_to.accessory_overlay.overlays += generate_accessory_overlay(attached_to) //uniform appearance will be updated by the caller
// Do on-equip effects if we're already equipped
var/mob/worn_on = attached_to.loc
if(istype(worn_on))
on_uniform_equipped(attached_to, worn_on, worn_on.get_slot_by_item(attached_to))
on_uniform_equipped(attached_to, worn_on, worn_on.get_slot_by_item(attached_to), update = TRUE)
SEND_SIGNAL(src, COMSIG_ACCESSORY_ATTACHED, attached_to)
SEND_SIGNAL(attached_to, COMSIG_CLOTHING_ACCESSORY_ATTACHED, src)
@@ -135,7 +125,7 @@
*
* We may have exited the clothing's loc at this point
*/
/obj/item/clothing/accessory/proc/detach(obj/item/clothing/under/detach_from)
/obj/item/clothing/accessory/proc/detach(obj/item/clothing/under/detach_from, update = TRUE)
SHOULD_CALL_PARENT(TRUE)
if(detach_from.atom_storage?.real_location == src)
@@ -147,7 +137,7 @@
UnregisterSignal(detach_from, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED, COMSIG_CLOTHING_UNDER_ADJUSTED, COMSIG_ATOM_UPDATE_OVERLAYS))
var/mob/dropped_from = detach_from.loc
if(istype(dropped_from))
on_uniform_dropped(detach_from, dropped_from)
on_uniform_dropped(detach_from, dropped_from, update = update)
SEND_SIGNAL(src, COMSIG_ACCESSORY_DETACHED, detach_from)
SEND_SIGNAL(detach_from, COMSIG_CLOTHING_ACCESSORY_DETACHED, src)
@@ -166,31 +156,32 @@
return TRUE
/// Signal proc for [COMSIG_ITEM_EQUIPPED] on the uniform we're pinned to
/obj/item/clothing/accessory/proc/on_uniform_equipped(obj/item/clothing/under/source, mob/living/user, slot)
/obj/item/clothing/accessory/proc/on_uniform_equipped(obj/item/clothing/under/source, mob/living/user, slot, update = FALSE)
SIGNAL_HANDLER
if(slot & source.slot_flags)
accessory_equipped(source, user)
if(!(slot & source.slot_flags))
return
accessory_equipped(source, user)
if (update) // Don't update_clothing twice if we were already attached to our holder
user.update_clothing(source.slot_flags)
/// Signal proc for [COMSIG_ITEM_DROPPED] on the uniform we're pinned to
/obj/item/clothing/accessory/proc/on_uniform_dropped(obj/item/clothing/under/source, mob/living/user)
/obj/item/clothing/accessory/proc/on_uniform_dropped(obj/item/clothing/under/source, mob/living/user, update = FALSE)
SIGNAL_HANDLER
accessory_dropped(source, user)
user.update_clothing(ITEM_SLOT_ICLOTHING|ITEM_SLOT_OCLOTHING|ITEM_SLOT_NECK)
if (update)
user.update_clothing(source.slot_flags)
/// Called when the uniform this accessory is pinned to is equipped in a valid slot
/obj/item/clothing/accessory/proc/accessory_equipped(obj/item/clothing/under/clothes, mob/living/user)
equipped(user, user.get_slot_by_item(clothes)) // so we get any actions, item_flags get set, etc
for(var/trait in clothing_traits) // Accessory don't have slot flags by def, but they still apply clothing traits when the suit is equipped in the right slot.
ADD_CLOTHING_TRAIT(user, trait)
user.update_clothing(ITEM_SLOT_OCLOTHING|ITEM_SLOT_NECK)
return
/// Called when the uniform this accessory is pinned to is dropped
/obj/item/clothing/accessory/proc/accessory_dropped(obj/item/clothing/under/clothes, mob/living/user)
dropped(user) //This handles removing clothing traits from the user by default everytime.
return
/// Signal proc for [COMSIG_CLOTHING_UNDER_ADJUSTED] on the uniform we're pinned to
/// Checks if we can no longer be attached to the uniform, and if so, drops us
@@ -206,8 +197,7 @@
/// Signal proc for [COMSIG_ATOM_UPDATE_OVERLAYS] on the uniform we're pinned to to add our overlays to the inventory icon
/obj/item/clothing/accessory/proc/on_uniform_update(obj/item/source, list/overlays)
SIGNAL_HANDLER
overlays |= src
overlays += appearance
/obj/item/clothing/accessory/attack_self_secondary(mob/user)
. = ..()
+2 -3
View File
@@ -287,9 +287,8 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories())
if(istype(equipped_item, /obj/item/clothing/accessory))
// Snowflake handing for accessories, because we need to update the thing it's attached to instead
if(isclothing(equipped_item.loc))
var/obj/item/clothing/under/attached_to = equipped_item.loc
attached_to.update_accessory_overlay()
update_flag |= (ITEM_SLOT_OCLOTHING|ITEM_SLOT_ICLOTHING)
var/obj/item/clothing/attached_to = equipped_item.loc
update_flag |= attached_to.slot_flags
else
update_flag |= equipped_item.slot_flags
break