Digitigrade clothing sprites (feat. GAGS and 0 sprite bloat) (#85406)

## About The Pull Request

Use GAGS to auto-generate digitgrade leg sprites based off of 1 basic
template + color sampling.
Icons are cached relatively aggressively, like female sprite variations.
So no need to worry about that.

The result: All of these sprites with only **3** icons added (the base
template, and the masks)
Obviously it's not perfect - some of the sprites (like the engineer's
jumpsuit) lose some "luster", but it does the job if I do say so myself.


![image](https://github.com/user-attachments/assets/5c23dc97-8716-490c-898c-58b1e01a8a02)

Only applied to undersuits, for now. 

## Why It's Good For The Game

- Allows Lizards to show off their handicap, rather than hiding it or
necessitating they use skirts.

- (Ideally) leads to an uptick in Digitigrade abuse (due to being easier
to identify), which is also a net positive.

- Implemented without any common issues revolving alt bodytype clothing
sprites (those being maintainability and sprite bloat)

## Changelog

🆑 Melbert
add: Auto-generated digitigrade clothing sprites for most jumpsuits
/🆑

---------

Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
This commit is contained in:
MrMelbert
2024-08-15 23:28:02 +02:00
committed by GitHub
co-authored by Ghom
parent c7a20e2300
commit ed2f017923
16 changed files with 171 additions and 57 deletions
+9 -3
View File
@@ -148,12 +148,18 @@ DEFINE_BITFIELD(no_equip_flags, list(
#define DIGITIGRADE_STYLE 2
//Flags (actual flags, fucker ^) for /obj/item/var/supports_variations_flags
///No alternative sprites based on bodytype
/// No alternative sprites or handling based on bodytype
#define CLOTHING_NO_VARIATION (1<<0)
///Has a sprite for digitigrade legs specifically.
/// Has a sprite for digitigrade legs specifically.
#define CLOTHING_DIGITIGRADE_VARIATION (1<<1)
///The sprite works fine for digitigrade legs as-is.
/// The sprite works fine for digitigrade legs as-is.
#define CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON (1<<2)
/// Auto-generates the leg portion of the sprite with GAGS
/// Suggested that you set [/obj/item/var/digitigrade_greyscale_config_worn] when using this flag
#define CLOTHING_DIGITIGRADE_MASK (1<<3)
/// All variation flags which render "correctly" on a digitigrade leg setup
#define DIGITIGRADE_VARIATIONS (CLOTHING_DIGITIGRADE_VARIATION|CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON|CLOTHING_DIGITIGRADE_MASK)
//flags for covering body parts
#define GLASSESCOVERSEYES (1<<0)
+1
View File
@@ -442,6 +442,7 @@ DEFINE_BITFIELD(supports_variations_flags, list(
"CLOTHING_NO_VARIATION" = CLOTHING_NO_VARIATION,
"CLOTHING_DIGITIGRADE_VARIATION" = CLOTHING_DIGITIGRADE_VARIATION,
"CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON" = CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON,
"CLOTHING_DIGITIGRADE_MASK" = CLOTHING_DIGITIGRADE_MASK,
))
DEFINE_BITFIELD(flora_flags, list(
@@ -276,6 +276,11 @@
icon_file = 'icons/mob/inhands/clothing/suits_righthand.dmi'
json_config = 'code/datums/greyscale/json_configs/jumpsuit_prison_inhand.json'
/datum/greyscale_config/jumpsuit/worn_digi
name = "Jumpsuit Worn (Digitigrate)"
icon_file = 'icons/mob/clothing/under/digi_template.dmi'
json_config = 'code/datums/greyscale/json_configs/jumpsuit_worn_digilegs.json'
/datum/greyscale_config/eth_tunic
name = "Ethereal Tunic"
icon_file = 'icons/obj/clothing/under/ethereal.dmi'
@@ -0,0 +1,10 @@
{
"": [
{
"type": "icon_state",
"icon_state": "jumpsuit",
"blend_mode": "overlay",
"color_ids": [ 1 ]
}
]
}
+6
View File
@@ -39,6 +39,12 @@
///The config type to use for greyscaled belt overlays. Both this and greyscale_colors must be assigned to work.
var/greyscale_config_belt
/// Greyscale config used when generating digitigrade versions of the sprite.
var/digitigrade_greyscale_config_worn
/// Greyscale colors used when generating digitigrade versions of the sprite.
/// Optional - If not set it will default to normal greyscale colors, or approximate them if those are unset as well
var/digitigrade_greyscale_colors
/* !!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!
IF YOU ADD MORE ICON CRAP TO THIS
+4 -4
View File
@@ -97,15 +97,15 @@
var/datum/sprite_accessory/underwear/underwear = SSaccessories.underwear_list[underwear_name]
if(underwear)
if(body_type == FEMALE && underwear.gender == MALE)
. += wear_female_version(underwear.icon_state, underwear.icon, BODY_LAYER, FEMALE_UNIFORM_FULL)
. += mutable_appearance(wear_female_version(underwear.icon_state, underwear.icon, FEMALE_UNIFORM_FULL), layer = -BODY_LAYER)
else
. += mutable_appearance(underwear.icon, underwear.icon_state, -BODY_LAYER)
. += mutable_appearance(underwear.icon, underwear.icon_state, layer = -BODY_LAYER)
var/datum/sprite_accessory/undershirt/undershirt = SSaccessories.undershirt_list[undershirt_name]
if(undershirt)
if(body_type == FEMALE)
. += wear_female_version(undershirt.icon_state, undershirt.icon, BODY_LAYER)
. += mutable_appearance(wear_female_version(undershirt.icon_state, undershirt.icon), layer = -BODY_LAYER)
else
. += mutable_appearance(undershirt.icon, undershirt.icon_state, -BODY_LAYER)
. += mutable_appearance(undershirt.icon, undershirt.icon_state, layer = -BODY_LAYER)
var/datum/sprite_accessory/socks/socks = SSaccessories.socks_list[socks_name]
if(socks)
. += mutable_appearance(socks.icon, socks.icon_state, -BODY_LAYER)
+2
View File
@@ -8,6 +8,8 @@
slot_flags = ITEM_SLOT_ICLOTHING
interaction_flags_click = NEED_DEXTERITY
armor_type = /datum/armor/clothing_under
supports_variations_flags = CLOTHING_DIGITIGRADE_MASK
digitigrade_greyscale_config_worn = /datum/greyscale_config/jumpsuit/worn_digi
equip_sound = 'sound/items/equip/jumpsuit_equip.ogg'
drop_sound = 'sound/items/handling/cloth_drop.ogg'
pickup_sound = 'sound/items/handling/cloth_pickup.ogg'
+1
View File
@@ -224,6 +224,7 @@
greyscale_config_inhand_left = null
greyscale_config_inhand_right = null
greyscale_config_worn = null
digitigrade_greyscale_colors = "#3f3f3f"
can_adjust = FALSE
flags_1 = NONE
@@ -31,6 +31,7 @@
inhand_icon_state = "clown"
female_sprite_flags = FEMALE_UNIFORM_TOP_ONLY
can_adjust = FALSE
supports_variations_flags = CLOTHING_NO_VARIATION
/obj/item/clothing/under/rank/civilian/clown/Initialize(mapload)
. = ..()
@@ -33,6 +33,7 @@
desc = "Groovy!"
icon_state = "psyche"
inhand_icon_state = "p_suit"
digitigrade_greyscale_colors = "#3f3f3f"
/obj/item/clothing/under/misc/vice_officer
name = "vice officer's jumpsuit"
@@ -572,7 +572,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
var/mutable_appearance/underwear_overlay
if(underwear)
if(species_human.dna.species.sexes && species_human.physique == FEMALE && (underwear.gender == MALE))
underwear_overlay = wear_female_version(underwear.icon_state, underwear.icon, BODY_LAYER, FEMALE_UNIFORM_FULL)
underwear_overlay = mutable_appearance(wear_female_version(underwear.icon_state, underwear.icon, FEMALE_UNIFORM_FULL), layer = -BODY_LAYER)
else
underwear_overlay = mutable_appearance(underwear.icon, underwear.icon_state, -BODY_LAYER)
if(!underwear.use_static)
@@ -584,9 +584,9 @@ GLOBAL_LIST_EMPTY(features_by_species)
if(undershirt)
var/mutable_appearance/working_shirt
if(species_human.dna.species.sexes && species_human.physique == FEMALE)
working_shirt = wear_female_version(undershirt.icon_state, undershirt.icon, BODY_LAYER)
working_shirt = mutable_appearance(wear_female_version(undershirt.icon_state, undershirt.icon), layer = -BODY_LAYER)
else
working_shirt = mutable_appearance(undershirt.icon, undershirt.icon_state, -BODY_LAYER)
working_shirt = mutable_appearance(undershirt.icon, undershirt.icon_state, layer = -BODY_LAYER)
standing += working_shirt
if(species_human.socks && species_human.num_legs >= 2 && !(species_human.bodyshape & BODYSHAPE_DIGITIGRADE))
@@ -800,7 +800,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
if(H.num_legs < 2)
return FALSE
if((H.bodyshape & BODYSHAPE_DIGITIGRADE) && !(I.item_flags & IGNORE_DIGITIGRADE))
if(!(I.supports_variations_flags & (CLOTHING_DIGITIGRADE_VARIATION|CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON)))
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!"))
return FALSE
@@ -548,12 +548,72 @@ There are several things that need to be remembered:
hands += hand_overlay
return hands
/proc/wear_female_version(t_color, icon, layer, type, greyscale_colors)
var/index = "[t_color]-[greyscale_colors]"
var/icon/female_clothing_icon = GLOB.female_clothing_icons[index]
if(!female_clothing_icon) //Create standing/laying icons if they don't exist
generate_female_clothing(index, t_color, icon, type)
return mutable_appearance(GLOB.female_clothing_icons[index], layer = -layer)
/// Modifies a sprite slightly to conform to female body shapes
/proc/wear_female_version(icon_state, icon, type, greyscale_colors)
var/index = "[icon_state]-[greyscale_colors]"
var/static/list/female_clothing_icons = list()
var/icon/female_clothing_icon = female_clothing_icons[index]
if(!female_clothing_icon) //Create standing/laying icons if they don't exist
var/female_icon_state = "female[type == FEMALE_UNIFORM_FULL ? "_full" : ((!type || type & FEMALE_UNIFORM_TOP_ONLY) ? "_top" : "")][type & FEMALE_UNIFORM_NO_BREASTS ? "_no_breasts" : ""]"
var/icon/female_cropping_mask = icon('icons/mob/clothing/under/masking_helpers.dmi', female_icon_state)
female_clothing_icon = icon(icon, icon_state)
female_clothing_icon.Blend(female_cropping_mask, ICON_MULTIPLY)
female_clothing_icon = fcopy_rsc(female_clothing_icon)
female_clothing_icons[index] = female_clothing_icon
return icon(female_clothing_icon)
// These coordonates point to roughly somewhere in the middle of the left leg
// Used in approximating what color the pants of clothing should be
#define LEG_SAMPLE_X_LOWER 13
#define LEG_SAMPLE_X_UPPER 14
#define LEG_SAMPLE_Y_LOWER 8
#define LEG_SAMPLE_Y_UPPER 9
/// Modifies a sprite to conform to digitigrade body shapes
/proc/wear_digi_version(icon/base_icon, key, greyscale_config = /datum/greyscale_config/jumpsuit/worn_digi, greyscale_colors)
ASSERT(key, "wear_digi_version: no key passed")
ASSERT(ispath(greyscale_config, /datum/greyscale_config), "wear_digi_version: greyscale_config is not a valid path (got: [greyscale_config])")
// items with greyscale colors containing multiple colors are invalid
if(isnull(greyscale_colors) || length(SSgreyscale.ParseColorString(greyscale_colors)) > 1)
var/pant_color
// approximates the color of the pants by sampling a few pixels in the middle of the left leg
for(var/x in LEG_SAMPLE_X_LOWER to LEG_SAMPLE_X_UPPER)
for(var/y in LEG_SAMPLE_Y_LOWER to LEG_SAMPLE_Y_UPPER)
var/xy_color = base_icon.GetPixel(x, y)
pant_color = pant_color ? BlendRGB(pant_color, xy_color, 0.5) : xy_color
greyscale_colors = pant_color || "#1d1d1d" // black pants always look good
var/index = "[key]-[greyscale_config]-[greyscale_colors]"
var/static/list/digitigrade_clothing_icons = list()
var/icon/digitigrade_clothing_icon = digitigrade_clothing_icons[index]
if(!digitigrade_clothing_icon)
var/static/icon/torso_mask
if(!torso_mask)
torso_mask = icon('icons/mob/clothing/under/masking_helpers.dmi', "digi_torso_mask")
var/static/icon/leg_mask
if(!leg_mask)
leg_mask = icon('icons/mob/clothing/under/masking_helpers.dmi', "digi_leg_mask")
base_icon.Blend(leg_mask, ICON_SUBTRACT) // cuts the legs off
var/icon/leg_icon = SSgreyscale.GetColoredIconByType(greyscale_config, greyscale_colors)
leg_icon.Blend(torso_mask, ICON_SUBTRACT) // cuts the torso off
base_icon.Blend(leg_icon, ICON_OVERLAY) // puts the new legs on
digitigrade_clothing_icon = fcopy_rsc(base_icon)
digitigrade_clothing_icons[index] = digitigrade_clothing_icon
return icon(digitigrade_clothing_icon)
#undef LEG_SAMPLE_X_LOWER
#undef LEG_SAMPLE_X_UPPER
#undef LEG_SAMPLE_Y_LOWER
#undef LEG_SAMPLE_Y_UPPER
/mob/living/carbon/human/proc/get_overlays_copy(list/unwantedLayers)
var/list/out = new
@@ -689,11 +749,30 @@ 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/mutable_appearance/standing
var/mob/living/carbon/wearer = loc
var/is_digi = istype(wearer) && (wearer.bodyshape & BODYSHAPE_DIGITIGRADE) && !wearer.is_digitigrade_squished()
var/mutable_appearance/standing // this is the actual resulting MA
var/icon/building_icon // used to construct an icon across multiple procs before converting it to MA
if(female_uniform)
standing = wear_female_version(t_state, file2use, layer2use, female_uniform, greyscale_colors) //should layer2use be in sync with the adjusted value below? needs testing - shiz
if(!standing)
standing = mutable_appearance(file2use, t_state, -layer2use)
building_icon = wear_female_version(
icon_state = t_state,
icon = file2use,
type = female_uniform,
greyscale_colors = greyscale_colors,
)
if(!isinhands && is_digi && (supports_variations_flags & CLOTHING_DIGITIGRADE_MASK))
building_icon = wear_digi_version(
base_icon = building_icon || icon(file2use, t_state),
key = "[t_state]-[file2use]-[female_uniform]",
greyscale_config = digitigrade_greyscale_config_worn || greyscale_config_worn,
greyscale_colors = digitigrade_greyscale_colors || greyscale_colors || color,
)
if(building_icon)
standing = mutable_appearance(building_icon, layer = -layer2use)
// no special handling done, default it
standing ||= mutable_appearance(file2use, t_state, layer = -layer2use)
//Get the overlays for this item when it's being worn
//eg: ammo counters, primed grenade flashes, etc.
@@ -47,6 +47,30 @@
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) \
|| (worn_suit?.flags_inv & 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
@@ -54,24 +78,13 @@
/obj/item/bodypart/leg/left/digitigrade/update_limb(dropping_limb = FALSE, is_creating = FALSE)
. = ..()
if(ishuman(owner))
var/mob/living/carbon/human/human_owner = owner
var/obj/item/clothing/shoes/worn_shoes = human_owner.get_item_by_slot(ITEM_SLOT_FEET)
var/uniform_compatible = FALSE
var/suit_compatible = FALSE
var/shoes_compatible = FALSE
if(!(human_owner.w_uniform) || (human_owner.w_uniform.supports_variations_flags & (CLOTHING_DIGITIGRADE_VARIATION|CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON))) //Checks uniform compatibility
uniform_compatible = TRUE
if((!human_owner.wear_suit) || (human_owner.wear_suit.supports_variations_flags & (CLOTHING_DIGITIGRADE_VARIATION|CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON)) || !(human_owner.wear_suit.body_parts_covered & LEGS)) //Checks suit compatability
suit_compatible = TRUE
if((worn_shoes == null) || (worn_shoes.supports_variations_flags & (CLOTHING_DIGITIGRADE_VARIATION|CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON)))
shoes_compatible = TRUE
if((uniform_compatible && suit_compatible && shoes_compatible) || (suit_compatible && shoes_compatible && human_owner.wear_suit?.flags_inv & HIDEJUMPSUIT)) //If the uniform is hidden, it doesnt matter if its compatible
limb_id = BODYPART_ID_DIGITIGRADE
else
limb_id = SPECIES_LIZARD
var/old_id = limb_id
limb_id = owner?.is_digitigrade_squished() ? SPECIES_LIZARD : BODYPART_ID_DIGITIGRADE
if(old_id != limb_id)
// Something unsquished / squished us so we need to go through and update everything that is affected
for(var/obj/item/thing as anything in owner?.get_equipped_items())
if(thing.supports_variations_flags & DIGITIGRADE_VARIATIONS)
thing.update_slot_icon()
/obj/item/bodypart/leg/right/digitigrade
icon_greyscale = 'icons/mob/human/species/lizard/bodyparts.dmi'
@@ -80,21 +93,10 @@
/obj/item/bodypart/leg/right/digitigrade/update_limb(dropping_limb = FALSE, is_creating = FALSE)
. = ..()
if(ishuman(owner))
var/mob/living/carbon/human/human_owner = owner
var/obj/item/clothing/shoes/worn_shoes = human_owner.get_item_by_slot(ITEM_SLOT_FEET)
var/uniform_compatible = FALSE
var/suit_compatible = FALSE
var/shoes_compatible = FALSE
if(!(human_owner.w_uniform) || (human_owner.w_uniform.supports_variations_flags & (CLOTHING_DIGITIGRADE_VARIATION|CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON))) //Checks uniform compatibility
uniform_compatible = TRUE
if((!human_owner.wear_suit) || (human_owner.wear_suit.supports_variations_flags & (CLOTHING_DIGITIGRADE_VARIATION|CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON)) || !(human_owner.wear_suit.body_parts_covered & LEGS)) //Checks suit compatability
suit_compatible = TRUE
if((worn_shoes == null) || (worn_shoes.supports_variations_flags & (CLOTHING_DIGITIGRADE_VARIATION|CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON)))
shoes_compatible = TRUE
if((uniform_compatible && suit_compatible && shoes_compatible) || (suit_compatible && shoes_compatible && human_owner.wear_suit?.flags_inv & HIDEJUMPSUIT)) //If the uniform is hidden, it doesnt matter if its compatible
limb_id = BODYPART_ID_DIGITIGRADE
else
limb_id = SPECIES_LIZARD
var/old_id = limb_id
limb_id = owner?.is_digitigrade_squished() ? SPECIES_LIZARD : BODYPART_ID_DIGITIGRADE
if(old_id != limb_id)
// Something unsquished / squished us so we need to go through and update everything that is affected
for(var/obj/item/thing as anything in owner?.get_equipped_items())
if(thing.supports_variations_flags & DIGITIGRADE_VARIATIONS)
thing.update_slot_icon()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 B

After

Width:  |  Height:  |  Size: 454 B