Utils/TintMaskComponent.cs
using HC3;

public interface ITintMaskEvents : ISceneEvent<ITintMaskEvents>
{
	/// <summary>
	/// Called when a tint mask component has changed its colors.
	/// </summary>
	public void OnColorsChanged( TintMaskComponent component ) { }
}

[Title( "Tint Mask Controller" )]
[Category( "Rendering" )]
public sealed class TintMaskComponent : Component, Component.ExecuteInEditor
{
	List<ModelRenderer> Renderers { get; set; } = new();

	[Property] public bool PrimaryEnabled { get; set; } = true;
	[Property, ShowIf( nameof( PrimaryEnabled ), true ), Change( nameof( UpdateTint ) )]
	public TycoonColor PrimaryColor { get; set; } = Color.Red;

	[Property] public bool SecondaryEnabled { get; set; } = false;
	[Property, ShowIf( nameof( SecondaryEnabled ), true ), Change( nameof( UpdateTint ) )]
	public TycoonColor SecondaryColor { get; set; } = Color.White;

	[Property] public bool AccentEnabled { get; set; } = false;
	[Property, ShowIf( nameof( AccentEnabled ), true ), Change( nameof( UpdateTint ) )]
	public TycoonColor AccentColor { get; set; } = Color.White;

	[Property] public bool DetailEnabled { get; set; } = false;
	[Property, ShowIf( nameof( DetailEnabled ), true ), Change( nameof( UpdateTint ) )]
	public TycoonColor DetailColor { get; set; } = Color.White;

	protected override void OnStart()
	{
		if ( GameObject.Tags.Has( "placing" ) ) return;

		Renderers = Components.GetAll<ModelRenderer>( FindMode.EverythingInSelfAndChildren ).ToList();
		UpdateTint();
	}

	public void InEditor()
	{
		if ( GameObject.Tags.Has( "placing" ) ) return;
		Renderers = Components.GetAll<ModelRenderer>( FindMode.EverythingInSelfAndChildren ).ToList();
		UpdateTint();
	}

	/// <summary>
	/// Set and broadcast a change to the primary color.
	/// </summary>
	[Rpc.Broadcast]
	public void SetPrimaryColor( Color color )
	{
		PrimaryColor = color;
		ITintMaskEvents.PostToGameObject( GameObject.Root, x => x.OnColorsChanged( this ) );

		Stats.Increment( "building.tinted" );
	}

	/// <summary>
	/// Set and broadcast a change to the secondary color.
	/// </summary>
	[Rpc.Broadcast]
	public void SetSecondaryColor( Color color )
	{
		SecondaryColor = color;
		ITintMaskEvents.PostToGameObject( GameObject.Root, x => x.OnColorsChanged( this ) );

		Stats.Increment( "building.tinted" );
	}

	/// <summary>
	/// Set and broadcast a change to the accent color.
	/// </summary>
	[Rpc.Broadcast]
	public void SetAccentColor( Color color )
	{
		AccentColor = color;
		ITintMaskEvents.PostToGameObject( GameObject.Root, x => x.OnColorsChanged( this ) );

		Stats.Increment( "building.tinted" );
	}

	/// <summary>
	/// Set and broadcast a change to the detail color.
	/// </summary>
	[Rpc.Broadcast]
	public void SetDetailColor( Color color )
	{
		DetailColor = color;
		ITintMaskEvents.PostToGameObject( GameObject.Root, x => x.OnColorsChanged( this ) );

		Stats.Increment( "building.tinted" );
	}

	private void UpdateTint()
	{
		foreach ( var renderer in Renderers )
		{
			if ( renderer is null ) continue;

			renderer.SceneObject.Batchable = false;
			renderer.SceneObject.Attributes.Set( "redChannel", PrimaryColor.Value );
			renderer.SceneObject.Attributes.Set( "greenChannel", SecondaryColor.Value );
			renderer.SceneObject.Attributes.Set( "blueChannel", AccentColor.Value );
			renderer.SceneObject.Attributes.Set( "alphaChannel", DetailColor.Value );
		}
	}
}
public enum TintChannel
{
	Primary,
	Secondary,
	Accent,
	Detail
}