Items/DecalItem.cs
using Clover.Persistence;
using Clover.Ui;
using Clover.Utilities;
using Sandbox.Diagnostics;
using Sandbox.Utility;

namespace Clover.Items;

public class DecalItem : Component, IPersistent, IPaintEvent
{
	private Decals.DecalData _decalData;
	protected Texture DecalTexture;

	private string _texturePath;


	[Sync]
	public string TexturePath
	{
		get => _texturePath;
		set
		{
			_texturePath = value;
			UpdateDecal();
		}
	}

	[Sync] public string DecalHash { get; set; }

	public void UpdateDecal()
	{
		if ( Scene.IsEditor ) return;
		if ( string.IsNullOrEmpty( TexturePath ) ) return;

		if ( IsProxy )
		{
			FileSystem.Data.CreateDirectory( "decalcache" );
			if ( FileSystem.Data.FileExists( $"decalcache/{DecalHash}.decal" ) )
			{
				_decalData = Decals.ReadDecal( $"decalcache/{DecalHash}.decal" );
				DecalTexture = Decals.GetDecalTexture( _decalData.ToRpc() );
				var material1 = Material.Create( $"{DecalHash}.vmat", "shaders/floor_decal.shader" );
				material1.Set( "Color", DecalTexture );
				// ModelRenderer.MaterialOverride = material1;
				OnMaterialUpdate( material1 );
				Log.Info( $"Updated cached decal '{_decalData.Name}' with texture: {TexturePath}" );
				return;
			}

			Log.Info( $"Decal '{TexturePath}' not found in cache, requesting..." );

			RequestDecal();
			return;
		}

		// Update decal
		var material = Material.Create( $"{TexturePath}.vmat", "shaders/floor_decal.shader" );

		try
		{
			_decalData = Decals.ReadDecal( TexturePath );
		}
		catch ( System.Exception e )
		{
			Log.Error( e.Message );
			return;
		}

		DecalTexture = _decalData.Texture;

		material.Set( "Color", _decalData.Texture );

		// ModelRenderer.MaterialOverride = material;
		OnMaterialUpdate( material );

		DecalHash = _decalData.GetHash();

		Log.Info( $"Updated decal '{_decalData.Name}' with texture: {TexturePath}" );
	}

	[Rpc.Owner]
	private void RequestDecal()
	{
		Assert.True( Networking.IsHost );

		var caller = Rpc.Caller;

		if ( string.IsNullOrEmpty( _texturePath ) )
		{
			Log.Warning( "Texture path is null or empty" );
			return;
		}

		if ( string.IsNullOrEmpty( _decalData.Name ) )
		{
			Log.Warning( "Decal name is null or empty" );
			return;
		}

		var rpcDecal = _decalData.ToRpc();

		Log.Info( $"Sending decal '{rpcDecal.Name}' by '{rpcDecal.Author}' to {caller}" );

		using ( Rpc.FilterInclude( caller ) )
		{
			RecieveDecal( _texturePath, rpcDecal );
		}
	}


	[Rpc.Broadcast]
	public void RecieveDecal( string filename, Decals.DecalDataRpc decal )
	{
		Log.Info( $"Recieved decal '{filename}':" );
		Log.Info( $"Size: {decal.Width}x{decal.Height}" );
		Log.Info( $"Name: {decal.Name}" );
		Log.Info( $"Author: {decal.Author}" );
		Log.Info( $"Palette: {decal.Palette}" );
		Log.Info( $"Image: {decal.Image?.Length} bytes" );

		if ( string.IsNullOrEmpty( decal.Name ) )
		{
			Log.Error( "Decal name is null or empty" );
			return;
		}

		_decalData = decal.ToDecalData();
		// _decalData = Decals.ToDecalData( decal );

		var hash = _decalData.GetHash();

		DecalTexture = Decals.GetDecalTexture( decal );

		var material = Material.Create( $"{hash}.vmat", "shaders/floor_decal.shader" );
		material.Set( "Color", DecalTexture );
		// ModelRenderer.MaterialOverride = material;
		OnMaterialUpdate( material );

		FileSystem.Data.CreateDirectory( "decalcache" );
		var file = FileSystem.Data.OpenWrite( $"decalcache/{hash}.decal" );
		Decals.WriteDecal( file, decal.ToDecalData() );
		file.Close();
	}

	public virtual void OnMaterialUpdate( Material material )
	{
	}

	public virtual void OnSave( PersistentItem item )
	{
		item.SetSaveData( "TexturePath", TexturePath );
	}

	public virtual void OnLoad( PersistentItem item )
	{
		TexturePath = item.GetSaveData<string>( "TexturePath" );
		// UpdateDecal();
	}

	void IPaintEvent.OnFileSaved( string path )
	{
		if ( TexturePath == path )
		{
			Log.Info( "Updating decal" );
			UpdateDecal();

			RecieveDecal( path, _decalData.ToRpc() );
		}
	}

	[ConCmd( "clover_delete_old_decals" )]
	public static void DeleteOldDecals()
	{
		var decals = Game.ActiveScene.GetAllComponents<DecalItem>();
		foreach ( var decal in decals )
		{
			if ( decal.DecalHash == null )
			{
				Log.Info( $"Deleting old decal: {decal.TexturePath}" );
				// decal.Delete();
			}
		}
	}
}