Code/CharacterKit/HumanMarkingModels.cs

Utility class for choosing alternate human models and materials when character makeup includes arm, chest or back markings. It decides if arm-region models are needed, maps native model paths to arm-region variants, ensures the arm-region model is loaded asynchronously, and computes native body material for an arm material slot.

File AccessExternal Download
using System;
using System.Threading.Tasks;

namespace CharacterEditor;
/// <summary>Native human derivatives differ only in arm polygon material assignments.
/// The source Clothing choice stays authoritative; no replacement rig or mesh is sculpted here.</summary>
public static class HumanMarkingModels
{
	const string ArmMaterials = "materials/makeup/arms/";
	public static bool NeedsArmRegions(CharacterMakeupData makeup) => makeup?.Markings?.Any(x => x.Style != "none" && x.Region is "left-arm" or "right-arm" or "chest" or "back") == true;
	public static string ModelPath(string native, CharacterMakeupData makeup) => NeedsArmRegions(makeup) ? native switch
	{
		"models/citizen_human/citizen_human_male.vmdl" => "models/makeup/human_male_arm_regions.vmdl",
		"models/citizen_human/citizen_human_female.vmdl" => "models/makeup/human_female_arm_regions.vmdl",
		_ => native
	} : native;
	public static async Task EnsureLoadedAsync(CharacterAppearanceData data)
	{
		if (!NeedsArmRegions(data?.Makeup))
			return;
		var skin = ResourceLibrary.Get<Clothing>(data.SkinPath);
		var path = ModelPath(skin.HumanSkinModel, data.Makeup);
		var model = await Model.LoadAsync(path);
		if (model is null || model.IsError)
			throw new InvalidOperationException("The independently marked human arms are unavailable.");
	}

	public static int ArmSide(Material material) => !material.Name.StartsWith(ArmMaterials, StringComparison.OrdinalIgnoreCase) ? 0 : material.Name.Contains("_left_", StringComparison.OrdinalIgnoreCase) ? 1 : -1;
	public static Material NativeMaterial(Material slot)
	{
		if (ArmSide(slot) == 0)
			return slot;
		var female = slot.Name.Contains("/female_", StringComparison.OrdinalIgnoreCase);
		var light = slot.Name.EndsWith("_light.vmat", StringComparison.OrdinalIgnoreCase);
		var name = female ? (light ? "female_body_mode_light" : "female_body_mode_dark") : (light ? "body_male_light" : "body_mode_dark");
		return Material.Load($"models/citizen_human/bodies/{(female ? "female" : "male")}/{name}.vmat");
	}
}