Weapons/SatchelChargeWeapon.cs
public sealed class SatchelChargeWeapon : BaseWeapon
{
	[Property] public GameObject SatchelCharge { get; set; }

	IEnumerable<SatchelChargeThrown> Satchels => !Owner.IsValid() ? Enumerable.Empty<SatchelChargeThrown>() : Scene.GetAllComponents<SatchelChargeThrown>().Where( x => x.Instigator == Owner.PlayerData );
	bool HasDeployedSatchels => Satchels.Any();

	public override void OnControl( Player player )
	{
		if ( TimeUntilNextShotAllowed > 0 ) return;

		// left click first deploy
		// then trigger
		if ( Input.Pressed( "Attack1" ) )
		{
			if ( HasDeployedSatchels )
			{
				Trigger();
				return;
			}

			Throw( player );
		}

		// right click always deploy
		if ( Input.Pressed( "Attack2" ) )
		{
			Throw( player );
		}
	}

	void Trigger()
	{
		TriggerExplode();

		if ( !HasAmmo() )
		{
			SwitchAway();
			GameObject.Destroy();
		}
	}

	protected override void OnEnabled()
	{
		base.OnEnabled();

		AddShootDelay( 0.5f );
	}

	public void Throw( Player player )
	{
		if ( !CanShoot() )
			return;

		if ( !TakeAmmo( 1 ) )
		{
			DryFire();
			return;
		}

		AddShootDelay( GameSettings.SatchelThrowDelay );
		ThrowSatchel();

		if ( !player.Controller.ThirdPerson && player.IsLocalPlayer )
		{
			new Sandbox.CameraNoise.Punch( new Vector3( Random.Shared.Float( -10, -15 ), Random.Shared.Float( -10, 0 ), 0 ), 1.0f, 3, 0.5f );
			new Sandbox.CameraNoise.Shake( 0.3f, 1.2f );
		}
	}

	[Rpc.Host]
	private void ThrowSatchel()
	{
		if ( !Owner.IsValid() )
			return;

		var forward = Owner.EyeTransform.Rotation.Forward;
		forward += Owner.EyeTransform.Rotation.Up * Random.Shared.Float( -1, 1 ) * 0.1f;
		forward = forward.Normal;

		var initialPos = Owner.EyeTransform.ForwardRay.Position + Vector3.Down * 16 + (forward * 16);
		var go = SatchelCharge.Clone( initialPos );

		var mine = go.GetComponent<SatchelChargeThrown>();
		Assert.True( mine.IsValid(), "SatchelChargeThrown not on sachel prefab" );
		mine.Instigator = Owner.PlayerData;

		var rb = go.GetComponent<Rigidbody>();
		var baseVelocity = Owner.Controller.Velocity;
		rb.Velocity = baseVelocity + forward * GameSettings.SatchelThrowPower + Vector3.Up * 100f;
		rb.AngularVelocity = go.WorldRotation.Right * 10f;

		go.NetworkSpawn();
	}

	[Rpc.Host]
	void TriggerExplode()
	{
		if ( !Owner.IsValid() )
			return;

		foreach ( var satchel in Satchels )
		{
			if ( satchel.IsValid() )
				satchel.Explode();
		}
	}
}