Digitigrade code refactor and shoes + explorer suit (Using GAGS and zero sprite bloat) (#88096)

## About The Pull Request
Refactors and extends the existing [digitigrade clothing
system](https://github.com/tgstation/tgstation/pull/85406) to also
support oversuits and shoes (only enabled for sneakers, specific boots,
and shaft miner's explorer suit). All digitigrade-specific clothes are
generated by GAGS by color sampling the base sprite and applying it to a
greyscale template.


![image](https://github.com/user-attachments/assets/c4ce5996-9373-4d76-a0e5-a6b094dd10b4)

More up to date boots:

![image](https://github.com/user-attachments/assets/8ead8056-25a3-42dd-9fde-e838e2a810b9)


![image](https://github.com/user-attachments/assets/783a1201-8a77-45a0-af85-8a63b41b63a8)


Credit to MrMelbert for prototype code and Junkgle for new sprites

## Why It's Good For The Game
Looks good, doesn't introduce maintainability issues or sprite bloat

## Changelog
🆑
add: digitigrade lizards can wear certain shoes and suits
image: added digitigrade shoes & oversuit templates
refactor: improved digi clothing generator code
/🆑

---------

Co-authored-by: MrMelbert <kmelbert4@gmail.com>
Co-authored-by: Roryl-c <5150427+Roryl-c@users.noreply.github.com>
This commit is contained in:
Runi-c
2024-11-28 16:17:18 +01:00
committed by GitHub
co-authored by MrMelbert Roryl-c
parent 38e1c4f21e
commit 8b9da265ba
19 changed files with 156 additions and 60 deletions
-1
View File
@@ -163,7 +163,6 @@ DEFINE_BITFIELD(no_equip_flags, list(
/// 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
@@ -276,11 +276,6 @@
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'
@@ -627,6 +622,14 @@
name = "Waistcoat (Worn)"
icon_file = 'icons/mob/clothing/accessories.dmi'
// Digi Stuff
/datum/greyscale_config/digitigrade
name = "Digitigrade Clothes"
icon_file = 'icons/mob/clothing/digi_template.dmi'
json_config = 'code/datums/greyscale/json_configs/digitigrade.json'
//
// SUIT + HEAD
// (Specifically for toggleable suits with hats, i.e. winter coats)
@@ -0,0 +1,39 @@
{
"jumpsuit_worn": [
{
"type": "icon_state",
"icon_state": "jumpsuit",
"blend_mode": "overlay",
"color_ids": [ 1 ]
}
],
"oversuit_worn": [
{
"type": "icon_state",
"icon_state": "oversuit",
"blend_mode": "overlay",
"color_ids": [ 1 ]
}
],
"sneakers_worn": [
{
"type": "icon_state",
"icon_state": "shoes_colored",
"blend_mode": "overlay",
"color_ids": [ 1 ]
},
{
"type": "icon_state",
"icon_state": "shoes_uncolored",
"blend_mode": "overlay"
}
],
"boots_worn": [
{
"type": "icon_state",
"icon_state": "boots",
"blend_mode": "overlay",
"color_ids": [ 1 ]
}
]
}
@@ -1,10 +0,0 @@
{
"": [
{
"type": "icon_state",
"icon_state": "jumpsuit",
"blend_mode": "overlay",
"color_ids": [ 1 ]
}
]
}
-6
View File
@@ -39,12 +39,6 @@
///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
+3
View File
@@ -110,6 +110,9 @@
var/mob/M = loc
M.update_worn_shoes()
/obj/item/clothing/shoes/generate_digitigrade_icons(icon/base_icon, greyscale_colors)
return icon(SSgreyscale.GetColoredIconByType(/datum/greyscale_config/digitigrade, greyscale_colors), "boots_worn")
/**
* adjust_laces adjusts whether our shoes (assuming they can_be_tied) and tied, untied, or knotted
*
+6
View File
@@ -3,6 +3,7 @@
desc = "High speed, low drag combat boots."
icon_state = "jackboots"
inhand_icon_state = "jackboots"
supports_variations_flags = CLOTHING_DIGITIGRADE_MASK
body_parts_covered = FEET|LEGS
armor_type = /datum/armor/shoes_combat
strip_delay = 40
@@ -45,6 +46,7 @@
desc = "Nanotrasen-issue Security combat boots for combat scenarios or combat situations. All combat, all the time."
icon_state = "jackboots"
inhand_icon_state = "jackboots"
supports_variations_flags = CLOTHING_DIGITIGRADE_MASK
strip_delay = 30
equip_delay_other = 50
resistance_flags = NONE
@@ -71,6 +73,7 @@
desc = "Is it just me or is there a pair of jackboots on the floor?"
icon_state = "ftc_boots"
inhand_icon_state = null
supports_variations_flags = NONE
/obj/item/clothing/shoes/jackboots/floortile/Initialize(mapload)
. = ..()
@@ -81,6 +84,7 @@
desc = "Boots lined with 'synthetic' animal fur."
icon_state = "winterboots"
inhand_icon_state = null
supports_variations_flags = CLOTHING_DIGITIGRADE_MASK
armor_type = /datum/armor/shoes_winterboots
cold_protection = FEET|LEGS
min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT
@@ -131,6 +135,7 @@
icon_state = "workboots"
inhand_icon_state = "jackboots"
armor_type = /datum/armor/shoes_workboots
supports_variations_flags = CLOTHING_DIGITIGRADE_MASK
strip_delay = 20
equip_delay_other = 40
lace_time = 8 SECONDS
@@ -155,6 +160,7 @@
icon_state = "rus_shoes"
inhand_icon_state = null
lace_time = 8 SECONDS
supports_variations_flags = CLOTHING_DIGITIGRADE_MASK
/obj/item/clothing/shoes/russian/Initialize(mapload)
. = ..()
+8
View File
@@ -9,9 +9,17 @@
greyscale_config_worn = /datum/greyscale_config/sneakers/worn
greyscale_config_inhand_left = /datum/greyscale_config/sneakers/inhand_left
greyscale_config_inhand_right = /datum/greyscale_config/sneakers/inhand_right
supports_variations_flags = CLOTHING_DIGITIGRADE_MASK
flags_1 = IS_PLAYER_COLORABLE_1
interaction_flags_mouse_drop = NEED_HANDS
/obj/item/clothing/shoes/sneakers/get_general_color(icon/base_icon)
var/colors = SSgreyscale.ParseColorString(greyscale_colors)
return colors ? colors[1] : ..()
/obj/item/clothing/shoes/sneakers/generate_digitigrade_icons(icon/base_icon, greyscale_colors)
return icon(SSgreyscale.GetColoredIconByType(/datum/greyscale_config/digitigrade, greyscale_colors), "sneakers_worn")
/obj/item/clothing/shoes/sneakers/random/Initialize(mapload)
. = ..()
greyscale_colors = "#" + random_color() + "#" + random_color()
+4
View File
@@ -43,3 +43,7 @@
if(ismob(loc))
var/mob/M = loc
M.update_worn_oversuit()
/obj/item/clothing/suit/generate_digitigrade_icons(icon/base_icon, greyscale_colors)
var/icon/legs = icon(SSgreyscale.GetColoredIconByType(/datum/greyscale_config/digitigrade, greyscale_colors), "oversuit_worn")
return replace_icon_legs(base_icon, legs)
+4 -1
View File
@@ -9,7 +9,6 @@
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'
@@ -142,6 +141,10 @@
adjusted = DIGITIGRADE_STYLE
update_appearance()
/obj/item/clothing/under/generate_digitigrade_icons(icon/base_icon, greyscale_colors)
var/icon/legs = icon(SSgreyscale.GetColoredIconByType(/datum/greyscale_config/digitigrade, greyscale_colors), "jumpsuit_worn")
return replace_icon_legs(base_icon, legs)
/obj/item/clothing/under/equipped(mob/living/user, slot)
..()
if((slot & ITEM_SLOT_ICLOTHING) && freshly_laundered)
+3 -1
View File
@@ -224,10 +224,12 @@
greyscale_config_inhand_left = null
greyscale_config_inhand_right = null
greyscale_config_worn = null
digitigrade_greyscale_colors = "#3f3f3f"
can_adjust = FALSE
flags_1 = NONE
/obj/item/clothing/under/color/rainbow/get_general_color(icon/base_icon)
return "#3f3f3f"
/obj/item/clothing/under/color/jumpskirt/rainbow
name = "rainbow jumpskirt"
desc = "A multi-colored jumpskirt!"
+3 -1
View File
@@ -33,7 +33,9 @@
desc = "Groovy!"
icon_state = "psyche"
inhand_icon_state = "p_suit"
digitigrade_greyscale_colors = "#3f3f3f"
/obj/item/clothing/under/misc/psyche/get_general_color(icon/base_icon)
return "#3f3f3f"
/obj/item/clothing/under/misc/vice_officer
name = "vice officer's jumpsuit"
@@ -6,6 +6,7 @@
icon = 'icons/obj/clothing/suits/utility.dmi'
worn_icon = 'icons/mob/clothing/suits/utility.dmi'
inhand_icon_state = null
supports_variations_flags = CLOTHING_DIGITIGRADE_MASK
body_parts_covered = CHEST|GROIN|LEGS|ARMS
cold_protection = CHEST|GROIN|LEGS|ARMS
min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT
@@ -15,6 +16,9 @@
armor_type = /datum/armor/hooded_explorer
resistance_flags = FIRE_PROOF
/obj/item/clothing/suit/hooded/explorer/get_general_color(icon/base_icon)
return "#796755"
/datum/armor/hooded_explorer
melee = 30
bullet = 10
@@ -573,20 +573,70 @@ There are several things that need to be remembered:
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
/// Modifies a sprite to conform to digitigrade body shapes
/proc/wear_digi_version(icon/base_icon, obj/item/item, key, greyscale_colors)
ASSERT(istype(item), "wear_digi_version: no item passed")
ASSERT(istext(key), "wear_digi_version: no key passed")
if(isnull(greyscale_colors) || length(SSgreyscale.ParseColorString(greyscale_colors)) > 1)
greyscale_colors = item.get_general_color(base_icon)
var/index = "[key]-[item.type]-[greyscale_colors]"
var/static/list/digitigrade_clothing_cache = list()
var/icon/resulting_icon = digitigrade_clothing_cache[index]
if(!resulting_icon)
resulting_icon = item.generate_digitigrade_icons(base_icon, greyscale_colors)
if(!resulting_icon)
stack_trace("[item.type] is set to generate a masked digitigrade icon, but generate_digitigrade_icons was not implemented (or error'd).")
return base_icon
digitigrade_clothing_cache[index] = fcopy_rsc(resulting_icon)
return icon(resulting_icon)
/// Modifies a sprite to replace the legs with a new version
/proc/replace_icon_legs(icon/base_icon, icon/new_legs)
var/static/icon/leg_mask
if(!leg_mask)
leg_mask = icon('icons/mob/clothing/under/masking_helpers.dmi', "digi_leg_mask")
// cuts the legs off
base_icon.Blend(leg_mask, ICON_SUBTRACT)
// staples the new legs on
base_icon.Blend(new_legs, ICON_OVERLAY)
return base_icon
/**
* Generates a digitigrade version of this item's worn icon
*
* Arguments:
* * base_icon: The icon to generate the digitigrade icon from
* * greyscale_colors: The greyscale colors to use for the digitigrade icon
*
* Returns an icon that is the digitigrade version of the item's worn icon
* Returns null if the item has no support for digitigrade variations via this method
*/
/obj/item/proc/generate_digitigrade_icons(icon/base_icon, greyscale_colors)
return null
/**
* Get what color the item is on "average"
* Can be used to approximate what color this item is/should be
*
* Arguments:
* * base_icon: The icon to get the color from
*/
/obj/item/proc/get_general_color(icon/base_icon)
if(greyscale_colors && length(SSgreyscale.ParseColorString(greyscale_colors)) == 1)
return greyscale_colors
return color
// These coordinates point to the middle of the left leg
#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)
/obj/item/clothing/get_general_color(icon/base_icon)
if(slot_flags & (ITEM_SLOT_ICLOTHING|ITEM_SLOT_OCLOTHING))
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)
@@ -594,37 +644,26 @@ There are several things that need to be remembered:
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
return 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)
return ..()
#undef LEG_SAMPLE_X_LOWER
#undef LEG_SAMPLE_X_UPPER
#undef LEG_SAMPLE_Y_LOWER
#undef LEG_SAMPLE_Y_UPPER
// Points to the tip of the left foot
#define SHOE_SAMPLE_X 11
#define SHOE_SAMPLE_Y 2
/obj/item/clothing/shoes/get_general_color(icon/base_icon)
// just grabs the color of the middle of the left foot
return base_icon.GetPixel(SHOE_SAMPLE_X, SHOE_SAMPLE_Y) || "#1d1d1d"
#undef SHOE_SAMPLE_X
#undef SHOE_SAMPLE_Y
/mob/living/carbon/human/proc/get_overlays_copy(list/unwantedLayers)
var/list/out = new
for(var/i in 1 to TOTAL_LAYERS)
@@ -774,9 +813,9 @@ generate/load female uniform sprites matching all previously decided variables
if(!isinhands && is_digi && (supports_variations_flags & CLOTHING_DIGITIGRADE_MASK))
building_icon = wear_digi_version(
base_icon = building_icon || icon(file2use, t_state),
item = src,
key = "[t_state]-[file2use]-[female_uniform]",
greyscale_config = digitigrade_greyscale_config_worn || greyscale_config_worn,
greyscale_colors = digitigrade_greyscale_colors || greyscale_colors || color,
greyscale_colors = greyscale_colors,
)
if(building_icon)
standing = mutable_appearance(building_icon, layer = -layer2use)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 B

After

Width:  |  Height:  |  Size: 433 B