Code/CharacterKit/CharacterAppearance.cs

Validation utility for character appearance data. It checks the data via CharacterAppearanceData.Validate, verifies required native skin and human model resources, ensures selected hair/facial hair clothing resources exist and match slot types, and confirms hairstyle models are compatible with the human body.

File Access
using System;

namespace CharacterEditor;
public static class CharacterAppearance
{
	public static string ValidateForApply(CharacterAppearanceData data)
	{
		if (CharacterAppearanceData.Validate(data)is string error)
			return error;
		var skin = ClothingResources.Get(UnifiedHuman.PresentationSkin(data));
		if (skin?.HasHumanSkin != true)
			return "The selected native skin resource is unavailable.";
		var body = Model.Load(data.BaseShape is null ? HumanMarkingModels.ModelPath(skin.HumanSkinModel, data.Makeup) : UnifiedHuman.ModelPath);
		if (!body.IsValid || body.IsError)
			return "The selected native human model is unavailable.";
		var clothes = new List<Clothing>
		{
			skin
		};
		foreach (var path in new[]
		{
			data.HairPath,
			data.FacialHairPath
		}.Where(x => !string.IsNullOrEmpty(x)))
		{
			var clothing = ClothingResources.Get(path);
			if (clothing is null)
				return "A selected native hairstyle resource is unavailable.";
			if (path == data.HairPath && !ClothingResources.IsHair(clothing) || path == data.FacialHairPath && !ClothingResources.IsFacialHair(clothing))
				return "The selected item does not belong in this hair slot.";
			clothes.Add(clothing);
		}

		var tags = new TagSet(skin.HumanSkinTags.TryGetAll());
		tags.Add("human");
		foreach (var clothing in clothes.Skip(1))
		{
			var path = clothing.GetModel(clothes, tags);
			if (string.IsNullOrEmpty(path))
				return "A selected hairstyle does not support this native human body.";
			var model = Model.Load(path);
			if (!model.IsValid || model.IsError)
				return "A selected native hairstyle model is unavailable.";
		}

		return null;
	}
}