Files
Bubberstation/code/modules/unit_tests/worn_icons.dm
Bloop cb51a652a9 Adds automatic GAGS icon generation for mapping and the loadout menu (#90940)
## About The Pull Request

Revival of https://github.com/tgstation/tgstation/pull/86482, which is
even more doable now that we have rustg iconforge generation.

What this PR does:

- Sets up every single GAGS icon in the game to have their own preview
icon autogenerated during compile. This is configurable to not run
during live. The icons are created in `icons/map_icons/..`
- This also has the side effect of providing accurate GAGS icons for
things like the loadout menu. No more having to create your own
previews.


![FOuGL6ofxC](https://github.com/user-attachments/assets/e5414971-7f13-4883-9f7f-a8a212b46fe8)

<details><summary>Mappers rejoice!</summary>


![StrongDMM_1oeMSoRHXT](https://github.com/user-attachments/assets/83dcfe4c-31be-4953-98f3-dff90268bbc4)


![StrongDMM_uyqu3CggPn](https://github.com/user-attachments/assets/7896f99e-2656-40e1-a9da-3a513882365a)

</details>

<details><summary>Uses iconforge so it does not take up much time during
init</summary>


![dreamdaemon_u4Md3Dqwge](https://github.com/user-attachments/assets/17baaff8-5d5e-4a4d-ba8f-9dd548024155)

</details>

---

### Copied from https://github.com/tgstation/tgstation/pull/86482 as
this still applies:

Note for Spriters:

After you've assigned the correct values to vars, you must run the game
through init on your local machine and commit the changes to the map
icon dmi files. Unit tests should catch all cases of forgetting to
assign the correct vars, or not running through init.

Note for Server Operators:

In order to not generate these icons on live I've added a new config
entry which should be disabled on live called GENERATE_ASSETS_IN_INIT in
the config.txt


## Why It's Good For The Game

No more error icons in SDMM and loadout.

## Changelog

🆑
refactor: preview icons for greyscale items are now automatically
generated, meaning you can see GAGS as they actually appear ingame while
mapping or viewing the loadout menu.
/🆑

---------

Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
2025-05-24 15:21:02 -07:00

106 lines
5.5 KiB
Plaintext

/// Makes sure suit slot items aren't using CS:S fallbacks.
/datum/unit_test/worn_icons
var/static/list/possible_icon_states = list()
/// additional_icon_location is for downstream modularity support for finding missing sprites in additonal DMI file locations.
/// Make sure this location is also present in tools/deploy.sh
/// If you need additional paths ontop of this second one, you can add another generate_possible_icon_states_list("your/folder/path/") below the if(additional_icon_location) block in Run(), and make sure to add that path to tools/deploy.sh as well.
var/additional_icon_location = null
/datum/unit_test/worn_icons/proc/generate_possible_icon_states_list(directory_path)
if(!directory_path)
directory_path = "icons/mob/clothing/"
for(var/file_path in flist(directory_path))
if(findtext(file_path, ".dmi"))
for(var/sprite_icon in icon_states("[directory_path][file_path]", 1)) //2nd arg = 1 enables 64x64+ icon support, otherwise you'll end up with "sword0_1" instead of "sword"
possible_icon_states[sprite_icon] += list("[directory_path][file_path]")
else
possible_icon_states += generate_possible_icon_states_list("[directory_path][file_path]")
/datum/unit_test/worn_icons/Run()
generate_possible_icon_states_list()
if(additional_icon_location)
generate_possible_icon_states_list(additional_icon_location)
var/list/already_warned_icons = list()
for(var/obj/item/item_path as anything in (subtypesof(/obj/item) - typesof(/obj/item/mod)))
var/cached_slot_flags = initial(item_path.slot_flags)
if(!cached_slot_flags || (cached_slot_flags & ITEM_SLOT_LPOCKET) || (cached_slot_flags & ITEM_SLOT_RPOCKET) || initial(item_path.item_flags) & ABSTRACT)
continue
if(initial(item_path.greyscale_colors) && initial(item_path.greyscale_config_worn)) //GAGS has its own unit test.
continue
var/worn_icon = initial(item_path.worn_icon) //override icon file. where our sprite is contained if set. (ie modularity stuff)
var/worn_icon_state = initial(item_path.worn_icon_state) //overrides icon_state.
var/icon_state = worn_icon_state || initial(item_path.icon_state) //icon_state. what sprite name we are looking for.
if(isnull(icon_state))
continue //no sprite for the item.
if(icon_state in already_warned_icons)
continue
var/match_message
if(icon_state in possible_icon_states)
for(var/file_place in possible_icon_states[icon_state])
match_message += (match_message ? " & '[file_place]'" : " - Matching sprite found in: '[file_place]'")
if(worn_icon) //easiest to check since we override everything. this automatically includes downstream support.
if(isnull(worn_icon_state)) // no worn sprite for this item.
continue
if(!(icon_state in icon_states(worn_icon, 1)))
TEST_FAIL("[item_path] using invalid [worn_icon_state ? "worn_icon_state" : "icon_state"], \"[icon_state]\" in worn_icon override file, '[worn_icon]'[match_message]")
continue
var/icon_file //checks against all the default icon locations if one isn't defined.
if(cached_slot_flags & ITEM_SLOT_BACK)
icon_file = 'icons/mob/clothing/back.dmi'
if(!(icon_state in icon_states(icon_file, 1)))
already_warned_icons += icon_state
TEST_FAIL("[item_path] using invalid [worn_icon_state ? "worn_icon_state" : "icon_state"], \"[icon_state]\" in '[icon_file]'[match_message]")
if(cached_slot_flags & ITEM_SLOT_ID)
icon_file = 'icons/mob/clothing/id.dmi'
if(!(icon_state in icon_states(icon_file, 1)))
already_warned_icons += icon_state
TEST_FAIL("[item_path] using invalid [worn_icon_state ? "worn_icon_state" : "icon_state"], \"[icon_state]\" in '[icon_file]'[match_message]")
if(cached_slot_flags & ITEM_SLOT_GLOVES)
icon_file = 'icons/mob/clothing/hands.dmi'
if(!(icon_state in icon_states(icon_file, 1)))
already_warned_icons += icon_state
TEST_FAIL("[item_path] using invalid [worn_icon_state ? "worn_icon_state" : "icon_state"], \"[icon_state]\" in '[icon_file]'[match_message]")
if(cached_slot_flags & ITEM_SLOT_EYES)
icon_file = 'icons/mob/clothing/eyes.dmi'
if(!(icon_state in icon_states(icon_file, 1)))
already_warned_icons += icon_state
TEST_FAIL("[item_path] using invalid [worn_icon_state ? "worn_icon_state" : "icon_state"], \"[icon_state]\" in '[icon_file]'[match_message]")
if(cached_slot_flags & ITEM_SLOT_EARS)
icon_file = 'icons/mob/clothing/ears.dmi'
if(!(icon_state in icon_states(icon_file, 1)))
already_warned_icons += icon_state
TEST_FAIL("[item_path] using invalid [worn_icon_state ? "worn_icon_state" : "icon_state"], \"[icon_state]\" in '[icon_file]'[match_message]")
if(cached_slot_flags & ITEM_SLOT_NECK)
icon_file = 'icons/mob/clothing/neck.dmi'
if(!(icon_state in icon_states(icon_file, 1)))
already_warned_icons += icon_state
TEST_FAIL("[item_path] using invalid [worn_icon_state ? "worn_icon_state" : "icon_state"], \"[icon_state]\" in '[icon_file]'[match_message]")
if(cached_slot_flags & ITEM_SLOT_MASK)
icon_file = 'icons/mob/clothing/mask.dmi'
if(!(icon_state in icon_states(icon_file, 1)))
already_warned_icons += icon_state
TEST_FAIL("[item_path] using invalid [worn_icon_state ? "worn_icon_state" : "icon_state"], \"[icon_state]\" in '[icon_file]'[match_message]")
if(cached_slot_flags & ITEM_SLOT_BELT)
icon_file = 'icons/mob/clothing/belt.dmi'
if(!(icon_state in icon_states(icon_file, 1)))
already_warned_icons += icon_state
TEST_FAIL("[item_path] using invalid [worn_icon_state ? "worn_icon_state" : "icon_state"], \"[icon_state]\" in '[icon_file]'[match_message]")