FatesActionTimer.cs
using System;
using Sandbox;


[Title( "ActionTimer" )]
[Category("The Fates/ActionTimer")]
[Icon("timer")] 
public class ActionTimer : Component
{
	[Property] public float Duration { get; set; } = 1.0f;
	[Property] public bool TimerLoops { get; set; } = false;
	[Property] public bool AutoStart { get; set; } = false;
	[Property] public bool UseThreading { get; set; } = false;
    
	// This generates a unique ID for the internal Timer class to prevent collisions
	private string TimerId => $"FatesTimer_{GameObject.Id}";

	public bool IsRunning => Timer.Exists( TimerId ) && Timer.All()[TimerId].status == Timer.Process.Continue;
	public float TimeRemaining => Timer.TimeLeft( TimerId );
    
	[Property] public Action OnTimerElapsed { get; set; }

	protected override void OnStart()
	{
		if ( AutoStart ) StartTimer();
	}

	public void StartTimer()
	{
		// Repetitions: 0 for infinite (mapped to -1 in Timer), 1 for single run
		int reps = TimerLoops ? 0 : 1;
        
		Timer.Create( TimerId, Duration, reps, () => {
			OnTimerElapsed?.Invoke();
		}, UseThreading );
	}

	public void Pause()
	{
		Timer.Pause( TimerId );
	}

	public void Resume()
	{
		Timer.UnPause( TimerId );
	}

	public void Stop()
	{
		Timer.Remove( TimerId );
	}

	protected override void OnDestroy()
	{
		// Clean up the static dictionary entry when the component is removed
		Timer.Remove( TimerId );
	}
}