private void ServerRemote_ReloadWeapon(ReloadWeaponRequest args)
        {
            var character = ServerRemoteContext.Character;

            // force re-select current item
            PlayerCharacter.SharedForceRefreshCurrentItem(character);

            var itemWeapon = args.Item;

            if (itemWeapon is null)
            {
                throw new Exception("Item not found.");
            }

            if (!(itemWeapon.ProtoItem is IProtoItemWeapon itemProto))
            {
                throw new Exception("Not a weapon: " + itemWeapon);
            }

            if (itemProto.AmmoCapacity == 0)
            {
                throw new Exception("The weapon is not reloadable: " + itemWeapon);
            }

            if (IsServer &&
                !Server.Core.IsInPrivateScope(character, itemWeapon))
            {
                throw new Exception(
                          $"{character} cannot access {itemWeapon} because it's container is not in the private scope");
            }

            if (itemWeapon.IsDestroyed ||
                itemWeapon.Count < 1)
            {
                throw new Exception($"{itemWeapon} is destroyed");
            }

            var privateState = itemWeapon.GetPrivateState <WeaponPrivateState>();

            var weaponState = PlayerCharacter.GetPrivateState(character).WeaponState;

            if (weaponState is null ||
                weaponState.ItemWeapon != itemWeapon)
            {
                throw new Exception(
                          $"Only current active weapon could be reloaded: want to reload {itemWeapon}, but current active weapon is {weaponState?.ItemWeapon}");
            }

            var selectedProtoItemAmmo = args.ProtoItemAmmo;
            var ammoCurrent           = privateState.AmmoCount;
            var ammoMax = itemProto.AmmoCapacity;

            if (weaponState.WeaponReloadingState is null &&
                ammoCurrent == ammoMax &&
                privateState.CurrentProtoItemAmmo == selectedProtoItemAmmo)
            {
                Logger.Warning("Weapon is already full, no need to reload " + itemWeapon, character);
                return;
            }

            if (weaponState.WeaponReloadingState != null &&
                weaponState.WeaponReloadingState.ProtoItemAmmo == selectedProtoItemAmmo)
            {
                Logger.Info("Weapon is already reloading this ammo, no need to reload " + itemWeapon, character);
                return;
            }

            // create reloading state on the Server-side
            var weaponReloadingState = new WeaponReloadingState(
                character,
                itemWeapon,
                itemProto,
                selectedProtoItemAmmo);

            weaponState.WeaponReloadingState = weaponReloadingState;

            Logger.Info(
                $"Weapon reloading started for {itemWeapon} reload duration: {weaponReloadingState.SecondsToReloadRemains:F2}s",
                character);

            if (weaponReloadingState.SecondsToReloadRemains == 0)
            {
                // instant-reloading weapon
                SharedProcessWeaponReload(character, weaponState, out _);
            }
            else if (IsServer)
            {
                ServerNotifyAboutReloading(character, weaponState, isFinished: false);
            }
        }
        public static void ClientTryReloadOrSwitchAmmoType(
            bool isSwitchAmmoType,
            bool sendToServer             = true,
            bool?showNotificationIfNoAmmo = null)
        {
            var character          = Api.Client.Characters.CurrentPlayerCharacter;
            var currentWeaponState = PlayerCharacter.GetPrivateState(character).WeaponState;

            var itemWeapon = currentWeaponState.ItemWeapon;

            if (itemWeapon is null)
            {
                // no active weapon to reload
                return;
            }

            var protoWeapon = (IProtoItemWeapon)itemWeapon.ProtoItem;

            if (protoWeapon.AmmoCapacity == 0)
            {
                // the item is non-reloadable
                return;
            }

            var itemPrivateState = itemWeapon.GetPrivateState <WeaponPrivateState>();
            var ammoCountNeed    = isSwitchAmmoType
                                    ? protoWeapon.AmmoCapacity
                                    : (ushort)Math.Max(0, protoWeapon.AmmoCapacity - itemPrivateState.AmmoCount);

            if (ammoCountNeed == 0)
            {
                Logger.Info("No need to reload the weapon " + itemWeapon, character);
                return;
            }

            var compatibleAmmoGroups = SharedGetCompatibleAmmoGroups(character, protoWeapon);

            if (compatibleAmmoGroups.Count == 0 &&
                !isSwitchAmmoType)
            {
                if (showNotificationIfNoAmmo.HasValue && showNotificationIfNoAmmo.Value ||
                    currentWeaponState.SharedGetInputIsFiring())
                {
                    protoWeapon.SoundPresetWeapon.PlaySound(WeaponSound.Empty,
                                                            character,
                                                            volume: SoundConstants.VolumeWeapon);
                    NotificationSystem.ClientShowNotification(
                        NotificationNoAmmo_Title,
                        NotificationNoAmmo_Message,
                        NotificationColor.Bad,
                        protoWeapon.Icon,
                        playSound: false);
                }

                if (currentWeaponState.SharedGetInputIsFiring())
                {
                    // stop firing the weapon
                    currentWeaponState.ProtoWeapon.ClientItemUseFinish(itemWeapon);
                }

                return;
            }

            IProtoItemAmmo selectedProtoItemAmmo = null;

            var currentReloadingState = currentWeaponState.WeaponReloadingState;

            if (currentReloadingState is null)
            {
                // don't have reloading state - find ammo item matching current weapon ammo type
                var currentProtoItemAmmo = itemPrivateState.CurrentProtoItemAmmo;
                if (currentProtoItemAmmo is null)
                {
                    // no ammo selected in weapon
                    selectedProtoItemAmmo = SharedFindNextAmmoGroup(protoWeapon.CompatibleAmmoProtos,
                                                                    compatibleAmmoGroups,
                                                                    currentProtoItemAmmo: null)?.Key;
                }
                else // if weapon already has ammo
                {
                    if (isSwitchAmmoType)
                    {
                        selectedProtoItemAmmo = SharedFindNextAmmoGroup(protoWeapon.CompatibleAmmoProtos,
                                                                        compatibleAmmoGroups,
                                                                        currentProtoItemAmmo)?.Key;
                        if (selectedProtoItemAmmo == currentProtoItemAmmo &&
                            itemPrivateState.AmmoCount >= protoWeapon.AmmoCapacity)
                        {
                            // this ammo type is already loaded and it's fully reloaded
                            Logger.Info("No need to reload the weapon " + itemWeapon, character);
                            return;
                        }
                    }
                    else // simple reload requested
                    {
                        // try to find ammo of the same type as already loaded into the weapon
                        var isFound = false;
                        foreach (var ammoGroup in compatibleAmmoGroups)
                        {
                            if (ammoGroup.Key == currentProtoItemAmmo)
                            {
                                isFound = true;
                                selectedProtoItemAmmo = currentProtoItemAmmo;
                                break;
                            }
                        }

                        if (!isFound)
                        {
                            // no group selected - select first
                            isSwitchAmmoType      = true;
                            sendToServer          = true;
                            selectedProtoItemAmmo = SharedFindNextAmmoGroup(protoWeapon.CompatibleAmmoProtos,
                                                                            compatibleAmmoGroups,
                                                                            currentProtoItemAmmo: null)?.Key;
                        }
                    }
                }
            }
            else
            {
                if (!isSwitchAmmoType)
                {
                    // already reloading
                    return;
                }

                // already reloading - try select another ammo type (alternate between them)
                var currentReloadingProtoItemAmmo = currentReloadingState.ProtoItemAmmo;
                selectedProtoItemAmmo = SharedFindNextAmmoGroup(protoWeapon.CompatibleAmmoProtos,
                                                                compatibleAmmoGroups,
                                                                currentReloadingProtoItemAmmo)?.Key;

                if (selectedProtoItemAmmo == currentReloadingProtoItemAmmo)
                {
                    // already reloading this ammo type
                    return;
                }
            }

            if (currentReloadingState != null &&
                currentReloadingState.ProtoItemAmmo == selectedProtoItemAmmo)
            {
                // already reloading with these ammo items
                return;
            }

            if (currentReloadingState is null &&
                selectedProtoItemAmmo is null &&
                itemPrivateState.CurrentProtoItemAmmo is null)
            {
                // already unloaded
                return;
            }

            // create reloading state on the Client-side
            var weaponReloadingState = new WeaponReloadingState(
                character,
                itemWeapon,
                protoWeapon,
                selectedProtoItemAmmo);

            currentWeaponState.WeaponReloadingState = weaponReloadingState;

            protoWeapon.SoundPresetWeapon.PlaySound(WeaponSound.Reload,
                                                    character,
                                                    SoundConstants.VolumeWeapon);
            Logger.Info(
                $"Weapon reloading started for {itemWeapon} reload duration: {weaponReloadingState.SecondsToReloadRemains:F2}s",
                character);

            if (weaponReloadingState.SecondsToReloadRemains <= 0)
            {
                // instant-reload weapon - perform local reloading
                SharedProcessWeaponReload(character, currentWeaponState, out _);
            }

            if (sendToServer || isSwitchAmmoType)
            {
                // perform reload on server
                var arg = new ReloadWeaponRequest(itemWeapon, selectedProtoItemAmmo);
                Instance.CallServer(_ => _.ServerRemote_ReloadWeapon(arg));
            }
        }