AI/Guests/Guest.Identity.cs
using System;
using System.Text.Json.Nodes;

namespace HC3;

public struct Identity
{
	public Color ShirtColor { get; set; }
	public Color HeadColor { get; set; }
	public Color ForearmsColor { get; set; }
	public Color PantsColor { get; set; }
	public Color ShoesColor { get; set; }
	public Color HairColor { get; set; }
	public float Scale { get; set; }
	public bool LoadedFromSave { get; set; }

	private static Color GetRandomShirt( Random random )
	{
		var colors = new Color[]
		{
			new( 0.8f, 0.2f, 0.2f ),       // Brick Red
	        new( 0.2f, 0.4f, 0.8f ),       // Royal Blue
	        new( 0.3f, 0.7f, 0.4f ),       // Forest Green
	        new( 0.7f, 0.3f, 0.7f ),       // Purple
	        new( 0.2f, 0.7f, 0.8f ),       // Teal
	        new( 0.9f, 0.6f, 0.2f ),       // Orange
	        new( 0.8f, 0.8f, 0.2f ),       // Yellow
	        new( 0.5f, 0.3f, 0.1f ),       // Brown
	        new( 0.9f, 0.5f, 0.5f ),       // Salmon
	        new( 0.5f, 0.5f, 0.5f ),       // Gray
	        new( 0.2f, 0.2f, 0.3f ),       // Navy Blue
	        new( 0.6f, 0.3f, 0.4f ),       // Maroon
	        new( 0.9f, 0.9f, 0.9f ),       // Off-White
	        new( 0.1f, 0.1f, 0.1f ),       // Charcoal
	        new( 0.7f, 0.2f, 0.3f ),       // Crimson
	        new( 0.4f, 0.7f, 0.1f ),       // Lime Green
	        new( 0.6f, 0.8f, 0.9f ),       // Sky Blue
	        new( 0.9f, 0.7f, 0.8f ),       // Pink
	        new( 0.4f, 0.3f, 0.6f ),       // Lavender
	        new( 0.5f, 0.8f, 0.6f )        // Mint Green
		};

		return random.FromArray( colors );
	}

	private static Color GetRandomHair( Random random )
	{
		var colors = new Color[]
		{
			new( 0.05f, 0.05f, 0.05f ),    // Black
	        new( 0.2f, 0.15f, 0.1f ),      // Dark Brown
	        new( 0.4f, 0.3f, 0.2f ),       // Medium Brown
	        new( 0.6f, 0.5f, 0.35f ),      // Light Brown
	        new( 0.8f, 0.7f, 0.45f ),      // Dirty Blonde
	        new( 0.9f, 0.8f, 0.5f ),       // Blonde
	        new( 0.8f, 0.3f, 0.2f ),       // Ginger/Red
	        new( 0.5f, 0.5f, 0.5f ),       // Silver/Gray
	        new( 0.7f, 0.7f, 0.7f ),       // White/Platinum
	        new( 0.3f, 0.2f, 0.15f ),      // Auburn
	        new( 0.15f, 0.1f, 0.05f ),     // Very Dark Brown
	        new( 0.7f, 0.6f, 0.4f )        // Sandy Blonde
		};

		return random.FromArray( colors );
	}

	private static Color GetRandomPants( Random random )
	{
		var colors = new Color[]
		{
			new( .1f, 0.1f, 0.15f ),      // Navy Blue
	        new( 0.2f, 0.2f, 0.2f ),       // Charcoal
	        new( 0.05f, 0.05f, 0.05f ),    // Black
	        new( 0.3f, 0.3f, 0.3f ),       // Dark Gray
	        new( 0.2f, 0.15f, 0.1f ),      // Dark Brown
	        new( 0.35f, 0.2f, 0.15f ),     // Medium Brown
	        new( 0.7f, 0.7f, 0.75f ),      // Light Gray
	        new( 0.15f, 0.25f, 0.35f ),    // Dark Denim Blue
	        new( 0.25f, 0.35f, 0.45f ),    // Medium Denim Blue
	        new( 0.35f, 0.45f, 0.55f ),    // Light Denim Blue
	        new( 0.5f, 0.35f, 0.2f ),      // Khaki
	        new( 0.6f, 0.5f, 0.4f ),       // Tan
	        new(0.3f, 0.25f, 0.2f ),      // Olive Green
	        new( 0.4f, 0.2f, 0.15f )       // Burgundy
		};

		return random.FromArray( colors );
	}

