```cs
using Unity.Netcode;
using UnityEngine;

namespace JebScripts.Game.Combat
{
    public class GunController : NetworkBehaviour
    {
        [Header("Basic Information")]
        public string gunName;
        
        [Header("Firing Information")]
        [Tooltip("Time between shots (seconds)")]
        [SerializeField] private float fireRate;
        [SerializeField] private float bulletSpeed;
        [SerializeField] private int maxMagAmmo;
        [SerializeField] private int maxReserveAmmo;
        [SerializeField] private FireType fireType;

        [Header("Projectiles")]
        [SerializeField] private GameObject serverBulletPrefab;
        [SerializeField] private GameObject clientBulletPrefab;
        
        [Header("Muzzle")]
        [SerializeField] private Transform muzzle;
        private Vector3 MuzzlePosition => muzzle.position;
        private Vector3 MuzzleDirection => muzzle.up;
        
        [Header("Visuals")]
        [SerializeField] private Transform visualTransform;
        
        public bool bIsFiring = false;
        private float currentFireDelay = 0f;
        
        private NetworkVariable<int> currentMagAmmo = new NetworkVariable<int>();
        private NetworkVariable<int> currentReserveAmmo = new NetworkVariable<int>();

        public override void OnNetworkSpawn()
        {
            if (IsServer)
            {
                RequestFillAmmo();
            }
            
            currentMagAmmo.OnValueChanged += CurrentMagAmmoChanged;
            currentReserveAmmo.OnValueChanged += CurrentReserveAmmoChanged;
        }
        
        public override void OnNetworkDespawn()
        {
            currentMagAmmo.OnValueChanged -= CurrentMagAmmoChanged;
            currentReserveAmmo.OnValueChanged -= CurrentReserveAmmoChanged;
        }

        private void CurrentReserveAmmoChanged(int previousvalue, int newvalue)
        {
            Debug.Log($"Reserve Ammo Ammo {newvalue}");
        }

        private void CurrentMagAmmoChanged(int previousvalue, int newvalue)
        {
            Debug.Log($"Mag Ammo {newvalue}");
        }

        // Tick Functions
        private void Update()
        {
            if (!IsOwner)
                return;

            if (currentFireDelay > 0f)
                currentFireDelay -= Time.deltaTime;
            
            fireType?.UpdateFiring(this);
        }

        // Shooting
        public void StartShooting()
        {
            fireType?.StartFiring(this);
        }
        
        public void StopShooting()
        {
            fireType?.StopFiring(this);
        }

        public bool CanFire()
        {
            return currentFireDelay <= 0f && currentMagAmmo.Value > 0;
        }
        
        public void ResetFireDelay()
        {
            currentFireDelay = fireRate;
        }
        
        public void FireProjectile()
        {
            if (!IsOwner) 
                return;

            Vector3 pos = MuzzlePosition;
            Vector3 direction = MuzzleDirection;
            
            SpawnProjectileServerRPC(pos, direction);
            SpawnDummyProjectile(pos, direction);
        }

        private void SpawnDummyProjectile(Vector3 position, Vector3 direction)
        {
            GameObject projectileInstance = Instantiate(
                clientBulletPrefab,
                position,
                Quaternion.identity);
            
            projectileInstance.transform.up = direction;

            if (!projectileInstance.TryGetComponent<ProjectilePropulsion>(out ProjectilePropulsion propulsion)) 
                return;
            float speed = bulletSpeed;
            propulsion.AddPropulsion(speed);
        }

        [ServerRpc]
        private void SpawnProjectileServerRPC(Vector3 position, Vector3 direction)
        {
            ReduceAmmo(1);
            
            GameObject projectileInstance = Instantiate(
                serverBulletPrefab,
                position,
                Quaternion.identity);
            
            projectileInstance.transform.up = direction;

            if (projectileInstance.TryGetComponent<ProjectilePropulsion>(out ProjectilePropulsion propulsion))
            {
                float speed = bulletSpeed;
                propulsion.AddPropulsion(speed);
            }
            
            SpawnDummyProjectileClientRPC(position, direction);
        }
        
        [ClientRpc]
        private void SpawnDummyProjectileClientRPC(Vector3 position, Vector3 direction)
        {
            if(IsOwner)
                return;
            
            SpawnDummyProjectile(position, direction);
        }
        
        // Ammo
        public void Reload()
        {
            RequestReloadMag();
        }

        public void RequestFillAmmo()
        {
            if (IsServer)
            {
                FillAmmo();
                return;
            }
            FillAmmoServerRPC();
        }

        [ServerRpc]
        private void FillAmmoServerRPC()
        {
            FillAmmo();
        }

        private void FillAmmo()
        {
            currentMagAmmo.Value = maxMagAmmo;
            currentReserveAmmo.Value = maxReserveAmmo;
        }

        private void RequestReloadMag()
        {
            if (IsServer)
            {
                ReloadMag();
                return;
            }
            ReloadMagServerRPC();
        }

        [ServerRpc]
        private void ReloadMagServerRPC()
        {
            ReloadMag();
        }

        private void ReloadMag()
        {
            int bulletsNeeded = maxMagAmmo - currentMagAmmo.Value;
            if (bulletsNeeded <= 0)
                return;
            
            int bulletsAvailable = Mathf.Min(bulletsNeeded, currentReserveAmmo.Value);
            if (bulletsAvailable <= 0)
                return;
            
            currentReserveAmmo.Value -= bulletsAvailable;
            currentMagAmmo.Value += bulletsAvailable;
        }
        
        private void ReduceAmmo(int ammo)
        {
            // Only Called on server
            currentMagAmmo.Value = Mathf.Clamp(
                currentMagAmmo.Value - 1,
                0,
                maxMagAmmo
            );
        }
        
        // Visuals

        public void ShowVisuals(bool show)
        {
            visualTransform.gameObject.SetActive(show);
        }
    }
}


```