Extends atom_reskin to be more modular/adds support for greyscale reskins in the loadout menu (#94466)

Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
This commit is contained in:
Bloop
2026-01-05 09:02:23 +00:00
committed by GitHub
co-authored by SmArtKar
parent 5536a209f9
commit 83cd43da91
27 changed files with 357 additions and 134 deletions
+107 -31
View File
@@ -16,13 +16,76 @@ SUBSYSTEM_DEF(greyscale_previews)
if(!CONFIG_GET(flag/generate_assets_in_init))
return SS_INIT_SUCCESS
#endif
ExportMapPreviews()
return SS_INIT_SUCCESS
/// Sets up the list of types to process for organizing icons into their respective .dmi.
/datum/controller/subsystem/greyscale_previews/proc/build_type_category_map(list/types_that_get_their_own_file)
var/list/type_to_filename = list()
for (var/filename in types_that_get_their_own_file)
var/root = types_that_get_their_own_file[filename]
for (var/atom/atom_type as anything in typesof(root))
// First match wins (prevents /obj from overwriting clothing buckets, etc.)
if (isnull(type_to_filename[atom_type]))
type_to_filename[atom_type] = filename
return type_to_filename
/// Builds a worklist of all the item types to try making a GAGS preview icon for.
/datum/controller/subsystem/greyscale_previews/proc/build_preview_worklists(list/types_that_get_their_own_file)
var/list/type_to_filename = build_type_category_map(types_that_get_their_own_file)
/// filename => list(entries)
var/list/worklists = list()
for (var/filename in types_that_get_their_own_file)
worklists[filename] = list()
worklists["unsorted"] = list()
/// ---- atom skins ----
for (var/skin_path, atom_skin in get_atom_skins())
var/datum/atom_skin/skin = atom_skin
var/atom/typepath = skin.greyscale_item_path
if (!GLOB.all_loadout_datums[typepath]) // We don't need reskin previews for non-loadout items
continue
if (isnull(skin.new_icon_state)) // This is the same as the default icon, which we will be generating below.
continue
if (!typepath::greyscale_config || !typepath::greyscale_colors)
continue
if (typepath::flags_1 & NO_NEW_GAGS_PREVIEW_1)
continue
var/filename = type_to_filename[typepath] || "unsorted"
worklists[filename] += skin
var/list/seen_typepaths = list()
/// ---- base atom types ----
for (var/filename, path in types_that_get_their_own_file)
var/atom/root = path
var/list/filename_worklist = worklists[filename]
for (var/atom/typepath as anything in valid_typesof(root))
if (seen_typepaths[typepath])
continue
seen_typepaths[typepath] = TRUE
if (!typepath::greyscale_config || !typepath::greyscale_colors)
continue
if (typepath::flags_1 & NO_NEW_GAGS_PREVIEW_1)
continue
filename_worklist += typepath
return worklists
/// Goes through all the valid GAGS item types in subtypes that fall under the types specified in types_that_get_their_own_file, creating a .dmi for each.
/datum/controller/subsystem/greyscale_previews/proc/ExportMapPreviews()
// Put subtypes before their parent or the parent file will take all the generated icons
var/static/list/types_that_get_their_own_file = list(
var/list/types_that_get_their_own_file = list(
"turfs" = /turf, // None of these yet but it's harmless to be prepared
"mobs" = /mob, // Ditto
"clothing/accessory" = /obj/item/clothing/accessory,
@@ -45,27 +108,23 @@ SUBSYSTEM_DEF(greyscale_previews)
)
#ifdef UNIT_TESTS
if(!check_map_previews_filepath_order(types_that_get_their_own_file))
if (!check_map_previews_filepath_order(types_that_get_their_own_file))
CRASH("The list 'types_that_get_their_own_file', used by ExportMapPreviews, is invalid. Please ensure that subtypes come BEFORE parent types in the list order.")
#endif
var/list/handled_types = list()
for(var/filename in types_that_get_their_own_file)
var/type_to_export = types_that_get_their_own_file[filename]
handled_types += ExportMapPreviewsForType(filename, type_to_export, handled_types)
ExportMapPreviewsForType("unsorted", /atom, handled_types)
var/list/worklists = build_preview_worklists(types_that_get_their_own_file)
for (var/filename in worklists)
ExportMapPreviewsForType(filename, worklists[filename])
/// Checks that we do not have any parent types coming before subtypes in the types_that_get_their_own_file list (which is an assoc list (filepath, typepath))
/datum/controller/subsystem/greyscale_previews/proc/check_map_previews_filepath_order(list/our_list)
var/list/type_paths_to_check = list()
for(var/filepath in our_list)
for (var/filepath in our_list)
type_paths_to_check += our_list[filepath]
if(!length(type_paths_to_check))
if (!length(type_paths_to_check))
return TRUE
for(var/i = 1 to length(type_paths_to_check))
for (var/i = 1 to length(type_paths_to_check))
var/path_i = type_paths_to_check[i]
for(var/j = i+1 to length(type_paths_to_check))
var/path_j = type_paths_to_check[j]
@@ -74,31 +133,49 @@ SUBSYSTEM_DEF(greyscale_previews)
return FALSE
return TRUE
/datum/controller/subsystem/greyscale_previews/proc/ExportMapPreviewsForType(filename, atom/atom_typepath, list/type_blacklist)
var/list/handled_types = list()
/datum/controller/subsystem/greyscale_previews/proc/ExportMapPreviewsForType(filename, list/entries)
var/list/icons = list()
for(var/atom/atom_type as anything in typesof(atom_typepath))
if(type_blacklist && type_blacklist[atom_type])
for (var/entry in entries)
var/atom/typepath
var/icon_state
var/reskin_icon_state
if (istype(entry, /datum/atom_skin))
var/datum/atom_skin/skin = entry
typepath = skin.greyscale_item_path
icon_state = skin.new_icon_state
reskin_icon_state = TRUE
else
typepath = entry
icon_state = typepath::post_init_icon_state
if (!typepath)
continue
handled_types[atom_type] = TRUE
var/greyscale_config = atom_type::greyscale_config
var/greyscale_colors = atom_type::greyscale_colors
if(!greyscale_config || !greyscale_colors || atom_type::flags_1 & NO_NEW_GAGS_PREVIEW_1)
var/greyscale_config = typepath::greyscale_config
var/greyscale_colors = typepath::greyscale_colors
if (!greyscale_config || !greyscale_colors || (typepath::flags_1 & NO_NEW_GAGS_PREVIEW_1))
continue
// This is what the actual icon state will be in the map_icon .dmi
var/key = reskin_icon_state ? "[typepath]--[icon_state]" : "[typepath]"
#ifdef CHECK_SPRITESHEET_ICON_VALIDITY
var/icon/map_icon = icon(SSgreyscale.GetColoredIconByType(greyscale_config, greyscale_colors))
if((map_icon.Height() > 32) || (map_icon.Width() > 32)) // No large icons, use icon_preview and icon_preview_state instead.
stack_trace("GAGS configuration is trying to generate a map preview graphic for '[atom_type]', which has a large icon. This is not suppoorted; implement icon_preview instead.")
var/icon/map_icon = icon(SSgreyscale.GetColoredIconByType(greyscale_config, greyscale_colors)) // No large icons, use icon_preview and icon_preview_state instead.
if (map_icon.Width() > 32 || map_icon.Height() > 32)
stack_trace("GAGS configuration is trying to generate a map preview graphic for '[typepath]' (icon state: [icon_state]), which has a large icon. This is not suppoorted; implement icon_preview instead.")
continue
if(!(atom_type::post_init_icon_state in map_icon.IconStates()))
stack_trace("GAGS configuration missing icon state needed to generate map preview graphic for '[atom_type]'. Make sure the right greyscale_config is set up.")
if (!(icon_state in map_icon.IconStates()))
stack_trace("GAGS configuration missing icon state ([icon_state]) needed to generate map preview graphic for '[typepath]'. Make sure the right greyscale_config is set up.")
continue
map_icon = icon(map_icon, atom_type::post_init_icon_state)
icons["[atom_type]"] = map_icon
map_icon = icon(map_icon, icon_state)
icons[key] = map_icon
#else // will be updated to use iconforge's new .dmi spritesheet generation instead
var/icon/map_icon = icon(SSgreyscale.GetColoredIconByType(greyscale_config, greyscale_colors))
map_icon = icon(map_icon, atom_type::post_init_icon_state)
icons["[atom_type]"] = map_icon
map_icon = icon(map_icon, icon_state)
icons[map_icon_key] = map_icon
#endif
var/icon/holder = icon('icons/testing/greyscale_error.dmi')
@@ -115,7 +192,6 @@ SUBSYSTEM_DEF(greyscale_previews)
if(old_md5 != new_md5)
stack_trace("Generated map icons were different than what is currently saved. If you see this in a CI run it means you need to run the game once through initialization and commit the resulting files in 'icons/map_icons/'")
#endif
return handled_types
#ifdef CHECK_SPRITESHEET_ICON_VALIDITY
#undef CHECK_SPRITESHEET_ICON_VALIDITY