test/Enemy.cs
using Sandbox.Services;

public sealed class Enemy : Component, Component.ICollisionListener, Component.IDamageable
{
	[RequireComponent] public PlayerController Controller { get; set; }
	[Property] GameObject BloodPrefab { get; set; }

	[Property] public float Health { get; set; } = 5f;
	[Property] public float Speed { get; set; } = 150f;
	[Property] public float Damage { get; set; } = 10f;

	TimeSince timeSinceLastCheck = 0f;
	List<Vector3> currentPath = new();

	protected override void OnAwake()
	{
		timeSinceLastCheck = 0f;
	}

	protected override void OnFixedUpdate()
	{
		var player = Player.FindLocalPlayer();
		if ( timeSinceLastCheck > 1.5f )
		{
			timeSinceLastCheck = 0f;

			if ( player.IsValid() )
			{
				var path = Scene.NavMesh.CalculatePath( new Sandbox.Navigation.CalculatePathRequest()
				{
					Start = WorldPosition,
					Target = player.WorldPosition
				} );
				//Log.Info( $"{GameObject.Name} got path with {path.Points.Count} points" );
				if ( path.IsValid && path.Points.Count > 0 )
				{
					currentPath.Clear();
					for ( int i = 0; i < path.Points.Count; i++ )
					{
						currentPath.Add( path.Points[i].Position );
					}
				}
			}
		}

		if ( currentPath.Count > 0 )
		{
			var target = currentPath[0];
			var dir = (target - WorldPosition).Normal.WithZ( 0 );
			Controller.WishVelocity = dir * Speed;
			Controller.EyeAngles = Rotation.LookAt( dir );
			if ( WorldPosition.Distance( target ) < 32f )
			{
				currentPath.RemoveAt( 0 );
			}
		}
		else if ( player.IsValid() )
		{
			var dir = (player.WorldPosition - WorldPosition).Normal.WithZ( 0 );
			Controller.WishVelocity = dir * Speed;
			Controller.EyeAngles = Rotation.LookAt( dir );
		}
		else
		{
			Controller.WishVelocity = 0f;
		}
	}

	void IDamageable.OnDamage( in DamageInfo damage )
	{
		Health -= damage.Damage;

		if ( BloodPrefab.IsValid() )
		{
			var bloodAm = Random.Shared.Int( 6, 15 ) / ((Health <= 0f) ? 1 : 5);
			for ( int i = 0; i < bloodAm; i++ )
			{
				var blood = BloodPrefab.Clone( new CloneConfig()
				{
					StartEnabled = true,
					Transform = new Transform()
					{
						Position = WorldPosition + Vector3.Up * (35f + Random.Shared.Float( 0f, 32f )),
						Rotation = WorldRotation
					}
				} );
			}
		}

		if ( Health <= 0f )
		{
			// Spawn sonar
			using ( Scene.Push() )
			{
				var go = new GameObject( "Sonar" );
				go.WorldPosition = WorldPosition;
				var volume = go.GetOrAddComponent<GaussianSplatVolume>();
				volume.Mode = SplatVolumeMode.Add;
				volume.Shape = SplatVolumeShape.Sphere;
				volume.Radius = 0f;
				var sonar = go.GetOrAddComponent<SonarComponent>();
				sonar.Volume = volume;
				sonar.TargetSize = 256f;
				sonar.Intensity = 0.05f;
			}

			// Award paint
			var player = Player.FindLocalPlayer();
			if ( player.IsValid() && player.Paint.IsValid() )
			{
				player.Paint.Paint += 5f;
			}

			// Award kill
			if ( GameManager.Instance.IsValid() )
			{
				GameManager.Instance.Kills++;
				Stats.Increment( "enemy_kills", 1 );
			}

			GameObject.Destroy();
		}
	}

	void ICollisionListener.OnCollisionStart( Collision collision )
	{
		//Log.Info( collision.Other.GameObject.Root );
		if ( collision.Other.GameObject.Root.Components.TryGet<Player>( out var player ) )
		{
			var info = new DamageInfo( Damage, GameObject, GameObject );

			player.OnDamage( info );
		}
	}
}