data/templates/NetworkedWeapon.cs
using Sandbox;

public sealed class NetworkedWeapon : Component
{
    [Property] public string WeaponName { get; set; } = "Pistol";
    [Property] public float Damage { get; set; } = 25f;
    [Property] public float FireRate { get; set; } = 0.15f;
    [Property] public SoundEvent FireSound { get; set; }
    [Property] public ParticleSystem MuzzleFlash { get; set; }

    [Sync] public int Ammo { get; set; } = 12;
    [Sync] public int MaxAmmo { get; set; } = 12;

    private TimeSince timeSinceLastShot;

    protected override void OnUpdate()
    {
        if ( IsProxy ) return;

        if ( Input.Down( "attack1" ) && timeSinceLastShot >= FireRate && Ammo > 0 )
        {
            timeSinceLastShot = 0;
            Shoot();
        }
    }

    private void Shoot()
    {
        Ammo--;

        var camera = Scene.Camera;
        var start = camera.WorldPosition;
        var end = start + camera.WorldRotation.Forward * 5000f;

        var trace = Scene.Trace.Ray( start, end )
            .IgnoreGameObjectHierarchy( GameObject )
            .Run();

        BroadcastShootEffects( start, trace.EndPosition );

        if ( trace.Hit && trace.GameObject.IsValid() )
        {
            var damageInfo = new DamageInfo( Damage, GameObject, GameObject, trace.Hitbox );
            trace.GameObject.TakeDamage( damageInfo );
        }
    }

    [Rpc.Broadcast]
    private void BroadcastShootEffects( Vector3 origin, Vector3 hitPos )
    {
        if ( FireSound != null )
            Sound.Play( FireSound, origin );

        if ( MuzzleFlash != null )
            Particles.Create( MuzzleFlash, origin );
    }
}