World/BlastDoor.cs
using System;
using Sandbox.MovieMaker;

namespace Causal;

public sealed class BlastDoor : Component
{
	[Property] public MovieResource OpenMovie { get; set; }
	[Property] public MovieResource CloseMovie { get; set; }
	[Property] public float ActionCooldown { get; set; } = 2f;
	[Property] public GameObject VfxObject { get; set; }
	[Property] public float VfxSeconds { get; set; } = 2f;
	[Property] public bool StartOpen { get; set; }
	[Property] public bool AllowInterrupt { get; set; } = false;
	[Property] public bool AdvanceWhileHidden { get; set; } = true;

	public bool IsOpen { get; private set; }

	public bool IsAnimating => _player.IsValid() && _player.IsPlaying;

	public bool CanClose => CloseMovie is not null && CloseMovie.IsValid();

	private MoviePlayer _player;
	private GameObject _vfx;
	private TimeSince _timeSinceAction = 99f;
	private TimeSince _timeSinceVfx = 99f;
	private bool _vfxShowing;
	private float _storedPosition;
	private bool _wasPlaying;
	private MovieResource _savedMovie;
	private float _savedPosition;
	private float _savedTimeScale = 1f;
	private bool _savedWasPlaying;
	private bool _hasSaved;
	private TimeSince _timeSinceHidden;

	protected override void OnStart()
	{
		_player = GetComponent<MoviePlayer>();

		if ( !_player.IsValid() )
		{
			Log.Warning( $"BlastDoor on '{GameObject.Name}' needs a MoviePlayer on the same GameObject." );
			return;
		}

		_player.CreateTargets = false;
		IsOpen = StartOpen;
		_timeSinceAction = 99f;
		ResolveVfx();
	}

	public void Open()
	{
		Play( OpenMovie, true );
	}

	public void Close()
	{
		Play( CloseMovie, false );
	}

	public void Toggle()
	{
		if ( IsOpen )
		{
			Close();
		}
		else
		{
			Open();
		}
	}

	protected override void OnDisabled()
	{
		if ( !_player.IsValid() )
		{
			return;
		}

		_savedMovie = _player.Resource as MovieResource;
		if ( !_savedMovie.IsValid() )
		{
			_savedMovie = IsOpen ? OpenMovie : CloseMovie;
		}

		_savedPosition = _player.PositionSeconds;
		_savedTimeScale = _player.TimeScale;
		_savedWasPlaying = _player.IsPlaying || _storedPosition > 0.05f;
		_timeSinceHidden = 0f;
		_hasSaved = true;
	}

	protected override void OnEnabled()
	{
		if ( !_hasSaved )
		{
			return;
		}

		_hasSaved = false;

		if ( !_savedWasPlaying || !_savedMovie.IsValid() || !_player.IsValid() )
		{
			return;
		}

		float end = MovieGate.MovieDurationSeconds( _savedMovie );
		float target = AdvanceWhileHidden ? _savedPosition + (float)_timeSinceHidden * _savedTimeScale : _savedPosition;
		target = end > 0f ? Math.Clamp( target, 0f, Math.Max( 0f, end - 0.05f ) ) : 0f;
		_player.Play( _savedMovie );
		_player.TimeScale = _savedTimeScale;
		_player.PositionSeconds = target;
		_player.IsPlaying = true;
		_storedPosition = target;
		_wasPlaying = true;
	}

	private void Play( MovieResource movie, bool targetState )
	{
		if ( !GameObject.IsValid() )
		{
			return;
		}

		if ( !_player.IsValid() )
		{
			Log.Warning( $"BlastDoor on '{GameObject.Name}' has no MoviePlayer." );
			return;
		}

		if ( movie is null || !movie.IsValid() )
		{
			Log.Warning( $"BlastDoor on '{GameObject.Name}' is missing its {(targetState ? "OpenMovie" : "CloseMovie")}." );
			return;
		}

		if ( IsOpen == targetState )
		{
			return;
		}

		if ( _timeSinceAction < ActionCooldown )
		{
			return;
		}

		if ( !AllowInterrupt && _player.IsPlaying )
		{
			return;
		}

		_player.Play( movie );
		_timeSinceAction = 0f;
		_storedPosition = 0f;
		IsOpen = targetState;
		if ( targetState )
		{
			ShowVfx();
		}
	}

	protected override void OnUpdate()
	{
		TickVfx();

		if ( !_player.IsValid() )
		{
			return;
		}

		if ( _player.IsPlaying )
		{
			_storedPosition = _player.PositionSeconds;
			_wasPlaying = true;
			return;
		}

		if ( !_wasPlaying )
		{
			return;
		}

		_wasPlaying = false;
		float pos = _player.PositionSeconds;
		float end = ClipEndSeconds();
		bool finished = end > 0f ? pos >= end - 0.05f : pos >= _storedPosition - 0.05f;
		if ( _storedPosition > 0.05f && !finished )
		{
			_player.PositionSeconds = _storedPosition;
			_player.IsPlaying = true;
		}
		else
		{
			_storedPosition = 0f;
		}
	}

	private float ClipEndSeconds()
	{
		var clip = _player.Clip;
		if ( clip is null )
		{
			return 0f;
		}

		return (float)clip.Duration.TotalSeconds;
	}

	private void ResolveVfx()
	{
		_vfx = VfxObject;

		if ( !_vfx.IsValid() )
		{
			Log.Warning( $"BlastDoor on '{GameObject.Name}' has no VfxObject assigned." );
			return;
		}

		_vfx.Enabled = false;
	}

	private void ShowVfx()
	{
		if ( !_vfx.IsValid() )
		{
			return;
		}

		_vfx.Enabled = true;
		_timeSinceVfx = 0f;
		_vfxShowing = true;
	}

	private void TickVfx()
	{
		if ( !_vfxShowing )
		{
			return;
		}

		if ( !_vfx.IsValid() )
		{
			_vfxShowing = false;
			return;
		}

		if ( _timeSinceVfx >= VfxSeconds )
		{
			_vfx.Enabled = false;
			_vfxShowing = false;
		}
	}
}