Refactors unique_reskin, deletes retool kit (#93775)

## About The Pull Request

Closes #93635

`unique_reskin` is no longer a list on `/item`, now `/datum/atom_skin`

The actual reskinning behavior has been moved out to
`/datum/component/reskinable_item`

PKC reskinning is now handled via alt-click reskin, rather than via the
retooling kit. The retooling kit has been removed.
There's no limit on how many times you can reskin your PKC (though
perhaps we limit it to one reskin and keep the retooling kit as a way to
allow a miner to reskin it a second time?)

The Ashen Skull unique reskin is still a trophy, and instead unlocks its
unique reskin option in the alt-click radial.

## Why It's Good For The Game

I'm unsure why the retooling kit exists on its own, when it's relatively
cheap and just performs the behavior of alt-click reskinning.

So to keep it consistent with all other forms of reskinning I've just
made it baseline. To accomplish that I refactored reskinning.

The new form of reskinning allows for greater potential in adding
reskins, allowing far more than just an icon state change. Also we can
put it on turfs and mobs and structures now which is cool I guess

There's also the added benefit of being able to see an item's reskins
without needing to instantiate it, which the loadout menu uses to great
effect.

## Changelog

🆑 Melbert
refactor: Refactored item reskinning (the alt-click way), report any
oddities with that
del: Deleted the crusher retool kit, now you can just reskin your
crusher with alt-click. The Skull skin is still locked behind having the
Ashen Skull trophy applied.
fix: Stunswords no longer have an incorrect lore blurb
fix: Fixed loadout item reskinning's UI
/🆑
This commit is contained in:
MrMelbert
2025-11-30 19:31:29 -07:00
committed by GitHub
parent 693d94d930
commit 6ebfbccebb
34 changed files with 757 additions and 489 deletions
@@ -82,4 +82,4 @@
/datum/loadout_item/accessory/pride
name = "Pride Pin"
item_path = /obj/item/clothing/accessory/pride
loadout_flags = LOADOUT_FLAG_ALLOW_RESKIN
reskin_datum = /datum/atom_skin/pride_pin
+19 -26
View File
@@ -50,8 +50,9 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories())
/// Icon state for the UI to use for preview icons.
/// Set automatically if null
var/ui_icon_state
/// Reskin options of this item if it can be reskinned.
VAR_FINAL/list/cached_reskin_options
/// 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
/// 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.
@@ -81,15 +82,6 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories())
ui_icon = item_path::icon_preview || item_path::icon
ui_icon_state = item_path::icon_state_preview || item_path::icon_state
if(loadout_flags & LOADOUT_FLAG_ALLOW_RESKIN)
var/obj/item/dummy_item = new item_path()
if(!length(dummy_item.unique_reskin))
loadout_flags &= ~LOADOUT_FLAG_ALLOW_RESKIN
stack_trace("Loadout item [item_path] has LOADOUT_FLAG_ALLOW_RESKIN but has no unique reskins.")
else
cached_reskin_options = dummy_item.unique_reskin.Copy()
qdel(dummy_item)
/datum/loadout_item/Destroy(force, ...)
if(!force)
stack_trace("QDEL called on loadout item [type]. This shouldn't ever happen. (Use FORCE if necessary.)")
@@ -126,7 +118,7 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories())
return set_name(manager, user)
if("set_skin")
if(loadout_flags & LOADOUT_FLAG_ALLOW_RESKIN)
if(reskin_datum)
return set_skin(manager, user, params)
return TRUE
@@ -205,10 +197,7 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories())
/// Used for reskinning an item to an alt skin.
/datum/loadout_item/proc/set_skin(datum/preference_middleware/loadout/manager, mob/user, params)
var/reskin_to = params["skin"]
if(!cached_reskin_options[reskin_to])
return FALSE
var/reskin_to = params["skin"] // sanity checking isn't necessary because it's all checked when equipped anyways
var/list/loadout = manager.preferences.read_preference(/datum/preference/loadout)
if(!loadout?[item_path])
return FALSE
@@ -283,11 +272,13 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories())
equipped_item.name = trim(item_details[INFO_NAMED], PREVENT_CHARACTER_TRIM_LOSS(MAX_NAME_LEN))
ADD_TRAIT(equipped_item, TRAIT_WAS_RENAMED, "Loadout")
if((loadout_flags & LOADOUT_FLAG_ALLOW_RESKIN) && item_details?[INFO_RESKIN])
if(reskin_datum && item_details?[INFO_RESKIN])
var/skin_chosen = item_details[INFO_RESKIN]
if(skin_chosen in equipped_item.unique_reskin)
equipped_item.current_skin = skin_chosen
equipped_item.icon_state = equipped_item.unique_reskin[skin_chosen]
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]
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
if(isclothing(equipped_item.loc))
@@ -296,6 +287,7 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories())
update_flag |= (ITEM_SLOT_OCLOTHING|ITEM_SLOT_ICLOTHING)
else
update_flag |= equipped_item.slot_flags
break
return update_flag
@@ -336,7 +328,7 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories())
if((loadout_flags & LOADOUT_FLAG_GREYSCALING_ALLOWED) && !(loadout_flags & LOADOUT_FLAG_JOB_GREYSCALING))
displayed_text[FA_ICON_PALETTE] = "Recolorable"
if(loadout_flags & LOADOUT_FLAG_ALLOW_RESKIN)
if(reskin_datum)
displayed_text[FA_ICON_SWATCHBOOK] = "Reskinnable"
return displayed_text
@@ -381,16 +373,17 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories())
* Returns a list of options this item can be reskinned into.
*/
/datum/loadout_item/proc/get_reskin_options() as /list
if(!(loadout_flags & LOADOUT_FLAG_ALLOW_RESKIN))
if(!reskin_datum)
return null
var/list/reskins = list()
for(var/skin in cached_reskin_options)
for(var/datum/atom_skin/skin as anything in valid_subtypesof(reskin_datum))
UNTYPED_LIST_ADD(reskins, list(
"name" = skin,
"tooltip" = skin,
"skin_icon_state" = cached_reskin_options[skin],
"name" = skin::new_name || skin::preview_name,
"tooltip" = skin::preview_name,
"skin_icon" = skin::new_icon,
"skin_icon_state" = skin::new_icon_state,
))
return reskins