CharacterKit/SkinStyleAssets.cs

Static helper that loads and caches skin-related materials and normal textures for character appearance. It loads different material paths and texture channels based on skin id, body type, and style, and exposes lookup helpers for head/body color and normal maps.

File Access
using System;
using System.Threading.Tasks;

namespace CharacterEditor;

/// <summary>Preserved and refreshed skin maps with matched collar color and surface detail.</summary>
public static class SkinStyleAssets
{
	static readonly Dictionary<string, Material> carriers = new();
	static readonly Dictionary<string, Texture> normals = new();
	static string Id(string skin) => System.IO.Path.GetFileNameWithoutExtension(skin);
	static string BodyId(string skin) => Id(skin) is "human01" or "human02" or "human06" ? "dark" : "light";

	public static async Task LoadAsync(CharacterAppearanceData data)
	{
		if (data.BaseShape is not null && !HasFemaleBody(data.SkinPath))
		{
			await Load($"materials/makeup/body/{(data.SkinStyle == "original" ? "original_" : "")}{BodyId(data.SkinPath)}.vmat");
			foreach (var channel in new[] { "Normal", "AgeNormal", "CombinedMasks" })
			{
				var key = "body_" + BodyId(data.SkinPath) + channel;
				if (normals.ContainsKey(key)) continue;
				var texture = await Texture.LoadAsync($"textures/makeup/body/{BodyId(data.SkinPath)}_{channel.ToLowerInvariant()}.vtex");
				if (texture is not { IsError: false }) throw new InvalidOperationException("The body map is unavailable: " + channel);
				texture.MarkUsed(Math.Max(texture.Width, texture.Height));
				normals[key] = texture;
			}
		}
		if (data.BaseShape is not null && HasFemaleBody(data.SkinPath))
		{
			await Load($"materials/makeup/female/{BodyId(data.SkinPath)}.vmat");
			foreach (var channel in new[] { "Normal", "AgeNormal", "CombinedMasks" })
			{
				var key = "female_" + BodyId(data.SkinPath) + channel;
				if (normals.ContainsKey(key)) continue;
				var texture = await Texture.LoadAsync($"textures/makeup/female/{BodyId(data.SkinPath)}_{channel.ToLowerInvariant()}.vtex");
				if (texture is not { IsError: false }) throw new InvalidOperationException("The female body map is unavailable: " + channel);
				texture.MarkUsed(Math.Max(texture.Width, texture.Height));
				normals[key] = texture;
			}
		}
		if (data.SkinStyle == "stylized")
		{
			await Load($"materials/makeup/neck/stylized/{Id(data.SkinPath)}.vmat");
			if (data.BaseShape is not null)
			{
				var key = Id(data.SkinPath) + "_stylized";
				if (!normals.ContainsKey(key))
				{
					var texture = await Texture.LoadAsync($"textures/makeup/neck/stylized/{Id(data.SkinPath)}_normal.vtex");
					if (texture is not { IsError: false }) throw new InvalidOperationException("The matched neck normal map is unavailable: " + key);
					texture.MarkUsed(Math.Max(texture.Width, texture.Height));
					normals[key] = texture;
				}
			}
			return;
		}
		await UnifiedNeckAssets.LoadHeadAsync(data.SkinPath);
		await LoadNormal(Id(data.SkinPath));
		await Load($"materials/makeup/original/{BodyId(data.SkinPath)}.vmat");
		await LoadNormal(BodyId(data.SkinPath));
	}

	static async Task LoadNormal(string id)
	{
		if (normals.ContainsKey(id))
			return;
		var texture = await Texture.LoadAsync($"textures/makeup/original/{id}_normal.vtex");
		if (texture is null || texture.IsError)
			throw new InvalidOperationException("The original skin normal map is unavailable: " + id);
		texture.MarkUsed(Math.Max(texture.Width, texture.Height));
		normals[id] = texture;
	}

	static async Task Load(string path)
	{
		if (!carriers.TryGetValue(path, out var material))
			material = await Material.LoadAsync(path);
		var texture = material?.GetTexture("g_tColor");
		if (texture is null || texture.IsError)
			throw new InvalidOperationException("The selected skin style is unavailable: " + path);
		texture.MarkUsed(Math.Max(texture.Width, texture.Height));
		carriers[path] = material;
	}

	static Texture Map(string name) => carriers[$"materials/makeup/{name}.vmat"].GetTexture("g_tColor");
	public static Texture HeadColor(string skin, string style) => style == "stylized" ? Map($"neck/stylized/{Id(skin)}") : UnifiedNeckAssets.ColorTexture(skin);
	public static Texture OriginalNormal(string skin, bool head) => normals[head ? Id(skin) : BodyId(skin)];
	public static Texture HeadNormal(string skin, string style) => normals[Id(skin) + (style == "stylized" ? "_stylized" : "")];
	public static Texture OriginalBodyColor(string skin) => Map($"original/{BodyId(skin)}");
	public static bool HasFemaleBody(string skin) => Id(skin) is "human02" or "human04";
	public static Texture FemaleBodyColor(string skin) => Map($"female/{BodyId(skin)}");
	public static Texture FemaleBodyNormal(string skin) => FemaleBodyMap(skin, "Normal");
	public static Texture FemaleBodyMap(string skin, string channel) => normals["female_" + BodyId(skin) + channel];
	public static Texture UnifiedBodyColor(string skin, string style) => HasFemaleBody(skin) ? FemaleBodyColor(skin) : Map($"body/{(style == "original" ? "original_" : "")}{BodyId(skin)}");
	public static Texture BodyMap(string skin, string channel) => HasFemaleBody(skin) ? FemaleBodyMap(skin, channel) : normals["body_" + BodyId(skin) + channel];
}