	private static Color GetRandomSkin( Random random )
	{
		var colors = new Color[]
		{
			new( 0.97f, 0.85f, 0.73f ),    // Very Light
	        new( 0.94f, 0.78f, 0.67f ),    // Light
	        new( 0.87f, 0.72f, 0.60f ),    // Light Medium
	        new( 0.80f, 0.65f, 0.52f ),    // Medium
	        new( 0.75f, 0.60f, 0.48f ),    // Medium Tan
	        new( 0.65f, 0.48f, 0.35f ),    // Tan
	        new( 0.55f, 0.38f, 0.27f ),    // Medium Dark
	        new( 0.45f, 0.30f, 0.22f ),    // Dark
	        new( 0.35f, 0.22f, 0.16f ),    // Deep Dark
	        new( 0.25f, 0.15f, 0.10f ),    // Very Deep Dark
	        new( 0.90f, 0.75f, 0.65f ),    // Peach
	        new( 0.85f, 0.68f, 0.55f ),    // Warm Medium
	        new( 0.70f, 0.55f, 0.42f ),    // Olive
	        new( 0.60f, 0.43f, 0.32f ),    // Golden Brown
	        new( 0.50f, 0.33f, 0.24f )     // Rich Brown
		};

		return random.FromArray( colors );
	}

	public static Identity Generate( Random random )
	{
		var skinColor = GetRandomSkin( random );

		return new Identity
		{
			ShirtColor = GetRandomShirt( random ),
			HeadColor = skinColor,
			ForearmsColor = skinColor,
			PantsColor = GetRandomPants( random ),
			ShoesColor = Color.Parse( "#8E8B8E" ).Value,
			HairColor = GetRandomHair( random ),
			LoadedFromSave = false,
			Scale = random.Float( 0f, 1f ) > 0.75f
				? random.Float( 0.5f, 0.7f )
				: random.Float( 0.8f, 1.0f )
		};
	}

	public void ApplyTo( SceneModel model )
	{
		model.Batchable = false;
		model.Attributes.Set( "ShirtColor", ShirtColor );
		model.Attributes.Set( "HeadColor", HeadColor );
		model.Attributes.Set( "ForearmsColor", ForearmsColor );
		model.Attributes.Set( "PantsColor", PantsColor );
		model.Attributes.Set( "ShoesColor", ShoesColor );
		model.Attributes.Set( "HairColor", HairColor );
	}

	public JsonObject ToJson()
	{
		return new JsonObject
		{
			["ShirtColor"] = ShirtColor.Hex,
			["HeadColor"] = HeadColor.Hex,
			["ForearmsColor"] = ForearmsColor.Hex,
			["PantsColor"] = PantsColor.Hex,
			["ShoesColor"] = ShoesColor.Hex,
			["HairColor"] = HairColor.Hex,
			["Scale"] = Scale
		};
	}

	public static Identity FromJson( JsonNode json )
	{
		return new Identity
		{
			ShirtColor = Color.Parse( json["ShirtColor"]?.ToString() ?? "#FFFFFF" ).Value,
			HeadColor = Color.Parse( json["HeadColor"]?.ToString() ?? "#FFFFFF" ).Value,
			ForearmsColor = Color.Parse( json["ForearmsColor"]?.ToString() ?? "#FFFFFF" ).Value,
			PantsColor = Color.Parse( json["PantsColor"]?.ToString() ?? "#FFFFFF" ).Value,
			ShoesColor = Color.Parse( json["ShoesColor"]?.ToString() ?? "#FFFFFF" ).Value,
			HairColor = Color.Parse( json["HairColor"]?.ToString() ?? "#FFFFFF" ).Value,
			Scale = json["Scale"]?.GetValue<float>() ?? 1.0f,
			LoadedFromSave = true
		};
	}
}

public partial class Guest
{
	[Sync( SyncFlags.FromHost )]
	protected Identity Identity { get; set; }

	/// <summary>
	/// Is this guest a male?
	/// </summary>
	[Property, Feature( "Identity" ), Group( "Identity" ), Sync( SyncFlags.FromHost )]
	public bool IsMale { get; set; }

	[Property] Texture _faceTexture { get; set; }

	void SetupIdentity()
	{
		if ( !Game.IsPlaying )
			return;

		// Already loaded from save
		if ( Identity.LoadedFromSave ) return;

		IsMale = Game.Random.Int( 0, 1 ) > 0;
		FullName = IdentitySystem.FetchName( IsMale, Delinquency );

		var seed = GameObject.Id.ToString().FastHash();
		var random = new Random( seed );
		Identity = Identity.Generate( random );
	}

	void SetupSkin()
	{
		// Can't batch these now
		Body.SceneModel.Batchable = false;
		
		Body.SceneModel.Attributes.Set( "Slot0", Identity.ShirtColor );
		Body.SceneModel.Attributes.Set( "Slot1", Identity.ForearmsColor );
		Body.SceneModel.Attributes.Set( "Slot2", Identity.HeadColor );

		Body.SceneModel.Attributes.Set( "Slot3", Identity.PantsColor );
		Body.SceneModel.Attributes.Set( "Slot4", Identity.ShoesColor );
		Body.SceneModel.Attributes.Set( "Slot5", Identity.HairColor );

		Body.SceneModel.Attributes.Set( "FaceTexture", _faceTexture );

		WorldScale = Identity.Scale;
	}
}