diff --git a/code/__DEFINES/reskin.dm b/code/__DEFINES/reskin.dm index 250b3959fe2..2647885fb7e 100644 --- a/code/__DEFINES/reskin.dm +++ b/code/__DEFINES/reskin.dm @@ -1,5 +1,10 @@ /// Global list of available atom skins -GLOBAL_LIST_INIT_TYPED(atom_skins, /datum/atom_skin, init_subtypes_w_path_keys(/datum/atom_skin)) +GLOBAL_LIST_EMPTY_TYPED(atom_skins, /datum/atom_skin) + +/proc/get_atom_skins() + if(!length(GLOB.atom_skins)) + GLOB.atom_skins = init_subtypes_w_path_keys(/datum/atom_skin) + return GLOB.atom_skins /// Sets the atom's varname to newvalue if newvalue is not null, otherwise resets it to its initial value if resetcondition is true #define APPLY_VAR_OR_RESET_INITIAL(atom, varname, newvalue, resetcondition) \ diff --git a/code/__HELPERS/abstract_types.dm b/code/__HELPERS/abstract_types.dm index 5a1f800b511..ecf8fb35bb4 100644 --- a/code/__HELPERS/abstract_types.dm +++ b/code/__HELPERS/abstract_types.dm @@ -6,7 +6,8 @@ abstracts = list() for(var/datum/sometype as anything in subtypesof(/datum)) if(sometype == sometype::abstract_type) - abstracts |= sometype::abstract_type + abstracts[sometype::abstract_type] = TRUE + return abstracts /// Like subtypesof, but automatically excludes abstract typepaths @@ -16,3 +17,47 @@ /// Like typesof, but automatically excludes abstract typepaths /proc/valid_typesof(datum/sometype) return typesof(sometype) - get_abstract_types() + +/// Returns a list of concrete types under abstract sub-branches of `root` +/proc/get_abstract_branch_descendants(datum/root) + var/list/abstracts = get_abstract_types() + var/list/to_remove = list() + var/list/seen_abstract_parents = list() + + for (var/datum/sometype as anything in subtypesof(root)) + if (abstracts[sometype]) + continue + + var/datum/parenttype = sometype.parent_type + while (parenttype && parenttype != root) + if (seen_abstract_parents[parenttype] || (abstracts[parenttype])) + seen_abstract_parents[parenttype] = TRUE + to_remove += sometype + break + parenttype = parenttype.parent_type + + return to_remove + +/// Like valid_subtypesof(), but excludes concrete descendants of abstract sub-branches +/proc/valid_direct_subtypesof(datum/root) + var/list/result = subtypesof(root) + + // Remove all abstract types + result -= get_abstract_types() + + // Remove concrete types under abstract sub-branches + result -= get_abstract_branch_descendants(root) + + return result + +/// Like valid_typesof(), but excludes concrete descendants of abstract sub-branches +/proc/valid_direct_typesof(datum/root) + var/list/result = typesof(root) + + // Remove all abstract types + result -= get_abstract_types() + + // Remove concrete types under abstract sub-branches + result -= get_abstract_branch_descendants(root) + + return result diff --git a/code/controllers/subsystem/greyscale_previews.dm b/code/controllers/subsystem/greyscale_previews.dm index e5b108b1fd4..d74f16ff7a3 100644 --- a/code/controllers/subsystem/greyscale_previews.dm +++ b/code/controllers/subsystem/greyscale_previews.dm @@ -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 diff --git a/code/datums/components/reskinnable_atom.dm b/code/datums/components/reskinnable_atom.dm index 84566458593..5f4847eafcb 100644 --- a/code/datums/components/reskinnable_atom.dm +++ b/code/datums/components/reskinnable_atom.dm @@ -24,6 +24,44 @@ var/new_icon /// Optional, icon_state to change the atom to when applied var/new_icon_state + /// Specifies the icon state for the atom's appearance in hand. Should appear in both new_lefthand_file and new_righthand_file. + var/new_inhand_icon_state + /// Optional, specifies the left hand inhand icon file. Don't forget to set the right hand file as well. + var/new_lefthand_file + /// Optional, specifies the right hand inhand icon file. Don't forget to set the left hand file as well. + var/new_righthand_file + /// Optional, specifies the worn icon file. + var/new_worn_icon + /// When set true, will allow concrete subtypes of abstract subtypes (such as for organizational purposes) to be selectable as reskins in the loadout menu. + var/allow_all_subtypes_in_loadout + /// Mandatory for GAGs items. The path to the greyscale item this is to be applied to. + var/atom/greyscale_item_path + /// Auto populated. The greyscale_config from greyscale_item_path. + VAR_FINAL/greyscale_config + /// Auto populated. The greyscale_colors from greyscale_item_path. + VAR_FINAL/greyscale_colors + +/datum/atom_skin/New() + . = ..() + if(isnull(greyscale_item_path)) + return + + // Populate the fields required for GAGS previews + greyscale_config = greyscale_item_path::greyscale_config + greyscale_colors = greyscale_item_path::greyscale_colors + + // The icon isn't 'new'. So we don't make an extra icon in map_icons. + if(greyscale_item_path::post_init_icon_state == new_icon_state) + new_icon_state = null + +/// Returns the correct preview icon state for this atom skin, whether it be a map_preview or a normal icon. This is assumed from our var population above. +/datum/atom_skin/proc/get_preview_icon_state() + if(isnull(greyscale_item_path)) // Not a GAGs icon + return new_icon_state + if(isnull(new_icon_state)) // The base GAGS item icon + return "[greyscale_item_path]" + + return "[greyscale_item_path]--[new_icon_state]" // A GAGs reskin of the item icon /** * Applies all relevant skin changes to the given atom @@ -31,17 +69,26 @@ * * * apply_to: The atom to apply the skin to */ -/datum/atom_skin/proc/apply(atom/apply_to) +/datum/atom_skin/proc/apply(atom/apply_to, mob/user) SHOULD_CALL_PARENT(TRUE) - APPLY_VAR_OR_RESET_INITIAL(apply_to, name, new_name, reset_missing) - APPLY_VAR_OR_RESET_INITIAL(apply_to, desc, new_desc, reset_missing) - APPLY_VAR_OR_RESET_INITIAL(apply_to, icon, new_icon, reset_missing) + if(!HAS_TRAIT(apply_to, TRAIT_WAS_RENAMED)) + APPLY_VAR_OR_RESET_INITIAL(apply_to, name, new_name, reset_missing) + APPLY_VAR_OR_RESET_INITIAL(apply_to, desc, new_desc, reset_missing) + if(!apply_to.greyscale_config) // Never want to be resetting a GAGS icon back to initial. + APPLY_VAR_OR_RESET_INITIAL(apply_to, icon, new_icon, reset_missing) APPLY_VAR_OR_RESET_TO(apply_to, icon_state, new_icon_state, reset_missing, initial(apply_to.post_init_icon_state) || initial(apply_to.icon_state)) if(change_base_icon_state) APPLY_VAR_OR_RESET_INITIAL(apply_to, base_icon_state, new_icon_state, reset_missing) - if(change_inhand_icon_state && isitem(apply_to)) + if(isitem(apply_to)) var/obj/item/item_apply_to = apply_to - APPLY_VAR_OR_RESET_INITIAL(item_apply_to, inhand_icon_state, new_icon_state, reset_missing) + if(!item_apply_to.greyscale_config_worn) + APPLY_VAR_OR_RESET_INITIAL(item_apply_to, worn_icon, new_worn_icon, reset_missing) + if(!item_apply_to.greyscale_config_inhand_left) + APPLY_VAR_OR_RESET_INITIAL(item_apply_to, lefthand_file, new_lefthand_file, reset_missing) + APPLY_VAR_OR_RESET_INITIAL(item_apply_to, righthand_file, new_righthand_file, reset_missing) + APPLY_VAR_OR_RESET_INITIAL(item_apply_to, worn_icon_state, new_icon_state, reset_missing) + if(change_inhand_icon_state || new_inhand_icon_state) + APPLY_VAR_OR_RESET_INITIAL(item_apply_to, inhand_icon_state, new_inhand_icon_state || new_icon_state, reset_missing) /** * Resets all changes this skin would have made to the given atom @@ -54,13 +101,20 @@ SHOULD_CALL_PARENT(TRUE) RESET_INITIAL_IF_SET(clear_from, name, new_name) RESET_INITIAL_IF_SET(clear_from, desc, new_desc) - RESET_INITIAL_IF_SET(clear_from, icon, new_icon) + var/is_greyscale = clear_from.greyscale_config || clear_from.greyscale_colors + if(!is_greyscale) + RESET_INITIAL_IF_SET(clear_from, icon, new_icon) RESET_TO_IF_SET(clear_from, icon_state, new_icon_state, initial(clear_from.post_init_icon_state) || initial(clear_from.icon_state)) if(change_base_icon_state) RESET_INITIAL_IF_SET(clear_from, base_icon_state, new_icon_state) - if(change_inhand_icon_state && isitem(clear_from)) + if(isitem(clear_from)) var/obj/item/item_clear_from = clear_from - RESET_INITIAL_IF_SET(item_clear_from, inhand_icon_state, new_icon_state) + RESET_INITIAL_IF_SET(item_clear_from, worn_icon, new_worn_icon) + RESET_INITIAL_IF_SET(item_clear_from, inhand_icon_state, change_inhand_icon_state ? new_inhand_icon_state : new_icon_state) + if(!is_greyscale) + RESET_INITIAL_IF_SET(item_clear_from, worn_icon_state, new_icon_state) + RESET_INITIAL_IF_SET(item_clear_from, lefthand_file, new_lefthand_file) + RESET_INITIAL_IF_SET(item_clear_from, righthand_file, new_righthand_file) /// Gets a preview image for this skin based on the given atom's icon and icon_state /datum/atom_skin/proc/get_preview_icon(atom/for_atom) @@ -82,14 +136,24 @@ VAR_PRIVATE/infinite_reskin = FALSE /// List of subtypes of /datum/atom_skin that are not allowed to be used for this item VAR_PRIVATE/list/blacklisted_subtypes - /// Currently applied skin preview_name VAR_PRIVATE/current_skin -/datum/component/reskinable_item/Initialize(base_reskin_type, infinite = FALSE, initial_skin, list/blacklisted_subtypes = list()) +/datum/component/reskinable_item/Initialize(base_reskin_type, infinite = FALSE, initial_skin, list/blacklisted_subtypes) if(!isatom(parent) || isarea(parent)) return COMPONENT_INCOMPATIBLE + var/atom/atom_parent = parent +#ifdef UNIT_TESTS + if(atom_parent.greyscale_config && (atom_parent.type in GLOB.all_loadout_datums)) // We only care about these when they're in the loadout + var/datum/atom_skin/reskin_type = base_reskin_type + if(reskin_type && isnull(reskin_type::greyscale_item_path)) // greyscale_item_path unset + stack_trace("[type] added to a greyscale item without setting the greyscale_item_path! In [reskin_type], add 'greyscale_item_path = [atom_parent::type]'.") + else if(atom_parent.type != reskin_type::greyscale_item_path) // greyscale_item_path set but doesn't match the item it's being added to + stack_trace("[type] added to an invalid item type, [atom_parent.type]. [reskin_type] is set up to only be added to: [reskin_type::greyscale_item_path]. \ + Either fix its greyscale_item_path if this is incorrect, or apply a different skin.") +#endif + src.base_reskin_type = base_reskin_type src.infinite_reskin = infinite src.blacklisted_subtypes = blacklisted_subtypes @@ -97,7 +161,10 @@ if(initial_skin) set_skin_by_name(initial_skin) - var/atom/atom_parent = parent + var/list/reskin_options = get_skins_by_name() + if(length(reskin_options) <= 1) // Check that we actually have reskin options - if not there's no point to existing past this point. + return COMPONENT_REDUNDANT + atom_parent.flags_1 |= HAS_CONTEXTUAL_SCREENTIPS_1 /datum/component/reskinable_item/RegisterWithParent() @@ -110,15 +177,20 @@ UnregisterSignal(parent, COMSIG_ATOM_EXAMINE_TAGS) UnregisterSignal(parent, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM) -/datum/component/reskinable_item/CheckDupeComponent(datum/component/comp, base_reskin_type, infinite = FALSE, initial_skin, list/blacklisted_subtypes = list()) - if(src.base_reskin_type != base_reskin_type) - return FALSE // new comp - though the alt-click behavior will collide - +/datum/component/reskinable_item/CheckDupeComponent(datum/component/comp, base_reskin_type, infinite = FALSE, initial_skin, list/blacklisted_subtypes) + // Always absorb added components + src.base_reskin_type = base_reskin_type src.infinite_reskin = infinite src.blacklisted_subtypes = blacklisted_subtypes - set_skin_by_name(initial_skin) - return TRUE // same comp + if(initial_skin) + set_skin_by_name(initial_skin) + + var/list/reskin_options = get_skins_by_name() + if(length(reskin_options) <= 1) // Check that we actually have reskin options - if not there's no point to existing past this point. + qdel(src) + + return TRUE /datum/component/reskinable_item/proc/get_skins_by_name() var/list/reskin_options = list() @@ -127,15 +199,16 @@ return reskin_options -/datum/component/reskinable_item/proc/set_skin_by_name(input_name) +/datum/component/reskinable_item/proc/set_skin_by_name(input_name, mob/user) var/list/reskin_options = get_skins_by_name() + var/list/atom_skins = get_atom_skins() if(current_skin) - var/datum/atom_skin/previous_skin = GLOB.atom_skins[reskin_options[current_skin]] - previous_skin.clear_skin(parent) + var/datum/atom_skin/previous_skin = atom_skins[reskin_options[current_skin]] + previous_skin.clear_skin(parent, user) if(input_name) - var/datum/atom_skin/reskin_to_apply = GLOB.atom_skins[reskin_options[input_name]] - reskin_to_apply.apply(parent) + var/datum/atom_skin/reskin_to_apply = atom_skins[reskin_options[input_name]] + reskin_to_apply.apply(parent, user) current_skin = input_name @@ -179,8 +252,10 @@ var/atom/atom_parent = parent var/list/items = list() + var/list/atom_skins = get_atom_skins() for(var/reskin_name, reskin_typepath in get_skins_by_name()) - items[reskin_name] = GLOB.atom_skins[reskin_typepath].get_preview_icon(atom_parent) + var/datum/atom_skin/reskin = atom_skins[reskin_typepath] + items[reskin_name] = image(icon = reskin.new_icon || atom_parent.icon, icon_state = reskin.new_icon_state || atom_parent.icon_state) sort_list(items) @@ -188,7 +263,7 @@ if(!pick || !items[pick]) return - set_skin_by_name(pick) + set_skin_by_name(pick, user) to_chat(user, span_info("[parent] is now skinned as '[pick].'")) if(!infinite_reskin) diff --git a/code/datums/martial/kaza_ruk.dm b/code/datums/martial/kaza_ruk.dm index 1f7b0625dc0..1d1fc00c445 100644 --- a/code/datums/martial/kaza_ruk.dm +++ b/code/datums/martial/kaza_ruk.dm @@ -311,8 +311,7 @@ max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT resistance_flags = NONE -/obj/item/clothing/gloves/kaza_ruk/sec/Initialize(mapload) - . = ..() +/obj/item/clothing/gloves/kaza_ruk/sec/setup_reskins() AddComponent(/datum/component/reskinable_item, /datum/atom_skin/kaza_ruk, infinite = TRUE) /obj/item/clothing/gloves/kaza_ruk/combatglovesplus diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm index a015ea68961..f8f34533fd6 100644 --- a/code/game/atom/_atom.dm +++ b/code/game/atom/_atom.dm @@ -977,3 +977,7 @@ if(pass_info.pass_flags & pass_flags_self) return TRUE . = !density + +/// Logic for adding reskin components goes here. Override for atom-specific reskin setups. +/atom/proc/setup_reskins() + return diff --git a/code/game/atom/atoms_initializing_EXPENSIVE.dm b/code/game/atom/atoms_initializing_EXPENSIVE.dm index f39b987990d..2d510a13fd5 100644 --- a/code/game/atom/atoms_initializing_EXPENSIVE.dm +++ b/code/game/atom/atoms_initializing_EXPENSIVE.dm @@ -154,6 +154,8 @@ if(ispath(ai_controller)) ai_controller = new ai_controller(src) + setup_reskins() + return INITIALIZE_HINT_NORMAL /** diff --git a/code/game/objects/items/floppy_disk.dm b/code/game/objects/items/floppy_disk.dm index 027cfa41cb5..41d22e0fa7a 100644 --- a/code/game/objects/items/floppy_disk.dm +++ b/code/game/objects/items/floppy_disk.dm @@ -83,15 +83,14 @@ var/sticker_icon_state = STARTING_STICKER /// Custom description var/custom_description - /// Whether this disk can be reskinned - var/reskin_allowed = TRUE /obj/item/disk/Initialize(mapload) . = ..() - if(reskin_allowed) - AddComponent(/datum/component/reskinable_item, /datum/atom_skin/floppy_disk, infinite = FALSE) update_appearance(UPDATE_OVERLAYS) +/obj/item/disk/setup_reskins() + AddComponent(/datum/component/reskinable_item, /datum/atom_skin/floppy_disk, infinite = TRUE) + /obj/item/disk/update_overlays() . = ..() if(sticker_icon_state) diff --git a/code/game/objects/items/melee/baton.dm b/code/game/objects/items/melee/baton.dm index 7d9c66ce2f0..5f1c330ca4e 100644 --- a/code/game/objects/items/melee/baton.dm +++ b/code/game/objects/items/melee/baton.dm @@ -794,8 +794,7 @@ obj_flags = UNIQUE_RENAME -/obj/item/melee/baton/security/stunsword/Initialize(mapload) - . = ..() +/obj/item/melee/baton/security/stunsword/setup_reskins() AddComponent(/datum/component/reskinable_item, /datum/atom_skin/stunsword) /obj/item/melee/baton/security/stunsword/add_deep_lore() diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm b/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm index 24314af0b46..7e9aa464142 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm @@ -9,7 +9,6 @@ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF read_only = TRUE read_only_locked = TRUE - reskin_allowed = FALSE /// Whether we're a real nuke disk or not. var/fake = FALSE @@ -30,6 +29,9 @@ // Ensure fake disks still have examine text, but dont actually do anything AddComponent(/datum/component/keep_me_secure) +/obj/item/disk/nuclear/setup_reskins() + return + /obj/item/disk/nuclear/proc/secured_process(last_move) var/turf/new_turf = get_turf(src) var/datum/round_event_control/operative/loneop = locate(/datum/round_event_control/operative) in SSevents.control diff --git a/code/modules/bitrunning/objects/disks.dm b/code/modules/bitrunning/objects/disks.dm index 3b1cd5de782..fbbca9ab8e8 100644 --- a/code/modules/bitrunning/objects/disks.dm +++ b/code/modules/bitrunning/objects/disks.dm @@ -10,7 +10,6 @@ base_icon_state = "datadisk" icon_state = "datadisk0" sticker_icon_state = "o_code" - reskin_allowed = FALSE /// Name of the choice made var/choice_made @@ -24,6 +23,9 @@ load_callback = CALLBACK(src, PROC_REF(load_onto_avatar)), \ ) +/obj/item/disk/bitrunning/setup_reskins() + return + /obj/item/disk/bitrunning/examine(mob/user) . = ..() . += span_infoplain("This disk must be carried on your person into a netpod to be used.") diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index 6ecc77d4ebf..db565991cee 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -796,4 +796,6 @@ desc = "This disk provides a firmware update to the Express Supply Console, granting the use of Nanotrasen's Bluespace Drop Pods to the supply department." icon_state = "datadisk12" sticker_icon_state = "o_cargopod" - reskin_allowed = FALSE + +/obj/item/disk/cargo/bluespace_pod/setup_reskins() + return diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm index f37cd9838f9..bcaef35e5d6 100644 --- a/code/modules/clothing/glasses/_glasses.dm +++ b/code/modules/clothing/glasses/_glasses.dm @@ -35,7 +35,7 @@ /obj/item/clothing/glasses/Initialize(mapload) . = ..() if(glass_colour_type) - AddElement(/datum/element/wearable_client_colour, glass_colour_type, ITEM_SLOT_EYES, GLASSES_TRAIT, forced = forced_glass_color) + AddElement(/datum/element/wearable_client_colour, glass_colour_type, ITEM_SLOT_EYES, GLASSES_TRAIT, forced = forced_glass_color, comsig_toggle = COMSIG_CLICK_ALT_SECONDARY) /obj/item/clothing/glasses/suicide_act(mob/living/carbon/user) user.visible_message(span_suicide("[user] is stabbing \the [src] into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!")) diff --git a/code/modules/clothing/glasses/engine_goggles.dm b/code/modules/clothing/glasses/engine_goggles.dm index 84173608562..e9aba450395 100644 --- a/code/modules/clothing/glasses/engine_goggles.dm +++ b/code/modules/clothing/glasses/engine_goggles.dm @@ -167,7 +167,6 @@ desc = "Used to see the boundaries of shuttle regions." modes = list(MODE_NONE, MODE_SHUTTLE) - /obj/item/clothing/glasses/meson/engine/atmos_imaging name = "atmospheric thermal imaging goggles" desc = "Goggles used by Atmospheric Technicians to see the thermal energy of gasses in open areas." diff --git a/code/modules/clothing/masks/costume.dm b/code/modules/clothing/masks/costume.dm index 27d17af36e0..008af69d0ac 100644 --- a/code/modules/clothing/masks/costume.dm +++ b/code/modules/clothing/masks/costume.dm @@ -31,8 +31,7 @@ clothing_flags = MASKINTERNALS flags_inv = HIDESNOUT -/obj/item/clothing/mask/joy/Initialize(mapload) - . = ..() +/obj/item/clothing/mask/joy/setup_reskins() AddComponent(/datum/component/reskinable_item, /datum/atom_skin/joy_mask, infinite = TRUE) /obj/item/clothing/mask/mummy diff --git a/code/modules/clothing/under/accessories/badges.dm b/code/modules/clothing/under/accessories/badges.dm index 9f871c71015..96495998567 100644 --- a/code/modules/clothing/under/accessories/badges.dm +++ b/code/modules/clothing/under/accessories/badges.dm @@ -219,8 +219,7 @@ icon_state = "pride" obj_flags = UNIQUE_RENAME -/obj/item/clothing/accessory/pride/Initialize(mapload) - . = ..() +/obj/item/clothing/accessory/pride/setup_reskins() AddComponent(/datum/component/reskinable_item, /datum/atom_skin/pride_pin, infinite = TRUE) /obj/item/clothing/accessory/deaf_pin diff --git a/code/modules/clothing/under/costume.dm b/code/modules/clothing/under/costume.dm index a3bd9c01f4e..d61e622f36f 100644 --- a/code/modules/clothing/under/costume.dm +++ b/code/modules/clothing/under/costume.dm @@ -306,8 +306,7 @@ alternate_worn_layer = GLOVES_LAYER //covers hands but gloves can go over it. This is how these things work in my head. can_adjust = FALSE -/obj/item/clothing/under/costume/mech_suit/Initialize(mapload) - . = ..() +/obj/item/clothing/under/costume/mech_suit/setup_reskins() AddComponent(/datum/component/reskinable_item, /datum/atom_skin/mech_suit) /obj/item/clothing/under/costume/russian_officer diff --git a/code/modules/loadout/loadout_items.dm b/code/modules/loadout/loadout_items.dm index 673950d31aa..defb470356c 100644 --- a/code/modules/loadout/loadout_items.dm +++ b/code/modules/loadout/loadout_items.dm @@ -54,7 +54,7 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories()) var/ui_icon_state /// Base typepath to what reskin datum this item can use to reskin into /// Doesn't verify that the item_path actually has these reskins - var/reskin_datum + var/datum/atom_skin/reskin_datum /// A list of greyscale colors that are used for items that have greyscale support, but don't allow full customization. /// This is an assoc list of /datum/job_department -> colors, or /datum/job -> colors, allowing for preset colors based on player chosen job. /// Jobs are prioritized over departments. @@ -276,10 +276,13 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories()) if(reskin_datum && item_details?[INFO_RESKIN]) var/skin_chosen = item_details[INFO_RESKIN] + var/list/atom_skins = get_atom_skins() for(var/datum/atom_skin/skin_path as anything in valid_subtypesof(reskin_datum)) if(skin_path::preview_name != skin_chosen) continue - var/datum/atom_skin/skin_instance = GLOB.atom_skins[skin_path] + if(skin_path::preview_name != skin_chosen) + continue + var/datum/atom_skin/skin_instance = atom_skins[skin_path] skin_instance.apply(equipped_item) if(istype(equipped_item, /obj/item/clothing/accessory)) // Snowflake handing for accessories, because we need to update the thing it's attached to instead @@ -394,13 +397,20 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories()) return null var/list/reskins = list() + var/list/atom_skins = get_atom_skins() + var/list/reskin_choices + if(reskin_datum::allow_all_subtypes_in_loadout) + reskin_choices = valid_subtypesof(reskin_datum) + else + reskin_choices = valid_direct_subtypesof(reskin_datum) - for(var/datum/atom_skin/skin as anything in valid_subtypesof(reskin_datum)) + for(var/datum/atom_skin/skin_path as anything in reskin_choices) + var/datum/atom_skin/atom_skin = atom_skins[skin_path] UNTYPED_LIST_ADD(reskins, list( - "name" = skin::new_name || skin::preview_name, - "tooltip" = skin::preview_name, - "skin_icon" = skin::new_icon, - "skin_icon_state" = skin::new_icon_state, + "name" = skin_path::new_name || skin_path::preview_name, + "tooltip" = skin_path::preview_name, + "skin_icon" = skin_path::new_icon, + "skin_icon_state" = atom_skin?.get_preview_icon_state() || skin_path::new_icon )) return reskins diff --git a/code/modules/mining/equipment/kinetic_crusher/trophies_misc.dm b/code/modules/mining/equipment/kinetic_crusher/trophies_misc.dm index dca037e37cd..e3aa4c9c8dd 100644 --- a/code/modules/mining/equipment/kinetic_crusher/trophies_misc.dm +++ b/code/modules/mining/equipment/kinetic_crusher/trophies_misc.dm @@ -4,18 +4,11 @@ change_base_icon_state = TRUE new_icon = 'icons/obj/mining.dmi' new_icon_state = "ipickaxe" - /// Specifies the icon state for the crusher's appearance in hand. Should appear in both new_lefthand_file and new_righthand_file. - var/new_inhand_icon = "ipickaxe" + new_inhand_icon_state = "ipickaxe" /// Specifies the icon file in which the crusher's projectile sprite is located. var/new_projectile_icon = 'icons/obj/weapons/guns/projectiles.dmi' /// For if the retool kit changes the projectile's appearance. var/new_projectile_icon_state - /// Specifies the left hand inhand icon file. Don't forget to set the right hand file as well. - var/new_lefthand_file - /// Specifies the right hand inhand icon file. Don't forget to set the left hand file as well. - var/new_righthand_file - /// Specifies the worn icon file. - var/new_worn_file /// Specifies the X dimensions of the new inhand, only relevant with different inhand files. var/new_inhandx /// Specifies the Y dimensions of the new inhand, only relevant with different inhand files. @@ -23,25 +16,15 @@ /datum/atom_skin/crusher_skin/apply(obj/item/kinetic_crusher/apply_to) . = ..() - APPLY_VAR_OR_RESET_INITIAL(apply_to, inhand_icon_state, new_inhand_icon, reset_missing) APPLY_VAR_OR_RESET_INITIAL(apply_to, projectile_icon, new_projectile_icon, reset_missing) APPLY_VAR_OR_RESET_INITIAL(apply_to, projectile_icon_state, new_projectile_icon_state, reset_missing) - APPLY_VAR_OR_RESET_INITIAL(apply_to, lefthand_file, new_lefthand_file, reset_missing) - APPLY_VAR_OR_RESET_INITIAL(apply_to, righthand_file, new_righthand_file, reset_missing) - APPLY_VAR_OR_RESET_INITIAL(apply_to, worn_icon, new_worn_file, reset_missing) - APPLY_VAR_OR_RESET_INITIAL(apply_to, worn_icon_state, new_icon_state, reset_missing) APPLY_VAR_OR_RESET_INITIAL(apply_to, inhand_x_dimension, new_inhandx, reset_missing) APPLY_VAR_OR_RESET_INITIAL(apply_to, inhand_y_dimension, new_inhandy, reset_missing) /datum/atom_skin/crusher_skin/clear_skin(obj/item/kinetic_crusher/clear_from) . = ..() - RESET_INITIAL_IF_SET(clear_from, inhand_icon_state, new_inhand_icon) RESET_INITIAL_IF_SET(clear_from, projectile_icon, new_projectile_icon) RESET_INITIAL_IF_SET(clear_from, projectile_icon_state, new_projectile_icon_state) - RESET_INITIAL_IF_SET(clear_from, lefthand_file, new_lefthand_file) - RESET_INITIAL_IF_SET(clear_from, righthand_file, new_righthand_file) - RESET_INITIAL_IF_SET(clear_from, worn_icon, new_worn_file) - RESET_INITIAL_IF_SET(clear_from, worn_icon_state, new_icon_state) RESET_INITIAL_IF_SET(clear_from, inhand_x_dimension, new_inhandx) RESET_INITIAL_IF_SET(clear_from, inhand_y_dimension, new_inhandy) @@ -49,20 +32,20 @@ new_name = "proto-kinetic sword" preview_name = "Sword" new_icon_state = "crusher_sword" - new_inhand_icon = "crusher_sword" + new_inhand_icon_state = "crusher_sword" /datum/atom_skin/crusher_skin/harpoon new_name = "proto-kinetic harpoon" preview_name = "Harpoon" new_icon_state = "crusher_harpoon" - new_inhand_icon = "crusher_harpoon" + new_inhand_icon_state = "crusher_harpoon" new_projectile_icon_state = "pulse_harpoon" -/datum/atom_skin/crusher_skin/harpoon/apply(atom/apply_to) +/datum/atom_skin/crusher_skin/harpoon/apply(atom/apply_to, mob/user) . = ..() RegisterSignal(apply_to, COMSIG_ITEM_ATTACK_ANIMATION, PROC_REF(on_attack_animation)) -/datum/atom_skin/crusher_skin/harpoon/clear_skin(atom/clear_from) +/datum/atom_skin/crusher_skin/harpoon/clear_skin(atom/clear_from, mob/user) . = ..() UnregisterSignal(clear_from, COMSIG_ITEM_ATTACK_ANIMATION) @@ -77,14 +60,14 @@ new_name = "proto-kinetic dual dagger and blaster" preview_name = "Dagger and Blaster" new_icon_state = "crusher_dagger" - new_inhand_icon = "crusher_dagger" + new_inhand_icon_state = "crusher_dagger" -/datum/atom_skin/crusher_skin/dagger/apply(atom/apply_to) +/datum/atom_skin/crusher_skin/dagger/apply(atom/apply_to, mob/user) . = ..() RegisterSignal(apply_to, COMSIG_ITEM_ATTACK_ANIMATION, PROC_REF(on_attack_animation)) RegisterSignal(apply_to, COMSIG_CRUSHER_FIRED_BLAST, PROC_REF(on_fired_blast)) -/datum/atom_skin/crusher_skin/dagger/clear_skin(atom/clear_from) +/datum/atom_skin/crusher_skin/dagger/clear_skin(atom/clear_from, mob/user) . = ..() UnregisterSignal(clear_from, COMSIG_ITEM_ATTACK_ANIMATION) UnregisterSignal(clear_from, COMSIG_CRUSHER_FIRED_BLAST) @@ -120,7 +103,7 @@ new_name = "proto-kinetic glaive" preview_name = "Glaive" new_icon_state = "crusher_glaive" - new_inhand_icon = "crusher_glaive" + new_inhand_icon_state = "crusher_glaive" new_lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' new_righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' new_inhandx = 64 @@ -133,7 +116,7 @@ /datum/atom_skin/crusher_skin/locked/ashen_skull preview_name = "Skull" new_icon_state = "crusher_skull" - new_inhand_icon = "crusher_skull" + new_inhand_icon_state = "crusher_skull" new_projectile_icon_state = "pulse_skull" /// Unlockable (or forced) skins diff --git a/code/modules/paperwork/clipboard.dm b/code/modules/paperwork/clipboard.dm index a556d90dd2b..d8291644134 100644 --- a/code/modules/paperwork/clipboard.dm +++ b/code/modules/paperwork/clipboard.dm @@ -51,6 +51,8 @@ /obj/item/clipboard/Initialize(mapload) update_appearance() . = ..() + +/obj/item/clipboard/setup_reskins() AddComponent(/datum/component/reskinable_item, /datum/atom_skin/clipboard) /obj/item/clipboard/Destroy() diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm index 54866f7a316..deae5f862d2 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -185,11 +185,12 @@ /datum/atom_skin/cap_pen abstract_type = /datum/atom_skin/cap_pen -/datum/atom_skin/cap_pen/apply(atom/apply_to) +/datum/atom_skin/cap_pen/apply(atom/apply_to, mob/user) . = ..() apply_to.desc = "It's an expensive [preview_name] fountain pen. The nib is quite sharp." + apply_to.update_desc() -/datum/atom_skin/cap_pen/clear_skin(atom/clear_from) +/datum/atom_skin/cap_pen/clear_skin(atom/clear_from, mob/user) . = ..() clear_from.desc = initial(clear_from.desc) @@ -244,10 +245,12 @@ speed = 20 SECONDS, \ effectiveness = 115, \ ) - AddComponent(/datum/component/reskinable_item, /datum/atom_skin/cap_pen) //the pen is mightier than the sword RegisterSignal(src, COMSIG_DART_INSERT_PARENT_RESKINNED, PROC_REF(reskin_dart_insert)) +/obj/item/pen/fountain/captain/setup_reskins() + AddComponent(/datum/component/reskinable_item, /datum/atom_skin/cap_pen) + /obj/item/pen/fountain/captain/proc/reskin_dart_insert(datum/component/dart_insert/insert_comp, skin) SIGNAL_HANDLER if(!istype(insert_comp)) //You really shouldn't be sending this signal from anything other than a dart_insert component diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm index 6f4b9be9330..ac7abb4c024 100644 --- a/code/modules/projectiles/guns/ballistic/revolver.dm +++ b/code/modules/projectiles/guns/ballistic/revolver.dm @@ -167,8 +167,7 @@ obj_flags = UNIQUE_RENAME -/obj/item/gun/ballistic/revolver/c38/detective/Initialize(mapload) - . = ..() +/obj/item/gun/ballistic/revolver/c38/detective/setup_reskins() AddComponent(/datum/component/reskinable_item, /datum/atom_skin/det_revolver) /obj/item/gun/ballistic/revolver/badass diff --git a/code/modules/projectiles/guns/ballistic/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm index 124284f3c99..0208a6a064d 100644 --- a/code/modules/projectiles/guns/ballistic/shotgun.dm +++ b/code/modules/projectiles/guns/ballistic/shotgun.dm @@ -338,8 +338,7 @@ can_be_sawn_off = TRUE pb_knockback = 3 // it's a super shotgun! -/obj/item/gun/ballistic/shotgun/doublebarrel/Initialize(mapload) - . = ..() +/obj/item/gun/ballistic/shotgun/doublebarrel/setup_reskins() AddComponent(/datum/component/reskinable_item, /datum/atom_skin/bar_shotgun) /obj/item/gun/ballistic/shotgun/doublebarrel/sawoff(mob/user) diff --git a/code/modules/reagents/reagent_containers/medigel.dm b/code/modules/reagents/reagent_containers/medigel.dm index d88a427277c..12ee08f0d30 100644 --- a/code/modules/reagents/reagent_containers/medigel.dm +++ b/code/modules/reagents/reagent_containers/medigel.dm @@ -52,8 +52,7 @@ var/self_delay = 30 custom_price = PAYCHECK_CREW * 2 -/obj/item/reagent_containers/medigel/Initialize(mapload) - . = ..() +/obj/item/reagent_containers/medigel/setup_reskins() if(icon_state == "medigel") // oh yeah baby raw icon state check to make sure we can't reskin preset gels AddComponent(/datum/component/reskinable_item, /datum/atom_skin/med_gel) diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm index 04d71ee8559..2ab855a0e23 100644 --- a/code/modules/reagents/reagent_containers/spray.dm +++ b/code/modules/reagents/reagent_containers/spray.dm @@ -435,8 +435,7 @@ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' volume = 100 -/obj/item/reagent_containers/spray/medical/Initialize(mapload, vol) - . = ..() +/obj/item/reagent_containers/spray/medical/setup_reskins() AddComponent(/datum/component/reskinable_item, /datum/atom_skin/med_spray) /obj/item/reagent_containers/spray/hercuri diff --git a/code/modules/unit_tests/reskin_validation.dm b/code/modules/unit_tests/reskin_validation.dm index 85e454058ce..8817c7ba4b5 100644 --- a/code/modules/unit_tests/reskin_validation.dm +++ b/code/modules/unit_tests/reskin_validation.dm @@ -2,14 +2,37 @@ /datum/unit_test/reskin_validation/Run() var/list/known_names = list() - for(var/datum/atom_skin/skin as anything in valid_subtypesof(/datum/atom_skin)) + for(var/atom_skin_path, atom_skin in get_atom_skins()) + var/datum/atom_skin/skin = atom_skin + if(isnull(skin::preview_name)) TEST_FAIL("Reskin [skin] is missing a preview_name.") // preview names are bundled by abstract types - else if(known_names["[skin::preview_name]-[skin::abstract_type]"]) - TEST_FAIL("Reskin [skin] has a duplicate preview_name [skin::preview_name].") + else if(known_names["[skin.preview_name]-[skin.abstract_type]"]) + TEST_FAIL("Reskin [skin] has a duplicate preview_name [skin.preview_name].") else - known_names["[skin::preview_name]-[skin::abstract_type]"] = TRUE + known_names["[skin.preview_name]-[skin.abstract_type]"] = TRUE - if(skin::new_icon && skin::new_icon_state && !icon_exists(skin::new_icon, skin::new_icon_state)) - TEST_FAIL("Reskin [skin] has a new_icon_state [skin::new_icon_state] that does not exist in file [skin::new_icon].") + if(skin.new_icon && skin.new_icon_state && !icon_exists(skin.new_icon, skin.new_icon_state)) + TEST_FAIL("Reskin [skin] has a new_icon_state [skin.new_icon_state] that does not exist in file [skin.new_icon].") + + var/atom/greyscale_item_path = skin.greyscale_item_path + var/datum/greyscale_config/greyscale_config = skin.greyscale_config + var/greyscale_colors = skin.greyscale_colors + var/field_count = 0 + if(greyscale_item_path) // If any of these are set, they should all be set + field_count++ + if(greyscale_config) + field_count++ + if(greyscale_colors) + field_count++ + if(!field_count) + continue + if(field_count != 3) + TEST_FAIL("[skin]: incomplete greyscale definition: item_path: [greyscale_item_path], \ + config: [greyscale_config], \ + colors: [greyscale_colors]") + + if(isnull(greyscale_item_path::greyscale_config) || isnull(greyscale_item_path::greyscale_colors)) + TEST_FAIL("[skin]: greyscale_item_path is set to an item that does not have a greyscale config or greyscale_colors set! \ + Either set those up for the item, or set all the greyscale fields on the skin to null.") diff --git a/tgui/packages/tgui/interfaces/SpawnSearch.tsx b/tgui/packages/tgui/interfaces/SpawnSearch.tsx index 0f33d11448f..a4334e8a431 100644 --- a/tgui/packages/tgui/interfaces/SpawnSearch.tsx +++ b/tgui/packages/tgui/interfaces/SpawnSearch.tsx @@ -35,13 +35,13 @@ type SpawnSearchData = { type SpawnAtomData = { // Type -> Name types: Record; - abstractTypes: Array; + abstractTypes: Record fancyTypes: Record; }; type AtomPathData = { types: Array; - abstractTypes: Array; + abstractTypes: Record fancyTypes: Record; }; @@ -56,7 +56,7 @@ export const SpawnSearch = () => { data; const [atomData, setAtomData] = useState({ types: [], - abstractTypes: [], + abstractTypes: {}, fancyTypes: {}, }); const [selected, setSelected] = useState(0); @@ -129,7 +129,7 @@ export const SpawnSearch = () => { (type: AtomTypeData) => (searchLambda(type.typepath) || (searchNames && searchLambda(type.name))) && - (includeAbstracts || !atomData.abstractTypes.includes(type.typepath)), + (includeAbstracts || !atomData.abstractTypes[type.typepath]), ); }; @@ -313,7 +313,7 @@ export const SpawnSearch = () => { > { > {item.name} - {!!atomData.abstractTypes.includes(item.typepath) && ( + {!!atomData.abstractTypes[item.typepath] && (