Camera/FallingCameraEffect.cs
using Sandbox.Utility;
using System.Drawing;

namespace Opium;

public record struct TimedSoundEntry
{
	[KeyProperty] public float Time { get; set; }
	[KeyProperty] public SoundEvent Sound { get; set; }
}

public partial class FallingCameraEffect : WeaponEffect
{
	public TimeUntil TimeUntilRotated { get; set; } = 1.5f;
	public TimeUntil TimeUntilHitFloor { get; set; } = 0.5f;


	[Property] public float HitFloorTime { get; set; } = 0.5f;
	[Property] public float RotateTime { get; set; } = 1.5f;

	[Property] public Curve HitFloorCurve { get; set; }
	[Property] public Curve RotationCurve { get; set; }

	[Property] public Vector3 PositionOffset { get; set; }
	[Property] public Angles Angles { get; set; }

	[Property] public CameraEffect HitFloorEffect { get; set; }
	[Property] public CameraEffect HitFirstObjectEffect { get; set; }

	[Property] public float RandSize = 1.0f;

	protected override void OnEnabled()
	{
		TimeUntilHitFloor = HitFloorTime;
		TimeUntilRotated = RotateTime;

		// Play another camera effect after (X) time
		AsyncEffect();
		AsyncFirstFall();
		AsyncDamage();

		Log.Info( "Enabled falling efx" );
	}
	
	async void AsyncFirstFall()
	{
		await GameTask.DelaySeconds( 1f );

		if ( HitFirstObjectEffect is not null )
		{
			HitFirstObjectEffect.Player = Player;
			HitFirstObjectEffect.Enabled = true;
		}

		Log.Info( "Hit first object!" );
	}

	async void AsyncDamage()
	{
		await GameTask.DelaySeconds( 0.7f );

		Player.GameObject.TakeDamage( Opium.DamageInfo.Bullet( 25, Player.GameObject, Player.GameObject ) );

		Player.ActiveWeapon.ViewModel.Components.Get<ViewModel>()?.ModelRenderer.Set( "b_falling", false );

		await GameTask.DelaySeconds( 0.3f );

		Player.GameObject.TakeDamage( Opium.DamageInfo.Bullet( 50, Player.GameObject, Player.GameObject ) );
	}

	async void AsyncEffect()
	{
		await GameTask.DelaySeconds( TimeUntilHitFloor );

		if ( HitFloorEffect is not null )
		{
			HitFloorEffect.Player = Player;
			HitFloorEffect.Enabled = true;
		}
	}

	protected override void OnDisabled()
	{
		base.OnDisabled();
		Player.EyeAngles = Player.EyeAngles.WithPitch( 0f );

		Log.Info( "Disable" );
	}

	public override bool TickEffect()
	{
		if ( Player is not null )
		{
			if ( !PositionOffset.IsNearlyZero() )
				Player.ExtraCamOffset = PositionOffset * HitFloorCurve.Evaluate( 1 - TimeUntilHitFloor.Fraction );

			var delta = TimeUntilHitFloor.Fraction;
			delta = Easing.EaseOut( delta );

			Vector3 rand = Vector3.Random;
			rand.z = 0;
			rand = rand.Normal;

			var rotation = Player.MainCamera.Transform.Rotation;
			Player.ExtraCamOffset += (rotation.Right * rand.x + rotation.Up * rand.y) * (1 - ( delta * 2 )) * RandSize;

			Player.EyeAngles = Player.EyeAngles.WithPitch( 90f );
			Player.CameraRotationOffset *= Rotation.From( Angles * RotationCurve.Evaluate( TimeUntilRotated.Fraction.Clamp( 0, 1 ) ) );
		}

		return TimeUntilHitFloor;
	}
}