Files
Bubberstation/code/modules/mob/living/carbon/carbon_update_icons.dm
SmArtKar fc71aa3977 Cleans up duplicate calls in bodypart overlays, implements a texture priority system (#89783)
## About The Pull Request

Implemented a bodypart texture overlay priority system - ensures that
derez suicide always goes ontop of carp infusions, and voidwalker curse
goes ontop of both - currently the last one applied is the one that
sticks with you. Also cleaned up duplicate update_body calls, either
removing them or adding ``update = FALSE`` to overlay code.

## Why It's Good For The Game

Having whichever overlay was applied the latest be displayed is just too
inconsistent. And duplicate calls eat perf, bad.
2025-03-11 13:28:34 -05:00

638 lines
24 KiB
Plaintext

/mob/living/carbon/update_obscured_slots(obscured_flags)
..()
update_body()
/// Updates features and clothing attached to a specific limb with limb-specific offsets
/mob/living/carbon/proc/update_features(feature_key)
switch(feature_key)
if(OFFSET_UNIFORM)
update_worn_undersuit()
if(OFFSET_ID)
update_worn_id()
if(OFFSET_GLOVES)
update_worn_gloves()
if(OFFSET_GLASSES)
update_worn_glasses()
if(OFFSET_EARS)
update_worn_ears()
if(OFFSET_SHOES)
update_worn_shoes()
if(OFFSET_S_STORE)
update_suit_storage()
if(OFFSET_FACEMASK)
update_worn_mask()
if(OFFSET_HEAD)
update_worn_head()
if(OFFSET_FACE)
dna?.species?.handle_body(src) // updates eye icon
update_worn_mask()
if(OFFSET_BELT)
update_worn_belt()
if(OFFSET_BACK)
update_worn_back()
if(OFFSET_SUIT)
update_worn_oversuit()
if(OFFSET_NECK)
update_worn_neck()
if(OFFSET_HELD)
update_held_items()
/mob/living/carbon
var/list/overlays_standing[TOTAL_LAYERS]
/mob/living/carbon/proc/apply_overlay(cache_index)
if((. = overlays_standing[cache_index]))
add_overlay(.)
SEND_SIGNAL(src, COMSIG_CARBON_APPLY_OVERLAY, cache_index, .)
/mob/living/carbon/proc/remove_overlay(cache_index)
var/I = overlays_standing[cache_index]
if(I)
cut_overlay(I)
overlays_standing[cache_index] = null
SEND_SIGNAL(src, COMSIG_CARBON_REMOVE_OVERLAY, cache_index, I)
/mob/living/carbon/update_body(is_creating = FALSE)
dna?.species.handle_body(src)
update_body_parts(is_creating)
/mob/living/carbon/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
. = ..()
if(same_z_layer)
return
update_z_overlays(GET_TURF_PLANE_OFFSET(new_turf), TRUE)
/mob/living/carbon/proc/refresh_loop(iter_cnt, rebuild = FALSE)
for(var/i in 1 to iter_cnt)
update_z_overlays(1, rebuild)
sleep(0.3 SECONDS)
update_z_overlays(0, rebuild)
sleep(0.3 SECONDS)
#define NEXT_PARENT_COMMAND "next_parent"
/// Takes a list of mutable appearances
/// Returns a list in the form:
/// 1 - a list of all mutable appearances that would need to be updated to change planes in the event of a z layer change, alnongside the commands required
/// to properly track parents to update
/// 2 - a list of all parents that will require updating
/proc/build_planeed_apperance_queue(list/mutable_appearance/appearances)
var/list/queue
if(islist(appearances))
queue = appearances.Copy()
else
queue = list(appearances)
var/queue_index = 0
var/list/parent_queue = list()
// We are essentially going to unroll apperance overlays into a flattened list here, so we can filter out floating planes laster
// It will look like "overlay overlay overlay (change overlay parent), overlay overlay etc"
// We can use this list to dynamically update these non floating planes, later
while(queue_index < length(queue))
queue_index++
// If it's not a command, we assert that it's an appearance
var/mutable_appearance/appearance = queue[queue_index]
if(!appearance || appearance == NEXT_PARENT_COMMAND) // Who fucking adds nulls to their sublists god you people are the worst
continue
var/mutable_appearance/new_appearance = new /mutable_appearance()
new_appearance.appearance = appearance
// Now check its children
if(length(appearance.overlays))
queue += NEXT_PARENT_COMMAND
parent_queue += appearance
for(var/mutable_appearance/child_appearance as anything in appearance.overlays)
queue += child_appearance
// Now we have a flattened list of parents and their children
// Setup such that walking the list backwards will allow us to properly update overlays
// (keeping in mind that overlays only update if an apperance is removed and added, and this pattern applies in a nested fashion)
// If we found no results, return null
if(!length(queue))
return null
// ALRIGHT MOTHERFUCKER
// SO
// DID YOU KNOW THAT OVERLAY RENDERING BEHAVIOR DEPENDS PARTIALLY ON THE ORDER IN WHICH OVERLAYS ARE ADDED?
// WHAT WE'RE DOING HERE ENDS UP REVERSING THE OVERLAYS ADDITION ORDER (when it's walked back to front)
// SO GUESS WHAT I'VE GOTTA DO, I'VE GOTTA SWAP ALLLL THE MEMBERS OF THE SUBLISTS
// I HATE IT HERE
var/lower_parent = 0
var/upper_parent = 0
var/queue_size = length(queue)
while(lower_parent <= queue_size)
// Let's reorder our "lists" (spaces between parent changes)
// We've got a delta index, and we're gonna essentially use it to get "swap" positions from the top and bottom
// We only need to loop over half the deltas to swap all the entries, any more and it'd be redundant
// We floor so as to avoid over flipping, and ending up flipping "back" a delta
// etc etc
var/target = FLOOR((upper_parent - lower_parent) / 2, 1)
for(var/delta_index in 1 to target)
var/old_lower = queue[lower_parent + delta_index]
queue[lower_parent + delta_index] = queue[upper_parent - delta_index]
queue[upper_parent - delta_index] = old_lower
// lower bound moves to the old upper, upper bound finds a new home
// Note that the end of the list is a valid upper bound
lower_parent = upper_parent // our old upper bound is now our lower bound
while(upper_parent <= queue_size)
upper_parent += 1
if(length(queue) < upper_parent) // Parent found
break
if(queue[upper_parent] == NEXT_PARENT_COMMAND) // We found em lads
break
// One more thing to do
// It's much more convinient for the parent queue to be a list of indexes pointing at queue locations
// Rather then a list of copied appearances
// Let's turn what we have now into that yeah?
// This'll require a loop over both queues
// We're using an assoc list here rather then several find()s because I feel like that's more sane
var/list/apperance_to_position = list()
for(var/i in 1 to length(queue))
apperance_to_position[queue[i]] = i
var/list/parent_indexes = list()
for(var/mutable_appearance/parent as anything in parent_queue)
parent_indexes += apperance_to_position[parent]
// Alright. We should now have two queues, a command/appearances one, and a parents queue, which contain no fluff
// And when walked backwards allow for proper plane updating
var/list/return_pack = list(queue, parent_indexes)
return return_pack
// Rebuilding is a hack. We should really store a list of indexes into our existing overlay list or SOMETHING
// IDK. will work for now though, which is a lot better then not working at all
/mob/living/carbon/proc/update_z_overlays(new_offset, rebuild = FALSE)
// Null entries will be filtered here
for(var/i in 1 to length(overlays_standing))
var/list/cache_grouping = overlays_standing[i]
if(cache_grouping && !islist(cache_grouping))
cache_grouping = list(cache_grouping)
// Need this so we can have an index, could build index into the list if we need to tho, check
if(!length(cache_grouping))
continue
overlays_standing[i] = update_appearance_planes(cache_grouping, new_offset)
/atom/proc/update_appearance_planes(list/mutable_appearance/appearances, new_offset)
var/list/build_list = build_planeed_apperance_queue(appearances)
if(!length(build_list))
return appearances
// hand_back contains a new copy of the passed in list, with updated values
var/list/hand_back = list()
var/list/processing_queue = build_list[1]
var/list/parents_queue = build_list[2]
// Now that we have our queues, we're going to walk them forwards to remove, and backwards to add
// Note, we need to do this separately because you can only remove a mutable appearance when it
// Exactly matches the appearance it had when it was first "made static" (by being added to the overlays list)
var/parents_index = 0
for(var/item in processing_queue)
if(item == NEXT_PARENT_COMMAND)
parents_index++
continue
var/mutable_appearance/iter_apper = item
if(parents_index)
var/parent_src_index = parents_queue[parents_index]
var/mutable_appearance/parent = processing_queue[parent_src_index]
parent.overlays -= iter_apper.appearance
else // Otherwise, we're at the end of the list, and our parent is the mob
cut_overlay(iter_apper)
// Now the back to front stuff, to readd the updated appearances
var/queue_index = length(processing_queue)
parents_index = length(parents_queue)
while(queue_index >= 1)
var/item = processing_queue[queue_index]
if(item == NEXT_PARENT_COMMAND)
parents_index--
queue_index--
continue
var/mutable_appearance/new_iter = new /mutable_appearance()
new_iter.appearance = item
if(new_iter.plane != FLOAT_PLANE)
// Here, finally, is where we actually update the plane offsets
SET_PLANE_W_SCALAR(new_iter, PLANE_TO_TRUE(new_iter.plane), new_offset)
if(parents_index)
var/parent_src_index = parents_queue[parents_index]
var/mutable_appearance/parent = processing_queue[parent_src_index]
parent.overlays += new_iter.appearance
else
add_overlay(new_iter)
// chant a protective overlays.Copy to prevent appearance theft and overlay sticking
// I'm not joking without this overlays can corrupt and be replaced by other appearances
// the compiler might call it useless but I swear it works
// we conjure the spirits of the computer with our spells, we conjur- (Hey lemon make a damn issue report already)
var/list/does_nothing = new_iter.overlays.Copy()
pass(does_nothing)
hand_back += new_iter
queue_index--
return hand_back
#undef NEXT_PARENT_COMMAND
/mob/living/carbon/regenerate_icons()
if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM))
return
icon_render_keys = list() //Clear this bad larry out
update_held_items()
update_worn_handcuffs()
update_worn_legcuffs()
update_body()
update_appearance(UPDATE_OVERLAYS)
/mob/living/carbon/update_held_items()
. = ..()
remove_overlay(HANDS_LAYER)
if (handcuffed)
drop_all_held_items()
return
overlays_standing[HANDS_LAYER] = get_held_overlays()
apply_overlay(HANDS_LAYER)
/// Generate held item overlays
/mob/living/carbon/proc/get_held_overlays()
var/list/hands = list()
for(var/obj/item/I in held_items)
if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD)
I.screen_loc = ui_hand_position(get_held_index_of_item(I))
client.screen += I
if(length(observers))
for(var/mob/dead/observe as anything in observers)
if(observe.client && observe.client.eye == src)
observe.client.screen += I
else
observers -= observe
if(!observers.len)
observers = null
break
var/icon_file = I.lefthand_file
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)
return hands
/mob/living/carbon/get_fire_overlay(stacks, on_fire)
var/fire_icon = "[dna?.species.fire_overlay || "human"]_[stacks > MOB_BIG_FIRE_STACK_THRESHOLD ? "big_fire" : "small_fire"]"
if(!GLOB.fire_appearances[fire_icon])
GLOB.fire_appearances[fire_icon] = mutable_appearance(
'icons/mob/effects/onfire.dmi',
fire_icon,
-HIGHEST_LAYER,
appearance_flags = RESET_COLOR,
)
return GLOB.fire_appearances[fire_icon]
/mob/living/carbon/update_damage_overlays()
remove_overlay(DAMAGE_LAYER)
var/mutable_appearance/damage_overlay
for(var/obj/item/bodypart/iter_part as anything in bodyparts)
if(!iter_part.dmg_overlay_type)
continue
if(isnull(damage_overlay) && (iter_part.brutestate || iter_part.burnstate))
damage_overlay = mutable_appearance('icons/mob/effects/dam_mob.dmi', "blank", -DAMAGE_LAYER, appearance_flags = KEEP_TOGETHER)
damage_overlay.color = iter_part.damage_overlay_color
if(iter_part.brutestate)
damage_overlay.add_overlay("[iter_part.dmg_overlay_type]_[iter_part.body_zone]_[iter_part.brutestate]0") //we're adding icon_states of the base image as overlays
if(iter_part.burnstate)
damage_overlay.add_overlay("[iter_part.dmg_overlay_type]_[iter_part.body_zone]_0[iter_part.burnstate]")
if(isnull(damage_overlay))
return
overlays_standing[DAMAGE_LAYER] = damage_overlay
apply_overlay(DAMAGE_LAYER)
/mob/living/carbon/update_wound_overlays()
remove_overlay(WOUND_LAYER)
var/mutable_appearance/wound_overlay
for(var/obj/item/bodypart/iter_part as anything in bodyparts)
if(iter_part.bleed_overlay_icon)
wound_overlay ||= mutable_appearance('icons/mob/effects/bleed_overlays.dmi', "blank", -WOUND_LAYER, appearance_flags = KEEP_TOGETHER)
wound_overlay.add_overlay(iter_part.bleed_overlay_icon)
if(isnull(wound_overlay))
return
overlays_standing[WOUND_LAYER] = wound_overlay
apply_overlay(WOUND_LAYER)
/mob/living/carbon/update_worn_mask(update_obscured = TRUE)
remove_overlay(FACEMASK_LAYER)
if(!get_bodypart(BODY_ZONE_HEAD)) //Decapitated
return
if(client && hud_used?.inv_slots[TOBITSHIFT(ITEM_SLOT_MASK) + 1])
var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_MASK) + 1]
inv.update_appearance()
if(wear_mask)
if(update_obscured)
update_obscured_slots(wear_mask.flags_inv)
if(!(check_obscured_slots() & ITEM_SLOT_MASK))
overlays_standing[FACEMASK_LAYER] = wear_mask.build_worn_icon(default_layer = FACEMASK_LAYER, default_icon_file = 'icons/mob/clothing/mask.dmi')
update_hud_wear_mask(wear_mask)
apply_overlay(FACEMASK_LAYER)
/mob/living/carbon/update_worn_neck(update_obscured = TRUE)
remove_overlay(NECK_LAYER)
if(client && hud_used?.inv_slots[TOBITSHIFT(ITEM_SLOT_NECK) + 1])
var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_NECK) + 1]
inv.update_appearance()
if(wear_neck)
if(update_obscured)
update_obscured_slots(wear_neck.flags_inv)
if(!(check_obscured_slots() & ITEM_SLOT_NECK))
overlays_standing[NECK_LAYER] = wear_neck.build_worn_icon(default_layer = NECK_LAYER, default_icon_file = 'icons/mob/clothing/neck.dmi')
update_hud_neck(wear_neck)
apply_overlay(NECK_LAYER)
/mob/living/carbon/update_worn_back(update_obscured = TRUE)
remove_overlay(BACK_LAYER)
if(client && hud_used?.inv_slots[TOBITSHIFT(ITEM_SLOT_BACK) + 1])
var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_BACK) + 1]
inv.update_appearance()
if(back)
if(update_obscured)
update_obscured_slots(back.flags_inv)
overlays_standing[BACK_LAYER] = back.build_worn_icon(default_layer = BACK_LAYER, default_icon_file = 'icons/mob/clothing/back.dmi')
update_hud_back(back)
apply_overlay(BACK_LAYER)
/mob/living/carbon/update_worn_legcuffs(update_obscured = TRUE)
remove_overlay(LEGCUFF_LAYER)
clear_alert("legcuffed")
if(legcuffed)
if(update_obscured)
update_obscured_slots(legcuffed.flags_inv)
overlays_standing[LEGCUFF_LAYER] = mutable_appearance('icons/mob/simple/mob.dmi', "legcuff1", -LEGCUFF_LAYER)
apply_overlay(LEGCUFF_LAYER)
throw_alert("legcuffed", /atom/movable/screen/alert/restrained/legcuffed, new_master = src.legcuffed)
/mob/living/carbon/update_worn_head(update_obscured = TRUE)
remove_overlay(HEAD_LAYER)
if(!get_bodypart(BODY_ZONE_HEAD)) //Decapitated
return
if(client && hud_used?.inv_slots[TOBITSHIFT(ITEM_SLOT_BACK) + 1])
var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_HEAD) + 1]
inv.update_appearance()
if(head)
if(update_obscured)
update_obscured_slots(head.flags_inv)
if(!(check_obscured_slots() & ITEM_SLOT_HEAD))
overlays_standing[HEAD_LAYER] = head.build_worn_icon(default_layer = HEAD_LAYER, default_icon_file = 'icons/mob/clothing/head/default.dmi')
update_hud_head(head)
apply_overlay(HEAD_LAYER)
/mob/living/carbon/update_worn_handcuffs(update_obscured = TRUE)
remove_overlay(HANDCUFF_LAYER)
if(handcuffed)
if(update_obscured)
update_obscured_slots(handcuffed.flags_inv)
var/mutable_appearance/handcuff_overlay = mutable_appearance('icons/mob/simple/mob.dmi', "handcuff1", -HANDCUFF_LAYER)
if(handcuffed.blocks_emissive != EMISSIVE_BLOCK_NONE)
handcuff_overlay.overlays += emissive_blocker(handcuff_overlay.icon, handcuff_overlay.icon_state, src, alpha = handcuff_overlay.alpha)
overlays_standing[HANDCUFF_LAYER] = handcuff_overlay
apply_overlay(HANDCUFF_LAYER)
//mob HUD updates for items in our inventory
//update whether handcuffs appears on our hud.
/mob/living/carbon/proc/update_hud_handcuffed()
if(hud_used)
for(var/hand in hud_used.hand_slots)
var/atom/movable/screen/inventory/hand/H = hud_used.hand_slots[hand]
if(H)
H.update_appearance()
//update whether our head item appears on our hud.
/mob/living/carbon/proc/update_hud_head(obj/item/I)
return
//update whether our mask item appears on our hud.
/mob/living/carbon/proc/update_hud_wear_mask(obj/item/I)
return
//update whether our neck item appears on our hud.
/mob/living/carbon/proc/update_hud_neck(obj/item/I)
return
//update whether our back item appears on our hud.
/mob/living/carbon/proc/update_hud_back(obj/item/I)
return
//Overlays for the worn overlay so you can overlay while you overlay
//eg: ammo counters, primed grenade flashing, etc.
//"icon_file" is used automatically for inhands etc. to make sure it gets the right inhand file
/obj/item/proc/worn_overlays(mutable_appearance/standing, isinhands = FALSE, icon_file)
SHOULD_CALL_PARENT(TRUE)
RETURN_TYPE(/list)
. = list()
if(blocks_emissive != EMISSIVE_BLOCK_NONE)
. += emissive_blocker(standing.icon, standing.icon_state, src, alpha = standing.alpha)
SEND_SIGNAL(src, COMSIG_ITEM_GET_WORN_OVERLAYS, ., standing, isinhands, icon_file)
///Checks to see if any bodyparts need to be redrawn, then does so. update_limb_data = TRUE redraws the limbs to conform to the owner.
///Returns an integer representing the number of limbs that were updated.
/mob/living/carbon/proc/update_body_parts(update_limb_data)
update_damage_overlays()
update_wound_overlays()
var/list/needs_update = list()
var/limb_count_update = 0
for(var/obj/item/bodypart/limb as anything in bodyparts)
limb.update_limb(is_creating = update_limb_data) //Update limb actually doesn't do much, get_limb_icon is the cpu eater.
var/old_key = icon_render_keys?[limb.body_zone] //Checks the mob's icon render key list for the bodypart
icon_render_keys[limb.body_zone] = (limb.is_husked) ? limb.generate_husk_key().Join() : limb.generate_icon_key().Join() //Generates a key for the current bodypart
if(icon_render_keys[limb.body_zone] != old_key) //If the keys match, that means the limb doesn't need to be redrawn
needs_update += limb
limb_count_update += length(needs_update)
var/list/missing_bodyparts = get_missing_limbs()
if(((dna ? dna.species.max_bodypart_count : BODYPARTS_DEFAULT_MAXIMUM) - icon_render_keys.len) != missing_bodyparts.len) //Checks to see if the target gained or lost any limbs.
limb_count_update += 1
for(var/missing_limb in missing_bodyparts)
icon_render_keys -= missing_limb //Removes dismembered limbs from the key list
. = limb_count_update
if(!.)
return
//GENERATE NEW LIMBS
var/list/new_limbs = list()
for(var/obj/item/bodypart/limb as anything in bodyparts)
if(limb in needs_update)
var/bodypart_icon = limb.get_limb_icon()
new_limbs += bodypart_icon
limb_icon_cache[icon_render_keys[limb.body_zone]] = bodypart_icon //Caches the icon with the bodypart key, as it is new
else
new_limbs += limb_icon_cache[icon_render_keys[limb.body_zone]] //Pulls existing sprites from the cache
remove_overlay(BODYPARTS_LAYER)
if(new_limbs.len)
overlays_standing[BODYPARTS_LAYER] = new_limbs
apply_overlay(BODYPARTS_LAYER)
/////////////////////////
// Limb Icon Cache 2.0 //
/////////////////////////
/**
* Called from update_body_parts() these procs handle the limb icon cache.
* the limb icon cache adds an icon_render_key to a human mob, it represents:
* - Gender, if applicable
* - The ID of the limb
* - Draw color, if applicable
* These procs only store limbs as to increase the number of matching icon_render_keys
* This cache exists because drawing 6/7 icons for humans constantly is quite a waste
* See RemieRichards on irc.rizon.net #coderbus (RIP remie :sob:)
**/
/obj/item/bodypart/proc/generate_icon_key()
RETURN_TYPE(/list)
. = list()
if(is_dimorphic)
. += "[limb_gender]-"
. += "[limb_id]"
. += "-[body_zone]"
if(should_draw_greyscale && draw_color)
. += "-[draw_color]"
if(is_invisible)
. += "-invisible"
for(var/datum/bodypart_overlay/overlay as anything in bodypart_overlays)
if(!overlay.can_draw_on_bodypart(src, owner))
continue
. += "-[jointext(overlay.generate_icon_cache(), "-")]"
if(ishuman(owner))
var/mob/living/carbon/human/human_owner = owner
. += "-[human_owner.mob_height]"
return .
///Generates a cache key specifically for husks
/obj/item/bodypart/proc/generate_husk_key()
RETURN_TYPE(/list)
. = list()
. += "[limb_id]-"
. += "[husk_type]"
. += "-husk"
. += "-[body_zone]"
if(ishuman(owner))
var/mob/living/carbon/human/human_owner = owner
. += "-[human_owner.mob_height]"
return .
/obj/item/bodypart/head/generate_icon_key()
. = ..()
if(lip_style)
. += "-[lip_style]"
. += "-[lip_color]"
if(facial_hair_hidden)
. += "-FACIAL_HAIR_HIDDEN"
else
. += "-[facial_hairstyle]"
. += "-[override_hair_color || fixed_hair_color || facial_hair_color]"
. += "-[facial_hair_alpha]"
if(gradient_styles?[GRADIENT_FACIAL_HAIR_KEY])
. += "-[gradient_styles[GRADIENT_FACIAL_HAIR_KEY]]"
. += "-[gradient_colors[GRADIENT_FACIAL_HAIR_KEY]]"
if(show_eyeless)
. += "-SHOW_EYELESS"
if(show_debrained)
. += "-SHOW_DEBRAINED"
return .
if(hair_hidden)
. += "-HAIR_HIDDEN"
else
. += "-[hairstyle]"
. += "-[override_hair_color || fixed_hair_color || hair_color]"
. += "-[hair_alpha]"
if(gradient_styles?[GRADIENT_HAIR_KEY])
. += "-[gradient_styles[GRADIENT_HAIR_KEY]]"
. += "-[gradient_colors[GRADIENT_HAIR_KEY]]"
if(LAZYLEN(hair_masks))
. += "-[jointext(hair_masks, "-")]"
return .
GLOBAL_LIST_EMPTY(masked_leg_icons_cache)
/**
* This proc serves as a way to ensure that legs layer properly on a mob.
* To do this, two separate images are created - A low layer one, and a normal layer one.
* Each of the image will appropriately crop out dirs that are not used on that given layer.
*
* Arguments:
* * limb_overlay - The limb image being masked, not necessarily the original limb image as it could be an overlay on top of it
* * image_dir - Direction of the masked images.
*
* Returns the list of masked images, or `null` if the limb_overlay didn't exist
*/
/obj/item/bodypart/leg/proc/generate_masked_leg(mutable_appearance/limb_overlay, image_dir = NONE)
RETURN_TYPE(/list)
if(!limb_overlay)
return
. = list()
var/icon_cache_key = "[limb_overlay.icon]-[limb_overlay.icon_state]-[body_zone]"
var/icon/new_leg_icon
var/icon/new_leg_icon_lower
//in case we do not have a cached version of the two cropped icons for this key, we have to create it
if(!GLOB.masked_leg_icons_cache[icon_cache_key])
var/icon/leg_crop_mask = (body_zone == BODY_ZONE_R_LEG ? icon('icons/mob/leg_masks.dmi', "right_leg") : icon('icons/mob/leg_masks.dmi', "left_leg"))
var/icon/leg_crop_mask_lower = (body_zone == BODY_ZONE_R_LEG ? icon('icons/mob/leg_masks.dmi', "right_leg_lower") : icon('icons/mob/leg_masks.dmi', "left_leg_lower"))
new_leg_icon = icon(limb_overlay.icon, limb_overlay.icon_state)
new_leg_icon.Blend(leg_crop_mask, ICON_MULTIPLY)
new_leg_icon_lower = icon(limb_overlay.icon, limb_overlay.icon_state)
new_leg_icon_lower.Blend(leg_crop_mask_lower, ICON_MULTIPLY)
GLOB.masked_leg_icons_cache[icon_cache_key] = list(new_leg_icon, new_leg_icon_lower)
new_leg_icon = GLOB.masked_leg_icons_cache[icon_cache_key][1]
new_leg_icon_lower = GLOB.masked_leg_icons_cache[icon_cache_key][2]
//this could break layering in oddjob cases, but i'm sure it will work fine most of the time... right?
var/mutable_appearance/new_leg_appearance = new(limb_overlay)
new_leg_appearance.icon = new_leg_icon
new_leg_appearance.layer = -BODYPARTS_LAYER
new_leg_appearance.dir = image_dir //for some reason, things do not work properly otherwise
. += new_leg_appearance
var/mutable_appearance/new_leg_appearance_lower = new(limb_overlay)
new_leg_appearance_lower.icon = new_leg_icon_lower
new_leg_appearance_lower.layer = -BODYPARTS_LOW_LAYER
new_leg_appearance_lower.dir = image_dir
. += new_leg_appearance_lower
return .