public static void ClientShowNotificationNotEnoughEnergyCharge(IProtoItem itemProto)
        {
            var playNotificationSound = true;

            if (itemProto is IProtoItemWeapon protoItemWeapon &&
                protoItemWeapon.SoundPresetWeapon.HasSound(WeaponSound.Empty))
            {
                protoItemWeapon.SoundPresetWeapon.PlaySound(WeaponSound.Empty,
                                                            volume: SoundConstants.VolumeWeapon);
                playNotificationSound = false;
            }

            if (playNotificationSound)
            {
                var itemSoundPreset = itemProto.SharedGetItemSoundPreset();
                if (itemSoundPreset.HasSound(ItemSound.CannotSelect))
                {
                    itemSoundPreset.PlaySound(ItemSound.CannotSelect);
                    playNotificationSound = false;
                }
            }

            var hasPowerBanks = ClientCalculateTotalEnergyCapacity() > 0;

            NotificationSystem.ClientShowNotification(
                string.Format(NotificationCannotUse_Title, itemProto.Name),
                hasPowerBanks
                    ? NotificationCannotUse_NotEnoughCharge
                    : NotificationCannotUse_NoPowerBanksEquipped,
                NotificationColor.Bad,
                itemProto.Icon,
                playSound: playNotificationSound);
        }
        private void ClientRemote_ItemBroke(IProtoItem protoItem)
        {
            NotificationSystem.ClientShowNotification(
                NotificationItemBroke_Title,
                string.Format(NotificationItemBroke_MessageFormat, protoItem.Name),
                color: NotificationColor.Bad,
                icon: protoItem.Icon,
                playSound: false);

            protoItem.SharedGetItemSoundPreset().PlaySound(ItemSound.Broken);
        }
        protected override void ClientInitialize(ClientInitializeData data)
        {
            base.ClientInitialize(data);

            var        character = data.GameObject;
            IProtoItem previousSelectedProtoItem   = null;
            var        currentItemIdleSoundEmitter = Client.Audio.CreateSoundEmitter(character,
                                                                                     SoundResource.NoSound,
                                                                                     isLooped: true,
                                                                                     volume: 0.5f);

            var publicState = data.PublicState;
            var clientState = data.ClientState;

            ClientCharacterEquipmentHelper.ClientRefreshEquipment(character, clientState, publicState);

            if (character.IsCurrentClientCharacter)
            {
                this.ClientInitializeCurrentCharacter(data);
                this.ClientInitializeOtherCharacter(data);
            }
            else
            {
                this.ClientInitializeOtherCharacter(data);
            }

            // subscribe on head style change
            publicState.ClientSubscribe(
                _ => _.FaceStyle,
                _ => ResetRendering(resetSkeleton: false),
                clientState);

            // subscribe on gender change
            publicState.ClientSubscribe(
                _ => _.IsMale,
                _ => ResetRendering(resetSkeleton: true),
                clientState);

            // subscribe on head equipment visibility change
            publicState.ClientSubscribe(
                _ => _.IsHeadEquipmentHiddenForSelfAndPartyMembers,
                _ => ResetRendering(resetSkeleton: false),
                clientState);

            // subscribe on vehicle change
            publicState.ClientSubscribe(
                _ => _.CurrentVehicle,
                _ =>
            {
                // reset input history for lag prediction
                CurrentCharacterInputHistory.Instance.Clear();
                // re-create physics
                this.SharedCreatePhysics(character);
                // re-select current item
                SharedForceRefreshCurrentItem(ClientCurrentCharacterHelper.Character);
                // reset rendering
                ResetRendering(resetSkeleton: false);
            },
                clientState);

            // subscribe on player character public action change
            publicState.ClientSubscribe(
                _ => _.CurrentPublicActionState,
                _ => this.ClientRefreshCurrentPublicActionState(character),
                clientState);

            this.ClientRefreshCurrentPublicActionState(character);

            publicState.ClientSubscribe(
                _ => _.SelectedItem,
                _ =>
            {
                // selected different item
                RefreshCurrentSelectedItem();
            },
                clientState);

            publicState.ClientSubscribe(
                _ => _.IsDead,
                _ =>
            {
                // re-create physics on death state change
                this.SharedCreatePhysics(character);
                ResetRendering();

                if (character.IsCurrentClientCharacter &&
                    publicState.IsDead)
                {
                    Menu.CloseAll();
                }
            },
                clientState);

            RefreshCurrentSelectedItem();

            void RefreshCurrentSelectedItem()
            {
                previousSelectedProtoItem?.SharedGetItemSoundPreset()
                .PlaySound(ItemSound.Deselect, character);

                currentItemIdleSoundEmitter.Stop();
                var currentItem     = publicState.SelectedItem;
                var itemSoundPreset = currentItem?
                                      .ProtoItem
                                      .SharedGetItemSoundPreset();

                previousSelectedProtoItem = currentItem?.ProtoItem;

                if (itemSoundPreset != null)
                {
                    itemSoundPreset.PlaySound(ItemSound.Select, character);

                    var idleSoundResource = itemSoundPreset.GetSound(ItemSound.Idle);
                    if (idleSoundResource != null)
                    {
                        currentItemIdleSoundEmitter.SoundResource = idleSoundResource;
                        currentItemIdleSoundEmitter.Delay         = 0.1;
                        currentItemIdleSoundEmitter.Play();
                    }
                }
            }

            void ResetRendering(bool resetSkeleton = false)
            {
                // reset hash to force updating equipment/skeleton in the next frame
                clientState.LastEquipmentContainerHash = null;
                if (resetSkeleton)
                {
                    clientState.CurrentProtoSkeleton = null;
                }
            }
        }
        protected override void ClientInitialize(ClientInitializeData data)
        {
            base.ClientInitialize(data);

            var        character = data.GameObject;
            IProtoItem previousSelectedProtoItem   = null;
            var        currentItemIdleSoundEmitter = Client.Audio.CreateSoundEmitter(character,
                                                                                     SoundResource.NoSound,
                                                                                     isLooped: true,
                                                                                     volume: 0.5f);

            var publicState = data.SyncPublicState;
            var clientState = data.ClientState;

            ClientCharacterEquipmentHelper.ClientRefreshEquipment(character, clientState, publicState);

            if (character.IsCurrentClientCharacter)
            {
                this.ClientInitializeCurrentCharacter(data);
            }
            else
            {
                this.ClientInitializeOtherCharacter(data);
            }

            // subscribe on head style change
            publicState.ClientSubscribe(
                _ => _.FaceStyle,
                _ => ResetRendering(resetSkeleton: true),
                clientState);

            // subscribe on gender change
            publicState.ClientSubscribe(
                _ => _.IsMale,
                _ => ResetRendering(),
                clientState);

            // subscribe on player character public action change
            publicState.ClientSubscribe(
                _ => _.CurrentPublicActionState,
                _ => this.ClientRefreshCurrentPublicActionState(character),
                clientState);

            this.ClientRefreshCurrentPublicActionState(character);

            publicState.ClientSubscribe(
                _ => _.SelectedHotbarItem,
                _ =>
            {
                // selected different item
                RefreshCurrentSelectedItem();
            },
                clientState);

            publicState.ClientSubscribe(
                _ => _.IsDead,
                _ =>
            {
                // re-create physics on death state change
                this.SharedCreatePhysics(character);
                ResetRendering();
            },
                clientState);

            RefreshCurrentSelectedItem();

            void RefreshCurrentSelectedItem()
            {
                previousSelectedProtoItem?.SharedGetItemSoundPreset()
                .PlaySound(ItemSound.Deselect, limitOnePerFrame: false);

                currentItemIdleSoundEmitter.Stop();
                var currentItem     = publicState.SelectedHotbarItem;
                var itemSoundPreset = currentItem?
                                      .ProtoItem
                                      .SharedGetItemSoundPreset();

                previousSelectedProtoItem = currentItem?.ProtoItem;

                if (itemSoundPreset == null)
                {
                    return;
                }

                itemSoundPreset.PlaySound(ItemSound.Select, limitOnePerFrame: false);
                var idleSoundResource = itemSoundPreset.GetSound(ItemSound.Idle);

                if (idleSoundResource != null)
                {
                    currentItemIdleSoundEmitter.SoundResource = idleSoundResource;
                    currentItemIdleSoundEmitter.Delay         = 0.1;
                    currentItemIdleSoundEmitter.Play();
                }
            }

            void ResetRendering(bool resetSkeleton = false)
            {
                // reset hash to force updating equipment/skeleton in the next frame
                clientState.LastEquipmentContainerHash = null;
                if (resetSkeleton)
                {
                    clientState.CurrentProtoSkeleton = null;
                }
            }
        }