Player/InteractUse.cs
using Opium;

public sealed class InteractUse : Component
{
	[Property] public bool IsDebugging { get; set; }

	public TimeSince TimeSinceObjectChanged { get; private set; } = 0;
	public TimeSince TimeSinceLookObjectChanged { get; private set; } = 0;


	private IInteractable obj;
	public IInteractable InteractingObject
	{
		get => obj;
		set
		{
			if ( InteractingObject == value ) return;

			obj = value;

			TimeSinceObjectChanged = 0;
		}
	}

	private IInteractable lookobj;
	public IInteractable LookingAtObject
	{
		get => lookobj;
		set
		{
			if ( LookingAtObject == value ) return;

			lookobj = value;
			TimeSinceLookObjectChanged = 0;
		}
	}

	protected override void OnUpdate()
	{
		if ( !Components.Get<Actor>().IsAlive ) return;

		var cameraObject = Components.Get<Actor>().CameraObject;

		var tr = Scene.Trace.Ray( cameraObject.Transform.Position, cameraObject.Transform.Position + cameraObject.Transform.Rotation.Forward * 100.0f )
			.IgnoreGameObjectHierarchy( GameObject )
			.WithoutTags( "ragdoll" )
			.Size( 7.5f )
			.Run();

		if ( IsDebugging )
		{
			Gizmo.Draw.Color = Color.White;
			Gizmo.Draw.Line( tr.StartPosition, tr.EndPosition );
			Gizmo.Draw.LineSphere( tr.EndPosition, 8 );
		}

        // If we're interacting with something right now
        if ( Input.Down( "Use" ) && InteractingObject != null 
            && InteractingObject.CanUse( GameObject ) )
        {
            InteractingObject.OnUseContinuous( GameObject );
            return;
        }

        if ( tr.GameObject.IsValid() && tr.GameObject.Components.Get<IInteractable>( FindMode.EverythingInSelfAndParent ) is IInteractable interactable )
        {
            if ( interactable.CanUse( GameObject ) )
            {
                LookingAtObject = interactable;

                if ( Input.Pressed( "Use" ) )
                {
                    InteractingObject = interactable;
                    InteractingObject.OnUse( GameObject );
                    InteractingObject.OnUseStart( GameObject );

					if ( GameObject.Components.Get<Opium.PlayerController>() is Opium.PlayerController player )
					{
						player.Inventory?.Current?.EventListener?.Invoke( "use" );
					}
                }
            }
			else
			{
				LookingAtObject = interactable;

				if ( Input.Pressed( "Use" ) )
				{
					LookingAtObject.OnUseFail( GameObject );
				}
			}
		}
        else
        {
            LookingAtObject = null;
        }

        if ( Input.Released( "Use" ) )
        {
            InteractingObject?.OnUseStop( GameObject );
            InteractingObject = null;
        }
	}
}

public interface IInteractable : IValid
{
    public GameObject GameObject { get; }
	public GameObject Source { get; }

	public bool CanUse( GameObject player );
	public void OnUse( GameObject player );

	public abstract bool ShowInteractionUI { get; }
	
	public virtual string GetUseIcon()
	{
		return null;
	}

    public virtual void OnUseStart( GameObject player )
    {
        //
    }

    public virtual void OnUseStop( GameObject player )
    {
        //
    }

	public virtual void OnUseFail( GameObject player )
	{
		//
	}

    public virtual void OnUseContinuous( GameObject player )
    {
        //
    }
}